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语句

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

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 块内有效,防止了变量名冲突。

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值