顶部还是底部的测试循环?(while 与 do while)[关闭]

Test loops at the top or bottom? (while vs. do while) [closed]

提问人: 提问时间:10/22/2008 最后编辑:4 revs, 3 users 100%Ferruccio 更新时间:2/10/2012 访问量:21470

问:

当我在大学里学习计算机科学时(80年代中期),一个不断重复的想法是总是写循环,在顶部(同时...)而不是在底部(做......while) 的循环。这些概念通常得到研究的支持,这些研究表明,在顶部测试的循环在统计学上比底部测试的循环更有可能是正确的。

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

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

或反之(在 中),这让我想知道他们是否真的以这种方式编写它,或者他们是否在意识到循环没有处理 null 情况时添加了语句。ifwhileif

我做了一些谷歌搜索,但找不到任何关于这个主题的文献。你们(和女孩)如何编写循环?

与语言无关 的循环 while-loop do-while

评论

6赞 MusiGenesis 10/22/2008
大约在1997年,我有一位计算机科学教授宣称,你在编程中的两个选择是C或Pascal,你应该选择Pascal,因为它不允许你从循环内部增加循环计数器。他可能是对的,但男孩他错了。
1赞 Ferruccio 6/24/2010
@Jay:我认为这意味着你更有可能通过底部测试环引入环边界误差,因此应该确保它是表达算法的最佳方式。不是完全禁止它们。
1赞 gnovice 7/31/2010
这场辩论的许多重复:stackoverflow.com/questions/224059/...stackoverflow.com/questions/390605/while-vs-do-whilestackoverflow.com/questions/1035229/...stackoverflow.com/questions/3094972/...等。
18赞 Porculus 1/12/2011
哇,这是一个完美的例子,说明为什么不应该合并不精确的重复项。看起来一半的答案最初一定是针对一个完全不同的问题给出的,就像“有什么区别”一样。我差点对他们中的一些人投了反对票,然后才意识到应该失去声誉的不是回答者,而是过于热心的主持人,他们得到了非常好的答案,并将他们转移到一个他们没有解决的不相关的问题上。
2赞 Jay 10/18/2011
什么结构“在统计学上更有可能是正确的”是一个奇怪的标准。重要的是,“对于这个特定问题,什么是正确的?从统计学上讲,我们想要加法的频率高于我们想要乘法的频率。因此,你会说任何时候你需要做算术时,你应该总是使用“+”,永远不要使用“*”,因为“统计上”,加法通常是正确的吗?或者,我去杂货店的次数比去汽车经销商的次数多,因此,每当我离开家时,我都会自动去杂货店——即使我想做的是买一辆新车。

答:

1赞 Chris Bunch 10/22/2008 #1

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

80赞 Brett McCann 10/22/2008 #2

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

评论

0赞 S.Lott 10/22/2008
由于我从未见过“一次或多次”循环,我认为这可以稍微简化一下。也许我只是生活在一个封闭的世界里。
0赞 mafu 6/15/2009
这是唯一有效的答案。
0赞 Will Bradley 1/28/2012
我能想到的唯一有效原因是,如果你需要该入口条件的 else 块(例如,如果集合中没有项目,则做一些完全不同的事情),为什么当你知道它很好时,你还要重新测试进入循环?所以,性能。(假设编译器不优化双重测试。
0赞 Samuel Edwin Ward 10/21/2012
@S.Lott,大多数情况下我见过它 do { emit prompt; read input } while (input is invalid)
7赞 mxcl 10/22/2008 #3

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

评论

0赞 Ates Goral 10/22/2008
虽然不如布雷特·麦肯(Brett McCann)的答案完整,但您的答案并不值得投反对票。我部分同意您的可读性问题。还有其他一些东西,如中断、返回等,它们为在底部测试的循环提供了等量的不确定性。
0赞 None 10/22/2008
我会说如果......做。。。虽然 construct 对程序员来说是糟糕的建议。做...而 construct 是众所周知的,并达到了它的目的。此外,使用此 if..做。。当您两次测试相同的条件时,这是不必要的性能影响。
0赞 Brad Gilbert 10/25/2008
如果它是一个短代码块,我偶尔会使用 。do {} while ()
2赞 Salamander2007 10/22/2008 #4

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

3赞 None 10/22/2008 #5

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

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

如果你想做一次某事,不管条件如何,然后根据条件评估继续做。做。。而

1赞 Cervo 10/22/2008 #6

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

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

1赞 staticsan 10/22/2008 #7

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

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

3赞 Chris Marasti-Georg #8

对于任何想不出理由进行一次或多次循环的人:

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

这同样可以用于任何类型的层次结构。

在类 Node 中:

public Node findSelfOrParentWithText(string text) {
    Node node = this;
    do {
        if(node.containsText(text)) {
            break;
        }
    } while((node = node.getParent()) != null);
    return node;
}

评论

0赞 Yishai 6/15/2009
节点 node = this;while (node != null) { if (node.containsTest(text) break; node = node.getParent(); } 对我来说更具可读性(当然不是在这样的评论中)。
20赞 Andrew G. Johnson 12/24/2008 #9

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

评论

2赞 devios1 12/24/2008
就我个人而言,我很少使用do-while。似乎总有一种方法可以将其表示为 while 循环或另一种循环,所以我经常忘记它的存在。
0赞 UnkwnTech 12/24/2008
我从未使用过它,当他修改其他开发人员的一些代码时,当他使用它时感觉不自然。
0赞 Andrew G. Johnson 12/26/2008
是的,我想不出我使用过 do while 循环的单个实例。
12赞 Uri 12/24/2008 #10

首先,如果条件为 false,则可能根本不执行。另一个将至少执行一次,然后检查conidition。

4赞 Eric Sabine 12/24/2008 #11

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

4赞 JW. 12/24/2008 #12

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

3赞 Artelius 12/24/2008 #13

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

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

从功能上讲,while() 等价于

startOfLoop:
    if (!condition)
        goto endOfLoop;

    //loop body goes here

    goto startOfLoop;
endOfLoop:

和做...while() 等价于

startOfLoop:

    //loop body

    //goes here
    if (condition)
        goto startOfLoop;

请注意,实现可能比这更有效。然而,一个做...while() 确实比 while() 少涉及一次比较,因此它稍微快一些。使用一个 do...while() 如果:

  • 您知道该条件在第一次出现时总是为真,或者
  • 希望循环执行一次,即使条件一开始就为 false。
0赞 plinth 12/24/2008 #14

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

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

3赞 Johannes Schaub - litb 12/24/2008 #15

翻译如下:

do { y; } while(x); 

同上

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

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

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

do 循环的相应 while 循环如下所示

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

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

评论

0赞 Robert Gamble 12/25/2008
另一种也许更好的方式是,“do {y} while (x);”等价于“while (1) { y;if (!x) 中断;}".
2赞 PolyThinker 12/24/2008 #16

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

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

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

可以更简洁地编写使用

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

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

while (++a < n) {}

但我认为你可以看到这里的观点。

1赞 erikkallen #17

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

1赞 Larry Watanabe #18

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

为了证明环路的正确性,标准方法是选择环路不变性和归纳证明。但是跳过复杂的词语:你非正式地所做的是弄清楚循环的每次迭代都是正确的,当循环完成时,你想要完成的事情现在是正确的。循环不变性在末尾为 false,用于终止循环。

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

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

1赞 Greg #19

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

两种类型的循环 while..做和做..而实际上是第三个更通用循环的实例,该循环的测试位于中间的某个地方。

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

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

0赞 Nefzen #20

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

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

0赞 pratik #21

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

58赞 Yacoby 6/23/2010 #22

如果要在循环的第一次迭代之前测试条件,请使用 while 循环。

如果要在运行循环的第一次迭代后测试条件,请使用 do-while 循环。

例如,如果您发现自己在执行以下任一代码段:

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

//or:

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

你应该把它改写为:

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

评论

0赞 ntd 7/31/2010
我经常使用第二个测试用例的变体,因为我需要在检查后做一些事情condition
5赞 Mohit Jain 6/23/2010 #23

雅..这是真的..do while 将至少运行一次。 这是唯一的区别。对此没有什么可争论的了

6赞 AshleysBrain 6/23/2010 #24

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

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

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

3赞 Jay 6/23/2010 #25

正如 Piemason 所指出的,区别在于循环是在执行测试之前执行一次,还是先执行测试,以便循环主体可能永远不会执行。

关键问题是哪个对你的应用有意义。

举两个简单的例子:

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

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

17赞 James McNellis #26

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

不。

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

评论

0赞 James McNellis 7/31/2010
@sje397:执行任何导致最不复杂、最易读的代码。与大多数编码标准一样,这是一个完全主观的衡量标准。至于 do/while 循环“不常见”,我认为只有初学者才会对 do/while 循环感到困惑。
0赞 sje397 7/31/2010
问题是,另一个问题的统计数据(它显示了一些代码,对我来说,显然应该是一个 do/while 循环)似乎表明我认为最可读的内容与大多数人的想法不同。
0赞 James McNellis 7/31/2010
@sje397:一个问题通常有多种可接受的解决方案。就我个人而言,我更喜欢 Jon 建议的 for 循环,但我不认为 while 循环或 do/while 循环一定是不合适的。
4赞 Toby 7/31/2010 #27

是的,就像使用 for 代替 while,或者使用 foreach 代替 for 来提高可读性一样。也就是说,有些情况需要 while 做,我同意你强迫这些情况进入 while 循环是愚蠢的。

4赞 Marcelo Cantos 7/31/2010 #28

从常见用法的角度思考会更有帮助。绝大多数 while 循环非常自然地使用 ,即使它们可以与 一起使用,所以基本上你应该在差异无关紧要的时候使用它。因此,我会在极少数情况下使用它,在这些场景中,它提供了可读性方面的显着改进。whiledo...whiledo...while

3赞 supercat 7/31/2010 #29

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

评论

0赞 Dan J 7/31/2010
+1.我被教导初始化一个变量来控制循环条件为 TRUE,并在条件在第一个循环中始终为 true 的情况下使用 WHILE 循环,回想起来,我想不出任何比仅使用 do-while 更具可读性的方法。
0赞 supercat 8/1/2010
在我看来,不遗余力地使用“while”而不是“due while”似乎是彻头彻尾的愚蠢。我的观点是,我使用“do-while”而不是“while”,即使两者都同样有效。
0赞 Jomu 7/31/2010 #30

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

2赞 Mark #31
while( someConditionMayBeFalse ){

// this will never run...

}


// then the alternative

do{

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

while( someConditionMayBeFalse );

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