函数是一个完成特定工作的独立程序模块,包括库函数和自定义函数两种。例如,scanf()、printf()等这些都为库函数,是由C语言系统提供定义,编程时直接调用即可;还有一种是自己定义的函数,我们主要介绍的就是这类函数。

1、函数的定义

C语言的函数与数学上的函数概念类似。例如计算圆柱体积的公式为 V = π r2hV=\pi r^2hV=πr2h,在数学上可以写成 V = f ( r , h )V=f(r,h)V=f(r,h)这个二元函数,其中 r , hr,hr,h是自变量, VVV是根据自变量所得到的函数值。在C语言中同样可以定义一个函数double cylinder(double r, double h),这个函数的功能就是计算圆柱的体积。其函数值的结果依赖于 r , hr,hr,h,在C语言中称为函数参数。而 f ( r , h )f(r,h)f(r,h)计算得到的函数值,在C语言中一定要为某一种数据类型,称为函数类型。
函数定义的一般形式:

函数类型 函数名(形式参数表)/*函数首部*/{函数实现过程/*函数体*/}

例如计算圆柱体积函数的定义:

double cylinder(double r, double h){double result;result = 3.1415926*r*r*h;/*计算圆柱体积*/return result;/*返回结果*/}

(1)函数首部
从上面函数定义的一般形式可以看到,函数首部是由函数类型、函数名和形式参数表(简称形参表)组成,位于函数定义的第一行。其中,函数名是函数整体的称谓,它需要用一个合法的标识符表示(同变量)。函数类型是指函数结果最后要返回什么类型,一般与return语句中表达式的类型一致。形参表中给出函数计算所要用到的相关已知条件,以类似变量定义的形式给出,其格式为:

类型1 形参1, 类型2 形参2, ..., 类型n 形参n

形参表中各个形参之间用逗号(这里的逗号仅仅是标点符号,而不是逗号运算符)分隔,每个形参前面的类型必须写明。形参的数量可以是一个,也可以是多个,还可以没有形参。
函数首部后面是不能加分号的,函数首部和函数体在一起构成了完整的函数的定义。如上面计算圆柱体积的例子,函数首部为:

double cylinder(double r, double h)

这里函数的类型是double,就是最后函数的结果类型;函数名是cylinder;该函数有两个形参r和h,它们的数据类型都是double,计算圆柱的体积时依赖这两个形参。cylinder()函数被调用时,这两个形参的值会由主调函数给出。要注意,形参的类型不能省略,这里写成double r, h错误的。
(2)函数体
函数体由一对大括号内的所有语句组成,其作用是实现该函数的具体功能,最后return语句返回函数的结果。例如上例的函数体为:

{double result;result = 3.1415926*r*r*h;/*计算圆柱体积*/return result;/*返回结果*/}

return后面的表达式应该要和函数的类型一致(不一致会发生隐式类型转换,后面有讲),例如该函数的类型是double型,result也是double型的。这里result是普通的变量,不是形参,它是该函数实现过程中需要用到的变量,只有函数圆括号内的变量才是形参。

例:计算圆柱体积

输入圆柱的高和半径,求圆柱体积volume=π× r 2×hvolume=\pi\times r^2\times h volume=π×r2×h。要求定义和调用函数cylinder(r,h)计算圆柱体的体积。

/*计算圆柱体积*/#include double cylinder(double r, double h);/*函数声明*/int main(void){double height, radius, volume;printf("Enter radius and height:");scanf("%lf%lf", &radius, &height);/*输入圆柱的半径和高度*/volume = cylinder(radius, height);/*调用函数,返回值赋给volume*/printf("volume = %.3f\n", volume);/*输出圆柱的体积*/return 0;}/*定义求圆柱体积的函数*/double cylinder(double r, double h){double result;result = 3.1415926*r*r*h;/*计算圆柱体积*/return result;/*返回结果*/}

2、函数的调用

函数定义后就可以使用了。在C语言中,调用标准库函数时,只需要在程序的最前面用#include命令包含相应的头文件即可;调用自定义函数时,程序中必须有与被调用函数相对应的函数定义。

(1)函数的调用过程
任何C语言执行,首先从主函数main()开始,如果遇到某个函数调用,主函数被暂停执行,转而执行相应的函数,该函数执行完后将返回主函数,然后再从原先暂停的位置继续执行。
下面根据上例看一下函数的调用过程:

上图中的序号为程序的执行流程:

①main()函数运行到:volume = cylinder(radius, height);时调用cylinder()函数,这里main()函数暂停执行,将变量radius和height的值传递给形参r和h;
②开始执行cylinder()函数,其中形参r和h分别接收变量radius和height的值;
③执行cylinder()函数中的语句,计算圆柱体积;
④函数cylinder()执行return result;语句后,结束函数运行,带着函数的结果result,返回到main()函数中调用它的地方。
⑤计算机从先前暂停的位置继续执行,将返回值赋给变量volume,输出体积。程序结束。
通常把调用其他函数的函数称为主调函数,在这里main()函数就是主调函数;被调用的函数称为被调函数,这里就是cylinder()函数。
(2)函数的调用的形式
函数调用的一般形式为:

函数名(实际参数表);

实际参数(简称实参)可以是常量、变量和表达式。但是数据类型应该要保持相同,虽说一般不会报错,但是很有可能会隐含一些隐蔽的逻辑错误,要养成良好的编程习惯。例子中变量radius和height就是实参。
带返回值的函数可以当做一个表达式使用,一般如下两种调用方式:
①赋值语句
即此时函数作为右值:

volume = cylinder(radius,height);

②输出函数的值
此时函数可以当做一个表达式被函数调用,这个函数的结果就是它的返回值:

printf("%f", cylinder(radius, height));

3、参数传递
函数首部的参数称为形参。主调函数传给形参的参数称为实参。形参除了能接受实参的值外,使用方法与普通的变量一样。形参与实参必须一一对应,数量和顺序都一致,且类型也要尽量一致。函数调用时,实参的值会依次传给形参。
上例中从函数首部double cylinder(double r, double h)可以知道形参是r和h,main()函数调用时volume = cylinder(radius, height);的实参就是radius和height,它们和r、h一一对应,值也会依次传递给r、h。
函数的形参必须是变量,用于接受实参传递过来的值;而实参可以是常量、变量或者表达式,其作用是把常量、变量或者表达式的值传递给形参。形参和实参可以同名。
传递参数过程中,是将实参的值复制给形参。这种参数传递是单向的,只允许实参把值复制给形参,形参的值即使在函数中改变了,也不会影响到实参。

4、函数结果返回
函数结果的返回形式如下:

return 表达式;

在返回过程中,会先求解表达式的值,再返回其值。一般来说这里表达式的类型应该与函数类型保持一致,若不一致,则以函数的类型为准。
return语句的作用有两个:一是结束函数的运行;二是带着运算结果(表达式的值)返回主调函数。注意:return语句只能返回一个值。

5、函数原型声明
C语言规定,函数要先定义后调用。如果自定义函数放在了主调函数后面,那么需要在函数调用前,加上函数原型声明(也称函数声明)。
函数声明的目的是说明函数的类型和参数的情况,保证程序编译时能判断对该函数的调用是否正确。函数声明的一般格式为:

函数类型函数名(参数表);

一般情况是与函数定义中的第一行(函数首部)相同,并以分号结束。这里的参数表也可以仅有参数的类型,没有参数的名称。例如double cylinder(double r, double h);这是一句函数声明,也可以写成double cylinder(double, double);即仅需要形参的数据类型。为了增强可读性,一般都会带上参数名。
注意函数声明和定义的区别:声明是一条C语言语句,而定义时的函数首部不是语句,后面不能跟分号。
函数声明的位置:可以在主调函数内,也可以在主调函数外。若是在主调函数内,则在函数声明后的主调函数语句才可以使用,或者函数定义后面的函数可以调用。

3、函数的优点

(1)程序结构清晰,逻辑关系明确,程序可读性强;
(2)解决相同或相似问题时不用重复编写代码,可通过调用函数来解决,减少代码量;
(3)利用函数实现模块化编程,各模块功能相对独立,利用“各个击破”降低调试难度。

例1:计算五边形的面积

将一个五边形分割成3个三角形,输入这些三角形的7条边长,计算边长为x、y、z的三角形面积。

#include #include int main(void){double a1, a2, a3, a4, a5, a6, a7, s;double area(double x, double y, double z);/*函数声明*/printf("Please input 7 side lengths in the order a1 to a7:\n");scanf("%lf%lf%lf%lf%lf%lf%lf", &a1, &a2, &a3, &a4, &a5, &a6, &a7); s = area(a1, a5, a6)+area(a4, a6, a7)+area(a2, a3, a7);/*调用3次area函数*/printf("The area of the Pentagon is %.2f\n", s);return 0;}/*使用海伦-秦九韶公式计算三角形面积的函数*/double area(double x, double y, double z)/*函数首部*/{double p = (x+y+z)/2;return sqrt(p*(p-x)*(p-y)*(p-z));}

Please input 7 side lengths in the order a1 to a7:
3.6 3.6 3.6 3.6 3.6 4.5 4.5
The area of the Pentagon is 20.07

这里的定义了一个函数area(),用海伦-秦九韶公式来计算三角形的面积。函数的声明放在了主调函数里面。

例2:判断完全平方数

定义一个判断完全平方数的函数IsSquare(n),当n为完全平方数时返回1,否则返回0,不允许调用数学库函数。
如果n是完全平方数,则可以找到正整数m,使m= n 2m=n^2 m=n2成立。在不使用函数sqrt()的情况下,我们也可以判断一个数是否为完全平方数。例如,当n是完全平方数时,则n可以采用以下等差数列求和公式计算。
1+3+5+7+…+(2∗m−1)= m 2=n1+3+5+7+…+(2*m-1)=m^2=n 1+3+5+7++(2m1)=m2=n

#include int IsSquare(int n);int main(void){int n;printf("Enter n:");scanf("%d", &n);if (IsSquare(n)){printf("%d is a Square!\n", n);} else {printf("%d is not a Square!\n", n);}return 0;}/*判断完全平方数的函数*/int IsSquare(int n)/*函数首部*/{int i;for (i=1; n>0; i=i+2){n = n-i;}if (n==0){return 1;/*是完全平方数返回1*/} else {return 0;/*不是完全平方数返回0*/}}

上述函数中有两个return语句,只要函数执行了return语句后,这个函数就会结束运行,后面的语句就不会执行了。同时在主调函数可以发现,最后n的值没有改变,即函数内部不会对实参产生影响。

例3:辗转相除法求最大公约数

/*采用辗转相除法求最大公约数的函数*/int gcd(int m, int n)/*定义求最大公约数函数gcd()*/{int r, temp;if (m < n){/*如果m小于n,则交换m和n的值*/temp = m;m = n;n = temp;}r = m%n;while (r != 0){m = n;n = r;r = m%n;}return n;}

辗转相除法,又称欧几里得算法,是求最大公约数的一种常用方法。具体做法是:用较大的数m除以较小的数n,再用出现的余数(第一余数)去除除数,再用出现的余数(第二余数)去除第一余数,如此反复,知道最后余数为0为止,那么最后的除数就是这两个数的最大公约数。这是一个迭代算法:
(1)用m除以n,得到余数赋值给r;
(2)如果r为0,则返回n的值作为结果并结束;否则进入第(3)步;
(3)将n的值赋给m,将余数r赋给n,返回第(1)步。

例4:判断素数

/*判断素数的函数*/int prime(int m){int i, limit;if(m <= 1){/*小于等于1的数不是素数*/return 0;} else if (m == 2){/*2是素数*/return 1;} else {/*其他情况:大于2的正整数*/limit = sqrt(m)+1;for (i=2; i<=limit; i++){if (m%i == 0){return 0;/*若m能被某个i整除,则m不是素数,返回0*/}}/*若循环正常结束,说明m不能被任何一个i整除,则m是素数,返回1*/return 1; }}

定义了一个判断素数的函数。若一个数能被 [ 2 , m][2, \sqrt m ][2,m ]之间任意一个数整除,则该数不是素数;否则为素数。

参考 C语言程序设计(第4版)/何钦铭,颜晖
例题及课后习题参考程序https://gitee.com/sgxgitee/mooc-c