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

时间:2009-06-15 07:44:28

标签: 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

19 个答案:

答案 0 :(得分:86)

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

#include <stdio.h>

int main() {
    char c;

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

    } while (c < '0' ||  c > '9'); 
}

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

答案 1 :(得分: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 ) );
}

答案 2 :(得分:17)

do { ... } while (0)是使宏表现良好的重要构造。

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

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

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

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

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

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

出于这个原因,我对Google Go的opted to remove the do-while construct感到非常失望。

答案 3 :(得分:7)

当你想“做”某事“直到”满足条件时,这是有用的。

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

while(true)
{

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

    if(condition_satisfied) 
        break;
}

答案 4 :(得分:6)

在我们的编码惯例中

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

所以我们几乎从来没有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 );
}

答案 5 :(得分:6)

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

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

避免do的重写似乎是:

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

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

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

答案 6 :(得分:6)

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

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

答案 7 :(得分:5)

关于可读性的全部内容 更易读的代码可以减少代码维护和更好协作的麻烦 到目前为止,其他考虑因素(例如优化)在大多数情况下并不那么重要 我会详细说明,因为我在这里发表评论:
如果您有使用do { ... } while()的代码段 A ,并且它比while() {...}等效 B 更具可读性,那么我会投票给< EM> A 的。如果您更喜欢 B ,因为您看到“预先”循环条件,并且您认为它更具可读性(因此可维护等) - 然后继续前进,使用 B
我的观点是:使用对您的眼睛(以及您的同事)更具可读性的代码。当然,选择是主观的。

答案 8 :(得分:3)

在我看来,这只是个人选择。

大多数情况下,您可以找到一种方法将do ... while循环重写为while循环;但不一定总是。另外,有时候编写一个do while循环以适应你所处的上下文可能更具逻辑性。

如果你看一下TimW的回复,它说明了一切。第二个是Handle,在我看来特别麻烦。

答案 9 :(得分:3)

这是我所见过的最干净的选择。这是推荐用于Python的习惯用法,它没有do-while循环。

有一点需要注意的是,你不能在continue中拥有<setup code>,因为它会跳过休息状态,但是没有一个显示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-while更可读的情况,因为条件保持在顶部附近,因为//get data通常很短但//process data部分可能很长。 / p>

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.
} 

答案 10 :(得分:2)

回答来自未知(谷歌)的问题/评论以及Dan Olson的回答:

“do {...} while(0)是使宏运行良好的重要构造。”

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

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

答案 11 :(得分:2)

我几乎没有因为以下因素而使用它们:

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

获取示例伪代码:

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

理论上听起来很简单,但在现实世界的情况下,它可能看起来像这样:

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

额外的“如果”检查是不值得的IMO。我发现很少有实例做一个do-while而不是while循环。 YMMV。

答案 12 :(得分:2)

阅读Structured Program Theorem。 do {} while()总是可以重写为while()do {}。序列,选择和迭代都是需要的。

因为循环体中包含的内容总是可以封装到例程中,所以必须使用while()的肮脏需要永远不会比

更糟糕
LoopBody()
while(cond) {
    LoopBody()
}

答案 13 :(得分:2)

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

是否仅使用while循环,while,do-while和for循环(或其任何组合)在很大程度上取决于您对美学的品味以及您正在进行的项目的惯例。

就个人而言,我更喜欢while循环,因为它简化了关于循环不变量IMHO的推理。

是否存在需要do-while循环的情况:而不是

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

你总是可以

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

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

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

BTW:你要找的报价可能是这个([1],6.3.3节的最后一段):

  

根据我的经验,do-statement是错误和混乱的根源。原因是它的主体总是在测试条件之前执行一次。然而,为了身体的正确运作,最终条件的类似条件必须在第一次运行中保持。比我预期的更多,我发现这些条件并非如此。当我从头开始编写有问题的程序然后测试它以及更改代码之后,就是这种情况。此外,我更喜欢条件“预先,我可以看到它”。因此,我倾向于避免做声明。

(注意:这是我对德语版的翻译。如果您碰巧拥有英文版,请随意编辑报价以符合他原来的措辞。不幸的是,Addison-Wesley讨厌谷歌。)

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

答案 14 :(得分:2)

首先,我同意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);
}

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

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

答案 15 :(得分:1)

考虑这样的事情:

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

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

答案 16 :(得分:1)

/ 我的问题 严格来说是在C中的实现。由于重复使用 while 关键字,经常让人们吵架,因为它看起来像是一个错误。

如果 只为 保留,而 循环且 执行 / ,而 已更改为 执行 / 直到< / em> 重复 / 直到 ,我不认为循环(这是当然,方便和编码某些循环的“正确”方法会造成很多麻烦。

I've ranted before about this in regards to JavaScript,这也继承了C的这个令人遗憾的选择。

答案 17 :(得分:1)

好吧也许这可以追溯到几步,但是在

的情况下
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
}

但同样,可读性也有所下降,更不用说在条件中使用赋值语句被认为是不好的做法,我只是想指出有更简洁的方法来执行它而不是分配“保留”值,表示循环是初始运行。

答案 18 :(得分:0)

我喜欢DavidBožjak的榜样。然而,为了扮演魔鬼的拥护者,我觉得你总是可以将你想要运行的代码至少分解为一个单独的函数,从而实现最可读的解决方案。例如:

int main() {
    char c;

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

    } while (c < '0' ||  c > '9'); 
}

可能会成为这个:

int main() {
    char c = askForCharacter();
    while (c < '0' ||  c > '9') {
        c = askForCharacter();
    }
}

char askForCharacter() {
    char c;
    printf("enter a number");
    scanf("%c", &c);
    return c;
}

(原谅任何不正确的语法;我不是C程序员)