第七章函数Function模块化程序设计的概念7.1库函数7.2函数的定义和调用7.3函数的返回值及其类型7.4函数调用时参数间的传递7.5函数的嵌套调用7.6函数的递归调用7.7局部变量和全局变量7.8静态存储变量和动态存储变量7.9内部函数和外部函数7.107.1模块化程序设计的概念在C语言中,函数分为主函数、库函数和用户自定义函数3种。程序的执行由主函数开始,然后调用其他函数,最终返回主函数并结束。main函数a函数0.69%期限为3年0.84%期限b函数c函数e函数d函数函数调用示意图7.2库函数C语言提供了丰富的标准函数,即库函数。这类函数是由系统提供并定义好的,不必用户再去编写。用户只需要了解函数的功能,并学会正确地调用标准函数即可。7.2.1C语言常用库函数对每一类库函数,在调用该类库函数时,用户在源程序的include命令中应包含该类库函数的头文件名。#includemath.h“#includestdio.h7.2.2标准库函数的调用include命令的格式为#include<头文件名>或#include头文件名说明:(1)include命令必须以#号开头,系统提供的头文件名都以.h作为后缀,头文件名用一对双引号(“”)或一对尖括号(<>)括起来。(2)在C语言中,调用库函数时不能缺少库函数的头文件,include命令不是语句,不能在最后加分号。(3)两种格式的区别是:使用尖括号时,系统到存放C库函数头文件所在的目录中寻找要包含的文件,即标准方式;使用双引号时,系统先在用户当前目录中寻找要包含的文件,若找不到,再按标准方式查找。小例子#includestring.h/*调用strlen函数需要包含的头文件*/#includestdio.h/*调用printf函数需要包含的头文件*/main(){charstr[]=abcde;inti;i=strlen(str);printf(%d,i);}7.3函数的定义和调用由用户编写的函数称为自定义函数。函数必须先定义后使用。7.3.1函数的定义函数定义的一般格式如下:类型说明符函数名(类型名形式参数1,类型名形式参数2,…){函数体;}说明:(1)函数名是由用户命名的、唯一标识一个函数的名字。(2)各个函数必须单独定义,不能嵌套定义,即不能在一个函数内部再定义函数。(3)形式参数用于在调用函数和被调用函数之进行数据传递,两者之间的数据类型应一致。(4)若在函数首部省略类型名,则默认函数返回值的数据类型为int类型。(5)在函数体中,除形参外,用到的其他变量必须在说明部分进行定义,这些变量(包括形参),只在函数调用时才临时开辟存储单元,当退出函数时,这些临时开辟的存储单元全被释放掉。因此,这种变量只在函数体内部起作用,与其他函数体中的变量互不相关,它们可以和其他函数中的变量同名。例:求两实数之和main(){floatadd(floatx,floaty);floata,b,c;scanf(%f%f,&a,&b);c=add(a,b);printf(%f,c);}floatadd(floatx,floaty){floatz;z=x+;returnz;}说明:(1)add是函数名,该函数返回值为float型。(2)形式参数为实型变量x和y,该参数接受调用本函数时实参数据的传递。(3)函数体内的返回值是z。自定义函数add的作用是求两实数之和,其返回值也是float型,由于定义add函数出现在调用该函数的赋值语句“c=add(a,b);”后,因此必须在调用函数中对add函数的返回值做类型说明,即floatadd(floatx,floaty)7.3.2函数的调用1.函数调用的一般形式函数调用的一般形式为函数名([实参表]);add(a,b);实参与形参的个数应相等,类型应一致。实参与形参按顺序对应,一一传递数据。2.函数调用的方式按函数在程序中出现的位置来分,有如下3种函数调用方式。(1)函数语句(2)函数表达式(3)函数参数(1)函数语句。函数语句的调用,是指把被调函数作为一个独立的语句直接出现在主调函数中。例如:printf(%d%d,i,j);max(a,b);/*调用有参函数max*/printstr();/*调用无参函数printstr*/这3个语句都是函数调用语句,简称函数语句。由函数语句直接调用的函数,一般不需要返回值,只要求函数完成某操作。(2)函数表达式。必须有一个函数返回值,例如:c=5*max(a,b);(3)函数参数。例如:main(){printf(%d,max(a,b));}3.调用函数时的注意事项(1)被调函数必须是已存在的函数。(2)在主调函数中,要对被调函数先做声明。如果被调函数在主调函数之前出现,则在主调函数中对被调函数可以不做声明。(3)如果被调函数的返回值为int类型,不需要在主调函数中说明。(4)如果被调用函数的声明放在源文件的开头,则该声明对整个源文件都有效。(5)如果被调用函数的声明是在调用函数定义的内部,则该声明仅对该调用函数有效。(6)函数调用应该注意以下几点。①实参应在个数、类型和顺序上与形参相一致。②实参可以是常量、变量名、数组名、数组元素或表达式,即必须具有确定的值。③为了保证函数调用的正确性,在调用之前应该先弄清被调用函数的功能、输入参数、返回值等,然后再进行调用。【例7-5】编制程序,求两数的乘积。main(){floatmul();/*进行两数相乘的函数*/floatx,y,z;/*定义主函数内部的局部函数*/scanf(%f%f,&x,&y);/*输入要进行相乘的两个数*/z=mul(x,y);/*调用函数,进行两数的相乘*/printf(Theproductis%f,z);/*输出结果*/}floatmul(floatx,floaty)/*函数及形参类型定义*/{floatz;/*定义浮点变量*/z=x*y;/*两数相乘*/return(z);/*返回结果*/}7.4函数的返回值及其类型一个函数有其特定的功能,也有其功能所实现的结果。这一结果可以通过函数的返回值表现出来。函数的返回值通过函数体内的return语句实现。return语句的格式为return表达式;或return(表达式);如果没有返回值,写为return;用“void”定义无返回值函数,只需在定义函数时,在函数名前加上void即可。例如:voidprintstr();/*定义pritstr为无返回值函数*/{…}函数类型决定返回值的类型。类型不一致时,对于数值型数据可以自动进行类型转换。如果函数有返回值,则在函数定义和函数调用时,一般都应该指明返回值的类型。例如:floatcount(intn){floats;…return(s);}在以下情况中,可以不在调用函数内对被调用函数进行类型说明:(1)当被调用函数的定义位于调用函数之前时,可以不必进行类型说明(2)函数没有返回值或返回值的类型为整型或字符型。(3)C语言允许在所有函数的外面、文件的开头对函数的类型进行说明,这样在调用函数时就可以不对被调用函数的类型进行说明。【例7-6】编一函数,求x的n次方的值,其中n是整数。此程序可以将x和n作为函数参数,所求结果通过return语句返回调用程序。doublepower(floatx,intn){inti;doublepw;pw=1;for(i=1;i=n;i++)pw*=x;return(pw);}7.5函数调用时参数间的传递7.5.1变量、常量、数组元素作为函数参数形参和实参分别占用不同的存储单元,这种传递方式称为“值传递”。实参——〉形参(单向)阅读下列程序,观察程序的运行结果。main(){inta=2,b=3,c=0;printf((1)a=%d,b=%d,c=%d\n,a,b,c);try(a,b,c);printf((4)a=%d,b=%d,c=%d\n,a,b,c);}try(intx,inty,intz){printf((2)x=%d,y=%d,z=%d\n,x,y,z);z=x+y;x=x*x;y=y*y;printf((3)x=%d,y=%d,z=%d\n,x,y,z);}运行结果为(1)a=2,b=3,c=0(2)x=2,y=3,z=0(3)x=4,y=9,z=5(4)a=2,b=3,c=0由以上输出结果可看出:(1)是在主函数中未调用try函数前执行printf函数的结果;(2)是try函数中形参值未发生变化时的结果;(3)是try函数中形参值发生变化后的结果;(4)是try函数调用结束后返回调用函数后的结果。可以看出,形参的变化未曾影响主函数中的实参,所以输出结果(1)和(4)是完全相同的。7.5.2数组名作为函数参数作为实参的数组名将数组元素首地址传递给形参所表示的数组名,即实参传给形参的是地址。【例7-10】编写程序,由主函数输入字符串,调用一函数使输入的字符串按反序排列,并在主函数中输出字符串。main(){voidfun();/*函数声明*/charstr[50];printf(inputstrplease:);scanf(%s,str);fun(str);printf(反序后字符串:%s\n,str);}voidfun(charstr1[]){charc;inti,j;j=strlen(str1);for(i=0;ij/2;i++,j−−){c=str1[i];str1[i]=str1[j−1];str1[j−1]=c;}}运行结果为输入:ABCDEF输出:FEDCBA程序说明:(1)在主调函数main中,定义了数组str,并调用fun函数,数组名str作为实参。(2)被调函数fun中,str1为形参数组名,它与实参数组名类型必须一致。(3)调用fun函数时,只是将实参数组的首地址传递给形参数组,故实参数组与形参数组的长度可以不一致,其大小由实参数组决定。例如形参数组的定义为fun(charstr1[])。7.6函数的嵌套调用函数的嵌套调用:在某函数体中调用了另一个函数,则在该函数被调用的过程中将发生另一次函数调用。intf1()/*定义函数f1*/{…}intf2()/*定义函数f2*/{…f1();}/*f2中调用函数f1*/voidmain(){…f2();/*主函数中调用函数f2*/}图7-3函数嵌套调用过程7.7函数的递归调用递归调用:一个函数自己调用自己。【例7-12】用递归算法计算n!根据数学知识,负数没有阶乘,0的阶乘为1,正整数n的阶乘为n×(n−1)×(n−2)×…×2×1可以用下列式子表示:由以上表达式可以看出:当n0时,求n!可以转化为求解n×(n−1)!的新问题,而求解(n−1)!与原来求n!的方法完全相同,只是所处理的对象在递减1,由n变成了(n−1)。依此类推,求(n−1)!的问题又可转化为(n−1)(n−2)!的问题,直至所处理对象的值减至0(即n=0)时,阶乘的值为l,递归结束,不再进行下去。至此,求n!的这个递归算法结束。总之,上面的公式说明了每一循环的结果都有赖于上一循环的结果,递归总有一个“结束条件”,例如n!的结束条件为n=0。floatfac(intn){intt;if(n==0||n==1)t=1;elset=fac(n−1)*n;returnt;}main(){intn,t;printf(entern:);scanf(%d,&n);if(n0)printf(n0,dataerror!\n);elset=fac(n);printf(\n%d!=%d,n,t);}运行结果为输入:5↙输出:5!=1207.8局部变量和全局变量一个函数中的语句和数据都有自己的作用域。根据作用域的不同,变量可以分为局部变量和全局变量。7.8.1局部变量在一个函数内部定义的变量称做局部变量,也称内部变量。作用域:本函数内部,不能在其他函数中使用。形参a,b和变量i,k有效intf1(inta,intb){inti,k;:}在f1函数内部形参a,b和变量i,k有