转自:http://www.softwarehistory.net/h/ChengxuShejiJichu/
第 9 章 模块化程序设计
9 .1 模块化程序设计概念
人类在解决复杂问题时普遍采用的策略是“分而治之,各个击破”。程序设计人员在设计比较复杂的应用系统软件时,采用的也是这样的策略,即将复杂的任务分解成若干个子任务或者模块,再分别设计每个子任务/ 模块,正好像搭积木、修房子一样,整个程序是由一个个模块组成,这就是模块化程序设计的概念。
1 .自顶向下的设计方法
对比较复杂的程序设计问题,程序设计人员通常是将任务逐步分解细化,首先把复杂问题分解为主要任务,然后在主要任务中再进一步细分为一系列子任务,直到所有的任务都被确定。这种先定主要任务,再逐步细分成子任务的过程称为自顶向下设计方法。
程序设计人员采用自顶向下的设计方法,在程序设计中引入了模块化的概念。也就是说在程序设计中,每个子任务最后都变成独立的程序模块。每个模块构成整个算法的一部分,并完成一个单独的功能。使用模块可以使整个算法更简单、更有系统性,并且能够减少错误。由于每个模块只完成一个单独的任务,程序设计人员可以逐个模块地进行开发,设计出相应算法,所有模块的算法都开发完成后,一个复杂问题就得到了完全的解决。
程序设计模块化的过程就是把复杂问题分解成独立模块的过程,每个模块完成一个单独的功能。自顶向下的设计方法可以让程序设计人员把全部精力集中在算法的总体设计上,而不必过多地考虑较低级模块的设计细节。该设计方法的另一个优点是,相对独立的模块很容易理解,也很容易编写和修改,具有较好的重用性,必要的话还可以单独对其进行修改。
126 程序设计基础
2 .程序设计模块化的过程
把复杂问题细分成更小的模块时,应首先列出将要完成的一系列事务或处理步骤,然
后将这些事务进行组合,形成便于管理的任务或功能模块。定义问题时,应将全部精力集
中在所要完成的任务或功能上,而不必关心每个模块的具体细节。每个功能由许多事务
组成,所有这些事务都致力于完成一个单独的任务。
每个模块完成一个相对独立的特定子功能,并且与其他模块之间的关系应简单,该模
块有一个入口、一个出口,并具有自顶向下顺序执行的指令。模块的名称应该描述所要做
的工作,即描述一个独立特定的功能。习惯上,命名模块采用动词短语,该动词后跟两个
词的宾语,这点尤其重要。由于名字有助于识别所设计模块完成的任务或功能,即“顾名
思义”,而且根据习惯严谨命名的模块会使算法和组合代码更容易被人所理解。采用见名
知义的模块名称,可以清楚地描述设计模块所完成的任务,而且阅读代码的任何人都可以
很容易地看出该模块的作用。
3 .主程序
由于每个模块都只完成一个特定的任务,因此需要一个主程序或模块进行总体控制,
把所有的模块组合在一起并协调它们之间的活动。从主程序中可以看出程序的主要功
能,以及它们完成的先后顺序,还可以看出数据流向和主要的控制结构。另外,主程序应
容易阅读,在程序长度上易于管理,并有合理的逻辑结构。
在C语言程序中,主程序就是主函数main()。
9 .2 模块化程序结构图
1 .程序结构图
程序设计任务经分解成模块之后,就可以用框图的形式来表示这些模块,该框图称为
程序结构图,它表示出了所有模块的名称及模块之间相互的层次关系。
程序结构图用树型结构的一系列多层次的矩形框来描绘软件的层次结构。程序结构
图中的每个矩形框对应于程序中的一个模块,矩形框间的连线表示模块之间的调用关系。
程序结构图并没有表示出处理模块的特定的先后顺序,只是模块之间在算法中出现的先
后顺序的排列。
程序结构图的顶层矩形框代表主控模块或主程序,相邻的下层模块是被主控模块直
接调用的,也就是说这些模块直接从属于主控模块。再下一层模块从属于第一层模块,以
此类推,直到最底层模块。这种框图形式表示的层次关系看起来与大型公司内部的人员 第9章 模块化程序设计 127关系有些类似。例如,大型公司一般有一个总经理,总经理下面分别设有负责销售、财务、人力资源的副总经理,每个副总经理的下面再设有若干个主管等,如图9-1所示。
图9-1 组织结构图
主程序对每个执行任务的从属模块都要进行控制,主程序模块要调用从属模块。因此,主程序模块被称为主调模块,从属模块称为被调模块。为完成任务,被调模块要把控制返回给主调模块。程序结构如图9-2所示。
图9-2 程序结构图
2 .模块间的通信
设计算法时,程序设计人员不仅要考虑把复杂问题划分成模块来处理,同时也要考虑模块间的信息流。模块间的通信越少、越简单,该模块也就越容易理解和维护,而不必过多涉及其他模块。模块间的这种信息流被称为模块间的通信。
模块间的通信有下面两种方法。
(1) 通过指定变量的作用域(局部或全局数据)来实现。
128 程序设计基础
(2) 通过参数调用来实现。
3 .变量的作用域
变量的作用域是指程序中变量被定义和可被引用的区域。变量可以是全局的,全
局变量在整个程序中都起作用;它也可以是局部的,局部变量只在所定义的模块内起
作用。
(1) 全局数据
在程序的主调模块和被调模块中,如果所用的信息或数据都源自相同的变量名,那么
这样的数据就称为全局数据,变量的作用域就是整个程序,这是因为该数据可以被程序中
的每一个模块调用。
注意,并不是所有的数据都有必要定义成全局数据。
(2) 局部变量
在从属模块内定义的变量称为局部变量。局部变量不可以被主调模块使用,也不可
以被任何其他模块使用,它只在模块的自身范围内起作用。采用局部变量可以减少所谓
的程序边际效应。
(3) 边际效应
边际效应指的是程序中一个模块与其他模块之间交互通信所带来的影响。在从属模
块内改变全局变量的值时容易产生边际效应。边际效应不一定有危害,但它会降低程序
的可管理性。程序设计人员应该意识到这一点。
在任何时候,如果需要由别人来改写程序,而这个程序员并不是当初编写程序的人
时,就有可能对全局变量的值进行修改,而在修改时可能没有意识到其他模块中全局变量
的值的意义可能不同,这样就可能引起边际效应甚至错误的结果。
4 .参数传递
模块间通信的另一种方式是由参数调用来实现的。在调用时,参数从主调模块传递
到从属模块,参数传递的都是相对简单的数据项。当从属模块执行完毕时又把控制转移
到调用方,同时参数中的值也被传回主调模块。
当主模块调用从属模块时,必须指定被调模块的名称及要传递给被调模块的参数列
表,参数是以括号的形式列出的。
被调模块名称之后也有一个参数序列,这些参数序列接收来自主调模块的参数值。
主调模块和被调模块的名称和参数序列不要求完全一致(尽管相同的话可读性要好一
些),但在参数数目、参数类型和次序上要求必须一致。
这些参数具有以下3种功能。
(1) 从主调模块向从属模块传递信息。从属模块在处理过程中接收来自主调模块的 第9章 模块化程序设计 129信息,但不需要将信息传递到主调模块。
(2) 从从属模块向主调模块传递信息。主调模块在随后的处理中将使用那些参数。
(3) 实现双向通信。主调模块可以向被调模块传递信息,这些信息在被调模块中进行相应处理后可返回给主调模块。
数据参数包括在模块间传递的实变量或数据项。
状态参数在程序中起标记的作用,它可以取“真”或“假”二者之一。也就是说,在特定条件下,这些程序标记或转换被设置为“真”或“假”,然后可被用于控制进一步的处理过程。
当设计模块化的程序时,程序设计员应避免使用数据参数来表示状态信息。因为这将会给程序带来下面两方面的影响。
(1) 容易使程序的读者产生困惑。由于变量的意义改变了,也就是说一个变量用于两种目的,这样会使阅读程序的人不易理解其中的含义。
(2) 在开发后期,维护人员对程序进行修正时容易造成不可预见的错误。因为维护人员可能不清楚或没有注意到变量的双重目的。
9 .3 模块的独立程度
模块的独立程度可以由两个定性标准度量,即内聚和耦合。耦合用来衡量不同模块彼此间互相依赖的紧密程度;内聚用来衡量一个模块内部各个元素彼此结合的紧密程度。
模块是整个算法的一部分,完成一个单独的功能。模块只有一个入口和一个出口,应该根据模块所完成的功能选择合适的名称。
程序设计员在模块化设计方面经常需要一些具体指导。最常遇到的问题是: 模块规模应该多大”, 模块是否太小了”或者“是否应该把所有的读操作放入一个模块”等。
9 .3 .2 模块耦合
程序设计人员设计程序算法时,不仅要考虑模块的内聚度,也要考虑模块之间信息流的传递情况。程序设计人员的目的在于保证模块的独立性,也就是说,某个模块和其他模块之间要有更少、更简单的连接方式。这些连接称为接口或耦合。
耦合是对程序结构中不同模块之间信息交换程度的度量。耦合的强弱取决于模块间接口的复杂程度,进入或访问一个模块的点,以及通过接口的数据。紧耦合意味着两个模块结构之间有更大的依赖性。由于模块间有更多个连接路径,因而发生在一个模块中的错误就有可能传播到程序的其他各部分模块。
松耦合和紧耦合是相对的。松耦合的模块更独立,并且更容易维护。
与Yourdon和Constantine 提出的内聚类型划分类似,Glenford Myers 也给出了几种类型的模块耦合。
图9-4是按照模块设计质量从最差到最好的顺序列出的5种类型的模块耦合图。需要指出的是,这些类型的耦合还没有严格的定义。它们只是Glenford Myers认为的在模块化程序设计中存在的几种耦合类型。
图9-4 模块耦合图
1 .公共耦合
公共耦合是指模块间访问同一全局数据结构(该数据结构可能是相关数据项的集合,例如一个记录或数组)的情况。当模块间采用公共耦合时,这些模块将共享某个全局数据结构。公共耦合意味着数据可以被程序中的任何模块访问和修改,这将使程序很难阅读和理解。
【例9-7】 程序中的add_score()模块和float average_score()模块访问同一全局数据结构iscore[SIZE],形成了公共耦合关系如下。
#include < stdio .h>
#define SIZE 5
136 程序设计基础
int iscore[SIZE] ={0};
voidadd_score(int n);
float average_score();
void main()
{
int iindex=0;
float fresult=0 .0;
for(iindex=0;iindex<size;iindex=iindex+1)< p="">
{
printf(″Please input age :\n″);
scanf(″%d″,&iscore[iindex]);
}
add_score(20);
fresult=average_score();
printf(″average score is %f″,fresult);
}
voidadd_score(int n)
{
int iindex;
for(iindex=0; iindex<size; iindex="iindex+1)</p">
iscore[iindex] =iscore[iindex] +n;
}
float average_score()
{
int iindex;
float ftmp=0;
for(iindex=0; iindex<size; iindex="iindex+1)</p">
ftmp=iscore[iindex] +ftmp;
returnftmp/ iindex;
}
2 .外部耦合
外部耦合是指两个或更多的模块访问同一全局数据变量,而不是同一全局数据结构
的情况。
第9章 模块化程序设计 137
外部耦合与公共耦合是相似的,外部耦合访问的全局数据是基本数据项,而不是数据结构。由于全局数据有更简单的结构,因而与公共耦合相比,外部耦合被认为是更松散的耦合形式。
【例9-8】 程序中的int powerno(int ifirst,int isecond)模块和int sumno(int ifirst,int isecond)模块访问同一全局数据变量int ifirst,isecond,形成外部耦合关系如下。
#include < stdio .h>
int ifirst=0,isecond=0;
int sumno(int ifirst,int isecond);
int powerno(int ifirst,int isecond);
void main()
{
int itmp=0;
scanf(″%d%d″,&ifirst,isecond);
itmp=sumno(ifirst,isecond);
printf(″sum is %f″,itmp);
itmp=powerno(ifirst,isecond);
printf(″power is %f″,itmp);
}
int sumno(int ifirst,int isecond)
{
return ifirst+isecond;
}
int powerno(int ifirst,int isecond)
{
return ifirst * isecond;
}
3 .控制耦合
控制耦合是指两个模块间传递控制变量,并通过该变量控制另外模块逻辑关系的情况。两个模块间通过参数传递信息,这些传递信息中包含控制变量,例如程序标记或转换开关等。
控制耦合模块的弱点在于,两个模块间需要传递控制信息,这就意味着其中一个模块要了解另外一个模块的内部逻辑关系。
138 程序设计基础
【例9-9】 主模块向add_all_score模块传递控制变量icontrol的程序。
#include < stdio .h>
void main()
{
int icontrol;
scanf(″%d″,&icontrol);
add_all_score(icontrol);
}
int add_all_score(int key)
{
switch(key)
{
case 1:
add_score1();
break;
case 2:
add_score2();
break;
case 3:
add_score3();
break;
}
}
4 .标记耦合
标记耦合是指一个模块向另一个模块传递非全局的数据结构。非全局数据结构通过
参数进行传递。
标记耦合模块是一种松散耦合的情况,并保证了良好的模块化程序设计质量。两个
模块间惟一的关联是通过数据结构来传递的。任何一个模块都没有必要了解其他模块内
部的逻辑关系。
【例9-10】 模块add_scores()与模块average_score()之间通过非全局的数据结构
iscore[SIZE]形成标记耦合的程序。
#include < stdio .h>
第9章 模块化程序设计 139#define SIZE 5
voidadd_score(int n,int iscore[]);
float average_score(int iscore[]);
void main()
{
int iindex=0;
float fresult=0 .0;
int iscore[SIZE] ={0};
for(iindex=0;iindex<size;iindex=iindex+1)< p="">
{
printf(″Please input age :\n″);
scanf(″%d″,&iscore[iindex]);
}
add_score(20,iscore);
fresult=average_score(iscore);
printf(″average score is %f″,fresult);
}
voidadd_score(int n,int iscore[])
{
int iindex;
for(iindex=0; iindex<size; iindex="iindex+1)</p">
iscore[iindex] =iscore[iindex] +n;
}
float average_score(int iscore[])
{
int iindex;
float ftmp=0;
for(iindex=0; iindex<size; iindex="iindex+1)</p">
ftmp=iscore[iindex] +ftmp;
returnftmp/ iindex;
}
5 .数据耦合
数据耦合是指一个模块向另一个模块传递非全局的数据变量。数据耦合和标记耦合140 程序设计基础
有些相似,只是数据耦合传递的非全局数据变量是基本数据项,而不是数据结构。
数据耦合是最松散耦合的情况,具有最好的模块化程序设计质量。两个模块间惟一
的关联是通过一个或多个基本数据项传递的。
【例9-11】 主模块向my_abs模块传递非全局的数据变量a的程序。
#include < stdio .h>
void main()
{
int x;
scanf(″%d″,&x);
printf(″%d″,my_abs(x));
}
int my_abs(int a)
{
int iresult=0;
if (a > =0)
iresult=a;
else
iresult= - a;
return iresult;
}
考虑耦合形式时应注意如下。
程序设计人员设计程序解决算法时,应该尽量遵循模块独立性及模块间信息交换量
最小的设计原则。即所设计的程序结构,应使得每个模块完成一个相对独立的特定子功
能,并且与其他模块之间的关系尽量简单。
在程序设计语言允许的情况下,应尽量使每个模块与相应的外部环境分离。为此,应
注意下面4个方面。
(1) 尽量以参数的形式向从属模块传递数据,而不要采用全局数据的形式。
(2) 把每个从属模块设计成独立的单元,只能接受外界传递给它的数据。
(3) 对某个模块所做的修改应不影响到程序的其他部分。
(4) 需要的话,可把信息传递到主调模块。
程序设计人员设计程序时,首要考虑的必须是保证所生成的模块和程序易于理解和
修改。如果所选的程序设计语言提供了惟一的全局数据,程序设计良好的标准是将紧密
耦合降低到最小限度。
第9章 模块化程序设计 141
9 .4 模块化程序设计步骤
自顶向下的模块化设计过程是相对简单的。在进行软件开发时,程序设计人员一般需要遵循如下模块化程序设计步骤。
(1) 定义问题。把所要解决问题划分成输入、输出和处理3个部分。处理部分由所要完成的一系列事务构成。
(2) 确定组成程序的模块。把事务按组分解成子任务或功能模块以进一步模块化。应记住模块是程序的一部分,它主要执行单独的功能。注意并不是所有的模块都可以在此阶段确定下来,只有程序结构图的第一级模块可在这时确定,其余的更多从属模块要在以后的工作中确定。
(3) 绘出程序结构图。主要用来清楚地描绘模块及模块之间的相互关系。程序的结构一经确定,就可以考虑各个模块的处理次序问题,还可以在这一步考虑模块间通信及参数调用问题。
(4) 用流程图等算法表示方法确定主程序的逻辑结构。主程序除了应该包括循环前的最初处理,循环内的处理,以及退出循环后的最终处理外,还应该包括对程序主要处理模块的调用,以及程序自身要容易阅读、易理解等。
(5) 为程序结构图中每个模块编写算法。当程序结构图中最底层模块的算法开发完毕时,也就完成了整个程序设计模块化过程。
(6) 最后审查整个算法。首先检查主控模块,然后依次检查每个从属模块,直到没有任何逻辑错误为止。
9 .5 模块化程序设计实例
下面以设计一个简单的成绩管理软件为例,一步一步地按模块化程序设计方法进行设计。
1 .定义问题
设计一个成绩管理软件,其基本功能包括:输入成绩,成绩加分,计算平均成绩,找出最高分,找出最低分,输出成绩等。
2 .确定组成程序的模块
根据成绩管理软件的功能,确定软件的基本模块包括:输入模块,加分模块,平均分模块,最高分模块,最低分模块,输出模块等。
142 程序设计基础
3 .绘制程序结构图
成绩管理软件的结构图如图9-5所示。
图9-5 成绩管理软件结构图
4 .流程图
用流程图确定主程序的逻辑结构,如图9-6所示。
在流程图中,istate 的作用是记录是否已经输入成绩。istate 的使用有如下两种
方式。
(1) 作为全局变量使用。此时istate可以在所有模块中改变其值,主程序更简洁,但
可能产生边际效应。
(2) 作为主程序的局部变量使用。此时istate只能在主程序中改变其值。在主程序
中可以直观地看到其变化,能够防止边际效应。
采用方式(2)的主程序如下。
#include < stdio .h>
#define SIZE 10
void main()
{
int iscore[SIZE] ={0};
int key= - 1;
int iresult=0;
float fresult=0;
int istate=0;
printf(″1:Input scores;\n″);
第9章 模块化程序设计 143
图9-6 成绩管理软件主程序流程图
printf(″2:Output scores;\n″);
printf(″3:Count for the max score;\n″);
printf(″4:Count for the minimum score;\n″);
printf(″5:Count for the total score;\n″);
printf(″6:Count for theaverage score;\n″);
printf(″- 1:Exit .\n″);
while(1)
{
printf(″Please input your choose:″);
scanf(″%d″,&key);
if (key = = - 1)
144 程序设计基础
break;
switch(key)
{
case1:
istate=input_all_numbers(iscore,SIZE);
break;
case2:
if (istate = =0)
printf(″ERROR:You must input scoresfirst ! \n″);
else
output_all_numbers(iscore,SIZE);
break;
case3:
if (istate = =0)
printf(″ERROR:You must input scoresfirst ! \n″);
else
{
iresult=count_for_max(iscore,SIZE);
printf(″the max score is %d\n″,iresult);
}
break;
case4:
if (istate = =0)
printf(″ERROR:You must input scoresfirst ! \n″);
else
{
iresult=count_for_min(iscore,SIZE);
printf(″the min score is %d\n″,iresult);
}
break;
case5:
if (istate = =0)
printf(″ERROR:You must input scoresfirst ! \n″);
else
第9章 模块化程序设计 145
{
iresult=count_for_total(iscore,SIZE);
printf(″the total score is %d\n″,iresult);
}
break;
case6:
if (istate = =0)
printf(″ERROR:You must input scoresfirst ! \n″);
else
{
fresult=count_for_average(iscore,SIZE);
printf(″the average score is % .2f\n″,fresult);
}
break;
default:
printf(″ERROR:Input error,please input again! \n″);
}
}
}
5 .编写算法
为程序结构图中每个模块编写算法。
在前面的学习中,已经学过如何加分,计算平均分,以及查找最高、最低分,在这里就不再画出流程图了。
6 .审查算法
最后审查整个算法,直到没有任何逻辑错误。
7 .编程调试
审查算法后,即可进行编程调试。
【例9-12】 成绩管理软件的完整程序。
/ *name:a management system about scores*/
/ *creat:stone,2004/ 3/ 8*/
146 程序设计基础
/ *modify:stone,2004/ 3/ 20*/
/ *version:1 .0
#include < stdio .h>
#define SIZE 5 / *定义成绩个数的符号常量*/
int input_all_numbers(int iscore[],int isize);
void output_all_numbers(int iscore[],int isize);
int count_for_max(int iscore[],int isize);
int count_for_min(int iscore[],int isize);
int count_for_total(int iscore[],int isize);
floatcount_for_average(int iscore[],int isize);
void main()
{
int iscore[SIZE] ={0};
int key= - 1;
int iresult=0;
float fresult=0;
/ *用于区分是否已经输入数据的标志,0表示未输入,1表示已经输入*/
int istate=0;
/ *主菜单,可以选择完成不同的成绩统计功能*/
printf(″**********************************************\n″);
printf(″This is a management system about scores .\n\t\tWELCOME ! \n″);
printf(″**********************************************\n″);
printf(″1:Input scores;\n″);
printf(″2:Output scores;\n″);
printf(″3:Count for the max score;\n″);
printf(″4:Count for the minimum score;\n″);
printf(″5:Count for the total score;\n″);
printf(″6:Count for theaverage score;\n″);
printf(″- 1:Exit .\n″);
while(1)
{
printf(″Please input your choose:″);
scanf(″%d″,&key);
/ *根据输入选择的不同,分别进行不同的处理*/
第9章 模块化程序设计 147if (key = = - 1)
break;
switch(key)
{
case 1:
istate=input_all_numbers(iscore,SIZE);
break;
case 2:
if (istate = =0)
printf(″ERROR:You must input scores first ! \n″);
else
output_all_numbers(iscore,SIZE);
break;
case 3:
if (istate = =0)
printf(″ERROR:You must input scoresfirst ! \n″);
else
{
iresult=count_for_max(iscore,SIZE);
printf(″the max score is %d\n″,iresult);
}
break;
case 4:
if (istate = =0)
printf(″ERROR:You must input scores first ! \n″);
else
{
iresult=count_for_min(iscore,SIZE);
printf(″the min score is %d\n″,iresult);
}
break;
case 5:
if (istate = =0)
printf(″ERROR:You must input scores first ! \n″);
148 程序设计基础
else
{
iresult=count_for_total(iscore,SIZE);
printf(″the total score is %d\n″,iresult);
}
break;
case 6:
if (istate = =0)
printf(″ERROR:You must input scores first ! \n″);
else
{
fresult=count_for_average(iscore,SIZE);
printf(″the average score is % .2f\n″,fresult);
}
break;
default:
printf(″ERROR:Input error,please input again! \n″);
}
}
}
/ *功能: 输入学生成绩*/
int input_all_numbers(int iscore[],int isize)
{
int iindex=0;
printf(″please input %d scores:\n″,isize);
for(iindex=0; iindex<isize; iindex="iindex+1)</p">
{
scanf(″%d″,&iscore[iindex]);
}
return1;
}
/ *功能: 输出学生成绩*/
第9章 模块化程序设计 149void output_all_numbers(int iscore[],int isize)
{
int iindex=0;
for(iindex=0; iindex<isize; iindex="iindex+1)</p">
{
printf(″iscore[%2d] = %3d\n″,iindex,iscore[iindex]);
}
}
/ *功能: 计算最高分*/
int count_for_max(int iscore[],int isize)
{
int imax=iscore[0];
int iindex=0;
for(iindex=0; iindex<isize; iindex="iindex+1)</p">
{
if (imax<iscore[iindex])< p="">
imax=iscore[iindex];
}
return imax;
}
/ *功能: 计算最低分*/
int count_for_min(int iscore[],int isize)
{
int imin=iscore[0];
int iindex=0;
for(iindex=0; iindex<isize; iindex="iindex+1)</p">
{
if (imin>iscore[iindex])
imin=iscore[iindex];
}
return imin;
150 程序设计基础
}
/ *功能: 计算总分*/
int count_for_total(int iscore[],int isize)
{
int iindex=0;
int isum=0;
for(iindex=0; iindex<isize; iindex="iindex+1)</p">
{
isum=isum+iscore[iindex];
}
return isum;
}
/ *功能: 计算平均分*/
floatcount_for_average(int iscore[],int isize)
{
int iindex=0;
float faverage=0;
for(iindex=0; iindex<isize; iindex="iindex+1)</p">
{
faverage=faverage+iscore[iindex];
}
faverage=faverage/ isize;
returnfaverage;
}
由于 C语言中,常用的输入语句 scanf()存在缺陷,在1 .0 版中,输入成绩时,如
果误按了字母键,则程序会出现异常。为此,我们给出了一个改进版,通过自定义函
数 int getdigi(int icount)进行输入,从而解决了这个问题,提高了程序的稳定性和健
壮性。这个主程序能够灵活处理不同的分支情况,可以作为 C语言程序的一个基本
框架。
程序设计中不同的风格与经验,对程序的易用性和健壮性影响很大,要设计和编写出
成功的程序,需要更深入的学习,需要丰富的经验。
第9章 模块化程序设计 151【例9-13】 成绩管理软件程序的改进版。
/ *name:a management system about scores*/
/ *creat:stone,2004/ 3/ 8*/
/ *modify:stone,2004/ 3/ 30*/
/ *version:1 .1
1 .改进了输入时误按字符导致程序失效的问题
2 .采用void 型函数,简化主程序
3 .采用功能键退出程序,符合常例*/
#include < stdio .h>
#include
#define ESC 27 / *使用功能键控制正常退出*/
#define SIZE 5 / *定义成绩个数的符号常量*/
#define MAXdigit 20 / *定义数据的最大字符个数*/
int input_all_score(int iscore[],int isize);
void output_all_score(int iscore[],int isize);
voidcount_for_max(int iscore[],int isize);
voidcount_for_min(int iscore[],int isize);
voidcount_for_total(int iscore[],int isize);
voidcount_for_average(int iscore[],int isize);
int getdigi(int icount);
void main()
{
int iscore[SIZE] ={0};
char ckey=′a′;
/ *用于区分是否已经输入了数据的标志,0表示未输入,1表示已经输入*/
int istate=0;
/ *主菜单,可以选择完成不同的成绩统计功能*/
do
{
clrscr();
printf(″********************************************** \n″);
printf(″This is a management system about scores .\ n\ t \ tWELCOME !
\n″);
printf(″**********************************************\n″);
152 程序设计基础
printf(″1:Input scores;\n″);
printf(″2:Output scores;\n″);
printf(″3:Count for the max score;\n″);
printf(″4:Count for the minimum score;\n″);
printf(″5:Count for the total score;\n″);
printf(″6:Count for the average score;\n″);
printf(″Esc:Exit system .\n″);
printf(″Please input your choose(1-6):\n″);
/ *根据输入的选择的不同,分别进行不同的处理*/
ckey=getch();
if(ckey= =′1′)
istate=input_all_score(iscore,SIZE);
else if((istate= =0)&&(ckey !=Esc))
{
printf(″\nERROR:You must input scores first !″);
printf(″\nPress a key tocontinue .″);
getch();
}
else if (ckey= =′2′)
output_all_score(iscore,SIZE);
else if(ckey= =′3′)
count_for_max(iscore,SIZE);
else if(ckey= =′4′)
count_for_min(iscore,SIZE);
else if(ckey= =′5′)
count_for_total(iscore,SIZE);
else if(ckey= =′6′)
count_for_average(iscore,SIZE);
}while(ckey !=Esc);
}
/ *功能: 输入学生成绩*/
int input_all_score(int iscore[],int isize)
第9章 模块化程序设计 153{
int iindex=0;
printf(″please input %d scores:\n″,isize);
for(iindex=0; iindex<isize; iindex="iindex+1)</p">
{
printf(″\nplease input the %d scores:″,iindex);
iscore[iindex] =getdigi(3);
}
return1;
}
/ *功能: 输出学生成绩*/
void output_all_score(int iscore[],int isize)
{
int iindex=0;
for(iindex=0; iindex<isize; iindex="iindex+1)</p">
{
printf(″iscore[%2d] = %3d\n″,iindex,iscore[iindex]);
}
getch();
}
/ *功能: 计算最高分*/
voidcount_for_max(int iscore[],int isize)
{
int imax=iscore[0];
int iindex=0;
for(iindex=0; iindex<isize; iindex="iindex+1)</p">
{
if (imax<iscore[iindex])< p="">
imax=iscore[iindex];
}
printf(″\nThe max score is %d″,imax);
154 程序设计基础
getch();
}
/ *功能: 计算最低分*/
voidcount_for_min(int iscore[],int isize)
{
int imin=iscore[0];
int iindex=0;
for(iindex=0; iindex<isize; iindex="iindex+1)</p">
{
if (imin>iscore[iindex])
imin=iscore[iindex];
}
printf(″\nThe min score is %d″,imin);
getch();
}
/ *功能: 计算总分*/
voidcount_for_total(int iscore[],int isize)
{
int iindex=0;
int isum=0;
for(iindex=0; iindex<isize; iindex="iindex+1)</p">
{
isum=isum+iscore[iindex];
}
printf(″\nThe total score is %d″,isum);
getch();
}
/ *功能: 计算平均分*/
voidcount_for_average(int iscore[],int isize)
{
第9章 模块化程序设计 155
int iindex=0;
float faverage=0;
for(iindex=0; iindex<isize; iindex="iindex+1)</p">
{
faverage=faverage+iscore[iindex];
}
faverage=faverage/ isize;
printf(″\nThe average score is % .2f″,faverage);
getch();
}
/ *function:输入指定个数的数字,构成一个整形数据,可以用来代替scanf*/
/ *name:getdigi() */
/ *creat:stone,2004/ 3/ 25*/
/ *modify:stone,2004/ 3/ 26*/
/ *parametric:int icount,数字个数*/
/ *return:int,整形数字*/
int getdigi(int icount)
{
int iloop=0;
char digit_str[MAXdigit] ={′0′};
iloop=0;
do
{
digit_str[iloop] =getch();
if (isdigit(digit_str[iloop]))
{
putch(digit_str[iloop]);
iloop+ + ;
}
}
while(iloop<icount);< p="">
returnatoi(digit_str);
}
156 程序设计基础
双语精髓
The mainline
Since each module performs a single specific task,a mainline routine must provide
the master control that tiesall themodules together andcoordinates their activity .This
program mainline should show the main processing functions,and the order in which
theyare to be performed . It should also show the flow of data and the major control
structures .The mainline should alsobeeasy to read,be of manageable length and show
sound logic structure .
主程序
由于每个模块完成一个特定的任务,因此需要一个主程序或主模块进行总体控制,把
所有的模块组合在一起并协调它们之间的活动。主程序显示了程序的主要功能,以及这
些功能完成的顺序,还显示了数据流向和主要的控制结构。另外,主程序应该容易阅读,
从程序长度上易于管理,并有合理的逻辑结构。
Module
A module must be large enough to perform its task,and must include only the
operations thatcontribute tothe performanceof that task .It shouldhaveasingleentry,
anda single exit with a top - to - bottom sequence of instructions . The name of the
module shoulddescribe thework tobedoneasa single specificfunction .Theconvention
of naming a module by using a verb,followed by a two - word object,is particularly
important here, as it helps to identity the separate task that the module has been
designed to perform .Also,the careful naming of modules using this convention makes
the algorithm and resultant code easier to follow . For example, typical module names
might be:
Print_page_headings
Calculate_sales_tax
Byusingmeaningfulmodule names suchas these,youcanautomaticallydescribe the
task that themodule has beendesigned toperform,andanyone reading thealgorithmcan
easily see what the module is supposed to do .
第9章 模块化程序设计 157
模块
模块的大小应能完成其任务,并且只包括完成其任务的操作。它应该只有一个入口,一个出口和一个从上至下的语句顺序。模块的名字应该表明它所完成的单一的特定功能。为了有助于区分所设计的完成不同功能的模块,命名一个模块的常用方法中,采用一个动词后面跟两个宾语词的命名方式在这里就显得特别重要。采用这种命名方法,仔细地对模块进行命名,可以使它代表的算法和产生的代码更容易理解。例如,
打印_报表_表头
计算_销售_税
通过采用见名知意的方法命名模块,可以清楚地描述所设计的模块完成的任务,并且阅读算法的人可以很容易地了解该模块的作用。
本章知识要点
(1) 掌握模块化设计的基本概念。
(2) 掌握模块间的通信方法。
(3) 理解模块的内聚和耦合。
练习题
1 .将例9-12的主程序改写为采用全局变量的形式。
2 .分析说明采用全局变量与局部变量的优劣。