310 likes | 510 Views
北京大学出版社. 《 C 语言程序设计案例教程 》. 汪新民、刘若慧主编. 主讲:傅 丰 黄淮学院信息工程学院. Huanghuai University Department of Information Engineering. 第四章 模块化程序设计. §1 函数 §4.1.1 C 程序结构 §4.1.2 函数 定义 与函数 声明 §4.1.3 函数的(传值) 调用 §4.1.4 函数的 嵌套 调用 §4.1.5 函数的 递归 调用 §2 变量的存储属性 §3 编译预处理. 2 学时. 本节. 2 学时.
E N D
北京大学出版社 《 C语言程序设计案例教程》 汪新民、刘若慧主编 主讲:傅 丰 黄淮学院信息工程学院 Huanghuai University Department of Information Engineering
第四章模块化程序设计 • §1 函数 • §4.1.1 C程序结构 • §4.1.2 函数定义与函数声明 • §4.1.3 函数的(传值)调用 • §4.1.4 函数的嵌套调用 • §4.1.5 函数的递归调用 • §2 变量的存储属性 • §3 编译预处理 2学时 本节 2学时
§1 函数(程序模块) f1( ) { … f11( ); … } f11( ) { … } main( ) main( ) { … f1( ); … f2( ); … } f1( ) f2( ) f21( ) { … } f2( ) { … f21( ); … f22( ); … } f11( ) f21( ) f22( ) f22( ) { … } 一、C程序的结构 P102例1(略) • 较大的程序一般分为若干个程序模块。每个模块实现一个特定的功能。 • 常将一些常用的功能模块编写成函数,以被多个程序调用。
二、函数定义与函数声明 函数值类型函数名(类型形参,…){函数体} 声明部分 语句部分 1、函数定义(自定义函数,即程序模块) 所需的已知量 return(表达式); 说 明 1、函数值类型:即函数体中return语句中表达式值的类型。无返回值时(无return语句),函数值类型为void。默认为int型。2、形式参数:形参间用逗号分隔,无参时括号不能省。编译时不为形参分配存储空间,调用时才临时为其分配存储空间,从调用函数的实参得到值,称为“虚实结合”。调用结束时,形参所占空间被释放。3、函数体:由声明部分和语句部分组成。函数体中定义的变量只在执行该函数时才存在。声明部分和语句部分都省略时,为空函数:void f( ) { }4、函数的返回:函数执行到最后一个操作或遇到return语句时,返回主调函数,同时撤消为函数体中变量及形参分配的存储空间。5、函数不能嵌套定义,一个函数不能定义在别的函数内部。
P107: 函数不能嵌套定义 P104例2:找出函数定义部分 main( ) { … double new_style(int a,double x); … } double new_style(int a,double x) { … /*函数体*/ } func1( ){ …func2( ) { … } …} 错
P105例3:编写求一个整数的绝对值的函数 int jdz(int x) { return(x>=0?x:-x); } 函数定义 ② 执行函数体 形参 ① 实参形参 main( ) { int a,b; scanf(“%d”,&a); b=jdz(a); printf(“%d\n”,b); } ③ 返回调用处 函数调用 实参
P106例4:编写打印n个空格的函数 void spc(int n) { int i; for (i=1;i<=n;i++) printf(“ ”); } 函数定义 形参 ② 执行函数体 ① 实参形参 main( ) { int m; scanf(“%d”,&m); spc(m); } ③ 返回调用处 函数调用 实参
x2-x+1 x<0 x3+x+3 x≥0 P106例5:编写求表达式值的函数:y= float f(float x){ if (x<0) return(x*x+x+1); else return(x*x*x+x+3);} 函数定义 一个函数中允许有一 个或多个reutrn语句。 每个return语句中表 达式的类型应相同。 形参 ② 执行函数体 P107例6: func(int n){ if (n>10) return(2*n+3); else return( );} ① 实参形参 main( ){ float x,y; scanf(“%f”,&x); y=f(x); printf(“%f\n”,y);} ③ 返回调用处 实参 函数调用 错
float f(float x,float y); main( ){ …} fun1( ){ …f (a,b);…} ③ fun2( ){ …f (c,d);…} ① float f(float x,float y){ …} ② 2、函数声明: 对在本函数中要调用的函数所做的说明 函数原型(函数定义的第一行) 函数值类型函数名(类型形参,…); 例:函数定义:double func(double a, int b, float c) { /*函数体*/ }函数声明应为:double func(double a, int b, float c);或:double func(double x, int 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、当某函数要被多个函数调用时,可将函数的调用声明写在所有函数前。如:
三、函数的(传值)调用 无返回值:函数名(实参表); 有返回值:变量=函数名(实参表); 函数名(实参,实参,…) main( ){ int a=3,b=5; void swap(int x,int y); swap(a,b); printf(“a=%d,b=%d\n”,a,b);} P109例7: 程序的运行: a b 函数声明 实参 函数调用 3 5 实参 ① 实参形参 ①值传递 x y ③ 形参 函数定义 void swap(int x,int y) { int temp; temp=x, x=y, y=temp; printf(“x=%d,y=%d\n”,x,y); } 形参 3 3 5 temp ③返回函数值 释放相应空间 ② ② 执行函数体 3 5 3 x=5,y=3 a=3,b=5
P110例8: 实参与形参的个数类型要一致 main( ){float add( ); float x=1.5, y= -5.7; printf(“%f+%f=%f\n”,x, y, add(x,y));} 程序的运行: 函数声明 x y 实参 函数调用 1.5 -5.7 ① 实参形参 实参 ① 函数定义 float add(unsigned int a,unsigned int b) { printf(“a=%u,b=%u\n”, a, b); return(a+b); } a b 形参 形参 ② 执行 函数 体 0 0 ② ③返回函数值 释放相应空间 a=0,b=0 1.500000+-5.700000=0.000000
i 7 j 6 形参 g x y 8 ② ② 7 2 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=%d\n”, x, y); fun(n,6);} n 7 6 实参 i j g x y 2 3 实参 ① 5 5 ① 9 i 7 j 6 形参 ③ g x y 8 ② ② 7 fun(int i,int j){int x,y,g; g=8; x=7; y=2;printf(“g=%d;i=%d;j=%d\n”, g, i, j); printf(“x=%d;y=%d\n”, x, y);} 形参 2 ③ ② g=8;i=7;j=6; x=7;y=2 g=5;i=2;j=3; x=5;y=9 g=8;i=7;j=6; x=7;y=2 ② ②
四、函数的嵌套调用 函数不能嵌套定义,但可以嵌套调用 f1函数 main函数 f2函数 { c=f2(b-1,b+1); } main( ){ u=f1(i,t);} ② return(x); ① ③ ④ ⑥ ⑤ ⑦
课堂练习 习题1:指出程序中的错误 main( ) { int x,n,s; s=power(x,n); } ①power函数的调用未声明。 ②实参x、n未赋值。 ③实参x、n与形参y个数不符。 ④整个程序无输出。 ⑤未说明形参y的类型。 ⑥变量n未定义,应将之作为形参定义。 ⑦通过主函数对power函数的调用可以看出该函数有返回值,因此power函数定义中应有return(p);语句,且在第一行应说明函数值的类型。 ⑧最后的;号多余。 求yn power(y) { int i,p=1; for (i=1;i<=n;++i) p=p*y; };
习题2:画正弦曲线 算法分析: # 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”); } } 若画下列形式的正弦曲线,则每行应输出: y个空格、1个*号、换行 空格数y与sin值有关。而sin值在[-1,1] 内,故应将放大同样倍数的sin值作为y值。 y个空格 * * * * * * * * * sin(x)
习题3:编写一个函数重复打印给定的字符n次 void p(char c,int n) { int i; for (i=1;i<=n;i++) printf(“%c”,c); printf(“\n”); } main( ) { int n; char ch; scanf(“%c%d”,&ch,&n); p(ch,n); }
i取最小因子2 当因子i不是n时(还有因子) n%i==0是否 输出因子i i=i+1 n=n/i 输出最后没有因子的数n或i 习题4:编写求一个给定数字的所有因子的函数。 如:72=2*2*2*3*3 void f(int n) { int i=2; while (n!=i) { if (n%i==0) { printf(“%d*”,i); n=n/i; } else i++; } printf(“%d\n”,n); } 递推函数 i变大 n变小
习题8:编写将整数转换为字符串的函数itoa void itoa(int x) { char ch1,ch2,ch3,ch4,ch5; int n; if (x<0) { printf(“-”); x=-x; } if (x/10000!=0) n=5; else if (x/1000!=0) n=4; 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); } 判断 位数 求每一位所对应的字符
课后作业及上机任务 • 教材习题:1~4、8 • 编写判断一个数是否为素数的函数,然后用主程序调用该函数。 • 编写并调试习题2~3、8
五、函数的递归调用 从一个故事和图片观察到的… 从前有座山,山里有个庙,庙里有个老和尚,老和尚在对小和尚讲故事,故事讲的是:“从前有座山,山里有个庙,庙里有个老和尚,老和尚在对小和尚讲故事,故事讲的是:‘从前有座山,山里有个庙…’”。 观察到的规律: 故事和图片都是直接由这个故事和图片本身组成的。
1、递归的概念 递归就是某一事物直接或间接地由自己组成。如果一个函数在它的函数体内,直接或间接地调用自身,则称这个函数为递归函数,称这种调用方式为函数的递归调用。 int B(int m) { …… y=A(m-1); …… } int A(int n) { …… x=B(n-1); …… } int f(int n) { …… x=f(n-1); …… } 直接递归 间接递归 说明:这里主要讨论函数的直接递归调用
2、递归函数的执行 例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("%d!=%ld\n",n,y); } 终止条件 参数传递 执行 递归公式 返回 执行过程(设n=3): fact(1) fact(2) main( ) fact(3) 1 递归 3 2 y=fact(3) 输出y x=3*fact(2) 返回x的值 x=2*fact(1) 返回x的值 x=1 返回x的值 4 5 6 回溯
3、递归函数的编写 1 n<=1 n*fact(n-1) n>1 fact(n) = 递归公式递归终止条件 从要解决的问题出发,按倒推的方法来思考:如果要解决一个规模大的问题,只要解决与之相似的小规模问题就好了(寻找递归公式);直到倒推到一个显而易见的已知问题为止,使这个已知的问题成立的条件就是递归终止条件。 long fact(int n) { long x; if (n<=1) x=1; else x=fact(n-1)*n; return(x); } fact(n)=n*fact(n-1) fact(n-1)=(n-1)*fact(n-2) …… fact(2)=2*fact(1) fact(1)=1 n!=n*(n-1)! (n-1)!=(n-1)*(n-2)! …… 2!=2*1! 1!=1 终止条件 递归公式
课堂训练(补充) 1 3 2 1 n<=2 f(n-1)+f(n-2) n>2 f(n) = 问题:用递归函数求Fibonacci数列的第n项。 算法分析: 递归函数: long f(int n) { long x; if (n<=2) //终止条件 x=1; else x=f(n-1)+f(n-2);//递归公式 return(x); } 设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 倒推 递归公式和终止条件:
例10:汉诺(hanoi)塔问题 相传在古代印度的布拉玛(Bramah)庙中,僧侣们常玩一种称为汉诺塔(Tower of Hanoi)的游戏,据说游戏结束就标志着世界末日的到来。游戏的装置是一块铜板,上面有三根杆,最左杆从上到下、从小到大顺序串了64个盘子,呈塔形。游戏要求把盘子从一根杆移到另一根杆,移动过程中要求:一次只能移动一个盘子,并且不允许大盘在小盘上面。 如果每秒移动一只盘子,也需要大约5800亿年才能完成。
1 1 …… …… 1 n-1 …… n-1 n-1 n>0时, n=0时,终止递归 分析: 设按规则把n个盘子从A借助C移到B的算法为hanoi(n,a,b,c),则: 1 ①把n-1个盘子从a借助b移到c 3 ②把第n个盘子从a移到b 2 n n ③把n-1 个盘子从c借助a移到b A B C 递归函数:void hanio(int n,char a,char b,char c){ if (n>0) { } } 终止条件: 递归 hanio(n-1,a,c,b); printf(Move NO %d from %c to %c\n”,n,a,b); hanio(n-1,c,b,a);
汉诺塔 游戏 汉诺塔问题的具体执行过程 请仔细体会递归算法的执行过程。 演示程序:zlhnt.exe
小结 递归算法在可计算性理论中占有重要地位,对拓展编程思路非常有用。但要建立起递归概念并不容易。特别是递归函数的执行过程,是本节也是本课程的一个重点和难点。 递归算法的优缺点: 优点 • 代码清晰、简洁。 • 符合人们的思维方式,使复杂问题自然、易于理解。 缺点 • 执行效率不高。
课堂练习 习题5:编写计算函数Ack(m,n)的值的递归函数。 Ack(0,n)=n+1 Ack(m,0)=Ack(m-1,1) Ack(m,n)=Ack(m-1,Ack(m,n-1)) 终止条件 递归公式 long Ack(int m,int n) { long y; if (m==0) y=n+1; else if (n==0) y=Ack(m-1,1); else y=Ack(m-1,Ack(m,n-1)); return(y); }
x0 x1 x2=2x*x1-2(n-1)*x0 x0 x1 x2=… 习题6:分别写出计算Hermite多项式Hn(x)的值的 递推和递归函数。 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(“error\n”); else { for (i=2.0;i<=n;i+=1.0) { x2=2*x*x1-2*(i-1)*x0; x0=x1; x1=x2; } return(x2); } } } 递推函数 H0(x) =1 H1(x) =2x Hn(x) =2x Hn-1(x)-2(n-1) Hn-2(x) 当x>1时 float H(int n,float x) { if (n==0) return(1.0); else if (n==1) return(2*x); else if (x>1) return(2*x*H(n-1,x)-2*(n-1)*H(n-2,x); else printf(“error\n”); } 递归函数 递推 公式
课后作业及上机任务 • 教材习题:5、6 • 编写一个求n的阶乘的函数,然后调用该函数求1!+2!+3!+4!+5!。 • 编写并调试习题5、6 • 通过汉诺塔游戏(hanio.swf或zlhnt.exe),体会递归算法的执行过程 • 思考扩展汉诺塔问题的算法:m个杆,有一根杆上自上而下从小到大有n个盘子,一次只能移动一个盘子,并且不允许大盘在小盘的上面,请问将这些盘子全部移动到另一根杆上,最少需要移动多少次?