宏定义
通过#define
定义宏,宏定义的格式为:
#define N 1000
通过#undef
取消宏定义
#undef N
所以宏的生命周期是从定义到取消定义,而不是从定义到程序结束。即
#define +
|
| // 存活周期
|
#endif +
|
| // 宏已经无法使用
宏在使用时,与普通变量一致
#define N 100
for(int i=0; i<N; i++){
printf("%d\n", i);
}
宏变量可以通过\
进行换行
#define N 1000 \
+ 100 \
+ 10
printf("%d\n", N); // 1110
宏与typedef的区别
宏只进行替换,而typedef是定义了一个新的类型,可以进行类型检查。两者大多数时候是可以互换的,但是在一些特殊情况下,比如指针类型,就会出现问题。
// 定义
#define ptr_int1 int *
typedef int * ptr_int2;
// 使用
ptr_int1 a, b; // a是int*,b是int
ptr_int2 a, b; // a和b都是int*
从上面的例子可以看出,宏定义只是进行了简单的替换
ptr_int1 a, b; // 预编译后:int * a, b;
而typedef定义了一个新的类型,所以ptr_int2是一个类型,而不是一个变量,所以a和b都是int*类型。
编译器定义的宏变量
编译器自身定义了一些宏变量,比如__FILE__
、__LINE__
、__DATE__
、__TIME__
等,这些宏变量在编译时会被替换成相应的值。比如__FILE__
会被替换成当前文件的文件名,__LINE__
会被替换成当前行号,__DATE__
会被替换成当前编译日期,__TIME__
会被替换成当前编译时间。
printf("当前文件名:%s\n", __FILE__);
定义宏函数
宏函数与普通函数相比,宏函数不需要提供参数的类型信息,也不需要返回值的类型信息,所以宏函数的定义比普通函数简单很多。比如
#define echo(x) printf("%d\n", x)
使用时,与普通函数一样
echo(123);
宏函数还支持可变参数定义时,通过...
表示,使用时通过__VA_ARGS__
表示。比如
#define echo(...) printf(__VA_ARGS__)
echo("%d\n", 123); // 预编译后:printf("%d\n", 123);
##与#
#
是将宏参数转换成字符串,##
是将两个宏参数连接成一个新的标识符。比如
#define STR(x) #x
#define CAT(x, y) x##y
printf("%s\n", STR(123)); // 123
printf("%d\n", CAT(1, 23)); // 123
一个更为复杂的例子
#define _STR(x) #x
#define STR(x) _STR(x)
这里定义了两个宏,STR
宏将参数转换成字符串,_STR
宏将参数转换成字符串,这里为什么要定义两个宏呢?因为如果只定义一个宏,比如
#define STR(x) #x
#define N 100
printf("%s\n", STR(N)); // N
上面这种情况下,STR(N)
会被替换成#N
,而不是#100
,主要原因是因为N
这个宏变量没有展开,要想展开N
这个宏变量,需要在STR
宏定义中再定义一个宏,比如
#define _STR(x) #x
#define STR(x) _STR(x)
#define N 100
printf("%s\n", STR(N)); // 100
上面这种写法之所以能够正常工作,是因为STR(N)
会被替换成_STR(100)
,而_STR(100)
会被替换成#100
,这样N
这个宏变量就展开了。
限制宏内部变量的作用域
宏定义的变量是全局的,如果想要限制宏定义的变量的作用域,可以使用do { ... } while(0)
来限制。比如
#define DO(a, b) do { \
int _a = (a); \
int _b = (b); \
printf("%d\n", _a + _b); \
} while(0)
int main(void){
DO(1, 2); // 3
DO(3, 4); // 7
return 0;
}
上面的例子中,_a
和_b
变量的作用域被限制在了do { ... } while(0)
中,所以不会污染全局变量。
括号
由于宏定义只是进行简单的替换,所以在使用宏定义时,需要注意括号的使用。比如
#define MUL(x, y) x * y
int main(void){
printf("%d\n", MUL(1 + 2, 3 + 4)); // 1 + 2 * 3 + 4 = 11
return 0;
}
上面的例子中,MUL(1 + 2, 3 + 4)
会被替换成1 + 2 * 3 + 4
,所以结果是11,而不是21。为了解决这个问题,需要在宏定义中加上括号,比如
#define MUL(x, y) ((x) * (y))
int main(void){
printf("%d\n", MUL(1 + 2, 3 + 4)); // (1 + 2) * (3 + 4) = 21
return 0;
}
在程序优化时,编译器会对括号进行优化,所以不用担心括号会影响程序的性能。
副作用
同样由于宏定义只是进行简单的替换,所以在使用宏定义时,需要注意副作用。比如
#define double(x) (x) + (x)
int do_something(int x){
printf("do something\n");
return x;
}
int main(void){
printf("%d\n", double(do_something(1)));
return 0;
}
经过预编译以后
printf("%d\n", (do_something(1)) + (do_something(1)));
所以do_something
函数会被调用两次,这就是副作用。因此在往宏定义中传递函数时,应该尽量避免传递函数调用。一个正确的写法是,先将函数调用的结果保存到一个变量中,然后再将变量传递给宏定义
int main(void){
int i = do_something(1);
printf("%d\n", double(i));
return 0;
}