最近在公司实习,看后台服务器的代码比较多,见识了很多东西的妙用,比如说do-while
循环的用法。
关于do-while
与while
,for
的区别我就不多说了,其中要明确的一点是do-while
循环是一定会执行一遍循环体。
利用do-while
循环是一定会执行一遍循环体的这个特点,可以完成两个功能:封装代码块、提供代码块入出口。下面细细来说。
封装代码块
这个东西主要体现在宏定义中,如果你经常看Linux内核就会发现类似下面的用法:
#define SWAP(a, b) \
do { \
int tmp = (a); \
(a) = (b); \
(b) = tmp; \
} while(0)
这个里面的\
是表示下一行和当前行属于同一个部分,最后一条语句不要加;
,因为调用的时候SWAP(a, b);
里面的;
会加在最后一条语句上。这就相当于用宏完成了一个函数,如果不用do_while
循环把这三行语句封装成一个代码块的话会出现很多问题。即
#define SWAP(a, b) \
int tmp = (a); \
(a) = (b); \
(b) = tmp
首先,如果这个SWAP
用到一个没有加{}
的for
循环中,就会出现问题,你可以试着展开下:
for (int i = 0; i < n; i++)
// SWAP(a, b);
int tmp = (a);
(a) = (b);
(b) = tmp;
这段代码只有int tmp = (a);
执行了n
次,故不对。用do-while
循环包裹就不会出现这中问题。
其次,如果用do-while
在宏中封转代码块还可以解决定义变量时的重命名问题、作用域问题。如果不用do-while
循环,在宏中定义的变量的作用域就是宏调用时所处的代码块,故这个时候如果该代码块之前定义了tmp
这个变量,那么就会出现重定义问题;而如果用了do-while
,do-while
中自成一块域,故在宏中定义的变量,在do-while
循环跳出时就超出作用域了,所以可以放心地在宏中定义变量。
for (int i = 0; i < n; i++) {
int tmp = 5;
// SWAP(a, b);
// do
int tmp = (a);
(a) = (b);
(b) = tmp;
// while (0);
}
提供代码块入出口
先来个代码,看看不用do-while
循环的样子。
void work1()
{
gg();
// start
if (condition1) {
deal1;
} else if (condition2) {
deal2;
} else if (condition3) {
deal3;
}
// end
gg();
aa();
}
这个代码中要通过3个互斥的判断条件才能执行下一步,里面使用了多了if - else
使得代码不那么直观。因此,我们可以通过goto
语句来解决这个问题。
void work2()
{
gg();
// start
if (condition1) {
deal1;
goto OUT;
if (condition2) {
deal2;
goto OUT;
if (condition3) {
deal3;
goto OUT;
}
// end
OUT:
gg();
aa();
}
现在就清晰多了,不过使用了很多的goto
语句,这是不提倡的,因此,现在用do-while
来封装一下。
void work3()
{
gg();
// start
do {
if (condition1) {
deal1;
break;
if (condition2) {
deal2;
break;
if (condition3) {
deal3;
break;
}
while (0);
// end
gg();
aa();
}
这个才是最清晰的写法,用break
跳出代码块代替goto
,且3个条件判断语句还有缩进。
正是break
提供了代码块的出口(出口在while (0);
的后一条语句),这也是break
加do-while
的一个局限吧,因为代码块的出口是固定的,而goto
提供的出口是任意的。
do-while
去掉了很多的if-else
和goto
语句。
总结
do-while
第一个功能是很明显的,也是很有道理的,第二个功能可能是仁者见仁智者见智了,我是觉得do-while
比if-else
和goto
更直观。这只是编程的技巧,选择你喜欢的技巧去用吧。