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

相关推荐
guigui_hello4 分钟前
VScode的简单使用
c++·ide·vscode·编辑器
不悔哥19 分钟前
openwrt wsdd模块介绍
linux·c语言·网络·tcp/ip·智能路由器
只想摆烂@24 分钟前
C# winfrom 如何多窗体优雅的回调方法
开发语言·c#
西猫雷婶25 分钟前
python画图|中秋到了,尝试画个月亮(球体画法)
开发语言·python
星迹日27 分钟前
C语言:结构体
c语言·开发语言·经验分享·笔记
会敲代码的小张40 分钟前
设计模式-观察者模式
java·开发语言·后端·观察者模式·设计模式·代理模式
jyan_敬言43 分钟前
虚拟机centos_7 配置教程(镜像源、配置centos、静态ip地址、Finalshell远程操控使用)
linux·运维·服务器·c语言·数据结构·tcp/ip·centos
宗浩多捞44 分钟前
C++设计模式(更新中)
开发语言·c++·设计模式
Smart-Space1 小时前
HtmlRender - c++实现的html生成类
c++·html
学习使我变快乐3 小时前
C++:析构函数
开发语言·c++