1 / 31

《 C 语言程序设计案例教程 》

北京大学出版社. 《 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 学时.

jalia
Download Presentation

《 C 语言程序设计案例教程 》

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 北京大学出版社 《 C语言程序设计案例教程》 汪新民、刘若慧主编 主讲:傅 丰 黄淮学院信息工程学院 Huanghuai University Department of Information Engineering

  2. 第四章模块化程序设计 • §1 函数 • §4.1.1 C程序结构 • §4.1.2 函数定义与函数声明 • §4.1.3 函数的(传值)调用 • §4.1.4 函数的嵌套调用 • §4.1.5 函数的递归调用 • §2 变量的存储属性 • §3 编译预处理 2学时 本节 2学时

  3. §1 函数(程序模块) f1( ) { … f11( ); … } f11( ) { … } main( ) main( ) { … f1( ); … f2( ); … } f1( ) f2( ) f21( ) { … } f2( ) { … f21( ); … f22( ); … } f11( ) f21( ) f22( ) f22( ) { … } 一、C程序的结构 P102例1(略) • 较大的程序一般分为若干个程序模块。每个模块实现一个特定的功能。 • 常将一些常用的功能模块编写成函数,以被多个程序调用。

  4. 二、函数定义与函数声明 函数值类型函数名(类型形参,…){函数体} 声明部分 语句部分 1、函数定义(自定义函数,即程序模块) 所需的已知量 return(表达式); 说 明 1、函数值类型:即函数体中return语句中表达式值的类型。无返回值时(无return语句),函数值类型为void。默认为int型。2、形式参数:形参间用逗号分隔,无参时括号不能省。编译时不为形参分配存储空间,调用时才临时为其分配存储空间,从调用函数的实参得到值,称为“虚实结合”。调用结束时,形参所占空间被释放。3、函数体:由声明部分和语句部分组成。函数体中定义的变量只在执行该函数时才存在。声明部分和语句部分都省略时,为空函数:void f( ) { }4、函数的返回:函数执行到最后一个操作或遇到return语句时,返回主调函数,同时撤消为函数体中变量及形参分配的存储空间。5、函数不能嵌套定义,一个函数不能定义在别的函数内部。

  5. P107: 函数不能嵌套定义 P104例2:找出函数定义部分 main( ) { … double new_style(int a,double x); … } double new_style(int a,double x) { … /*函数体*/ } func1( ){ …func2( ) { … } …} 错

  6. 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); } ③ 返回调用处 函数调用 实参

  7. P106例4:编写打印n个空格的函数 void spc(int n) { int i; for (i=1;i<=n;i++) printf(“ ”); } 函数定义 形参 ② 执行函数体 ① 实参形参 main( ) { int m; scanf(“%d”,&m); spc(m); } ③ 返回调用处 函数调用 实参

  8. 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);} ③ 返回调用处 实参 函数调用 错

  9. 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、当某函数要被多个函数调用时,可将函数的调用声明写在所有函数前。如:

  10. 三、函数的(传值)调用 无返回值:函数名(实参表); 有返回值:变量=函数名(实参表); 函数名(实参,实参,…) 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

  11. 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

  12. 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 ② ②

  13. 四、函数的嵌套调用 函数不能嵌套定义,但可以嵌套调用 f1函数 main函数 f2函数 { c=f2(b-1,b+1); } main( ){ u=f1(i,t);} ② return(x); ① ③ ④ ⑥ ⑤ ⑦

  14. 课堂练习 习题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; };

  15. 习题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)

  16. 习题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); }

  17. 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变小

  18. 习题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); } 判断 位数 求每一位所对应的字符

  19. 课后作业及上机任务 • 教材习题:1~4、8 • 编写判断一个数是否为素数的函数,然后用主程序调用该函数。 • 编写并调试习题2~3、8

  20. 五、函数的递归调用 从一个故事和图片观察到的… 从前有座山,山里有个庙,庙里有个老和尚,老和尚在对小和尚讲故事,故事讲的是:“从前有座山,山里有个庙,庙里有个老和尚,老和尚在对小和尚讲故事,故事讲的是:‘从前有座山,山里有个庙…’”。 观察到的规律: 故事和图片都是直接由这个故事和图片本身组成的。

  21. 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); …… } 直接递归 间接递归 说明:这里主要讨论函数的直接递归调用

  22. 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 回溯

  23. 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 终止条件 递归公式

  24. 课堂训练(补充) 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 倒推 递归公式和终止条件:

  25. 例10:汉诺(hanoi)塔问题 相传在古代印度的布拉玛(Bramah)庙中,僧侣们常玩一种称为汉诺塔(Tower of Hanoi)的游戏,据说游戏结束就标志着世界末日的到来。游戏的装置是一块铜板,上面有三根杆,最左杆从上到下、从小到大顺序串了64个盘子,呈塔形。游戏要求把盘子从一根杆移到另一根杆,移动过程中要求:一次只能移动一个盘子,并且不允许大盘在小盘上面。 如果每秒移动一只盘子,也需要大约5800亿年才能完成。

  26. 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);

  27. 汉诺塔 游戏 汉诺塔问题的具体执行过程 请仔细体会递归算法的执行过程。 演示程序:zlhnt.exe

  28. 小结 递归算法在可计算性理论中占有重要地位,对拓展编程思路非常有用。但要建立起递归概念并不容易。特别是递归函数的执行过程,是本节也是本课程的一个重点和难点。 递归算法的优缺点: 优点 • 代码清晰、简洁。 • 符合人们的思维方式,使复杂问题自然、易于理解。 缺点 • 执行效率不高。

  29. 课堂练习 习题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); }

  30. 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”); } 递归函数 递推 公式

  31. 课后作业及上机任务 • 教材习题:5、6 • 编写一个求n的阶乘的函数,然后调用该函数求1!+2!+3!+4!+5!。 • 编写并调试习题5、6 • 通过汉诺塔游戏(hanio.swf或zlhnt.exe),体会递归算法的执行过程 • 思考扩展汉诺塔问题的算法:m个杆,有一根杆上自上而下从小到大有n个盘子,一次只能移动一个盘子,并且不允许大盘在小盘的上面,请问将这些盘子全部移动到另一根杆上,最少需要移动多少次?

More Related