目录
1.引言
在C++中,do...while(0) 通常是用来做循环用的,然而我们做循环操作可能用for和while要多一些。经常看到一些开源代码会出现do...while(0)这样的代码,这样的代码看上去肯定不是用来做循环的,那为什么要这样用呢?下面就讲讲使用它的好处。
2.do...while(0)消除goto语句
通常,如果在一个函数中开始要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,当然,退出前先释放资源,我们的代码可能是这样:
bool Execute()
{
// 分配资源
int *p = new int;
bool bOk(true);
// 执行并进行错误处理
bOk = func1();
if(!bOk)
{
delete p;
p = NULL;
return false;
}
bOk = func2();
if(!bOk)
{
delete p;
p = NULL;
return false;
}
bOk = func3();
if(!bOk)
{
delete p;
p = NULL;
return false;
}
// ..........
// 执行成功,释放资源并返回
delete p;
p = NULL;
return true;
}
这里一个最大的问题就是代码的冗余,而且我每增加一个操作,就需要做相应的错误处理,非常不灵活。于是我们想到了goto:
bool Execute()
{
// 分配资源
int *p = new int;
bool bOk(true);
// 执行并进行错误处理
bOk = func1();
if(!bOk)
goto errorhandle;
bOk = func2();
if(!bOk)
goto errorhandle;
bOk = func3();
if(!bOk)
goto errorhandle;
// ..........
// 执行成功,释放资源并返回
delete p;
p = NULL;
return true;
errorhandle:
delete p;
p = NULL;
return false;
}
代码冗余是消除了,但是我们引入了C++中身份比较微妙的goto语句,虽然正确的使用goto可以大大提高程序的灵活性与简洁性,但太灵活的东西往往是很危险的,它会让我们的程序捉摸不定,那么怎么才能避免使用goto语句,又能消除代码冗余呢? 请看do...while(0)循环:
bool Execute()
{
// 分配资源
int *p = new int;
bool bOk(true);
do
{
// 执行并进行错误处理
bOk = func1();
if(!bOk)
break;
bOk = func2();
if(!bOk)
break;
bOk = func3();
if(!bOk)
break;
// ..........
}while(0);
// 释放资源
delete p;
p = NULL;
return bOk;
}
3.用do...while(0)包裹复杂的宏
假设定义了一个这样的宏:
#define SWAP(a, b) \
int temp = a; \
a = b; \
b = temp;
如果直接在代码中使用 SWAP(x, y);
,可能会导致意料之外的行为,特别是在条件语句中:
if (condition)
SWAP(x, y);
else
// 其他操作
上面的代码将展开为:
if (condition)
int temp = x; x = y; y = temp;
else
// 其他操作
这将导致编译错误,因为 else
部分并没有与 if
关联。为了避免这种问题,可以使用 do-while
包裹:
#define SWAP(a, b) \
do { \
int temp = a; \
a = b; \
b = temp; \
} while (0)
这样展开后,代码将变为:
if (condition)
do { int temp = x; x = y; y = temp; } while (0);
else
// 其他操作
这样就保证了 if
和 else
结构的完整性,不会出现编译错误。
实际案例
为了更好地理解 do-while
包裹宏的好处,让我们看看一个实际的示例。假设我们需要编写一个宏来打印调试信息:
#define DEBUG_PRINT(msg) \
do { \
printf("DEBUG: %s\n", msg); \
} while (0)
在调试模式下,我们可以安全地使用这个宏:
if (debugMode)
DEBUG_PRINT("Debugging information");
else
printf("Normal operation\n");
由于使用了 do-while
包裹,DEBUG_PRINT
宏将安全地作为一个单独的块执行,不会影响 if-else
结构。
do{...}while(0);
在宏定义中的妙用在于它能够确保宏的行为符合预期,特别是在复杂的控制流语句中。它提供了一个清晰的方式来定义宏,使得宏的调用看起来和感觉起来都像是单条语句,无论宏内部包含多少条实际的语句。这种技术有助于提高代码的可读性和可维护性,并减少因宏展开而导致的潜在错误。
4.防止意外错误
如果没有使用do...while(0)
或其他形式的封装,宏展开后的多条语句可能会因为缺少分号、括号不匹配等原因导致编译错误。此外,如果宏被用在需要单条语句的地方,而宏内部有多条语句且没有适当的封装,那么忘记在宏调用后加分号可能会影响到后续的代码。
5.避免变量作用域问题
当你的功能很复杂,变量很多你又不愿意增加一个函数的时候,使用do{}while(0);,将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复。例如:
#define MAX(a, b) \
do { \
int _a = (a); \
int _b = (b); \
(void)((_a > _b) ? _a : _b); \
} while (0)
这里 _a
和 _b
仅在 do-while
块内有效,防止了变量名冲突。