【问题标题】: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 &gt;= N 的弱保证,但您会很容易假设i == N

如果由于任何原因将增量 ++i 更改为类似 i += 2,或者如果 i 在循环内被修改,或者如果 N 为负数,则程序可能会失败:

  • 第一个可能会陷入无限循环。它在有错误的循环中提前失败。调试很简单。

  • 第二个循环将终止,并且由于您对i == N 的错误假设,稍后程序可能会失败。它可能会在远离导致错误的循环的地方失败,从而难以追溯。或者它可以默默地继续做一些意想不到的事情,这更糟糕。

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

【问题讨论】:

  • 对于那些提到 i 在 for 之外不可用的人,请注意,在推理程序时可以使用终止时的 i 值。例如,如果你想用一个保持不变 P(i) 的循环来建立 P(N),那么 i==N 会给你 P(N) 而 i>=N 不会。
  • +1 表示写得很好的问题,清楚地表达了每个选项的优点。

标签: language-agnostic for-loop conditional correctness


【解决方案1】:

在 C++ 中,为了通用性,首选使用 != 测试。 C++ 中的迭代器有多种概念,如input iteratorforward iteratorbidirectional iteratorrandom access iterator,每一个都扩展了前一个概念,并提供了新的功能。要使&lt; 工作,需要随机访问迭代器,而!= 只需要输入迭代器。

【讨论】:

    【解决方案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&lt;N; ++$i ){...}
                【解决方案9】:

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

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

                • 您已声明i for 内的索引,所以我不会在循环后押注i 的值。 这些示例有点误导,因为它们隐含地假设 for 是 一个确定的循环。实际上它只是一种更方便的写作方式:

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

                  注意i 在块之后是如何未定义的。

                如果程序员知道以上所有内容,就不会对i的值做出一般假设,而是明智地选择i&lt;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 (&lt;)。很容易跟踪某些变量的增加(在var = x 之前,在var = x+n 之后对于一些非负数n)。但是,要看到i==N 最终成立并不容易。为此,需要推断i 在每一步中恰好增加了1,这(在更复杂的示例中)可能由于抽象而丢失。

                        如果您考虑以 2 为增量的循环 (i = i + 2),这个总体思路就会变得更容易理解。为了保证终止,现在需要知道i%2 == N%2,而当使用&lt; 作为条件时,这无关紧要。

                        【讨论】:

                          猜你喜欢
                          • 2017-02-09
                          • 2023-03-30
                          • 2017-08-10
                          • 1970-01-01
                          • 2012-08-20
                          • 1970-01-01
                          • 2017-08-31
                          • 2020-07-18
                          • 1970-01-01
                          相关资源
                          最近更新 更多