实验五模块化的程序设计_第1页
实验五模块化的程序设计_第2页
实验五模块化的程序设计_第3页
实验五模块化的程序设计_第4页
实验五模块化的程序设计_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、单元5 模块化的程序结构实验五 模块化的程序设计实验目的(1)理解自顶向下,逐步细化的模块化设计思想划分子模块;知道模块化程序设计方法。(2)熟悉函数的定义、函数的声明和函数的调用,理解函数调用的实现过程。(3)理解函数调用时的数据传送机制,通过参数将主调函数的数据传递到被调函数,运用return语句将被调函数的处理结果返回主调函数。(4)通过设置断点和单步执行,观察子函数的执行路径,和子函数中变量的值的变化。实验范例引导任务任务1会变化的三角形任务2 计算生日相差几天任务 会变化的三角形任务描述在屏幕上打印一个三角形,如图5-1所示,运行程序时输入三角形的级别n,可以打印不同大小的三角形。图

2、5-1 输出可变的三角形任务解决任务解决路径:图5-2 任务1 解决路径第一步:学习无参无返回值函数的定义、声明和调用;第二步:了解函数参数的作用,学习使用带参的函数构建多层模块程序的构建;第三步:学习带参无返回值函数的设计;1编写打印三角形的函数(1)问题分析要在屏幕上打印一个三角形,可以直接在main函数中使用printf语句完成,如下程序代码所示:#include <stdio.h>int main() printf("打印一个三角形n "); printf(" *n"); printf(" *n"); printf

3、("*n");printf("打印结束n"); return 0; 在本任务中,要求调整程序的结构,写一个打印三角形的无参无返回值函数,然后在main函数中调用它实现打印三角形。程序员编写的函数称为用户自定义函数,相对于系统函数而言,不是系统已写好的代码,需要程序员自己定义函数和编写实现函数的程序代码。无参无返回值函数通常执行一个具体的操作,无计算结果。(2)函数的应用使用用户自定义函数分三步:函数定义,函数声明和函数调用。无参无返回值函数的定义无参无返回值函数定义的一般形式为:void函数名(void).一个打印三角形的函数的定义可以为:void tr

4、iangle(void) printf(" *n"); printf(" *n"); printf("*n"); triangle是函数名,函数名应能体现函数的功能,以增强程序的可读性,这个函数的功能是打印一个固定的三角形。函数类型为void,表示没有返回值,形参表中写void,表示没有参数,void可以省略,但一对圆括号不能省略,表示函数运算符。 函数的声明用户自定义函数要先声明后调用。无参无返回值的函数声明的一般形式:void 函数名(void);例如triangle函数的声明语句为:void triangle(void);函数声

5、明所需要的信息都在函数的首部,书写函数声明时可以复制函数首部,加分号。 函数调用无返回值函数的调用形式为:函数名();例如triangle函数的调用语句为:triangle();虽然triangle函数没有参数,实参列表为空,但一对圆括号不能省略,而且圆括号中不能写void。这种从函数名开始的调用形式,称为函数语句。(3)编写程序最后关注这三个组成部分在程序中出现的位置,程序sample05_01.cpp演示了一个规范的程序代码格式。【例5.1 sample05_01.cpp】/* 打印一个三角形*/ #include <stdio.h>/函数声明void triangle(voi

6、d); int main() printf("打印一个三角形n"); /函数调用triangle(); printf("打印结束n"); ; return 0; /函数定义void triangle(void) printf(" *n"); printf(" *n"); printf("*n"); 在程序中,函数定义的位置最好在main函数后面,因为程序从main函数开始执行,到main函数的最后一句语句结束,我们阅读程序也习惯从main函数开始。函数和函数之间没有先后关系,也没有包含关系,不能

7、在一个函数的内部定义另一个函数,每个函数都是独立定义的。函数声明通常放在预编译命令的后面,main函数之前。这是一个全局位置,函数声明对所有的函数有效。如果函数声明放在另一个函数的内部,那只对该函数有效。函数调用按程序的需要进行,一个函数定义并声明后,可以多次调用。程序的执行从main函数的第一条可执行语句开始,执行完printf语句,输出字符串“打印一个三角形”,顺次执行函数调用语句“triangle();”,程序转向triangle的函数定义处,执行triangle函数中的语句,执行完后回到main函数,接着向下执行printf语句和return语句,main执行结束,程序结束。思考:如果

8、要在垂直方向上连续输出三个三角形,如图所示,该如何修改你的程序呢?图5-3 输出三个三角形提示.444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444:函数是通用项目,可以多次调用。2. 使用通用函数编写三角形函数先阅读两个通用功能函数line和point,它们实现基本的显示线段功能。line函数定义如下:void line(intstart,int end ) inti; for(i=1;i<start

9、;i+) /画start-1个空格printf(" "); for(i=start;i<=end;i+)/画*构成的线段printf("*");printf("n"); /回车line函数的作用是画一条从start开始到end结束的由*组成的线段,start和end是两个整型参数变量,可以接受两个整数值。例如line(5,10)的作用是先画4个空格,从第5个位置开始画6个星号,回车结束。line(5,10) *void point(intstart,int end ) inti;for(i=1;i<start;i+) /画

10、start-1个空格printf(" "); printf("*");/开始星号for(i=start+1;i<end;i+)/画*之间的空格printf(" ");printf("*n") ; /结束星号和回车point函数的作用是画一条从start到end的两端星号中间空格的线段。例如point(5,10)的作用是先画4个空格,从第5个位置 画一个星号,继续画空格,到第10个位置画一个星号,回车结束。point(5,10) * *line函数和point函数与前面的triangle函数相比,增加了两个参数

11、变量start和end,这两个变量在函数调用时可以接受两个整数,决定画出的线段的长短。参数变量定义的方法与一般函数体内定义变量的方法相似,要设定变量的数据类型,但不同的是每个参数变量必须单独定义。带参无返回值的函数定义一般格式如下:void 函数名( <type1> 形参变量1, <type2> 形参变量2,.) .line函数的两个参数变量都是整型,也不能共用一个int类型,下面的定义形式是错误的,end参数前的int不能省略:void line(int start, end)带参无返回值的函数声明同样是函数首部加分号,一般格式如下:void 函数名( <typ

12、e1> 形参变量1, <type2> 形参变量2,.);在调用带参数的函数时,要确保每个形参变量都能得到指定类型的数据,数据的形式不限,可以是常量数据,可以是变量数据,还可以是表达式数据。无返回值带参的函数调用一般格式如下:函数名(实参值1,实参值2.);【例5.2 line函数的调用示例】int s=10,e=20; line( 10,30); /输出9个空格后,从10位置开始输出21个星号,回车结束 line( s,e); /输出9个空格后,从10位置开始输出11个星号,回车结束 line( s,s+24); /输出9个空格后,从10位置开始输出25个星号,回车结束函数的

13、调用,其实质完成了将实参值赋给形参变量的操作如图5-4所示:函数调用:实参值line(s,s+24);line(s , e);line( 10,30);函数定义:形参变量void line(intstart,int end ) 图5-4 参数的传递示意图思考:如果要在11的位置输出一个星号,是调用line函数还是point函数?下面使用line函数和point函数重新实现例5-1的triangle函数,设置三角形向右平移8个空格,函数定义如下:void triangle(void) line(11,11); point(10,12); line(9,13);图5-5 空心三角形实现绘制空心三角

14、形的完整程序:【例5.3 sample05_02.cpp】/* 打印一个三角形*/ #include <stdio.h>void line(intstart,int end ) ;void point(intstart,int end ) ;void triangle(void); int main()printf("打印一个三角形n"); /函数调用triangle(); printf("打印结束n"); return 0;/*画一个空心三角形*/void triangle(void)line(11,11);point(10,12);lin

15、e(9,13);/*画一条从start开始到end结束的由*组成的线段*/void line(intstart,int end ) inti;for(i=1;i<start;i+) /画start-1个空格printf(" "); for(i=start;i<=end;i+)/画*构成的线段printf("*");printf("n"); /回车 /*画一条从start到end的两端星号中间空格的线段*/void point(intstart,int end ) inti;for(i=1;i<start;i+) /画

16、start-1个空格printf(" "); printf("*");/开始星号for(i=start+1;i<end;i+)/画*之间的空格printf(" ");/结束星号printf("*n") ; /回车12与例5-1比较,这个程序中增加了两个用户自定义函数line和point,调用这两个函数重写了triangle函数,输出了一个空心三角形。main函数不需要作改动。3. 输出可变的三角形最后来实现三角形的变化,输入一个n值,n值不同,输出三角形的大小也随之变化。(1)问题分析修改triangle函数

17、的功能定义,给triangle函数增加一个形参变量n,三角形的大小由n值确定,一共n行,每行2*i-1 个星号。这样,通过增加形参,triangle函数可处理的三角形增多,通用性增强。(2)算法设计上例中triangle函数的三句代码标号如下,分析每一句语句的作用, line(11,11);point(10,12);line(9,13);语句的作用是画三角形的最上面的一个顶点;语句的作用画三角形的中部空心线,从顶点向下,每行的空心线开始端减1,结束端加1;语句的作用是画三角形的底部实心线,画2n-1个星号。设定:三角形上方顶点位置middle(最后一行的中间位置),线段开始端s,结束端e。算法

18、设计如下:1.调用line函数画顶点星号,middle位置是(2n-1)/2+12.设置s为middle-1,e为middle+13.画n-2条三角形的中部空心线 循环i从2到n-1 ,步长1 3.1 调用point函数画从s到e的空心线 3.2 s减1,e增14. 调用line函数画从s到e的实线。(3)编写程序triangle函数的实现代码void triangle(int n )int middle=(2*n-1)/2+1; / middle是最后一行的中间位置ints,e; line(middle,middle);/ 画顶点星号 s=middle-1; /从顶点向下,每行的空心线开始端

19、减1,结束端加1 e=middle+1; for(int i=2;i<n;i+)/画n-2条三角形的中部的空心线 point(s,e); s-;e+; line(s,e); /是画三角形的底部实心线main函数调用时,增加输入n值输入的语句,调用triangle函数时,传递n值到triangle函数,实现输出可变的空心三角形。【例5.4 sample05_03.cpp】/* 打印一个三角形*/ #include <stdio.h>void line(int start,int end ) ;void point(int start,int end ) ;void triang

20、le(int n); int main()int n;printf("打印一个三角形n"); printf("n=");scanf("%d",&n);triangle(n); printf("打印结束n"); ;return 0;void triangle(int n ) 略 void line(intstart,int end ) 略void point(intstart,int end ) 略任务2计算生日相差几天任务描述班主任想知道班上某两位同学在本年度的生日差几天,设计程序,任意输入两个人在本年度的

21、生日日期,能够计算两人的生日差几天。要求使用模块化设计的方法完成任务。任务解决一个多函数结构的C程序是模块化设计的结果,模块化设计的思想实际上是一种“分而治之”的思想,把一个大任务分为若干个子任务,每一个子任务就相对简单了。在拿到一个程序模块以后,根据程序模块的功能将它划分为若干个子模块,如果这些子模块的规模还嫌大,还再可以划分为更小的模块。这个过程采用自顶向下的方法来实现。任务解决路径:图5-6 任务2 解决路径第一步 学习模块化的设计方法得到程序的函数构成第二步 学习带参有返回值函数的定义第三步 学习带参有返回值函数的声明和调用1.模块化设计计算生日日期之间的差值,可以分别先算出该日期是该

22、年的第几天,再求两者的差值即可。第一层得到的算法如下:1. 输入本年度year2. 输入两个同学的生日日期(m1-d1,m2-d2)3. 计算第一个同学的生日是本年的第几天days14. 计算第二个同学的生日是本年的第几天days25. 计算并输出days1和days的差值在第一层的算法分析中,第3、4步还需要细化,解决求一个日期是该年的第几天的问题。例如求2012年4月5日是2012年的第几天,通常的做法是前3个月的总的天数相加,再加上4月份的5天。每个月的总的天数分三种情况:有31天的,有30天的,2月份单独按闰年和非闰年区分为29天和28天,得到算法如下:year,month,day表示

23、日期,days表示第几天1. 将day赋给days2. 累加从month-1月到1月的总的天数到days3. 返回days完成累加从month-1月到1月的总的天数到days,可以利用switch语句的case语句没有break语句,则顺次进入下一case分支的特性来完成累计。2月份要根据是否闰年区分是29天还是28天。判断一个年份是否是闰年的算法:如果year能被4整除但不能被100整除,或者能被400整除则返回1 否则返回0 到此向下细化的任务完成,得到模块结构如图5-7所示1.计算生日的差值2.计算某年某月某日是该年的第几天3.判断闰年图5-7 任务2 模块结构图2. 编写有返回值的带参

24、函数C语言实现模块结构图中模块是通过函数来完成的,任务2包括三个函数,模块1对应的是main函数,模块2和模块3是两个有返回值的函数。模块2是已知一个日期求是该年的第几天;模块3是一个判断函数,判断是否是闰年,返回真或假。由于C语言不提供布尔类型表示真假,通常返回1表示真,返回0表示假。带参有返回值的函数定义的一般形式为:函数类型 函数名(数据类型 形参变量1,数据类型 形参变量2) return 表达式;return语句的作用是将函数的计算结果返回到函数调用处,表达式的值的类型应与函数类型一致。表达式可以直接是一个常量,也可以是变量,也可以是计算表达式。在定义函数的时候,函数名应与函数实现的

25、功能相呼应,已知的数据要设计形参变量来接受,计算的结果数据决定函数的类型。模块3的实现功能是已知一个年份求是否是闰年。形参需要一个,接受年份,int类型。返回值是0或1,int类型,可以定义函数如下:intisLeap (int y) return (y%4=0&&y%100!=0 |y%400=0);模块2的实现是已知年月日,求第几天,形参变量需要三个,分别表示年月日,int类型;返回值是第几天,int类型。根据算法可以定义函数如下:intcalcDays(inty,intm,int d) int days=d; switch(m-1) case 12: days+=31;c

26、ase 11:days+=30; case 10: days+=31; case 9 :days+=30;case 8 : days+=31; case 7 :days+=31;case 6 :days+=30;case 5 : days+=31;case 4 :days+=30;case 3 : days+=31;case 2 :days+=isLeap(y)?29:28;case 1 : days+=31;return days;3. 调用有返回值的带参函数的形式调用有返回值的带参函数和调用无返回值的带参函数类似,区别在于需要设计如何接收函数计算的返回值。可接收返回值的位置主要有三种,以调

27、用calcDays函数为例:(1)赋值语句index=calcDays(2012,4,5);(2)运算表达式 d=calcDays(2012,8,5)- calcDays(2012,4,5);(3)函数的参数printf(“%d-%d-%d是该年的第几天”,year,month,day,calcDays(year,month,day);返回值为真和假的判断函数调用位置通常在书写if条件或者是循环条件的位置,例如函数calcDays中在一个条件表达式中调用isLeap函数:days+=isLeap(y)?29:28;isLeap(y)根据y是否为闰年返回1或0,1为真,条件表达式的值取?后面的2

28、9;0为假,条件表达式的值取:后面的28。条件表达式的值累加给days变量。完成本任务的完整程序如下所示,粗体部分为函数调用的语句【例5.5 sample05_04.cpp】#include <stdio.h>#include<math.h>intisLeap (int y);intcalcDays(int y,int m,int d);int main()int year,m1,d1,days1,m2,d2,days2;printf("计算两个生日相差的天数n");/输入年份和两个同学的生日日期。printf("year=");

29、scanf("%d",&year);printf("第一个同学的生日(mm-dd):");scanf("%d-%d",&m1,&d1);printf("第二个同学的生日(mm-dd):");scanf("%d-%d",&m2,&d2);/计算同学的生日是本年的第几天。days1= calcDays(year,m1,d1);days2= calcDays(year,m2,d2);/计算并输出days1和days的差值printf("两个生日相差的天

30、数为%dn",abs(days1-days2);return 0;/* *求一个日期是该年的第几天 */intcalcDays(int y,int m,int d) int days=d;switch(m-1) case 12: days+=31;case 11:days+=30;case 10: days+=31;case 9 :days+=30;case 8 : days+=31;case 7 :days+=31;case 6 :days+=30;case 5 : days+=31;case 4 :days+=30;case 3 : days+=31;case 2 :days+=

31、isLeap(y)?29:28;case 1 : days+=31;return days;/* *判断一个年份是否是闰年 */int isLeap (int y)return (y%4=0&&y%100!=0 |y%400=0);运行示例图5-8 运行结果l 调试错误程序示例多函数结构的程序查错步骤一般如下:(1)首先要定位出错的函数。在main函数中,断点设置位置往往在IPO的交界处,以确定是输入的错误,还是处理的错误,还是输出的错误。带参有返回值的函数的调用位置往往是断点的设置位置。(2)如果处理函数的返回值出错,则在出错的函数中第一句设置断点,确定参数传值是否正确。(3

32、)如果参数传值正确,则在return语句前设置断点,确定是处理算法出错,还是return语句出错。(4)如果算法出错,则在算法的合适位置设置断点,单步执行,观察变量的值找出错误。打开程序文件error05_01.cpp,实现功能与smaple05_04.cpp相同。#include <stdio.h>#include<math.h>intcalcDays(int y,int m,int d);int main()int year,m1,d1,days1,m2,d2,days2;printf("计算两个生日相差的天数n");/输入年份和两个同学的生日日

33、期 printf("year=");scanf("%d",&year);printf("第一个同学的生日(mm-dd):");scanf("%d-%d",&m1,&d1);printf("第二个同学的生日(mm-dd):");scanf("%d-%d",&m2,&d2);/计算同学的生日是本年的第几天days1= calcDays(int year,m1,d1);days2= calcDays(int year,m2,d2);/计算并输

34、出days1和days的差值printf("两个生日相差的天数为%dn",abs(days1-days2);return 0;intcalcDays(int y,int m,int d) int days=d;switch(m) case 12: days+=31;case 11: days+=30;case 10: days+=31;case 9 :days+=30;case 8 :days+=31;case 7 :days+=31;case 6 :days+=30;case 5 :days+=31;case 4 :days+=30;case 3 :days+=31;ca

35、se 2 :days+=isLeap(y)?29:28;case 1 :days+=31;return days;int isLeap (int y) if(y%4=0&&y%100!=0 |y%400=0) return 0; else return 1;先对程序文件进行编译,有编译错误,首先修改编译错误。第一条编译错误指向main函数的calcDays函数调用语句,函数调用时应给出参数的值(实参),赋给函数定义处的形参变量,调用时不需要加变量类型。错误语句days1= calcDays(int year,int m1,int d1);days2= calcDays(int

36、year,int m2,int d2);编译错误提示error05_01.cpp(20) : error C2144: syntax error : missing ')' before type 'int'修改后语句days1= calcDays(year,m1,d1);days2= calcDays(year,m2,d2);修改后重新编译,第一条编译错误指向calcDays函数定义中的case 2语句。编译错误提示isLeap是没有声明的标示符,isLeap是用户自定的函数,编译系统不能识别一般两种原因,一是没有函数声明,二是拼写错误。错误语句case 2

37、:days+=isLeap(y)?29:28;编译错误提示error C2065: 'isLeap' : undeclared identifier修改在main函数前增加函数声明语句。int isLeap (int y);再次编译通过,可以运行,运行结果如图所示,结果相差一天,与结果不符,存在逻辑错误。图5-8调试运行图(1)调试逻辑错误,在main函数最后的输出语句前设置断点,按F5调试运行,输入year和两个同学的生日日期后,程序在断点处停下,如图5-9所示,在locals选项卡中观察变量的当前值,year和两个同学的生日日期数据正确,days1和days2的值不正确,第

38、一个同学的生日日期应为当年的第28天,第二个同学的生日日期应为当年的第126天,calcDays计算出错,按F5结束本次运行。图5-9调试运行图(2)继续检查calcDays函数的出错原因,calcDays函数的return语句直接return days,没有错误,故可以确定函数算法有误,在switch语句前设置断点。按F5调试运行,输入year和两个同学的生日日期后,程序在断点处停下,如图5-10所示,观察变量的值,calcDays函数的参数y、m、d已正确获得main函数传来数据2012、1、28,days取得初值28。继续单步执行按F10后,跳转到switch语句中的case 1,day

39、s增至59,这里产生一个逻辑错误。第一个同学是1月份的,不需要加上一月的最大天数,算法上应累加1(m-1)个月的最大天数,switch语句的开关表达式应为(m-1)。图5-10调试运行图(3)执行菜单命令stop debugging结束本次调试,修改后switch语句的表达式后再次运行仍不对,再次断点调试发现days1的值正确为28,但days2的值错误,为125。单步调试calcDays函数,计算第二个同学的生日时,程序正确地进入case 2,在watch1窗口测试isLeap函数的值发现isLeap的值错误,如图5-11所示。2012年是闰年,isLeap(y)应为1。检查isLeap函数

40、发现逻辑错误,交换两个return语句的位置,再执行程序正确。图5-11调试运行图(4)独立任务任务1改错计算n!程序文件error5-2.cpp的功能是调用fact(n) 函数,计算n!。#include <stdio.h>int main(void)int n;double f;printf("Input n:");scanf("%d",&n);f=fact(m); /设置调试断点1printf("%d!=%fn",n,f);return 0; /设置调试断点2double fact(m)int i;doubl

41、e product;for(i=1;i<=m;i+)product= product*i;return 0; /设置调试断点3要求:1、请根据VC6的compile和link错误信息改正错误,使程序可以运行。(要求:截屏一个错误信息,给出对应的修改语句)2、请按注释要求设置3个调试断点,纠正程序逻辑错误,使其能正确输出n!。(1)逻辑错误纠正前的调式结果:l 执行到断点1处,能显示n变量值的watch窗口截屏:l 执行到断点3处,能显示i、product变量值的watch窗口截屏:l 执行到断点2处,能显示f变量值的watch窗口截屏:(2)逻辑错误纠正后的调式结果:l 执行到断点1处,能显示n变量值的watch窗口截屏:l 执行到断点3处,能显示i、product变量值的watch窗口截屏:l 执行到断点2处,能显示f变量值的watch窗口截屏:任务2改写程序增加参数可以增加函数的可变性和通用性,请修改sample05_03.cpp:(1)复制sample05_03.cpp并改名

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论