第七章 函数
C 语言程序的基本组成单位是函数。
利用 C 语言中的函数来实现这些功能模块,整个程序由多个函数和一个主函数组成,函数之间通过调用,执行各个函数的代码,从而实现整个程序的整体功能。
7.1 函数的概念
一个稍复杂一些的 C 程序通常由一个主函数和多个子函数组成,每个函数完成某种操作或运算功能。函数之间通过调用,执行各个函数中的程序。
main ()函数是程序执行的开始点,子函数中的代码只有被调用时才会执行。
一般由 main ()函数调用其他子函数,子函数还可以调用其他子函数,但是子函数不能调用 main ()函数。
调用其他函数的函数,称为主调函数,被调用的函数称为被调函数。
C 函数分为标准库函数和用户自定义函数两种。
7.1.1 标准库函数
标准库函数是 C 系统定义好的函数,存放在标准函数库中,可以供用户直接使用。
使用库函数时,要用预编译命令# include ,将有关的头文件包含到用户的源程序文件中,头文件中包含了与所调用的库函数相关的信息。
1.输入输出函数
输入输出函数: printf ()、 scanf ()、 putchar ()和 getchar ()等。
调用输入输出库函数时,需要包含的头文件为 stdio . h ,即应在源程序文件中使用编译预处理命令# include < stdio . h >,将该头文件包含进来。
2.数学函数
调用数学函数时,要包含头文件 math . h ,即使用编译预处理命令# include < math . h >.
常用的数学库函数如下。
abs ( int x ):功能为求整数 x 的绝对值。
fabs ( double x ):功能为求实数 x 的绝对值。
sqrt ( double x ):功能为求 x 的平方根(要求 x ≥0)。
pow ( double x , double y ):功能为求 x 。
sin ( double x ):功能为计算 x 的正弦值,要求角度 x 以弧度表示。
cos ( double x ):功能为计算 x 的余弦值,要求角度 x 以弧度表示。
3.字符和字符串处理函数
调用字符串处理函数时,要包含头文件 string . h ;而调用字符处理函数时,要包含头文件 ctype . h 。
常用的字符串函数如下。
strcpy (x1,x2):功能为将字符串x2复制到x1中。
strlen ( x ):功能为统计字符串 x 中字符的个数(不包括\0)。
strcat (x1,x2):功能为将字符串x2连接到x1的后边,结果在x1中。
stromp :比较两个字符串的大小
4.动态存储分配函数
调用动态存储分配函数时,要包含头文件 stdlib . h 。
常用的函数如下。
calloc ( n , size ):功能是分配 n 个 size 大小的内存连续空间。
malloc ():功能是分配 size 字节的存储区。
free ():功能是释放动态分配的内存区。
7.1.2 用户自定义函数
用户自定义函数是用户为解决自己的专门问题而编写的。在程序开发中,常将一些经常用到的功能模块编写成函数,或放在公共函数库中供大家使用,从而尽量减少重复书写相同的程序段。
7.2 函数的定义
函数定义的一般格式如下:
类型名函数名(形参列表) /*函数的头部*/
{ 定义变量
语句序列
}/*函数体*/
说明如下:
(1)格式中第一行为函数的首部,其中函数名由用户自己定义,要符合 C 标识符的命名规则。
(2)形式参数(简称形参),是函数被调用时用来接受传来的实际参数的。形参列表中的每个形参都要声明其类型。
(3)根据是否有形参,可以将函数分为有参函数和无参函数。有参函数的形参可以有一个或多个。若有两个以上的形参,则各参数之间要以逗号分隔。若没有形参,则称该函数为无参函数。
(4)函数头部下边的()部分,称为函数体,是函数要执行的程序,用于实现函数的功能。
(5)函数名前面的类型名是指函数值的数据类型。函数值的类型名若省略,则默认函数值为int型。
(6)函数的返回值。函数一般有返回值。函数返回值由return语句得到,并传递给主调函数。
返回语句的格式为:
return(表达式);
或
return 表达式;
return后面的值可以是常量、变量,有没有括号都可。
return后面的值也可以是个表达式。
函数首部的类型定义与 return 后边表达式的类型要一致。
当函数的返回值与定义的函数值类型不一致时,就以函数值类型为准,对数值型数据可以自动进行类型转换。
如果函数不带回值,只完成某种操作,则函数结尾可以用不带表达式的 return 语句。或省略 return 语句。
7.3 函数的调用
7.3.1 调用函数
调用函数的一般格式:
函数名(实参列表)
说明如下:
(1)当调用无形参函数时,不应有实参。
(2)若有多个实参,要用逗号分隔。且实参要与形参类型匹配,一一对应。
(3)实参可以是常量、变量或表达式。
一般函数的调用有以下几种方式。
(1)函数语句。如 pstar ();,这种方式适用于无返回值( void )函数的调用。
(2)函数表达式。如 v = funv ( a , b , c )*10+2;,这种方式适用于有返回值的函数调用。
(3)函数值作为参数。如 printf (" V =8d\ n ", funv (1,3,9));/*运行结果输出 V =27*/该语句执行结果是输出函数值 funv (1,3,9)。这种方式适用于有函数返回值的调用。
7.3.2 声明函数
在 C 语言中,变量需要先声明后使用,与此相似,如果要调用自定义的函数,一般也要在主调函数中先对被调函数进行声明,然后调用该函数。
在声明函数时也可以不写形参变量名,只写形参类型。例如: float funv ( float , float , float );称为函数原型。
如果自定义函数放在主调函数之前,则在主调函数之中可以省略函数声明语句。也可以在所有函数之前声明函数原型,则在主调函数中就不必再声明函数。
7.4 函数的参数
传递定义函数时所带的参数称为形式参数(简称形参),调用函数时所带的参数称为实队参数(简称实参)。当调用函数时,实参的值要传递给对应的形参,传递方向是单向的。只能是实参向形参传递。
实参与形参的传递方式有两种:传值方式和传地址方式。
7.4.1 传值方式
传值方式有以下特点:
(1)实参和形参各占有不同的存储单元。所以在被调函数中若改变了形参的值,并不会影响实参的值。实参与形参允许同名,不会混淆。
(2)实参的值传递给形参,称为值传递,且是单向传递,即只能由实参传值给形参。
(3)形参和实参数目要相同、类型要匹配。
(4)实参的求值顺序是自右到左。
由于形参变量与实参变量各有自己的存储单元,故形参值的交换不会影响到实参的值,
7.4.2 传地址方式
传地址方式是指实参传递给形参的是变量的地址,即指针。
由于数组名代表数组的首地址,所以这里以数组为例,介绍传地址方式的参数传递的特点。调用函数时,若以数组名作为函数的实参,则要求形参也应是数组名(或指针变量)。由于传递的是数组的首地址,则实参和形参都是实参数组的首地址,即实参和形参指同一个数组。因此在函数中若改变了形参数组的值,实际上也等于改变了实参数组的值。
将数组名作为参数时,实参数组与形参数组类型要一致。
多维数组作为函数参数,形参数组定义时第一维下标可以省略。
7.5 函数的嵌套调用与递归调用
7.5.1 函数的嵌套调用
函数的嵌套调用,即在被调用的函数中又调用其他函数。
在 C 语言中,函数的定义是平行的、独立的,即一个函数内部不能包含另一个函数的定义。所以函数的定义不允许嵌套定义,但函数的调用是允许嵌套的。
7.5.2 函数的递归调用
1.函数递归调用的概念
当一个函数在执行的过程中,出现直接或间接调用该函数本身,称为函数的递归调用。
在函数中直接调用该函数本身的形式,称为直接递归调用。直
如果是通过调用另外一个函数来调用本函数,则称为间接递归调用。
递归过程分为回推和递推两个阶段。
函数的递归调用可以理解为嵌套调用的特殊形式,只不过是函数自己调用自己。在理解函数递归调用时,可以认为,每当递归调用一次函数本身,就相当于产生了该函数的一个副本,直到最后一次递归调用结束,程序的流程返回二次函数调用点处,依次类推,直到返回到最初一次的函数调用点处。
2.递归调用的条件
定义递归函数时,应考虑以下条件。
(1)必须有完成函数任务的语句。
(2)有一个测试条件,决定是否继续递归调用。
(3)每次递归调用函数的实参应逐渐逼近测试条件,才能最终结束递归调用。
(4)在递归函数定义中,要先测试条件,后递归调用。即递归是有条件的,只有在一定条件下才可以递归调用。
7.6 变量的作用域
C 程序是由 main ()函数和若干函数组成的,在不同的地方定义的变量,其作用域(有效范围)是不同的。从变量作用域的角度看,可将变量分为局部变量和全局变量。
7.6.1 局部变量
在函数内部定义的变量,称为局部变量。以前介绍过的各种类型的变量大多是在函数内定义的变量,所以都是局部变量。
局部变量的有效范围(作用域)只在本函数内。在某复合语句的()内定义的局部变量,只在本{ }内有效。离开有效范围,变量的值将丢失。所以不同的函数,可以有同名的局部变量,它们各有自己的作用域,互不干扰。
局部变量的生存期,也就是变量的存在时间,是指变量被分配存储单元开始到变量的存储单元被释放,即变量消失这段时间。
只有当程序执行函数时,才给函数内的局部变量分配存储单元,当执行完函数体代码,离开本函数时,这些局部变量所占的存储单元就被释放,即变量不存在了。
函数的形参也属于局部变量。当程序调用函数时,才给函数的形参分配存储单元,程序执行完函数体代码离开本函数时,形参变量因存储单元被释放,而使形参的值丢失。
7.6.2 全局变量
在函数之外定义的变量称为全局变量。全局变量可以为各函数所共用。全局变量的作用域是从定义的位置开始,到本文件的结束。
可见因为形参和实参都是局部变量,作用域仅限于各自的函数体,即使形参和实参同名,二者也互不干扰。所以形参的交换,并没有影响实参。
在 C 语言中,可以通过 return 语句,将被调函数的值,带回主调函数,但只能返回一个值,而通过定义全局变量的方法,可以在函数之间传递多个值。
C 语言规定如果在同一程序中,全局变量与局部变量同名,则在二者的作用域重叠的区间,局部变量有效,全局变量不起作用。
全局变量在整个程序的执行过程中,都占用存储单元,其中的值不丢失。利用全局变量可以实现各函数之间的参数传递。
除非十分必要,一般不提倡使用全局变量,其原因有以下几点:
(1)由于全局变量属于程序中的所有函数,因此在程序执行过程中,一直占用存储空间,即使正在执行的函数根本不用这些全局变量,全局变量也要占用存储空间。
(2)在某函数中若用到了全局变量,则所有调用该函数的主调函数都要使用这些全局变量,从而降低了函数的通用性。
(3)函数中使用了全局变量,各函数之间的相互影响较大,从而使函数的独立性变差。如果在某函数中改变了全局变量,则所有使用该全局变量的函数都会受到影响。
(4)在函数中使用了全局变量后,会降低程序的清晰性,使程序的可读性变差。
7.7变量的存储属性
7.7.1 变量的存储类型
一般来说,计算机内存中供用户使用的存储空间分为程序区、静态存储区和动态存储区三部分。其中程序区是用来存放程序的,静态存储区和动态存储区是用来存放数据的。
静态存储区是在程序开始执行时就分配的固定存储空间,存放在该区域的变量称为静态存储变量。如全局变量就存放在静态存储区,在程序开始执行时,系统就为全局变量分配存储空间,程序结束时才释放变量。
动态存储区是在程序运行期间,根据需要动态分配和释放的存储空间。该区域一般存放函数的形参变量、局部的自动变量、函数调用时的现场保护和返回地址等。
变量从作用域的角度可分为局部变量和全局变量;从存在的时间角度分为动态存储变量和静态存储变量。
静态存储变量,在整个程序执行过程中,始终占据固定的存储单元,其值一直不丢失,直到程序结束,才释放变量。
动态存储变量,只有在要使用时才被分配存储单元,程序执行离开其有效范围时就释放存储单元,变量的值也就丢失了。
在 C 语言中,变量和函数有以下两个基本属性。
(1)数据类型:如整型、实型和字符型等。
(2)存储类型:分为自动变量( auto )、静态变量( static )、寄存器变量( register )和外部变量( extern )。
7.7.2 动态变量
动态变量,也称自动变量,属于动态存储变量,可关键字 auto 声明动态变量。
对于局部变量,若不加以任何说明,默认是动态变量。
动态变量的赋初值是在调用函数时进行的,在函数执行结束时,释放其存储单元,变量的数据丢失。再次调用函数时,又重新为动态变量赋初值,若没有为动态变量赋初值,则动态变量的初值是随机的。
7.7.3 局部静态变量
静态变量属于静态存储变量,可用关键字 static 声明局部静态变量。
局部静态变量赋初值是在编译时进行的,若没有为静态变量赋初值的语句,则自动初始化为0。
静态变量的值在函数调用结束时仍保留,下次再调用该函数时,静态变量的值是上次调用结束时的值,而不再重新赋初值。
7.7.4 外部变量
用 extern 声明的变量,称为外部变量。
利用外部变量可以扩展全局变量的作用域。
外部变量有以下用途。
(1)在同一文件中,全局变量若在文件的开始处定义,则在整个文件范围内的函数都可以使用该变量。但若不是在文件的开头定义的全局变量,则只有该变量定义点之后的函数可以直接使用。若在全局变量的定义点之前的函数要使用该变量,则要先用 extern 声明外部变量。
(2)一个 C 程序可以由一个或多个源程序文件组成。对于多文件的 C 程序,若一个文件中的程序,要使用另外一个文件中的全局变量,就要用 extern 声明外部变量,这样就可将全局变量的作用域扩展到其他文件。
7.7.5 静态外部变量
用 static 声明的全局变量,称为静态外部变量。这种变量只能被本文件中的函数使用。其他文件即使用 extern 声明该变量也无法使用该变量。