测试循环在顶部还是底部? (while vs. do while)[关闭]
Posted
技术标签:
【中文标题】测试循环在顶部还是底部? (while vs. do while)[关闭]【英文标题】:Test loops at the top or bottom? (while vs. do while) [closed] 【发布时间】:2010-09-18 10:51:49 【问题描述】:当我在大学学习 CS 时(80 年代中期),不断重复的想法之一是始终编写循环测试在顶部 (while...) 而不是在底部 (do ... while ) 的循环。这些概念经常得到研究参考的支持,这些研究表明,在顶部测试的循环在统计上比底部测试的循环更可能是正确的。
因此,我几乎总是编写在顶部进行测试的循环。如果它在代码中引入额外的复杂性,我不会这样做,但这种情况似乎很少见。我注意到一些程序员倾向于几乎专门编写在底部进行测试的循环。当我看到这样的结构时:
if (condition)
do
...
while (same condition);
或相反(while
中的if
),这让我想知道他们是否真的是这样写的,或者当他们意识到循环没有处理 null 情况时是否添加了if
语句。
我已经进行了一些谷歌搜索,但无法找到有关此主题的任何文献。你们(和女孩)如何编写循环?
【问题讨论】:
我有一位大约在 1997 年的 CS 教授宣称您在编程中的两个选择是 C 或 Pascal,并且您应该选择 Pascal,因为它不允许您从循环内部增加循环计数器。他可能是对的,但他错了。 @Jay:我认为这意味着您更有可能通过底部测试循环引入循环边界错误,因此应确保这是表达算法的最佳方式。不要直接禁止他们。 许多 次辩论重复:***.com/questions/224059/…、***.com/questions/390605/while-vs-do-while、***.com/questions/1035229/…、***.com/questions/3094972/… 等。 哇,这是一个完美的例子,说明为什么不应该合并不精确的重复项。看起来一半的答案最初必须是针对“有什么区别”的完全不同的问题。在我意识到应该失去声誉的不是回答者,而是过分热心的版主给出了非常好的答案并将他们转移到一个他们没有解决的无关问题之前,我几乎对其中一些人投了反对票。 什么构造“在统计上更可能是正确的”是一个奇怪的标准。重要的是,“对于这个特定问题,什么是正确的?”从统计上讲,我们想要增加的次数比想要的增加次数要多。因此,您是否会说,任何时候您需要进行算术运算时,都应该始终使用“+”,而永远不要使用“*”,因为“从统计上”,加法通常是正确的?或者,我去杂货店的次数比去汽车经销商的次数多,因此,每当我离开家时,我都会自动去杂货店——即使我想做的是买一辆新车。 【参考方案1】:我总是遵循这样的规则,如果它应该运行零次或多次,则在开始时进行测试,如果必须运行一次或更多次,则在结束时进行测试。我认为没有任何合乎逻辑的理由使用您在示例中列出的代码。它只会增加复杂性。
【讨论】:
由于我从未见过“一次或多次”循环,我认为这可以简化一些。也许我只是生活在一个封闭的世界里。 这是唯一有效的答案。 我能想到的唯一有效的原因是你可能需要这个 bug 中的结构是如果你需要一个 else 块来满足那个进入条件(比如,如果没有项目,做一些完全不同的事情设置),当你知道它没问题时,为什么还要重新测试循环的入口?所以,性能。 (假设编译器没有优化双重测试。) @S.Lott,大多数情况下我已经看到它了 do emit prompt;读取输入 while (输入无效)【参考方案2】:如果您想在循环的第一次迭代之前测试条件,请使用 while 循环。
如果您想在运行循环的第一次迭代后测试条件,请使用 do-while 循环。
例如,如果您发现自己在执行以下任一 sn-ps 的操作:
func();
while (condition)
func();
//or:
while (true)
func();
if (!condition) break;
你应该把它改写成:
do
func();
while(condition);
【讨论】:
我经常使用你的第二个测试用例的变体,因为我需要在condition
检查之后做一些事情【参考方案3】:
不同之处在于do循环执行一次“做某事”,然后检查条件是否应该重复“做某事”,而while循环在做任何事情之前检查条件
【讨论】:
我个人很少使用do-while。似乎总有一种方法可以将其表示为 while 循环或另一种循环,所以我经常忘记它甚至存在。 我从未使用过它,并且在修改其他开发人员的某些代码时,使用它时感觉不自然。 是的,我也想不出一个我使用过 do while 循环的实例。【参考方案4】:避免
do
/while
真的有助于使我的代码更具可读性吗?
没有。
如果使用do
/while
循环更有意义,那么就这样做。如果您需要在测试条件之前执行一次循环体,那么do
/while
循环可能是最直接的实现。
【讨论】:
@sje397:做任何能产生最不复杂、最易读的代码的事情。与大多数编码标准一样,这是一个完全主观的衡量标准。至于 do/while 循环“不常见”,我认为只有初学者才会对 do/while 循环感到困惑。 问题是,另一个问题的统计数据(它向我显示了一些代码,显然应该是一个 do/while 循环)似乎表明什么我认为最易读的与大多数人的想法不同。 @sje397:一个问题通常有多种可接受的解决方案。我个人更喜欢 Jon 建议的 for 循环,但我认为 while 循环或 do/while 循环不一定不合适。【参考方案5】:如果条件为假,第一个可能根本不会执行。其他的将至少执行一次,然后检查条件。
【讨论】:
【参考方案6】:为了可读性,在顶部进行测试似乎是明智的。它是一个循环的事实很重要。阅读代码的人应该在尝试理解循环体之前了解循环条件。
【讨论】:
任何人都无法将我的回答视为无用。 虽然不如 Brett McCann 的回答那么完整,但你的回答真的不值得投反对票。我部分同意您的可读性问题。还有其他一些东西,比如中断、返回等,给在底部测试的循环带来了同等数量的不确定性。 我会说 if...do...while 结构对程序员来说是个糟糕的建议。 do...while 构造是众所周知的,并且服务于它的目的。此外,使用 if..do..while 来测试相同的条件两次,这是不必要的性能损失。 如果是短代码块,我偶尔会使用do while ()
。【参考方案7】:
这是我最近遇到的一个很好的真实示例。假设您有许多处理任务(例如处理数组中的元素),并且您希望在每个 CPU 内核的一个线程之间分配工作。必须至少有一个内核才能运行当前代码!所以你可以使用do... while
类似的东西:
do
get_tasks_for_core();
launch_thread();
while (cores_remaining());
它几乎可以忽略不计,但它可能值得考虑性能优势:它同样可以写成标准的 while
循环,但这总是会产生不必要的初始比较,总是会评估 true
- 并且单-core,do-while 条件的分支更可预测(始终为假,而不是标准while
的交替真/假)。
【讨论】:
【参考方案8】:第一个在执行之前测试条件,因此您的代码可能永远不会进入下面的代码。第二个将在测试条件之前执行代码。
【讨论】:
【参考方案9】:while 循环将首先检查“条件”;如果它是假的,它永远不会“做某事”。但是 do...while 循环会先“做某事”,然后检查“条件”。
【讨论】:
【参考方案10】:Yaa..its true.. do while 将至少运行一次。 那是唯一的区别。没有什么可讨论的了
【讨论】:
【参考方案11】:是的,就像使用 for 代替 while,或使用 foreach 代替 for 一样可以提高可读性。那就是说某些情况需要做while,我同意你将这些情况强制进入while循环是愚蠢的。
【讨论】:
【参考方案12】:从常用用法的角度思考更有帮助。绝大多数 while 循环与while
一起工作很自然,即使它们可以与do...while
一起工作,所以基本上你应该在差异无关紧要时使用它。因此,我会使用do...while
来处理罕见的情况,因为它可以显着提高可读性。
【讨论】:
【参考方案13】:两者的用例不同。这不是一个“最佳实践”问题。
如果您希望循环仅基于条件执行而不是使用 for 或 while
如果您想在任何条件下执行一次操作,然后根据条件评估继续执行此操作。 做..while
【讨论】:
【参考方案14】:对于任何想不出有一个或多个时间循环的理由的人:
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;
【讨论】:
节点 node = this; while (node != null) if (node.containsTest(text) break; node = node.getParent(); 对我来说更具可读性(当然不是在这样的评论中)。【参考方案15】: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() 如果:
您知道条件在第一次时总是为真,或者 您希望循环执行一次,即使条件一开始就为假。【讨论】:
【参考方案16】:翻译如下:
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
是的,您看到 do 循环的相应 while 与您的 while 不同 :)
【讨论】:
如果“做某事”部分有变量定义,它们就不一样了。 很好看,罗伯特。生病修复它。谢谢! 另一种可能更好的显示等价性的方法是“do y while (x);”相当于“while (1) y; if (!x) break; ”。【参考方案17】:正如 Piemasons 所指出的,区别在于循环是在进行测试之前执行一次,还是先完成测试以使循环体可能永远不会执行。
关键问题是哪个对您的应用有意义。
举两个简单的例子:
假设您正在循环遍历数组的元素。如果数组没有元素,您不想处理第一个零。所以你应该使用 WHILE。
您想显示一条消息,接受响应,如果响应无效,请再次询问,直到您得到有效响应。所以你总是想问一次。在得到响应之前,您无法测试响应是否有效,因此您必须先遍历循环体一次,然后才能测试条件。你应该使用 DO/WHILE。
【讨论】:
【参考方案18】:我自己更喜欢 do-while 循环。如果条件在循环开始时总是为真,我更愿意在结束时对其进行测试。在我看来,测试条件(除了断言)的全部意义在于人们不知道测试的结果。如果我在顶部看到一个带有条件测试的 while 循环,我倾向于考虑循环执行零次的情况。如果这永远不会发生,为什么不以清楚地表明这一点的方式编写代码?
【讨论】:
+1。我被教导初始化一个变量以将循环条件控制为 TRUE,并在第一个循环中条件始终为真的情况下使用 WHILE 循环,回想起来,我想不出任何比仅使用更具可读性的方法一会儿。 特意使用“while”而不是“due-while”对我来说似乎非常愚蠢。我的观点是,我使用“do-while”而不是“while”,即使两者都同样有效。【参考方案19】:它实际上是为了不同的东西。在 C 中,您可以使用 do - while 构造来实现这两种情况(至少运行一次并在 true 时运行)。但是 PASCAL 对每个场景都有 repeat - until 和 while,如果我没记错的话,ADA 有另一个结构可以让你在中间退出,但当然不是这样你在问。 我对您的问题的回答:我喜欢在顶部进行测试的循环。
【讨论】:
【参考方案20】:如果您知道如何正确编写代码,这两种约定都是正确的:)
通常使用第二个约定( do while() )是为了避免在循环外出现重复的语句。考虑以下(过度简化)示例:
a++;
while (a < n)
a++;
可以更简洁地写成
do
a++;
while (a < n)
当然,这个特定的例子可以写成更简洁的方式(假设 C 语法)
while (++a < n)
但我想你可以在这里看到重点。
【讨论】:
【参考方案21】:while( someConditionMayBeFalse )
// this will never run...
// then the alternative
do
// this will run once even if the condition is false
while( someConditionMayBeFalse );
区别很明显,允许您运行代码,然后评估结果以查看是否必须“再做一次”,而另一种 while 方法允许您在条件不是时忽略脚本块遇到了。
【讨论】:
【参考方案22】:我几乎只在顶部编写我的测试。它的代码更少,所以至少对我来说,搞砸的可能性更小(例如,复制粘贴条件会使您总是需要更新两个地方)
【讨论】:
【参考方案23】:这真的取决于有些情况你想在顶部进行测试,有些情况下你想在底部进行测试,还有一些情况你想在中间进行测试。
然而,给出的例子似乎很荒谬。如果您要在顶部进行测试,请不要使用 if 语句并在底部进行测试,只需使用 while 语句,这就是它的用途。
【讨论】:
【参考方案24】:您应该首先将测试视为循环代码的一部分。如果测试在逻辑上属于循环处理的开始,那么它就是循环顶部测试。如果测试在逻辑上属于循环的末尾(即它决定循环是否应该继续运行),那么它可能是循环底部测试。
如果测试在逻辑上属于它们中间,你将不得不做一些花哨的事情。 :-)
【讨论】:
【参考方案25】:我猜有些人在底部进行测试,因为 30 年前这样做可以节省一个或几个机器周期。
【讨论】:
【参考方案26】:要编写正确的代码,基本上需要进行心理上的,也许是非正式的正确性证明。
要证明循环正确,标准方法是选择循环不变量和归纳证明。但是跳过复杂的词:你所做的,非正式地,找出循环的每次迭代都是真的,当循环完成时,你想要完成的现在是真的。循环不变量最后为假,循环终止。
如果循环条件很容易映射到不变量,并且不变量位于循环的顶部,并且通过处理循环代码推断该不变量在循环的下一次迭代中为真,那么很容易找出循环是正确的。
但是,如果不变量位于循环的底部,那么除非您在循环之前有一个断言(一个好的做法),否则它会变得更加困难,因为您必须从本质上推断该不变量应该是什么,并且任何在循环之前运行的代码都使循环不变为真(因为没有循环前置条件,代码将在循环中执行)。证明正确变得更加困难,即使它是一种非正式的头脑证明。
【讨论】:
【参考方案27】:这并不是真正的答案,而是重申了我的一位讲师所说的内容,当时我很感兴趣。
while..do 和 do..while 这两种类型的循环实际上是第三种更通用循环的实例,它的测试位于中间的某处。
begin loop
<Code block A>
loop condition
<Code block B>
end loop
代码块 A 至少执行一次,B 执行零次或多次,但不会在最后一次(失败的)迭代中运行。 while 循环是当代码块 a 为空时,而 do..while 是当代码块 b 为空时。但如果您正在编写编译器,您可能会对将这两种情况推广到这样的循环感兴趣。
【讨论】:
【参考方案28】:在计算机科学的典型离散结构课程中,很容易证明两者之间存在等价映射。
在风格上,我更喜欢 while (easy-expr) ,因为它预先知道 easy-expr 并准备就绪,并且循环没有很多重复的开销/初始化。我更喜欢 do while (somewhat-less-easy-expr);当有更多的重复开销并且提前设置条件可能不是那么简单时。如果我写一个无限循环,我总是使用 while (true) 。我无法解释为什么,但我就是不喜欢为 (;;) 写作。
【讨论】:
【参考方案29】:我会说编写 if..do..while 循环是不好的做法,原因很简单,这会增加代码的大小并导致代码重复。代码重复很容易出错,应该避免,因为对一个部分的任何更改也必须在重复上执行,但情况并非总是如此。此外,更大的代码意味着 CPU 缓存上的时间更难。最后,它处理空情况,解决头痛问题。
只有当第一个循环根本不同时,才应该使用 do..while,例如,如果使您通过循环条件的代码(如初始化)在循环中执行。否则,如果确定该循环永远不会落在第一次迭代中,那么是的,do..while 是合适的。
【讨论】:
【参考方案30】:根据我对代码生成的有限知识,我认为编写底部测试循环可能是一个好主意,因为它们使编译器能够更好地执行循环优化。对于底部测试循环,保证循环至少执行一次。这意味着循环不变代码“支配”了出口节点。因此可以在循环开始之前安全地移动。
【讨论】:
以上是关于测试循环在顶部还是底部? (while vs. do while)[关闭]的主要内容,如果未能解决你的问题,请参考以下文章