第八章函数8.1概述1.一个较大的程序一般应分为若干个程序模块,每一个模块用来实现一个特定的功能。所有的高级语言中都有子程序这一概念,用子程序实现模块的功能。在C语言中,子程序的作用是由函数来实现的。一个C程序可由一个主函数和若干个其它函数构成。由主函数调用其它函数,其它函数也可以相互调用。同一个函数可以被一个或多个函数调用任意多次。2.例如:#includestdio.hintisPrime(intn){inti;for(i=2;i=n-1;i++)if(n%i==0)break;if(i==n)return1;elsereturn0;}voidmain(){intn;printf(“pleaseenterapositiveinteger:“);scanf(“%d”,&n);if(isPrime(n)==1)printf(“%disaprimenumber.\n”,n);elseprintf(“%disnotaprimenumber.\n“,n);}说明:(1)一个c程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。一个源文件可以为多个c程序共用。(2)一个源程序文件由一个或多个函数以及其他有关内容组成。一个源程序文件是一个编译单位。(3)C程序的执行是从main函数开始的,如是在main函数中调用其它函数,在调用后流程返回到main函数,在main函数中结束整个程序的运行。(4)所有函数都是平行的,即在定义函数时是分别进行的,是相互独立的。一个函数并不能从属于另一个函数,即函数不能嵌套定义。函数间可以相互调用,但不能调用main函数,main函数时系统调用的。(5)从用户使用来看,函数分为标准函数,即库函数;用户自定义函数。(6)从函数形式看,函数分为无参函数,在调用无参函数时,主调函数不像被调用函数传递数据。无参函数可以带回不带回函数值,一般不带回;有参函数,在调用有参函数时,主调函数要像被调用函数传递数据。有参函数一定要带回函数值,供主调函数使用。8.2函数定义的一般形式1.无参函数定义的一般形式:类型标识符函数名(){声明部分语句部分}在定义函数时要用“类型标识符”指定函数值的类型,即函数带回来的值得类型。2.有参函数定义的一般形式:类型标识符函数名(形式参数表列){声明部分语句部分}例如:Intmax(intx,inty){Intz;Z=xy?x:y;Return(z);}大括号内是函数体,它包括声明部分和语句部分。声明部分包括对函数中用到的变量进行定义以及对要调用的函数进行声明等内容。如果在定义函数时不指定函数类型,系统会隐含指定函数类型为int型。3.空函数形式:类型说明符函数名(){}例如:Voiddummy(){}调用此函数,什么也不做,以便扩充新功能。8.3函数参数和函数的值1.在调用函数时,大多数情况下,主调函数和被调函数之间有数据传递关系。这就是前面所说的有参函数。2.形式参数和实际参数在定义函数时函数名后面括号中的变量名称为“形式参数”;在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。例如:#includestdio.hvoidmain(){intf(int);inta=2,i;for(i=0;i3;i++)printf(“%d”,f(a));}intf(inta){intb=0;staticintc=3;b=b+1;c=c+1;return(a+b+c);}说明:(1)在定义函数中指定的形参,在未出现函数调用时,他们并不占内存中的存储单元。只有在发生调用时,形参才有存储空间,调用结束后,空间被释放。(2)实参可以是常量、变量与表达式。(3)在被定义的函数中,必须指定形参的类型。(4)是参与形参的类型应相同或赋值兼容。(5)实参向形参的数据传递是“值传递”,单向传递。2.函数的返回值希望同函数调用使主调函数能得到一个确定的值,这就是函数的返回值。例:编写一个求两个数的最大值的函数intmax(intx,inty){intz;z=xy?x:y;return(z);}主函数的返回值可以是void,也可以是int型intmain(){……;return0;}说明:(1)函数的返回值是通过函数中的return语句获得的。Return语句将被调用函数中的一个确定值待会主调函数中去。一个函数中可以有一个以上的return语句,执行到哪个,哪个起作用。Return语句后面的括号也可以不要。Return后面的值可以是一个表达式。(2)在定义函数时应指定函数值的类型。凡不加类型说明的函数,自动按整型处理。(3)在定义函数时指定的函数类型一般应该和return语句中的表达式类型一致。如果不一致,以函数类型为准。(4)对于不带回值的函数,应当用“void”定义为“无类型”。8.4函数的调用1.函数调用的一般形式:函数名(实参表列);(1)注:1)如果是调用无参函数,则“实参表列”可以没有,但括号不能省略。2)如果实参表列包含多个实参,实参之间用逗号隔开。实参与形参个数应相等,类型应一致,形成一一对应.(2)函数调用的作用(1)用实参向形参传递数据(2)为形参和被调函数中声明的变量分配空间(3)中断现行函数,把流程转到被调函数的入口处,开始执行被调函数2.函数调用方式(1)函数语句不要求函数带回值,只要求函数完成一定操作。(2)函数表达式函数出现在表达式中,这种表达称为函数表达式。这时要求函数带回一个确定的值以参加表达式的运算。(3)函数参数函数的调用作为参数的实参,实质上也是函数表达式调用的一种,因为为函数的参数本来就要求是表达是形式。3.例如:函数的参数传递机制#includestdio.hintmax(intx,inty){return(xy?x:y);}voidmain(){inta,b,c;scanf(“%d%d”,&a,&b);c=max(a,b);printf(“%d\n”,c);}4.在一个函数中调用另一个函数(被调函数)需要具备的条件:(1)首先被调用的函数必须是已经存在的函数。(2)如果使用库函数,还应该在本文件的开头用#include命令将调用库函数时所需用到的信息“包含”到本文件中来。(3)如果使用用户自己定义的函数,而该函数的位置在调用的函数的后面,应该在主调函数中对被调用的函数作声明。5.函数原型的一般形式有两种:(1)函数类型函数名(参数类型1、参数类型2.。。。参数类型n)(2)函数类型函数名(参数类型1、参数类型1、参数类型2、参数类型2、。。。参数类型n、参数类型n)说明:(1)以前的c语言版本的函数声明方式不是采用函数原型,而只声明函数名和类型。(2)如果被调用函数的定义出现在主调函数之前,可以不必加以声明。(3)如果已在文件的开头(在所有函数之前),已对本文件中所调用的函数进行了声明,则在各函数中不必对其所调用的函数再作声明。(4)如果被调用的函数是整型,c语言允许在调用函数之前不必作函数原型声明。8.5函数的嵌套调用1.C语言的函数定义是互相平行、独立的,也就是说,在定义函数时,一个函数内不能包含另一个函数。2.C语言不能嵌套定义函数,但可以嵌套调用函数,也就是说,在调用一个函数的过程中,又调用另一个函数。3.两层嵌套main(){……调用函数a……}函数a{……调用函数b……}函数b{……}4.两层嵌套(包括main函数共3层),其执行过程是:(1)执行main函数的开头部分;(2)遇函数调用语句,调用函数a,流程转去a函数;(3)执行a函数的开头部分;(4)遇函数调用语句,调用函数b,流程转去函数b;(5)执行b函数,如果再无其他嵌套的函数,则完成b函数的全部操作;(6)返回到a函数中调用b函数的位置;(7)继续执行a函数中尚未执行的部分,直到a函数结束;(8)返回main函数中调用a函数的位置;(9)继续执行main函数的剩余部分直到结束。5.例如:用截弦法求方程f(x)=x3-4x2+9x-16的根设计3个函数:f(x)---------根据x求f(x)的值point(x1,x2)---------求f(x1)与f(x2)的连线与x轴的交点root(x1,x2)-----------求方程在(x1,x2)区间的根(1)#includestdio.h#includemath.hfloatf(floatx);floatroot(floatx1,floatx2);floatxpoint(floatx1,floatx2);(2)voidmain(){floatx1,x2,f1,f2,x;do{printf(“inputx1,x2:\n”);scanf(“%f,%f”,&x1,&x2);f1=f(x1);f2=f(x2);}while(f1*f2=0);x=root(x1,x2);printf(“Arootofequationis%8.4f\n”,x);}(3)floatf(floatx){returnx*x*x-5*x*x+16*x-80;}(4)floatroot(floatx1,floatx2){floaty1,x,y;y1=f(x1);do{x=xpoint(x1,x2);y=f(x);if(y*y10){y1=y;x1=x;}elsex2=x;}while(fabs(y)=0.0001);returnx;}(5)floatxpoint(floatx1,floatx2){floaty;y=(x1*f(x2)-x2*f(x1))/(f(x2)-f(x1));returny;}8.6函数的递归调用1.在调用一个函数的过程中又出现直接或间接的调用该函数本身,称为函数的递归调用。C语言的特点之一就在于允许函数的递归调用。例如:直接递归:intf(intx){inty,z;z=f(y);return(2*z);}2.在调用函数f的过程中又要调用f函数,这就是直接调用本函数;如果在调用f1函数的过程中要调用f2函数,而在调用f2函数过程中又要调用f1函数,就是间接调用本函数。例如:递归计算n!的函数。#includestdio.hintrfact(intn){if(n==1)return1;elsereturnn*rfact(n-1);//递归调用}voidmain(){intt;printf(请输入要计算的数:);scanf(“%d”,&t);printf(n!=%d\n,rfact(t));}用递归函数求n!#includestdio.hvoidmain(){floatfac(intn);intn;floaty;printf(“inputanintegernumber:”);scanf(“%d”,&n);y=fac(n);printf(“%d!=%10.0f\n”,n,y);}floatfac(intn){floatf;if(n0)printf(“n0,dataerror!”);elseif(n==0||n==1)f=1;elsef=fac(n-1)*n;return(f);}3.程序中不应出现无终止的递归调用。8.7数组作为函数参数1.前面已经介绍了可以用变量作函数参数,显然,数组元素也可以作函数实参,其用法与变量相同。此外数组名可以作实参和形参,传递的是数组首元素的地址。2.数组元素作函数实参:(1)由于实参可以是表达式,而数组元素可以是表达式的组成部分,因此数组元素当然可以作为函数的实参,与用变量作实参一样,是单向传递,即“值传送”方式。(2)值传递(单个变量或数组元素作参数)(3)方式:函数调用时,为形参分配单元,并将实参的值复制到形参中;调用结束,形参单元被释放,实参单元仍保留并维持原值。(4)特点:1)形参与实参占用不同的内存单元。2)单向传递。只能由实参传给形参;不能由形参传给实参。例如:/*交换两个数*/#includestdio.hvoidswap(inta,intb){inttemp;temp=a;a=b;b=temp;}voidmain(){intx=7,y=