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)
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)的用法的更多相关文章
- linux find命令中-print0和xargs中-0的用法
linux find命令中-print0和xargs中-0的用法. 1.默认情况下, find命令每输出一个文件名, 后面都会接着输出一个换行符 ('\n'), 因此find 的输出都是一行一行的: ...
- Cocos2d-x 3.0 Json用法 Cocos2d-x xml解析
Cocos2d-x 3.0 加入了rapidjson库用于json解析.位于external/json下. rapidjson 项目地址:http://code.google.com/p/rapidj ...
- basename $0的用法
basename 从文件名中去掉路径信息, 只打印出文件名. 结构 basename $0 可以让脚本知道它自己的名字, 也就是, 它被调用的名字. 可以用来显示用法信息, 比如如果你调用脚本的时候缺 ...
- (27)Cocos2d-x 3.0 Json用法
Cocos2d-x 3.0 加入了rapidjson库用于json解析.位于external/json下. rapidjson 项目地址:http://code.google.com/p/rapidj ...
- ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]的用法
父类:public class BaseHibernateDaoSupport<T>{ private Class<T> entityClass; public BaseHib ...
- 新浪微博OAuth2.0的用法
最近学习Android开发,照着视频开发新浪微博,但是视频里的介绍的是OAuth1.0的授权方式,试了半天发现用不了. 原来现在一般没审核的用户只能使用OAuth2.0了,视频教学里的方法已经过时了. ...
- href="javascript:void(0)" 的用法
href=”javascript:void(0);”这个的含义是,让超链接去执行一个js函数,而不是去跳转到一个地址,而void(0)表示一个空的方法,也就是不执行js函数. 为什么要使用href=” ...
- vue过滤器在v2.0版本用法
vue 1.x 的写法在 vue 2.x版本已经废除 vue 1.x 写法 <body> <div id="app"> {{message | capit ...
- C语言变长数组 struct中char data[0]的用法
版权声明:本文为博主原创文章,未经博主允许不得转载. 今天在看一段代码时出现了用结构体实现变长数组的写法,一开始因为忘记了这种技术,所以老觉得作者的源码有误,最后经过我深思之后,终于想起以前看过的用s ...
随机推荐
- Noip模拟57 2021.9.20
规律总结:联考必爆炸 T1 2A 没$A$掉的大水题,但是是真的不知道$000$前面的$00$也算先导$0$,以后要长记性,这种东西不能再错了 再打三遍: $000$前面的$00$也算先导$0$ $0 ...
- AOP源码解析:AspectJAwareAdvisorAutoProxyCreator类的介绍
AspectJAwareAdvisorAutoProxyCreator 的类图 上图中一些 类/接口 的介绍: AspectJAwareAdvisorAutoProxyCreator : 公开了Asp ...
- MD5函数(公共方法)
1 #region MD5函数 2 /// <summary> 3 /// MD5函数 4 /// </summary> 5 /// <param name=" ...
- sort-list leetcode C++
Sort a linked list in O(n log n) time using constant space complexity. C++ /** * Definition for sing ...
- tarjan知识点梳理
tarjan在图论中还是挺重要的.这里就简要的梳理一下tarjan的知识点. tarjan算法与无向图连通性. 首先说一下图中割点和桥的定义. 桥:也称割边,定义类似,在无向图中,若去掉某条边,导致整 ...
- 西邮Linux兴趣小组第一次技术分享会
2016年10月30日晚,西邮Linux兴趣小组技术分享会在西安邮电大学长安校区东区逸夫教学楼FF305室成功举办.200多名来自全校不同专业的15,16级同学参加了此次分享会. 分享会于20:00正 ...
- httprunner3源码解读(4)parser.py
源码结构目录 可以看到此模块定义了4个属性和12个函数,我们依次来讲解 属性源码分析 # 匹配http://或https:// absolute_http_url_regexp = re.compil ...
- SpringBoot热部署(7)
1.引入热部署依赖包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId ...
- TestNG 参数化应用
一.第一种参数化方式(testng.xml配置参数) 1.新建ParameterDemo04类 2.拷贝类的路径 3.testng.xml配置类地址及参数 <?xml version=" ...
- Qt 实时显示系统时间
前言 我们用一个label控件来实时显示系统时间,用到 QTimer 和 QDateTime 这个两个类. 正题 头文件: #ifndef MAINWINDOW_H #define MAINWINDO ...