在for循环中,应该使用<还是<=?

129

如果你需要循环7次,你会使用以下哪种方法:

for (int i = 0; i < 7; i++)
或者:
for (int i = 0; i <= 6; i++)

有两个方面需要考虑:

  • 性能
  • 可读性

对于性能,我假设使用Java或C#。使用“小于”还是“小于等于”有影响吗?如果您对其他编程语言有了解,请注明。

对于可读性,我假设使用从0开始的数组。

更新:我之前提到的从0开始的数组可能会引起混淆。我不是在谈论迭代数组元素,只是一般的循环。

下面有一个好点子,可以使用常数来解释这个神奇的数字。因此,如果我有“int NUMBER_OF_THINGS = 7”,那么“i <= NUMBER_OF_THINGS - 1”看起来很奇怪,不是吗。


我会说:如果你遍历整个数组,永远不要在左侧加减任何数字。 - Letterman
39个回答

294

第一个更具惯用语性。特别是,它(从基于0开始的意义上)指示了迭代次数。当使用基于1的东西(例如JDBC,如我所知)时,我可能会倾向于使用<=。 所以:

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

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

我认为在实际编程中,性能差异会非常小,几乎可以忽略不计。


31
你几乎可以保证不会有性能差异。许多体系结构(如x86)都具有“在最后一次比较中跳转到小于或等于”的指令。你可能看到性能差异的最有可能的方式是某种解释性语言实现不良。 - Wedge
3
你会考虑使用 "!=" 吗?我认为这最清楚地将 i 确定为一个循环计数器而没有其他含义。 - yungchin
22
我通常不会这样做,因为太不熟悉了。如果在循环期间意外增加i,它还有可能进入一个非常非常长的循环。 - Jon Skeet
5
使用STL迭代器进行泛型编程需要使用!=。对我来说,这种(意外的双重增量)并不是问题。我同意对于小于(或对于降序是大于)的索引更清晰和惯例化。 - Jonathan Graehl
1
确实 - 因为STL习惯用法(正如wrang-wrang所提到的),我发现自己越来越经常使用!=作为循环终止测试,即使不使用迭代器。起初我有些犹豫,因为Jon/Tony the Pony的保留意见,但实践中我并没有发现这是个问题。 - Michael Burr
2
记住,如果使用 < 循环遍历数组的长度,JIT 会优化数组访问(删除边界检查)。所以使用 < 应该比使用 <= 更快。虽然我没有验证过。 - configurator

72

这两个循环都执行7次。我认为那个包含数字7的更易读/更清晰,除非你有一个真正好的理由使用另一个。


我还记得当我第一次开始学习Java时,我非常讨厌0-based索引的概念,因为我一直使用1-based索引。所以我总是使用<=6变量(如问题中所示)。这对我自己有害,因为最终会更加困惑for循环实际退出的时间。只使用<更简单。 - James Haug

56

我记得在大学学习8086汇编语言时,做以下操作能够提高性能:

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

由于存在JNS操作,即“如果没有符号就跳转”,使用它意味着每个周期后不需要进行内存查找来获取比较值,也不需要进行比较。如今,大多数编译器都会优化寄存器的使用,因此内存方面已不再重要,但仍会出现不必要的比较。

顺便提一下,在循环中放入7或6会引入“魔法数字”。为了更好的可读性,您应该使用一个具有意图清晰的名称的常量。就像这样:

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

编辑:由于有些人不理解汇编语言的相关知识,因此需要提供一个更完整的示例:

如果我们执行for (i = 0; i <= 10; i++),您需要执行以下操作:

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

如果我们使用for (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 <= 10; i++)”这段代码易读性更好通常比错过一个处理器命令要重要得多。

†其他编译器可能会有不同的操作。


4
“魔法数字”这个案例很好地说明了为什么通常最好使用<符号而不是<=符号。 - Rene Saarsoo
11
另一个版本是 "for (int i = 10; i--; )"。有些人使用 "for (int i = 10; i --> 0; )" 并假装组合符号 --> 表示“到达”的意思。 - Zayenz
2
+1 表示汇编代码。 - neuro
1
但是当实际使用循环计数器时,例如用于数组索引时,您需要执行7-i,这将淹没从反向计数获得的任何优化。 - Lie Ryan
@Lie,这仅适用于您需要按顺序处理项目的情况。对于这种循环中的大多数操作,您可以按任何顺序将它们应用于循环中的项目。例如,如果您正在搜索一个值,则无论您从列表的末尾开始向上工作还是从列表的开头开始向下工作(假设您无法预测项目可能位于列表的哪一端且内存缓存不是问题),都没有关系。 - Martin Brown
i <= 6 通常意味着进行两次减法和一次逻辑或操作。由于任何一个操作数的结果为“真”都会使逻辑或操作的整体结果为“真”,因此可以将其优化为最少1次和最多2次的减法操作。相比之下,i < 7总是只需要进行一次减法操作。因此,在一般情况下,这两个表达式中,i < 7是更高效的,尽管平均差距非常小。但正如其他人指出的那样,使用<表达式还有其他原因。 - Trunk

27

我总是使用 < array.length,因为它比 <= array.length-1 更易读。

另外,如果你知道从0开始索引,那么 < 7 就很直观地表示迭代次数。


在使用循环时,您应始终小心检查Length函数的成本。例如,如果您在C/C++中使用strlen,将大大增加执行比较所需的时间。这是因为strlen必须迭代整个字符串才能找到其答案,这可能只需要执行一次,而不是在循环的每次迭代中执行。 - Martin Brown
2
@Martin Brown:在Java(我相信C#也是如此)中,String.length和Array.length是常量,因为String是不可变的,而Array具有不可变长度。由于String.length和Array.length是字段(而不是函数调用),因此您可以确信它们必须是O(1)。至于C++,那么你为什么要首先使用C字符串呢? - Lie Ryan

18

从优化的角度来看,这并不重要。

从代码风格的角度来看,我更喜欢 < 。原因:

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

比起其他方式,使用HTML更易读。

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

另一个支持使用 < 的原因是,它可以避免很多意外的偏差错误,并且同时提供迭代次数。

另外一个支持 < 的理由是,您可以避免许多意外的“偏移值为一”的错误。


10

@Chris,你关于.NET中.Length成本高昂的说法实际上不正确,在简单类型的情况下恰好相反。

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没有被修改,并且会强制对数组进行每个索引查找的边界检查。


在Java中,.length 在某些情况下可能是昂贵的。因为它调用了 .length.size 方法。我不确定,也不自信,但想确保一下。 - Ravi Parekh

6

就性能而言,它们没有实质性的区别。因此,在解决问题时,我会使用哪个更容易理解,二者均可。


5
在C++中,我更喜欢使用!=,因为它适用于所有STL容器。并非所有STL容器迭代器都可以进行小于比较。

1
这让我有点害怕,因为有一种非常微小的可能性,即某些东西可能会迭代计数器超过我的预期值,从而使其成为无限循环。机会很小,很容易检测 - 但是使用“_feels_”更安全。 - Rob Allen
2
如果代码中存在这样的错误,崩溃和失败可能比默默地继续更好 :-) - Eggs McLaren
这是正确的答案:它对您的迭代器需求较少,并且如果代码出现错误,则更有可能显示出来。"<"的论点是短视的。也许在70年代,当您支付CPU时间时,无限循环会很糟糕。 "!="不太可能隐藏错误。 - David Nehme
1
对迭代器进行循环迭代和通过计数器进行循环迭代是完全不同的情况。"!=" 对于迭代器非常重要。 - DJClayworth

5

我愿意:

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

我认为更容易将其翻译为“循环迭代7次”。关于性能影响,我不确定 - 我怀疑任何差异都会被编译掉。

4
我认为性能上没有区别。但第二种形式更易读,你不需要在脑海中减去1来找到最后一次迭代的编号。
编辑:我看到其他人持不同意见。就我个人而言,我喜欢在循环结构中看到实际的索引号码。也许是因为它更像Perl的“0..6”语法,我知道它等同于“(0,1,2,3,4,5,6)” 。如果我看到7,我必须检查旁边的运算符,以确定实际上没有达到索引7。

这取决于您认为“最后一次迭代编号”比“迭代次数”更重要。使用基于0的API,它们始终会相差1... - Jon Skeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接