轻松学会C语言:[6]第五章:函数

时间:2026-02-12 20:13:48

概述  在第一章中已经介绍过,C源程序是由函数组成的。


虽然在前面各章的程序中都只有一个主函数main(), 但实用程序往往由多个函数组成。函数是C源程序的基本模块,
通过对函数模块的调用实现特定的功能。C语言中的函数相当于其它高级语言的子程序。 C语言不仅提供了极为丰富的库函数(如Turbo C,MS C
都提供了三百多个库函数),还允许用户建立自己定义的函数。用户可把自己的算法编成一个个相对独立的函数模块,然后用调用的方法来使用函数。  可以说C程序的全部工作都是由各式各样的函数完成的,
所以也把C语言称为函数式语言。 由于采用了函数模块式的结构,
C语言易于实现结构化程序设计。使程序的层次结构清晰,便于程序的编写、阅读、调试。  在C语言中可从不同的角度对函数分类。1.
从函数定义的角度看,函数可分为库函数和用户定义函数两种。(1)库函数  由C系统提供,用户无须定义,
也不必在程序中作类型说明,只需在程序前包含有该函数原型的头文件即可在程序中直接调用。在前面各章的例题中反复用到printf 、 scanf 、 getchar
、putchar、gets、puts、strcat等函数均属此类。(2)用户定义函数  由用户按需要写的函数。对于用户自定义函数,
不仅要在程序中定义函数本身, 而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。2.
C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数两种。(1)有返回值函数  此类函数被调用执行完后将向调用者返回一个执行结果,
称为函数返回值。如数学函数即属于此类函数。
由用户定义的这种要返回函数值的函数,必须在函数定义和函数说明中明确返回值的类型。(2)无返回值函数  此类函数用于完成某项特定的处理任务,
执行完成后不向调用者返回函数值。这类函数类似于其它语言的过程。 由于函数无须返回值,用户在定义此类函数时可指定它的返回为“空类型”,
空类型的说明符为“void”。3.
从主调函数和被调函数之间数据传送的角度看又可分为无参函数和有参函数两种。(1)无参函数  函数定义、函数说明及函数调用中均不带参数。
主调函数和被调函数之间不进行参数传送。
此类函数通常用来完成一组指定的功能,可以返回或不返回函数值。(2)有参函数  也称为带参函数。在函数定义及函数说明时都有参数,
称为形式参数(简称为形参)。在函数调用时也必须给出参数, 称为实际参数(简称为实参)。
进行函数调用时,主调函数将把实参的值传送给形参,供被调函数使用。4. C语言提供了极为丰富的库函数,
这些库函数又可从功能角度作以下分类。(1)字符类型分类函数  用于对字符按ASCII码分类:字母,数字,控制字符,分隔符,大小写字母等。(2)转换函数  用于字符或字符串的转换;在字符量和各类数字量
(整型,
实型等)之间进行转换;在大、小写之间进行转换。(3)目录路径函数  用于文件目录和路径操作。(4)诊断函数  用于内部错误检测。(5)图形函数  用于屏幕管理和各种图形功能。(6)输入输出函数  用于完成输入输出功能。(7)接口函数  用于与DOS,BIOS和硬件的接口。(8)字符串函数  用于字符串操作和处理。(9)内存管理函数  用于内存管理。(10)数学函数  用于数学函数计算。(11)日期和时间函数  用于日期,时间转换操作。(12)进程控制函数  用于进程管理和控制。(13)其它函数  用于其它各种功能。  以上各类函数不仅数量多,而且有的还需要硬件知识才会使用,因此要想全部掌握则需要一个较长的学习过程。
应首先掌握一些最基本、 最常用的函数,再逐步深入。由于篇幅关系,本书只介绍了很少一部分库函数,
其余部分读者可根据需要查阅有关手册。  还应该指出的是,在C语言中,所有的函数定义,包括主函数main在内,都是平行的。也就是说,在一个函数的函数体内,
不能再定义另一个函数, 即不能嵌套定义。但是函数之间允许相互调用,也允许嵌套调用。习惯上把调用者称为主调函数。 函数还可以自己调用自己,称为递归调用。main
函数是主函数,它可以调用其它函数,而不允许被其它函数调用。 因此,C程序的执行总是从main函数开始,
完成对其它函数的调用后再返回到main函数,最后由main函数结束整个程序。一个C源程序必须有,也只能有一个主函数main。函数定义的一般形式1.无参函数的一般形式类型说明符 函数名(){类型说明语句}  其中类型说明符和函数名称为函数头。 类型说明符指明了本函数的类型,函数的类型实际上是函数返回值的类型。
该类型说明符与第二章介绍的各种说明符相同。 函数名是由用户定义的标识符,函数名后有一个空括号,其中无参数,但括号不可少。{}
中的内容称为函数体。在函数体中也有类型说明, 这是对函数体内部所用到的变量的类型说明。在很多情况下都不要求无参函数有返回值,
此时函数类型符可以写为void。我们可以改为一个函数定义:void
Hello(){printf ("Hello,world
\n");} 这里,只把main改为Hello作为函数名,其余不变。Hello
函数是一个无参函数,当被其它函数调用时,输出Hello world字符串。2.有参函数的一般形式类型说明符 函数名(形式参数表)型式参数类型说明{类型说明语句}  有参函数比无参函数多了两个内容,其一是形式参数表,
其二是形式参数类型说明。在形参表中给出的参数称为形式参数, 它们可以是各种类型的变量,
各参数之间用逗号间隔。在进行函数调用时,主调函数将赋予这些形式参数实际的值。 形参既然是变量,当然必须给以类型说明。例如,定义一个函数,
用于求两个数中的大数,可写为:int max(a,b)int a,b;{if
(a>b) return a;else return b;}  第一行说明max函数是一个整型函数,其返回的函数值是一个整数。形参为a,b。第二行说明a,b均为整型量。 a,b
的具体值是由主调函数在调用时传送过来的。在{}中的函数体内, 除形参外没有使用其它变量,因此只有语句而没有变量类型说明。 上边这种定义方法称为“传统格式”。
这种格式不易于编译系统检查,从而会引起一些非常细微而且难于跟踪的错误。ANSI C
的新标准中把对形参的类型说明合并到形参表中,称为“现代格式”。  例如max函数用现代格式可定义为:int max(int a,int b){if(a>b) return a;else return
b;}  现代格式在函数定义和函数说明(后面将要介绍)时, 给出了形式参数及其类型,在编译时易于对它们进行查错,
从而保证了函数说明和定义的一致性。例1.3即采用了这种现代格式。
在max函数体中的return语句是把a(或b)的值作为函数的值返回给主调函数。有返回值函数中至少应有一个return语句。
在C程序中,一个函数的定义可以放在任意位置, 既可放在主函数main之前,也可放在main之后。例如例1.3中定义了一个max 函数,其位置在main之后,
也可以把它放在main之前。修改后的程序如下所示。int max(int a,int
b){if(a>b)return a;else return b;}void main(){int
max(int a,int b);int x,y,z;printf("input two
numbers:\n");scanf("%d%d",&x,&y);z=max(x,y);printf("maxmum=%d",z);}  现在我们可以从函数定义、
函数说明及函数调用的角度来分析整个程序,从中进一步了解函数的各种特点。程序的第1行至第5行为max函数定义。进入主函数后,因为准备调用max函数,故先对max函数进行说明(程序第8行)。函数定义和函数说明并不是一回事,在后面还要专门讨论。
可以看出函数说明与函数定义中的函数头部分相同,但是末尾要加分号。程序第12
行为调用max函数,并把x,y中的值传送给max的形参a,b。max函数执行的结果
(a或b)将返回给变量z。最后由主函数输出z的值。  函数调用的一般形式前面已经说过,在程序中是通过对函数的调用来执行函数体的,其过程与其它语言的子程序调用相似。C语言中,
函数调用的一般形式为:  函数名(实际参数表) 对无参函数调用时则无实际参数表。
实际参数表中的参数可以是常数,变量或其它构造类型数据及表达式。 各实参之间用逗号分隔。'Next of
Page在C语言中,可以用以下几种方式调用函数:1.函数表达式  函数作表达式中的一项出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。例如:
z=max(x,y)是一个赋值表达式,把max的返回值赋予变量z。'Next of
Page2.函数语句  函数调用的一般形式加上分号即构成函数语句。例如: printf ("%D",a);scanf
("%d",&b);都是以函数语句的方式调用函数。3.函数实参  函数作为另一个函数调用的实际参数出现。
这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的。例如: printf("%d",max(x,y));
即是把max调用的返回值又作为printf函数的实参来使用的。在函数调用中还应该注意的一个问题是求值顺序的问题。
所谓求值顺序是指对实参表中各量是自左至右使用呢,还是自右至左使用。 对此, 各系统的规定不一定相同。在3.1.3节介绍printf
函数时已提到过,这里从函数调用的角度再强调一下。 看例5.2程序。void
main(){int
i=8;printf("%d\n%d\n%d\n%d\n",++i,--i,i++,i--);}如按照从右至左的顺序求值。例5.2的运行结果应为:8778如对printf语句中的++i,--i,i++,i--从左至右求值,结果应为:9889  应特别注意的是,无论是从左至右求值,
还是自右至左求值,其输出顺序都是不变的, 即输出顺序总是和实参表中实参的顺序相同。由于Turbo
C现定是自右至左求值,所以结果为8,7,7,8。上述问题如还不理解,上机一试就明白了。函数的参数和函数的值一、函数的参数  前面已经介绍过,函数的参数分为形参和实参两种。
在本小节中,进一步介绍形参、实参的特点和两者的关系。 形参出现在函数定义中,在整个函数体内都可以使用,
离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。 形参和实参的功能是作数据传送。发生函数调用时,
主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。  函数的形参和实参具有以下特点:1.形参变量只有在被调用时才分配内存单元,在调用结束时,
即刻释放所分配的内存单元。因此,形参只有在函数内部有效。
函数调用结束返回主调函数后则不能再使用该形参变量。2.实参可以是常量、变量、表达式、函数等,
无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。
因此应预先用赋值,输入等办法使实参获得确定值。3.实参和形参在数量上,类型上,顺序上应严格一致,
否则会发生“类型不匹配”的错误。4.函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。
因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。例5.3可以说明这个问题。void
main(){int n;printf("input
number\n");scanf("%d",&n);s(n);printf("n=%d\n",n);}int
s(int n){int
i;for(i=n-1;i>=1;i--)n=n+i;printf("n=%d\n",n);}本程序中定义了一个函数s,该函数的功能是求∑ni=1i
的值。在主函数中输入n值,并作为实参,在调用时传送给s 函数的形参量n( 注意,本例的形参变量和实参变量的标识符都为n,
但这是两个不同的量,各自的作用域不同)。 在主函数中用printf 语句输出一次n值,这个n值是实参n的值。在函数s中也用printf
语句输出了一次n值,这个n值是形参最后取得的n值0。从运行情况看,输入n值为100。即实参n的值为100。把此值传给函数s时,形参 n
的初值也为100,在执行函数过程中,形参n的值变为5050。
返回主函数之后,输出实参n的值仍为100。可见实参的值不随形参的变化而变化。二、函数的值  函数的值是指函数被调用之后,
执行函数体中的程序段所取得的并返回给主调函数的值。如调用正弦函数取得正弦值,调用例5.1的max函数取得的最大数等。对函数的值(或称函数返回值)有以下一些说明:1.
函数的值只能通过return语句返回主调函数。return 语句的一般形式为:return 表达式;或者为:return
(表达式);该语句的功能是计算表达式的值,并返回给主调函数。 在函数中允许有多个return语句,但每次调用只能有一个return 语句被执行,
因此只能返回一个函数值。2. 函数值的类型和函数定义中函数的类型应保持一致。
如果两者不一致,则以函数类型为准,自动进行类型转换。3. 如函数值为整型,在函数定义时可以省去类型说明。4.
不返回函数值的函数,可以明确定义为“空类型”, 类型说明符为“void”。如例5.3中函数s并不向主函数返函数值,因此可定义为:void s(int n){ ……}  一旦函数被定义为空类型后,
就不能在主调函数中使用被调函数的函数值了。例如,在定义s为空类型后,在主函数中写下述语句 sum=s(n); 就是错误的。为了使程序有良好的可读性并减少出错,
凡不要求返回值的函数都应定义为空类型。函数说明在主调函数中调用某函数之前应对该被调函数进行说明, 这与使用变量之前要先进行变量说明是一样的。
在主调函数中对被调函数作说明的目的是使编译系统知道被调函数返回值的类型, 以便在主调函数中按此种类型对返回值作相应的处理。
对被调函数的说明也有两种格式,一种为传统格式,其一般格式为: 类型说明符 被调函数名();
这种格式只给出函数返回值的类型,被调函数名及一个空括号。  这种格式由于在括号中没有任何参数信息,
因此不便于编译系统进行错误检查,易于发生错误。另一种为现代格式,其一般形式为:类型说明符 被调函数名(类型 形参,类型 形参…);或为:类型说明符 被调函数名(类型,类型…);  现代格式的括号内给出了形参的类型和形参名,
或只给出形参类型。这便于编译系统进行检错,以防止可能出现的错误。例5.1 main函数中对max函数的说明若用传统格式可写为:int
max();用现代格式可写为:int max(int a,int b);或写为:int
max(int,int);  C语言中又规定在以下几种情况时可以省去主调函数中对被调函数的函数说明。1. 如果被调函数的返回值是整型或字符型时,
可以不对被调函数作说明,而直接调用。这时系统将自动对被调函数返回值按整型处理。例5.3的主函数中未对函数s作说明而直接调用即属此种情形。2.
当被调函数的函数定义出现在主调函数之前时, 在主调函数中也可以不对被调函数再作说明而直接调用。例如例5.1中, 函数max的定义放在main
函数之前,因此可在main函数中省去对 max函数的函数说明int max(int a,int b)。3. 如在所有函数定义之前,
在函数外预先说明了各个函数的类型,则在以后的各主调函数中,可不再对被调函数作说明。例如:char str(int
a);float f(float b);main(){……}char str(int
a){……}float f(float
b){……}其中第一,二行对str函数和f函数预先作了说明。
因此在以后各函数中无须对str和f函数再作说明就可直接调用。4. 对库函数的调用不需要再作说明,
但必须把该函数的头文件用include命令包含在源文件前部。数组作为函数参数数组可以作为函数的参数使用,进行数据传送。
数组用作函数参数有两种形式,一种是把数组元素(下标变量)作为实参使用;
另一种是把数组名作为函数的形参和实参使用。一、数组元素作函数实参数组元素就是下标变量,它与普通变量并无区别。
因此它作为函数实参使用与普通变量是完全相同的,在发生函数调用时,
把作为实参的数组元素的值传送给形参,实现单向的值传送。例5.4说明了这种情况。[例5.4]判别一个整数数组中各元素的值,若大于0
则输出该值,若小于等于0则输出0值。编程如下:void nzp(int
v){if(v>0)printf("%d ",v);elseprintf("%d
",0);}main(){int a[5],i;printf("input 5
numbers\n");for(i=0;i<5;i++){scanf("%d",&a[i]);nzp(a[i]);}}void
nzp(int v){ ……}main(){int a[5],i;printf("input 5
numbers\n");for(i=0;i<5;i++){
scanf("%d",&a[i]);nzp(a[i]);}}  本程序中首先定义一个无返回值函数nzp,并说明其形参v 为整型变量。在函数体中根据v值输出相应的结果。在main函数中用一个for
语句输入数组各元素, 每输入一个就以该元素作实参调用一次nzp函数,即把a[i]的值传送给形参v,供nzp函数使用。二、数组名作为函数参数  用数组名作函数参数与用数组元素作实参有几点不同:1.
用数组元素作实参时,只要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的。因此,
并不要求函数的形参也是下标变量。 换句话说,对数组元素的处理是按普通变量对待的。用数组名作函数参数时,
则要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组说明。当形参和实参二者不一致时,即会发生错误。2.
在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送是把实参变量的值赋予形参变量。在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。那么,数据的传送是如何实现的呢?
在第四章中我们曾介绍过,数组名就是数组的首地址。因此在数组名作函数参数时所进行的传送只是地址的传送,
也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。图5.1说明了这种情形。图中设a为实参数组,类型为整型。a占有以2000
为首地址的一块内存区。b为形参数组名。当发生函数调用时,进行地址传送, 把实参数 组a的首地址传送给形参数组名b,于是b也取得该地址2000。
于是a,b两数组共同占有以2000
为首地址的一段连续内存单元。从图中还可以看出a和b下标相同的元素实际上也占相同的两个内存单元(整型数组每个元素占二字节)。例如a[0]和b[0]都占用2000和2001单元,当然a[0]等于b[0]。类推则有a[i]等于b[i]。[例5.5]数组a中存放了一个学生5门课程的成绩,求平均成绩。float aver(float a[5]){int i;float av,s=a[0];for(i=1;i<5;i++)s=s+a[i];av=s/5;return av;}void main(){float
sco[5],av;int i;printf("\ninput 5
scores:\n");for(i=0;i<5;i++)scanf("%f",&sco[i]);av=aver(sco);printf("average
score is %5.2f",av);}float aver(float a[5]){ ……}void
main(){……for(i=0;i<5;i++)scanf("%f",&sco[i]);av=aver(sco);……}  本程序首先定义了一个实型函数aver,有一个形参为实型数组a,长度为5。在函数aver中,把各元素值相加求出平均值,返回给主函数。主函数main
中首先完成数组sco的输入,然后以sco作为实参调用aver函数,函数返回值送av,最后输出av值。
从运行情况可以看出,程序实现了所要求的功能3.
前面已经讨论过,在变量作函数参数时,所进行的值传送是单向的。即只能从实参传向形参,不能从形参传回实参。形参的初值和实参相同,
而形参的值发生改变后,实参并不变化, 两者的终值是不同的。例5.3证实了这个结论。 而当用数组名作函数参数时,情况则不同。 由于实际上形参和实参为同一数组,
因此当形参数组发生变化时,实参数组也随之变化。
当然这种情况不能理解为发生了“双向”的值传递。但从实际情况来看,调用函数之后实参数组的值将由于形参数组值的变化而变化。为了说明这种情况,把例5.4改为例5.6的形式。[例5.6]题目同5.4例。改用数组名作函数参数。void nzp(int a[5]){int i;printf("\nvalues of array a
are:\n");for(i=0;i<5;i++){if(a[i]<0) a[i]=0;printf("%d
",a[i]);}}main(){int b[5],i;printf("\ninput 5
numbers:\n");for(i=0;i<5;i++)scanf("%d",&b[i]);printf("initial
values of array b are:\n");for(i=0;i<5;i++)printf("%d
",b[i]);nzp(b);printf("\nlast values of array b
are:\n");for(i=0;i<5;i++)printf("%d ",b[i]);}void nzp(int
a[5]){ ……}main(){int
b[5],i;……nzp(b);……}  本程序中函数nzp的形参为整数组a,长度为 5。
主函数中实参数组b也为整型,长度也为5。在主函数中首先输入数组b的值,然后输出数组b的初始值。
然后以数组名b为实参调用nzp函数。在nzp中,按要求把负值单元清0,并输出形参数组a的值。 返回主函数之后,再次输出数组b的值。从运行结果可以看出,数组b
的初值和终值是不同的,数组b 的终值和数组a是相同的。这说明实参形参为同一数组,它们的值同时得以改变。 用数组名作为函数参数时还应注意以下几点:a.
形参数组和实参数组的类型必须一致,否则将引起错误。b.
形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不检查形参数组的长度。当形参数组的长度与实参数组不一致时,虽不至于出现语法错误(编译能通过),但程序执行结果将与实际不符,这是应予以注意的。如把例5.6修改如下:void nzp(int a[8]){int i;printf("\nvalues of array
aare:\n");for(i=0;i<8;i++){if(a[i]<0)a[i]=0;printf("%d",a[i]);}}main(){int
b[5],i;printf("\ninput 5
numbers:\n");for(i=0;i<5;i++)scanf("%d",&b[i]);printf("initial
values of array b
are:\n");for(i=0;i<5;i++)printf("%d",b[i]);nzp(b);printf("\nlast
values of array b
are:\n");for(i=0;i<5;i++)printf("%d",b[i]);}  本程序与例5.6程序比,nzp函数的形参数组长度改为8,函数体中,for语句的循环条件也改为i<8。因此,形参数组
a和实参数组b的长度不一致。编译能够通过,但从结果看,数组a的元素a[5],a[6],a[7]显然是无意义的。c.
在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数组元素的个数。例如:可以写为:void nzp(int
a[])或写为void nzp(int a[],int
n)  其中形参数组a没有给出长度,而由n值动态地表示数组的长度。n的值由主调函数的实参进行传送。由此,例5.6又可改为例5.7的形式。[例5.7]void nzp(int a[],int n){int i;printf("\nvalues of
array a are:\n");for(i=0;i<n;i++){if(a[i]<0)
a[i]=0;printf("%d ",a[i]);}}main(){int
b[5],i;printf("\ninput 5
numbers:\n");for(i=0;i<5;i++)scanf("%d",&b[i]);printf("initial
values of array b are:\n");for(i=0;i<5;i++)printf("%d
",b[i]);nzp(b,5);printf("\nlast values of array b
are:\n");for(i=0;i<5;i++)printf("%d ",b[i]);}void nzp(int
a[],int n){
……}main(){……nzp(b,5);……}本程序nzp函数形参数组a没有给出长度,由n
动态确定该长度。在main函数中,函数调用语句为nzp(b,5),其中实参5将赋予形参n作为形参数组的长度。d. 多维数组也可以作为函数的参数。
在函数定义时对形参数组可以指定每一维的长度,也可省去第一维的长度。因此,以下写法都是合法的。int MA(int
a[3][10])或int MA(int a[][10])(共篇)上一篇:第四章: 数组|下一篇:
© 2026 裕芯经验网
信息来自网络 所有数据仅供参考
有疑问请联系站长 site.kefu@gmail.com