字符串化操作符 #
有时候我们希望能够将参数转换为字符串进行处理,#
可以将一个 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__)
|