呵呵,这个问题其实应该这么看:
对于:
#define cat(a,b) a ## b
cat(cat(1,2),3)
很多回复的朋友都有各种各样的结论,有的说是从最外层往里开始替换的,也有的说是从最里层往外替换的,两种说法都能刚好解释一些特例而已,其实两者都不完全对,通过我的实践和对标准的理解得出微软的编译器的处理办法如下:
cat(cat(1,2),3)
cat(1,2) ## 3
cat(1,2)3 //由规则2得到cat(1,2)和3即使是宏也不进行求值
而对于:
#define cat(a,b) a##b
#define xcat(a,b) cat(a,b)
xcat(xcat(1,2),3)
事实上微软和大部分编译器都是这样进行替换的:
xcat(xcat(1,2),3)
cat(xcat(1,2),3) //由规则2得到xcat(1,2)是一个宏且没有相关的#和##,因此对此宏进行求值展开,注意是完全展开,而不是只展开一层,同时当前宏展开的编译器程序暂停,递归调用了另一个宏替换程序
xcat(1,2) -> cat(1,2) -> 1 ## 2 ->12 //该宏替换子程序结束返回结果12给上一级宏替换程序
cat(12,3) //主宏替换程序继续进行,xcat(1,2)没有相连的#,##所以求值展开为12
123 //最后结果就很明显了
现在再来看lz这个问题:
cat(cat(1,2),3)
cat(a,b)
f(cat(cat(1,2),3))
第一个已经解决,第二个展开如下:
cat(a,b)
a ## b //由标准规则2知道,即使a,b是宏在这里也不进行求值展开了
ab //主宏替换程序结束,再次扫描发现ab还是一个宏,且没有相连的#,##因此调用宏展开子程序求其值
ab -> AB
AB //再次扫描已经没有宏了,主宏替换程序结束
第三个宏:
f(cat(cat(1,2),3)) //经扫描发现有递归宏,从最外层开始替换,遇到需要展开的宏则调用宏展开子程序
fff cat(cat(1,2),3) //由规则2继续,对cat(cat(1,2),3)调用宏展开子程序
cat(cat(1,2),3) -> cat(1,2) ## 3 ->cat(1,2)3 //由于cat(1,2)3不是宏,宏替换子程序结束并返回
fff cat(1,2)3
再对supermegaboy的另一个问题解释一下:
#define N 10
如果想制造字符串化效果"10",直接#define X(N) #N是不行的,根据第一点,结果只会是"N",而不是"10",这时候应该这样定义宏:
#define N 10
#define Y(N) X(N)
#define X(N) #N
对于他的这部分说明,我们来用我刚刚的方法进行展开即可:
首先:
#define N 10
#define X(N) #N
宏替换开始:X(N) -> #N -> "N",因为N遇到了#,由标准可知不对它展开
为什么
#define N 10
#define Y(N) X(N)
#define X(N) #N
这样行呢?
依次展开就明白了:Y(N) -> X(N) -> X(10) -> #10 ->"10" //在第二步时N没有#,##因此在对X(N)递归展开时先对N展开了,N展开后再继续对X(N)的展开。
我写两个宏,有需要看各种宏展开的可以像这样用:
#define TOSTR_HELPER(a) #a
#define TOSTR(a) TOSTR_HELPER(a)
#define … //定义你想要看最后展开结果的递归宏
void main()
{
std::cout《TOSTR(…)《endl;
}
这样就可以看TOSTR()中宏的展开情况了,因为它会把里面的宏展开结果转化成字符串并输出。
思考:编译器其实也是一个程序,单纯从最外层向最里层替换的操作在程序上不好设计,单纯从最里向最外层替换也一样,所以实际的替换过程其实是里外都在进行,我用伪代码写一个可以实现上述方法的替换程序,猜测那些编译器大致是这么处理的:
TEXT MacroSubstitute(TEXT Macro , TEXT macro[])
{
扫描该Macro分离出该Macro的参数TEXT parameter[…](如果有的话);
if(该Macro不被#和##修饰)
Macro=为其定义的宏; //参数还没有展开,只针对宏体
else
return Macro; //如果被修饰则不对它展开直接返回
for(对该Macro的参数进行遍历 : i=0 -> N)
if(parameter[i]存在于macro[]中)
parameter[i]=MacroSubstitute(parameter[i],macro); //对参数进行展开,递归调用宏替换程序
if(Macro在macro[]中) //被展开的宏体仍然是宏
Macro(…)=Macro(parameter[0],parameter …);//用已经展开的参数替换原来的参数形成新的宏
return MacroSubstitute(Macro,macro); //最后把这个新宏再按照宏替换的方式展开返回
}
最后总结一下吧:
标准没有规定替换的具体实现,只是提到了一些需要注意的规则,所以替换的具体实现在不同的编译器下是不同的,目前为止,GNU和微软的编译器处理办法就是我上面所说的那样,算是标准的一种实现吧。
问题是解决了,但是建议这样的宏尽量不要用,现在已经迈入标准C++(www.cppentry.com)的时代了还是用const和inline吧