【问题标题】:Should one use < or <= in a for loop [closed]是否应该在 for 循环中使用 < 或 <= [关闭]
【发布时间】:2010-09-16 00:07:48
【问题描述】:

如果你必须循环 7 次,你会使用:

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

或:

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

有两个考虑:

  • 性能
  • 可读性

为了性能,我假设使用 Java 或 C#。使用“小于”或“小于或等于”是否重要?如果您对其他语言有见解,请指出是哪一种。

为了便于阅读,我假设基于 0 的数组。

UPD:我提到的基于 0 的数组可能会造成混淆。我不是在谈论遍历数组元素。只是一个一般的循环。

下面有一个很好的观点,关于使用常数可以解释这个幻数是什么。所以如果我有“int NUMBER_OF_THINGS = 7”,那么“i &lt;= NUMBER_OF_THINGS - 1”会看起来很奇怪,不是吗。

【问题讨论】:

  • 我会说:如果您遍历整个数组,请不要在左侧减去或添加任何数字。

标签: performance conventions readability


【解决方案1】:

顺便说一句,在 .Net 中循环遍历数组或其他集合时,我发现

foreach (string item in myarray)
{
    System.Console.WriteLine(item);
}

比数字 for 循环更具可读性。这当然假设循环代码中没有使用实际的计数器 Int 本身。不知道性能有没有变化。

【讨论】:

  • 这也要求您在循环期间不要修改集合大小。
  • For(i = 0, i
【解决方案2】:

第一个是idiomatic。特别是,它表示(在基于 0 的意义上)迭代次数。当使用基于 1 的东西(例如 JDBC、IIRC)时,我可能会想使用

for (int i=0; i < count; i++) // For 0-based APIs

for (int i=1; i <= count; i++) // For 1-based APIs

我希望实际代码中的性能差异很小。

【讨论】:

  • 几乎可以保证不会有性能差异。许多体系结构,如 x86,都有“在上次比较中小于或等于跳转”指令。您最有可能看到性能差异的方式是使用某种执行不善的解释语言。
  • 您会考虑改用 != 吗?我想说的是,这最清楚地将 i 建立为循环计数器,仅此而已。
  • 我通常不会。简直太陌生了​​。如果有人在循环期间不小心增加了 i,它也有可能进入一个非常非常长的循环。
  • 使用 STL 迭代器的通用编程要求使用 !=。它(意外的双倍递增)对我来说不是问题。我确实同意索引 用于降序)更清晰和传统。
  • 请记住,如果您使用
【解决方案3】:

您也可以改用!=。这样,如果您在初始化时出错,您将获得一个无限循环,从而导致更早发现错误,并且它导致的任何问题都仅限于陷入循环中(而不是在很久以后出现问题并且找不到它)。

【讨论】:

    【解决方案4】:

    首先,不要使用 6 或 7。

    更好用:

    int numberOfDays = 7;
    for (int day = 0; day < numberOfDays ; day++){
    
    }
    

    在这种情况下它比使用更好

    for (int day = 0; day <= numberOfDays  - 1; day++){
    
    }
    

    更好(Java / C#):

    for(int day = 0; day < dayArray.Length; i++){
    
    }
    

    甚至更好(C#)

    foreach (int day in days){// day : days in Java
    
    }
    

    反向循环确实更快,但由于它更难阅读(如果不是你被其他程序员阅读),最好避免使用。尤其是在 C#、Java 中......

    【讨论】:

      【解决方案5】:

      这么多答案......但我相信我有一些东西要补充。

      我更喜欢用文字数字清楚地显示“i”将在循环中取什么值。所以在迭代一个从零开始的数组的情况下:

      for (int i = 0; i &lt;= array.Length - 1; ++i)

      如果您只是循环,而不是遍历数组,那么从 1 计数到 7 非常直观:

      for (int i = 1; i &lt;= 7; ++i)

      在您对其进行分析之前,可读性胜过性能,因为在此之前您可能不知道编译器或运行时将如何处理您的代码。

      【讨论】:

        【解决方案6】:

        我记得我们在大学做 8086 组装的时候,它的性能更高:

        for (int i = 6; i > -1; i--)
        

        因为有一个JNS 操作意味着如果没有符号则跳转。使用这意味着在每个周期之后没有内存查找来获取比较值,也没有比较。如今,大多数编译器都优化了寄存器的使用,因此内存不再重要,但您仍然可以进行不必要的比较。

        顺便说一下,将 7 或 6 放入循环中就是引入了“magic number”。为了更好的可读性,您应该使用带有 Intent Revealing Name 的常量。像这样:

        const int NUMBER_OF_CARS = 7;
        for (int i = 0; i < NUMBER_OF_CARS; i++)
        

        编辑:人们没有得到组装的东西,所以显然需要一个更完整的例子:

        如果我们为 (i = 0; i

            mov esi, 0
        loopStartLabel:
                        ; Do some stuff
            inc esi
                        ; Note cmp command on next line
            cmp esi, 10
            jle exitLoopLabel
            jmp loopStartLabel
        exitLoopLabel:
        

        如果我们为 (int i = 10; i > -1; i--) 做,那么你就可以摆脱这个:

            mov esi, 10
        loopStartLabel:
                        ; Do some stuff
            dec esi
                        ; Note no cmp command on next line
            jns exitLoopLabel
            jmp loopStartLabel
        exitLoopLabel:
        

        我刚刚查了一下,微软的 C++ 编译器没有做这个优化,但是如果你做了,它会做:

        for (int i = 10; i >= 0; i--) 
        

        因此,如果您使用的是 Microsoft C++†,并且升序或降序没有区别,那么为了获得一个快速循环,您应该使用:

        for (int i = 10; i >= 0; i--)
        

        而不是以下任何一种:

        for (int i = 10; i > -1; i--)
        for (int i = 0; i <= 10; i++)
        

        但坦率地说,获得“for (int i = 0; i

        † 其他编译器可能会做不同的事情。

        【讨论】:

        • “幻数”案例很好地说明了为什么通常使用
        • 另一个版本是“for (int i = 10; i--; )”。有些人使用“for (int i = 10; i --> 0; )”并假装组合-->表示去。
        • +1 表示汇编代码
        • 但是到了实际使用循环计数器的时候,例如对于数组索引,那么您需要执行 7-i 这将淹没您从倒数计数中获得的任何优化。
        • @Lie,这仅适用于您需要按顺序处理项目的情况。对于此类循环中的大多数操作,您可以按照您喜欢的任何顺序将它们应用于循环中的项目。例如,如果您正在搜索一个值,无论您是从列表的末尾开始向上工作还是从列表的开头向下工作(假设您无法预测您的项目在列表的哪一端)都没有关系可能是,内存缓存不是问题)。
        【解决方案7】:

        很好的问题。我的回答:使用A型('

        • 您清楚地看到您有多少次迭代 (7)。
        • 两个端点之间的差异是范围的宽度
        • 更少的字符使其更具可读性
        • 更多时候是元素总数i &lt; strlen(s),而不是最后一个元素的索引,因此一致性很重要。

        另一个问题是整个结构。 i 在其中出现 3 次,因此可能会输入错误。 for 循环结构表示如何做,而不是做什么。我建议采用这个:

        BOOST_FOREACH(i, IntegerInterval(0,7))

        这个更清楚,编译成完全一样的asm指令等等。如果你愿意,可以问我IntegerInterval的代码。

        【讨论】:

          【解决方案8】:

          在性能方面没有任何实际影响。因此,我会在您正在解决的问题的上下文中使用更容易理解的那个。

          【讨论】:

            【解决方案9】:

            我认为应该是

            为什么要使用很多词,而少数人会这样做。一项测试比两项更容易理解。因此,以后更容易进行单元测试和修改。

            差别很小吗?是的。但是,为什么在没有必要的情况下增加任何复杂性。

            最后,当代码开始优化时,您不依赖于任何优化器或解释器的实现。引用爱因斯坦的话,“尽可能简单,但不要简单”。

            【讨论】:

            • You are using why in a sentence without question mark ;) 引用 python 的核心原则,可通过在 python 控制台中键入 import this 获得:显式是优于隐式可读性计数
            【解决方案10】:

            没有速度差异,但

            for (i = 7; --i >= 0; ) ...
            

            【讨论】:

              【解决方案11】:

              过早的优化是万恶之源。除非有充分的理由担心&lt; 而不是&lt;=,否则保持可读性。

              【讨论】:

                【解决方案12】:

                Edsger Dijkstra wrote an article 在 1982 年的这篇文章中,他主张更低

                有一个最小的自然数。排除下界(如 b)和 d)中的下界会强制从最小自然数开始的子序列将下界作为非自然数领域中提到的。这很难看,所以对于下界,我们更喜欢 a) 和 c) 中的 ≤。现在考虑从最小自然数开始的子序列:包含上限将迫使后者在序列缩小到空序列时变得不自然。这很难看,所以对于上限,我们更喜欢

                【讨论】:

                  【解决方案13】:

                  这直接属于"Making Wrong Code Look Wrong" 的类别。

                  在 Java 或 C# 等从零开始的索引语言中,人们习惯于 index &lt; count 条件的变体。因此,利用这一事实上的约定将使错误的错误更加明显。

                  关于性能:任何值得其内存占用的好的编译器都应该呈现为非问题。

                  【讨论】:

                    【解决方案14】:

                    结果没有意义。

                    从硬件的角度来看,带有 loopNumber-1 的

                    【讨论】:

                    • 这仅在您的循环计数是动态的(即数组长度)时才重要。在最初的例子中,他使用了一个固定常数——这消除了减法运算。
                    【解决方案15】:

                    正如大家所说,习惯上使用 0 索引的迭代器,即使是数组之外的东西。如果所有内容都以0 开始并以n-1 结束,并且下限始终为&lt;= 而上限始终为&lt;,那么您在查看代码时就不必考虑那么多了。

                    【讨论】:

                      【解决方案16】:

                      @Chris,您关于 .Length 在 .NET 中代价高昂的说法实际上是不真实的,而在简单类型的情况下恰恰相反。

                      int len = somearray.Length;
                      for(i = 0; i < len; i++)
                      {
                        somearray[i].something();
                      }
                      

                      实际上比

                      for(i = 0; i < somearray.Length; i++)
                      {
                        somearray[i].something();
                      }
                      

                      后者是由运行时优化的情况。由于运行时可以保证 i 是数组的有效索引,因此不会进行边界检查。在前者中,运行时不能保证 i 在循环之前没有被修改,并强制对数组进行边界检查以进行每次索引查找。

                      【讨论】:

                      【解决方案17】:

                      正如人们所观察到的,您提到的两种选择中的任何一种都没有区别。为了确认这一点,我在 JavaScript 中做了一些简单的基准测试。

                      您可以看到结果here。不清楚的是,如果我交换第一次和第二次测试的位置,那么这两个测试的结果交换,这显然是一个内存问题。然而,第 3 次测试,即我颠倒迭代顺序的测试显然更快。

                      【讨论】:

                      • 第二种情况为什么要从 i = 1 开始?
                      • 嗯,可能是个愚蠢的错误?我很快就完成了,大概 15 分钟。
                      【解决方案18】:

                      对于某些语言/技术,例如 .NET,使用 .Size 或 .Length 或 size()/length() 是一个坏主意,因为它每次迭代时都会访问该属性,因此将其分配给变量的性能稍差命中。

                      【讨论】:

                      • 如果我必须将如此简单的操作分配给一个变量以获得高性能代码,那么我宁愿改用更智能的语言/编译器。
                      【解决方案19】:

                      我更喜欢这个:

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

                      但是(这只是一个想法),它的可读性可能与数组是基于 0(C#、Java)还是基于 1(VB .NET)有关。我这样说是因为当你使用基于 0 的数组时,你会认为 0-6 会运行 7 次。我认为 0-6 比 1-7 更直观。再说一次,我来自 C++、Java、C# 背景。

                      【讨论】:

                        【解决方案20】:

                        '

                        '

                        使用 ++i 代替 i++ 可以提​​高 C++ 中的性能,但不能在 C# 中提高性能 - 我不了解 Java。

                        【讨论】:

                          【解决方案21】:

                          在 C++ 中,我更喜欢使用 !=,它适用于所有 STL 容器。并非所有 STL 容器迭代器都具有可比性。

                          【讨论】:

                          • 这让我有点害怕,因为有一个非常小的外部机会可能会在我的预期值上迭代计数器,然后使它成为一个无限循环。机会微乎其微且易于检测 - 但 感觉 更安全。
                          • 如果你的代码中存在这样的错误,那么崩溃和烧毁可能比默默地继续更好:-)
                          • 这是正确的答案:它对您的迭代器的要求更少,并且如果您的代码中有错误,它更有可能出现。
                          • 遍历迭代器与使用计数器循环是完全不同的情况。 != 对于迭代器来说是必不可少的。
                          【解决方案22】:

                          不要使用幻数。

                          为什么是 7? (或 6 个)。

                          为您要使用的数字使用正确的符号...

                          在这种情况下我认为最好使用

                          for ( int i = 0; i < array.size(); i++ )
                          

                          【讨论】:

                            【解决方案23】:

                            当您遍历数组时,养成使用

                            这几乎肯定比

                            另一个注意事项是养成使用 ++i 而不是 i++ 的习惯会更好,因为 fetch 和 increment 需要一个临时的 and increment 而 fetch 不需要。对于整数,你的编译器可能会优化掉临时变量,但如果你的迭代类型更复杂,它可能无法做到。

                            【讨论】:

                              【解决方案24】:

                              首先考虑可读性,然后再优化(尽管老实说,我无法想象有什么明显的区别)。

                              请注意,0 -> K 形式是 C# 中的 C 习语,通过使数组基于 0。遵循成语,不要违反最小惊讶原则。

                              【讨论】:

                                【解决方案25】:

                                我使用第一种方法,因为该成语经常重复。

                                for (int index = 0; index

                                String s = oldString.substring(0, numChars);

                                等等。

                                我已经习惯了被排除在外的上限,除非有充分的理由更改它,否则我更愿意保持这种状态。 (示例 -- 基于 1 的索引)

                                【讨论】:

                                  【解决方案26】:

                                  至少与 x86 编译器的性能应该没有差异。据我所知,JL 和 JLE 同时工作。至于可重复性,对包含七个元素的数组使用“

                                  【讨论】:

                                    【解决方案27】:

                                    我一直喜欢:

                                    for ( int count = 7 ; count > 0 ; -- count )
                                    

                                    【讨论】:

                                    • 你的理由是什么?我真的很感兴趣。
                                    • 这对于反向循环来说非常好......如果你需要这样的东西
                                    • 一个原因是在 uP 级别与 0 相比速度更快。另一个是它对我来说很好读,并且计数让我可以轻松地指示还剩下多少次。
                                    【解决方案28】:

                                    写 i

                                    【讨论】:

                                      【解决方案29】:

                                      严格从逻辑的角度来看,您必须认为&lt; count 会比&lt;= count 更有效,因为&lt;= 也将测试相等性。

                                      【讨论】:

                                      • 我不这么认为,在汇编程序中归结为 cmp eax、7 jl LOOP_START 或 cmp eax、6 jle LOOP_START 都需要相同数量的周期。
                                      • )
                                      • !(>) 仍然是两条指令,但 Treb 是正确的,JLE 和 JL 都使用相同数量的时钟周期,因此
                                      【解决方案30】:

                                      我会说使用“

                                      我不会担心“

                                      如果您确实想提高速度,请考虑以下事项:

                                      for (int i = 0; i < this->GetCount(); i++)
                                      {
                                        // Do something
                                      }
                                      

                                      为了提高性能,您可以将其稍微重新排列为:

                                      const int count = this->GetCount();
                                      for (int i = 0; i < count; ++i)
                                      {
                                        // Do something
                                      }
                                      

                                      请注意从循环中删除 GetCount()(因为将在每个循环中查询)以及将“i++”更改为“++i”。

                                      【讨论】:

                                      • 我更喜欢第二个,因为它更容易阅读,但它真的每次都重新计算 this->GetCount() 吗?更改 this 时我被这个抓住了,计数保持不变,迫使我做一个 do..while this->GetCount()
                                      • 在第一个示例中,每次迭代都会调用 GetCount()。在第二个例子中它只会被调用一次。如果您需要 GetCount() 每次都被调用,那么如果您不尝试将其保留在循环中,则将其置于循环中。
                                      • 是的,我试过了,你是对的,我很抱歉。
                                      • 使用 ++i 而不是 i++ 有什么区别?
                                      • 使用整数时速度略有提高,但如果您增加自己的类,速度可能会更大。基本上 ++i 增加实际值,然后返回实际值。 i++ 创建一个 temp var,递增 real var,然后返回 temp。 ++i 不需要创建 var。
                                      猜你喜欢
                                      • 2013-08-07
                                      • 1970-01-01
                                      • 2021-08-16
                                      • 2019-03-23
                                      • 2018-07-30
                                      • 1970-01-01
                                      • 2012-02-15
                                      • 1970-01-01
                                      • 2018-02-22
                                      相关资源
                                      最近更新 更多