零.导引
第一次见到 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)
LOG

cout <<"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)
LOG
else
{
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)不满足,自己跳出去。
---------------------
作者:majianfei1023
来源:CSDN
原文:https://blog.csdn.net/majianfei1023/article/details/45246865
版权声明:本文为博主原创文章,转载请附上博文链接!

do{...}while(0)的用法的更多相关文章

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

    linux find命令中-print0和xargs中-0的用法. 1.默认情况下, find命令每输出一个文件名, 后面都会接着输出一个换行符 ('\n'), 因此find 的输出都是一行一行的: ...

  2. Cocos2d-x 3.0 Json用法 Cocos2d-x xml解析

    Cocos2d-x 3.0 加入了rapidjson库用于json解析.位于external/json下. rapidjson 项目地址:http://code.google.com/p/rapidj ...

  3. basename $0的用法

    basename 从文件名中去掉路径信息, 只打印出文件名. 结构 basename $0 可以让脚本知道它自己的名字, 也就是, 它被调用的名字. 可以用来显示用法信息, 比如如果你调用脚本的时候缺 ...

  4. (27)Cocos2d-x 3.0 Json用法

    Cocos2d-x 3.0 加入了rapidjson库用于json解析.位于external/json下. rapidjson 项目地址:http://code.google.com/p/rapidj ...

  5. ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]的用法

    父类:public class BaseHibernateDaoSupport<T>{ private Class<T> entityClass; public BaseHib ...

  6. 新浪微博OAuth2.0的用法

    最近学习Android开发,照着视频开发新浪微博,但是视频里的介绍的是OAuth1.0的授权方式,试了半天发现用不了. 原来现在一般没审核的用户只能使用OAuth2.0了,视频教学里的方法已经过时了. ...

  7. href="javascript:void(0)" 的用法

    href=”javascript:void(0);”这个的含义是,让超链接去执行一个js函数,而不是去跳转到一个地址,而void(0)表示一个空的方法,也就是不执行js函数. 为什么要使用href=” ...

  8. vue过滤器在v2.0版本用法

    vue 1.x 的写法在  vue 2.x版本已经废除 vue 1.x 写法 <body> <div id="app"> {{message | capit ...

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

    版权声明:本文为博主原创文章,未经博主允许不得转载. 今天在看一段代码时出现了用结构体实现变长数组的写法,一开始因为忘记了这种技术,所以老觉得作者的源码有误,最后经过我深思之后,终于想起以前看过的用s ...

随机推荐

  1. SpringBoot整合Easyexcel操作Excel,闲暇之余,让我们学习更多

    关于封面:晚饭后回自习室的路上 Easyexcel 官方文档 Easyexcel | github 前言 最近也是在写的一个小练习中,需要用到这个.趁着这次就将写个整合的Demo给大家. 希望能够让大 ...

  2. 大型DELETE(删除大量数据)的一种解决方案

    通过执行单条DELETE语句来删除一个大型的数据集会有以下的缺点: 1.DELETE语句的操作要被完整地记录到日志中,这要求在事务日志中要有足够的空间以完成整个事务: 2.在删除操作期间(可能会花费很 ...

  3. HTML基础强化

    1.如何理解HTML? HTML类似于一份word"文档" 描述文档的"结构" 有区块和大纲 2.对WEB标准的理解? Web标准是由一系列标准组合而成.一个网 ...

  4. uni-app使用wx-canvas实现微信小程序上显示地图map和坐标geo

    源码 <template> <view class="echart-box"> <canvas class="ec-canvas" ...

  5. 服务集与AP的配合

    一.实验目的 1)掌握添加无线网络配置 2)掌握配置信道和协议使用并配置在一个天线上同时运行两个服务集,即两个无线网络 二.实验仪器设备及软件 仪器设备:一台AC,两台AP,一台AR,一台LSW 软件 ...

  6. 修改 oracle 数据库的 sys 账号密码,ERROR at line 1: ORA-01034: ORACLE not available

    挺久没有登录的 oracle 数据库,因为公司要求加固密码,登录后修改失败 1.启动数据库的同时启动控制文件.数据文件,提示:cannot mount database in EXCLUSIVE mo ...

  7. Vmware 中 Kali linux 2020 设置共享文件夹

    前言 kali2020已经自带vmware-tools工具,因此,只要是原装的kali2020是不需要继续安装vmhgfs工具的. 过程 vmware 设置共享目录 使用vmware-hgfsclie ...

  8. Fiddler抓包工具简介:(一)认识Fiddler

    认识Fiddler Fiddler是一个http协议调试代理工具,它能够记录并检查所有你的电脑和互联网之间的http通讯,设置断点,查看所有的"进出"Fiddler的数据(指coo ...

  9. Win10-更改c盘下的用户文件夹名

    如果你是win10家庭版,请先升级成专业版 win10家庭版升级到win10专业版 修改用户名称

  10. Go语言核心36讲(Go语言进阶技术十六)--学习笔记

    22 | panic函数.recover函数以及defer语句(下) 我在前一篇文章提到过这样一个说法,panic 之中可以包含一个值,用于简要解释引发此 panic 的原因. 如果一个 panic ...