函数与数据内存

本文详细探讨了C语言中函数的各个方面,包括库函数、函数定义、调用与返回、函数参数传递、函数声明、函数的嵌套与递归调用、数组作为函数参数以及变量的作用域。此外,还讲解了主函数参数、局部变量、全局变量、静态存储和动态存储的区别,以及静态函数的概念。内容涵盖了函数在内存中的工作原理和如何有效地使用函数进行程序设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数与数据内存

函数时C语言实现模块化设计的方式,它是C程序中最小的功能单位。在一个C程序的实现过程中,通过使用函数,可以把较大、较复杂的程序任务分解成若干较小、较单一的独立任务

函数的优点:

  1. 降低了程序设计的复杂度,单个函数的功能更简单、独立,容易编程实现
  2. 降低了程序员开发程序的工作量,编程人员可以在已有函数的基础上构造新的程序,而不需要从头做起,避免重复编写
  3. 对于一些具体操作的细节,可以通过函数隐藏,是整个程序结果更清晰

一个完整C程序的函数可以分为两类:

  • 由用户自己定义并编写功能代码的函数,称为用户自定义函数
  • 由C系统标准库提供的标准库函数(简称库函数),使用其功能时,用户只需要遵循其调用格式进行调用就可以了

1. 库函数

注:在调用库函数时,需要在源文件中加入预处理命令#include <库函数名>

2. 函数定义

1. 函数定义的格式

函数返回值的类型名 函数名(类型名 形式参数1,类型名 形式参数2···)

函数定义的注意点:

  1. 函数名、圆括号中的参数、函数体内的变量它们都是由用户自定义的标识符,应当符合C语言语法中标识符的命名规则
  2. 同一个C程序中,不允许出现同名函数
  3. 同一个函数体范围内,不允许定义同名变量(定义在复合语句中的变量除外)
  4. C语言规定,函数不可以嵌套定义,即不能在一个函数的函数体中去定义另一个函数,每一个函数都是一个独立的函数模块
  5. 函数的返回值类型若为int或char,通常类型关键字可以缺省不写
  6. 函数必须先定义(先声明),后调用
  7. 若函数功能仅仅是完成了一些操作,没有返回值,则函数首部的函数返回值类型名应定义为void
  8. C程序允许定义空函数。空函数没有返回值,圆括号中没有参数,函数体也是空的,编译时不产生任何指令,执行时若调用空函数则直接返回,不执行任何功能,它的存在就像是在程序开发时留下的"功能扩展槽"
2. 有参函数与无参函数

函数首部圆括号内的参数称为形式参数,简称形参。一个函数可以没有形参,也可以有一个或多个形参,这是根据函数本身所要实现的功能需要而设定的

通常,有参函数调用时,首先需要通过其形式参数得到具体的值,然后才执行该函数

而对于无参函数,函数没有必要必须提前得到一个值才能执行

函数有多个形式参数时,必须逐个说明每一个形式参数的数据类型,无论它们的数据类型一致还是不一致,这与变量定义的格式不一样

int Exp(int a, int b)
{
    int x,y;
    ···
}

其中,a,b是形参,而x,y是函数Exp函数的局部变量。虽然a,b的数据类型一样,但函数定义格式要求它们必须分别说明自己的数据类型;对于局部变量x,y,则不需要如此

3. 函数的调用与返回

函数都是通过调用得以执行其功能的。

对于一个C程序来说,主函数main()是由系统调用执行的,而其他函数则是由主函数调用的。把调用其他函数的函数称为主调函数,被主调函数调用的函数称为被调函数。

程序的一次执行过程:程序开始执行时,系统总是会先找到程序中的main()函数,从main()函数开始顺序执行其函数体的语句,在执行的过程中遇到了对其他函数的调用时,再转入其他函数开始执行,直至返回到main()函数,最终待main()函数执行结束后,返回调用它的系统,程序的一次执行过程结束

1. 函数调用形式

函数调用的一般形式:

被调函数名 (实际参数1, 实际参数2···)

实际参数可以是C语言的任意合法值,可以是常量、变量、表达式的值或者某个函数调用的返回值。当实际参数个数多于一个时,参数之间用逗号","隔开。实参与形参的个数应相等,类型应一致(或赋值兼容);按参数定义的顺序实参与形参一一对应

如果被调函数是无参函数,则实际参数列表可以没有,但括号不能省略

函数通常由两种调用方式:

  1. 作为函数表达式的一部分或某个函数的参数调用(由返回值函数调用形式)

当被调函数是用于求某个值或者得到一个结果时,函数的调用可作为表达式的一部分或者另一个函数调用的参数出现在程序中。主调函数并不一定必须使用被调函数的返回值。如果程序需要使用被调函数的返回值做运算或处理时,该被调函数必须是一个由返回值的函数

  1. 函数调用语句(无返回值函数调用形式)

如果被调函数的返回值类型为void,则表示该函数调用执行后不会返回一个值或结果

2. 函数调用时的参数调用
1. 函数调用时实际参数与形式参数的关系

有参函数被调用时,主调函数与被调函数之间通过实参和形参传递数据。函数发生调用时,把实参的值赋值传递给相对应的形参变量,从而使得形参在被调函数执行之前,便获得了确定的值,之后在被调函数执行的过程中,就可以通过形参变量对这些传递过来的数据进行运算和处理。因此,实参与形参应个数相等,类型一致,一一对应

2. 值传递方式

实参可以是常量、变量、表达式的值或者某个函数调用的返回值,无论实参是什么形式,当发生函数调用时,实参是一个确定的值,它以赋值的方式将这个值存入形参变量。传递之后,实参与形参持有相同的值。但是,如果在被调函数执行的过程中,改变了形参变量的值,对实参的值是没有任何影响的,因为从本质上讲,形参与实参是两组不同的值,参数传递只是使得它们的值一样,当被调函数改变了形参的值时,其实与实参是无关的

说明:

  1. 在函数没有被调用之前,系统不给形参分配存储单元,形参并不占内存中的存储单元,只有函数被调用时系统才会给形参变量分配存储单元,调用结束后,形参所占用的存储单元都被自动释放
  2. 当执行函数调用时,实参有确定的数据。实参可以是常量、变量或表达式。但要求实参必须有确定的值,以便在函数调用时将实参的值赋给形参
  3. 在定义函数时,必须指定形参的类型
  4. 实参和形参的类型应相同或赋值兼容,个数应相同,位置应一一对应。如果实参和形参的类型不同,则按照不同类型数据的赋值规则进行转换
  5. 实参变量对形参变量的数据传递时单向的值传递,即由实参传递给形参,而不能由形参传回了给实参。在内存中,实参单元与形参单元占用不同的存储空间
  6. 只要实参与形参类型、个数、位置一一对应,若被调函数中没有使用某个形参,该形参虽然存在,但可以不定义形参名称
  7. 实参若为变量,则其变量名与形参变量名可以不同,也可以相同
3. 函数返回与返回值

调用一个函数的目的是通过执行这个函数来使用它的特定功能,功能实现后程序的控制回到主调函数,这称为函数返回,函数返回后程序继续在主调函数中执行下去。有些被调函数执行后会给主调函数反馈一个执行结果,而有些则不会。被调函数反馈的执行结果称为函数的返回值

两种情况下函数结束执行并返回:

  • 函数执行时与return语句
  • 函数执行完毕遇函数体的右花括号"}"
1. return语句

​ return语句的基本功能是结束被调函数的执行并返回主调函数。return函数的三种格式:

  • return 表达式;
  • return(表达式);
  • return;

函数如果有返回值则必须通过return语句获得

一般情况下,函数返回值的类型应和函数首部定义的返回值类型应保持一致,当不一致时,系统不管return语句的返回值是什么数据类型,都会按照函数首部定义的类型进行自动类型转换,并将转换类型后的返回值返回给主调函数

无论函数有没有return语句,函数执行结束后遇到函数体的右花括号"}",都会返回主调函数,至于函数有没有返回值,则不取决于return语句,而是由函数的返回类型是否为void决定的

2. 函数无返回值(void)

注:为使程序的代码规范与结构清晰,建议无论函数有没有返回值,都用return语句返回

4. 被调函数的声明和函数原型

1. 函数声明的意义

C语言中,除了main()函数,所有的用户自定义函数都必须先定义,后使用

对于返回值为其他类型的函数,若把函数的定义放在调用之后,应在调用之前对函数进行声明,称为函数原型说明

2. 函数声明格式和函数原型

函数声明的位置可以放在所有函数外边,也可以放在某个函数的函数体内,可以是一条独立的函数声明语句,也可以与普通变量一起出现在同一个类型定义语句中

3. 函数声明的位置

被调函数声明一定要在被调函数调用之前

5. 函数的嵌套调用与递归调用

1. 函数的嵌套调用

在一个北电函数执行的过程中,又调用了另一个函数,这样的调用方式被称为函数的嵌套调用。C语言规定函数不能嵌套定义,但允许嵌套调用

2. 函数的递归调用

函数的递归调用属于函数嵌套调用的一种特殊情况。简单来说,函数的递归调用就是函数嵌套调用自身

适合用递归算法解决问题的三个条件:

  1. 待解决问题可以转化为一个更简单的问题,而该问题的解决方法与原来问题的解决方法相同,只是所处理的对象有规律的递增或递减
  2. 可以应用这个转化过程,使问题得到解决
  3. 必定要有一个明确的结束递归的条件

6. 数组做函数参数

1. 数组元素做函数参数(值传递)

当调用函数时,数组元素可以作为实参传递给形参,实际上数组的每个元素就是一个确定类型的变量,只不过相同类型的变量通过一个数组组织在一起,一起申请空间,一起使用,用数组名作为集合名称,用来一次性处理相同类型的批量数据

2. 数组名做函数参数(地址传递)

数组名也可以作为实参传递,因为实参与形参类型必须一致,所以此时形参也应定义成相同类型的数组形式(也可以定义为指针变量)。数组的本质是一个地址常量,对应的形参就应当是一个存放地址的变量(或指针变量),因此,所谓的形参数组,其本质是一个地址变量,其接收到的数据是函数调用时传递过来的实参数组的首地址

说明:

  1. 用数组名做函数参数,应当在主调函数和被调函数中分别定义数组
  2. 实参数组与形参数组类型应一致,如果不一致,结果将出错
  3. 在被调函数中声明了形参数组的大小,但实际上指定形参数组大小是不起任何作用的,因为C编译时对形参数组大小不做检查,只是将实参数组的首地址传给形参数组
  4. 形参数组可以不指定大小,定义数组时在数组名后面跟一个空的方括号,如果需要在被调函数中用循环逐个处理数组元素,可以另设一个参数,传递数组元素的个数
  5. 用数组名做函数实参时,不是把数组的值传递给形参,而是把实参数组的起始地址传递给形参数组,这样两个数组就共占同一段内存单元

7. main函数的参数

main()函数通常可用两个参数:

main(int argc,char * argv)
{
    ···
}

其中,argc和argv是两个参数名,可以由用户自己命名,但是它们的类型却是固定的。第一个参数argc必须是整型;第二个参数argv是一个指向字符型的指针数组的指针,这个字符型指针数组的每个指针都指向一个字符串,因此第二个参数还可以直接定义成基类型为字符型的指针数组

8. 变量的作用域

在C语言中,由用户命名的标识符都有一个有效的作用域.所谓标识符的"作用域",就是指程序中的某一部分,在这一部分中,该标识符是有定义的,可以被C编译和连接程序所识别

1. 局部变量和全局变量的定义及其作用域

在C语言中,将变量的有效作用范围或者变量的可见性称为变量的作用域

在一个函数内部定义的变量只在本函数范围内有效,此变量称为内部变量或局部变量,而在函数之外定义的变量称为外部变量或全局变量

1. 局部变量

局部变量是指在一定的范围内有效的变量

  1. 在函数体内部定义的变量.在本函数范围内有效作用域局限于该函数体内
  2. 在复合语句内定义的变量.在本复合语句范围内有效,作用域局限于该复合语句内
  3. 有参函数的形参也是局部变量.只在其所在的函数范围内有效
2. 全局变量

在函数外定义的变量称为全局变量.全局变量的作用域是从定义全局变量的位置起到本源程序结束为止

说明:

全局变量可以和局部变量同名,当局部变量有效时,同名的全局变量不起作用

2. 全局变量声明
1. 同一编译单位内,用说明符扩展全局变量的作用域

当全局变量定义在后,引用它的函数在前时,应该在引用它的函数中用extern对此全局变量进行说明,以便通知编译程序:该变量是一个已在外部定义的全局变量,已经分配了存储单元,不需要再为它另外开辟存储单元,这时其作用域从extern说明处起,延伸到该函数末尾

extern 数据类型 全局变量名

注意:

  1. 声明语句中的全局变量是一个已经定义的全局变量

  2. 全局变量的说明与全局变量的定义不同

变量的定义(开辟存储单元)只能出现一次,在定义全局变量时,不可使用extern说明符,而对全局变量的说明,则可以多次出现在需要的地方,这时必须使用extern说明符

2. 不同编译单位内,用说明符扩展全局变量的作用域

在实际应用中,一个C程序通常由许多函数组成,这些函数可以分别存放在不同的源文件中,每个源文件可以单独进行编译,进行语法检查,若无错误即可生成目标文件,然后可用系统提供的连接程序,把多个目标文件连接成一个可执行程序,通常人们把每个可进行单独编译的源文件称为编译单位

重复定义的解决方法:

在其中一个文件中定义所有全局变量,而在其他用到这些全局变量的文件中用extern说明符对这些变量进行声明,声明这些变量已在其他编译单位中定义.

3. 静态存储与动态存储

在C语言中,内存中供用户使用的存储空间可以分为:程序代码区、静态存储区和动态存储区

在程序执行过程中,程序代码存放在代码区,数据分别放在静态存储区和动态存储区

1. 动态存储区存放的数据
  1. 函数形式参数.在调用函数时给形参分配存储空间
  2. 自动局部函数
  3. 函数调用时的现场保护和返回地址

在函数调用开始时分配动态存储空间,函数结束时释放这些空间.在程序执行过程中,这种分配和释放是动态的,如果一个程序中两次调用同一个函数,分配给此函数中局部变量的存储空间地址可能是不相同的.如果一个程序包含若干个函数,每个函数中局部变量的生存期并不等于整个程序的执行周期,它只是程序执行周期的一部分.根据函数调用的需要,动态地分配和释放存储空间

2. 静态存储区存放的数据
  1. 全局变量
  2. 静态局部变量
  3. 字符串常量

在程序开始执行时给全局变量分配存储区,在整个程序执行完毕后才释放.在程序执行过程中它们占据固定的存储单元,而不是东涛的进行分配和释放

4. 变量的存储类型及生存期

在C语言中,每一个变量都有两个属性:

  • 数据类型
  • 数据的存储类型

数据的存储类型是指数据在内存中存储的方式.

存储方式由两大类:

  • 静态存储类
  • 动态存储类

局部变量有三种存储类型:

  • auto:自动类型
  • static:静态类型
  • register:寄存器类型

全局变量的存储类型只有静态类型

局部变量的存储类型缺省时,默认为auto(自动类型)

全局变量的存储类型缺省时,默认为static(静态类型)

1. 局部变量的存储类型及生存期
1. 自动局部变量(auto变量)

当在函数内部或复合语句内定义变量时,如不专门声明变量的存储类型,或使用了auto存储类型说明符,系统就认为所定义的变量具有自动类别,当调用此函数时系统会给这些变量在内存的动态存储区分配存储空间;并且当函数调用结束时自动释放这些存储空间.因此这类变量称为自动变量.所有局部变量(除静态局部变量以外)的生存期为从变量定义完成到函数调用结束.自动变量用关键字auto做存储类别的声明

2. 寄存器局部变量(register变量)

用register定义的变量,建议编译程序将变量的值保留在CPU的寄存器中,而不是像一般变量那样占内存单元.程序运行时,访问存于寄存器内的值,要比访问存于内存中的值快的多,当程序对运行速度有较高要求时,对于一些使用频繁的少数变量,C语言允许将局部变量的值存放在CPU的寄存器中,需要用时直接从寄存器取出参加运算,不必再到内存中去存取.这种变量为寄存器变量,用关键字register做声明.因为寄存器变量不是保存在内存中的,它没有地址,因此不能进行取地址等运算

说明:

  1. 只有局部自动变量和形式参数可以作为寄存器变量
  2. 由于寄存器数量有限,并且寄存器空间很小,所以只有char型或int型变量可以声明为寄存器变量
  3. 局部静态变量不能定义为寄存器变量
3. 静态局部变量(static变量)

当在函数体(或复合语句)内使用static关键字来定义一个变量时,称该变量为静态局部变量(不能简称为静态变量,因为还有静态全局变量).静态局部变量在整个程序运行期间在内存的静态存储区中占据着永久性的存储单元.即使退出函数后,下次再进入该函数时,静态局部变量仍使用原来的存储单元.由于并不释放这些存储单元,因此这些存储单元中的值得以保留,从而可以继续使用存储单元中原来的值.由此可见,静态局部变量的生存期一致延长到程序运行结束

在定义局部变量时不赋初值,则对静态局部变量来时,编译时数值型变量自动赋初值0,字符型变量自动赋初值’\0’;而对自动变量来说,如果不赋初值则它的值是一个随机值

2. 全局变量的存储类型及生存期

全局变量的数据空间是在内存静态存储区分配的,具备静态特性.通常,全局变量定义时并不特别说明其存储特性为static,这使得该全局变量能够被其作用域范围内的所有函数或者有extern扩展声明的文件访问并修改其值.不过,如果全局变量一旦显示的声明其存储类型为static,则该全局变量称为静态全局变量

1. 静态全局变量

当用关键字static定义全局变量时,该变量称为静态全局变量.静态全局变量只限于当前源文件中使用,不能被其他源文件中的函数引用

2. 用extern声明外部变量

当使用extern关键字说明全局变量(该变量不能定义为静态全局变量)时,可以扩展外部变量的作用域

  1. 在同一源文件内用extern来扩展全局变量的作用域.如果外部变量不在文件的开头定义,其作用域范围只限于定义处到文件末尾.如果定义点之前的函数想引用该外部变量,则应该在引用之前用关键字extern对该变量做外部变量声明,表示该变量是一个已经定义的外部变量.有了此声明,就可以从声明处起,合法的使用该外部变量
  2. 在不同源文件内使用extern关键字来扩展全局变量的作用域.一个C程序总是由许多函数组成,这些函数可以分别放在不同的源文件中.每个源文件进行单独编译.这些可以单独编译的源文件称为"编译单位".每一个程序由多个编译单位构成,并且在每个文件中均需要引用同一个全局变量时,为了防止变量名重复定义,应在其中一个文件中定义所有的全局变量,而其他用到这些全局变量的文件中用extern对这些变量进行说明,表示这些变量已在其他编译单位中定义
5. 局部变量和全局变量的区别

局部变量和全局变量的区别
比较类别局部变量全局变量
存储类型autoregisterstaticstaticextern
生存周期所在函数被调用的时间整个程序运行的时间
作用域所在函数内部或所在复合语句所在源文件中由extern说明的所有文件
未初始化随机数0值
初始化每次所在函数被调用时编译时初始化,程序运行期间不再初始化

9. 静态函数

所有函数都是外部函数,因为不允许在函数内部定义另一个函数.但是当定义函数时,可以使用extern或static说明符,以区别一个函数是否允许其他文件中的函数对其进行调用

1. 用extern声明的函数

形式:

extern 函数返回值类型 函数名(形参列表)

extern可以缺省.外部函数除了可被本源文件中的其他函数调用之外,也可被其他源文件中的函数调用,当函数调用语句与被调用函数不在同一个源文件中,且函数的返回值为非整型时,应该在调用语句所在函数的声明部分对调用的函数进行函数声明.所有的标准库函数都是外部函数

2. 用static声明的静态函数

次所在函数被调用时
编译时初始化,程序运行期间不再初始化

9. 静态函数

所有函数都是外部函数,因为不允许在函数内部定义另一个函数.但是当定义函数时,可以使用extern或static说明符,以区别一个函数是否允许其他文件中的函数对其进行调用

1. 用extern声明的函数

形式:

extern 函数返回值类型 函数名(形参列表)

extern可以缺省.外部函数除了可被本源文件中的其他函数调用之外,也可被其他源文件中的函数调用,当函数调用语句与被调用函数不在同一个源文件中,且函数的返回值为非整型时,应该在调用语句所在函数的声明部分对调用的函数进行函数声明.所有的标准库函数都是外部函数

2. 用static声明的静态函数

如果在一个源文件中定义的函数,只能被本源文件中的其他函数调用,而不能被同一程序中其他源文件中的函数调用,则这种函数称为静态函数.静态函数的作用域限于定义它的源文件内部.静态函数的定义需要在函数类型标识符前加static关键字,且不能省略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值