首发于微信公众号:【码农在新加坡】,欢迎关注。

个人博客网站:do{...}while(0)的用法

零.导引
第一次见到 do{...}while(0)是在学习libevent的时候,看到里面有很多类似

#define TT_URI(want) do {                      \char *ret = evhttp_uri_join(uri, url_tmp, sizeof(url_tmp));   \tt_want(ret != NULL);                     \tt_want(ret == url_tmp);                 \if (strcmp(ret,want) != 0)                    \TT_FAIL(("\"%s\" != \"%s\"",ret,want));         \} while(0)

当时特别疑惑,do{...}while()不是做循环的吗,类似for,while的语法,不过现实开发中,用for和while的比较多,do{...}while()比较少了,算是比较不常用的语法。
但是在这里,这样的代码一看就不是一个循环,do..while表面上在这里一点意义都没有,那么为什么要这么用呢?特别疑惑的google之,恍然大悟,原来do{...}while()还有此等妙用,看来自己还差得远啊。

总体来说,do{...}while(0)有两种用法。

一.定义宏,实现局部作用域。

1.大家做c语言题目的时候,一道必考题就是 #define的算术运算。
比如,我随手写一个最简单的#define

#define FUNC(x) x*3+4
...
int result = 2 * FUNC(3);

result输出多少?  26?错!
这是c语言新手一定会犯的错误,至少我上大学的时候第一次看到这,我就做错了。
要知道这道题答案是多少,首先就要知道#define的作用。
1).#define M (a+b) 它的作用是指定标识符M来代替表达式(a+b)。在编写源程序时,所有的(a+b)都可由M代替,而对源程序作编译时,将先由预处理程序进行宏代换,即用(a+b)表达式去置换所有的宏名M,然后再进行编译。
2).c语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。(以上两句来自百度百科)

也就是 #define是在预处理的时候进行直接替换!(这句话是这一节的重点)
例如之上的展开就是.
int result = 2 * x * 3 + 4
x用实参3代替就是:
int result = 2 * 3 * 3 + 4 = 22而不是26.

有些人可能说,这些我都知道,这跟do{...}while(0)有什么关系。

其实,我只是为了告诉你,#define使用的时候要特别小心,尤其是#define一个很复杂的逻辑的时候。

我们举个简单的#define的例子:

void print()
{cout<<"print: "<<endl;
}void send()
{cout <<"send: "<<endl;
}#define LOG print();send();int main(){if (false)LOGcout <<"hello world"<<endl;system("pause");return 0;
}

这个代码输出什么?理论上,if(false)里面的代码不会被执行,也就是LOG不会被执行,所以只应该打印出"hello world".

但是事实上:

纳闷?

注意我上面说的一句话:

也就是 #define是在预处理的时候进行直接替换!(这句话是这一节的重点)

也就是说,上面的if(false)...在这里是:

   if (false)print();send();cout <<"hello world"<<endl;

懂了吧。

怎么解决了,有些人马上想到,用{...}把#define 的值括住不就可以了。的确,在这里是可以的。

我们在写代码的时候都习惯在语句右面加上分号,如果在宏中使用{},我们通常会这么写:

#define LOG {print();send();};

当我们的if后面有一个else呢?

就变成了:

   if (false){print();send();};else{cout <<"hello"<<endl;}

这样就会因为if语句后面多加了个;而编译不通过。不要说你说,那我不加;那要是你开发一个大型项目的时候你自己也不知道你自己要不要加;了,你就会被自己给绕晕了,所以统一的规范很重要。

那么来我们的最终版本:do{...}while(0);

#define LOG do{print();send();}while (0);int main(){if (false)LOGelse{cout <<"hello"<<endl;}cout <<"hello world"<<endl;system("pause");return 0;
}

就相当于:

   if (false)do{print();send();}while (0);else{cout <<"hello"<<endl;}cout <<"hello world"<<endl;

用do{...}while(0);包裹住要操作的#define,无论你外面怎么操作,都不会影响#define的操作。妙哉妙哉啊。

二.替代goto.

int dosomething()
{return 0;
}int clear()
{}int foo()
{int error = dosomething();if(error = 1){goto END;}if(error = 2){goto END;}END:clear();return 0;
}

当然这只是一个简单的例子,有些人说,我可以不用goto,在每一个goto调用的地方直接,那么加一个判断,你就要加一条clear(),万一你漏了呢?而且正常情况下,foo里面的if有很多个,你要写很多goto,END里面的逻辑也更复杂。这样就更要小心。

由于goto不符合软件工程的结构化,而且有可能使得代码难懂,所以很多人都不倡导使用,那这个时候就可以用do{}while(0)来进行统一的管理:

int foo()
{do {int error = dosomething();if(error = 1){break;}if(error = 2){break;}} while (0);clear();return 0;
}

是不是看起来好看多了,而且还避免了由于错误导致的严重bug(比如你在clear里面是清理内存的操作,你忘记了写goto,而走不到END里面)。

在do{...}while(0)里面,在任何地方都可以break跳出,然后继续下面的执行逻辑。即使你不写break,也会在执行完一遍do之后,while(0)不满足,自己跳出去。

<全文完>

欢迎关注我的微信公众号:码农在新加坡,有更多好的技术分享。

个人博客网站:do{...}while(0)的用法

do{...}while(0)的用法相关推荐

  1. 在 Flutter 中使用 webview_flutter 4.0 | 基础用法与事件处理

    大家好,我是 17. Flutter WebView 一共写了四篇文章 在 Flutter 中使用 webview_flutter 4.0 | js 交互 Flutter WebView 性能优化,让 ...

  2. a href=javascritp:void(0)的用法

    转自:http://speed-guo.iteye.com/blog/1003496 1. <a href="#"> 点击链接后,页面会向上滚到页首,# 默认锚点为 # ...

  3. while (n-- > 0) 的用法

    while (n-- > 0) 的用法 今天下午做题时遇到了,写篇文章记录一下 while(n- - > 0) 使用后缀递减运算符,它的意思是循环n次,除了最常用的 for (int i ...

  4. GStreamer1.0 工具用法

    1. gst-discoverer 作用:提取文件信息. 用法:gst-discoverer-1.0 /media/boot-animation.mp4 示例: 2. gst-typefind 作用: ...

  5. softmax(a,axis=0)的用法理解 总结

    对于3维度数组 总结axis=0 , 1 ,2 axis=0 沿着 axis=0方向 (可以认为是时间的方向)取每个单元对应元素进行计算softmax()  //通俗理解就是今天8点钟的对应行对应列的 ...

  6. linux find命令中-print0和xargs中-0的用法

    1.默认情况下, find命令每输出一个文件名, 后面都会接着输出一个换行符 ('\n'), 因此find 的输出都是一行一行的: [bash-4.1.5] ls -ltotal 0-rw-r--r- ...

  7. %date:~0,10%用法

    其实很简单: 0,开始位置; 10,取字符的个数; 例如当前时间是: 日期是: %date:~0,10%就是2008-05-29 %time:~0,2%就是14 %time:~3,2%就是13 如果我 ...

  8. C语言变长数组 struct中char data[0]的用法

    摘要:在实际的编程中,我们经常需要使用变长数组,但是C语言并不支持变长的数组.此时,我们可以使用结构体的方法实现C语言变长数组. struct MyData  {  int nLen;  char d ...

  9. Vue2.0基本用法之组件的注册和传值(父子props,插槽,$emit)和学写购物车

    1.Vue2.0的组件注册 组件可以是全局注册和局部注册,全局注册的组件是在其他组件里也可以使用 而局部注册的组件只能在该组件里面使用. <body><div id="ap ...

最新文章

  1. 某程序员大佬北漂16年,从住地下室到身家千万,如今回老家躺平!
  2. android告别篇-对于源码我的一些看法
  3. SpringMVC容器和Spring容器
  4. 云原生的本质_CloudNative
  5. C++构造函数的各种用法全面解析(C++初学面向对象编程)
  6. PyTorch 深度学习:33分钟快速入门——VGG
  7. 从码农到架构师,如何成长为技术领导者?
  8. 常见字符的ASCII码值
  9. VOIP Codec 三剑客之 ISAC/ILBC -- ISAC (2) Low Band Encode 模块
  10. 计算机word排版实训报告,Word排版实训报告
  11. 灵雀云:etcd 集群运维实践
  12. 荣耀MagicOS 7.0正式发布;快手科技2022年第三季度收入同比增长12.9% | 美通企业日报...
  13. gridcontrol 添加行删除行
  14. 苹果ios超级签名源码包java版带分发页面支持安卓合并部署教程
  15. SpriteAtlas
  16. Java后端--67--Springboot的响应式编程
  17. markdown如何打印拼音
  18. System Verilog clocking块
  19. 鹏业软件导入布局图纸
  20. 4月第2周业务风控关注 |互联网信息服务投诉平台正式上线试运行

热门文章

  1. 如何插入文献及交叉引用
  2. 查看pr 值是否劫持方法
  3. OpenGL ES 2.0 for Android教程(六):进入第三维
  4. 计算某日新增用户,及其次日、3日、3日内的留存率
  5. 0x80070002(0x80070002错误代码怎么解决)
  6. 搭建日志服务器 rsyslog
  7. 【HTML5】H5新标签大实例
  8. Jenkin 配置 Gerrit Trigger
  9. silverlight java通信_Silverlight使用JavaSocket连接jabber服务器
  10. 基于python的情感分析案例-python snownlp情感分析简易demo(分享)