_RX.h 中的宏

431 阅读3分钟

宏 ## 拼接

#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";

背景知识:参数预扫描

  1. 宏展开时,会先对宏参数进行预扫描,如果参数也匹配宏定义,则先会对参数完全展开,然后使用展开后的结果对宏体进行替换。感觉是对宏参数的深度优先遍历展开。
  2. 进行替换之后对宏体进行二次扫描,继续展开结果里出现的宏。感觉是个递归的过程。 但是在第一步中有两个特例,如果某个宏参数是用于字符串化或者拼接,则不会对参数进行展开,而是直接当做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
  1. 由于RX_CAT2的两个参数都不是用于 ##拼接 或者 #字符串化,所以会先对参数进行预扫描,也就是先处理参数。
  2. 第一个参数 foo_ 没有什么好处理的。
  3. 第二个参数 BAR 可以匹配宏定义,所以先展开第二个参数为4。
  4. 替换宏体为 RX_CAT2(foo, 4)
  5. 对宏体进行二次扫描,发现还可以展开,这次两个参数是用于拼接,所以不进行预扫描,直接拼接为 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);

  1. 对参数进行预扫描: STR_ARGS 被展开 RX_ELEMENT_AT(0, "0", "1", "2", "3", "4", "5");

  2. RX_ELEMENT_AT 展开 RX_CAT2(RX_ELEMENT_AT, 0)("0", "1", "2", "3", "4", "5")

  3. RX_CAT2(RX_ELEMENT_AT, 0) 被替换 _RX_ELEMENT_AT_0("0", "1", "2", "3", "4", "5")

  4. 根据 #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。