1、C语言程序设计教程Huanghuai University Department of Computer Science主讲:傅主讲:傅 丰丰黄淮学院黄淮学院计算机计算机科学科学系系高等教育出版社谭浩强 张基温等编著第四章第四章 模块化程序设计模块化程序设计 1 函数 4.1.1 C程序结构 4.1.2 函数定义定义与函数声明声明 4.1.3 函数的(传值)调用调用 4.1.4 函数的嵌套嵌套调用 4.1.5 函数的递归递归调用 2 变量的存储属性 3 编译预处理2学时2学时本节本节2学时2学时1 函数(程序模块)函数(程序模块)一、一、C程序的结构程序的结构v 较大的程序一般分为若干个程序模
2、块较大的程序一般分为若干个程序模块。每个模块实现一个特定的功能。每个模块实现一个特定的功能。v常将一些常用的功能模块编写成函数,以被多个程序调用常将一些常用的功能模块编写成函数,以被多个程序调用。main()f1();f2();f1()f11();f2()f21();f22();f11()f21()f22()main()f1()f2()f11()f21()f22()P102例例1(略)(略)函数值类型函数值类型 函数名(类型函数名(类型 形参形参,)函数体函数体二、函数定义与函数声明二、函数定义与函数声明1、函数定义(自定义函数,即程序模块)、函数定义(自定义函数,即程序模块)1、函数值类型:
3、即函数体中、函数值类型:即函数体中return语句中表达式值的类型。无返回值时语句中表达式值的类型。无返回值时(无(无return语句),函数值类型为语句),函数值类型为void。默认为。默认为int型。型。2、形式参数:形参间用逗号分隔,无参时括号不能省。编译时不为、形式参数:形参间用逗号分隔,无参时括号不能省。编译时不为形参形参分配存储空间,调用时才临时为其分配存储空间,从调用函数的分配存储空间,调用时才临时为其分配存储空间,从调用函数的实参实参得得到值,称为到值,称为“虚实结合虚实结合”。调用结束时,形参所占空间被释放。调用结束时,形参所占空间被释放。3、函数体:由声明部分和语句部分组成
4、。函数体中定义的变量只在执行、函数体:由声明部分和语句部分组成。函数体中定义的变量只在执行该函数时才存在。声明部分和语句部分都省略时,为空函数:该函数时才存在。声明部分和语句部分都省略时,为空函数:void f()4、函数的返回:函数执行到最后一个操作或遇到、函数的返回:函数执行到最后一个操作或遇到return语句时,语句时,返回返回主主调函数,同时调函数,同时撤消撤消为函数体中为函数体中变量变量及及形参形参分配的存储空间。分配的存储空间。5、函数不能嵌套定义,一个函数不能定义在别的函数内部。、函数不能嵌套定义,一个函数不能定义在别的函数内部。说说明明声明部分声明部分语句部分语句部分retur
5、n(表达式表达式);所需的已知量所需的已知量P104例例2:找出函数定义部分:找出函数定义部分 main()double new_style(int a,double x);double new_style(int a,double x)/*函数体*/func1()func2()错错 P107:函数不能嵌套定义函数不能嵌套定义int jdz(int x)return(x=0?x:-x);P105例例3:编写求一个整数的绝对值的函数:编写求一个整数的绝对值的函数 main()int a,b;scanf(“%d”,&a);b=jdz(a);printf(“%dn”,b);函数定义函数定义 函数调用
6、函数调用 形参形参 实参实参 实参实参形参形参执行函数体执行函数体返回调用处返回调用处P106例例4:编写打印:编写打印n个空格的函数个空格的函数void spc(int n)int i;for(i=1;i=n;i+)printf(“”);main()int m;scanf(“%d”,&m);spc(m);函数定义函数定义 函数调用函数调用 形参形参 实参实参 实参实参形参形参执行函数体执行函数体返回调用处返回调用处P106例例5:编写求表达式值的函数:编写求表达式值的函数:y=float f(float x)if(x0)return(x*x+x+1);else return(x*x*x+x+
7、3);main()float x,y;scanf(“%f”,&x);y=f(x);printf(“%fn”,y);函数定义函数定义 函数调用函数调用 形参形参 实参实参 实参实参形参形参执行函数体执行函数体返回调用处返回调用处 x2-x+1 x10)return(2*n+3);else return();错错 P107例例6:例:函数定义:例:函数定义:double func(double a,int b,float c)/*函数体函数体*/函数声明应为:函数声明应为:double func(double a,int b,float c);或:或:double func(double x,in
8、t y,float z);1、可不写形参名:、可不写形参名:double func(double,int,float);2、不能只写形参名而不写类型:、不能只写形参名而不写类型:double func(x,y,z);3、只有函数返回值为、只有函数返回值为int或或char时,函数值类型才可以省略。时,函数值类型才可以省略。4、函数定义的声明中形参的次序与类型要一致。如:、函数定义的声明中形参的次序与类型要一致。如:double func(int y,float z,double x);错误错误5、当某函数要被多个函数调用时,可将函数的调用声明写在、当某函数要被多个函数调用时,可将函数的调用声明
9、写在所有函数前。如:所有函数前。如:2、函数声明:、函数声明:函数值类型函数值类型 函数名(类型函数名(类型 形参,形参,););对在本函数中要调用的函数所做的说明对在本函数中要调用的函数所做的说明 函数原型(函数函数原型(函数定义的第一行)定义的第一行)说说明明fun1()f(a,b);fun2()f(c,d);float f(float x,float y)main()float f(float x,float y);三、函数的三、函数的(传值传值)调用调用函数名(实参,实参,函数名(实参,实参,)无返回值:函数名无返回值:函数名(实参表实参表);有返回值:变量有返回值:变量=函数名函数名
10、(实参表实参表);P109例例7:main()int a=3,b=5;void swap(int x,int y);swap(a,b);printf(“a=%d,b=%dn”,a,b);void swap(int x,int y)int temp;temp=x,x=y,y=temp;printf(“x=%d,y=%dn”,x,y);函数调用函数调用 函数定义函数定义形参形参 实参实参 执行函数体执行函数体返回函数值返回函数值释放相应空间释放相应空间函数声明函数声明 实参实参形参形参实参实参 形参形参 35ab3 35xy533tempx=5,y=3a=3,b=5程序的运行:程序的运行:值传递值
11、传递P110例例8:实参与形参的个数类型要一致实参与形参的个数类型要一致main()float add();float x=1.5,y=-5.7;printf(“%f+%f=%fn”,x,y,add(x,y);float add(unsigned int a,unsigned int b)printf(“a=%u,b=%un”,a,b);return(a+b);函数调用函数调用 形参形参 实参实参 执行执行函数函数体体返回函数值返回函数值释放相应空间释放相应空间函数声明函数声明 实参实参形参形参实参实参 形参形参 1.5-5.7xyaba=0,b=01.500000+-5.700000=0.0
12、00000程序的运行:程序的运行:00函数定义函数定义P136习题习题9:写出程序的输出结果:写出程序的输出结果main()int i,j,x,y,n,g;i=2;j=3;g=x=5;y=9;n=7;fun(n,6);printf(“g=%d;i=%d;j=%d;n”,g,i,j);printf(“x=%d;y=%dn”,x,y);fun(n,6);fun(int i,int j)int x,y,g;g=8;x=7;y=2;printf(“g=%d;i=%d;j=%dn”,g,i,j);printf(“x=%d;y=%dn”,x,y);形参形参 实参实参 实参实参 7n6g=8;i=7;j=6
13、;x=7;y=2g=5;i=2;j=3;x=5;y=9g=8;i=7;j=6;x=7;y=2程序的运行:程序的运行:ijgxy23559形参形参 ij76gxy872形参形参 ij76gxy872main()u=f1(i,t);函数不能嵌套定义,但可以嵌套调用函数不能嵌套定义,但可以嵌套调用四、函数的嵌套调用四、函数的嵌套调用 c=f2(b-1,b+1);return(x);main函数函数f1函数函数f2函数函数课堂练习课堂练习main()int x,n,s;s=power(x,n);P136习题习题1:指出程序中的错误:指出程序中的错误power(y)int i,p=1;for(i=1;i
14、=n;+i)p=p*y;power函数的调用未声明。函数的调用未声明。实参实参x、n未赋值。未赋值。实参实参x、n与形参与形参y个数不符。个数不符。整个程序无输出。整个程序无输出。未说明形参未说明形参y的类型。的类型。变量变量n未定义,应将之作为未定义,应将之作为形参定义。形参定义。通过主函数对通过主函数对power函数的函数的调用可以看出该函数有返回值,调用可以看出该函数有返回值,因此因此power函数定义中应有函数定义中应有return(p);语句,且在第一行语句,且在第一行应说明函数值的类型。应说明函数值的类型。最后的最后的;号多余。号多余。求求ynmain()int n;char ch
15、;scanf(“%c%d”,&ch,&n);p(ch,n);P136习题习题3:编写一个函数重复打印给定的编写一个函数重复打印给定的字符字符n次次void p(char c,int n)int i;for(i=1;i=n;i+)printf(“%c”,c);printf(“n”);#define PI 3.14#include“math.h”main()int x,y,i;double rd=PI/180;for(x=0;x=360;x=x+15)y=(int)(10+10*sin(x*rd);for(i=1;i=y;i+)printf(“”);printf(“*n”);P136习题习题2:画
16、正弦曲线:画正弦曲线 若画下列形式的正弦若画下列形式的正弦曲线,则每行应输出:曲线,则每行应输出:y个空格、个空格、1个个*号、换行号、换行 空格数空格数y与与sin值有关。值有关。而而sin值在值在-1,1 内,故内,故应将放大同样倍数的应将放大同样倍数的sin值作为值作为y值。值。算法分析:算法分析:y个空格个空格*sin(x)P136习题习题4:编写求一个给定数字的所有因子的函数。编写求一个给定数字的所有因子的函数。void f(int n)int i=2;while(n!=i)if(n%i=0)printf(“%d*”,i);n=n/i;else i+;printf(“%dn”,n);
17、如:如:72=2*2*2*3*3n变小变小i变大变大递推函数递推函数n=n/i n%i=0 是是 否否当因子当因子i i不是不是n n时(还有因子)时(还有因子)输出因子输出因子i i=i+1输出最后没有因子的数输出最后没有因子的数n n或或i ii i取最小因子取最小因子2 2判断正负判断正负P136习题习题8:编写将编写将整数整数转换为字符串的函数转换为字符串的函数itoavoid itoa(int x)char ch1,ch2,ch3,ch4,ch5;int n;if(x0)printf(“-”);x=-x;if(x/10000!=0)n=5;else if(x/1000!=0)n=4;
18、else if(x/100!=0)n=3;else if(x/10!=0)n=2;else n=1;ch5=x/10000+0;ch4=(x%10000)/1000+0;ch3=(x%1000)/100+0;ch2=(x%100)/10+0;ch1=x%10+0;输出每一位对应的字符输出每一位对应的字符也可用数组实现也可用数组实现switch(n)case 5:printf(“%c”,ch5);case 4:printf(“%c”,ch4);case 3:printf(“%c”,ch3);case 2:printf(“%c”,ch2);case 1:printf(“%c”,ch1);判断判断位
19、数位数求每求每一位一位所对所对应的应的字符字符输出每一位对应的字符输出每一位对应的字符课后作业及上机任务课后作业及上机任务v 教材教材P136习题:习题:14、8v 编写判断一个数是否为素数的编写判断一个数是否为素数的函数,然后用主程序调用该函数。函数,然后用主程序调用该函数。v 编写并调试习题编写并调试习题23、8五、函数的递归调用五、函数的递归调用从一个故事和图片观察到的从一个故事和图片观察到的 从前有座山,山里从前有座山,山里有个庙,庙里有个老和有个庙,庙里有个老和尚,老和尚在对小和尚尚,老和尚在对小和尚讲故事,故事讲的是:讲故事,故事讲的是:“从前有座山,山里有从前有座山,山里有个庙,
20、庙里有个老和尚,个庙,庙里有个老和尚,老和尚在对小和尚讲故老和尚在对小和尚讲故事,故事讲的是:事,故事讲的是:从从前有座山,山里有个前有座山,山里有个庙庙”。故事和图片都是直接由这个故事和图片本身组成的故事和图片都是直接由这个故事和图片本身组成的。观察到的规律:观察到的规律:P111图图4.8说明:这里主要讨论函数的直接递归调用说明:这里主要讨论函数的直接递归调用 递归递归就是某一事物就是某一事物直接直接或或间接间接地由自己组成。如果一个地由自己组成。如果一个函数在它的函数体内,直接或间接地调用自身,则称这个函函数在它的函数体内,直接或间接地调用自身,则称这个函数为数为递归函数递归函数,称这种
21、调用方式为,称这种调用方式为函数的递归调用函数的递归调用。1 1、递归的概念、递归的概念int f(int n)x=f(n-1);int A(int n)x=B(n-1);int B(int m)y=A(m-1);直接递归直接递归间接递归间接递归2 2、递归函数的执行、递归函数的执行 执行过程(设执行过程(设n=3)n=3):P111例例9:通过函数的递归调用计算n的阶乘。long fact(int n)long x;if(n=1)x=1;else x=n*fact(n-1);return(x);main()int n;long y;scanf(%d,&n);y=fact(n);printf(
22、%d!=%ldn,n,y);y=fact(3)输出输出yfact(3)main()x=3*fact(2)返回返回x的值的值x=2*fact(1)返回返回x的值的值x=1 返回返回x的值的值fact(2)fact(1)123645递归递归回溯回溯参数参数传递传递执执行行返回返回递归递归函数函数递归递归公式公式终止终止条件条件3、递归函数的编写、递归函数的编写 从要解决的问题出发,按倒推倒推的方法来思考:如果要解决一个规模大的问题,只要解决与之相似的小规模问题就好了(寻找);直到倒推到一个显而易见的已知问题为止,使这个已知的问题成立的条件就是。long fact(int n)long x;if(n
23、=1)x=1;else return(x);1 n1fact(n)=(n-1)!=(n-1)*(n-2)!2!=2*1!1!=1fact(n-1)=(n-1)*fact(n-2)fact(2)=2*fact(1)fact(1)=1设设f(n)为求为求数列数列第第n项的函数,项的函数,则:则:f(n)=f(n-1)+f(n-2)f(n-1)=f(n-2)+f(n-3)f(3)=f(1)+f(2)而而f(1)=f(2)=1问题:问题:用递归函数求Fibonacci数列的第n项。课堂训练(补充)课堂训练(补充)递归公式和终止条件:递归公式和终止条件:递归函数:递归函数:long f(int n)lo
24、ng x;if()x=1;else return(x);倒推倒推1 n2 f(n)=321算法分析:算法分析:P113例例10:汉诺(:汉诺(hanoi)塔问题)塔问题 相传在古代印度的布拉玛(相传在古代印度的布拉玛(Bramah)庙中,僧侣们)庙中,僧侣们常玩一种称为汉诺塔(常玩一种称为汉诺塔(Tower of Hanoi)的游戏,据说)的游戏,据说游戏结束就标志着世界末日的到来。游戏的装置是一块游戏结束就标志着世界末日的到来。游戏的装置是一块铜板,上面有三根杆,最左杆从上到下、铜板,上面有三根杆,最左杆从上到下、从小到大从小到大顺序顺序串了串了64个盘子,呈塔形。游戏要求把盘子个盘子,呈塔
25、形。游戏要求把盘子从一根杆移到从一根杆移到另一根杆另一根杆,移动过程中要求:,移动过程中要求:一次只能移动一个盘子一次只能移动一个盘子,并且并且不允许大盘在小盘上面不允许大盘在小盘上面。如果每秒移动一只盘子,也需要大约如果每秒移动一只盘子,也需要大约5800亿年才能完成。亿年才能完成。hanio(n-1,a,c,b);printf(Move NO%d from%c to%cn”,n,a,b);hanio(n-1,c,b,a);终止条件:终止条件:把把n-1个盘子个盘子从从a借助借助b移到移到cn0时,n=0时,终止递归终止递归A nn-11C n-11B nn-11 123把第把第n个盘个盘子
26、从子从a移到移到b把把n-1 个盘子个盘子从从c借助借助a移到移到b递归函数:递归函数:void hanio(int n,char a,char b,char c)if(n0)递归递归分析:分析:设按规则把设按规则把n个盘子从个盘子从A借助借助C移到移到B的算法为的算法为hanoi(n,a,b,c),则:,则:请仔细体会请仔细体会递归算法的递归算法的执行过程。执行过程。汉诺塔汉诺塔游戏游戏汉诺塔问题的具体执行过程汉诺塔问题的具体执行过程演示程序:演示程序:zlhnt.exe小小 结结v 代码清晰、简洁。代码清晰、简洁。v 符合人们的思维方式,使复杂问题自然、易于理解。符合人们的思维方式,使复杂
27、问题自然、易于理解。v 执行效率不高。执行效率不高。递归算法在可计算性理论中占有重要地位,递归算法在可计算性理论中占有重要地位,对拓展编程思路非常有用。但要建立起递归概念对拓展编程思路非常有用。但要建立起递归概念并不容易。特别是并不容易。特别是递归函数的执行过程递归函数的执行过程,是本节,是本节也是本课程的一个重点和难点。也是本课程的一个重点和难点。递归算法的优缺点:递归算法的优缺点:优点优点缺点缺点P136习题习题5:编写计算函数编写计算函数Ack(m,n)的值的递归函数。的值的递归函数。long Ack(int m,int n)long y;if(m=0)y=n+1;else if(n=0
28、)y=Ack(m-1,1);else y=Ack(m-1,Ack(m,n-1);return(y);Ack(0,n)=n+1Ack(m,0)=Ack(m-1,1)Ack(m,n)=Ack(m-1,Ack(m,n-1)课堂练习课堂练习float H(int n,float x)float i,x0,x1,x2;x0=1.0,x1=2*x;switch(n)case 0:return(x0);break;case 1:return(x1);break;default:if(x=1)printf(“errorn”);else for(i=2.0;i1)return(2*x*H(n-1,x)-2*(n
29、-1)*H(n-2,x);else printf(“errorn”);递归函数递归函数P136习题习题6:分别写出计算分别写出计算Hermite多项式多项式Hn(x)的值的的值的 递推和递归函数。递推和递归函数。H0(x)=1H1(x)=2xHn(x)=2x Hn-1(x)-2(n-1)Hn-2(x)当当x1时时递推函数递推函数递推递推公式公式x0 x1 x2=2x*x1-2(n-1)*x0 x0 x1 x2=课后作业及上机任务课后作业及上机任务v 教材教材P136习题:习题:5、6v 编写一个求编写一个求n的阶乘的函数,然后调用该函数求的阶乘的函数,然后调用该函数求1!+2!+3!+4!+5!。!。v 编写并调试习题编写并调试习题5、6v通过汉诺塔游戏(通过汉诺塔游戏(hanio.swf或或zlhnt.exe),体会递),体会递归算法的执行过程归算法的执行过程v思考扩展汉诺塔问题的算法:思考扩展汉诺塔问题的算法:m个杆个杆,有一根杆上,有一根杆上自上而下从小到大有自上而下从小到大有n个盘子,一次只能移动一个盘个盘子,一次只能移动一个盘子子,并且,并且不允许大盘在小盘的上面不允许大盘在小盘的上面,请问将这些盘子,请问将这些盘子全部移动到另一根杆上,最少需要移动多少次?全部移动到另一根杆上,最少需要移动多少次?