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,以便循环体可能永远不会执行。
关键的问题是你的应用程序是有意义的。
举两个简单的例子:
-
假设你正在循环一个数组的元素。 如果数组没有元素,则不想处理零的第一个数字。 所以你应该使用WHILE。
-
你想显示一条消息,接受一个回应,如果回应无效,再次询问,直到你得到一个有效的回应。 所以你总是想问一次。 你不能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执行一次。 这意味着循环不变代码“支配”出口节点。 因此可以在循环开始之前安全地移动。