函数是一个完成特定工作的独立程序模块,包括库函数和自定义函数两种。例如,scanf()、printf()等这些都为库函数,是由C语言系统提供定义,编程时直接调用即可;还有一种是自己定义的函数,我们主要介绍的就是这类函数。
C语言的函数与数学上的函数概念类似。例如计算圆柱体积的公式为 V = π r 2 h V=\pi r^2h V=πr2h,在数学上可以写成 V = f ( r , h ) V=f(r,h) V=f(r,h)这个二元函数,其中 r , h r,h r,h是自变量, V V V是根据自变量所得到的函数值。在C语言中同样可以定义一个函数double cylinder(double r, double h)
,这个函数的功能就是计算圆柱的体积。其函数值的结果依赖于 r , h r,h r,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是普通的变量,不是形参,它是该函数实现过程中需要用到的变量,只有函数圆括号内的变量才是形参。
输入圆柱的高和半径,求圆柱体积 v o l u m e = π × r 2 × h volume=\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; /*返回结果*/
}
函数定义后就可以使用了。在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语言语句,而定义时的函数首部不是语句,后面不能跟分号。
函数声明的位置:可以在主调函数内,也可以在主调函数外。若是在主调函数内,则在函数声明后的主调函数语句才可以使用,或者函数定义后面的函数可以调用。
(1)程序结构清晰,逻辑关系明确,程序可读性强;
(2)解决相同或相似问题时不用重复编写代码,可通过调用函数来解决,减少代码量;
(3)利用函数实现模块化编程,各模块功能相对独立,利用“各个击破”降低调试难度。
将一个五边形分割成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(),用海伦-秦九韶公式来计算三角形的面积。函数的声明放在了主调函数里面。
定义一个判断完全平方数的函数IsSquare(n),当n为完全平方数时返回1,否则返回0,不允许调用数学库函数。
如果n是完全平方数,则可以找到正整数m,使 m = n 2 m=n^2 m=n2成立。在不使用函数sqrt()的情况下,我们也可以判断一个数是否为完全平方数。例如,当n是完全平方数时,则n可以采用以下等差数列求和公式计算。
1 + 3 + 5 + 7 + … + ( 2 ∗ m − 1 ) = m 2 = n 1+3+5+7+…+(2*m-1)=m^2=n 1+3+5+7+…+(2∗m−1)=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的值没有改变,即函数内部不会对实参产生影响。
/*采用辗转相除法求最大公约数的函数*/
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)步。
/*判断素数的函数*/
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