testing循环的顶部或底部? (while while do do while)

当我在大学攻读CS时(80年代中期),其中一个不断被重复的想法是总是写出循环testing在顶部(while …),而不是在底部(做… while)循环。 这些概念往往是支持与研究的参考,表明在顶部testing循环统计更有可能是正确的比底部testing的同行。

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

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

或者反过来( if在内部的话),这让我怀疑他们是不是真的这样写了它,或者当他们意识到循环没有处理空情况时添加了if语句。

我做了一些Googlesearch,但一直没能find关于这个主题的任何文献。 你们(和加尔)如何写你的循环?

我总是遵循如果运行零次或多次的规则,开始testing,如果运行一次或多次,testing结束。 我没有看到使用你的例子中列出的代码的任何合乎逻辑的理由。 这只会增加复杂性。

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

当您想要在运行循环的第一次迭代之后testing条件时,使用do-while循环。

例如,如果您发现自己正在做这样的片段之一:

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

你应该把它改写成:

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

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

避免do /真正帮助使我的代码更可读?

没有。

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

如果条件不成立,第一个可能根本就不执行。 其他人将执行至less一次,然后检查conidition。

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

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

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

这几乎可以忽略不计,但是可能值得考虑性能上的好处:它可以同时作为一个循环的标准写出来,但是总是会进行不必要的初始比较,总是会评估true – 而在单核上,条件分支更可预测(总是错误的,而对于一个标准交替真/假)。

第一次在执行之前testing条件,所以你的代码可能不会进入下面的代码。 第二个将在testing条件之前执行代码。

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

Yaa ..它是真的..尽pipe会运行至less一次。 这是唯一的区别。 没有别的辩论这个

是的,就像使用while而不是foreach,而不是为了提高可读性。 这就是说有些情况需要做,而我同意你会愚蠢的强迫这些情况进入一个while循环。

从常用的angular度思考更有帮助。 绝大多数的while循环都是相当自然的,即使它们可以与do...while一起工作,所以基本上你应该在差别无关紧要时使用它。 因此,我会使用do...while在罕见的情况下,它提供了明显的可读性提高。

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

如果你想循环执行基于条件不是使用或while

如果你想做一些事情,而不pipe条件如何,然后继续做基于条件评估。 do..while

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

因此,** do … while()**将总是至less执行一次循环体。

在function上,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()要less一个比较,所以它稍微快一点。 使用do … while()如果:

  • 知道这个情况在第一时间总会是真的,或者
  • 即使条件为假,也要循环执行一次。

这是翻译:

 do { y; } while(x); 

与…一样

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

注意,额外的大括号是为了在y有variables定义的情况。 这些必须保持在本地,就像在循环的情况下一样。 所以,一个do-while循环至less执行一次它的主体。 除此之外,两个循环是相同的。 所以,如果我们将这个规则应用到您的代码

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

do循环的相应while循环看起来像

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

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

我倾向于喜欢do-while循环,我自己。 如果条件在循环开始时总是成立的,我宁愿在最后testing它。 在我看来,testing条件(除断言之外)的全部重点是不知道testing的结果。 如果我在顶部看到一个条件testing的while循环,我倾向于考虑循环执行零次的情况。 如果这种情况永远不会发生,为什么不用一种明确表示的方式进行编码呢?

这实际上是为了不同的事情。 在C中,可以使用do-while构造来实现这两个场景(至less运行一次,并且运行正确)。 但是PASCAL 重复着 – 直到每一个场景,而且如果我没有记错的话,ADA有另外一个可以让你在中途退出的构造,但是这当然不是你要求的。 我的回答你的问题:我喜欢我的循环testing顶部。

对于任何想不到有一个或多个时间循环的理由的人:

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

同样可以用于任何types的分层结构。

在类Node:

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

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

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

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

可以使用更简洁的书写

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

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

 while (++a < n) {} 

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

正如Piemasons指出的那样,区别在于循环在执行testing之前是执行一次,还是先执行testing,以便循环体可能永远不会执行。

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

举两个简单的例子:

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

  2. 你想显示一条消息,接受一个回应,如果回应无效,再次询问,直到你得到一个有效的回应。 所以你总是想问一次。 你不能testing响应是否有效,直到你得到响应,所以在testing条件之前你必须经过循环体。 你应该使用DO / WHILE。

我写我的几乎完全在顶部testing。 这是更less的代码,至less对我来说,把事情弄糟的可能性不大(例如,复制粘贴条件使得两个地方总是需要更新)

这真的取决于有些情况下,当你想testing的顶部,其他人当你想在底部testing,还有一些当你想在中间testing。

然而,给出的例子似乎是荒谬的。 如果您要在顶部testing,不要使用if语句,并在底部testing,只需使用while语句即可。

您应该首先将testing看作循环代码的一部分。 如果testing逻辑上属于循环处理的开始,那么这是一个顶级的testing。 如果testing在逻辑上属于循环的结尾(即它决定循环是否应该继续运行),那么这可能是一个底部循环testing(bottom-of-the-loop test)。

如果testing逻辑上属于他们中间,你将不得不做一些奇特的事情。 🙂

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

要编写正确的代码,基本上需要执行一个精确的,也许非正式的非正式的certificate。

为了certificate一个循环是正确的,标准的方法是select一个循环不variables和一个归纳certificate。 但是可以跳过复杂的单词:非正式地,你所做的是找出循环的每一次迭代都是真实的,当循环完成时,你想完成的事情现在成立。 循环不变最后是假的,循环终止。

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

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

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

这两种types的while循环while和do..while实际上是第三个更通用的循环的实例,它在中间的某处进行testing。

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

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

 while( someConditionMayBeFalse ){ // this will never run... } // then the alternative do{ // this will run once even if the condition is false while( someConditionMayBeFalse ); 

区别是显而易见的,并且允许您运行代码,然后评估结果以查看是否必须“再次执行”,而如果条件未满足,另一种方法允许您忽略一个脚本块。

在计算机科学中的一个典型的离散结构类中,两者之间存在等价映射是一个简单的certificate。

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

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

只有当第一个循环根本不同的时候,才会有一个使用。而且,如果在循环中执行使您通过循环条件(如初始化)的代码。 否则,如果肯定循环将不会落在第一次迭代上,那么是的,一个do … while是适当的。

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