1

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

 2 years ago
source link: https://www.leftpocket.cn/post/cpp/do_while/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

2021-11-22 约 1827 字 预计阅读 4 分钟 17 次阅读

​​ 原文地址:码农在新加坡的个人博客

第一次见到 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 的语法,不过现实开发中,用 forwhile 的比较多,do{...}while() 比较少了,算是比较不常用的语法。 但是在这里,这样的代码一看就不是一个循环,do{...}while()表面上在这里一点意义都没有,那么为什么要这么用呢?特别疑惑的google之,恍然大悟,原来 do{...}while() 还有此等妙用,看来自己还差得远啊。

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

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

大家做c语言题目的时候,一道必考题就是 #define 的算术运算。

比如,我随手写一个最简单的 #define

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

result输出多少?  26? 错!

这是c语言新手一定会犯的错误,至少我上大学的时候第一次看到这,我就做错了。

要知道这道题答案是多少,首先就要知道 #define 的作用。

  • #define M (a+b) 它的作用是指定标识符M来代替表达式(a+b)。在编写源程序时,所有的(a+b)都可由M代替,而对源程序作编译时,将先由预处理程序进行宏代换,即用(a+b)表达式去置换所有的宏名M,然后再进行编译。
  • 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.

但是事实上:

send:
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)不满足,自己跳出去。

<全文完>

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

pic


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK