C/C++为什么要开发do...while, while, for功能相同的循环结构?

 

喜欢的可以收藏转发加关注

对于较老的标准,这三者可以做等价代换。但是实际上,他们还可以做一些很geek的事,可能有点跑题就附在下面。

先讨论他们如何等价

do-while -> while

do{
 do_something();
}while(condition);

可以转化为

do_something();
while(condition){
 do_something();
}

while -> do-while

while(condition){
 do_something();
}

可以转化为

if(condition){
 do{
 do_something();
 }while(condition);
}

for -> while

for(init-expr;condition;update-expr){
 do_something();
}

可以转化为

init-expr;
while(condition){
 do_something();
 update-expr;
}

--------补--------

后来想到,for循环内部使用continue的时候,仍然是会执行update-expr的,所以如果考虑上这个,可能要这么写

if(condition){
 do_something();
 while(update-expr,condition){
 do_something();
 }
}

-------------------

while -> for 过于简单,略去

本身,这三种语法就是等价、可互相转换的。用的时候大多只是考虑它们的可读性罢了

在较高标准(c++11后),出现了range-based for,如

int a[]={1,2,3,4,5,6,7,8,9,10};
for(auto i:a){
 do_something(i);
}

这种情况下,用for能大大提高可读性并减小犯错概率(还能略微提升性能)

以下为一些比较geek的东西,和题目相关性有点低,感兴趣的话可以看看,也可以和我讨论~

do-while的用途

I. 无返回值的宏

现在考虑你要写一个宏,比如叫做Assert,你希望这个宏执行很多条语句,你可以写成

#define Assert(...) 
 do_something(); 
 do_something_more() 

这样的话,这个宏会在Assert(something) 没加semicolon的时候报错

那现在考虑,如果有人写

a=Assert(something)

是不是会产生非预期的结果呢?

所以如果你希望避免,你可以这样写

#define Assert(...) 
 ( 
 do_something(), 
 do_something_more(), 
 default_return_value )

(用到了逗号表达式)

这样,当有人写

a=Assert(something)

的时候,它就会得到一个默认的返回值。

考虑更多的,如果你不希望Assert(...)能作为rvalue怎么办?

经测试,Linux下可以这么写

#define Assert(...) 
 ( 
 do_something(), 
 do_something_more(), 
 )

即最后一个逗号后留空。而实际上,这似乎不是标准的C所支持的语法。借助do-while,我们其实可以写

#define Assert(...) 
 do{ 
 do_something(); 
 do_something_more(); 
 }while(0)

这样一来,不仅Assert这个宏不能作为rvalue,它后面甚至只能接semicolon而不能跟逗号。

(灵感来源:南京大学计算机系基地班“计算机系统基础”实验代码 Line32-41)

II.更好的跳转

现在考虑,你有一段代码,只执行一次,并且很多个判断失败之后需要退出这一段代码块,你会怎么写呢?

if(condition1){
 if(condition2){
 }
}else{
}

这样写固然可以,那如果要求当condition1满足的时候,做某些事,且做这些事的时候,如果condition2满足,则做完就退出,否则,再做别的事,用goto描述,即如下

if(condition1){
 do_something();
 if(exit_condition1){
 goto finish;
 }
}
if(condition2){
 do_something_more();
 if(exit_condition2){
 goto finish;
 }
}
do_something_more_and_more();
finish:;

或许码量小的时候,用goto还行,但是码量一旦大了,就可能会大大降低可读性。这时,我们可以这样写

do{
 if(condition1){
 do_something();
 if(exit_condition1){
 break;
 }
 }
 if(condition2){
 do_something();
 if(exit_condition2){
 break;
 }
 }
 do_something_more_and_more();
}while(0);

这样写的话,我们甚至可以允许在某些条件达成的时候,回到condition1的判断处重新执行(continue),是不是很酷呢

(此处有误,continue会对条件进行判断,因此对while(0) 而言,continue和break功能相似,感谢评论区知友指出)

同时,另一位知友给出了解决方案

do{
 if(condition1){
 do_something();
 if(exit_condition1){
 break;
 }
 }
 if(condition2){
 do_something();
 if(exit_condition2){
 break;
 }
 }
 if(some_special_condition){
 continue;
 }
 do_something_more_and_more();
 break;
}while(1);

这种方法就可以实现在某些特殊情况下想要重新执行一遍流程的目的了。

当然,还有几种其他方法实现这样的逻辑,比如子函数,但是子函数调用时的参数传递在参数较多时会使得逻辑变得复杂,且子函数会带来较大的开销。

还有一种解决方案,便是C++的lambda函数!上述代码可以写成

[&]{
 if(condition1){
 do_something();
 if(exit_condition1){
 return;
 }
 }
 if(condition2){
 do_something();
 if(exit_condition2){
 return;
 }
 }
 do_something_more_and_more();
}();

[&]的含义为“根据需要,按引用捕获外部的变量”,主要解决了子函数手动传参的麻烦之处。

由于本人对C++编译器不甚了解,这样写的性能开销会更高或更低我不太清楚,如果有人了解,望指点一二,不甚感激。

在考虑另一个情况,你想一次性退出多重循环,要怎么写呢?

for(int i=0;i<100&&flag;++i){
 for(int j=0;j<100&&flag;++j){
 for(int k=0;k<100&&flag;++k){
 if(test(i,j,k)){
 goto finish;
 }
 }
 }
}
finish:;

这种写法,用到了goto

flag=1;
for(int i=0;i<100&&flag;++i){
 for(int j=0;j<100&&flag;++j){
 for(int k=0;k<100&&flag;++k){
 if(test(i,j,k)){
 flag=0;
 }
 }
 }
}

这样写的话,除了每次判断flag带来的些许(实际上很小)性能开销,还需要维护flag,如果这个循环不是普通的顺序执行,而是可能被多种方式进入,flag的维护就需要非常小心

(原本此处的内容希望利用do-while解决该问题,存在错误,现已删除)

用lambda,我们可以写

[&]{
 for(int i=0;i<100&&flag;++i){
 for(int j=0;j<100&&flag;++j){
 for(int k=0;k<100&&flag;++k){
 if(test(i,j,k)){
 return;
 }
 }
 }
 }
}();

学习C/C++的伙伴可以私信回复小编“学习”领取全套C/C++学习资料、视频

C/C++为什么要开发do...while, while, for功能相同的循环结构?