提问人: 提问时间:10/22/2008 最后编辑:4 revs, 3 users 100%Ferruccio 更新时间:2/10/2012 访问量:21470
顶部还是底部的测试循环?(while 与 do while)[关闭]
Test loops at the top or bottom? (while vs. do while) [closed]
问:
当我在大学里学习计算机科学时(80年代中期),一个不断重复的想法是总是写循环,在顶部(同时...)而不是在底部(做......while) 的循环。这些概念通常得到研究的支持,这些研究表明,在顶部测试的循环在统计学上比底部测试的循环更有可能是正确的。
因此,我几乎总是编写在顶部测试的循环。如果它在代码中引入了额外的复杂性,我不会这样做,但这种情况似乎很少见。我注意到一些程序员几乎只编写在底部测试的循环。当我看到这样的结构时:
if (condition)
{
do
{
...
} while (same condition);
}
或反之(在 中),这让我想知道他们是否真的以这种方式编写它,或者他们是否在意识到循环没有处理 null 情况时添加了语句。if
while
if
我做了一些谷歌搜索,但找不到任何关于这个主题的文献。你们(和女孩)如何编写循环?
答:
我几乎只在顶部编写我的测试。它的代码更少,所以至少对我来说,搞砸东西的可能性较小(例如,复制粘贴条件使两个地方你总是需要更新它)
我总是遵循这样的规则,如果它应该运行零次或更多次,则在开始时进行测试,如果它必须运行一次或多次,则在结束时进行测试。我看不出有任何合乎逻辑的理由来使用您在示例中列出的代码。这只会增加复杂性。
评论
为了可读性,在顶部进行测试似乎是明智的。它是一个循环这一事实很重要;阅读代码的人在尝试理解循环的主体之前应该了解循环条件。
评论
do {} while ()
它实际上是为了不同的事情。在 C 语言中,可以使用 do - while 构造来实现这两种情况(至少运行一次,并在 true 时运行)。但是 PASCAL 有重复 - 直到每个场景,如果我没记错的话,ADA 还有另一种结构可以让你在中途退出,但当然这不是你要问的。 我对你的问题的回答:我喜欢我的循环,上面有测试。
两者的用例不同。这不是一个“最佳实践”问题。
如果您希望循环仅根据条件执行,而不是使用 for 或 while
如果你想做一次某事,不管条件如何,然后根据条件评估继续做。做。。而
这真的取决于有些情况你想在顶部测试,有些情况下你想在底部测试,还有一些情况你想在中间测试。
然而,给出的例子似乎很荒谬。如果你要在顶部进行测试,不要使用 if 语句,在底部进行测试,只需使用 while 语句,这就是它的用途。
首先,应将测试视为循环代码的一部分。如果测试在逻辑上属于循环处理的开始,则它是循环顶部测试。如果测试在逻辑上属于循环的末尾(即它决定循环是否应该继续运行),那么它可能是一个循环底部的测试。
如果测试在逻辑上属于其中间,您将不得不做一些花哨的事情。:-)
对于任何想不出理由进行一次或多次循环的人:
try {
someOperation();
} catch (Exception e) {
do {
if (e instanceof ExceptionIHandleInAWierdWay) {
HandleWierdException((ExceptionIHandleInAWierdWay)e);
}
} while ((e = e.getInnerException())!= null);
}
这同样可以用于任何类型的层次结构。
在类 Node 中:
public Node findSelfOrParentWithText(string text) {
Node node = this;
do {
if(node.containsText(text)) {
break;
}
} while((node = node.getParent()) != null);
return node;
}
评论
不同之处在于,do 循环执行一次“do something”,然后检查条件以查看它是否应该重复“do something”,而 while 循环在执行任何操作之前检查条件
评论
首先,如果条件为 false,则可能根本不执行。另一个将至少执行一次,然后检查conidition。
第一个在执行之前测试条件,因此您的代码可能永远不会在下面输入代码。第二个将在测试条件之前执行代码。
while 循环将首先检查“条件”;如果它是假的,它永远不会“做某事”。但是做...而 loop 将首先“做某事”,然后检查“条件”。
while() 在每次执行循环体之前检查条件,并执行...while() 在每次执行循环体后检查条件。
因此,**做...while()**s 将始终至少执行一次循环体。
从功能上讲,while() 等价于
startOfLoop:
if (!condition)
goto endOfLoop;
//loop body goes here
goto startOfLoop;
endOfLoop:
和做...while() 等价于
startOfLoop:
//loop body
//goes here
if (condition)
goto startOfLoop;
请注意,实现可能比这更有效。然而,一个做...while() 确实比 while() 少涉及一次比较,因此它稍微快一些。使用一个 do...while() 如果:
- 您知道该条件在第一次出现时总是为真,或者
- 您希望循环执行一次,即使条件一开始就为 false。
在计算机科学中典型的离散结构类中,这很容易证明两者之间存在等价映射。
从风格上讲,我更喜欢 while (easy-expr) { } 当 easy-expr 预先知道并准备好使用时,并且循环没有太多重复开销/初始化。我更喜欢做 { } while (somewhatwhat-less-easy-expr);当有更多重复的开销并且条件可能不那么简单时,提前设置。如果我写一个无限循环,我总是使用 while (true) { }。我无法解释为什么,但我只是不喜欢为(;;)写作{ }.
翻译如下:
do { y; } while(x);
同上
{ y; } while(x) { y; }
请注意,额外的大括号集适用于在 中具有变量定义的情况。这些范围必须保持在本地,就像在 do-loop 案例中一样。因此,do-while 循环至少只执行其主体一次。除此之外,这两个循环是相同的。因此,如果我们将此规则应用于您的代码y
do {
// do something
} while (condition is true);
do 循环的相应 while 循环如下所示
{
// do something
}
while (condition is true) {
// do something
}
是的,您会看到 do 循环的相应 while 与 while :)不同
评论
如果您知道如何正确编写代码,则这两种约定都是正确的:)
通常使用第二种约定 ( do {} while() ) 是为了避免在循环之外出现重复的语句。请考虑以下(过度简化)示例:
a++;
while (a < n) {
a++;
}
可以更简洁地编写使用
do {
a++;
} while (a < n)
当然,这个特定的例子可以用更简洁的方式编写(假设是 C 语法)
while (++a < n) {}
但我认为你可以看到这里的观点。
我猜有些人在底部进行测试,因为 30 年前这样做可以节省一个或几个机器周期。
要编写正确的代码,基本上需要对正确性进行心理证明,也许是非正式的证明。
为了证明环路的正确性,标准方法是选择环路不变性和归纳证明。但是跳过复杂的词语:你非正式地所做的是弄清楚循环的每次迭代都是正确的,当循环完成时,你想要完成的事情现在是正确的。循环不变性在末尾为 false,用于终止循环。
如果循环条件很容易映射到不变量,并且不变量位于循环的顶部,并且通过循环的代码推断出不变量在循环的下一次迭代中为真,那么很容易确定循环是正确的。
但是,如果不变量位于循环的底部,那么除非您在循环之前有一个断言(这是一个很好的做法),否则它会变得更加困难,因为您必须从根本上推断该不变量应该是什么,并且在循环之前运行的任何代码都会使循环不变性为真(因为没有循环前提条件, 代码将在循环中执行)。证明正确变得更加困难,即使它是一个非正式的头脑证明。
这并不是一个真正的答案,而是对我的一位讲师所说的话的重申,当时我很感兴趣。
两种类型的循环 while..做和做..而实际上是第三个更通用循环的实例,该循环的测试位于中间的某个地方。
begin loop
<Code block A>
loop condition
<Code block B>
end loop
代码块 A 至少执行一次,B 执行零次或多次,但不会在最后一次(失败)迭代时运行。while 循环是指代码块 A 为空且 do 为空。while 是代码块 B 为空时。但是,如果你正在编写一个编译器,你可能有兴趣将这两种情况推广到这样的循环中。
我会说如果写是不好的做法。做。。while 循环,原因很简单,这会增加代码的大小并导致代码重复。代码重复容易出错,应避免,因为对一个部分的任何更改也必须对副本执行,但情况并非总是如此。此外,更大的代码意味着更难处理 cpu 缓存。最后,它处理空情况,并解决头疼。
只有当第一个循环根本不同时,才应该使用 do..同时,比如说,如果让你传递循环条件(如初始化)的代码是在循环中执行的。否则,如果确定该循环永远不会落在第一次迭代中,那么是的,一个 do..虽然是合适的。
根据我对代码生成的有限了解,我认为编写底部测试循环可能是一个好主意,因为它们使编译器能够更好地执行循环优化。对于底部测试循环,保证循环至少执行一次。这意味着循环不变代码“主导”出口节点。因此可以在循环开始之前安全地移动。
如果要在循环的第一次迭代之前测试条件,请使用 while 循环。
如果要在运行循环的第一次迭代后测试条件,请使用 do-while 循环。
例如,如果您发现自己在执行以下任一代码段:
func();
while (condition) {
func();
}
//or:
while (true){
func();
if (!condition) break;
}
你应该把它改写为:
do{
func();
} while(condition);
评论
condition
雅..这是真的..do while 将至少运行一次。 这是唯一的区别。对此没有什么可争论的了
这是我最近遇到的一个很好的真实世界的例子。假设您有许多处理任务(例如处理数组中的元素),并且您希望在每个 CPU 内核的一个线程之间拆分工作。必须至少有一个内核才能运行当前代码!因此,您可以使用类似的东西:do... while
do {
get_tasks_for_core();
launch_thread();
} while (cores_remaining());
它几乎可以忽略不计,但可能值得考虑性能优势:它同样可以编写为标准循环,但这总是会进行不必要的初始比较,而这种比较总是会进行评估 - 并且在单核上,do-while 条件分支更可预测(总是 false,而不是标准交替 true/false)。while
true
while
正如 Piemason 所指出的,区别在于循环是在执行测试之前执行一次,还是先执行测试,以便循环主体可能永远不会执行。
关键问题是哪个对你的应用有意义。
举两个简单的例子:
假设您正在遍历数组的元素。如果数组没有元素,则不希望处理零中的第一个元素。所以你应该使用 WHILE。
您希望显示一条消息,接受响应,如果响应无效,请再次询问,直到获得有效的响应。所以你总是想问一次。在获得响应之前,无法测试响应是否有效,因此必须先遍历循环主体一次,然后才能测试条件。您应该使用 DO/WHILE。
避免/真的有助于使我的代码更具可读性吗?
do
while
不。
如果使用 / 循环更有意义,那就这样做。如果你需要在测试条件之前执行一次循环的主体,那么 / 循环可能是最直接的实现。do
while
do
while
评论
是的,就像使用 for 代替 while,或者使用 foreach 代替 for 来提高可读性一样。也就是说,有些情况需要 while 做,我同意你强迫这些情况进入 while 循环是愚蠢的。
从常见用法的角度思考会更有帮助。绝大多数 while 循环非常自然地使用 ,即使它们可以与 一起使用,所以基本上你应该在差异无关紧要的时候使用它。因此,我会在极少数情况下使用它,在这些场景中,它提供了可读性方面的显着改进。while
do...while
do...while
我自己更喜欢 do-while 循环。如果条件在循环开始时始终为真,我更喜欢在最后测试它。在我看来,测试条件(断言除外)的全部意义在于人们不知道测试的结果。如果我看到一个 while 循环,条件测试位于顶部,我倾向于考虑循环执行零次的情况。如果这永远不会发生,为什么不以一种清楚地显示这一点的方式进行编码呢?
评论
通常,这取决于您如何构建代码。正如有人已经回答的那样,某些算法至少需要执行一次迭代。因此,为了逃避额外的迭代计数或至少一次发生交互的标志 - 您可以使用 do/while。
while( someConditionMayBeFalse ){
// this will never run...
}
// then the alternative
do{
// this will run once even if the condition is false
while( someConditionMayBeFalse );
区别是显而易见的,它允许你运行代码,然后评估结果,看看你是否必须“再做一次”,而while的另一种方法允许你在不满足条件时忽略一个脚本块。
评论