Следует ли использовать <или <= в цикле 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" выглядело бы странно, не так ли.


Я бы сказал: если вы прогоните весь массив, никогда не вычитайте и не добавляйте какое-либо число в левую часть.
Леттерман

Ответы:


287

Первый более идиоматичен . В частности, он указывает (отсчитываемое от нуля) количество итераций. При использовании чего-то на основе 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, имеют инструкции «перейти на меньшее или равное при последнем сравнении». Скорее всего, вы заметите разницу в производительности на каком-то плохо реализованном интерпретируемом языке.
Wedge

3
Не могли бы вы вместо этого использовать! =? Я бы сказал, что это наиболее четко устанавливает i как счетчик циклов и ничего больше.
yungchin

21
Обычно я бы не стал. Это слишком незнакомо. Он также рискует попасть в очень и очень длинный цикл, если кто-то случайно увеличит i во время цикла.
Джон Скит,

5
Общее программирование с итераторами STL требует использования! =. Это (случайное двойное увеличение) не было для меня проблемой. Я согласен, что для индексов <(или> по убыванию) более понятны и условны.
Jonathan Graehl

2
Помните, что если вы выполняете цикл по длине массива с помощью <, JIT оптимизирует доступ к массиву (удаляет связанные проверки). Так что это должно быть быстрее, чем использование <=. Но я не проверял
конфигуратор

72

Оба этих цикла повторяются 7 раз. Я бы сказал, что тот, у которого есть 7, более читабелен / яснее, если у вас нет действительно веской причины для другого.


Я помню, когда впервые начал изучать Java. Я ненавидел концепцию индекса с отсчетом от 0, потому что я всегда использовал индексы с отсчетом от 1. Поэтому я бы всегда использовал вариант <= 6 (как показано в вопросе). К моему собственному ущербу, потому что в конечном итоге это сбило бы меня с толку, когда цикл for действительно завершился. Проще просто использовать <
Джеймс Хауг

55

Я помню, как в те дни, когда мы делали сборку 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++)

EDIT: люди не получают сборку, поэтому, очевидно, требуется более полный пример:

Если мы делаем для (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
Случай с «магическим числом» прекрасно иллюстрирует, почему обычно лучше использовать <, чем <=.
Рене Саарсу,

11
Другая версия - «for (int i = 10; i--;)». Некоторые люди используют «for (int i = 10; i -> 0;)» и делают вид, что комбинация -> означает переход к.
Zayenz,

2
+1 за ассемблерный код
нейро

1
но когда приходит время фактически использовать счетчик циклов, например, для индексации массива, тогда вам нужно сделать, 7-iчто затмит любые оптимизации, которые вы получаете от обратного подсчета.
Ли Райан

@Lie, это применимо только в том случае, если вам нужно обработать элементы в прямом порядке. С большинством операций в таких циклах вы можете применять их к элементам цикла в любом порядке. Например, если вы ищете значение, не имеет значения, начинаете ли вы с конца списка и работаете вверх или с начала списка и работаете вниз (при условии, что вы не можете предсказать, в каком конце списка находится ваш элемент. вероятно, и кеширование памяти не проблема).
Мартин Браун

27

Я всегда использую <array.length, потому что его легче читать, чем <= array.length-1.

также имея <7 и учитывая, что вы знаете, что он начинается с индекса 0, должно быть интуитивно понятно, что число - это количество итераций.


Вы всегда должны быть осторожны, проверяя стоимость функций Length при использовании их в цикле. Например, если вы используете strlen в C / C ++, вы значительно увеличите время, необходимое для сравнения. Это потому, что strlen должен перебирать всю строку, чтобы найти свой ответ, что вы, вероятно, захотите делать только один раз, а не для каждой итерации вашего цикла.
Мартин Браун

2
@Martin Brown: в Java (и я считаю, что C #) String.length и Array.length являются постоянными, потому что String неизменяема, а Array имеет неизменную длину. А поскольку String.length и Array.length - это поле (а не вызов функции), вы можете быть уверены, что они должны быть O (1). В случае с C ++, ну, какого черта вы вообще используете C-строку?
Ли Райан

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 .Length в некоторых случаях может быть дорогостоящим. stackoverflow.com/questions/6093537/for-loop-optimization b'coz он вызывает .lenghtOR .size. Я не уверен, но и не уверен, но хочу только убедиться.
Рави Парекх

6

Когда дело доходит до производительности, это не имеет никакого значения. Поэтому я бы использовал то, что легче понять в контексте решаемой вами проблемы.


5

Я предпочитаю:

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

Я думаю, это легче переводится как «повторение цикла 7 раз».

Я не уверен в последствиях для производительности - подозреваю, что любые различия будут скомпилированы.


4

В Java 1.5 вы можете просто сделать

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

так что в случае с массивом вам не о чем беспокоиться.


4

Я не думаю, что есть разница в производительности. Вторая форма определенно более читабельна, вам не нужно мысленно вычитать единицу, чтобы найти номер последней итерации.

РЕДАКТИРОВАТЬ: Я вижу, что другие не согласны. Лично мне нравится видеть фактические номера индексов в структуре цикла. Возможно, это потому, что он больше напоминает 0..6синтаксис Perl , который, как я знаю, эквивалентен (0,1,2,3,4,5,6). Если я вижу 7, я должен проверить оператор рядом с ним, чтобы увидеть, что на самом деле индекс 7 никогда не достигается.


Это зависит от того, считаете ли вы, что «номер последней итерации» важнее, чем «количество итераций». С API на основе 0 они всегда будут отличаться на 1 ...
Джон Скит

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, и счетчик остался прежним, вынуждая меня сделать что-то ... в то время как this-> GetCount ()
osp70

GetCount () будет вызываться на каждой итерации в первом примере. Во втором примере он будет вызываться только один раз. Если вам нужно, чтобы GetCount () вызывалась каждый раз, включите его в цикл, если вы не пытаетесь оставить его снаружи.
Марк Ингрэм

Да, я попробовал, и вы правы, мои извинения.
osp70

Какая разница в использовании ++ i вместо i ++?
Рене Саарсу,

Незначительное увеличение скорости при использовании int, но увеличение может быть больше, если вы увеличиваете свои собственные классы. Обычно ++ i увеличивает фактическое значение, а затем возвращает фактическое значение. i ++ создает временную переменную, увеличивает реальную переменную на единицу, а затем возвращает временную переменную. С ++ i создание var не требуется.
Марк Ингрэм,

4

В C ++ я предпочитаю использовать !=, который можно использовать со всеми контейнерами STL. Не все итераторы контейнеров STL менее чем сопоставимы.


Это меня немного пугает только потому, что есть очень небольшая внешняя вероятность, что что-то может перебрать счетчик по моему предполагаемому значению, что затем сделает этот цикл бесконечным. Шансы малы и легко обнаруживаются, но < кажется безопаснее.
Роб Аллен

2
Если в вашем коде есть подобная ошибка, вероятно, лучше разбиться и сжечь, чем молча продолжать :-)

Это правильный ответ: он снижает нагрузку на ваш итератор и с большей вероятностью обнаружится, если в вашем коде есть ошибка. Аргумент в пользу <недальновиден. Возможно, бесконечный цикл был бы плохим в 70-х, когда вы платили за процессорное время. '! =' с меньшей вероятностью скроет ошибку.
Дэвид Нехме

1
Цикл по итераторам - это совершенно другой случай, чем цикл со счетчиком. ! = важен для итераторов.
DJClayworth

4

Эдсгер Дейкстра написал статью об этом еще в 1982 году, в которой он аргументирует нижний <= i <верхний:

Есть наименьшее натуральное число. Исключение нижней границы - как в b) и d) - вынуждает подпоследовательность, начинающуюся с наименьшего натурального числа, сделать нижнюю границу, как упомянуто, в области неестественных чисел. Это некрасиво, поэтому для нижней оценки мы предпочитаем ≤, как в а) и в). Теперь рассмотрим подпоследовательности, начинающиеся с наименьшего натурального числа: включение верхней границы заставило бы последнее быть неестественным к тому времени, когда последовательность сжалась до пустой. Это некрасиво, поэтому для верхней границы мы предпочитаем <как в а) и г). Мы заключаем, что предпочтительнее соглашение а).


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 <size по сравнению с i <= LAST_FILLED_ARRAY_SLOT
Крис Кадмор,

2

Еще в колледже я кое-что помню об этих двух операциях, которые схожи по времени вычислений на ЦП. Конечно, мы говорим на уровне сборки.

Однако, если вы говорите о C # или Java, я действительно не думаю, что один из них будет повышать скорость по сравнению с другим. Несколько наносекунд, которые вы выиграете, скорее всего, не стоят того, чтобы вы вводили путаницу.

Лично я бы написал код, который имеет смысл с точки зрения бизнес-реализации и позаботится о том, чтобы его было легко читать.


2

Это прямо подпадает под категорию «Сделать неправильный код неправильным» .

В языках индексирования с нулевым отсчетом, таких как Java или C #, люди привыкли к вариациям index < countусловий. Таким образом, использование этого фактического соглашения сделало бы отдельные ошибки более очевидными.

Что касается производительности: любой хороший компилятор, достойный своего объема памяти, не должен вызывать проблем.


2

В качестве небольшого отступления, при просмотре массива или другой коллекции в .Net я нахожу

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

быть более читабельным, чем числовой цикл for. Это, конечно, предполагает, что сам фактический счетчик Int не используется в коде цикла. Не знаю, есть ли изменение производительности.


Это также требует, чтобы вы не изменяли размер коллекции во время цикла.
Джефф Би,

Так было бы For (i = 0, i <myarray.count, i ++)
Роб Аллен,

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.

Вы можете увидеть результаты здесь . Из этого не ясно, что если я поменяю местами 1-й и 2-й тесты, результаты этих 2-х тестов поменяются местами, это явно проблема памяти. Однако третий тест, в котором я меняю порядок итерации, явно быстрее.


почему вы начинаете с i = 1 во втором случае?
Юджин Кац

Хммм, наверное, глупая ошибка? Я сделал это довольно быстро, минут 15.
Дэвид Вис,

1

Как все говорят, принято использовать итераторы с нулевым индексом даже для вещей вне массивов. Если все начинается 0и заканчивается в n-1, а нижние границы всегда, <=а верхние границы всегда <, гораздо меньше размышлений, которые вам нужно делать при просмотре кода.


1

Отличный вопрос. Мой ответ: используйте тип A ('<')

  • Вы четко видите, сколько у вас итераций (7).
  • Разница между двумя конечными точками - это ширина диапазона
  • Меньше символов делает его более читаемым
  • Чаще всего у вас есть общее количество элементов, i < strlen(s)а не индекс последнего элемента, поэтому важна единообразие.

Другая проблема связана с этой конструкцией. iпоявляется в нем 3 раза , так что можно опечататься. Конструкция for-loop говорит, как делать, а не что делать . Я предлагаю принять это:

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 оба используют одинаковое количество тактов, поэтому <и <= занимают одинаковое количество времени.
Джейкоб Кролл
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.