6

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

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

4

13 回答 13

4

我倾向于使用第二种形式,只是因为这样我可以更确定循环将终止。即,通过在循环内更改 i 来引入非终止错误会更加困难。

当然,它还有一个稍微懒惰的优势,那就是少输入一个字符;)

我还要争辩说,在具有合理范围规则的语言中,由于 i 在循环构造内声明,它不应该在循环外可用。这将减轻对 i 在循环结束时等于 N 的任何依赖...

于 2008-09-25T08:57:54.403 回答
2

我们不应该孤立地查看计数器 - 如果出于任何原因有人更改了计数器的递增方式,他们将更改终止条件和结果逻辑(如果 i==N 需要)。

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

于 2008-09-25T08:45:34.573 回答
1

在 C++ 中,!=为了通用性,首选使用测试。C++ 中的迭代器有多种概念,例如输入迭代器前向迭代器双向迭代器随机访问迭代器,每一个都扩展了前一个迭代器的新功能。为了<工作,需要随机访问迭代器,而!=只需要输入迭代器。

于 2008-09-25T08:45:30.873 回答
1

如果你信任你的代码,你可以做任何一个。

如果您希望您的代码可读且易于理解(从而更宽容地从您必须假设为笨拙的人更改),我会使用类似的东西;

for ( int i = 0 ; i >= 0 && i < N ; ++i) 
于 2008-09-25T08:45:54.273 回答
1

我总是使用#2,因为这样你就可以确定循环将终止......之后依赖它等于 N 是依赖于副作用......你不是更好地使用变量 N 本身吗?

[编辑]对不起......我的意思是#2

于 2008-09-25T08:46:14.243 回答
1

我认为大多数程序员都使用第二个,因为它有助于弄清楚循环内部发生了什么。我可以看一下,并且“知道”我将从 0 开始,并且肯定会小于 N。

第一个变体没有这种品质。我可以看一下,我所知道的是,我将从 0 开始,并且永远不会等于 N。不太有帮助。

不管你如何终止循环,在循环外使用循环控制变量时要非常小心。在您的示例中,您(正确地)在循环内声明 i ,因此它不在循环外的范围内,并且其值的问题没有实际意义......

当然,第二个变体还有一个优点,那就是我见过的所有 C 引用都使用它:-)

于 2008-09-25T08:49:58.883 回答
0

如果在循环外使用 i,在 c# 中使用上述任何一种都会导致编译器错误

于 2008-09-25T09:20:56.843 回答
0

我有时更喜欢这个:

for (int i = 0; (i <= (n-1)); i++) { ... }

这个版本直接显示了我可以拥有的值的范围。我对检查范围的下限和上限的看法是,如果你真的需要这个,你的代码有太多的副作用,需要重写。

另一个版本:

for (int i = 1; (i <= n); i++) { ... }

帮助您确定调用循环体的频率。这也有有效的用例。

于 2008-09-25T09:23:02.377 回答
0

一般来说,我更喜欢

for ( int i = 0; i < N; ++i )

对生产中的错误程序的惩罚似乎不那么严重,您不会让线程永远卡在 for 循环中,这种情况可能非常危险且难以诊断。

另外,一般来说,我喜欢避免使用这种循环,而使用更具可读性的 foreach 样式循环。

于 2008-09-25T08:49:23.470 回答
0

我更喜欢使用#2,只是因为我尽量不在 for 循环之外扩展 i 的含义。如果我要跟踪这样的变量,我会创建一个额外的测试。有人可能会说这是多余或低效的,但它提醒读者我的意图:在这一点上,我必须等于 N

@timyates - 我同意不应该依赖副作用

于 2008-09-25T08:50:33.763 回答
0

我认为您很好地说明了两者之间的区别。不过,我确实有以下评论:

  • 这不是“与语言无关”,我可以看到您的示例使用 C++,但是有些语言不允许您修改循环内的循环变量,而其他语言不能保证索引的值在之后可用循环(有些人两者都做)。

  • 您已经在其中声明了i 索引,for所以我不会赌i循环后的值。这些示例有点误导,因为它们暗示这for是一个明确的循环。实际上它只是一种更方便的写作方式:

    // version 1
    { int i = 0;
      while (i != N) {
        ...
        ++i;
      }
    }
    

    请注意i块之后的未定义方式。

如果程序员知道以上所有内容,就不会对 的值进行一般假设,i并且足够明智地选择i<N作为结束条件,以确保最终满足退出条件。

于 2008-09-25T08:59:31.683 回答
0

对于一般的编程工作,我更喜欢

for ( int i = 0; i < N; ++i )

for ( int i = 0; i != N; ++i )

因为它不容易出错,尤其是在代码被重构时。我曾看到这种代码不小心变成了无限循环。

这个论点提出“你会很想假设 i == N”,我不相信这是真的。我从来没有做过这样的假设,也没有见过其他程序员做出这样的假设。

于 2009-10-17T01:06:29.370 回答
0

从形式验证和自动终止分析的角度来看,我非常喜欢#2 ( <)。很容易跟踪某些变量的增加(对于某些非负数之前var = x,之后)。然而,最终成立并不那么容易。为此,需要推断出在每一步中都会精确地增加,这(在更复杂的示例中)可能会由于抽象而丢失。var = x+nni==Ni1

如果您考虑以 2 ( i = i + 2) 递增的循环,则这个一般概念会变得更容易理解。为了保证终止,现在需要知道,而在用作条件i%2 == N%2时这无关紧要。<

于 2012-10-17T15:27:11.850 回答