C++惯用法:do...while(0)的妙用

目录

1.引言

2.do...while(0)消除goto语句

3.用do...while(0)包裹复杂的宏

4.防止意外错误

5.避免变量作用域问题


1.引言

在C++中,do...while(0) 通常是用来做循环用的,然而我们做循环操作可能用for和while要多一些。经常看到一些开源代码会出现**do...while(0)**这样的代码,这样的代码看上去肯定不是用来做循环的,那为什么要这样用呢?下面就讲讲使用它的好处。

2.do...while(0)消除goto语句

通常,如果在一个函数中开始要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,当然,退出前先释放资源,我们的代码可能是这样:

cpp 复制代码
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:

cpp 复制代码
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)循环:

cpp 复制代码
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)包裹复杂的宏

假设定义了一个这样的宏:

cpp 复制代码
#define SWAP(a, b) \
    int temp = a; \
    a = b; \
    b = temp;

如果直接在代码中使用 SWAP(x, y);,可能会导致意料之外的行为,特别是在条件语句中:

cpp 复制代码
if (condition)
    SWAP(x, y);
else
    // 其他操作

上面的代码将展开为:

cpp 复制代码
if (condition)
    int temp = x; x = y; y = temp;
else
    // 其他操作

这将导致编译错误,因为 else 部分并没有与 if 关联。为了避免这种问题,可以使用 do-while 包裹:

cpp 复制代码
#define SWAP(a, b) \
    do { \
        int temp = a; \
        a = b; \
        b = temp; \
    } while (0)

这样展开后,代码将变为:

cpp 复制代码
if (condition)
    do { int temp = x; x = y; y = temp; } while (0);
else
    // 其他操作

这样就保证了 ifelse 结构的完整性,不会出现编译错误。

实际案例

为了更好地理解 do-while 包裹宏的好处,让我们看看一个实际的示例。假设我们需要编写一个宏来打印调试信息:

cpp 复制代码
#define DEBUG_PRINT(msg) \
    do { \
        printf("DEBUG: %s\n", msg); \
    } while (0)

在调试模式下,我们可以安全地使用这个宏:

cpp 复制代码
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);,将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复。例如:

cpp 复制代码
#define MAX(a, b) \
    do { \
        int _a = (a); \
        int _b = (b); \
        (void)((_a > _b) ? _a : _b); \
    } while (0)

这里 _a_b 仅在 do-while 块内有效,防止了变量名冲突。

相关推荐
朱一头zcy6 分钟前
C语言复习第9章 字符串/字符/内存函数
c语言
此生只爱蛋9 分钟前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
blammmp16 分钟前
Java:数据结构-枚举
java·开发语言·数据结构
何曾参静谧28 分钟前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
暗黑起源喵34 分钟前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong39 分钟前
Java反射
java·开发语言·反射
Troc_wangpeng40 分钟前
R language 关于二维平面直角坐标系的制作
开发语言·机器学习
努力的家伙是不讨厌的42 分钟前
解析json导出csv或者直接入库
开发语言·python·json
Envyᥫᩣ1 小时前
C#语言:从入门到精通
开发语言·c#
童先生1 小时前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go