1
C语言程序设计
1.8.8.1 7.8.1 宏替换——#define

7.8.1 宏替换——#define

在前面章节的C语言程序中,我们已使用过#define命令控制行定义过一些符号常量。用#define作为标志的预处理命令不仅可以定义符号常量及字符串,而且也可以定义带参数的宏。

1.简单的字符串替换

宏替换命令#define用来定义一个宏标识符和一个字符串,在程序中每次遇到该标识符时,就用所定义的字符串替换。这个标识符也叫宏替换名,替换过程称为宏替换,宏替换命令的一般形式是:

  #define 宏标识符 字符串

其中,宏标识符通常用大写字母表示,以便与程序中其他变量名相区别。明显地标示出程序中需要宏替换的地方,读程序时更加醒目。#define、宏标识符、字符串各部分之间用空格分隔,其末尾不带分号,以换行结束。每行只能定义一个预处理行。

例如在C5-8.C语言程序的开头,有如下宏替换命令:

  #define N 10

在对该程序进行预处理时,凡程序中以N作为学生人数的地方都用10替换。

由于程序中多处用到N作为二维数组和一维数组的下标变量,而且N也作为for循环语句控制变量的终止值,因此在程序调试过程中,将N定义为10,大大减少了数据的输入量,便于程序的查错修改和验证,节省了调试程序的时间。当程序调试完成后,将学生人数从10改为实际的人数,或由于其他原因需要更改学生人数时,都只需要修改这条宏替换命令,再重新编译一次就可以了。这样对于修改、扩充、阅读和移植程序都是十分方便的。

又如,在有布尔量的高级语言中,通常用“TRUE”和“FALSE”表示逻辑表达式运算的结果。在C语言中没有布尔量,所以用非零表示“TRUE”,用零表示“FALSE”。为了使这些代码的确切含义更为清楚,有利于阅读程序,通常可以使用两个宏替换#define进行定义:

  #define TRUE 1

  #define FALSE 0

在符号常量定义中,常使用下面一些定义:

  #define ON 1

  #define OFF 0

  #define YES 1

  #define NO 0

在数值计算中,如果要多次使用某些具有一定精度的float和double类型的数值,为了便于程序中使用,可进行如下定义:

  #define PI 3.14159

  #define E 2.7183

  #define INC 200.0

在图形显示系统中,如果想用数值表示显示器使用的颜色,可进行如下定义:

  #define BLACK 0

  #define BLUE 1

  #define GREEN 2

  #define CYAN 3

  #define RED 4

  #define MAGENTA 5

  #define BROWN 6

  #define WHITE 7

这样,在编译时,预处理程序每当在源程序中遇到颜色标识符时就自动用对应数值替换。由于宏标识符比对应的数值更容易记忆,因此不仅方便了编程,也方便了程序的阅读。

为了进一步说明宏替换仅仅是简单地用所定义的字符串来替换对应的宏标识符的过程。下面的例子定义了一个标准出错信息并输出:

img601

预处理程序在遇到标识符E-ms时,用定义的字符串来替换。对编译程序来说,printf()语句的实际形式是:

printf("standard error On input\n");

在长字符串作宏替换时,如果字符串的长度大于一行,可以用一个反斜杠符号写在每行的行尾。

又如,对于熟悉PASCAL或ALGOL语言的用户来说,我们可用

  #define BEGIN {

  #define END }

进行定义。我们可以将一个C语言程序用类似于PASCAL或ALGOL语言程序的形式进行编写。

img602

利用宏替换增加程序的可移植性,这是一个很重要的方法。假定一个图形程序,在进行图形显示时,需要按指定工作模式确定显示坐标的取值范围。如果图形工作模式的分辨率为640×200,则坐标的取值范围是:

  0≤X≤639

  0≤Y≤199

由于图形适配器可能有不同的工作模式,因此当改变工作模式时,就要改变程序中X 和Y坐标的取值范围,程序的可移植性受到影响。如果采用宏替换方式定义显示坐标的取值范围,当运行环境的工作模式改变时,只需要改变宏替换中的替换值,重新编译程序,而对程序内部的语句不必改动。

例如,在640×200的图显工作模式下,作如下宏定义:

  #define XMAX 639

  #define YMAX 199

  #define XM IN 0

  #define YM IN 0

当改为640×350的图显工作模式时,则宏定义可更改成:

  #define XMAX 639

  #define YMAX 349

  #define XM IN 0

  #define YM IN 0

这就增强了图形程序对硬件环境的适应性,有利于程序的移植。

还要说明一点,宏定义允许嵌套,即可以用已经定义的名字来定义后面的名字,例如:

  #define PI 3.14159

  #define TWOPI(2*PI)

  #define ONE 1

  #define TWO ONE+ONE

  #define THREE ONE+TWO

2.带参数宏定义及宏调用

前面介绍的字符串是宏定义和宏替换的简单应用。宏替换命令#define还有一个重要的特点,即宏标识符像函数一样可以带有形式参数。在程序中用到宏标识符时,实际参数将代替这些形式参数,使用更为灵活。带参数宏定义的一般形式为:

  #define宏标识符(参数表) 表达式

宏标识符就是带参数宏的名字,参数表中的参数类似于函数中的形式参数,表达式是用于替换的表达式,宏标识符与左圆括号之间不能有空格。

宏调用的一般形式是:

  宏标识符(参数表)

此处的宏标识符是已被定义的宏标识符,参数表中的参数类似于函数中的实参数,例如,要求两个数中较大数,可以定义宏:

  #define MAX(a,b)(a〉b)?a:b

这个定义类似于函数定义,其中a、b是形式参数。对带参数宏的调用类似于函数调用。下面程序是使用宏定义MAX的例子。

例7-11 使用带参数宏定义的简单程序。

程序如下:

img603

运行结果:

the maximum data is 20

在编译时,预处理程序先扫视源程序,当发现标识符MAX并且后跟一对带有参数的圆括号时,就用表达式替换该名字,同时用实参x、y替换参数a、b。printf()语句被代换为如下形式:

  printf("the maximum data is %d",(x〉y)?x:y);

程序运行结果说明10和20中,20是较大数。

如果要求一个数的绝对值可作如下的宏定义:

  #define ABS(x)(((x)〉0)?(x):−(x))

它表示宏ABS对参数x取绝对值作为宏运算的值,当x〉0时,宏运算值为x,否则为−x。

例7-12 使用宏ABS的简单例子。

程序如下:

img604

运行结果:

  100’s abs is:100

  0’S abs is:0

  −100.000000’s abs is:100.000000

如果要表示一个数的符号,可进行如下的宏定义:

  #define SIGN(x)((x)〉0?l:((x)==0? 0:(−1)))

它表示宏SIGN对参数x取其符号值作为宏运算的结果,当x〉0、x==0或x〈0时,宏运算结果分别为1,0,−1。

当需要求一个数的立方值时,也可以使用宏定义,例如:

  #define CUBE(x)(x*x*x)

这个宏表示对参数x求立方值作为宏CUBE的值。在预处理时,凡CUBE(x)处,都用(x*x*x)进行替换,如果程序中使用语句:

  m=CUBE(4.7);

经预处理,语句将替换成:

  m=(4.7*4.7*4.7);

必须指出,在进行宏定义时,最好把整个表达式及各个参数用圆括号括起来,以避免经宏替换后可能产生的不可预见的错误。

例如,求一个整数是否为偶数的宏定义为如下形式:

  #define EVEN(a) a%2==0?1:0

当a是一个整型数时,该宏替换是正确的,但如果用一个比较复杂的参数(如一个表达式)调用宏时,便可能产生错误。

例7-13 不正确使用宏替换的程序。

img605

运行结果:

  is odd

这个宏替换的结果是不正确的。因为调用参数是一个表达式,预处理时,EVEN(9+1) 被9+1%2==0?1:0替换,由于取模操作符%优先于加法操作符,即替换后先对l取模再加9,结果不等于0,打印出错误结果10是奇数。因此,对于这种情况,正确的宏定义应该给表达式中的a加上圆括号,这样在取模前先计算9+l的值。为了使上述程序得到期望的结果,宏定义应该改写成:

  #define EVEN(a)(a)%2==0?1:0

重新运行改写后的程序,得到了正确的结果。

例7-14 改写例7-13的程序。

img606

运行结果:

  is even

同样,CUBE宏定义及MAX宏定义也可改写成下面的形式,使用更安全些。

  #define CUBE(x)((x)*(x)*(x))

  #define MAX(a,b)((a)〉(b))?(a):(b)

从上面的一些例子中,可以看出带参数的宏与函数在使用方式上确有类似之处。宏也带有参数并返回宏的值,而且调用方式也与函数相似,也要求实际参数与形式参数数目相等、顺序对应,但带参数的宏定义不是函数,函数调用时,函数在程序被执行时进行处理,将实参的值传递给形参,且必须保证函数类型与参数类型相同;带参数的宏则是在程序预处理阶段进行简单的替换,因此无类型要求,可适用于任何类型参数。

使用宏的主要优点是运行速度快,其定义在预处理阶段就被展开,省去了函数调用时所需的开销。但是,由于宏要在编译前被展开,宏替换会使程序代码长度增大,如果宏在程序中出现的次数很多时更是如此。函数无论被调用多少次它们在程序中只被定义一次,在目标程序中,只占用同样的一个存储空间。但函数调用时参数的传递与返值,现场的恢复与保护都要占用时间,使程序运行速度变慢。因此在程序设计中究竟使用带参数的宏好,还是使用函数好,应酌情而定。

宏一旦被定义,在其所在的文件中均是存在和可见的。这一点很像外部变量。如果要对某一个宏定义撤销,可用如下预处理命令:

  #undef 宏标识符

一旦消除了原来的定义,这个宏标识符可以重新被定义。

例7-15 使用带参数的宏写出将两个参数的值互换的程序。

程序如下:

img607

img608

运行结果:

  Enter two integer a,b:25 35

  Exchanged:a=35,b=25

这个程序与在前面章节中采用函数编写的程序具有相同的功能,实现了两个参数值的互换。