do{...}while(0)的用法

欢迎转载,转载请注明原文地址:http://blog.csdn.net/majianfei1023/article/details/45246865

零.导引

第一次见到 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)不满足,自己跳出去。

时间: 2024-10-14 12:29:14

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

新浪微博OAuth2.0的用法

最近学习Android开发,照着视频开发新浪微博,但是视频里的介绍的是OAuth1.0的授权方式,试了半天发现用不了. 原来现在一般没审核的用户只能使用OAuth2.0了,视频教学里的方法已经过时了.于是只好自己研究如何进行微博认证. OAuth2.0的授权过程 说白了,就是请求获取Grant Code→使用Grant Code申请Access Token→以后就使用这个Access Token获取微博的服务,这比OAuth1.0的授权方式简化了不少. 如果不使用别的第三方OAuth认证的库或者

北京PK10平台架设 href=&quot;javascript:void(0)&quot;的用法

href="javascript:void(0)"的用法 href="javascript:void(0)"的理解 href="javascript:void(0);"的含义是,让超链接去执行一个js函数,而不是去跳转到一个地址,而void(0)表示一个空的方法,也就是不执行js函数.北京PK10平台架设 Q-2189563389为什么要使用href="javascript:void(0);" javascript:是伪协议,

PHP set_time_limit(0)函数用法

本文为大家讲解的是php中的set_time_limit函数的用法,set_time_limit函数可以用来控制脚本的执行时间,比如在浏览器中可以控制脚本永不超时,一直执行下去,感兴趣的同学参考下. 需求: 每次我们访问PHP脚本的时候,都是当所有的PHP脚本执行完成后,我们才得到返回结果.如果我们需要一个脚本持续的运行,那么我们就要通过php长连接的方式,来达到运行目的.每个PHP脚本都限制了执行时间,所以我们需要通过 set_time_limit 来设置一个脚本的执行时间为无限长:然后使用

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

Cocos2d-x 3.0 加入了rapidjson库用于json解析.位于external/json下. rapidjson 项目地址:http://code.google.com/p/rapidjson/wiki:http://code.google.com/p/rapidjson/wiki/UserGuide 下面就通过实例代码讲解rapidjson的用法. 使用rapidjson解析json串 引入头文件 1 2 #include "json/rapidjson.h" #inc

(27)Cocos2d-x 3.0 Json用法

Cocos2d-x 3.0 加入了rapidjson库用于json解析.位于external/json下. rapidjson 项目地址:http://code.google.com/p/rapidjson/wiki:http://code.google.com/p/rapidjson/wiki/UserGuide 下面就通过实例代码讲解rapidjson的用法. 使用rapidjson解析json串 引入头文件 #include "json/rapidjson.h" #include

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

1.结构体内存布局(padding) 为了让CPU能够更舒服地访问到变量,struct中的各成员变量的存储地址有一套对齐的机制.这个机制概括起来有两点:第一,每个成员变量的首地址,必须是它的类型的对齐值的整数倍,如果不满足,它与前一个成员变量之间要填充(padding)一些无意义的字节来满足:第二,整个struct的大小,必须是该struct中所有成员的类型中对齐值最大者的整数倍,如果不满足,在最后一个成员后面填充. The following typical alignments are va

深入理解公式{1,0}的用法

一.从右往走进行VLOOKUP 查找KT002叫什么名字 公式:VLOOKUP("KT002",IF({1,0},$H$7:$H$10,$G$7:$G$10),2,FALSE) 公式解释 二.多条件查询 根据"郭靖男",查询出对应的年龄 公式:VLOOKUP("郭靖男",IF({1,0},G7:G10&I7:I10,J7:J10),2,FALSE) 在这个公式中,IF({1,0},G7:G10&I7:I10,J7:J10)的作用是

[zz]linux find命令中-print0和xargs中-0的用法

  默认情况下, find命令每输出一个文件名, 后面都会接着输出一个换行符 ('n'), 因此find 的输出都是一行一行的: [bash-4.1.5] ls -ltotal 0-rw-r--r-- 1 root root 0 2010-08-02 18:09 file1.log-rw-r--r-- 1 root root 0 2010-08-02 18:09 file2.log[bash-4.1.5] find -name '*.log'./file2.log./file1.log 比如用f

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

版权声明:本文为博主原创文章,未经博主允许不得转载. [cpp] view plain copy print? 今天在看一段代码时出现了用结构体实现变长数组的写法,一开始因为忘记了这种技术,所以老觉得作者的源码有误,最后经过我深思之后,终于想起以前看过的用struct实现变长数组的技术.下面是我在网上找到的一篇讲解很清楚的文章. 在实际的编程中,我们经常需要使用变长数组,但是C语言并不支持变长的数组.此时,我们可以使用结构体的方法实现C语言变长数组. struct MyData { int nL