宏定义分类:
1 . 不带参数的宏定义 : #define 宏名 [宏体] 例: #define TRUE 1
2. 带参数的宏 : #define 宏名( 参数表) [宏体] 例:#define MAX(x,y) ((x)>(y)?(x):(y))
宏定义的作用 :
1 )程序中多次使用TRUE,如果需要对TRUE的值进行修改,只需改动一处就可以了,简化程序维护
2 )提高程序的运行效率
宏定义的展开是在程序的预处理阶段完成的,无需运行时分配内存,能够部分实现函数的功能,却没有函数调用的压栈、弹栈开销,效率较高
3 )增强可读性
当我们看到类似PI这样的宏定义时,自然可以想到它对应的是圆周率常量
宏定义的# 、## 和 do...while(0)或do...while(false) 使用说明 :
1 )# 简化理解:#是“字符串化”的意思,出现在宏定义中的#是把跟在后面的参数转换成一个字符串
#define #define PRINTF(s) #s
#include <stdio.h>#define #define PRINTF(s) #svoid main(void) {printf(PRINTF(abcdef));
}
2 )## 是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。
普通宏定义,预处理器一般把空格解释成分段标志,对于每一段和前面比较,相同的就被替换。
但是这样做的结果,被替换段之间会存在一些空格。如果不希望出现这些空格,通过添加一些##来替代空格。
A: #define DEFINITION1(type,name) type name_##type##_type
B: #define DEFINITION2(type,name) type name##_##type##_type
DEFINITION1( int,a1); /* 等价于: int name_int_type; */DEFINITION2(int,a1); /* 等价于: int a1_int_type; */
A:"name"和第一个"_"之间,以及第2个"_"和第二个"type"之间没有被分隔,所以预处理器会把name_##type##_type解释成3段:“name_”、“type”、以及“_type”,这中间只有“type”是在宏前面出现过的,所以它可以被宏替换。B:“name”和第一个“_”之间也被分隔了,所以预处理器会把name##_##type##_type解释成4段:“name”、“_”、“type以及“_type”,这其间,就有两个可以被宏替换了。如果 ## 前面随意加上一些空格 ##会把前面的空格去掉完成强连接
#define DEFINITION1(type,name) type name_ ##type##_type
等价于#define DEFINITION1(type,name) type name_##type##_type
3 ) do...while(0)或do...while(false) 使用说明 :
使用do{….}while(0) 把宏体包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。
同时因为绝大多数的编译器都能够识别do{…}while(0)这种无用的循环并进行优化,
所以使用这种方法也不会导致程序的性能降低
a 空的宏定义避免warning:
#define foo() do{}while(0)
b 存在一个独立的block,可以用来进行变量定义,进行比较复杂的实现。
c 如果出现在判断语句过后的宏,这样可以保证作为一个整体来是实现:
#define foo(x) action1(); action2();
在以下情况下:
if(NULL == pPointer)
foo();
就会出现action1和action2不会同时被执行的情况,而这显然不是程序设计的目的。
d 以上的第3种情况用单独的{}也可以实现,但是为什么一定要一个do{}while(0)呢
#define switch(x,y) {int tmp; tmp="x";x=y;y=tmp;}
if(x>y)
switch(x,y);
else //error, parse error before else
otheraction();
在把宏引入代码中,会多出一个分号,从而会报错。这对这一点,可以将if和else语句用{}括起来,可以避免分号错误。
4 ) 用于程序调试跟踪
常见的用于调试的宏有,_ L I N E _,_ F I L E _,_ D A T E _,_ T I M E _, _ S T D C _,__FUNCTION__
printf("__FUNCTION__:%s\n", __FUNCTION__);
printf("__FILE__:%s\n", __FILE__);
printf("__LINE__:%d\n", __LINE__);
... ...
宏定义使用的一些问题:
1. 错误的嵌套
宏的定义不一定要有完整的、配对的括号,但是为了避免出错并且提高可读性,最好避免这样使用。
2. 由操作符优先级引起的问题由于宏只是简单的替换,宏的参数如果是复合结构,
那么通过替换之后可能由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,
如果我们不用括号保护各个宏参数,可能会产生预想不到的情形
#define ceil_div(x, y) (x + y - 1) / y
如果 a = ceil_div( b & c, sizeof(int) );
等价于 a = ( b & c + sizeof(int) - 1) / sizeof(int);
由于 + / - 的优先级高于 & 的优先级 那么 等价于
a = ( b & (c + sizeof(int) - 1)) / sizeof(int); //这个结果并不是我们想要的
我们实际想要的结果是:
#define ceil_div(x, y) (((x) + (y) - 1) / (y))
3. 消除多余的分号函数模样的宏在表面上看起来像一个通常的C语言调用一样,通常情况下我们在宏的后面加上一个分号,比如下面的带参宏: MY_MACRO(x);
下面的情况:
#define MY_MACRO(x) {
/* line 1 */
/* line 2 */
/* line 3 */ }
if (condition())
MY_MACRO(a);
else
{...}
这样会由于多出的那个分号产生编译错误。为了避免这种情况出现同时保持MY_MACRO(x);的这种写法,
我们需要把宏定义为这种形式:
#define MY_MACRO(x) do {
/* line 1 */
/* line 2 */
/* line 3 */ } while(0)
这样只要保证总是使用分号,就不会有任何问题。
4 . 重复的副作用
这里的Side Effect是指宏在展开的时候对其参数可能进行多次取值,但是如果这个宏参数是一个函数,
那么就有可能被调用多次从而达到不一致的结果,甚至会发生更严重的错误。
#define min(X,Y) ((X) > (Y) ? (Y) : (X))
c = min(a,foo(b));
这时foo()函数就被调用了两次。为了解决这个潜在的问题,我们应当这样写min(X,Y)这个宏:
#define min(X,Y) ({
typeof (X) x_ = (X);
typeof (Y) y_ = (Y);
(x_ < y_) ? x_ : y_; })
({...})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部作用域)