探秘C语言中的宏定义
1、简单替换(object-like 宏)
N
// ->
42
2、带参数的宏( function-like宏)
ADD(1, 2)
// ->
1 + 2
标准定义格式如下
// obj-like
#define 宏名 替换列表 换行符
//func-like
#define 宏名 ([标识符列表]) 替换列表 换行符
3、宏定义以换行符结尾,这就意味着一个宏定义不论多长都只能写在一行中,如果要分行写,请使用\,示例如下:
int main() \
{ \
return 0; \
}
4、宏的操作符
4.1、字符串化操作符#
有时候我们希望能够将参数转换为字符串进行处理,#可以将一个token字符串化,示例如下:
{\
fprintf (stderr,
}\
WARN_IF (x/* const char* */ == "0")
// ->
if (x == "0") { fprintf (stderr, "Warning: " "x == \"0\"" "\n"); }
需要注意的三点:
(1)#操作符并不是简单的添加双引号,它会自动对特殊字符进行转义
(1)#操作符并不是简单的添加双引号,它会自动对特殊字符进行转义
(2)#操作符只能对func-like的参数使用
(3)由于参数会转换为token列表,所以前后的空白符都会被忽略,中间的空白符会被压缩为1个,注释会被忽略并变成一个空白符。
4.2、token粘贴操作符##
合并新的token可以用来提供动态生成token的能力,示例如下:
GETTER(foo, const int)
//->
const int get_foo() {return this->foo;}
5、obj-like的递归展开
在替换列表中出现的宏会被展开,这一过程将递归的进行下去,且是深度优先的,示例如下:
foo
-> foz bar
-> baz bar
-> 1 bar
-> 1 123
可以看到当一个宏完全展开后,下一个宏才会被展开,但是如果只有这一条规则,很容易出现无限递归的情况,示例如下:
foo
-> bar
-> foo
-> 无限循环
标准中对这种情况作了限制,如下所示:
(1)在展开的过程中,如果替换列表中出现了被展开宏,那么该展开宏不会被展开
(2)更进一步,在展开的过程中,任何嵌套的展开过程中出现了被展开宏,该被展开宏也不会被展开
6、func-like的宏展开
(1)identifier-list也就是参数列表里的参数会被完全展开,但如果该参数在替换列表中被#或##所调用,那么该参数不展开;
(2)使用展开后的结果替换列表中的相关内容;
(3)执行#和##的结果,并替换相关内容;
(4)将得到的新的替换列表重新扫描找到可替换的宏名并展开。
FOO_作为参数会先被展开为0,然后再替换替换列表中相应的部分,示例如下:
PRIMITIVE_CAT(FOO_, 1)
-> 0 1
CAT中的参数FOO_由于在替换列表中被##所调用,所以并不会被展开,而是直接合并成一个token FOO_1,
并且在重新扫描的阶段被展开为1,在该宏中实际上进行了两次扫描展开,示例如下:
CAT(FOO_, 1)
-> FOO_1
-> 1
如果我们希望先展开参数,然后再进行拼接呢?需要借助一个额外的宏来间接做这件事,
由于PRIMITIVE_CAT的存在参数FOO_在第一层中并没有被##直接调用,所以FOO_作为参数会被
首先展开为0,之后在PRIMITIVE_CAT中完成拼接,注意这回的01之间没有空格,因为它们被
拼接成了一个token,示例如下:
CAT(FOO_, 1)
-> PRIMITIVE_CAT(0, 1)
-> 01
接下来再看一个涉及自指的例子
在第一次FOO展开的时候,当前的蓝色集合为{BAZ}。
首先先完全展开参数,此时展开第一个参数BAZ时,由于此时BAZ已在蓝色集合中,所以停止展开,第二个FOO不在蓝色集合中,因此可以展开
BAZ(15)
-> FOO(BAZ(16), FOO(11, 0)) + 15
-> FOO(BAZ(16), 11 + 0) + 15
-> BAZ(16) + 11 + 0 + 15
7、Token粘贴,示例如下:
CAT(FOO_, 1)
-> PRIMITIVE_CAT(0, 1)
-> 01
8、括号表达式,将参数用括号括起来使用,这样利用func-like的宏不接括号不会被展开的特性,可以完成一些有意思的东西,示例如下:
EXPAND_IF_PAREN((12))
-> EAT (12)
->
EXPAND_IF_PAREN(12)
-> EAT 12
9、检测,示例如下:
CHECK(PROBE())
-> CHECK(x, 1,)
-> GET_SEC(x, 1, 0)
-> 1
CHECK(sth_not_empty)
-> GET_SEC(sth_not_empty, 0)
-> 0