在顶部或底部进行测试循环?(while vs. do while)

28

我在大学学习计算机科学时(80年代中期),经常听到一个观点,即始终编写在循环顶部进行测试的循环(while...),而不是在底部进行测试的循环(do ... while)。这些观点通常会引用研究结果来支持,表明在顶部进行测试的循环比底部测试的循环更有可能正确。

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

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

对于反向的情况(ifwhile内),我想知道他们是否确实是以这种方式编写的,还是当他们意识到循环不能处理空情况时,才添加了if语句。

我已经搜索了一些资料,但没有找到关于这个主题的文献。你们如何编写循环?


6
我曾经的计算机科学教授在1997年左右声称编程的两种选择是C和Pascal,他建议选择Pascal,因为它不允许你从循环内部递增循环计数器。也许他当时是正确的,但是他的观点现在看来是错误的。 - MusiGenesis
1
@Jay:我认为这个意思是,你通过底部测试循环更有可能引入循环边界错误,因此应该确保它是表达算法的最佳方式,而不是完全禁止使用。 - Ferruccio
1
许多重复的辩论:https://dev59.com/5HVC5IYBdhLWcg3wpi58,http://stackoverflow.com/questions/390605/while-vs-do-while,https://dev59.com/OnNA5IYBdhLWcg3wQ7Yw,http://stackoverflow.com/questions/3094972/when-should-i-use-do-while-instead-of-while-loops等。 - gnovice
18
哇,这是不应该合并不完全相同的副本的完美例子。看起来一半的答案最初可能是针对完全不同的问题给出的,类似于“有什么区别”。在意识到回答者不应该失去声望而是过于热心的版主把完全正确的答案移动到与其无关的问题之前,我差点对其中一些答案进行了负评。 - Porculus
2
“在统计意义上更有可能正确”的概念是一个奇怪的标准。重要的是,“针对这个特定问题,什么是正确的?”从统计学的角度来看,我们更倾向于添加而不是乘法。因此,您会说每次需要进行算术运算时,都应该始终使用“+”,而永远不使用“*”,因为“统计上”添加更常用?或者,我去杂货店的频率比去汽车经销商的频率更高,因此,即使我想买一辆新车,每次离开家我也自然而然地去杂货店购物吗? - Jay
显示剩余7条评论
31个回答

80

我一直遵循的规则是:如果应该零次或更多次运行,请在开头进行测试;如果必须运行一次或更多次,请在结尾进行测试。我不认为使用您在示例中列出的代码有任何逻辑上的理由。它只会增加复杂性。


由于我从未见过“一次或多次”循环,所以我认为这可以简化一些。也许我只是生活在一个封闭的世界里。 - S.Lott
这是唯一有效的答案。 - mafu
我能想到的唯一一个使用“bug”中结构的有效原因是,在该entry条件下需要else块(例如,如果集合中没有项目,则完全执行不同的操作),那么为什么要重新测试循环入口条件,当您知道它是可以的?因此,性能方面。(假设编译器不会优化双重测试。) - Will Bradley
@S.Lott,我大多数情况下看到它是这样的:do { emit prompt; read input } while (input is invalid) - Samuel Edwin Ward

58

当您想在循环的第一次迭代之前测试条件时,请使用while循环。

当您想在运行第一次迭代后测试条件时,请使用do-while循环。

例如,如果您发现自己正在执行以下任何一个片段中的操作:

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

//or:

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

您应该将其重写为:

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

我经常使用你第二个测试用例的变体,因为我需要在“条件”检查之后执行某些操作。 - ntd

20

do循环执行"做某事"一次,然后检查条件以确定是否应重复"做某事",而while循环在执行任何操作之前先检查条件


2
就个人而言,我很少使用do-while循环。似乎总有一种方式可以将其表达为while循环或其他类型的循环,所以我经常忘记它的存在。 - devios1
我从未使用过它,当我修改另一位开发人员的代码时,他使用它时感觉很不自然。 - UnkwnTech
是的,我也想不起来我曾经使用过 do while 循环。 - Andrew G. Johnson

17

避免使用 do/while 会使我的代码更易读吗?

不一定。

如果使用 do/while 循环更为合理,那么请使用它。如果需要在测试条件之前先执行循环体,则 do/while 循环可能是最直接的实现方式。


@sje397:尽可能编写最简单、最易读的代码。与大多数编码标准一样,这是完全主观的度量标准。至于do/while循环是否“不常见”,我认为只有初学者才会对do/while循环感到困惑。 - James McNellis
问题在于,另一个问题的统计数据(其中显示了一些代码,对我来说,显然应该是一个do/while循环)似乎表明我认为最易读的与大多数其他人认为的不同。 - sje397
@sje397:解决问题通常有多种可接受的方案。个人而言,我更喜欢Jon建议的for循环,但我认为while循环或do/while循环并不一定不合适。 - James McNellis

12

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


7

为了易读性,将测试放在顶部似乎是明智的。循环的事实非常重要;在试图理解循环体之前,阅读代码的人应该意识到循环条件。


我无法理解为什么有人会认为我的回答没有帮助。 - mxcl
虽然不如Brett McCann的回答完整,但你的回答并不应该被投票否决。我部分同意你对可读性的担忧。还有其他一些像换行、返回等等的事情,会给一个在底部测试的循环带来同样的不确定性。 - Ates Goral
我认为if...do...while结构对程序员来说是糟糕的建议。do...while结构是众所周知的,而且它能够完成其任务。此外,使用if..do..while会导致测试相同的条件两次,这是不必要的性能损失。 - None
如果代码块很短,我偶尔会使用 do {} while () - Brad Gilbert

6

最近我遇到了一个很好的实际例子。假设您有多个处理任务(比如处理数组中的元素),并且希望在每个CPU核心之间分配工作。当前代码必须至少有一个核心在运行!因此,您可以使用类似于do... while的方法:

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

这个性能提升可能微不足道,但值得考虑:它同样可以用标准的while循环来编写,但这样会始终进行一个不必要的初始比较,而且在单核处理器上,使用do-while条件分支更加可预测(始终为false,而标准的while则交替为true/false)。


5

是的,没错。do-while循环至少会运行一次,这是唯一的区别。其他方面没有什么争议。


4

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


4

while循环将首先检查“条件”,如果为false,它将永远不会“执行某事”。但是do...while循环将首先“执行某事”,然后再检查“条件”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接