字符串化操作符 #
有时候我们希望能够将参数转换为字符串进行处理,#
可以将一个 token 字符串化,示例如下:
1 2 3 4 5 6 7 8 9 10 11 #define WARN_IF(EXP) \ if (EXP) \ {\ printf ("Warning: " #EXP "\n" );\ } string x = "0" ;WARN_IF(x == "0" ); void * p = nullptr ;WARN_IF(p == nullptr );
需要注意的三点:
#
操作符并不是简单的添加双引号,它会自动对特殊字符进行转义
#
操作符只能对 func-like 的参数使用
由于参数会转换为 token 列表,所以前后的空白符都会被忽略 ,中间的空白符会被压缩为 1 个,注释会被忽略并变成一个空白符。
token粘贴操作符 ##
合并新的token可以用来提供动态生成token的能力:
1 2 3 4 #define GETTER(x, T) T get_##x() { return this->x; } GETTER(foo, const int ) -> const int get_foo () { return this ->x; }
obj-like的递归展开 在替换列表中出现的宏会被展开,这一过程将递归的进行下去,且是深度优先的,示例如下:
1 2 3 4 5 6 7 8 9 10 #define foo foz bar #define bar 123 #define foz baz #define baz 1 foo -> foz bar -> baz bar -> 1 bar -> 1 123
可以看到当一个宏完全展开后,下一个宏才会被展开,但是如果只有这一条规则,很容易出现无限递归的情况,示例如下:
1 2 3 4 5 6 7 #define foo bar #define bar foo foo -> bar -> foo -> 无限循环
标准中对这种情况作了限制,如下所示:
在展开的过程中,如果替换列表中出现了被展开宏,那么该展开宏不会被展开
更进一步,在展开的过程中,任何嵌套的展开过程中出现了被展开宏,该被展开宏也不会被展开
func-like的宏展开
identifier-list也就是参数列表里的参数会被完全展开,但如果该参数在替换列表中被#或##所调用 ,那么该参数不展开;
使用展开后的结果替换列表中的相关内容;
执行#和##的结果,并替换相关内容;
将得到的新的替换列表重新扫描找到可替换的宏名并展开。
下例中的CAT中的参数FOO_由于在替换列表中被##所调用,所以并不会被展开,而是直接合并成一个token FOO_1,并且在重新扫描的阶段被展开为1,在该宏中实际上进行了两次扫描展开:
1 2 3 4 5 6 7 #define FOO_ 0 #define FOO_1 1 #define CAT(x, y) x ## y CAT(FOO_, 1 ) -> FOO_1 -> 1
如果我们希望先展开参数,然后再进行拼接呢?需要借助一个额外的宏来间接做这件事:
1 2 3 4 5 6 7 8 #define FOO_ 0 #define FOO_1 1 #define PRIMITIVE_CAT(x, y) x ## y #define CAT(x, y) PRIMITIVE_CAT(x, y) CAT(FOO_, 1 ) -> PRIMITIVE_CAT(0 , 1 ) -> 01
自指的蓝色集合 在第一次FOO展开的时候,当前的蓝色集合为{BAZ}。
首先先完全展开参数,此时展开第一个参数BAZ时,由于此时BAZ已在蓝色集合中,所以停止展开,第二个FOO不在蓝色集合中,因此可以展开:
1 2 3 4 5 6 7 8 #define FOO(x, y) x + y #define BAR(y) 13 + y #define BAZ(z) FOO(BAZ(16), FOO(11, 0)) + z BAZ(15 ) -> FOO(BAZ(16 ), FOO(11 , 0 )) + 15 -> FOO(BAZ(16 ), 11 + 0 ) + 15 -> BAZ(16 ) + 11 + 0 + 15
括号表达式 将参数用括号括起来使用,这样利用func-like的宏不接括号不会被展开的特性,可以完成一些有意思的东西,示例如下:
1 2 3 4 5 6 7 8 9 #define EXPAND_IF_PAREN(x) EXPAND x #define EAT(x) EXPAND_IF_PAREN((12 )) -> EAT (12 ) -> EXPAND_IF_PAREN(12 ) -> EAT 12
检测 1 2 3 4 5 6 7 8 9 10 11 12 #define GET_SEC(x, n, ...) n #define CHECK(...) GET_SEC(__VA_ARGS__, 0) #define PROBE(x) x, 1 CHECK(PROBE()) -> CHECK(x, 1 ,) -> GET_SEC(x, 1 , 0 ) -> 1 CHECK(sth_not_empty) -> GET_SEC(sth_not_empty, 0 ) -> 0
__VA_ARGS__
__VA_ARGS__
是一个可变参数的宏,替换为宏定义中参数列表的最后一个参数为省略号。
##__VA_ARGS__
宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的”,”去掉的作用,否则会编译出错。
一般用在调试信息的输出中:
1 2 3 4 5 6 7 #define my_print1(...) printf(__VA_ARGS__) #define my_print2(fmt,...) printf(fmt,__VA_ARGS__) #define my_print3(fmt,...) printf(fmt,##__VA_ARGS__) my_print1("i=%d,j=%d\n" ,i,j); my_print2("iiiiiii\n" ); my_print3("iiiiiii\n" );
1 2 3 #define MODULE_NAME "MY_LIBS" #define error_printf(fmt,...)\ printf ("[ERROR][" MODULE_NAME"](%s|%d) " fmt,__func__,__LINE__,##__VA_ARGS__)