循环终止条件
Posted
技术标签:
【中文标题】循环终止条件【英文标题】:Loop termination conditions 【发布时间】:2010-09-13 00:40:44 【问题描述】:这些for
-loops 是算法形式正确性证明的第一个基本示例。它们具有不同但等效的终止条件:
1 for ( int i = 0; i != N; ++i )
2 for ( int i = 0; i < N; ++i )
后置条件中的区别变得明显:
第一个给出了循环终止后i == N
的有力保证。
第二个仅在循环终止后提供i >= N
的弱保证,但您会很容易假设i == N
。
如果由于任何原因将增量 ++i
更改为类似 i += 2
,或者如果 i
在循环内被修改,或者如果 N
为负数,则程序可能会失败:
第一个可能会陷入无限循环。它在有错误的循环中提前失败。调试很简单。
第二个循环将终止,并且由于您对i == N
的错误假设,稍后程序可能会失败。它可能会在远离导致错误的循环的地方失败,从而难以追溯。或者它可以默默地继续做一些意想不到的事情,这更糟糕。
您更喜欢哪种终止条件,为什么?还有其他考虑吗?为什么很多知道这个的程序员都拒绝应用?
【问题讨论】:
对于那些提到 i 在 for 之外不可用的人,请注意,在推理程序时可以使用终止时的 i 值。例如,如果你想用一个保持不变 P(i) 的循环来建立 P(N),那么 i==N 会给你 P(N) 而 i>=N 不会。 +1 表示写得很好的问题,清楚地表达了每个选项的优点。 【参考方案1】:在 C++ 中,为了通用性,首选使用 !=
测试。 C++ 中的迭代器有多种概念,如input iterator、forward iterator、bidirectional iterator、random access iterator,每一个都扩展了前一个概念,并提供了新的功能。要使<
工作,需要随机访问迭代器,而!=
只需要输入迭代器。
【讨论】:
【参考方案2】:我们不应该孤立地查看计数器 - 如果出于任何原因有人更改了计数器的递增方式,他们会更改终止条件和结果逻辑(如果 i==N 需要)。
我更喜欢第二个条件,因为它更标准,不会导致无限循环。
【讨论】:
【参考方案3】:如果您信任您的代码,您可以选择其中任何一种。
如果您希望您的代码可读且易于理解(因此更能容忍从您必须假设为笨拙的人进行更改),我会使用类似的东西;
for ( int i = 0 ; i >= 0 && i < N ; ++i)
【讨论】:
【参考方案4】:我总是使用#2,因为这样你就可以确定循环将终止......之后依赖它等于 N 是依赖于副作用......你会不会更好地使用变量 N 本身?
[编辑] 抱歉...我的意思是 #2
【讨论】:
【参考方案5】:总的来说,我更喜欢
for ( int i = 0; i < N; ++i )
对生产中的错误程序的惩罚似乎不那么严重,您不会让线程永远卡在 for 循环中,这种情况可能非常危险且难以诊断。
另外,一般来说,我喜欢避免使用此类循环,而使用更具可读性的 foreach 样式循环。
【讨论】:
【参考方案6】:我认为大多数程序员都使用第二个,因为它有助于弄清楚循环内部发生了什么。我可以看它,并且“知道”我将从 0 开始,并且肯定会小于 N。
第一个变体没有这种品质。我可以看一下,我只知道我将从 0 开始,并且永远不会等于 N。没有那么有用。
不管你如何终止循环,在循环外使用循环控制变量时要非常小心。在您的示例中,您(正确地)在循环内声明 i,因此它不在循环外的范围内,并且其值的问题没有实际意义......
当然,第二个变体还有一个优点,那就是我见过的所有 C 引用都使用它:-)
【讨论】:
【参考方案7】:我更喜欢使用#2,只是因为我尽量不在 for 循环之外扩展 i 的含义。如果我要跟踪这样的变量,我会创建一个额外的测试。有人可能会说这是多余或低效的,但它提醒了读者我的意图:此时,我必须等于 N
@timyates - 我同意不应该依赖副作用
【讨论】:
【参考方案8】:我倾向于使用第二种形式,只是因为这样我可以更确定循环将终止。 IE。通过在循环内更改 i 来引入非终止错误更难。
当然,它还有一个稍微懒惰的优势,就是少输入一个字符;)
我还认为,在具有合理范围规则的语言中,由于 i 在循环构造内声明,它不应该在循环外可用。这将减轻对 i 在循环结束时等于 N 的任何依赖...
【讨论】:
我也在想同样的事情,当然大多数语言只会在循环范围内使用 i。 +1 在 Perl 中:for( my $i=0; $i<N; ++$i )...
【参考方案9】:
我认为您很好地说明了两者之间的区别。不过,我确实有以下 cmets:
这不是“语言不可知论”,我可以看到您的示例是用 C++ 编写的,但是 是不允许修改循环变量的语言 循环和其他不保证索引的值在之后可用的 循环(有些人两者都做)。
您已声明i
for
内的索引,所以我不会在循环后押注i
的值。
这些示例有点误导,因为它们隐含地假设 for
是
一个确定的循环。实际上它只是一种更方便的写作方式:
// version 1
int i = 0;
while (i != N)
...
++i;
注意i
在块之后是如何未定义的。
如果程序员知道以上所有内容,就不会对i
的值做出一般假设,而是明智地选择i<N
作为结束条件,以确保最终满足退出条件。
【讨论】:
【参考方案10】:如果在循环外使用 i,在 c# 中使用上述任何一种都会导致编译器错误
【讨论】:
【参考方案11】:我有时更喜欢这个:
for (int i = 0; (i <= (n-1)); i++) ...
这个版本直接显示了我可以拥有的值的范围。我对检查范围的下限和上限的看法是,如果你真的需要这个,你的代码有太多的副作用,需要重写。
另一个版本:
for (int i = 1; (i <= n); i++) ...
帮助您确定调用循环体的频率。这也有有效的用例。
【讨论】:
【参考方案12】:对于一般的编程工作我更喜欢
for ( int i = 0; i < N; ++i )
到
for ( int i = 0; i != N; ++i )
因为它不易出错,尤其是在代码被重构时。我曾看到这种代码不小心变成了无限循环。
那个论点说“你会很想假设 i == N”,我不相信这是真的。我从来没有做过这样的假设,也没有见过其他程序员做出这样的假设。
【讨论】:
【参考方案13】:从形式验证和自动终止分析的角度来看,我非常喜欢#2 (<
)。很容易跟踪某些变量的增加(在var = x
之前,在var = x+n
之后对于一些非负数n
)。但是,要看到i==N
最终成立并不容易。为此,需要推断i
在每一步中恰好增加了1
,这(在更复杂的示例中)可能由于抽象而丢失。
如果您考虑以 2 为增量的循环 (i = i + 2
),这个总体思路就会变得更容易理解。为了保证终止,现在需要知道i%2 == N%2
,而当使用<
作为条件时,这无关紧要。
【讨论】:
以上是关于循环终止条件的主要内容,如果未能解决你的问题,请参考以下文章