在函数式宏定义中,#运算符用于创建字符串,#运算符后面应该跟一个形参(中间可以有空格或Tab),s将被作为一个字符串替换,并且s中的多个空格或tab将被替换为一个空格。
#define STR(s) # s
STR(hello world)
预处理后变为”hello world” 。
##运算符把前后两个预处理Token连接成一个预处理Token,和#运算符不同,##运算符不仅限于函数式宏定义,变量式宏定义也可以用。
先列出一个例子,当然这不是一段可执行的程序,甚至编译不了,只是为了说明宏展开的步骤问题,事实上这样的宏定义很多地方都能见到。
#define _sh(x) p_f(”n”,#x,”=%d, or %d\n”,n##x,alt[x])
#define sh(x) _sh(x)
#define NA 26
_sh(NA)
sh(NA)
可能发现第二行的定义有些多余,但结果却是截然不同的。
使用cpp命令做一下预处理就可以看到_sh(NA)和sh(NA)展开的不同,以下为展开结果。
p_f(”n”,”NA”,”=%d, or %d\n”,nNA,alt[26])
p_f(”n”,”26″,”=%d, or %d\n”,n26,alt[26])
下边分别说一下_sh(NA)和sh(NA)的展开步骤
_sh(NA)展开的步骤如下:
1.#x要替换成”NA”。
2.n##x要替换成nNA。
3.除了带#和##运算符的参数之外,其它参数在替换之前要对实参本身做充分的展开,所以应该先把NA展开成26再替换到alt[x]中x的位置。换言之,带有#和##的不会再进行二次替换。
4.现在展开成了p_f(”n” “NA” “=%d, or %d\n”,nNA,alt[26]),所有参数都替换完了,这时编译器会再扫描一遍,再找出可以展开的宏定义来展开,假设NA或alt是变量式宏定义,这时会进一步展开,但经#或##替换的除外,所以”NA”并没有成为”26“,nNA也没有被替换为n26。
sh(NA)展开的步骤如下:
1.用_sh(NA)替换sh(NA),并扫描,发现NA可以再展开,展开为_sh(26)
2. 展开_sh(26)为p_f(”n”,”26″,”=%d, or %d\n”,n26,alt[26])
所以,当期望宏替换为变量式宏的值而不是这个宏名,并且有#或者##在使用时,就要格外注意了,需要再加一条看似废话的宏定义。
另外,关于带有可变参数的函数在宏定义时,除了可以使用__VA_ARGS__来替代可变参数外,也可以使用##来连接。如下的两个宏定义是类似的
#define vprint(fmt, …) log_z(__FILE__,__LINE__,fmt,__VA_ARGS__)
#define vprint(fmt,args…) log_z(__FILE__,__LINE__,fmt,## args)
这个宏常用在日志函数中,调用vprint函数就加入了文件名和行号,方便debug,也可以加入__DATE__,__TIME__等等预定义变量,log_z函数实现如下
void log_z(char *fn,int ln,const char *fmt, …)
{
va_list ap;
char buf[4096];
va_start(ap,fmt);
vsnprintf(buf,4096,fmt,ap);
printf(”[%s %d]“,fn,ln);
printf(buf);
printf(”\n”);
}
另一个更加简单有效的增加行号的做法是这样定义的
#define vprint(fmt,args…) printf(”[%s %d]“fmt,__FILE__,__LINE__,##args)
COMMENTS