首先声明一下do...while语句的原型:(注意最后位置需要一个分号,这个特性带来一些好处

do{
    /*循环体*/
}
while(condition);

如果你是C++程序员,我有理由相信你用过,或者接触过,至少听说过MFC, 在MFC的afx.h文件里面, 你会发现很多宏定义都是用了do…while(0)或do…while(false), 比如说:#define AFXASSUME(cond) do { bool __afx_condVal=!!(cond); ASSERT(__afx_condVal); __analysis_assume(__afx_condVal); } while(0) 
粗看我们就会觉得很奇怪,既然循环里面只执行了一次,我要这个看似多余的do…while(0)有什么意义呢? 

 

为什么需要使用do...whie(0),我们都知道do{...}while(condition)可以表示循环,但我们会遇见一些宏定义中可以不用循环的地方,使用到了do{...}while(0)。
ex1:

#define foo(x) do{\\
    statement1;\\
    statement2;\\
}while(0)

#define foo(x) do{ statement1; statement2;}while(0)


在初次遇见这样的宏定义的过程中会觉得比较奇怪,既然循环里面的语句只执行了一次,为什么会需要看似多余的do...while(0)有什么意义。我们为什么不直接写出如下表达。
ex2:
#define foo(x)

    statement1;\\
    statement2;\\
}

#define foo(x){ statement1;statement2;}

ex3:(不带大括号,是错误的。)

#define foo(x)  statement1;statement2;


我们都知道宏在预处理的过程中会被直接展开

对于上面的ex2如果按照如下常规调用:

//调用语句

if(condition)
    Foo(x)
else
...

//展开语句
if(condition)
{
    statement1;
    statement2;
};
else
...

显然:else上面有一个分号是会报错的。VS中对else报错,提示“应输入一个语句”。

既然如此在调用的时候我不加上分号不就好了吗?答案是:这样的确是可以的。这样就变成了

//调用语句

if(condition)
    Foo(x)
else
...

//展开后的效果

if(condition)
{
    statement1;
    statement2;
}
else
...

只不过,这样不带分号不符合C语言的书写习惯,而且会给初学者一种错觉,应该避免才是。

然而采用ex1所示的宏定义就完全没有这种顾虑。因为如下:

//调用语句

if(condition)
    Foo(x);
else
...

//展开语句

if(condition)
do{
    statement1;
    statement2;
}while;
else

...

其实这种看起来的巧妙效果的实现非常简单。那就是do...while语句在while末尾处一定需要一个分号配对才可以。这样正好就把调用处的分号用于do...while语句的配对上了。既满足了do...while语句的语法需求,又在形式上保持了C语言调用语句出有一个分号的形式。

 

另外上述ex3形式是错误的,见如下例子:
#define Foo(x) (x)+=1;(x)+=1;

if(condition)
    Foo(x)
else
...
会被展开成
if(condition)
    (x)+=1;
    (x)+=1;
else
...
显然这样会被导致else语句孤立而出现编译错误。


总结:通过do{...}while(0)我们使得宏能够被正确的展开,保留原始的语义,从而保证程序的正确性。

收藏 打印