预定义宏
__LINE__ // 当前行号
__FILE__ // 当前文件名
__FUNCTION__ // 当前函数名
带参数的宏
宏定义各部分的名称约定:
基本展开规则:
- 如果替换语句中含有宏定义,先展开;与
##
参与运算的除外 - 如果实参是宏定义,先展开,与
#
或##
参与运算的除外
省略参数 ...
a. 基本使用:省略号必须位于参数列表的末尾
// 使用方法1:不指定省略参数的名称
#define FOO(...) foo(__VA_ARGS__)
// 使用方法2:指定省略参数的名称
#define FOO(args...) foo(args)
b. 省略参数为空,前方有逗号,需要使用 ##
// 错误
#define LOG(format, args...) fprintf(stderr, format, args)
LOG("test"); // 展开为:fprintf(stderr, "test", );
// 正确
#define LOG(format, args...) fprintf(stderr, format, ##args)
LOG("test"); // 展开为:fprintf(stderr, "test");
字符串化运算符 #
使用 #
符号可以将实参字符串化
注:宏的参数也是参数,需要符合文法,例如引号需要成对
a. 如果实参是另一个宏定义符号,那么这个符号不会被展开
#define PUTS(arg) puts(#arg)
#define ABC abc
// 这样的使用:
PUTS(ABC);
// 被展开为:
puts("ABC");
b. 如果实参是字符串,那么宏展开时:除了添加双引号,还会在原有的 "
和 \
前面加上 \
#define PUTS(arg) puts(#arg)
// 这样使用:
PUTS("abc\n");
// 被展开为:
puts("\"abc\\n\"");
c. 如果实参不是字符串,那么宏展开时,只会添加双引号
注意这里的转义字符
#define PUTS(arg) puts(#arg)
// 这样使用:
PUTS(abc\n);
// 被展开为:
puts("abc\n");
d. 对省略参数进行字符串化,会在参数之间添加 ,
,连续的空格会被替换为一个空格
#define PUTS(...) puts(#__VA_ARGS__)
// 这样使用:
PUTS(abc, 123,456);
// 被展开为:
puts("abc, 123,456");
记号粘贴运算符 ##
a. ##
作用的过程:
- 将实参带入,合并
##
左右两侧的内容,得到一个新的token
- 尝试展开新得到的
token
注:如果替换语句得 ##
两侧含有宏定义,不会单独展开;如果实参是宏定义,也不会单独展开
#define BOX_FIRE "FUKA"
#define box_fire "fuka"
#define BOX box
#define FIRE fire
// 替换语句的 ## 两侧有宏定义 BOX,不会单独展开
#define PRINT(arg) printf(BOX ## _ ## arg)
// 实参是宏定义 FIRE,不会单独展开
PRINT(FIRE);
/**
@最终展开:
printf("FUKA");
@解析:
可以看到:宏定义中的 BOX 和实参中的 FIRE 都没有被展开
通过 ## 得到 BOX_FIRE 后,再展开为 "FUKA"
*/
b. ##
两侧可以有空格,特定情况下参数可以为空
#define FOO(arg) printf("123" ## arg)
// 这样使用:
FOO();
// 被展开为:
printf("123");
使用示例
a. 打印日志
#define err_log(format, args...) \
do { printf("\033[36m""[%s, %s, %d]: "format"\033[0m\n", __FILE__, __FUNCTION__, __LINE__, ##args); }while(0)