宏定义
大家已经知道,C源程序需要经过编译和链接才能生成可执行文件。实际上,在C源程序进行编译的第一遍扫描(词法扫描和语法分析)之前,首先需要经历一个预处理阶段。预处理将由预处理程序负责完成。当编译器对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕再进入对源程序的编译。
程序在编译预处理的阶段,不仅提供了宏定义的功能,还提供了文件包含以及条件编译的功能。文件包含在讲解“头文件”时已经提及,本章将详细介绍预处理中的“宏定义”,详细介绍预处理功能中的“条件编译”。
在C语言源程序中,宏定义是指用一个标志符代表一个字符串,该标志符就称为宏名。在程序编译预处理阶段,所有宏名将会被替换为宏定义中的字符串,这个操作被成为宏展开。
宏定义分为不带参数的宏定义和带参数的宏定义,其定义格式如下:
不带参数格式:#define 标识符 字符串
带参数的格式:#define 标识符(参数表) 字符串
其中的标识符,我们称之为宏名。
13.1.1 不带参数的宏定义
先来看不带参数的宏定义,比如在计算圆的面积的时候,经常使用PI的值。于是可以把PI的值利用宏定义来表示。
#define PI 3.14
float calccirclearea(float r)
{
return PI*r*r;
}
那么,在进行预处理阶段,预处理程序会把代码中的宏名替换为3.14,即代码变为:
float calccirclearea(float r)
{
return 3.14*r*r;
}
既然预处理是将PI替换为3.14,那么为什么不直接把3.14写进代码,这样就可以免去了预处理的时候进行替换呢?
首先,使用宏名PI在编写程序的时候代替3.14,这样可以提高程序的可读性,这是其一。其二,如果PI的值在程序中有多次引用,一旦PI的值需要在精度方面进行修改,那么使用了宏之后,只需要在宏定义的时候进行一次修改就可,而没有使用宏定义的时候,就需要在程序中多处修改。
13.1.2 带参数宏
接下来再看带参数的宏的定义方法。比如,在程序里经常会遇到计算2个值中的最大值的算法问题。这个算法问题可以用宏定义如下:
#define MAX(X, Y) (X) > (Y) ? (X)
: (Y)
void main(void)
{
int a = 10;
int b = 11;
int c = MAX(a, b);
printf(“max = %d\n”, c);
}
上面的程序在预处理阶段会被替换为下面的代码:
void main(void)
{
int a = 10;
int b = 11;
int c =(a)>(b)?(a):(b);
printf(“max = %d\n”, c);
}
上面分别介绍了用宏来定义一个常量和带参的宏定义的情况。但它们都只有一条语句。现在来看用宏定义一个多条语句的宏。在程序设计中,另外一个很经典的算法就是将两个数进行交换。比如有2个整数:
int a = 10;
int b = 20;
交换后,a的值为20,b的值为10。在程序里面,必须要使用一个临时变量,来先把a的值保存起来,然后再把b的值赋给a,再把临时变量保存起来的a的值赋值给b。如果不用临时变量把a的值保存起来,那么当把b的值赋给a的时候,a的值就会丢失。
错误的写法如下:
a = b; //a的值丢失
b = a;//a和b的值最终都是b的值
正确的写法:
int tmp = a; //使用tmp变量保存a的值
a = b;
b = tmp;
上面的算法可以用宏来定义如下:
#define SWAP(a,b)
{ \
int tmp; \
tmp = a; \
a= b; \
b = tmp; \
}
上面的宏定义中,每行语句都用一个’\’来连接,用来将两个int类型的整数交换。然后在程序中使用这个宏定义如下:
void main(void)
{
int a = 10;
int b = 20;
SWAP(a, b);
printf(“a=%d, b=%d\n”, a, b);
}
这种对于多条语句的定义方法,虽然在一般情况下不会出现什么问题。但是,来看看下面的条件语句:
If (x>y)
SWAP(a, b);
else
SWAP(a,b);
如果对上面的条件语句进行宏展开,就得到了下面的程序:
if (x>y)
{
int tmp;
tmp = a;
a= b;
b = tmp;
};//注意这里的分号,将if和else拆开了,else无法和if配对
else
{
int tmp;
tmp = a;
a= b;
b = tmp;
};
很显然,else语句和if语句被分号;分割,出现一个语法错误,即else语句找不到if语句与之配对。
于是,当包含多条语句的宏,一般采用do-while(0)的格式来定义,即在语句外面加上一个do-while(0)来包装:
#define SWAP(a,b)
do { \
int tmp; \
tmp = a; \
a= b; \
b = tmp; \
}while(0)
这样,上面的程序展开之后:
if (x>y)
do{
int tmp;
tmp = a;
a= b;
b = tmp;
}while(0);
else
do {
int tmp;
tmp = a;
a= b;
b = tmp;
}while(0);
这样do-while(0)本身就只是一条语句,宏定义中的语句也只执行一次就退出了do-while循环,也避免了上面宏定义出现的一个特殊错误。