探秘C语言中的宏定义

1、简单替换(object-like 宏)

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