首先声明一下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语句孤立而出现编译错误。
见如下具体例子:
#include <iostream>
#include <string>
using namespace std;
#define Foo(x) do{cout<<"hello";cout<<" world!";}while(0)
//#define Foo(x) cout<<"hello";cout<<" world!";
int main() {
if (true == 1)
Foo(2);
else
cout << "run else" << endl;
}
总结:通过do{...}while(0)我们使得宏能够被正确的展开,保留原始的语义,从而保证程序的正确性。