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
《 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 函数(程序模块)一、 C 程序的结构
较大的程序一般分为若干个程序模块。每个模块实现一个特定的功能。 常将一些常用的功能模块编写成函数,以被多个程序调用。
main( ){ … f1( ); … f2( ); …}
f1( ){ … f11( ); …}
f2( ){ … f21( ); … f22( ); …}
f11( ){ …}
f21( ){ …}
f22( ){ …}
main( )
f1( ) f2( )
f11( ) f21( ) f22( )
P102 例 1(略)
函数值类型 函数名(类型 形参,…){ 函数体}
二、函数定义与函数声明
1 、函数定义(自定义函数,即程序模块)
1 、函数值类型:即函数体中 return 语句中表达式值的类型。无返回值时(无 return 语句),函数值类型为 void 。默认为 int 型。2 、形式参数:形参间用逗号分隔,无参时括号不能省。编译时不为形参分配存储空间,调用时才临时为其分配存储空间,从调用函数的实参得到值,称为“虚实结合”。调用结束时,形参所占空间被释放。3 、函数体:由声明部分和语句部分组成。函数体中定义的变量只在执行该函数时才存在。声明部分和语句部分都省略时,为空函数: void f( ) { }4 、函数的返回:函数执行到最后一个操作或遇到 return 语句时,返回主调函数,同时撤消为函数体中变量及形参分配的存储空间。5 、函数不能嵌套定义,一个函数不能定义在别的函数内部。
说明
声明部分
语句部分 return( 表达式 );
所需的已知量
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(“%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);
}
函数定义
函数调用
形参
实参
①实参形参
②执行函数体
③返回调用处
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 :
例:函数定义: 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);
③
②
①
三、函数的 ( 传值 ) 调用函数名(实参,实参,…)
无返回值:函数名 ( 实参表 ) ;
有返回值:变量 = 函数名 ( 实参表 ) ;
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
程序的运行:
②③① 值传递
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②
函数定义
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
②
②
main( ){ u=f1(i,t);}
函数不能嵌套定义,但可以嵌套调用
四、函数的嵌套调用
{
c=f2(b-1,b+1);
} return(x);
main 函数 f1 函数f2 函数
①②
⑥⑦ ⑤
③ ④
课堂练习
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
# 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)
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”);}
习题 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
判断正负
习题 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 f(int n)
{ ……
x=f(n-1) ;
……
}
int A(int n)
{ ……
x=B(n-1) ;
……
}
int B(int m)
{ ……
y=A(m-1) ;
……
}
直接递
归
间接递
归
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
递归
回溯
参数传递执
行
返回
递归函数
递归公式
终止条件
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
递归公式递归公式终止条件终止条件
递归公式递归公式递归终止条件递归终止条件
设 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
算法分析:
例 10 :汉诺( hanoi )塔问题
相传在古代印度的布拉玛( Bramah )庙中,僧侣们常玩一种称为汉诺塔( Tower of Hanoi )的游戏,据说游戏结束就标志着世界末日的到来。游戏的装置是一块铜板,上面有三根杆,最左杆从上到下、从小到大顺序串了 64 个盘子,呈塔形。游戏要求把盘子从一根杆移到另一根杆,移动过程中要求:一次只能移动一个盘子,并且不允许大盘在小盘上面。
如果每秒移动一只盘子,也需要大约 5800亿年才能完成。
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) ,则:
请仔细体会递归算法的执行过程。
汉诺塔
游戏
汉诺塔问题的具体执行过程
演示程序: zlhnt.exe
小 结
代码清晰、简洁。 符合人们的思维方式,使复杂问题自然、易于理解。
执行效率不高。
递归算法在可计算性理论中占有重要地位,对拓展编程思路非常有用。但要建立起递归概念并不容易。特别是递归函数的执行过程,是本节也是本课程的一个重点和难点。
递归算法的优缺点:优点
缺点
习题 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)) 递归公式递归公式
终止条件终止条件
课堂练习
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=…
课后作业及上机任务
教材习题: 5 、 6
编写一个求 n 的阶乘的函数,然后调用该函数求 1 !+2 ! +3 ! +4 ! +5!。 编写并调试习题 5 、 6
通过汉诺塔游戏( hanio.swf 或 zlhnt.exe ),体会递归算法的执行过程思考扩展汉诺塔问题的算法: m 个杆,有一根杆上自上而下从小到大有 n 个盘子,一次只能移动一个盘子,并且不允许大盘在小盘的上面,请问将这些盘子全部移动到另一根杆上,最少需要移动多少次?