是否需要“do {…} while()”循环?

Bjarne Stroustrup(C ++创build者)曾经说过,他避免了“do / while”循环,而倾向于用“while”循环来编写代码。 [见下面的报价。]

既然听到了,我就发现这是真的。 你怎么看? 是否有一个例子,“do / while”比使用“while”更清晰,更容易理解?

回答一些答案:是的,我了解“do / while”和“while”之间的技术差异。 这是涉及循环的可读性和结构化代码的更深层次的问题。

让我以另外一种方式问:假设你被禁止使用“do / while” – 是否有一个现实的例子,在这种情况下你会别无select,只能用“while”来写不干净的代码?

从“C ++编程语言”6.3.3:

根据我的经验,这个声明是错误和混乱的根源。 原因是它的身体总是在条件评估之前执行一次。 但是,为了身体正常工作,非常像条件的东西必须在第一次通过时才能保持。 比我想象的更多的时候,我发现这个条件不是在程序第一次被编写和testing的时候,或者是在它之前的代码被修改之后的那个时候。 我也更喜欢“前面我能看到它”的条件。 因此,我倾向于避免这样做。 -Bjarne

是的,我同意while循环可以重写为while循环,但我不同意总是使用while循环更好。 做总是至less运行一次,这是一个非常有用的属性(最典型的例子是input检查(从键盘))

#include <stdio.h> int main() { char c; do { printf("enter a number"); scanf("%c", &c); } while (c < '0' || c > '9'); } 

这当然可以重写为一个while循环,但这通常被认为是一个更优雅的解决scheme。

do-while是一个带有后置条件的循环。 在循环体至less被执行一次的情况下,你需要它。 对于需要进行某些操作的代码,在可以合理评估循环条件之前,这是非常必要的。 使用while循环,您必须从两个站点调用初始化代码,而您只能从一个站点调用它。

另一个例子是当第一次迭代开始时你已经有了一个有效的对象,所以你不想在第一次迭代开始之前执行任何事情(包括循环条件评估)。 一个例子是FindFirstFile / FindNextFile Win32函数:调用FindFirstFile,它返回一个错误或search句柄到第一个文件,然后调用FindNextFile,直到它返回一个错误。

伪代码:

 Handle handle; Params params; if( ( handle = FindFirstFile( params ) ) != Error ) { do { process( params ); //process found file } while( ( handle = FindNextFile( params ) ) != Error ) ); } 

do { ... } while (0)是一个macros的重要构造,performance良好。

即使在实际代码中并不重要(我并不一定同意),但对于解决预处理器的一些缺陷是非常重要的。

编辑:我碰到了一个情况,今天在我自己的代码中干净得多了。 我正在对配对的LL / SC指令进行跨平台的抽象。 这些需要在循环中使用,如下所示:

 do { oldvalue = LL (address); newvalue = oldvalue + 1; } while (!SC (address, newvalue, oldvalue)); 

(专家们可能会意识到在SC实现中oldvalue是未被使用的,但是它被包含在内,以便CAS可以模拟这个抽象。)

LL和SC是一个很好的例子,在这种情况下,do / while明显比同等的performance更清晰:

 oldvalue = LL (address); newvalue = oldvalue + 1; while (!SC (address, newvalue, oldvalue)) { oldvalue = LL (address); newvalue = oldvalue + 1; } 

因为这个原因,我对Google Go已经select删除do-while构造感到非常失望。

当你想“做”某些事情时“有用”,直到满足条件。

它可以像这样在一个while循环中被隐藏起来:

 while(true) { // .... code ..... if(condition_satisfied) break; } 

(假设你知道两者的区别)

做/虽然在你的条件被检查和运行while循环之前,对引导/预初始化代码是有好处的。

在我们的编码习惯中

  • 如果/当/ …条件没有副作用和
  • variables必须被初始化。

所以我们几乎从来没有do {} while(xx)因为:

 int main() { char c; do { printf("enter a number"); scanf("%c", &c); } while (c < '0' || c > '9'); } 

改写为:

 int main() { char c(0); while (c < '0' || c > '9'); { printf("enter a number"); scanf("%c", &c); } } 

 Handle handle; Params params; if( ( handle = FindFirstFile( params ) ) != Error ) { do { process( params ); //process found file } while( ( handle = FindNextFile( params ) ) != Error ) ); } 

改写为:

 Params params(xxx); Handle handle = FindFirstFile( params ); while( handle!=Error ) { process( params ); //process found file handle = FindNextFile( params ); } 

下面这个常见的成语对我来说很直接:

 do { preliminary_work(); value = get_value(); } while (not_valid(value)); 

避免重写似乎是:

 value = make_invalid_value(); while (not_valid(value)) { preliminary_work(); value = get_value(); } 

第一行用于确保第一次testing始终评估为真 。 换句话说,第一次testing总是多余的。 如果这个多余的testing不在那里,也可以省略最初的任务。 这段代码给人的印象是它自己打架。

在这样的情况下, do构造是非常有用的select。

这都是关于可读性的
更可读的代码可以减less代码维护的难度,并且更好的协作。
其他考虑因素(如优化)在大多数情况下并不重要。
我会详细说明,因为我在这里得到了一个评论:
如果你有一个使用do { ... } while()的代码片段A ,并且它比while() {...}等价的B更可读,那么我会投票给A。 如果你更喜欢B ,由于你看到循环条件“前面”, 并且你认为它更具可读性 (因而可维护等),那么就直接使用B。
我的观点是:使用更易读的代码(和你的同事)。 当然,这个select是主观的。

我认为这只是个人select。

大多数时候,你可以find一种方法来重写do … while循环到while循环; 但不一定总是。 另外,编写do while while循环可能会更符合逻辑,以适应您所处的上下文。

如果你从上面看,TimW的回答,这本身就说明了一切。 第二个与Handle,尤其是在我看来更乱。

我几乎没有使用它们,只是因为以下几点:

即使循环检查后置条件,仍然需要在循环中检查此后置条件,以便不处理后置条件。

以伪代码为例:

 do { // get data // process data } while (data != null); 

听起来很简单,但在现实世界的情况下,它可能会变成这样:

 do { // get data if (data != null) { // process data } } while (data != null); 

额外的“如果”检查是不值得的海事组织。 我发现了很less的情况下,做一个do-while而不是while循环更简洁。 因人而异。

回应丹麦奥尔森(Dan Olson)的回答:来自未知(谷歌)的问题/评论:

“do {…} while(0)是使macrosperformance良好的重要构造。”

 #define M do { doOneThing(); doAnother(); } while (0) ... if (query) M; ... 

你看到没有do { ... } while(0)会发生do { ... } while(0) ? 它将始终执行doAnother()。

阅读结构化程序定理 。 do {} while()总是可以被重写为while()do {}。 序列,select和迭代都是需要的。

由于循环体中包含的任何东西都可以被封装成一个例程,所以不得不使用while()的肮脏程度永远不会比

 LoopBody() while(cond) { LoopBody() } 

do-while循环总是可以重写为while循环。

是否只使用while循环或while,do-while和for-loops(或它们的任意组合)很大程度上取决于您对美学和您正在进行的项目的惯例的喜好。

就个人而言,我更喜欢while循环,因为它简化了循环不变式恕我直言的推理。

至于是否有什么情况需要do-while循环:而不是

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

你可以随时

 loopBody(); while(condition()) { loopBody(); } 

所以,不,如果你不能出于某种原因,你永远不需要使用do-while。 (当然这个例子违反了DRY,但这只是一个概念certificate,根据我的经验,通常有一种方法是将do-while循环转换为while循环,而不是在任何具体的用例中违反DRY。

“在罗马时,像罗马人一样。”

顺便说一句:你正在寻找的报价可能是这个(6.3.3节的最后一段)[1]:

从我的经验来看,这个声明是错误和混淆的根源。 原因是它的身体总是在条件被testing之前执行一次。 然而,为了身体的正常function,与最终状况相似的条件必须在第一轮中保持。 我比我预期的更频繁地发现这些条件是不正确的。 无论是从头开始编写程序,还是在代码更改之后对其进行testing,都是如此。 另外,我更喜欢“前面,我可以看到它”的条件。 因此,我倾向于避免这样做。

(注意:这是我的德语版的翻译,如果你碰巧拥有这个英文版的话,可以随意修改引用来匹配他原来的措辞,不幸的是,Addison-Wesley恨Google)。

B. Stroustrup: C ++编程语言。 第3版。 Addison-Wessley,Reading,1997。

这是我所见过的最干净的select。 这是推荐用于Python的成语,它没有do-while循环。

一个需要注意的是你不能在<setup code> continue ,因为它会跳转到break状态,但是没有任何一个显示do-while的好处的例子在条件之前需要继续。

 while (true) { <setup code> if (!condition) break; <loop body> } 

这里将其应用于上面的do-while循环的一些最好的例子。

 while (true); { printf("enter a number"); scanf("%c", &c); if (!(c < '0' || c > '9')) break; } 

下一个例子是结构比do更具可读性的情况,而由于条件保持在最接近顶部,所以//get data通常很短,但是//process data部分可能很长。

 while (true); { // get data if (data == null) break; // process data // process it some more // have a lot of cases etc. // wow, we're almost done. // oops, just one more thing. } 

考虑这样的事情:

 int SumOfString(char* s) { int res = 0; do { res += *s; ++s; } while (*s != '\0'); } 

恰巧'\ 0'是0,但我希望你明白这一点。

我的问题与/执行完全是在C中实现的。由于while关键字的重用,它经常会跳出来,因为看起来像是一个错误。

如果while被保留仅用于while循环, do / while被改成do / until或者repeat / until ,我不认为循环(这当然是方便的,并且是正确的方式来编码一些循环)会导致这么麻烦。

关于JavaScript , 我之前就已经讨论过了 ,这也从Cinheritance了这个遗憾的select。

那么也许这可以追溯到几步,但在案件

 do { output("enter a number"); int x = getInput(); //do stuff with input }while(x != 0); 

这将是可能的,但不一定是可读的使用

 int x; while(x = getInput()) { //do stuff with input } 

现在,如果你想使用一个不是0的数字来退出循环

 while((x = getInput()) != 4) { //do stuff with input } 

但是,可读性也有所损失,更不要说在条件内部使用赋值语句被认为是不好的做法,我只想指出,除了指定一个“保留”值来指示它的更紧凑的方法到循环,它是最初的运行。

首先,我确实认为do-while的可读性比while

但令我惊讶的是,经过这么多的答案之后,没有人考虑过为什么要do-while这种语言甚至存在。 原因是效率。

假设我们有一个带有N条件检查的do-while循环,条件的结果取决于循环体。 那么如果我们用一个while循环replace它,我们就会得到N+1条件检查,而额外的检查是毫无意义的。 如果循环条件只包含一个整数值的检查,那么没有什么大不了的,但可以说我们有

 something_t* x = NULL; while( very_slowly_check_if_something_is_done(x) ) { set_something(x); } 

那么循环第一圈的函数调用是多余的:我们已经知道x还没有被设置为任何东西。 那么为什么要执行一些无意义的开销代码

在编写实时embedded式系统时,我经常使用do-while来实现这一目的,在embedded式系统中,条件内的代码相对较慢(检查某些慢速硬件外设的响应)。