宏 ## 拼接
#define RX_CAT2(_1, _2) _RX_CAT2(_1, _2)
#define _RX_CAT2(_1, _2) _1 ## _2
背景知识:字符串化与token拼接
##用于将前后两个token拼接起来: Hello 跟 World 两个token合并为HelloWorld一个token。
#用于将token字符串化。将abc这个token字符串化为一个字符串"abc"。
#define CONCAT(_1, _2) _1 ## _2
#define STRINGIFY(_name) #_name
static char *CONCAT(Hello, World) = STRINGIFY(abc);
// 展开结果为
static char *HelloWorld = "abc";
背景知识:参数预扫描
- 宏展开时,会先对宏参数进行预扫描,如果参数也匹配宏定义,则先会对参数完全展开,然后使用展开后的结果对宏体进行替换。感觉是对宏参数的深度优先遍历展开。
- 进行替换之后对宏体进行二次扫描,继续展开结果里出现的宏。感觉是个递归的过程。 但是在第一步中有两个特例,如果某个宏参数是用于字符串化或者拼接,则不会对参数进行展开,而是直接当做token进行拼接。
为什么需要定义两层?
#define BAR 4
#define CONCAT(_1, _2) _1 ## _2
CONCAT(foo_, BAR) 展开为 foo_BAR
因为CONCAT宏的两个参数都是用于 ## 拼接,所以不会对参数BAR先展开,而是把BAR当做token直接拼接。
如果使用双层宏的定义来做,过程如下:
#define BAR 4
#define RX_CAT2(_1, _2) _RX_CAT2(_1, _2)
#define _RX_CAT2(_1, _2) _1 ## _2
RX_CAT2(foo_, BAR) 展开为 foor_4
- 由于RX_CAT2的两个参数都不是用于
##拼接 或者#字符串化,所以会先对参数进行预扫描,也就是先处理参数。 - 第一个参数
foo_没有什么好处理的。 - 第二个参数
BAR可以匹配宏定义,所以先展开第二个参数为4。 - 替换宏体为 RX_CAT2(foo, 4)
- 对宏体进行二次扫描,发现还可以展开,这次两个参数是用于拼接,所以不进行预扫描,直接拼接为 foor_4。
两层定义的主要作用是 外层用于展开参数,里层再做拼接。
宏 可变参数的个数
#define RX_ELEMENT_AT(n, ...) RX_CAT2(_RX_ELEMENT_AT_, n)(__VA_ARGS__)
#define _RX_ELEMENT_AT_0(x, ...) x
#define _RX_ELEMENT_AT_1(_0, x, ...) x
#define _RX_ELEMENT_AT_2(_0, _1, x, ...) x
#define _RX_ELEMENT_AT_3(_0, _1, _2, x, ...) x
#define _RX_ELEMENT_AT_4(_0, _1, _2, _3, x, ...) x
#define _RX_ELEMENT_AT_5(_0, _1, _2, _3, _4, x, ...) x
#define _RX_ELEMENT_AT_6(_0, _1, _2, _3, _4, _5, x, ...) x
RX_ELEMENT_AT宏后面有两个部分,第一个部分是一个整数,第二个部分是可变参数,目的是从第二部分的可变参数中,拿出第一部分的那个整数的位置对应的位置的参数。下边从0开始算。
比如
#define STR_ARGS "0", "1", "2", "3", "4", "5"
const char *str0 = RX_ELEMENT_AT(0, STR_ARGS);
const char *str1 = RX_ELEMENT_AT(1, STR_ARGS);
const char *str2 = RX_ELEMENT_AT(2, STR_ARGS);
const char *str3 = RX_ELEMENT_AT(3, STR_ARGS);
const char *str4 = RX_ELEMENT_AT(4, STR_ARGS);
const char *str5 = RX_ELEMENT_AT(5, STR_ARGS);
预处理后得到:
const char *str0 = "0";
const char *str1 = "1";
const char *str2 = "2";
const char *str3 = "3";
const char *str4 = "4";
const char *str5 = "5";
正好把第二部分开始的可变参数的对应位置的参数取了出来。
如何做到的呢?举个例子: RX_ELEMENT_AT(0, STR_ARGS);
-
对参数进行预扫描: STR_ARGS 被展开 RX_ELEMENT_AT(0, "0", "1", "2", "3", "4", "5");
-
RX_ELEMENT_AT 展开 RX_CAT2(RX_ELEMENT_AT, 0)("0", "1", "2", "3", "4", "5")
-
RX_CAT2(RX_ELEMENT_AT, 0) 被替换 _RX_ELEMENT_AT_0("0", "1", "2", "3", "4", "5")
-
根据 #define _RX_ELEMENT_AT_0(x, ...) x x匹配 "0", 后面...匹配剩下的"1", "2", "3", "4", "5",所以整个宏被替换为 "0"
#define RX_COUNT(...) RX_ELEMENT_AT(6, ## __VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
可以算出 ... 匹配的参数的个数。
举个例子: RX_COUNT("1", "2", "3")
展开为:
- RX_ELEMENT_AT(6, )
- _RX_ELEMENT_AT_6("1", "2", "3", 6, 5, 4, 3, 2, 1, 0)
根据 #define _RX_ELEMENT_AT_6(_0, _1, _2, _3, _4, _5, x, ...) x
"1", "2", "3", 6, 5, 4, 3, 2, 1, 0
_0, _1, _2, _3,_4,_5, x ...
通过上下对应,x正好匹配到3,所以整个宏被替换为3。