26

当我在大学(80 年代中期)学习 CS 时,不断重复的想法之一是始终编写循环测试在顶部(while...)而不是底部(do ...while)环形。这些概念经常得到研究参考的支持,这些研究表明,在顶部测试的循环在统计上比底部测试的循环更可能是正确的。

结果,我几乎总是编写在顶部进行测试的循环。如果它在代码中引入额外的复杂性,我不会这样做,但这种情况似乎很少见。我注意到一些程序员倾向于几乎专门编写在底部进行测试的循环。当我看到如下结构时:

if (condition)
{
    do
    {
       ...
    } while (same condition);
}

或者相反(if在内部while),这让我想知道他们是否真的是这样写的,或者if当他们意识到循环没有处理空情况时他们是否添加了语句。

我已经进行了一些谷歌搜索,但无法找到有关此主题的任何文献。你们(和女孩)如何编写循环?

4

31 回答 31

78

我总是遵循这样的规则,如果它应该运行零次或多次,则在开始时进行测试,如果必须运行一次或多次,则在结束时进行测试。我认为没有任何合乎逻辑的理由使用您在示例中列出的代码。它只会增加复杂性。

于 2008-10-22T00:37:07.933 回答
57

Use while loops when you want to test a condition before the first iteration of the loop.

Use do-while loops when you want to test a condition after running the first iteration of the loop.

For example, if you find yourself doing something like either of these snippets:

func();
while (condition) {
   func();
}

//or:

while (true){
    func();
    if (!condition) break;
}

You should rewrite it as:

do{
    func();
} while(condition);
于 2010-06-22T16:17:13.000 回答
20

不同之处在于 do 循环执行一次“做某事”,然后检查条件以查看它是否应该重复“做某事”,而 while 循环在做任何事情之前检查条件

于 2008-12-24T01:51:37.427 回答
17

避免do/while真的有助于使我的代码更具可读性吗?

不。

如果使用do/while循环更有意义,那么就这样做。如果您需要在测试条件之前执行一次循环体,那么do/while循环可能是最直接的实现。

于 2010-07-31T02:30:47.277 回答
12

如果条件为假,第一个可能根本不会执行。另一个将至少执行一次,然后检查条件。

于 2008-12-24T01:51:52.463 回答
7

为了可读性,在顶部进行测试似乎是明智的。它是一个循环的事实很重要。阅读代码的人应该在尝试理解循环体之前了解循环条件。

于 2008-10-22T00:50:03.227 回答
6

这是我最近遇到的一个很好的真实示例。假设您有许多处理任务(例如处理数组中的元素),并且您希望在每个 CPU 内核的一个线程之间分配工作。必须至少有一个内核才能运行当前代码!所以你可以使用类似的do... while东西:

do {
    get_tasks_for_core();
    launch_thread();
} while (cores_remaining());

它几乎可以忽略不计,但可能值得考虑性能优势:它同样可以编写为标准while循环,但这总是会进行不必要的初始比较,总是会评估true- 在单核上,do-while 条件分支更可预测(总是错误的,而不是标准的交替真/假while)。

于 2010-06-22T16:55:10.983 回答
4

Yaa..its true.. do while will run atleast one time. Thats the only difference. Nothing else to debate on this

于 2010-06-22T16:18:41.970 回答
4

第一个在执行之前测试条件,因此您的代码可能永远不会进入下面的代码。第二个将在测试条件之前执行代码。

于 2008-12-24T01:52:23.790 回答
4

while 循环将首先检查“条件”;如果它是假的,它永远不会“做某事”。但是 do...while 循环将首先“做某事”,然后检查“条件”。

于 2008-12-24T01:53:09.920 回答
4

是的,就像使用 for 代替 while,或使用 foreach 代替 for 一样可以提高可读性。那就是说某些情况需要做while,我同意您将这些情况强制进入while循环是愚蠢的。

于 2010-07-31T02:32:46.480 回答
4

从常用用法的角度思考更有帮助。绝大多数 while 循环很自然地与 一起工作while,即使它们可以与 一起工作do...while,所以基本上你应该在差异无关紧要时使用它。因此,我将使用do...while它在可读性方面提供显着改进的罕见场景。

于 2010-07-31T02:32:53.550 回答
3

两者的用例不同。这不是“最佳实践”问题。

如果您希望循环仅基于条件执行,而不是使用 forwhile

如果您想在任何条件下执行一次操作,然后根据条件评估继续执行此操作。 做..虽然

于 2008-10-22T00:51:46.533 回答
3

For anyone who can't think of a reason to have a one-or-more times loop:

try {
    someOperation();
} catch (Exception e) {
    do {
        if (e instanceof ExceptionIHandleInAWierdWay) {
            HandleWierdException((ExceptionIHandleInAWierdWay)e);
        }
    } while ((e = e.getInnerException())!= null);
}

The same could be used for any sort of hierarchical structure.

in class Node:

public Node findSelfOrParentWithText(string text) {
    Node node = this;
    do {
        if(node.containsText(text)) {
            break;
        }
    } while((node = node.getParent()) != null);
    return node;
}
于 2008-10-22T12:03:27.520 回答
3

while()在每次执行循环体之前检查条件,而do...while()在每次执行循环体之后检查条件。

因此,**do...while()**s 将始终至少执行一次循环体。

在功能上,while() 等价于

startOfLoop:
    if (!condition)
        goto endOfLoop;

    //loop body goes here

    goto startOfLoop;
endOfLoop:

并且 do...while() 相当于

startOfLoop:

    //loop body

    //goes here
    if (condition)
        goto startOfLoop;

请注意,实现可能比这更有效。但是, do...while() 确实比 while() 少了一个比较,因此它稍微快一些。在以下情况下使用 do...while():

  • 知道第一次条件总是为真,或者
  • 即使条件为假,您也希望循环执行一次。
于 2008-12-24T02:15:35.070 回答
3

这是翻译:

do { y; } while(x); 

如同

{ y; } while(x) { y; }

请注意,额外的大括号集适用于您在y. 这些范围必须像在 do-loop 情况下一样保持本地化。因此,do-while 循环至少执行一次它的主体。除此之外,这两个循环是相同的。因此,如果我们将此规则应用于您的代码

do {
    // do something
} while (condition is true);

你的 do-loop 对应的 while 循环看起来像

{
    // do something
}
while (condition is true) {
    // do something
}

是的,您看到 do 循环的相应 while 与您的 while 不同 :)

于 2008-12-24T02:37:38.923 回答
3

我自己更喜欢 do-while 循环。如果条件在循环开始时始终为真,我更愿意在结束时对其进行测试。在我看来,测试条件(除了断言)的全部意义在于人们不知道测试的结果。如果我在顶部看到一个带有条件测试的 while 循环,我倾向于考虑循环执行零次的情况。如果这永远不会发生,为什么不以一种清楚地表明这一点的方式编码呢?

于 2010-07-31T04:28:21.900 回答
3

正如 Piemasons 所指出的,不同之处在于循环是在执行测试之前执行一次,还是先执行测试以使循环体可能永远不会执行。

关键问题是哪个对您的应用程序有意义。

举两个简单的例子:

  1. 假设您正在循环遍历数组的元素。如果数组没有元素,您不想处理第一个零。所以你应该使用 WHILE。

  2. 您想显示一条消息,接受响应,如果响应无效,请再次询问,直到获得有效响应。所以你总是想问一次。在得到响应之前,您无法测试响应是否有效,因此您必须先遍历循环体一次,然后才能测试条件。您应该使用 DO/WHILE。

于 2010-06-22T17:52:17.327 回答
2

它实际上是为了不同的东西。在 C 中,您可以使用 do - while构造来实现这两种情况(至少运行一次并在 true 时运行)。但是 PASCAL 对每个场景都有重复 - 直到同时,如果我没记错的话,ADA 有另一个结构可以让你在中间退出,但当然这不是你要问的。我对您的问题的回答:我喜欢在顶部进行测试的循环。

于 2008-10-22T00:50:28.423 回答
2

如果您知道如何正确编写代码,这两种约定都是正确的:)

通常使用第二个约定(do {} while())是为了避免在循环之外有重复的语句。考虑以下(过度简化)示例:

a++;
while (a < n) {
  a++;
}

可以更简洁地使用

do {
  a++;
} while (a < n)

当然,这个特定的例子可以写成更简洁的方式(假设 C 语法)

while (++a < n) {}

但我想你可以在这里看到重点。

于 2008-12-24T02:40:02.087 回答
2
while( someConditionMayBeFalse ){

// this will never run...

}


// then the alternative

do{

// this will run once even if the condition is false

while( someConditionMayBeFalse );

区别很明显,允许您运行代码,然后评估结果以查看是否必须“再做一次”,而另一种 while 方法允许您在不满足条件时忽略脚本块。

于 2012-02-10T00:34:25.800 回答
1

这真的取决于有些情况你想在顶部进行测试,有些情况下你想在底部进行测试,还有一些情况你想在中间进行测试。

然而,给出的例子似乎很荒谬。如果您要在顶部进行测试,请不要使用 if 语句并在底部进行测试,只需使用 while 语句,这就是它的用途。

于 2008-10-22T00:56:41.593 回答
1

我几乎只在顶部编写我的测试。它的代码更少,所以至少对我来说,搞砸的可能性更小(例如,复制粘贴条件会使您总是需要更新两个地方)

于 2008-10-22T00:36:45.643 回答
1

您应该首先将测试视为循环代码的一部分。如果测试在逻辑上属于循环处理的开始,则它是循环顶部测试。如果测试逻辑上属于循环的末尾(即它决定循环是否应该继续运行),那么它可能是循环底部测试。

如果测试在逻辑上属于它们中间,你将不得不做一些花哨的事情。:-)

于 2008-10-22T01:50:26.960 回答
1

我猜有些人在底部进行测试,因为 30 年前这样做可以节省一个或几个机器周期。

于 2009-06-15T12:34:21.077 回答
1

要编写正确的代码,基本上需要进行心理上的,也许是非正式的正确性证明。

要证明循环正确,标准方法是选择循环不变量和归纳证明。但是跳过复杂的词:你所做的,非正式地,找出循环的每次迭代都是真的,当循环完成时,你想要完成的现在是真的。循环不变量最后为假,循环终止。

如果循环条件很容易映射到不变量,并且不变量位于循环的顶部,并且通过处理循环的代码推断该不变量在循环的下一次迭代中为真,那么很容易找出循环是正确的。

但是,如果不变量位于循环的底部,那么除非您在循环之前有一个断言(一个好的做法),否则它会变得更加困难,因为您必须从本质上推断该不变量应该是什么,并且任何代码在循环之前运行使循环不变为真(因为没有循环前提条件,代码将在循环中执行)。即使它是非正式的头脑中的证明,证明正确也变得更加困难。

于 2009-06-15T12:34:52.150 回答
1

这并不是一个真正的答案,而是我的一位讲师所说的话的重申,当时我很感兴趣。

这两种循环 while..do 和 do..while 实际上是第三个更通用的循环的实例,它在中间的某个地方进行了测试。

begin loop
  <Code block A>
  loop condition
  <Code block B>
end loop

代码块 A 至少执行一次,B 执行零次或多次,但不会在最后一次(失败)迭代中运行。while 循环是当代码块 a 为空时,而 do..while 是当代码块 b 为空时。但是,如果您正在编写编译器,您可能会对将这两种情况推广到这样的循环感兴趣。

于 2009-06-15T12:39:27.240 回答
0

在计算机科学的典型离散结构课程中,很容易证明两者之间存在等价映射。

从风格上讲,我更喜欢 while (easy-expr) { } 当easy-expr 预先知道并准备就绪时,并且循环没有很多重复的开销/初始化。我更喜欢 do { } while (somewhat-less-easy-expr); 当有更多的重复开销并且提前设置条件可能不是那么简单时。如果我写一个无限循环,我总是使用 while (true) { }。我无法解释为什么,但我就是不喜欢为 (;;) { } 写作。

于 2008-12-24T02:32:04.490 回答
0

根据我对代码生成的有限知识,我认为编写底部测试循环可能是一个好主意,因为它们使编译器能够更好地执行循环优化。对于底部测试循环,保证循环至少执行一次。这意味着循环不变代码“支配”了出口节点。因此可以在循环开始之前安全地移动。

于 2010-05-27T20:25:16.893 回答
0

我会说编写 if..do..while 循环是不好的做法,原因很简单,这会增加代码的大小并导致代码重复。代码重复很容易出错,应该避免,因为对一个部分的任何更改也必须在重复上执行,但情况并非总是如此。此外,更大的代码意味着 CPU 缓存上的时间更难。最后,它处理空情况,解决头痛问题。

只有当第一个循环根本不同时,才应该使用 do..while,例如,如果让您通过循环条件的代码(如初始化)在循环中执行。否则,如果确定该循环永远不会落在第一次迭代中,那么是的,do..while 是合适的。

于 2009-06-15T13:01:05.193 回答
0

通常,这取决于您如何构建代码。正如有人已经回答的那样,某些算法至少需要执行一次迭代。因此,为了逃避额外的迭代计数或至少一个交互发生的标志 - 你使用 do/while。

于 2010-07-31T05:58:33.817 回答