C语言程序设计基础(第二版)课件:函数-模块化程序设计 方法的实现_第1页
C语言程序设计基础(第二版)课件:函数-模块化程序设计 方法的实现_第2页
C语言程序设计基础(第二版)课件:函数-模块化程序设计 方法的实现_第3页
C语言程序设计基础(第二版)课件:函数-模块化程序设计 方法的实现_第4页
C语言程序设计基础(第二版)课件:函数-模块化程序设计 方法的实现_第5页
已阅读5页,还剩123页未读 继续免费阅读

下载本文档

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

文档简介

函数——模块化程序设计

 方法的实现7.1模块化程序设计方法与函数7.2函数的定义7.3函数的调用7.4函数调用的条件与函数声明7.5函数的嵌套调用和递归调用7.6变量的作用域与函数间的7.7用函数实现模块化程序设计实操训练课外练习

7.1模块化程序设计方法与函数

程序模块与函数有何关系?怎样根据问题功能模块设计函数?结构化程序设计由迪克斯特拉(E.W.Dijkstra)在1969年提出,是以模块化设计为中心,将待开发的软件系统划分为若干个相互独立的模块,这样使完成每一个模块的工作变得单纯而明确,为设计一些较大的软件打下了良好的基础。

模块化程序设计的基本要点是贯彻“自顶向下,逐步细化”的思想方法,即将一个复杂功能的编程问题划分成若干个功能相对简单的子问题。这种划分可以逐层进行,直到便于编程为止。在C语言中,一个模块的功能由一个函数来实现。顶层函数是主函数main()。功能模块与C语言函数的关系如图7.1所示。图7.1模块与函数

分析问题,划分出模块之后,C程序设计的工作就是函数的定义了。一般来说,底层模块函数的定义主要是某种功能的实现,上层模块函数的定义主要考虑对下层模块函数的调用与数据传递。

C语言中包含多种函数类型。从用户使用的角度可分为标准库函数和用户自定义函数;从函数接口可分为有参函数和无参函数。标准库函数是由系统提供的,用户只需按要求调用即可。

7.2函 数 的 定 义

怎样定义函数?定义函数要考虑哪些问题?定义函数要解决两个方面的问题:一是函数间的接口问题,二是功能实现问题。接口问题包括如何被上层函数调用,调用时需传递什么数据,调用返回时需传递什么数据。功能实现问题就是如何实现模块的过程算法。

函数定义的一般形式为

下面对函数定义的各组成部分作一说明。

(1)基类型符可以是基本数据类型中任一类型的标识符,如int、char、float、double等。其作用是定义函数返回值的类型。当函数执行后不需要返回值或不允许返回值时,函数类型标识符用void表示。函数类型标识符可以缺省,系统默认为int型。当缺省时,在TurboC系统中编译可以完全通过,而在VC++6.0系统中会出现警告。标准C语言中不建议省略函数类型。

(2)函数名是函数调用的名称。其命名规则同变量名、数组名一样。实质上,函数名表示函数存储的首地址,将在第8章详细介绍。

(3)形式参数,简称形参,用于接收函数调用时所传递的数据,可以是变量名、数组名等。因为在函数定义时不表示具体数据,只表示虚设的数据对象,所以称为形式参数。当形参多于1个时,形参之间要用逗号分隔,就构成了形参表。在第一种定义形式中,每一个形参前要说明其类型。在第二种定义形式中,只列出形参名,但在其后对形参的类型逐一进行了说明。如果形式参数表中没有参数,则括号必须保留,就成为无参函数定义。

“基类型符函数名(形参表)”构成函数的头部,是函数调用的接口。

(4)用一对花括号括起来的内容称为函数体。函数体一般包含三个部分:

①函数体内数据对象定义或声明部分,是对在函数体内使用的数据对象(如变量、数组等)的定义,或有些数据对象在函数外已经定义,在被定义的函数中使用,需要进行声明。

②函数功能实现部分,是一个程序段,要依据实现某种功能的算法进行设计。

③函数体最后使用一个“return(返回值);”语句,括号中的返回值是一个需传递给主调函数的数据对象。如果没有返回值,可以省略这个语句。VC++6.0系统中提倡在main()函数体后使用一个“return0;”语句,表示正常返回系统,否则会出现警告。如果函数体仅是一对花括号,没有任何语句,就是空函数。空函数一般是无参函数。

下面通过实现两数相加功能来说明函数的定义:

例7.1编程实现从键盘输入三角形三条边的长度,并判定是否构成三角形。

编程思路:问题虽简单,但为了说明模块化程序设计方法,可将问题划分为三个模块,如图7.2所示,每一个模块用函数实现。输入、输出使用系统提供的库函数,只需自行定义判定函数。图7.2例7.1的模块与函数

分析:在process中,定义了3个整型形参x、y、z,表示三角形三条边的数据。根据三角形的构成条件,在if语句中判定,若能构成三角形,返回值为“1”,否则返回值为“0”。在主函数中调用process,根据返回值输出判定结果。

7.3函 数 的 调 用

怎样调用已定义函数?程序执行中,调用函数的动态过程是怎样的?一个C语言程序中只包含一个main函数,但可以包含多个其他函数。程序设计中定义的函数都是静态独立的,只有通过调用才能建立动态联系,实现函数之间的协调与配合,实现整体功能。

7.3.1函数调用方法与过程

在main函数中调用其他函数,在其他函数中还可以调用别的函数。C语言中,函数调用有多种方式,可以在程序中采用独立的调用语句,也可以在表达式中调用函数,还可以在函数参数位置调用函数。函数调用的一般形式为

函数名(实参表)

其中,函数名是被调用的函数名称;实参表与被调用函数的形参表相对应,即参数个数和类型要保持一致。实参是具体的数据,所以称为实参。通过实参可向被调函数的形参传递数据。

C语言函数调用相当于其他语言中的子程序调用,不论哪种方式的调用,都使程序的执行流从调用处转向被调函数,当被调函数执行结束时又返回到调用处继续执行。函数调用过程如图7.3所示。图7.3函数调用过程

例7.2编写一个求阶乘的函数,在主函数中输入求阶乘的数,调用函数求出阶乘值并输出。

分析:在主函数中输入10并赋给变量n,n作为实参调用factorial函数,将n的值传给形参x,此时,x具有值10,factorial函数求出10的阶乘并置于变量c中,c是返回值。返回后,将c的值赋给变量fact。

例7.3编写一个函数,求两数中的最小数。在主函数中输入两个数,调用函数求出最小数,再求最小数的平方根并输出。

编程思路:定义一个求两数中最小数的函数,在主函数中调用该函数,再调用求平方根的库函数,最后输出结果。

分析:sqrt是求平方根库函数,在程序开头用预处理命令包含头文件“math.h”。在库函数sqrt和printf的参数位置调用min函数,其实是调用后的返回值作参数。

7.3.2参数传递

从函数的调用过程可知,函数调用时,实参把数据传递给形参,形参接收实参数据,在函数中进行特定处理。C语言与其他高级语言有所不同,函数的参数传递是单向的,即只能将实参数据传递给形参,形参数据不能传递给实参。也就是说,函数调用返回后,对形参数据的引用是无效的。

函数参数传递的单向性其实质在于:在函数中定义的形参,当函数未被调用时,系统并不给它们分配存储单元;只有当调用发生时,系统才给形参分配存储单元。调用结束后,形参所占的存储单元即被释放(不存在了),形参中的数据也就无效了。

函数的参数传递还要求类型一致,即实参与形参要保持类型匹配。如整型形参只能接收整型或字符型实参数据,不能接收其他类型的实参数据,否则,将发生“类型不匹配”错误。

例7.4下面的程序是在一函数中交换两个变量的值,并输出交换后形参的值,在主函数中提供两数,调用函数后输出实参的值。

分析:exchange中实现对两形参值的交换,输出形参值交换后的结果;主函数中给变量a、b分别赋值5和10,调用exchange,交换形参a、b的值,输出结果a=10,b=5。调用返回输出实参的值a=5,b=10。函数调用参数传递及变化情况如图7.4所示。图7.4例7.4函数调用参数传递及变化情况

由图7.4可知,虽然实参和形参定义了相同的变量名,但它们是不同的变量,系统分别分配不同的存储单元。实参在编译时就分配存储单元,而且在程序执行的全过程有效。形参在调用时才分配存储单元,调用结束就不复存在了。调用时,实参把数据传递给形参,在函数中对形参进行交换,不影响实参。函数调用返回后,输出的实参变量仍是原值(单向传递)。

7.3.3函数的返回值

函数的返回值是函数调用后带回的主调函数的一个确定值,用return语句带回。

return语句有3种形式:

形式一:

return表达式;

形式二:

return(表达式);

形式三:

return;

形式一和形式二的作用完全相同,表达式的意义是结束函数的运行并带回表达式的值;形式三中无表达式,其作用是结束函数的运行且无返回值。return中表达式的值就是函数的返回值,其值的类型由函数类型决定。如果表达式值的类型与函数类型不一致,则以函数类型为准,由系统自动转换。

根据return语句的意义,可以得出结论:一个非void类型函数有且仅有一个返回值。当函数体中出现return语句时,函数的类型就不能是void类型,反之亦然。如果一个函数体中有几个返回语句,则执行第1个返回语句后就返回,其后的语句不再起作用。

7.4函数调用的条件与函数声明

一个函数满足哪些条件才能被正确调用?怎样设置调用条件?函数调用的必要条件是被调函数一定存在。函数存在的方式有多种。系统提供的库函数在库文件中。主调函数和被调函数可以在同一文件中,也可以不在同一文件中。同一文件中的函数,被调函数可以在主调函数之前,也可以在主调函数之后。主调函数与被调函数不同的存在方式,除满足“存在性”这一必要条件外,还需满足相应的调用条件。

7.4.1调用后定义的函数

在前面的例子中,我们把定义函数放在程序的前面,主函数放在后面,也就是说,函数定义在前,调用在后。所以直接按基本调用方法调用,没有提及调用函数的附加条件。如果被调函数的定义在调用之后,若直接调用,则VC系统将会出现对被调函数不能辨识的错误。如果主调函数与被调函数在一个程序文件中,即被调函数定义在后,主调函数在前,则要求在主调函数的前面对被调函数作“声明”。

函数声明的一般形式为

基类型符函数名(类型符形式参数1,类型符形式参数2,…);

还可以采用简化形式:

基类型符函数名(类型符,类型符,…);

在TurboC系统中还可以为

基类型符函数名();

函数声明是用函数定义的头部或简化形式,对函数返回值类型、函数名、参数的类型的说明,不包含函数体。

7.4.2调用库函数

C语言提供了丰富的库函数。为方便用户使用,库函数划分为不同的类。每一类库函数有一个扩展名为“.h”的头文件。头文件中包含了相关函数的声明、宏定义和数据结构的定义。在程序中要调用库函数,需要在程序开头使用预处理命令将该库函数所在类的头文件包含在程序中,否则会出现库函数不能辨识的错误。库函数的分类及其头文件可查阅附录D。例如,stdio.h、math.h、string.h、stdlib.h分别是输入/输出库函数、数学库函数、字符及字符串库函数、动态存储器分配库函数的头文件。

预处理命令的使用有两种形式:

#include<头文件名>

#include"头文件名"

这两种形式稍有区别,尖括号表示在包含文件目录中查找(包含目录是由用户在设置环境时设置的),而不在源文件目录中查找;双引号则表示首先在当前的源文件目录中查找,若未找到,再到包含目录中查找。用户编程时可根据自己文件所在的目录来选择某一种使用形式。

7.4.3调用外部函数

C语言中,根据函数能否被不同的源程序文件调用,将函数区分为内部函数和外部函数。

如果一个函数只能在同一文件中被调用,则称之为内部函数。在定义内部函数时,需要在“基类型符”前加“static”。其定义的形式为

内部函数又称为静态函数。因为内部函数只限于本文件使用,所以,在不同的程序文件中可以使用同名的内部函数,而不会产生干扰。这样在开发大型软件时,不同的人可分别编写不同的函数,而不必担心与其他程序文件中的函数同名。

外部函数可以在不同文件中被调用。定义时,可以在“基类型符”前加“extern”,表示外部函数。其定义的形式为

extern基类型符函数名(形参表)

{

}

如果不加“extern”,系统也默认其为外部函数。在此之前所用的函数都可作为外部函数。

调用外部函数时,一般要在调用函数的文件中作外部函数的声明。声明的一般形式为

extern函数名();

例7.7通过4个文件来实现:输入一个字符串,删除其中指定的一个字符,然后输出字符串。

分析:3个函数分别在3个文件中定义,虽然函数前未加“extern”说明,但系统默认为外部函数。在主函数中对3个函数进行了外部函数声明,分别调用了3个文件中的函数。

7.5函数的嵌套调用和递归调用

7.5.1函数的嵌套调用

何谓函数的嵌套调用?函数的嵌套调用有何特点?C语言允许一个函数在调用另一个函数的过程中再调用其他函数,形成多层调用关系。这种调用关系称为嵌套调用。嵌套调用可以实现多层模块结构的程序设计。

例7.8编程实现求2|x|+1的值。

编程思路:为了说明嵌套调用,求绝对值用一个函数实现,求表达式的值用另一个函数实现,通过3层嵌套调用得以求解。

分析:嵌套调用关系及其执行过程如图7.5所示。图7.5例7.8程序嵌套调用关系及其执行过程

程序的执行步骤:

(1)程序进入main函数,给变量f赋初值-1.5。

(2) main函数的printf语句中调用了func,将实际参数-1.5传递给func的形式参数r。

(3)程序进入func函数,在func里调用了fabs函数,将-1.5传递给fabs的形参x。

(4)程序进入fabs,计算x的绝对值,将1.5返回到func函数中调用fabs函数处,继续执行func函数。

(5)在func函数中计算表达式2*fabs(r)+1的值,将结果4.0返回到main函数中调用func函数处。

(6) main函数的printf函数输出func的返回值4.0。

7.5.2函数的递归调用

何谓函数的递归调用?函数的递归调用有何特点?

一个函数在它的函数体内直接或间接调用自身称为递归调用,这种函数称为递归函数或自调用函数,如图7.6所示。C语言中允许函数的递归调用。图7.6函数递归调用关系

函数递归调用特别适合递推问题的求解。利用递归算法,需对问题进行以下三个方面的分析:

(1)分析出递推公式。

(2)确定问题的边界条件或边界值,即递推的终结条件,否则问题求解将进入死圈。

(3)利用递归函数调用实现递推公式求解。

例7.9用递归方法求解n!。

编程思路:由n!=n(n-1)(n-2)…1可得递推公式n!=n*(n-1)!,终结条件是0或1的阶乘值为1。利用函数递归调用可以很容易地实现。

分析:在主函数中输入5,从fac(5)开始函数的递归调用,调用过程如图7.7所示。图7.7例7.9函数递归调用示意图

由图7.7可知,递归调用过程可分成两个阶段。第一阶段是“递推”,n的阶乘表示为n-1的阶乘,而n-1的阶乘仍未知,进而表示为n-2的阶乘,如此推到1的阶乘为1,就不必再推了。然后开始第二阶段的“反推”。由1的阶乘推算出2的阶乘,由2的阶乘推算出3的阶乘,如此反推到第一次调用,算出5的阶乘,最后返回到主程序的调用处。

从上述分析易知,递归调用程序结构简洁,但函数的调用次数、参数传递并没有节省内存资源的使用和程序的执行时间。因递归调用很容易解决递推算法问题,从而成为了C程序设计中的一种重要方法。

例7.10用递归方法求一个等差数列第n项an的值,其中首项a1的值是1,公差d

为3。

编程思路:由等差数列通项公式an=a1+(n-1)d可知,求第n项的值是一个递推问题,即an=an-1+d,终结条件是首项已知。

分析:考虑程序的通用性,getan函数的形参设置为3个,分别是首项、公差和项数。主程序中输入首项值和项数,如果公差也输入,则程序成为求等差数列任一项的通用程序。在主函数中得到实参值后调用getan函数,进入递归调用过程,共调用10次,再经过“反推”过程,回到主函数,输出结果。

7.6变量的作用域与函数间的数据传递

变量在程序中的作用范围如何界定?变量能否在函数之间传递数据?从变量值的引用特性来说,变量的作用域是变量值可引用的范围;从变量的存储特性来说,变量的作用域是变量占据所分配存储单元的周期。变量定义的方式不同,其作用域也是不同的。从函数调用时的参数传递可知,实参可单向将数据传递给形参,函数调用可返回一个值。

在了解了变量的作用域后,可知通过在变量作用域内的函数调用,全局变量可以在函数间实现值的双向传递,即增加了函数间数据传递的通道。本节实质上是进一步了解函数间的数据传递,扩大函数调用的效能。

7.6.1局部变量和全局变量

C程序中的变量,按照作用域可分为两种:局部变量和全局变量。

1.局部变量

在一个函数内部定义的变量,称为内部变量。内部变量只能在所定义的函数范围内使用(有效),在其外就不能使用了,或者说在其外就无效了,所以内部变量是局部变量。

关于局部变量的几点说明:

(1) main()函数中定义的变量也只能在主函数中使用,不能在其他函数中使用。同时,主函数中也不能使用其他函数中定义的变量。

(2)形参变量属于局部变量。函数调用结束后,形参变量就不复存在了。

(3)在不同的函数中可以使用同名变量,因为它们仅在所属的函数内有效,系统分配给不同的单元,代表不同的数据对象,互不干扰,也不会发生混淆。

(4)一个函数内部的复合语句中定义的变量,其作用范围是从定义处到复合语句尾部,局部变量作用范围如图7.8所示。图7.8局部变量作用范围示意图

2.全局变量

一个源程序文件中可以包含多个函数,在每一个函数外定义的变量统称为外部变量。外部变量是全局变量。外部变量的有效范围是从定义变量处到源程序文件结束。源文件中变量的作用范围如图7.9所示。图7.9源文件中变量的作用范围示意图

关于全局变量的几点说明:

(1)在一个程序文件中若存在局部变量和全局变量的共同有效区域,则既可使用局部变量,又可使用全局变量。定义局部变量和全局变量时应避免同名。若有同名变量,则在局部变量的作用范围内,外部变量不起作用。

例7.11分析变量的作用范围和程序的运行结果。

分析:在主函数中,a既在局部变量的作用范围,又在全局变量的作用范围,此时a用局部变量的值,b用全局变量的值,所以函数调用是max(8,5)。实参值传递给形参a、b,在max函数中形参起作用,全局变量a、b不起作用,调用返回值为8。

(2)如果想在定义点之前的函数中使用外部变量,C语言是允许的,但可在该函数中用“extern”作外部变量声明,表示该变量在函数外部定义,在本函数内部使用。声明的一般形式为

extern类型说明符变量列表;

这样就扩大了外部变量的作用范围。外部变量声明和外部变量定义的作用意义是不一样的。外部变量定义在函数之外,只能有一次。而外部变量声明是在要使用的函数之内,可以有多次声明。系统根据外部变量定义给变量分配存储单元,而不是根据外部变量声明。

例7.12分析变量的作用范围和程序的运行结果。

分析:在main函数中,x既是局部变量又是全局变量,应取局部变量的值5,y是全局变量,在main函数中有效,所以函数调用为mul(5,4)。在mul中,对外部变量z作了声明,可以引用z的值。因此函数返回值为x*y*z=5*4*5=100。

(3)由于全局变量能被一个文件中的各个函数引用,因而在一个函数中改变全局的值,在其他函数中可以引用改变后的值,相当于将一个函数中的值传递到其他函数中。所以,利用全局变量就增加了函数间数据传递的通道

利用外部变量提高函数间数据传递能力的同时,也会给程序设计带来一些负面影响:

(1)会使程序的通用性降低。因为如果将一个函数移植到另一文件中,还需要将相关的外部变量及其值一并移植。若所移植的外部变量与文件中的变量同名,则会出现问题,从而降低程序的通用性和可靠性。

(2)使用全局变量会使程序的清晰性变差。因为在分析程序时,很难把握全局变量的动态值。

(3)全局变量在程序运行的全过程中一直占据存储单元,过多使用全局变量,不利于存储器资源的有效利用。

综上所述,在程序设计中,只有非用不可时才定义全局变量,应尽量减少全局变量的数目。

例7.13从键盘输入0~4000范围内的整数,以输入负数作为结束标志,并通过一个函数实现最大值、最小值和平均值的计算。

编程思路:一个函数通过return语句只能返回一个值,现在要求计算最大值、最小值和平均值,都需要返回输出。可利用全局变量传递值。

分析:由于数据个数不限,通过负数来结束输入,因此将全局变量max、min、sum、compute函数定义为无返回值函数,通过全局变量传递最大值、最小值和累加和。在循环外输入一个数,作为max、min、sum的初值,在循环中再输入数,调用compute()进行判断及求和,循环控制变量i记录循环输入数据的个数。因为在循环外还输入了一个数,所以求平均值的除数为i+1。

7.6.2变量的存储类型

在C语言中,每个变量都有两个属性:数据类型和存储类型。数据的存储类型是指程序执行时数据在内存中的存储方式。了解变量的存储类型,能指导我们在程序设计中合理地定义数据对象,有效地利用存储器资源。

程序在执行时,系统为程序的执行划分了3个内存区域:程序区、动态存储区和静态存储区。

分配在动态存储区的数据对象,在程序执行过程中对存储单元的占用情况是动态变化的。函数调用时系统分配存储单元,返回时自动释放。可在动态存储区中存储的数据有函数的形参、自动局部变量、函数调用的现场保护和返回地址等。

分配在静态存储区的数据对象,在程序执行过程中对存储单元的占用是固定的。程序执行时分配存储单元,直到结束时才释放。静态存储区一般存储全局数据对象和被定义的静态局部变量。

从系统对存储区的划分可以看出,数据对象的存储方式有两大类:动态存储类和静态存储类。程序中定义的全局变量按静态存储方式存储,局部变量一般按动态存储方式存储。

变量对存储单元的占用期称为生存期。动态存储变量的生存期是函数调用的期限。静态存储变量的生存期是程序执行的期限。

系统对局部变量一般自动地按动态方式存储,但也允许把局部变量改变为静态存储类。为了和CPU内部寄存器相联系,局部变量可专门定义为寄存器变量。如此,局部变量又可划分为三个存储类型:自动变量、静态变量和寄存器变量。

1.自动变量

函数中的局部变量,如没有专门的定义,都存储在动态存储区中,动态分配存储空间。分配和释放存储空间的工作是由系统自动处理的。因此,这类局部变量称为自动变量。自动变量可以使用关键字“auto”来显式定义。例如:

在局部变量定义的类型符前加存储类关键字“auto”,就形成了完整的自动变量的定义。在局部变量定义的类型符前不加任何存储类关键字,系统默认为自动变量。所以上面函数中的变量x、y、a和k都是自动变量。

2.静态变量

局部变量也可以定义为静态变量,按静态方式存储。静态变量定义的一般形式为

static基本类型符变量列表;

即在局部变量的定义前增加静态存储类关键字“static”就定义了静态变量。在函数中定义的静态局部变量,函数调用结束后,所占用的存储单元不释放,仍保留变量的值。静态存储类虽然改变了局部变量的生存期,但不改变其作用域,也就是说,函数调用结束后,保留的值不能被其他函数引用,只能在下一次调用时,在本函数中使用。

例7.14计算1+2+3+…+100,并分析程序的运行结果和静态变量的使用。

编程思路:编写一个每次累加一个数的函数,在函数中循环调用,实现多数累加。可利用静态局部变量保持前一次的累加值。

分析:函数add_one中的静态变量s默认初始化为0,每次被调用时,在保留上一次的结果上累加本次实参传递的值。主函数每次调用只得到本次返回值,最终的输出get_sum是最后一次调用的返回值。输出s的值不是最后一次累加的值,而是一个系统随机数。这就说明,静态局部变量只能在所属的函数内使用,不能在其他函数中引用。

3.寄存器变量

寄存器变量是与CPU内部寄存器相关联的局部变量。CPU内部寄存器的功能是暂存操作数或中间运算结果,直接向运算器传送数据,以提高运算速度。把局部变量定义成寄存器变量即为了此目的。寄存器变量定义的一般形式是在定义变量的前面加上寄存器关键字“register”。例如,“registerinti;”就定义了i为寄存器变量。在有些情况下,如果一些变量在程序中要多次使用,将这些变量定义成寄存器变量,可缩短程序的执行时间。比如本书中多处所举的求累加和的例子,程序中的累加数变量及累加和变量在每次累加时都要使用,若将其定义成寄存器变量,则可提高程序的执行速度。

有关局部变量存储方式的几点说明如下:

(1)对局部静态变量赋初值是在编译时进行的,即只赋初值一次,程序运行中保留当前的操作结果。对自动变量赋初值不是在编译时进行的,而是在函数调用时赋初值,因为每次调用函数时,会给自动变量重新分配存储单元。

(2)如果在定义局部静态变量时不赋初值,则编译时自动赋初值0(数值型变量)或空字符(字符型变量)。如果在定义自动变量时不赋初值,则变量是一个不确定的值(所分配单元的当时值)。

(3)虽然局部静态变量在函数调用结束后仍然存在,但只能在下次调用时在本函数内使用,不能被其他函数引用。

7.7用函数实现模块化程序设计

怎样利用函数实现模块化程序设计?C语言函数用于实现模块化程序设计中的模块功能,是程序中功能相对独立的一个基本单元。模块化程序设计贯彻“自顶向下,逐步细化,逐层分解”的基本思想,最后形成一个模块结构的树形图,如图7.1所示。图中最顶层模块称为树根模块,没有下层模块的模块称为树叶模块,中间层模块称为树枝模块。

逐层分解是一个抽象到具体的思维过程。在顶层先抓住问题实质,暂时忽略细节,只注重解决问题的大过程。越到下层模块,越接近模块问题处理的方法。直到树叶模块,需要考虑问题的细节。模块分解要遵循“强内聚,弱耦合”的原则。这个原则的内涵是:模块功能应尽量具有完整性和独立性,减少对其他模块的依附性,避免模块之间传递冗余数据,尽量减少模块之间所传递数据的数量和复杂的数据结构。

设计完问题的功能模块后,就可以针对模块设计C语言函数了。设计C函数时,一般从树叶模块开始,逐层向上进行。也就是说,模块设计是自顶向下的,函数实现是自底向上的。树叶模块的函数设计要考虑解决问题的算法、数据及数据结构。树枝层和树根层模块主要考虑调用函数的接口问题,一般包含调用函数时需传递哪些数据,是什么类型的数据,调用返回需带什么数据,通过什么方式传回数据等。

在C语言程序中,模块之间的数据传递主要有两种方式:一是通过调用函数传递,传入数据采用的是实参与形参的结合方式,传出数据是函数的返回值,只能返回一个数据;二是通过全局变量来传递数据,这种传递方式是双向的,既可传入,也可传出。利用全局变量传递数据会降低程序的移植性、可靠性和易懂性,会使模块间的耦合性增强。所以,要尽可能地减少采用全局变量传递数据。

例7.15设计程序,实现用弦截法求方程的根。

(1)算法分析。

①取两个不同点x1、x2,如果f(x1)和f(x2)符号相反,则(x1、x2)区间必有一个根。如果f(x1)和f(x2)同号,则改变x1、x2,直到f(x1)和f(x2)异号。注意x1、x2的值不宜相差太大,以保证(x1、x2)区间只有一个根。

②连接f(x1)和f(x2)对应的两点,连线(即弦)交X轴于x,如图7.10所示。x点坐标可由下式求出,再从x求出f(x)。

③若f(x)与f(x1)同符号,则根必在(x,x2)区间,此时将x作为x1。如果f(x)与f(x2)同符号,则根必在(x1,x)区间,此时将x作为x2。

④重复步骤②和③,直到|f(x)|<ε,ε是一很小的数,说明|f(x)|接近于0,此时的x即为方程的近似根。图7.10弦截法求根示意图

(2)模块设计。

根据上述迭代算法,从顶层看,应包含输入初始x坐标点x1、x2模块,求根模块和根输出模块。输入和输出模块可调用库函数实现。关键是求根模块的实现。

从算法分析可知,求根是一个迭代过程。在迭代过程中求根是目的,但需求出弦交点,求弦交点又需求函数值。在迭代过程中这3个模块需反复调用。为了保证程序的可读性、通用性、可靠性,可将求根、求弦交点、求函数值划分为3个模块。模块划分如图7.11所示。图7.11弦截法求根模块

(3)程序设计。

分析:程序从main函数开始执行。先执行do_while循环,输入x1和x2,判断f(x1)和f(x2)是否异号。如果非异号,则重新输入x1和x2的值,直到满足f(x1)与f(x2)异号为止。然后调用root(x1,x2)求根x。调用root中,要调用xpoint函数来求弦的交点x。在调用xpoint函数中要调用函数f来求f(x1)和f(x2)的值。这种嵌套调用一直到弦截点x的函数值f(x)的绝对值小于10-4为止,即达到求根精度要求。在主函数中输出所求根值。

实操训练

实训任务七学习模块化程序设计的方法

实训项目1用函数模块设计运算器。运算器功能及输入/输出界面可参照图7.12所示。图7.12实训项目1界面式样

实训指导

1.设计程序

把每一个运算功能分别设计成一个函数,屏幕菜单也可设计成一个函数。在菜单函数中调用格式输出函数,依次输出菜单显示项,并提示选择运算功能,函数返回所选择的运算序号。考虑到每一种运算的输入/输出形式不同,在运算函数中,完成数据输入、运算和输出,函数设计为无参函数。在主函数中,调用菜单函数,根据功能选择调用相应函数,可用switch语句来实现。

2.调试运行程序

(1)分别选择菜单中的每一个功能选项,测试程序运行结果是否正确。

(2)在菜单中添加运算功能,如求余数、修改程序、调试运行程序、测试结果是否正

温馨提示

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

评论

0/150

提交评论