第四章模块化程序设计函数.docx
- 文档编号:30774295
- 上传时间:2023-08-23
- 格式:DOCX
- 页数:27
- 大小:49.45KB
第四章模块化程序设计函数.docx
《第四章模块化程序设计函数.docx》由会员分享,可在线阅读,更多相关《第四章模块化程序设计函数.docx(27页珍藏版)》请在冰豆网上搜索。
第四章模块化程序设计函数
第四章模块化程序设计(函数)
【计划课时】授课8课时+上机4课时(预习内容:
教材第七章、第八章)
一、概述
回顾:
·程序设计方法:
自上而下,逐步细化
·C语言:
函数式语言
在C程序设计中,通常:
·将一个大程序分成几个子程序模块(自定义函数)
·将常用功能做成标准模块(标准函数)放在函数库中供其他程序调用
如果把编程比做制造一台机器,函数就好比其零部件。
·可将这些“零部件”单独设计、调试、测试好,用时拿出来装配,再总体调试。
·这些“零部件”可以是自己设计制造/别人设计制造/现在的标准产品
而且,许多“零部件”我们可以只知道需向它提供什么(如控制信号),它能产生什么(如速度/动力),并不需要了解它是如何工作、如何设计制造的——所谓“黑盒子”。
控制信号速度/动力
(输入参数)(返回结果)
【例】编写一个儿童算术能力测试软件
显示软件封面
检查密码
产生题目
接受回答
评判计分
显示结果
如果要继续练习
告别词
main(){
charans=‘y’;
clrscr();
cover();/*调用软件封面显示函数*/
password();/*调用密码检查函数*/
while(ans==’y’||ans==’Y’)
{question();/*调用产生题目函数*/
answers();/*调用接受回答函数*/
marks();/*调用评分函数*/
results();/*调用结果显示函数*/
·这些函数现在不编程或还不会编程,可先放空。
·可以多人合作,每人完成若干个函数(模块化)。
·可在另一个源程序文件中定义。
printf(“是否继续练习?
(Y/N)\n”);
ans=getch();
}
printf(“谢谢使用,再见!
”);
}
/*定义所用函数*/
cover(){}/*软件封面显示函数*/
password(){}/*密码检查函数*/
question(){}/*产生题目函数*/
answers(){}/*接受回答函数*/
marks(){}/*评分函数*/
results(){}/*结果显示函数*/
函数使用常识:
P102
1、C程序执行总是从main函数开始,调用其它函数后总是回到main函数,最后在main函数中结束整个程序的运行。
2、一个C程序由一个或多个源(程序)文件组成——可分别编写、编译和调试。
3、一个源文件由一个或多个函数组成,可为多个C程序公用。
4、C语言是以源文件为单位而不以函数为单位进行编译的。
5、所有函数都是平行的、互相独立的,即在一个函数内只能调用其他函数,不能再定义一个函数(嵌套定义)。
6、一个函数可以调用其他函数或其本身,但任何函数均不可调用main函数。
二、函数的定义
函数定义——“制造函数”
1、无参函数P102
定义格式:
数据类型函数名()/*现代风格是:
函数名(void)*/
{函数体(说明部分+语句)}
【注意】每个函数之前可以有自己的编译预处理命令组。
数据类型为int时,可以省略。
2、有参函数P102
定义格式:
数据类型函数名(形参表)/*现代风格是:
函数名(带类型形参表)*/
形参类型说明;
{函数体(说明部分+语句)}
函数的返回值通过函数体中的return语句获得。
形式:
return(x);return(x+y);return(x>y?
x:
y);
语句中圆括号亦可省略。
【注意】如果函数值类型与return语句表达式值的类型不一致,以函数类型为准(数值型会自动进行类型转换)。
如果明确表示不需返回值,可用void作函数的数据类型。
【例一】
main()
{floata=1.5,b=2.5;
intc;
c=max(a,b);
printf(“Maxis%d\n”,c);
}
max(x,y)/*用“值传递”分析法进行变量跟踪*/
floatx,y;
{floatz;
z=x>y?
x:
y;
returnz;
}
结果:
Maxis2(编译通过,结果错误)
【讨论】如何改错?
·如果将输出语句中的%d改为%f,结果:
Maxis0.125000(亦错)
·如果将max(x,y)改为floatmax(x,y),结果编译出错:
Typemismatchinredeclarationof‘max’
为什么?
(原因见下)。
三、函数的调用
函数和变量一样,在其主调函数中也必须“先说明,后使用”。
注意关系:
函数定义——制造函数
函数使用——说明(准备使用)
调用(使用函数)
调用方式:
·赋值如:
c=max(x,y);
·表达式中c=1+max(x,y);printf(“Max=%d\n”,max(x,y));
·执行函数max(x,y);
1、调用外部函数(其他源文件中定义的函数)时
函数说明语句extern函数名();
【例】文件file1.c中
main()
{intx=80,y=90,c;
externmax();/*函数说明*/
c=max(x,y)+20;/*调用max函数*/
printf(“Maxis%d\n”,c);
}
文件files2.c中(与file1.c同目录)
externmax(inta,intb)/*extern可省*/
{floatc;
c=a>b?
a:
b;
returnc;
}
2、调用同一源文件中的非标准函数时
也必须在主调函数中对所调函数进行说明:
函数说明语句数据类型函数名();(不说明会出现编译错误)
但三种情况下可以省略说明:
①函数值是整型(int)或字符型(char)时——系统自动按整型说明;
②所调函数的定义出现在主调函数之前时;
③文件一开头,在所有函数之前,对所用函数作了说明
3、函数的嵌套调用P111
四、函数的递归调用
1、递归的概念P115
直接递归调用调用函数的过程中又调用该函数本身
间接递归调用调用f1函数的过程中调用f2函数,而f2中又需要调用f1。
以上均为无终止递归调用。
为此,一般要用if语句来控制使递归过程到某一条件满足时结束。
2、递归法
类似于数学证明中的反推法,从后一结果与前一结果的关系中寻找其规律性。
归纳法可以分为:
·递推法从初值出发,归纳出新值与旧值间直到最后值为止存在的关系
要求通过分析得到:
初值+递推公式
编程:
通过循环控制结构实现(循环的终值是最后值)
·递归法从结果出发,归纳出后一结果与前一结果直到初值为止存在的关系
要求通过分析得到:
初值+递归函数
编程:
设计一个函数(递归函数),这个函数不断使用下一级值调用自身,直到结果已知处——选择控制结构
其一般形式是:
在主函数中用终值n调用递归函数,而在递归函数中:
递归函数名f(参数x)
{if(n=初值)
结果=…;
else
结果=含f(x-1)的表达式;
返回结果(return);
}
【例一】(P118例7.8)用递归法求n!
分析比较:
递推法
递归法
0!
=1
1!
=0!
×1
2!
=1!
×2
3!
=2!
×3
……
n!
=(n-1)!
×n
分析得Sn=n!
的求解
1(n=1,0)
Sn=
Sn-1×n
(n>1)
其中Sn-1先求出
n!
=(n-1)!
×n
(n-1)!
=(n-2)!
×(n-1)
(n-2)!
=(n-3)!
×(n-2)
(n-3)!
=(n-4)!
×(n-3)
……
2!
=1!
×2
分析得f(n)=n!
的求解
1(n=1,0)
f(n)=
f(n-1)×n
(n>1)
其中f(n-1)未求出
实际上,递归程序分两个阶段执行——
①回推(调用):
欲求n!
→先求(n-1)!
→(n-2)!
→…→1!
若1!
已知,回推结束。
②递推(回代):
知道1!
→2!
可求出→3!
→…→n!
程序如下:
main()
{intn;
floats;
floatfac();
clrscr();
printf("Inputn=");
scanf("%d",&n);
s=fac(n);
printf("%d!
=%.0f",n,s);
}
floatfac(intx)
{intf;
if(x==0||x==1)f=1;
elsef=fac(x-1)*x;
returnf;
}
执行:
Inputn=5
结果:
5!
=120
【例二】(P116例7.7)有5个人,第5个人说他比第4个人大2岁,第4个人说他对第3个人大2岁,第3个人说他对第2个人大2岁,第2个人说他比第1个人大2岁,第1个人说他10岁。
求第5个人多少岁。
分析:
10(n=1)
age(n)=
age(n-1)+2(n>1)
程序如下:
main()
{clrscr();
printf("%d",age(5));
}
age(intn)
{intc;
if(n==1)c=10;
elsec=age(n-1)+2;
returnc;
}
结果:
18
【例三】在屏幕上显示杨辉三角形
1分析:
若起始行为第1行
11则:
第x行有x个值
121对第x行第y列,其值(不计左侧空格时)
13311(y=1或y=x)
14641c(x,y)=
15101051c(x-1,y-1)+c(x-1,y)
………………
程序如下:
main(){
inti,j,n;
clrscr();
printf("Inputn=");
scanf("%d",&n);
for(i=1;i<=n;i++)
{for(j=0;j<=n-i;j++)
printf("");/*为了保持三角形态,此处输出两个空格*/
for(j=1;j<=i;j++)
printf("%3d",c(i,j));
printf("\n");
}
}
intc(intx,inty)
{intz;
if(y==1||y==x)return1;
else
{z=c(x-1,y-1)+c(x-1,y);
returnz;
}
}
【例四】Fibonacci数列问题。
1(n=1)
分析:
fib(n)=1(n=2)
fib(n-1)+fib(n-2)(n>1)
程序如下:
fib(intn)
{
intf;
if(n==1||n==2)
f=1;
else
f=fib(n-1)+fib(n-2);
return(f);
}
main()
{
inti,s=0;
clrscr();
for(i=1;i<=12;i++)
s=s+fib(i);
printf("n=12,s=%d",s);
}
结果:
376
【例五】运行下列程序,当输入字符序列AB$CDE并回车时,程序的输出结果是什么?
#include
rev()
{charc;
c=getchar();
if(c=='$')printf("%c",c);
else
{rev();
printf("%c",c);
}
}
main(){
rev();
}
结果:
$BA
【例六】反向输出一个整数(非数值问题)
非数值问题的分析无法象数值问题那样能得出一个初值和递归函数式,但思路是相同的。
分析方法:
①简化问题:
设要输出的正整数只有一位,则“反向输出”问题可简化为输出一位整数。
②对大于10的正整数,逻辑上可分为两部分:
个位上的数字和个位以前的全部数字。
将个位以前的全部数字看成一个整体,则为了反向输出这个大于10的正整数,可按以下步骤:
a、输出个位上的数字;
b、将个位除外的其他数字作为一个新的整数,重复a步骤的操作。
其中b问题只是对原问题在规模上进行了缩小——递归。
所以,可将反向输出一个正整数的算法归纳为:
if(n为一位整数)
输出n;
else
{输出n的个位数字;
对剩余数字组成的新整数重复“反向输出”操作;
}
程序如下:
#include
voidmain()
{voidprintn(intx);
intn;
printf("Inputn=");
scanf("%d",&n);
if(n<0)
{n=-n;putchar('-');}
printn(n);
}
voidprintn(intx)/*反向输出整数x*/
{if(x>=0&&x<=9)/*若x为一位整数*/
printf("%d",x);/*则输出整数x*/
else/*否则*/
{printf("%d",x%10);/*输出x的个位数字*/
printn(x/10);/*将x中除个位数字外的全部数字形成新的x后,继续递归操作*/
}
}
执行:
Inputn=12345
结果:
54321
执行:
Inputn=-12479
结果:
-97421
【讨论】Inputn=123456
-6167
为什么:
123456710=111100010010000002
int类型的数实际只能存入16位,即11100010010000002
第1位为符号位,其余各位取反后再加1,得原码1001110111000000(真值-761610)
【例七】汉诺塔(TowerofHanoi)问题。
P118例7.9《试题汇编》【7.18】
也是一个非数值问题。
分析方法:
①简化问题:
设盘子只有一个,则本问题可简化为a→b。
②对于大于一个盘子的情况,逻辑上可分为两部分:
第n个盘子和除n以外的n-1个盘子。
如果将除n以外的n-1个盘子看成一个整体,则要解决本问题,可按以下步骤:
a、将a杆上n-1个盘子借助于b先移到c杆;a→c(n-1,a,c,b)
b、将a杆上第n个盘子从a移到b杆;a→b
c、将c杆上n-1个盘子借助a移到b杆。
c→b(n-1,c,b,a)
五、变量的存储类型
模块化设计要求研究不同模块(函数、源文件)间变量的关系。
变量两大属性:
·数据类型·存储类别
1、数据类型(复习)
通过变量说明(定义)来规定其数据类型:
格式数据类型关键字变量名
如chara;
intb,c;
floatx,y;
为什么使用变量前要先对其数据类型进行“说明”——好比看电影前先买票订座
·预留存储空间(如char型为1个字节,int型为2个字节……)
·确定存储方式(如char型存放ASCII值,int型存放补码值……)
char型(用一个字节存放该字符的ASCII值)
int型(用两个字节存放该数值的补码)
float型(四个字节)
尾数(补码)阶码
一个变量的数据类型定义后,就规定了该变量只能存储相应类型的数据。
如定义intx,则x只能存放整型数,如果x=3.14159,会被自动转为整型存放。
2、存储类别P130
规定了变量在计算机内部的存放位置→决定变量的“寿命”(何时“生”,何时“灭”)
一个完整的变量说明格式如下:
存储类别数据类型变量名
如staticintx,y;
CPURAM
…
代码区
常量区
静态存储区
动态存储区
…
用户区
数据区
存储类别:
·register型(寄存器型)
变量值存放在运算器的寄存器中——存取速度快,一般只允许2~3个,且限于char型和int型,通常用于循环变量(在微机的TurboC中实际上自动转为auto型)
·auto型(自动变量型)
变量值存放在主存储器的动态存储区(堆栈方式)
优点——同一内存区可被不同变量反复使用
以上两种均属于“动态存储”性质,即调用函数时才为这些变量分配单元,函数调用结束其值自动消失。
·static型(静态变量型)
变量值存放在主存储器的静态存储区
程序执行开始至结束,始终占用该存储空间
·extern型(外部变量型)
同上,其值可供其他源文件使用
以上两种均属于“静态存储”性质,即从变量定义处开始,在整个程序执行期间其值都存在。
未说明存储类别时,函数内定义的变量为auto型,函数外定义的变量为extern型。
六、局部变量与全局变量
1、局部变量——函数内部或复合语句内定义的变量
auto(默认)所在函数调用结束时,其值自动消失
局部变量register如不赋初值,取不确定值为初值
static所有函数调用结束,其值仍保留
如不赋初值,取初值为0(数值型)或空格(字符型)
所有形参都是局部变量
局部变量只在本函数或本复合语句内才能使用,在此之外不能使用(视为不存在)——main函数也不例外。
【例一】求程序运行结果
main()
{inta=2,i;
clrscr();
for(i=0;i<3;i++)
printf("%4d",f(a));
}
f(inta)
{intb=0;staticintc=3;
b++;c++;
returna+b+c;
}
aibcf(a)
200→147
10→158
20→169
【结果】789
【例二】求程序运行结果
main()
{intk=4,m=1,p;
clrscr();
p=func(k,m);printf("%d,",p);
p=func(k,m);printf("%d",p);
}
func(inta,intb)
{staticintm=0,i=2;
i+=m+1;
m=i+a+b;
returnm;
}
kmabmi
41410→82→3
41418→173→12
【结果】8,17
2、全局变量——在函数之外定义的变量
extern(默认)允许本源文件中其他函数及其他源文件使用
全局变量
static只限本源文件中使用
有效作用范围:
从定义变量位置开始直到本源文件结束
如果需要将全局变量的作用范围扩展至整个源文件——
法1:
全部在源文件开头处定义
法2:
在引用函数内,用extern说明P129例7.16
法3:
在源文件开头处,用extern说明
【例三】求程序运行结果
externintx,y;/*可以省略int。
如果没有此句,编译就会出错*/
{clrscr();
printf("x=%d,y=%d\n",x,y);
}
intx=100,y=200;
如果要将全局变量作用范围扩展到其他源文件,只需在使用这些变量的文件中对变量用extern加以说明。
【注意】
1、所有全局变量加不加static,都属于静态存储,如不赋初值,取初值为0(数值型)或空格(字符型)(注意与函数内部定义的static型局部变量的区别)
2、如果在同一个源文件中,全局变量与局部变量同名,则在局部变量作用范围内,全局变量不起作用。
P129
【例四】求程序运行结果
inta=3,b=5;
max(inta,intb)
{intc;
c=a>b?
a:
b;
returnc;
}
main()
{inta=8;
printf("%d\n",max(a,b));
}
结果:
8
【讨论】如果主函数中没有inta=8,结果?
(5)
如果让主函数中inta=4或a=-1,结果?
(均为5)
【例五】求程序运行结果
voidnum()
{externintx,y;
inta=15,b=10;
x=a-b;
y=a+b;
}
intx,y;
main()
{inta=7,b=5;
x=a+b;
y=a-b;
num();
printf("%d,%d\n",x,y);
}
结果:
5,25
【讨论】如果第二行不加上extern呢?
(12,2)
【例六】求程序运行结果
inta;
fun(inti)
{a+=2*i;
returna;}
main(){
inta=10;
printf("%d,%d\n",fun(a),a);
}
结果:
20,10
【例七】求程序运行结果
inti=0;
main()
{inti=5;
clrscr();
reset(i/2);printf("i=%d\n",i);/*此处i为局部变量值,即i=5*/
reset(i=i/2);printf("i=%d\n",i);/*局部变量i=i/2,即i=2*/
reset(i/2);printf("i=%d\n",i);/*局部变量i的值未变*/
workover(i);printf("i=%d\n",i);/*局部变量i的值未变*/
}
workover(inti)
{i=(i%i)*((i*i)/(2*i)+4);
printf("i=%d\n",i);
returni;
}
reset(inti)/*此处形参i使用主函数中局部变量i的值*/
{i=i<=2?
5:
0;/*此处i为全部变量*/
printf("i1=%d",i);
returni;
}
结果:
i1=5i=5
i1=5i=2
i1=5i=2
i=0
i=2
【讨论】如果将主函数中第二个reset(i/2)改为reset(i=3)
结果:
1=5i=5
i1=5i=2
i1=0i=3
i=0
i=3
七、编译预处理
编译预处理:
宏定义/文件包含/条件编译
【编译】C编译系统对源程序进行:
词法和语法分析,代码生成,优化→.OBJ文件
【编译预处理】编译前对源程序进行一些预加工(改善程序设计环境/模块化设计)
编译预处理命令均以#开头,未尾不加分号
可出现在程序的任何位置,其作用范围:
出现点至所在源程序未尾。
1、宏定义
格式#define宏名宏体
宏名和宏体均为字符串,前者必须符合标识符命名规则。
预处理时在程序中用宏体替换宏名。
注意:
可以用#undef宏名终止该宏名的作用范围
①定义符号常量
【例一】
#defineM3
#defineN(M+1)
#defineN
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第四 模块化 程序设计 函数