环路终止条件

时间:2008-09-25 08:42:07

标签: language-agnostic for-loop conditional correctness

这些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的错误假设,程序可能会失败。它可能会远离导致错误的循环失败,从而难以追溯。或者它可以默默地继续做一些意想不到的事情,这更糟糕。

您更喜欢哪种终止条件?为什么?还有其他考虑因素吗?为什么许多知道这一点的程序员拒绝应用它?

13 个答案:

答案 0 :(得分:4)

我倾向于使用第二种形式,因为那时我可以更加确定循环将终止。即通过在循环中改变我来引入非终止错误更难。

当然,它也有一个稍微懒惰的优点,即输入一个较少的字符;)

我还要争辩说,在具有合理范围规则的语言中,因为我在循环结构中声明了它,所以它不应该在循环之外可用。这将减少在循环结束时对i等于N的依赖......

答案 1 :(得分:2)

我们不应该孤立地看待计数器 - 如果由于某种原因有人改变了计数器递增的方式,他们会改变终止条件和结果逻辑,如果i == N需要它。

我更喜欢第二个条件,因为它更标准,不会导致无限循环。

答案 2 :(得分:1)

在C ++中,使用!=测试是一般性的首选。 C ++中的迭代器有各种概念,例如input iteratorforward iteratorbidirectional iteratorrandom 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,而使用<作为条件则无关紧要。