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

相关推荐

  1. C++惯用:do...while(0)

    2024-07-15 20:32:02       23 阅读
  2. C++ 中 Pimpl 惯用

    2024-07-15 20:32:02       32 阅读
  3. C++之assert惯用

    2024-07-15 20:32:02       28 阅读
  4. C++ Pimpl惯用(桥接模式特例)智能指针

    2024-07-15 20:32:02       38 阅读
  5. C++惯用: 通过std::decltype来SFINAE掉表达式

    2024-07-15 20:32:02       19 阅读
  6. linux | && 和 &

    2024-07-15 20:32:02       39 阅读
  7. C++】C++中extern

    2024-07-15 20:32:02       21 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-15 20:32:02       66 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-15 20:32:02       70 阅读
  3. 在Django里面运行非项目文件

    2024-07-15 20:32:02       57 阅读
  4. Python语言-面向对象

    2024-07-15 20:32:02       68 阅读

热门阅读

  1. python:罗马数字转整数

    2024-07-15 20:32:02       19 阅读
  2. 如何设置单核测试

    2024-07-15 20:32:02       18 阅读
  3. 模板方法原理与C++实现

    2024-07-15 20:32:02       23 阅读
  4. kmeans.fit_predict 和 kmeans.fit有什么区别

    2024-07-15 20:32:02       20 阅读
  5. C++ --> 类和对象(三)

    2024-07-15 20:32:02       23 阅读
  6. 用Python爬虫能实现什么?得到什么?

    2024-07-15 20:32:02       21 阅读
  7. JVM堆内存的结构,YGC,FGC的原理

    2024-07-15 20:32:02       21 阅读
  8. Spring boot 2.0 升级到 3.3.1 的相关问题 (二)

    2024-07-15 20:32:02       21 阅读
  9. LeetCode题练习与总结:寻找峰值--162

    2024-07-15 20:32:02       17 阅读
  10. Mysql数据库(一)

    2024-07-15 20:32:02       25 阅读