文档库 最新最全的文档下载
当前位置:文档库 › c语言程序设计教程第四章模块化程序设计

c语言程序设计教程第四章模块化程序设计

本文由瓦斯202贡献
ppt文档可能在WAP端浏览体验不佳。建议您优先选择TXT,或下载源文件到本机查看。
第4章 模块化程序设计
●函 数 ●变量的存储属性 ● 模块的编译和链接 ● 宏定义与宏替换
第4章 模块化程序设计
C语言是一种较现代的程序开发语言。它提 供如下一些支持模块化软件开发的功能: (1) C语言用函数组织程序,在C语言程序中, 一个程序由一个或多个程序文件组或,每一个程 序文件就是一个程序模块,每一个程序模块由一 个或多个函数组成。程序设计的任务就是设计一 个个函数,并且确定它们之间的调用关系。在设 计函数时要使每个函数都具有各自独立的功能和 明显的界面。 (2) 通过给变量定义不同的存储类别,控制模 块内部及外部的信息交换。 (3) 具有编译预处理功能,为程序的调试、移 植提供了方便,也支持了模块化程序设计。
4.1函 4.1函

4.1.1 设计 语言程序就是设计函数 设计C语言程序就是设计函数 无论涉及的问题是复杂还是简单,规模是大 还是小,用C语言设计程序,任务只有一种,就 是编写函数,至少也要编写一个main函数。执行C 程序也就是执行相应的main函数。即从main函数 的第一个前花括号开始,依次执行后面的语句, 直到最后的后花括号为止。 模块化程序设计有一个原则:就是每个模块 的规模一般不能太大(有人认为要控制在40~60行 之间),以便于阅读,便于检查其中的错误。在 C语言中,减少主函数规模的一项基本措施就是 通过调用其它函数来实现主函数需要的一些功能。
f1() { main(void) { ┇ f1(); ┇ f2(); ┇ return 0; } ┇ ┇ } f11();
f11() { ┇ } f21() { ┇ } f22( );┇ f22() { ┇ }
操 作 系 统
f1() { ┇ ┇ } main() 参数 返回 参数 f1() 返回 f2() 参数 f21();
参数 返回 f11()
返回 f21()
参数
返回 f22()
4.1.2 函数结构
为了定义函数,必须首先了解函数的结 构。一个C语言函数的结构形式如下: 函数头 { 函数体 }
1. 函数头函数类型 函数名(形式参数表列) 一个函数的函数头的结构如下: (1)函数类型。指定函数值的类型,即函数返 回值的类型。 (2)函数名。函数名必须采用合法的用户标识 符。 (3)圆括号:在函数名后面的一对圆括号是 “函数运算符”,表示进行函数运算,函数运算符 具有很高的运算优先级别 (4)形式参数表。形式参数表由写在一对圆括 号(函数运算符)中的一系列参数组成。每一个参数 由一个类型符和一个参数名组成。参数名也应当是 合法的用户关键字。函数可以没有参数,这时在函 数运算符内写一个“void”,也可以

空允白。
2. 函数体 函数体由一些语句组成。主要是三种类型的语句: ? 声明语句:声明在函数中要使用的变量等程序实体。 ? 用来实现函数的功能的可执行语句:包括若干流程控制 语句和表达式语句,。 ? return语句。使流程返回到调用处。 这里主要介绍return语句的用法。 当函数执行到return语句时,将停止本函数的执行, 将流程送回到调用处。同时,编译器将函数分为三类进行 处理: ? 纯粹计算型函数,如squt()和sin()。这类函数将返回一 个计算结果。 ? 完成一项具体工作,返回完成的成败。如printf()执行成 功时,返回显示的字节数;失败时,返回一个负整数。 ? 只执行一个过程的函数,不产生返回值,其类型应定义 为void。 C99规定,对于非void类型的函数,必须使用有返回 值的return语句。
int absolutevalue (int x) { return (x>=0?x:-x); } void spc (int n) { int i; for (i=0; i3. 函数中变量的作用域
作用域指的是一个程序段中的代码的 作用范围,在一个函数中定义的变量只在 本函数中有效,在其它函数中不能使用这 个变量,因此说,该变量的作用域是它所 在的函数(从定义该变量的行开始到函数末 尾)。即使在不同的函数中定义了同名的变 量,它们数也指的是不同的变量。
#include int func(int x) { x=5; return x+3; } int main(void) { printf("x=%d\n",x); return 0; } 这个程序企图在main函数中使用func函数中的 变量x。编译这个程序,系统将给出如下编译错误: c(13) : error C2065: 'x' : undeclared identifier
4. 空函数
空函数是一个不产生任何有效操作的函数,但 它却是一个合法的C函数。例如函数 void null (void){} 就是一个空函数。 空函数多使用在模块化程序的设计或测试中。 一般首先写好main函数,确定需要调用的函数, 然后逐步编写这些函数,如果在有一些函数还未 编写好时想对已有俩编好的函数进行调试,可以 先用空函数(函数名用将来使用的实际函数名,如 sort)放在程序中原定的位置上,这样就可以调试 程序的其它部分,等以后再逐步补上。
4.1.3 函数定义与函数声明
1. 函数定义 函数定义是按照C语言的语法规则引入新的函 数,并提供如下信息: ? 函数的返回值类型(如果有); ? 参数的个数及类型和名称; ? 调用函数时要执行的代码; ? 函数的有效性。
2. 函数声明
函数声明是对所用到的函数的特征进行 必要的声明。编译系统以函数声明中给出 的信息为依据,对调用表达式进行检测, 例如,形参与实参类型是否一致,使用函 数返回值的类型是否正确,以保证调用表 达式与函数之间的参数正确

传递。 声明语句提供的必要信息包括:函数名, 函数的类型,函数参数的个数、排列次序 以及每个参数的类型。函数声明的一般格 式为:
类型标识符 函数名(类型标识符形参,类型标 识符形参,…);
设有一函数的定义为: double func (double a, int b, char c) { /* 函数体*/ } 与之相应的函数声明应为: double func (double x, int y, char z); 注意末尾的分号 */
/*
4.1.4 虚实结合与传值调用
一个函数中的函数体,只有在该函数 被调用时才会执行。在函数被调用时,将 要进行如下两个操作: ? 将函数调用中的实际参数值传送给函数定 义中的形式参数; ? 将流程从调用处转到被调用的函数的开头, 开始执行函数体中的代码。
1. 函数调用时的虚实结合
参数是函数调用时进行信息交换的载体。实参是调 用函数中的变量,形参是被调函数中的变量。在函数调用 过程中实现实参与形参的结合。这称为“虚实结合”。 float add( ); int main(void) { float x=1.5, y=-5.7; printf (″%f+%f=%f\n″, x,y, add(x,y)); } float add (unsigned int a, unsigned int b) { printf(″a=%u, b=%u\n″, 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=%d\n″, a,b); \ return 0; } void swap (int x, int y) { int temp; temp=x, x=y, y=temp; printf(″x=%d,y=%d\n″,x,y); \ } 执行结果: 执行结果: x=5,y=3 a=3,b=5
/* 交换变量的值 */
4.1.5 递归函数 由前面的学习已经知道,一个函数可 以调用另一个函数。C语言还允许一个函数 自己调用自己(直接地或间接地调用自己)。 于是形成一种特殊的函数结构——递归函 数,并且,前者称为直接递归函数调用, 后者称为间接递归调用函数。
递归计算n!的函数rfact( )。 通常,n的阶乘可以描述为: n!=1·2·…·n 但是,也可以描述为: n!= n·(n-1)·…·2·1 或者可以写为: n!= n·(n-1)! 更一般的形式是:
1 n!= (x≤1)
n·(n-1)! (x>1) ( )
这就形成一个

递归表达式。这个递归表达式可以用下 面的函数实现。 long rfact(int n) { if (n<0) { printf(″Negative argument to fact !\n″); exit (-1); } else if (n<=1) return (1); else return (n*rfact (n-1)); /*自己调用自己*/ }
当n=5时rfact的其调用和回代过程。 n=5时rfact的其调用和回代过程
rfact(5) 5*rfact(4) 4*rfact(3) 3*rfact(2) 2*rfact(1) 1 120 5*24 4*6 3*2 2*1
汉诺塔(Tower 汉诺塔(Tower of Hanoi)问题。 Hanoi)问题。
据传古代印度布拉玛庙里僧侣们玩一种称为汉 诺塔的游戏,据说游戏结束就标志着世界末日的到 来。游戏的装置是一块铜板,上面有三根杆,最左 杆自下而上、由大到小顺序串有64个金盘,呈一个 塔形(图4.8)。游戏要求把左边杆上的金盘全部移到 最右边的杆上,条件是一次只能够动一个盘,并且 不允许大盘在小盘上面。容易推出,n个盘从一根 杆移到另一根杆需要2n-1次,所以64个盘的移动次 数为:264-1=18,446,744,073,709,511,615,这是 一个天文数字,即使一台功能很强的现代计算机来 解汉诺塔问题,每一微秒可能计算(不印出)一次移 动,那么也需要几乎100万年。而如果每秒移动一 次,则需近5800亿年。
a
b
c
① no1 no2 no3 ② no4 no4 ③ no1 no2 no3
下面设计一个模拟僧侣们移动盘子的算法。 假定僧侣们要把n个盘子按题中的规定由a杆借 助c杆移到b杆。模拟这一过程的算法称为 hanoi(n,a,b,c)。那么,很自然的想法是:
a
b
c
① no1 no2 no3 ② no4 no4 ③ no1 no2 no3
第一步:先把上面的n-1个盘子设法借助b杆放到c杆, 如图4.8中的箭头①所示,记做hanoi(n-1, a,c,b)。 第二步:把第n个盘子从a杆直接移到b杆,如图4.8中的 箭头②所示。 第三步:把c杆上的n-1个盘子借助a杆移到b杆,如图4.8 中的箭头③所示,记做hanoi(n-1,c,b,a)。
#include 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", &n);
hanoi (n,'a','b','c'); return 0; } void hanoi (int n, char a, char b, char c)/***汉诺塔问题 ***/ { if (n>0) { hanoi (n-1, a,c,b); printf (" Move disc %d from pile %c to %c\n", 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(2,c,b,a) { h(1,c,a,b); no2:c->b; h(1,a,b,c); }
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); }
4.2 变量的存储属性
变量是

对程序中数据存储的抽象。如前所述, C语言程序中的变量都是有类型的,数据类型是变 量的运算属性的抽象,决定了该变量的取值范围和 可以施加的运算种类。除此之外,变量还有变量一 些属性,例如: ? 一个变量在程序的哪个范围内是可以使用的——变 量的可用域; ? 它什么时候生成以及什么时候被撤消——变量的生 存期; ? 它存储在什么哪种类型的存储器中以及用什么机制 进行存储——变量的存储区。 这些都称为变量的存储属性。
4.2.1 变量的可用域和生存期
1. 局部变量和全局变量 变量的可用域是指一个变量的标识符在程序的哪个域 内才是可以被识别的,也称为可见(或可用)的。大体上 可见( 可见 可以分为两种:全局可用——全局变量 全局变量与局部可用——局 全局变量 局 部变量。 部变量 局部变量是定义在一个程序块(用一对花括号括起的 语句块)内的变量。程序块可能是一个函数体(主函数), 也可能是一个循环体或是选择结构中的一个分支语句块, 也可以是任何一个用花括号扩起的语句块。而全局变量是 定义在函数之外的变量。一般说来,定义在什么范围的变 量,其作用域就在那个范围,并且在从定义语句开始到这 个域结束的范围(域)内被使用,在这个域之外是不可见 的。 在C语言中,凡是声明在函数内部的变量都是局部变 量。
#include int main(void) { /* printf("a1=%d",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
b的作用 域
a的作用 域
2. 动态变量与静态变量
任何一个运行中的程序,在内存中都被分成代码区和 数据区两大部分,而数据区又被分为静态存储区、自动存 储区和动态分配区等三部分。 自动存储区是按栈组织的存储区。除特别声明的外, 局部变量通常被存放在栈区。这些变量在进入所在的块时 被创建,所在的块结束时被撤销,当所在的块结束后,各 变量的值不再保留。并且,变量必须有程序员显式地进行 初始化,否则它们的初始值是不确定的。前面例 存储区是在程序编译时就分配的存储区,分配在静态 存储区的变量在程序开始执行时被创建并自动初始化(数 值变量被初始化为0),当程序结束时才被撤销。所以常 称静态变量的生存期是永久的。全局变量就是被分配在静 态存储区的。下面的例子说明将例4.8中的程序改用全局变 量实现后的情形。
4.2.2 C语言中变量的存储类型 C语言中变量的存储类型
根据程序中实际应用的需要,C语言将 可用域和生存期整合成4种存储类型:

? 局部自动类型,在函数内部用标识符auto 或register声明。 ? 静态局部类型,在函数内部,使用static 声明。 ? 静态全局类型,在函数外部,使用static 声明,也称静态外部变量。 ? 全局类型,不需要标识符声明。在函数外 部直接声明即可,通称外部变量。
1. auto存储类型和register存储类型 auto声明符的作用就是告诉编译器将变量分配 在自动存储区。也就是说,使用auto声明的变量是 局部变量。其格式为: [auto] 数据类型变量名[=初值表达式],…; 其中,方括号表示可省略,auto是自动变量的存 auto, 储类别标识符。如果省略auto,系统隐含认为此变量 为auto。 auto 函数的参数也是一种自动变量。 register的作用是用来声明寄存器存储自动变量 寄存器存储自动变量。 寄存器存储自动变量 这类变量具有局部变量的所有特点。当把一个变 量指定为寄存器存储类别时,系统就将它存放在 CPU中的一个寄存器中。通常把使用频率较高的变 量(如循环次数较多的循环变量)定义为register类别。
2. 静态局部类型
定义局部变量时,使用static修饰,可 以在不改变局部量的可见域的前提下,使 变量成为静态的,即当函数撤销后,变量 的值还保留。或者说,这些变量的生存期 是永久的,只是在变量的作用域外是不可 见的。这样,避免了全局变量的值可以被 多处修改将引起的副作用,又可以使函数 基于前一次调用的值的基础上工作。
#include void increment(void); int main(void) { increment ( ); ; increment ( ); ; increment ( ); ; return 0; } void increment(void) { int x=0; /*auto*/ x++; printf (″%d\n″,x); } 运行结果: 运行结果: 1 1 1
#include void increment (void); int main(void) { increment ( ); ; increment ( ); ; increment ( ); ; return 0; } void increment (void) { static int x=0; x++; printf (″%d\n″,x); } 运行结果: 运行结果: 1? 2 3
/*使用了 使用了static说明符 */ 说明符 使用了
3. 静态全局类型
在多文件程序中,用static声明外部变 量,这样,该外部变量的作用域仅限于所 在的文件,而不用static声明的外部变量的 作用域为整个程序。例如,某个程序中要 用到大量函数,而有几个函数需要共同使 用几个全局变量时,可以将这几个函数组 织在一个文件中,而将这几个全局变量定 义为静态的,以保证它们不会与其他文件 中的变量发生名字冲突,保证文件的独立 性。
4. 外部变量的定义与声明
严格地说,定义与声明是两个不相同的概念。 声明的含义更广一些,定义的含义稍窄一些,定 义是声明的一种形式,定义具有分配存储的功能, 凡是定义都属于声明,称为定义

性声明(defining 定义性声明( 定义性声明 declaration)。另一种声明称为引用性声明 引用性声明 (referencing declaration),它仅仅是对编译系统 提供一些信息,声明并不都是定义。 前几章例题程序中定义变量的声明语句,是定 义性声明,为方便,一般简称为定义。而函数声 定义。 定义 明就是引用性声明,它和函数定义的作用是不同 的。一般把引用性声明简称为声明 声明(狭义的声明)。 声明 明。 对同一作用域中的同一变量只能定义一次,如果 对同一变量定义多次,就会出现语法错误。在定 义变量时可以用auto,static, register等关键字声明该 变量的存储类型。
4. 外部变量的定义与声明
对于外部变量(除了静态全局变量 外),除了可以定义一次外部变量外, 还可以多次进行声明(引用性声明)。 (1)通过声明将外部变量的作用域 在本文件范围内扩充——向前引用
通常,一个外部变量的作用域是从其定 义点到本文件末尾。对于位于定义点之前的 函数,可以用extern声明使变量的作用域扩 充到需要用到它的函数。 (2)利用声明语句将作用域扩大到其他 文件——建立对象的外部链接 假设一个程序由两个以上的文件组成。 当一个全局变量定义在文件file1中时,在另 外的文件中使用extern声明,可以通知编译 系统一个信息:“此变量到外部去找”。或 者说在链接时,告诉链接器:“到别的文件 中找这个变量的定义”。
(4) 全局变量的副作用 前面已提到,如果没有外部变量,函 数只能通过参数与外界发生数据联系,有了 外部变量以后,增加了一条与外界传递数据 的渠道。这种方法有利有弊。外部变量作为 公共信息的一种载体,虽然给程序设计带来 一些方便,但也会产生一些副作用
4.2.3 用const声明将变量存储在只读区 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 && #define OR || #define NOT ! … void func(void) BEGIN if((a AND b)>6) … else if((c OR d)>3) … else if(NOT(e==9)) … END 如果没有前面的宏定义,后面的程序是不符合C语言语法的。只 有经过编译预处理,程序部分符合C语言的语法。
4.4.2 使用宏需要注意的问题
(1)在#define命令中,宏名字与字符串(宏 体)之间用一个或多个空格分隔。 (2)宏名字的使用: 宏名字不能用引号括起来。如: ? #define ″YES″ 1 ? … ? printf(““YES””); ? 将不进行宏定义。 宏名中不能含有空格。例如想用“A NAME” 定义“SMISS”,而写成:
#define A NAME SMISS 则实际进行的宏定义是A为宏名字,宏体是 “NAME SMISS”。 ? C程序员一般都习惯用大写字母定义宏名字。这 样的表示方法使宏名与变量名有明显的区别,以 避免混淆。此外有助于快速识别要发生宏替换的 位置,提高程序的可读性。 ? 不能进行宏名字的重定义。 (3)定义一个宏名字以后,就可以在后面使用这个 宏名字了,包括在后面的宏定义中使用。例如求 圆的周长和面积的程序可以改写为 #include
#define PI 3.1415926 #define R 1.0 #define CIRCUM 2.0*PI*R /* 使用了前面定义的R和PI */ #define AREAPI*R*R int main(void) { printf(“The circum is %f and area is %f\n”,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 %f\n",circum(),area()); } (4)不能进行的宏

替换: ? 不可以替换作为用户标识符中的成分。例如,在例4.23中, 不可以用“R”替换“CIRCUM”中的“R”。 ? 不能替换字符串常量中的成分。
#include #define PI 3.1415926 #define R 1.0 #define CIRCUM2.0*PI*R #define AREA PI*R*R int main(void) { printf(“The CIRCUM is %f and AREA is %f\n”,CIRCUM,AREA); } 运行结果为: The CIRCUM is 6.283185 and AREA is 3.141593 不会用宏名字“CIRCUM”和“AREA”替换格式串“CIRCUM”和 “AREA”。 (5)一行中写不下的宏定义,应在前一行结尾使用一个续行符“\”,并 且在下一行开始不使用空格。例如 #define AIPHABET ABCDEFGHHIJKLMN\ OPQRSTUVWXY (6) 宏定义可以写在源程序中的任何地方,但一定要写在程序中引用该宏 之前,通常写在一个文件之首。对多个文件可以共用的宏定义,可以 集中起来构成一个单独的头文件。
4.4.3 撤销已定义的宏
用命令#undef可以撤销已定义的宏。如: … #define OK 1 … #undef OK … 在#undef命令行之后的范围,OK不再代表1。
4.4.4 带参数的宏定义
带参宏定义的格式为: #define 标识符(形参表) 宏体 应当注意,这时的宏体是一个表达式,正确的 书写宏体的方法是将宏体及其各个形参应该用圆 括号括起来。 对于求平方的宏定义,可以写出4种形式: #define SQUARE(x) x*x /* (a) */ #define SQUARE(x)(x*x) /* (b) */ #define SQUARE(x) (x)*(x) /* (c) */ #define SQUARE(x) ((x)*(x)) /* (d) */ 到底哪个对呢?下面用几个表达式进行测试:
(1)用表达式a=SQUARE(n+1)测试, 按(a),将替换为a=n+1*n+1。显然结果不对。 按(b),将替换为a=(n+1*n+1),结果与按(a)相同。 按(c),将替换为a=(n+1)*(n+1),结果对。 按(d),将替换为a=((n+1)*(n+1)),结果对。 (2)用表达式a=16/SQUARE(2)测试 对剩下的(c)和(d)进行测试。 按 (c) ,将替换为a=16/(2)*(2)=32,显然结果不对。 按 (d) ,将替换为a=16/((2)*(2))=4,结果对。 所以,还是把宏体及其各形参都用圆括号括起来稳妥。 宏与函数都可以作为程序模块应用于模块化程序设计之中,但它 们各有特色。 (1) 时空效率不相同。宏定义时要用宏体去替换宏名,往往使程 序体积膨胀,加大了系统的存储开销。但是它不像函数调用要进行参 数传递、保存现场、返回等操作,所以时间效率比函数高。所以,通 常对简短的表达式,以及调用频繁、要求快速响应的场合(如实时系 统中),采用宏比采用函数合适。 (2) 宏虽然可以带有参数,但宏定义过程中不像函数那样要进行 参数值的计算、传递及结果返回等操作;宏定义只是简单的字符替换, 不进行计算。因而一些过程是不能用宏代替函数的,如递归调用。同 时,也可能不适合某

些计算,产生函数调用所没有的副作用。

1

相关文档
相关文档 最新文档