31
C 语语语语语语语语语语Huanghuai University Department of Information Engineering 主主 主主主主主主主主主主 语语语语语语语 语语语 语语语语语

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

  • Upload
    jalia

  • View
    123

  • Download
    0

Embed Size (px)

DESCRIPTION

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

Citation preview

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

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

Huanghuai University Department of Information

Engineering

主讲:傅 丰黄淮学院信息工程学院

北京大学出版社

汪新民、刘若慧主编

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

第四章 模块化程序设计

• §1 函数– §4.1.1 C 程序结构– §4.1.2 函数定义与函数声明– §4.1.3 函数的(传值)调用– §4.1.4 函数的嵌套调用– §4.1.5 函数的递归调用

• §2 变量的存储属性• §3 编译预处理

2 学时

本节2 学时

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

§1 函数(程序模块)一、 C 程序的结构

较大的程序一般分为若干个程序模块。每个模块实现一个特定的功能。 常将一些常用的功能模块编写成函数,以被多个程序调用。

main( ){ … f1( ); … f2( ); …}

f1( ){ … f11( ); …}

f2( ){ … f21( ); … f22( ); …}

f11( ){ …}

f21( ){ …}

f22( ){ …}

main( )

f1( ) f2( )

f11( ) f21( ) f22( )

P102 例 1(略)

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

函数值类型 函数名(类型 形参,…){ 函数体}

二、函数定义与函数声明

1 、函数定义(自定义函数,即程序模块)

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

说明

声明部分

语句部分 return( 表达式 );

所需的已知量

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

P104 例 2 :找出函数定义部分

main( ) { …

double new_style(int a,double x); … } double new_style(int a,double x) { … /* 函数体 */ }

func1( ){ … func2( ) { … } …} 错

P107 :函数不能嵌套定义

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

int jdz(int x)

{

return(x>=0?x:-x);

}

P105 例 3 :编写求一个整数的绝对值的函数

main( )

{ int a,b;

scanf(“%d”,&a);

b=jdz(a);

printf(“%d\n”,b);

}

函数定义

函数调用

形参

实参

①实参形参

②执行函数体

③返回调用处

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

P106 例 4 :编写打印 n 个空格的函数

void spc(int n)

{ int i;

for (i=1;i<=n;i++)

printf(“ ”);

}

main( )

{ int m;

scanf(“%d”,&m);

spc(m);

}

函数定义

函数调用

形参

实参

①实参形参

②执行函数体

③返回调用处

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

P106 例 5 :编写求表达式值的函数: y=

float f(float x){ if (x<0) return(x*x+x+1); else return(x*x*x+x+3);}

main( ){ float x,y; scanf(“%f”,&x); y=f(x);

printf(“%f\n”,y);}

函数定义

函数调用

形参

实参

①实参形参

②执行函数体

③返回调用处

x2-x+1 x<0 x3+x+3 x≥0

一个函数中允许有一个或多个 reutrn 语句。每个 return 语句中表达式的类型应相同。

func(int n){ if (n>10) return(2*n+3); else return( );} 错

P107 例 6 :

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

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

2 、函数声明:

函数值类型 函数名(类型 形参,…);

对在本函数中要调用的函数所做的说明

函数原型(函数定义的第一行)

说明

fun1( ){ … f (a,b); …}

fun2( ){ … f (c,d); …}

float f(float x,float y){ …}

main( ){ …}

float f(float x,float y);

     ③

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

三、函数的 ( 传值 ) 调用函数名(实参,实参,…)

无返回值:函数名 ( 实参表 ) ;

有返回值:变量 = 函数名 ( 实参表 ) ;

P109 例 7 :main( ){ int a=3,b=5; void swap(int x,int y); swap(a,b);

printf(“a=%d,b=%d\n”,a,b);}

void swap(int x,int y)

{ int temp;

temp=x, x=y, y=temp;

printf(“x=%d,y=%d\n”,x,y);

}

函数调用

函数定义形参

实参

②执行函数体

③ 返回函数值释放相应空间

函数声明

①实参形参

实参

形参

3 5

a b

33 5

x y

5 3 3

temp

x=5,y=3a=3,b=5

程序的运行:

②③① 值传递

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

P110 例 8 : 实参与形参的个数类型要一致

main( ){float add( ); float x=1.5, y= -5.7;

printf(“%f+%f=%f\n”,x, y, add(x,y));}

float add(unsigned int a,unsigned int b)

{

printf(“a=%u,b=%u\n”, a, b);

return(a+b);

}

函数调用

形参

实参

②执行函数

体③ 返回函数值释放相应空间

函数声明

①实参形参

实参

形参

1.5 -5.7

x y

a b

a=0,b=01.500000+-5.700000=0.000000

程序的运行:

0 0②

函数定义

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

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);}

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);}

形参

实参

②③

实参 7n 6

g=8;i=7;j=6;x=7;y=2g=5;i=2;j=3;x=5;y=9g=8;i=7;j=6;x=7;y=2

程序的运行:

ijgxy

23559

形参 i j7 6

②②gxy

872

③形参 i j7 6

②②gxy

872

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

main( ){ u=f1(i,t);}

函数不能嵌套定义,但可以嵌套调用

四、函数的嵌套调用

{

c=f2(b-1,b+1);

} return(x);

main 函数 f1 函数f2 函数

①②

⑥⑦ ⑤

③ ④

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

课堂练习

main( ){ int x,n,s; s=power(x,n);}

习题 1 :指出程序中的错误

power(y){ int i,p=1; for (i=1;i<=n;++i) p=p*y;};

① power 函数的调用未声明。② 实参 x 、 n 未赋值。③ 实参 x 、 n 与形参 y 个数不符。④ 整个程序无输出。⑤ 未说明形参 y 的类型。⑥ 变量 n 未定义,应将之作为形参定义。⑦ 通过主函数对 power 函数的调用可以看出该函数有返回值,因此 power 函数定义中应有 return(p); 语句,且在第一行应说明函数值的类型。⑧ 最后的 ; 号多余。

求 yn

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

# 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”); }}

习题 2 :画正弦曲线

若画下列形式的正弦曲线,则每行应输出:y 个空格、 1 个 * 号、换行 空格数 y 与 sin 值有关。而 sin 值在 [-1 , 1] 内,故应将放大同样倍数的sin 值作为 y 值。

算法分析:

y 个空格* * * * * * * **

sin(x)

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

main( ){ int n; char ch; scanf(“%c%d”,&ch,&n); p(ch,n);}

习题 3 :编写一个函数重复打印给定的字符 n 次

void p(char c,int n){ int i; for (i=1;i<=n;i++) printf(“%c”,c); printf(“\n”);}

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

习题 4 :编写求一个给定数字的所有因子的函数。

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);}

如: 72=2*2*2*3*3

n 变小i 变大

递推函数

n=n/i

n%i==0 是 否

当因子 i 不是 n 时(还有因子)

输出因子 i i=i+1

输出最后没有因子的数 n 或 i

i 取最小因子 2

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

判断正负

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

判断位数

求每一位所对应的字符

输出每一位对应的字符

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

课后作业及上机任务

教材习题: 1~4 、 8

编写判断一个数是否为素数的函数,然后用主程序调用该函数。 编写并调试习题 2~3 、 8

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

五、函数的递归调用 从一个故事和图片观察到的…

从前有座山,山里有个庙,庙里有个老和尚,老和尚在对小和尚讲故事,故事讲的是:“从前有座山,山里有个庙,庙里有个老和尚,老和尚在对小和尚讲故事,故事讲的是:‘从前有座山,山里有个庙…’ ”。

故事和图片都是直接由这个故事和图片本身组成的 。

观察到的规律:

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

说明:这里主要讨论函数的直接递归调用

递归就是某一事物直接或间接地由自己组成。如果一个函数在它的函数体内,直接或间接地调用自身,则称这个函数为递归函数,称这种调用方式为函数的递归调用。

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

……

}

直接递

间接递

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

2 、递归函数的执行

执行过程(设 n=3) :

例 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);} 

y=fact(3)输出 y

fact(3) main( )

x=3*fact(2) 返回 x 的值

x=2*fact(1) 返回 x 的值

x=1 返回 x 的值

fact(2) fact(1) 1 2 3

6 45

递归

回溯

参数传递执

返回

递归函数

递归公式

终止条件

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

3 、递归函数的编写

从要解决的问题出发,按倒推的方法来思考:如果要解决一个规模大的问题,只要解决与之相似的小规模问题就好了(寻找递归公式递归公式);直到倒推到一个显而易见的已知问题为止,使这个已知的问题成立的条件就是递归终止条件递归终止条件。

long fact(int n){ long x;   if (n<=1) x=1;   else  x=fact(n-1)*n;x=fact(n-1)*n; return(x);}1 n<=1

n*fact(n-1) n>1fact(n) =

n!=n*(n-1)!n!=n*(n-1)!

(n-1)!=(n-1)*(n-2)!

……

2!=2*1!

1!=1

fact(n)=n*fact(n-1)fact(n)=n*fact(n-1)

fact(n-1)=(n-1)*fact(n-2)

……

fact(2)=2*fact(1)

fact(1)=1

递归公式递归公式终止条件终止条件

递归公式递归公式递归终止条件递归终止条件

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

设 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){ long x; if (n<=2n<=2) // // 终止条件终止条件 x=1; else x=f(n-1)+f(n-2);x=f(n-1)+f(n-2); //// 递归公式递归公式 return(x);}

倒推

1 n<=2 f(n-1)+f(n-2) n>2

f(n) =

32

1

算法分析:

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

例 10 :汉诺( hanoi )塔问题

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

如果每秒移动一只盘子,也需要大约 5800亿年才能完成。

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

hanio(n-1,a,c,b); printf(Move NO %d from %c to %c\n”,n,a,b); hanio(n-1,c,b,a);

终止条件:

① 把 n-1 个盘子从 a借助 b移到c

n>0 时,

n=0 时,终止递归

A

n

n-1

……1

C

n-1……

1

B

n

n-1……

1

1

2

3②把第 n 个盘子从 a移到b③ 把 n-1  个盘子从 c借助 a移到 b 递归函数:

void hanio(int n,char a,char b,char c){ if (n>0)

{

}}

递归

分析:设按规则把 n 个盘子从 A借助 C移到 B 的算法为 hanoi(n,a,b,c) ,则:

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

请仔细体会递归算法的执行过程。

汉诺塔

游戏

汉诺塔问题的具体执行过程

演示程序: zlhnt.exe

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

小 结

代码清晰、简洁。 符合人们的思维方式,使复杂问题自然、易于理解。

执行效率不高。

递归算法在可计算性理论中占有重要地位,对拓展编程思路非常有用。但要建立起递归概念并不容易。特别是递归函数的执行过程,是本节也是本课程的一个重点和难点。

递归算法的优缺点:优点

缺点

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

习题 5 :编写计算函数 Ack(m,n) 的值的递归函数。

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);}

Ack(0,n)=n+1Ack(m,0)=Ack(m-1,1)Ack(m,n)=Ack(m-1,Ack(m,n-1)) 递归公式递归公式

终止条件终止条件

课堂练习

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

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); } }}

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”);}

递归函数

习题 6 :分别写出计算 Hermite 多项式 Hn(x) 的值的递推和递归函数。

H0(x) =1

H1(x) =2x

Hn(x) =2x Hn-1(x)-2(n-1) Hn-2(x) 当 x>1

递推函数

递推公式

x0 x1 x2=2x*x1-2(n-1)*x0

x0 x1 x2=…

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

课后作业及上机任务

教材习题: 5 、 6

编写一个求 n 的阶乘的函数,然后调用该函数求 1 !+2 ! +3 ! +4 ! +5!。 编写并调试习题 5 、 6

通过汉诺塔游戏( hanio.swf 或 zlhnt.exe ),体会递归算法的执行过程思考扩展汉诺塔问题的算法: m 个杆,有一根杆上自上而下从小到大有 n 个盘子,一次只能移动一个盘子,并且不允许大盘在小盘的上面,请问将这些盘子全部移动到另一根杆上,最少需要移动多少次?