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