模块化程序设计11.ppt_第1页
模块化程序设计11.ppt_第2页
模块化程序设计11.ppt_第3页
模块化程序设计11.ppt_第4页
模块化程序设计11.ppt_第5页
已阅读5页,还剩57页未读 继续免费阅读

下载本文档

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

文档简介

第4章 模块化程序设计 函 数 变量的存储属性 模块的编译和链接 宏定义与宏替换 第4章 模块化程序设计 C语言是一种较现代的程序开发语言。它提 供如下一些支持模块化软件开发的功能: (1) C语言用函数函数组织程序,在C语言程序中 ,一个程序由一个或多个程序文件组或,每一个 程序文件就是一个程序模块,每一个程序模块由 一个或多个函数组成。程序设计的任务就是设计 一个个函数,并且确定它们之间的调用关系。在 设计函数时要使每个函数都具有各自独立的功能 和明显的界面。 (2) 通过给变量定义不同的存储类别,控制模 块内部及外部的信息交换。 (3) 具有编译预处理功能,为程序的调试、移 植提供了方便,也支持了模块化程序设计。 4.1函 数 4.1.1 设计C语言程序就是设计函数 无论涉及的问题是复杂还是简单,规模是大 还是小,用C语言设计程序,任务只有一种,就 是编写函数,至少也要编写一个main函数。执行 C程序也就是执行相应的main函数。即从main函 数的第一个前花括号开始,依次执行后面的语句 ,直到最后的后花括号为止。 模块化程序设计有一个原则原则:就是每个模块 的规模一般不能太大(有人认为要控制在4060 行之间),以便于阅读,便于检查其中的错误。 在C语言中,减少主函数规模的一项基本措施就 是通过调用其它函数来实现主函数需要的一些功 能。 main(void ) f1(); f2(); return 0; 操 作 系 统 f1() f11() ; f1() f21() ; f22( ); f11() f21() f22() main() f1()f2() f22()f21()f11() 参数 参数参数 参数 参数返回 返回 返回 返回 返回 4.1.2 函数结构 为了定义函数,必须首先了解函数的结 构。一个C语言函数的结构形式如下: 函数头 函数体 1.函数头 函数类型 函数名(形式参数表列) 一个函数的函数头的结构如下: (1)函数类型。指定函数值的类型,即函数返 回值的类型。 (2)函数名。函数名必须采用合法的用户标识 符。 (3)圆括号:在函数名后面的一对圆括号是“ 函数运算符”,表示进行函数运算,函数运算符具 有很高的运算优先级别 (4)形式参数表。形式参数表由写在一对圆括 号(函数运算符)中的一系列参数组成。每一个参数 由一个类型符和一个参数名组成。参数名也应当 是合法的用户关键字。函数可以没有参数,这时 在函数运算符内写一个“void”,也可以空允白。 2. 函数体 函数体由一些语句组成。主要是三种类型的语句: 声明语句:声明在函数中要使用的变量等程序实体。 可执行语句:包括若干流程控制 语句和表达式语句, 。 return语句:使流程返回到调用处。 这里主要介绍return语句的用法。 当函数执行到return语句时,将停止本函数的执行, 将流程送回到调用处。同时,编译器将函数分为三类进 行处理: 纯粹计算型函数,如sqrt()和sin()。这类函数将返回一 个计算结果。 完成一项具体工作,返回完成的成败。如printf()执行 成功时,返回显示的字节数;失败时,返回一个负整数 。 只执行一个过程的函数,不产生返回值,其类型应定义 为void。 C99规定,对于非void类型的函数,必须使用有返回 值的return语句。 int absolutevalue (int x) return (x=0?x:-x); voidvoid spc (int n) int i; for (i=0; i int func(int x) x=5; return x+3; int main(void) printf(“x=%dn“,x); return 0; 这个程序企图在main函数中使用func函数中的 变量x。编译这个程序,系统将给出如下编译错误 : c(13) : error C2065: x : undeclared identifier 4. 空函数 空函数是一个不产生任何有效操作的函数,但 它却是一个合法的C函数。例如函数 void null (void) 就是一个空函数。 空函数多使用在模块化程序的设计或测试中。 一般首先写好main函数,确定需要调用的函数, 然后逐步编写这些函数,如果在有一些函数还未 编写好时想对已有俩编好的函数进行调试,可以 先用空函数(函数名用将来使用的实际函数名, 如sort)放在程序中原定的位置上,这样就可以 调试程序的其它部分,等以后再逐步补上。 4.1.3 函数定义与函数声明 1. 1. 函数定义函数定义 函数定义是按照C语言的语法规则引 入新的函数,并提供如下信息: 函数的返回值类型(如果有); 参数的个数及类型和名称; 调用函数时要执行的代码; 函数的有效性。 2. 函数声明 函数声明函数声明是对所用到的函数的特征进行 必要的声明。编译系统以函数声明中给出的 信息为依据,对调用表达式进行检测,例如 ,形参与实参类型是否一致,使用函数返回 值的类型是否正确,以保证调用表达式与函 数之间的参数正确传递。 声明语句提供的必要信息包括:函数名 ,函数的类型,函数参数的个数、排列次序 以及每个参数的类型。函数声明的一般格式 为: 类型标识符 函数名(类型标识符形参,类型标 识符形参,); 设有一函数的定义为: double func (double a, int b, char c) /* 函数体*/ 与之相应的函数声明应为: double func (double x, int y, char z); /* 注意末尾的分号 */ 1、形参名字不重要,重要 的是类型,不能不写。 2、形参的次序也不能错。 3、不能不写函数的类型。 4、只有函数的返回值是 int(char)时,函数的类型标 识符才可以省略。 4.1.4 虚实结合与传值调用 一个函数中的函数体,只有在该函数 被调用时才会执行。在函数被调用时,将 要进行如下两个操作: 将函数调用中的实际参数值传送给函数 定义中的形式参数; 将流程从调用处转到被调用的函数的开 头,开始执行函数体中的代码。 1. 函数调用时的虚实结合 参数是函数调用时进行信息交换的载体。 实参是调用函数中的变量,形参是被调函数中的变量。 在函数调用过程中实现实参与形参的结合。这称为“虚实结 合”。 float add( ); int main(void) float x=1.5, y=-5.7; printf (%f+%f=%fn, x,y, add(x,y); float add (unsigned int a, unsigned int b) printf(a=%u, b=%un, a,b); return (a+b); 2. 传值调用的虚实结合过程 程序进行编译时,并不为形式参数分配存储 空间。只有在被调用时,形式参数才临时地占有 存储空间,其过程如下: (1)调用开始,系统为形参开辟一个临时存 储区,形参与实参各占一个独立的存储空间。 (2)然后将各实参之值传递给形参,这时形 参就得到了实参的值。这种虚实结合方式称为“ 值结合”。 (3)函数返回时,临时存储区也被撤销。 要特别注意在C程序中实参与形参结合的传值 调用(call by value) 的特点。即函数中对形参 变量的操作不会影响到调用函数中的实参变量, 即形参值不能传回给实参。 #include void swap (int x, int y); int main(void) int a=3, b=5; swap (a,b); printf (a=%d, b=%dn, a,b); return 0; void swap (int x, int y) int temp; temp=x, x=y, y=temp; /* 交换变量的值 */ printf(x=%d,y=%dn,x,y); 执行结果: x=5,y=3 a=3,b=5 开辟存储空间 ab xy 4.1.5 递归函数 由前面的学习已经知道,一个函数可以 调用另一个函数。 C语言还允许一个函数自己调用自己( 直接地或间接地调用自己)。于是形成一 种特殊的函数结构递归函数递归函数,并且, 前者称为直接递归函数调用,后者称为间 接递归调用函数。 递归计算n!的函数rfact( )。 通常,n的阶乘可以描述为: n!=12n 但是,也可以描述为: n!= n(n-1)21 或者可以写为: n!= n(n-1)! 更一般的形式是: 1 (x1) n!= n(n-1)! (x1) 这就形成一个递归表达式。这个递归表达式可 以用下面的函数实现。 long rfact(int n) if (n void hanoi(int n, char a, char b ,char c); int main(void) int n; printf (“*n“); printf (“*Program for simulating the solution*n“); printf (“* of the game of tower of Hanoi *n“); printf (“*n“); printf (“Please enter the number of disks to be moved:“); scanf (“%d“, hanoi (n,a,b,c); return 0; void hanoi (int n, char a, char b, char c)/*汉诺塔问 题*/ if (n0) hanoi (n-1, a,c,b); printf (“ Move disc %d from pile %c to %cn“, n,a,b); (n-1, c,b,a); h(3,a,b,c) h(2,a,c,b); no3:a-b; h(2,c,b,a); h(2,a,b,c) h(1,a,b,c); no2:a-c; h(1,b,c,a); h(1,a,b,c) h(0,a,c,b); no0:a-b; h(0,c,b,a); h(1,b,c,a) h(0,b,a,c); no1:b-c; h(0,a,c,b); h(1,c,a,b) h(0,c,b,a); no1:c-a; h(0,b,a,c); h(1,a,b,c) h(0,a,c,b); no1:a-b; h(0,c,b,a); h(2,c,b,a) h(1,c,a,b); no2:c-b; h(1,a,b,c); 4.2 变量的存储属性 变量是对程序中数据存储的抽象。 如前所述,C语言程序中的变量都是有类型的,数据 类型是变量的运算属性的抽象,决定了该变量的取值范围 和可以施加的运算种类。除此之外,变量还有变量一些属 性,例如: 变量的可用域 一个变量在程序的哪个范围内是可以使用的; 变量的生存期 它什么时候生成以及什么时候被撤消; 变量的存储区 它存储在什么哪种类型的存储器中以及用什么机 制进行存储。 这些都称为变量的存储属性。 4.2.1 变量的可用域和生存期 1. 局部变量和全局变量局部变量和全局变量 变量的可用域是指一个变量的标识符在程序的哪个域 内才是可以被识别的,也称为可见(或可用)的。大体 上可以分为两种:全局可用全局变量与局部可用 局部变量。 局部变量是定义在一个程序块(用一对花括号括起的 语句块)内的变量。程序块可能是一个函数体(主函数 ),也可能是一个循环体或是选择结构中的一个分支语 句块,也可以是任何一个用花括号扩起的语句块。而全 局变量是定义在函数之外的变量。一般说来,定义在什 么范围的变量,其作用域就在那个范围,并且在从定义 语句开始到这个域结束的范围(域)内被使用,在这个 域之外是不可见的。 在C语言中,凡是声明在函数在函数内部的变量内部的变量都是局部变都是局部变 量量, ,在函数在函数外部的变量外部的变量都是外部变量。都是外部变量。 #include int main(void) /* printf(“a1=%d“,a); */ b的作用域 a的作用域 int a=3; printf(“a2=%d“,a); int b=5; printf(“b1=%d“,b); /* printf(“b2=%d“,b); */ printf(“a3=%d“,a); return 0; 则运行可以得到如下结果: a2=3 b1=5 a3=3 2. 动态变量与静态变量 任何一个运行中的程序,在内存中都被分成代码区和 数据区两大部分,而数据区又被分为静态存储区、自静态存储区、自 动存储区和动态分配区动存储区和动态分配区等三部分。 自动存储区是按栈组织按栈组织的存储区。除特别声明的外, 局部变量通常被存放在栈区。这些变量在进入所在的块 时被创建,所在的块结束时被撤销,当所在的块结束后 ,各变量的值不再保留。并且,变量必须有程序员显式 地进行初始化,否则它们的初始值是不确定的。 存储区是在程序编译时就分配的存储区,分配在静态 存储区的变量在程序开始执行时被创建并自动初始化( 数值变量被初始化为0),当程序结束时才被撤销。所以 常称静态变量的生存期是永久的静态变量的生存期是永久的。全局变量就是被分配 在静态存储区的。下面的例子说明将例4.8中的程序改用 全局变量实现后的情形。 4.2.2 C语言中变量的存储类型 根据程序中实际应用的需要,C语言将可用域 和生存期整合成4种存储类型: 局部自动类型局部自动类型,在函数内部用标识符auto或 register声明。 静态局部类型静态局部类型,在函数内部,使用static声明。 静态全局类型静态全局类型,在函数外部,使用static声明, 也称静态外部变量。 全局类型全局类型,不需要标识符声明。在函数外部直 接声明即可,通称外部变量。 auto存储类型和register存储类型 auto声明符的作用就是告诉编译器将变量分配 在自动存储区。也就是说,使用auto声明的变量是 局部变量。其格式为: auto 数据类型变量名=初值表达式,; 其中,方括号表示可省略,auto是自动变量的存 储类别标识符。如果省略auto,系统隐含认为此变量 为auto。 函数的参数也是一种自动变量。 register的作用是用来声明寄存器存储自动变量 。这类变量具有局部变量的所有特点。当把一个 变量指定为寄存器存储类别时,系统就将它存放 在CPU中的一个寄存器中。通常把使用频率较高的 变量(如循环次数较多的循环变量)定义为register类 别。 2. 静态局部类型 定义局部变量时,使用staticstatic修饰, 可以在不改变局部量的可见域的前提下, 使变量成为静态的,即当函数撤销后,变 量的值还保留。或者说,这些变量的生存 期是永久的,只是在变量的作用域外是不 可见的。这样,避免了全局变量的值可以 被多处修改将引起的副作用,又可以使函 数基于前一次调用的值的基础上工作。 #include void increment(void); int main(void) increment ( ); increment ( ); increment ( ); return 0; void increment(void) int x=0; /*auto*/ x+; printf (%dn,x); 运行结果: 1 1 1 #include void increment (void); int main(void) increment ( ); increment ( ); increment ( ); return 0; void increment (void) static int x=0; /*使用了static说明符 */ x+; printf (%dn,x); 运行结果: 1 2 3 3. 静态全局类型 在多文件程序中,用用staticstatic声明外部变声明外部变 量量,这样,该外部变量的作用域仅限于所 在的文件,而不用static声明的外部变量的 作用域为整个程序。 例如,某个程序中要用到大量函数, 而 有几个函数需要共同使用几个全局变量时可 以将这几个函数组织在一个文件中,而将这 几个全局变量定义为静态的,以保证它们不 会与其他文件中的变量发生名字冲突,保证 文件的独立性。 4. 外部变量的定义与声明 严格地说,定义与声明是两个不相同的概念定义与声明是两个不相同的概念。声明的 含义更广一些,定义的含义稍窄一些,定义是声明的一 种形式,定义具有分配存储的功能,凡是定义都属于声 明,称为定义性声明定义性声明(defining declaration)。另一种声 明称为引用性声明引用性声明(referencing declaration),它仅仅是 对编译系统提供一些信息,声明并不都是定义。 前几章例题程序中定义变量的声明语句,是定义性声 明,为方便,一般简称为定义。而函数声明就是引用性 声明,它和函数定义的作用是不同的。一般把引用性声 明简称为声明(狭义的声明)。对同一作用域中的同一变 量只能定义一次,如果对同一变量定义多次,就会出现 语法错误。在定义变量时可以用auto,static, register等关键 字声明该变量的存储类型。 4. 外部变量的定义与声明 对于外部变量(除了静态全局变量外 ),除了可以定义一次外部变量外,还 可以多次进行声明(引用性声明)。 (1)通过声明将外部变量的作用域 在本文件范围内扩充向前引用 通常,一个外部变量的作用域是从其定义点到 本文件末尾。对于位于定义点之前的函数,可以 用externextern声明使变量的作用域扩充到需要用到它 的函数。 (2)利用声明语句将作用域扩大到其他文件 建立对象的外部链接 假设一个程序由两个以上的文件组成。当一个 全局变量定义在文件file1中时,在另外的文件中 使用extern声明,可以通知编译系统一个信息:“ 此变量到外部去找”。或者说在链接时,告诉链 接器:“到别的文件中找这个变量的定义”。 (4) 全局变量的副作用 前面已提到,如果没有外部变量,函 数只能通过参数与外界发生数据联系,有 了外部变量以后,增加了一条与外界传递 数据的渠道。这种方法有利有弊。外部变 量作为公共信息的一种载体,虽然给程序 设计带来一些方便,但也会产生一些副作 用。 4.2.3 用const声明将变量存储在只读区 C语言除了可以为程序开辟栈区、堆区、 静态区外,还可以开辟一个只读区。既然只 读区是内存的一个区,所以数据是以变量的 形式存储的,但它又是只读的,即不可修改 的。所以这也就是把变量定义为只读变量。 定义只读变量的方法是在声明变量时使用修 饰符const。格式为: const 数据类型 变量1=初始表达式1,变量2=初始表达式2, 4.3 模块的编译和链接 4.3.1 分别编译 C语言是一种支持模块化程序设计的语言, 它允许将一个大型程序分成多个程序文件分别进 行编译。这样的好处在于 当程序的一个局部有错误或对局部进行了修改 时,可以只重新编译该局部,不需要将整个程序 都重新编译。 某些经过考验的函数的目标代码可以添加为的 库函数,供其他程序中使用。 4.3.2 用项目管理多文件程序的编译与链 接过程 多文件程序的编译、链接过程是比较 麻烦的。为了方便用户进行多文件程序的 编译、链接,各种程序开发软件都提供了 用项目(project)管理多文件程序的编 译和链接过程。其使用方法因开发工具而 异,请参考有关手册。 4.3.3 头文件 用户显式地保证程序一致性的基本方法是保持声明的 一致性。保持声明一致性的简单办法是提供一个头文件 ,让头文件中的信息作为各模块之间的接口信息,有利 于提供可重用的模块。使用头文件将把程序分为程序头 和程序体两部分。 好的头文件应包含如下一些内容: 类型定义类型定义,如定义一个枚举类型enum color RED,BLUE,GREEN,YELLOW; 函数声明函数声明,如extern int strlen const char*; 嵌入函数声明嵌入函数声明,如inline char get() return *p+; 数据声明数据声明,如extern int a; 常量定义常量定义,如const float pi=3.141593; 包含指令包含指令,如#include 4.3.3 头文件 宏定义,如#define case break; case 注释 好的头文件不能包含以下内容: 一般函数定义 数据定义,如int a; 常量聚集定义,如 const tbl=/* */; 用户头文件是由用户自己定义的头文件。系统将用双 引号方式进行搜索。程序员可以用它们建立各文件之间 的信息窗口。 应当注意,修改了头文件时,所有涉及该头文件的程 序都应重新编译,所以在编制头文件时应尽量考虑周全 ;另外,当使用很多头文件时,常常有重复嵌入的情形 发生,在这种情形下,应防止标识符重复定义的错误。 为避免这些错误,应使用条件编译来测试有无重复定义 。 4.4 宏定义与宏替换 编译预处理是以“#”开头的一些命令,它通知编译 系统:在进行正式编译前应当先进行一些前期工作(即编 译预处理)。目前,C语言的编译预处理功能主要有如下 一些: (1)文件包含,使用命令:#include。 (2)宏定义,使用命令:#define、#undef。 (3)条件编译,使用命令:#if、#ifdef、#ifndef 、#else、#endif、#elif、defind。 (4)提供行号信息,使用命令:#line。 (5)编译杂注,使用命令:#progma。 (6)错误信息,使用命令:#error。 在这些编译预处理中,应用最多的就是对宏的处理 。它允许程序员使用define定义并应用宏名字书写C语 言程序,同时也指示编译器在正式编译前通过宏替换, 使程序能按照C语言的语法进行编译。 4.4.1 字符串宏定义及其基本格式 define命令最简单的应用是将一个字符串定 义为一个宏名,格式为: define 宏名 字符串 编译预处理时,预处理程序将把程序文件中 在该宏定义之后的所有宏名,用#define命令中定 义的字符串(称为宏体)替换,这个过程称为宏替 换。符号常数的定义就是这种宏定义的一种应用 。例如 #define PI 3.14159265 #define RADIUS 2.0 使用宏定义的好处是: (1) 提高了程序的可读性提高了程序的可读性。如果人们看到以下语句 return (2.0* 3.1415926536* 2.0); 和 return (3.1415926536* 2.0* 2.0); 是否能很快地看出它们在计算什么呢?显然不能。如果 写成下面的形式 return (2.0* PI* RADIUS); 和 return (PI *RADIUS* RADIUS); 任何人一眼都可以看出:这是在计算圆周长和圆面积 。使用宏定义可以提高可理解性。 (2) 易修改性好易修改性好。如要将RADIUS的值由2.0修改为3.0,只 要在define命令中修改一处便可。而在不使用宏定义的 文件中,要将3处的2.0修改为3.0,而且很可能也错把 circum函数中的第一个返回语句也修改成 return (3.0*3.1415926536*3.0); (3)宏定义可以给程序设计带来许多方便宏定义可以给程序设计带来许多方便。如熟悉 PASCAL的读者,完全可以用自己已习惯的风格来写程序。 #define BEGIN #define END #define AND 将不进行宏定义。 (3 3)宏名中不能含有空格。)宏名中不能含有空格。例如想用“A NAME”定义“SMISS”,而写成: #define A NAME SMISS 则实际进行的宏定义是A为宏名字,宏体是 “NAME SMISS”。 C程序员一般都习惯用大写字母定义宏名字大写字母定义宏名字。 这样的表示方法使宏名与变量名有明显的区别, 以避免混淆。此外有助于快速识别要发生宏替换 的位置,提高程序的可读性。 不能进行宏名字的重定义。 (4 4)定义一个宏名字以后,就可以在后面使用这)定义一个宏名字以后,就可以在后面使用这 个宏名字了,包括在后面的宏定义中使用。个宏名字了,包括在后面的宏定义中使用。例如 求圆的周长和面积的程序可以改写为 #include #define PI3.1415926 #define R1.0 #define CIRCUM2.0*PI*R /* 使用了前面定义的R和PI */ #define AREAPI*R*R int main(void) printf(“The circum is %f and area is %fn”,CIRCUM,AREA); 这种宏定义称为嵌套宏定义嵌套宏定义。运行这个程序的结果为: The circum is 6.283185 and area is 3.141593 下面是另外一种形式的宏定义: #include #define PI 3.14159265 #define RADIUS 1.0 #define CIRCUM return(2.0*PI* RADIUS); /* 最后的分号是 return语句的一部分 */ #define AREA return(PI* RADIUS* RADIUS); double circum( ) CIRCUM double area( ) AREA int main(void) printf(“The circum is %f and area is %fn“,circum(),area(); (5 5)不能进行的宏替换:)不能进行的宏替换: 不可以替换作为用户标识符中的成分。例如,在例4.23 中,不可以用“R”替换“CIRCUM”中的“R”。 不能替换字符串常量中的成分。 #include #define PI3.1415926 #define R1.0 #define CIRCUM2.0*PI*R #define AREAPI*R*R int main(void) printf(“The CIRCUM is %f and AREA is %fn”,CIRCUM,AREA); 运行结果为: The CIRCUM is 6.283185 and AREA is 3.141593 不会用宏名字“CIRCUM”和“AREA”替换格式串“CIRCUM”和“AREA”。 ( 6 6 )一行中写不下的宏定义,应在前一行结尾使用一个续行符)一行中写不下的宏定义,应在前一行结尾使用一个续行符“ “”,并,并 且在下一行开始不使用空格。且在下一行开始不使用空格。例如 #define AIPHABET ABCDEFGHHIJKLMN OPQRSTUVWXY (7) (7) 宏定义可以写在源程序中的任何地方,但一定要写在程序中引用该宏定义可以写在源程序中的任何地方,但一定

温馨提示

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

评论

0/150

提交评论