【文档说明】C语言课件第8章函数.ppt,共(61)页,386.366 KB,由小橙橙上传
转载请保留链接:https://www.ichengzhen.cn/view-44588.html
以下为本文档部分文字说明:
第8章函数主要内容:1、函数的定义2、函数的调用(包括嵌套调用和递归调用)4、局部变量和全局变量5、变量的存储类别重点:1)如何定义函数(即如何编写函数)2)如何调用函数(包括库函数和自定义函数)函数的引入函数:函数是完成某些特定功能的代码块。使用函数的优点:1)实现模块化设计:将一个
大任务分解成一个个的小任务,然后每个任务分别用函数实现。2)实现“编写一次,多次调用”,避免在不同的程序中重复编写相同的函数。3)便于程序调试和维护,因为每个函数之间是相互独立的。函数的分类:库函数和用户自定义函数8.1函数的定义例8.1编写函数max:求两个整数x和y中的较大数。intmax(i
ntx,inty)/*函数头*/{intz=x;/*函数体{……}*/if(x<y)z=y;returnz;}main(){inta,b,d;printf("inputaandb:");scanf{"%d%d"
,&a,&b};d=max(a,b);/*函数调用*/printf("maxis:%d",d);}例8.2:设某程序中需多次用到打印表头(如下所示)的功能,该功能用函数实现:voidline(){pr
intf("\n********************");printf("\n*scorelist*");printf("\n********************");}本例中,函数的类型:void,
函数名为line,函数无参数。该函数无返回值,所以函数类型为void型。函数定义的一般形式如下:函数类型函数名(类型名形式参数1,类型名形式参数2,…){说明部分语句部分}1.函数名:是该函数的唯一标识符,一般函数的命名最好做到“见名知义”。2.函数类型:函数的返回值的类型。当函
数无返回值时,规定其类型为:void。当函数返回值为int时,函数类型可以省略。3.形式参数:形参是函数被调用时用于接收实参值的变量。根据实际需要可有可无。没有形参时,圆括号也不可省;多个参数之间应用逗号分隔。参数包括参数名和
参数类型。形参的类型说明可有如下两种格式:intmax(inta,intb)/*形参的类型在形参表中直接说明*/{return(a>b?a:b);}或intmax(a,b)inta,b;/*形参的类型在函数体前、函数名后说明*/{return
(a>b?a:b);}前者为标准格式,后者为传统格式,通常用前者。4.函数体:即函数功能的具体实现。它包括两部分:说明部分和执行部分,其中说明部分包括函数中所用的局部变量等的说明、函数中要调用的函数的
说明。注意:函数不能嵌套定义,即函数内不能再定义函数,这样可以保证函数间是相互独立的,以实现模块化程序设计。5.空函数:函数类型函数名(){}调用此函数时,什么也不做。只是表明这里要调用一个函数,而现在这个函数的功能还没实现。空函数在程序设计中常常用
到的:1)预留函数,便于以后扩充程序功能。2)便于程序的模块化设计和调试:程序设计中往往根据需要确定若干模块,分别由一些函数来实现。一个大系统,需要编写很多用户函数,而这些函数不可能也没有必要同步完成,通常足从一些基本模块开始,编写一个调试一个,对于没有编写的函数就
需要用空函数代替。从而也有利于集体创作。空函数举例例8.3编写小学生算术练习系统的主程序:显示主菜单,用户选择,根据选择执行加、减、乘、除、退出5项功能之一。重复上述步骤,直至选择退出。其中主程序调用的函数有:显示主菜单函数list_menu(),加、减、乘、除、退出函数分别是add
(),sub(),mul(),divide(),end().以上函数除list_menu()外此时均为空函数。程序:l8_1_4.c#include"stdio.h"/*l8_1_4.c*/main()
{voidadd(),sub(),mul(),divide(),end(),list_menu();intn;do{list_menu();scanf("%d",&n);getchar();switch(n){case1:add();break;case2:sub();bre
ak;case3:mul();break;case4:divide();break;case5:end();break;default:printf("\nentererror,pleaseagain.");}
}while(n!=5);}voidlist_menu(){printf("\n**theexercisesystemforprimitivestudents***");printf("\n**1.add**");prin
tf("\n**2.sub**");printf("\n**3.mul**");printf("\n**4.divide*");printf("\n**5.end**\n");}voidadd(){}voidsub()
{}voidmul(){}voiddivide(){}voidend(){}问题:如何定义一个函数第一步:分析函数需要的参数,包括参数的的个数以及每个参数的类型,第二步:分析函数返回值的类型,若无返回值,则为void。函
数的返回值可看作是函数执行完后需输出的一个数据。第三步:编写函数体说明:参数和返回值是函数之间的接口,即函数之间通过参数和返回值进行通信。参数包括执行该函数时需要的数据信息,以及返回数据的有关信息。例如:1)求n!:要处理的数据
是n,因此必须有一个参数n,类型为int。返回值为long型。即longfact(intn){}2)打印表头:不需输入任何数据即可执行该函数,因此无参数。执行该函数无返回值,因此函数类型为void。即voidline(){}3)求两个整数m和n的最小公倍数,
执行该功能时必须有两个整型参数,返回值为整型。程序:l8_1_5.cintmin_multiple(intm,intn){}(见下页)4)求一批整型数据(n个)中的最大值。实现该功能的函数的参数有两个:该批数据的首地址及数据的个数。返回值为一个整型数。intmax(intdata[]
,intn){}/*例3:计算两个整数的最小公倍数l8_1_5.c*/main(){intm,n,min;intmin_multiple(int,int);/*函数声明*/printf("\ninputm,n:")
;scanf("%d%d",&m,&n);min=min_multiple(m,n);/*函数调用*/printf("\nbeishu:%d",min);}intmin_multiple(intx,inty)/*函数定义*/{inti;i=1;while(x*i%y!=
0)i++;return(x*i);}8.2函数的调用重点:1、对被调函数的声明2、如何调用一个函数3、主调函数和被调函数之间如何进行数据传递例8.5调用函数fact()求n!(n由用户输入)。分三种情况:(1)函数fact()与主函数在同一文件中,且main()在fact()前面。(2)
函数fact()与主函数在同一文件中,且main在fact之后。(3)函数fact与main不再同一程序文件中。main()/*(1)main()在fact()前面*/{intn;longp;longfact(int);/*函数声明*/scanf("%d",&
n);p=fact(n);/*函数调用*/printf("\n%ld",p);}longfact(intm)/*函数定义*/{inti;longs=1;for(i=1;i<=m;i++)s*=i;return(s);/*函数返回*/}
结论:被调函数在后,需在主调函数中先声明后调用。/*(2)主调函数在被调函数之后*/longfact(intm)/*函数定义*/{inti;longs=1;for(i=1;i<=m;i++)s*=i;r
eturn(s);}main(){intn;longp;/*不需函数声明*/scanf("%d",&n);p=fact(n);/*函数调用*/printf("\n%ld",p);}结论:被调函数先于主调函数被编译,因此
在编译主调函数时已知被调函数的类型等信息。故不需函数声明。/*(3)设fact()函数存放在文件f1.c中,则编写主调函数时需用include命令对被调函数声明*/#include<f1.c>main(){intn;longp;scanf("%d",&n);p=fac
t(n);printf("\n%ld",p);}结论:该程序的效果与(2)相同,由此可实现将多个文件连接成一个程序。同理,对库函数的调用都要在main函数前用include命令将函数所在的头文件包含进来。函数声明小结:被调函数必须是已存在的函数,
通过“函数声明”告知编译系统关于被调函数的有关信息。函数声明的形式:函数类型函数名(参数表);(注意与函数定义的区别)1、若被调函数是库函数或用户已编写的函数(与主调函数不在同一文件中),则使用前需在程序的开头用include命令将被调函数的信息
包含进来。2、若主调函数与被调函数在同一文件内,且主调函数在前,则必须在主调函数的说明部分或主调函数的前面对被调函数进行说明。3、以下情况下可以省略对被调函数的说明。a)函数类型为整型b)被调函数在主调函数之前定
义通常,将所有函数的说明集中在程序开头;或将所有函数的信息写入一个文件,编程时用include命令将其包含进来即可。函数的调用与返回过程小结1、函数调用的一般形式:函数名(实参表)如p=fact(n);pr
intf(“%d”,power(2,n));等注意:实参与形参的类型、个数、顺序必须一致。2、调用过程:1)在调用函数时,首先将实参的值赋给形参;再将控制流程转到被调函数;2)然后执行被调函数。3)当被调函数执行到return语句,或执行到被调函数函数体最后的一个
大花括号时,控制流程返回到主调函数的断点处继续执行主调函数。如果被调函数有返回值,则控制流程返回的同时将该返回值带回主调函数。3、函数的返回函数返回的实现:1)函数体中通过执行return语句返回,其格式有3种:return(expre
ssion);或returnexpression;或return;2)若函数体中无return语句,当执行到函数末尾时自动返回到调用函数。注意:1)函数的返回值最多只有一个,可通过return语句返回主
调函数。2)当有多个值需要返回主调函数时,用return语句无法实现,只能通过传地址调用实现。如对数组元素排序等。4.参数传递:实参与形参的结合形参:定义函数时的参数为形参,此时的参数无具体的值,仅仅表示参数的类型、个数、以及在函数体内对其如何处理。其作用是:该函数被调用时用来接收实
参的值.实参;调用函数时的参数为实参,它表示该函数要处理的数据的信息,因此实参必须有确定的值。调用时,将实参的值传给形参。要求:调用函数时,实参与形参的类型、个数必须完全一致。分析例8.4程序的调用过程:明确实参与形参的作用。(传
值调用:单向传递)例8.4以下程序企图通过调用swap函数,交换主函数中变量x和y中的数据。请观察程序的输出结果。voidswap(inta,intb){intt;printf("(2)a=%db=%d\n",a,b);t=a;a=
b;b=t;printf("(3)a=%db=%d\n",a,b);}main(){intx=10,y=20;printf("(1)x=%dy=%d\n",x,y);swap(x,y);printf("(4)x=%dy=%d\n",x,y);}程序运行结果如下:(1)x=10y=20
(2)a=10b=20(3)a=20b=10(4)x=10y=20结论:参数的传递是单向的,即只能由实参传给形参,在被调函数中对形参的改变的不影响实参的值。例8.12编写函数实现:用选择法对n个整数排序。编写主函数实现数据的输入输出。分析:主程
序的算法:S1:输入一批数据(个数为N),存入一维数组aa。S2:调用函数sort(),对一维数组中的数据按从小到大的顺序排序。S3:输出数组aa中的各元素。函数sort()的编写:首先,确定函数的类型:void;参数:一维数组的首地址,数据的个数,共2个;然后编写函数
体,实现排序。重点:分析参数的传递,比较传值与传地址的区别,各有何用处?(图示:形参数组与实参数组的结合方式)/*主函数*/#defineN10main(){intaa[N],i;voidsort(intb[],intn);pri
ntf("\nenterintegersforsort;");for(i=0;i<N;i++)scanf("%d",&aa[i]);sort(aa,N);/*函数调用*/printf("\naftersort:\n");for(i=0;i<N;i++)printf("%6d
",aa[i]);}/*函数sort()*/voidsort(intb[],intn){inti,j,t,k;for(i=1;i<=n-1;i++){k=0;for(j=1;j<=n-i;j++)if(b[k]<b[j])k=j
;t=b[k];b[k]=b[n-i];b[n-i]=t;}/*循环体:排一遍*/}数组aa与数组b实际上是一个数组。b[]的长度无意义,因此可以不说明。例8.12运行结果与图示:enter5integers:1257
3↙aftersort:75321比较两种参数传递方式传值调用:单向数据传递,对形参的改变不影响实参的值,且只能通过return语句返回最多一个值例8.4传地址调用:实参传给形参的是数据的地址,所达到的目的是:形参与实参共用同一片存
储单元,对形参的改变实际上是对实参的改变,从而实现主、被调函数之间的多个数据传递。例8.125.函数的嵌套调用例8.6嵌套调用实例.f(intx){intt;t=x+x;return(t);}g(inta,intb){intz;z
=f(a*b);return(z);}函数类型省略,默认为整型。main(){intx1=2,x2=5,y;y=g(x1,x2);printf("%d",y);}被调函数在前,不需函数声明。调用过程:在
main函数中调用g(),执行g函数的的过程中调用f();执行完f()函数后返回到g()接着执行,执行完g()后返回到main().(图示)例8.7编写函数,验证陈景润研究的哥德巴赫猜想:任意大偶数为两个素数之和并输出这
两个素数(所谓大偶数是指6开始的偶数)。分析:isprime(inta)实现判断一个数a是否为素数;even(intx)实现找到并输出两个素数(其和等于x);main()实现输入一个大偶数,并调用even()输出该大偶数对应的两个素数。设变量x为任意大偶数,可从x中依次减去i,i从2变化到x/2
;依次判断i、x-I是否为素数。步骤如下:①i初值为2。②判断i是否是素数。若是,执行步骤③;若不是,执行步骤⑤。③判断x-i是否是素数。若是,执行步骤④;若不是,执行步骤⑤。④输出结果,返回调用函数。⑤使i增1。⑥重复执行
步骤②。/*例8.7程序*/#include"math.h"intisprime(int);/*函数声明*/voideven(int);main(){inta;printf("Enteraevennumber(≥6):");sca
nf("%d",&a);if(a%2==0&&a>=6)even(a);/*函数调用语句*/elseprintf("The%disn'tevennumber\n",a);}voideven(intx)/*函数定义*/{inti;for(i=2;i<=x/2
;i++)if(isprime(i))if(isprime(x-i)){printf("%d=%d+%d\n",x,i,x-i);return;}}intisprime(inta)/*函数定义*/{inti,k=sqrt(a);for(i=2;i<=k;i+
+)if(a%i==0)return0;return1;}8.4递归调用递归:一个函数直接或间接的调用自己。分析递归问题的关键:1)每次调用自己时参数的变化规律。2)递归结束的条件。例8.8求n!.分析:f(n)=f(n-1)*n(n>1)------规
律f(1)=1(n=1)-------递归结束条件程序:l8_4_1.c分析程序的执行过程,理解递归中的“递进”与“回推”。main()/*程序:L8_4_1.c*/{intn;longp;longf(int);/*对被调函数的声明*/printf("\ninputn:");sca
nf("%d",&n);p=f(n);/*函数调用*/printf("\nn!=%ld",p);}longf(intm)/*函数定义*/{longt;if(m==1)t=1;elset=m*f(m-1);/*函数递归调用*/retu
rn(t);/*函数的返回*/}类似的递归问题:1。猴子吃桃:intpeach(intday){intn;if(d==10)n=1;elsen=2*(peach(day+1)+1);returnn;}2。猜年龄:第一个人说它比第二个人大4岁,第二个人说它比第三个人大4岁,
第三个人说它比第四个人大四岁,第四个人10岁,问:第一个人多大?8.5局部变量和全局变量指变量的作用范围不同。局部变量:在函数体内定义的变量。作用范围:只在本函数内有效如前面例题中的变量、数组等。全局变量:在函数体外定义的变量作用范围:从定义该变量的位置开始,到本源程序文
件结束。程序:/*全局变量x,y*/intx=100;floaty=66.6;f1(){floaty=0;/*局部变量y*/printf(“x=%d\t”,x);printf(“y=%f\t”,y);}intz=1;f2(){inti;/*局部变量i*
/for(i=1;i<5;i++)putchar('*');printf("\nz=%d\t",z);}/*全局变量z从定义起至程序末起作用*/main(){f1();f2();printf(“y=%f\n”,y);/*输出全局变量y*/}运行结果:x=100y=0.000
000****z=1y=66.600000/*全局变量x,y在本程序内起作用*//*局部变量y的作用范围内,全局变量y不作用*/intd=1;/*全局变量d*/fun(intp){intd=5;/*局部变量d*/d+=p++;/*使用局部变量d*/printf("%
d",d);}main(){inta=3;fun(a);d+=a++;/*使用全局变量d*/printf("%d",d);}运行结果:84结论:在同一源程序文件中,若外部变量与局部变量同名,则在局部变量的作用范围内,外部变量不起
作用。局部变量和全局变量小结局部变量:保证了函数之间的独立性。(常用)全局变量:增加了函数之间数据传递的通道,但降低了函数间的独立性,降低了程序的清晰性,因此副作用太大。除非特别需要时,一般不用。占用内存情况:局部变量仅当他所在
的函数被调用时才存在,执行完该函数返回后,该变量不再存在全局变量在程序的全部执行过程中一直存在,直至程序执行完,才释放它所占的内存空间8.6变量的存储类别变量和函数均有两个属性:数据类型和存储类别存储类别指
数据在内存中的存储方式。根据变量的“生存期”不同,变量的存储类别包含以下四种:自动的:auto静态的:static寄存器的:register外部的:extern自动的(auto)变量函数的形参和在函数中定义的变量(通常省略存储类别,)即
隐含指定为自动变量。前面1~7章中的变量均属自动变量。自动变量在需要时系统给他门分配存储空间,在函数调用结束时自动释放这些存储空间。例autointa=2,b=3;与inta=2,b=3;等价。静态的(static)1、静态局部变量:作用域为本函数内部存
储类别为静态存储类,因此其生存期与该函数所在程序运行期间相同。即当函数调用结束时能保留原值,在下一次调用该函数时该变量的值是上一次函数调用结束时的值,直至程序运行结束。。例(nextpage)func(inta,intb)/*例8.6.1*/{staticin
tm=0,i=2;/*静态局部变量m,I*/i+=m+1;m=i+a+b;return(m);}main(){intk=4,m=1,p;/*局部变量k,m,p*/p=func(k,m);printf("%5d",p);p=func(k,m);printf("
%5d\n",p);}运行结果:817/*图示执行过程中变量的存储空间占用及值的变化情况*/intd=1;/*全局变量d*//*例8.6.2*/fun(intp){staticintd=5;/*静态局部变量d*/d+=p;printf("%5d",d)
;return(d);}main(){inta=3;/*自动变量a*/printf("%5d\n",fun(a+fun(d)));}运行结果:61515Register变量registerintk;则给变量k分配的空间为某个
寄存器。优点:速度快。只有局部变量和形参可以定义为register变量。因为机器的寄存器数量有限,因此该类型不常用。外部的(extern)变量用extern声明外部变量,是为了扩展外部变量的作用域。因外部
变量不常用,因此extern也很少使用。必须使用外部变量时,一般建议使用静态全局变量。即在函数体外定义变量时存储类别为:static如staticinta=2;f(){……}变量a的作用范围仅限于本源程序文
件内。其它程序中即使用externinta;声明也不能引用a。8.7内部函数与外部函数外部函数:如不加特别说明,函数都是全局的,即外部的,一个函数可以调用另一文件中的函数。(项目文件的使用)内部函数:存储类别为static,该函数仅限于本程序文件使用,其它程序不能调用之。综合题1编程实现小学
生算术练习系统:主菜单包括5项(加法、减法、乘法、除法、退出),前四项中每一项又包括子菜单(一级、二级、三级、返回4项),其中一级实现10以内的运算,二级实现50以内的整数运算,三级实现100以内的整数运算;进入某一级后,反复练习(由机器产生两个随机数,用户输入运算结果,输
出正确或错误)待结束时给出本级题目中计算正确的百分比。要求每个功能分别用函数实现。程序:l8_t.c综合题2自动阅卷程序:设单选题20个(2分/题),多选题20个(每题4个供选答案,3分/题,且只要与答案不一致即错)。编写函数实现:阅单选题,阅多选题,阅一个人的答题;编写主函数实
现批阅N个人的答题卡(正确答案在主程序中输入)。分析:Main():1)输入正确答案,分别存入一维数组dd,二维数组ss;2)批阅N个人的答题(用循环),将成绩存入数组sc。3)输出每个人的最后成绩。Person():函数类型为int;参数两个:dd,ss;函数
体:1)输入某人的答案,分别存入dd1和ss1,2)分别调用函数single()和many()判别对错并计分,返回总成绩。Single():函数类型int,参数dd,dd1;函数体:统计数组dd与dd1
中相同元素的个数,乘2即得单选题的成绩,返回该成绩。Many():函数类型int,参数ss,ss1;函数体:统计数组ss与数组ss1中对应行元素相同的行数,乘3即得多选题的成绩,并返回。然后分析各函数的具体实现算法。程序1
:l8_3_2.c分析程序中的传地址调用,嵌套调用时程序的执行过程。程序2:l8_3_2_q.c定义存放正确答案的数组为全局的,则dd,ss将不作为参数使用。(一般情况使用参数传递)#include"stdio.h"/*程序1*/#defineN400/*numofperson*/#d
efineNUM20/*numofquestion*/main(){chardd[NUM+1],ss[NUM+1][5];intsc[N+1],i;printf("\nenterrightanswerofsin
gleselect:\n");printf("\n(format:pressenterafterfinishedinputingallofanswers.\n");for(i=1;i<=NUM;i++)dd[i]=getchar();getchar();printf("\nen
teranswerofmultiselect:\n");printf("\nformat:(pressenterafterinputingaquestion)\n");for(i=1;i<=NUM;i++)gets(ss[i]);for(i=1;i<=N;i++)sc[i]=pe
rson(dd,ss);printf("\nno:score:");for(i=1;i<=N;i++)printf("\n%-6d%-6d",i,sc[i]);}intperson(chardd[],char
ss[][5]){chardd1[21],ss1[31][5];ints,i;intsignle(),many();printf("\nenteranswerofsingleselect:");for(i=1;i<=NUM;i++)dd1[i]=
getchar();getchar();printf("\nenteranswerofmultiselect:");for(i=1;i<=NUM;i++)gets(ss1[i]);s=single(dd,dd1)+many(ss,ss1);return(s);}intsingle(cha
rdd[],chardd1[]){intn=0,i;for(i=1;i<=NUM;i++)if(dd[i]==dd1[i])n++;return(2*n);}intmany(charss[][5],charss1[][5]){
intn=0,i;for(i=1;i<=NUM;i++)if(!strcmp(ss[i],ss1[i]))n++;return(n*3);}多个文件组成一个程序的方法:1、使用project生成项目文件见ch
16.82、使用include将所有的文件包含到一个文件中。例8.5(3)作业8.1、8.2、8.3、8.4、8.10练习1、求一元二次方程的根.要求:a,b,c由主函数输入,用3个函数分别求b2-4ac的值大于0、等于0、小于0时方程的根并输出。
分析:主函数:(1)输入a,b,c(2)计算d=b2-4ac,然后根据d的值大于0、等于0、小于0分别调用三个函数:f1()f2()f3()分析各个函数的实现:函数类型函数名(形参及类型)程序:x8_
1.c(常见错误)练习例8.2(程序x8_2.c)main(){voidadd(float,float);floata,b;scanf("%f%f",&a,&b);add(a,b);}voidadd(floatx,
floaty){floats;s=x+y;printf("\n%f",s);}问题:若将函数add()的类型改为float,则运行时出错:46CR10.000000floatpointerror
:domain若改为float后在add函数体末加return(s);则结果正确。若将add声明中的参数去掉,则实参并未传给形参。若参数为整型,则函数声明时参数类型可可省略。递归调用练习x8.9:分析:函数类型:float函数名:pp参数:2个n(整型),x(实型)函数体:根
据n的值决定采用3个公式中的一个计算函数值。(用选择结构)当n>1时,计算函数值的过程中又调用函数pp,即递归调用。参数的变化规律为:n-1,n-2。即if(n>1)h=2*x*pp(n-1,x)-2*(n-1)*pp(n-2,x)递归结束的条件为:n
等于0或n等于1程序:x8_13.c通过参数在函数间传递数据X8.14分析:主函数调用4个函数,设函数名分别为average1,average2,max_sc,jun.参数分别为:voidaverage1(
floats[][5],floata[])voidaverage(floats[][5])voidmax_sc(floats[][5])voidjun(floata[])分别实现的功能为: