Следует ли использовать <или <= в цикле 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++ )

также <сразу дает вам количество итераций.

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

erlando
источник
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) {
    ...
}

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

JesperE
источник
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 менее чем сопоставимы.

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

Carra
источник
2

Я согласен с толпой, утверждающей, что в данном случае 7 имеет смысл, но я бы добавил, что в случае, когда важна 6, скажем, вы хотите прояснить, что вы воздействуете только на объекты до 6-го индекса, тогда <= лучше, так как это облегчает просмотр 6.

tloach
источник
Итак, i <size по сравнению с i <= LAST_FILLED_ARRAY_SLOT
Крис Кадмор,
2

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

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

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

casademora
источник
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. Если вы пишете для удобства чтения, используйте форму, которую все сразу узнают.

DJClayworth
источник
1

Я всегда предпочитал:

for ( int count = 7 ; count > 0 ; -- count )
Kenny
источник
В чем ваше объяснение? Мне искренне интересно.
Крис Кадмор
это отлично подходит для обратной петли .. если вам когда-нибудь понадобится такая вещь
ShoeLace
1
Одна из причин заключается в том, что на уровне uP сравнение с 0 происходит быстро. Во-вторых, он хорошо читается, и счет дает мне легкое представление о том, сколько еще раз осталось.
Кенни
1

Привычка использовать <сделает его согласованным как для вас, так и для читателя, когда вы выполняете итерацию по массиву. Всем будет проще иметь стандартное соглашение. А если вы используете язык с массивами, начинающимися с 0, то условием является <.

Это почти наверняка имеет большее значение, чем разница в производительности между <и <=. Сначала стремитесь к функциональности и удобочитаемости, а затем оптимизируйте.

Еще одно замечание: было бы лучше иметь привычку делать ++ i, а не i ++, поскольку для выборки и увеличения требуются временные значения, а для увеличения и выборки - нет. Для целых чисел ваш компилятор, вероятно, оптимизирует временное отключение, но если ваш повторяющийся тип более сложен, он может быть не в состоянии.

JohnMcG
источник
1

Не используйте магические числа.

Почему 7? (или 6, если на то пошло).

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

В этом случае я думаю, что лучше использовать

for ( int i = 0; i < array.size(); i++ )
Krakkos
источник
1

Операторы '<' и '<=' имеют одинаковую стоимость производительности.

Оператор «<» является стандартным и его легче читать в цикле с отсчетом от нуля.

Использование ++ i вместо i ++ улучшает производительность в C ++, но не в C # - я не знаю о Java.

Джефф Б.
источник
1

Как люди заметили, нет никакой разницы ни в одной из двух упомянутых вами альтернатив. Чтобы подтвердить это, я провел несколько простых тестов в JavaScript.

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

Дэвид Уиз
источник
почему вы начинаете с i = 1 во втором случае?
Юджин Кац
Хммм, наверное, глупая ошибка? Я сделал это довольно быстро, минут 15.
Дэвид Вис,
1

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

ephemient
источник
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

Я думаю, что оба варианта подходят, но когда вы выбрали, придерживайтесь одного или другого. Если вы привыкли использовать <=, постарайтесь не использовать <и наоборот.

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

seanyboy
источник
0

Строго с логической точки зрения, вы должны думать, что это < countбыло бы более эффективно, чем <= countпо той причине, по которой <=также будет проверяться равенство.

Генри Б.
источник
Я так не думаю, в ассемблере все сводится к cmp eax, 7 jl LOOP_START или cmp eax, 6 jle LOOP_START оба требуют одинакового количества циклов.
Treb
<= может быть реализовано как! (>)
JohnMcG
! (>) по-прежнему две инструкции, но Treb прав, что JLE и JL оба используют одинаковое количество тактов, поэтому <и <= занимают одинаковое количество времени.
Джейкоб Кролл