应该在for循环中使用<或<=


124

如果您必须迭代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 <= NUMBER_OF_THINGS - 1”看起来会很奇怪,不是吗。


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

Answers:


287

首先是比较惯用的。特别是,它指示(从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

我希望在实际代码中,性能差异不会很小。


30
您几乎可以保证不会有性能差异。许多架构(例如x86)都具有“在上一次比较中小于或等于跳转”的指令。您可能会发现性能差异的最可能方式是某种解释性语言实施不善。
楔子

3
您会考虑使用!=代替吗?我要说的是,这最清楚地将i设置为循环计数器,仅此而已。
yungchin

21
我通常不会。太陌生了。如果有人在循环中意外增加i,也有进入非常长循环的风险。
乔恩·斯基特

5
使用STL迭代器的通用编程要求使用!=。对我来说,它(偶然的双重递增)不是问题。我确实同意,对于索引<(或对于降序)而言,更清晰和常规。
乔纳森·格雷尔

2
请记住,如果使用<循环遍历数组的Length,则JIT会优化数组访问(删除绑定检查)。因此使用<=应该更快。我没有检查过
配置器2010年

72

这些循环都重复7次。我想说一个带有7的字母更具可读性/清晰性,除非您确实有充分的理由选择另一个。


我记得我刚开始学习Java的时候。我讨厌基于0的索引的概念,因为我一直使用基于1的索引。因此,我将始终使用<= 6变体(如问题所示)。对我自己不利,因为这最终会使我更加困惑何时真正退出for循环。只需使用<
James Haug

55

我记得从大学时代开始做8086大会的那一天,我的表现更加出色:

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

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

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

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

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

如果我们这样做(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:

如果我们这样做(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:

我刚刚检查了一下,Microsoft的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,仅在您需要按向前顺序处理项目时适用。通过此类循环中的大多数操作,您可以按自己喜欢的顺序将它们应用于循环中的项目。例如,如果您要搜索一个值,则从列表的末尾开始并向上处理还是在列表的起始处并向下进行都没关系(假设您无法预测项目在列表的末尾)确实如此,内存缓存不是问题)。
马丁·布朗

27

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

也具有<7,并且假设您知道它以0索引开头,那么直观的数字就是迭代次数。


在循环中使用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-string?
Lie Ryan

18

从优化角度看,这没关系。

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

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

比它更具可读性

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

还<可以立即为您提供迭代次数。

<的另一种投票方式是,您可以防止很多意外的一次性错误。


10

@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是数组的有效索引,因此不进行边界检查。在前一种情况下,运行时无法保证不会在循环之前对我进行修改,并且会针对每次索引查找对数组强制执行边界检查。


在Java中,在某些情况下,长度可能会很昂贵。 stackoverflow.com/questions/6093537/for-loop-optimization b'coz调用.lenghtOR .size。我不确定但又不自信,但只想确定。
拉维·帕雷克

6

在性能方面没有任何有效的区别。因此,在您要解决的问题中,我将使用较容易理解的方式。



4

在Java 1.5中,您可以做

for (int i: myArray) {
    ...
}

因此对于阵列的情况,您不必担心。


4

我认为没有性能差异。不过,第二种形式绝对更易读,您不必费心地减去一个就可以找到最后的迭代数。

编辑:我看到其他人不同意。就我个人而言,我喜欢在循环结构中看到实际的索引号。也许是因为它使人联想到Perl的0..6语法,我知道它等同于(0,1,2,3,4,5,6)。如果看到7,则必须检查它旁边的运算符,以查看实际上从未到达索引7。


这取决于您是否认为“上次迭代次数”比“迭代次数”更重要。使用基于0的API,它们将始终相差1 ...
Jon Skeet

4

我会说使用“ <7”版本,因为这是大多数人会读的内容-因此,如果人们略读了您的代码,他们可能会错误地解释它。

我不会担心“ <”是否比“ <=”更快,只是为了提高可读性。

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

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时,我一直被它吸引住了,计数仍然保持不变,这迫使我去做一个..while this-> GetCount()
osp70

在第一个示例中,每次迭代都会调用GetCount()。在第二个示例中,它将仅被调用一次。如果您需要每次都调用GetCount(),则可以将其放入循环中(如果您不尝试将其保留在外部)。
马克·英格拉姆

是的,我确实尝试过,对不起,我很抱歉。
osp70

在i ++上使用++ i有什么区别?
Rene Saarsoo,

使用int时,速度会稍有提高,但是如果您要增加自己的班级,则增加的幅度可能更大。基本上,++ i会增加实际值,然后返回实际值。i ++创建一个临时变量,增加实变量,然后返回临时变量。++ i不需要创建var。
马克·英格拉姆

4

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


这使我有些害怕,只是因为有很小的外部机会,某些东西可能会使计数器超出我的预期值,从而使它成为无限循环。机会很少,很容易发现-但是< 感觉更安全。
罗伯·艾伦,

2
如果您的代码中存在类似的错误,崩溃和刻录比静默继续运行更好:-)

这是正确的答案:它对迭代器的需求较少,并且如果代码中有错误,则更有可能显示出来。<的参数是短视的。也许在您为CPU时间付出代价时,无限循环可能会回到70年代。'!='不太可能隐藏错误。
David Nehme,

1
遍历迭代器与使用计数器循环完全不同。!=对于迭代器至关重要。
DJClayworth

4

Edsger Dijkstra 早在1982年就在此发表了一篇文章,他主张更低的<= i <更高的:

有一个最小的自然数。排除下限(如b)和d)中所示,强制从最小自然数开始的子序列将下限引入非自然数域中。这很丑陋,因此对于下限,我们更喜欢a)和c)中的≤。现在考虑从最小自然数开始的子序列:包含上限将在序列缩小为空数时迫使后者成为非自然数。这很丑陋,因此对于上限,我们更喜欢<,如a)和d)。我们得出结论,约定a)是首选。


3

首先,请勿使用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中...


2

我同意人群的意见,认为在这种情况下7是有意义的,但我要补充一点,在6非常重要的情况下,请说您想弄清楚您只对达到6索引的对象起作用,然后<=更好,因为它使6更容易看清。


因此,i <与i <= LAST_FILLED_ARRAY_SLOT相比的大小
克里斯·

2

回到大学时,我记得这两个操作在CPU上的计算时间相似。当然,我们在装配级别上谈不上。

但是,如果您使用的是C#或Java,我真的不认为一个方法会比另一个方法提速。您获得的几纳秒的时间很可能不会引起您的困惑。

就个人而言,我将编写从业务实现角度讲有意义的代码,并确保它易于阅读。



2

稍微说一点,当遍历.Net中的数组或其他集合时,我发现

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

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


这还要求您在循环期间不要修改集合大小。
杰夫·B

For(i = 0,i <myarray.count,i ++)
Rob Allen

1

写i <7有很多充分的理由。将数字7循环迭代7次是很好的。性能实际上是相同的。几乎每个人都写i <7。如果您出于可读性考虑写作,请使用每个人都会立即识别的格式。


1

我一直喜欢:

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

你的理由是什么?我真的很感兴趣
克里斯·库德莫

反向循环非常好..如果您需要这样的东西
ShoeLace

1
原因之一是在uP级别比较快为0。另一个是,它对我来说读起来很不错,而计数可以让我很容易地知道还剩下多少次。
肯尼,

1

习惯使用<会使遍历数组时对您和读者保持一致。每个人都有一个标准约定会更简单。而且,如果您使用的是基于0的数组的语言,则<是惯例。

这几乎肯定比<和<=之间的任何性能差异都重要。首先要实现功能性和可读性,然后进行优化。

另一个要注意的是,养成使用++ i而不是i ++的习惯会更好,因为获取和增量需要一个临时的增量,而获取不需要。对于整数,编译器可能会优化临时变量,但是如果您的迭代类型更复杂,则可能无法。


1

不要使用幻数。

为什么是7?(或6)。

为您要使用的号码使用正确的符号...

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

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

1

“ <”和“ <=”运算符的性能成本完全相同。

“ <”运算符是标准的,在从零开始的循环中更易于阅读。

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


1

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

您可以在此处查看结果。由此不清楚的是,如果我交换第一个和第二个测试的位置,那两个测试的结果会交换,这显然是内存问题。但是,第3个测试(我颠倒迭代顺序的测试)显然更快。


为什么在第二种情况下以i = 1开始?
Eugene Katz

嗯,可能是一个愚蠢的错误?我很快就把它搅打了一下,也许是15分钟。
David Wees

1

众所周知,习惯于使用0索引的迭代器,即使对于数组之外的东西也是如此。如果一切都始于,0结束于n-1,并且下限始终是<=,上限始终是<,那么在检查代码时,您不必做很多事情。


1

好问题。我的答案:使用A型('<')

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

整个结构还有另一个问题。在其中i出现3次,因此可能输入错误。for循环结构说明了如何做而不是做什么。我建议采用以下方法:

BOOST_FOREACH(i, IntegerInterval(0,7))

这更加清楚,可以编译为相同的asm指令,等等。如果您愿意,请问我IntegerInterval的代码。


1

这么多答案...但是我相信我要补充一点。

我的首选是使文字数字清楚地表明循环中将使用“ i”值。因此,在遍历从零开始的数组的情况下:

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

而且,如果只是循环而不是遍历数组,则从1到7的计数非常直观:

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

除非对性能进行概要分析,否则可读性会比性能更重要,因为在此之前,您可能不知道编译器或运行时将对代码进行什么处理。


1

您也可以!=改用。这样,如果您在初始化过程中出错,将导致无限循环,从而导致该错误更早被注意到,并且它引起的任何问题都被限制在循环中(而不是在以后出现问题而不是发现问题)它)。


0

我认为两者都可以,但是当您选择后,请坚持其中一个。如果您习惯使用<=,请尽量不要使用<,反之亦然。

我更喜欢<=,但是在您使用从零开始的索引的情况下,我可能会尝试使用<。这都是个人喜好。


0

从逻辑的角度严格来说,您必须认为这< count将比<= count出于同样的原因(<=将测试相等性)更有效。


我不这么认为,在汇编程序中,它归结为cmp eax,7 jl LOOP_START或cmp eax,6 jle LOOP_START都需要相同数量的循环。
Treb

<=可以实现为!(>)
JohnMcG,

!(>)仍然是两条指令,但是Treb正确地说JLE和JL都使用相同数量的时钟周期,因此<和<=占用相同的时间。
Jacob Krall
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.