在顶部或底部测试循环? (而与之相比)

时间:2008-10-22 00:29:15

标签: language-agnostic loops while-loop do-while

当我在大学(80年代中期)学习CS时,不断重复的一个想法就是总是写出在顶部(而不是......)而不是在底部进行测试的循环(做...同时)循环。这些概念通常以对研究的参考作为后盾,这些研究表明,在顶部测试的循环在统计上比他们的底部测试对应物更可能是正确的。

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

这样的结构时
if (condition)
{
    do
    {
       ...
    } while (same condition);
}

if内的反向(while),它让我想知道他们是否真的以这种方式编写它,或者当他们意识到循环没有添加if语句时t处理空案例。

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

31 个答案:

答案 0 :(得分:74)

我始终遵循以下规则:如果它应该运行零次或多次,则在开始时进行测试,如果必须运行一次或更多次,请在结束时进行测试。我没有看到使用您在示例中列出的代码的任何逻辑原因。它只会增加复杂性。

答案 1 :(得分:55)

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

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

例如,如果您发现自己正在执行这些代码段之一:

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

//or:

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

你应该把它重写为:

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

答案 2 :(得分:20)

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

答案 3 :(得分:17)

  

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

没有

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

答案 4 :(得分:12)

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

答案 5 :(得分:7)

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

答案 6 :(得分:6)

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

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

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

答案 7 :(得分:4)

从常见用法考虑更有帮助。绝大多数while循环使用while非常自然地工作,即使它们可以与do...while一起使用,所以基本上你应该在差异无关紧要时使用它。因此,我将do...while用于罕见的情况,它可以显着提高可读性。

答案 8 :(得分:4)

是的,就像使用for而不是while,或foreach而不是提高可读性。那说某些情况需要这样做,我同意你愚蠢地强迫这些情况进入一个循环。

答案 9 :(得分:4)

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

答案 10 :(得分:4)

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

答案 11 :(得分:4)

是..这是真的..同时至少运行一次。 这是唯一的区别。没有其他事情要讨论这个

答案 12 :(得分:3)

我倾向于选择do-while循环,我自己。如果条件在循环开始时总是为真,我宁愿在最后测试它。在我看来,测试条件的整个要点(断言除外)是人们不知道测试的结果。如果我在顶部看到条件测试的while循环,我倾向于考虑循环执行零次的情况。如果这种情况永远不会发生,为什么不以明确表示的方式进行编码?

答案 13 :(得分:3)

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

如果您希望循环基于条件而不是使用而执行 代表代表

如果您想在不考虑条件的情况下做一次,然后根据条件评估继续进行。 的 do..while

答案 14 :(得分: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()if:

  • 知道条件在第一次出现时始终为真,或
  • 想要循环执行一次,即使条件为false也是如此。

答案 15 :(得分: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
}

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

答案 16 :(得分:2)

如Piemasons所述,区别在于循环在执行测试之前是执行一次,还是首先完成测试以便循环体可能永远不会执行。

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

举两个简单的例子:

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

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

答案 17 :(得分:2)

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

通常使用第二个约定( do {} while())意味着避免在循环外部有重复的语句。请考虑以下(简化)示例:

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

可以使用

更简洁地编写
do {
  a++;
} while (a < n)

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

while (++a < n) {}

但我认为你可以在这里看到这一点。

答案 18 :(得分:2)

对于那些无法想出有一次或多次循环的理由的人:

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

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

节点中的

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

答案 19 :(得分:2)

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

答案 20 :(得分:1)

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

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

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

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

答案 21 :(得分:1)

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

为了证明循环正确,标准方法是选择循环不变量和感应证明。但是跳过复杂的话:非正式地做的是找出循环的每次迭代都是正确的东西,并且当循环完成时,你想要完成的东西现在是真的。循环不变量在结尾处为假,以便循环终止。

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

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

答案 22 :(得分:1)

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

答案 23 :(得分:1)

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

答案 24 :(得分:1)

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

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

答案 25 :(得分:1)

这确实取决于你想要在顶部进行测试的情况,有些时候你想要在底部进行测试,还有一些情况你想在中间进行测试。

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

答案 26 :(得分:1)

while( someConditionMayBeFalse ){

// this will never run...

}


// then the alternative

do{

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

while( someConditionMayBeFalse );

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

答案 27 :(得分:0)

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

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

答案 28 :(得分:0)

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

答案 29 :(得分:0)

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

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

答案 30 :(得分:0)

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