是否需要"do {...} while()"循环?

Dus*_*ell 69 c c++ loops

Bjarne Stroustrup(C++创建者)曾说过他避免使用"do/while"循环,而更喜欢用"while"循环编写代码.[见下面的引文.]

听到这个,我发现这是真的.你的想法是什么?是否有一个例子,"do/while"比使用"while"更清晰,更容易理解?

回答一些答案:是的,我理解"do/while"和"while"之间的技术差异.这是一个关于可读性和构造涉及循环的代码的更深层次的问题.

让我问另一种方式:假设你被禁止使用"do/while" - 是否有一个现实的例子,这会让你别无选择,只能使用"while"编写不干净的代码?

从"The C++ Programming Language",6.3.3:

根据我的经验,do-statement是错误和混乱的根源.原因是它的主体总是在评估条件之前执行一次.然而,为了使身体正常工作,非常像条件的东西必须在第一次通过时才能保持.比我想象的更频繁,我发现在程序第一次编写和测试时,或者在修改之前的代码之后,条件不能按预期保持. 我也更喜欢条件"我可以看到它的前方".因此,我倾向于避免做声明.-Bjarne

Dav*_*jak 89

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

#include <stdio.h>

int main() {
    char c;

    do {
        printf("enter a number");
        scanf("%c", &c);

    } while (c < '0' ||  c > '9'); 
}
Run Code Online (Sandbox Code Playgroud)

这当然可以重写为while循环,但这通常被视为更优雅的解决方案.

  • @Dustin,IMO你的反例不太可读,因为你使用了幻数(-1).因此,就"更好"的代码而言(当然这是主观的),我认为@ Rekreativc的原始代码更好,因为它对*human*眼睛更具可读性. (45认同)
  • 我不确定我会称它为"魔术"数字,但无论如何,你可以将它初始化为"char c ='0' - 1;" 如果那更清楚.也许你真正抵制的是'c'具有初始值的事实?我同意它有点奇怪,但另一方面,'while'很好,因为它预先说明了延续条件,所以读者立即理解循环的生命周期. (8认同)
  • @Dustin:重写的重要缺点是你需要在`char`域中保留一个元素(这里是-1)作为特殊标记.虽然这不会影响这里的逻辑,但通常它意味着您不再能够处理任意字符流,因为它可能包含值为-1的字符. (8认同)
  • 我的反对意见是肯定的,原则上,"c"根本不应具有可观察到的初始值.这就是为什么我更喜欢do-while版本.(虽然我认为你对现实世界的可读性有一点看法.) (5认同)
  • 这是“while”版本: char c = -1; while (c &lt; '0' || c &gt; '9') { printf(...); 扫描(...);} (3认同)
  • “char”的符号是实现定义的,因此使用幻数 -1 并不明智,原因有很多,它可能是一个潜在的错误。您不应尝试在无符号变量中存储负数。 (2认同)

sha*_*oth 38

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

另一个例子是当你要启动第一次迭代时已经有一个有效的对象,所以你不希望在第一次迭代开始之前执行任何操作(包括循环条件评估).一个例子是FindFirstFile/FindNextFile Win32函数:你调用FindFirstFile,它会向第一个文件返回一个错误或一个搜索句柄,然后你调用FindNextFile直到它返回一个错误.

伪代码:

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

  • 一个先决条件?如果在一次执行身体后检查条件不是后置条件?:) (8认同)
  • 这是一个我认为更具可读性(并且嵌套更少)的重写句柄handle = FindFirstFile(params); while(句柄!=错误){process(params); handle = FindNextFile(params); } (8认同)
  • @Dustin:它也可以这样做(也可能应该)`for(handle = FindFirstFile(params); handle!= Error; handle = FindNextFile(params)){}`. (2认同)

Dan*_*son 17

do { ... } while (0) 是使宏行为良好的重要结构.

即使它在实际代码中并不重要(我不一定同意),但对于修复预处理器的一些缺陷也很重要.

编辑:我遇到了一个情况,在我自己的代码中今天做/更清洁.我正在对配对的LL/SC指令进行跨平台抽象.这些需要在循环中使用,如下所示:

do
{
  oldvalue = LL (address);
  newvalue = oldvalue + 1;
} while (!SC (address, newvalue, oldvalue));
Run Code Online (Sandbox Code Playgroud)

(专家可能会意识到旧的值在SC实现中未被使用,但它包含在内以便可以使用CAS模拟这种抽象.)

LL和SC是一个很好的例子,其中do/while比同等形式更清晰:

oldvalue = LL (address);
newvalue = oldvalue + 1;
while (!SC (address, newvalue, oldvalue))
{
  oldvalue = LL (address);
  newvalue = oldvalue + 1;
}
Run Code Online (Sandbox Code Playgroud)

出于这个原因,我对Google Go 选择删除do-while结构这一事实感到非常失望.

  • 看看<http://c2.com/cgi/wiki?TrivialDoWhileLoop>.预处理器宏不会"处理"C语法,所以这样的聪明黑客是必要的,以防止宏在没有大括号的`if`语句中进行barfing. (5认同)

has*_*sen 7

当你想"做"某事"直到"满足条件时,它是有用的.

它可以像这样在while循环中捏造:

while(true)
{

    // .... code .....

    if(condition_satisfied) 
        break;
}
Run Code Online (Sandbox Code Playgroud)


Suv*_*apa 6

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

在检查条件并运行while循环之前,Do/While适用于引导/预初始化代码.


Tim*_*imW 6

在我们的编码约定中

  • if/while/...条件没有副作用
  • varibles必须初始化.

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

int main() {
    char c;

    do {
        printf("enter a number");
        scanf("%c", &c);

    } while (c < '0' ||  c > '9'); 
}
Run Code Online (Sandbox Code Playgroud)

被改写为:

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

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

被改写为:

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

  • 查看第一个示例中的错误?也许do-while循环会让你避免这种情况.:-) (6认同)
  • 我讨厌用do ... while循环来读代码.就好像有人给你一些你根本不在乎的事情的细节,然后经过15分钟的抨击,他们会告诉你为什么你应该首先关心它.使代码可读.请事先告诉我循环的内容,并且不要让我无缘无故地滚动. (3认同)
  • 大多数程序员似乎没有中间条件的问题(`if(...)break;`),通常甚至与`while(true)`resp组合.`为(;;)`.那么是什么让'做......同时',你至少知道在哪里寻找病情,更糟糕的是? (3认同)

Sva*_*nte 6

以下常见习语对我来说似乎非常简单:

do {
    preliminary_work();
    value = get_value();
} while (not_valid(value));
Run Code Online (Sandbox Code Playgroud)

要避免的重写do似乎是:

value = make_invalid_value();
while (not_valid(value)) {
    preliminary_work();
    value = get_value();
}
Run Code Online (Sandbox Code Playgroud)

第一行用于确保测试始终在第一次评估为true.换句话说,第一次测试总是多余的.如果没有这个多余的测试,也可以省略初始分配.此代码给人的印象就是它会自己打架.

在像这样的情况下,do构造是一个非常有用的选择.


Lun*_*din 6

首先,我确实同意 其do-while可读性低于while.

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

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

something_t* x = NULL;

while( very_slowly_check_if_something_is_done(x) )
{
  set_something(x);
}
Run Code Online (Sandbox Code Playgroud)

那么循环第一圈中的函数调用是多余的:我们已经知道x尚未设置任何内容。那么为什么要执行一些无意义的开销代码呢?

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

  • @DBFred 但这仍然会带来更差的性能,因为您可能会向循环中的每一圈添加一个额外的分支。 (3认同)
  • 向 while `while (x == null || check(x)) 添加检查。。.`解决了这个问题 (2认同)

Ron*_*ein 5

这都是关于可读性的.
更易读的代码可以减少代码维护和更好的协作.
到目前为止,其他考虑因素(例如优化)在大多数情况下并不那么重要.
我会详细说明,因为我在这里得到了一个评论:
如果你有一个使用的代码片段A,do { ... } while()它比while() {...}同等的B更具可读性,那么我就投票给A了.如果你喜欢,因为你看到的循环条件"前面",你认为这是更具可读性(因此维护等) -那么请便,用.
我的观点是:使用对您的眼睛(以及您的同事)更具可读性的代码.当然,选择是主观的.


dan*_*lmo 5

这是我见过的最干净的 do-while 替代方法。这是推荐用于没有 do-while 循环的 Python 的习惯用法。

一个警告是你不能有continuein ,<setup code>因为它会跳过中断条件,但是显示 do-while 好处的例子都不需要在条件之前继续。

while (true) {
       <setup code>
       if (!condition) break;
       <loop body>
}
Run Code Online (Sandbox Code Playgroud)

在这里,它应用于上述 do-while 循环的一些最佳示例。

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

下一个例子是结构比 do-while 更具可读性的情况,因为条件保持在顶部附近,//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.
} 
Run Code Online (Sandbox Code Playgroud)