Якщо слід використовувати <або <= у циклі 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

Перший - більш ідіоматичний . Зокрема, він вказує (у значенні 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
Чи можете ви замість цього використати! = Я б сказав, що це найбільш чітко встановлює я як лічильник циклу і більше нічого.
юнчин

21
Я зазвичай не хотів. Це занадто незнайомо. Це також ризикує потрапити в дуже, дуже довгий цикл, якщо хтось випадково зростає i під час циклу.
Джон Скіт

5
Узагальнене програмування з ітераторами STL вимагає використання! =. Це (випадкове подвійне збільшення) для мене не було проблемою. Я згоден, що для індексів <(або> для зменшення) більш чіткі та звичайні.
Джонатан Грейл

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

72

Обидві петлі повторюються 7 разів. Я б сказав, що той, у якого є 7, є більш зрозумілим / зрозумілішим, якщо тільки у вас немає поважної причини для іншого.


Пам’ятаю, коли я вперше почав вивчати Java. Я ненавидів концепцію індексу на основі 0, тому що я завжди використовував 1-базисні індекси. Тому я завжди використовував би варіант <= 6 (як показано в запитанні). На мій шкоду, тому що це збентежить мене з часом, коли цикл for фактично вийшов. Простіше просто використовувати <
James Haug

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;)" і роблять вигляд, що комбінація -> означає.
Заєнц

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-string?
Лі Лі Раян

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();
}

Пізніше - випадок, оптимізований під час виконання. Оскільки час виконання може гарантувати, що я є дійсним індексом у масиві, перевірка меж не проводиться. У першому, час виконання не може гарантувати, що я не був змінений до циклу і примушує перевіряти межі масиву для кожного пошуку індексу.


У Java .Length в деяких випадках може бути дорогим. stackoverflow.com/questions/6093537/for-loop-optimization b'coz ній виклики .lenghtАБО .size. Я не впевнений, але не впевнений, але хочу лише переконатися.
Раві Парех

6

Що стосується продуктивності, це не має жодної ефективної різниці. Тому я б використовував те, що легше зрозуміти в контексті проблеми, яку ви вирішуєте.


5

Я віддаю перевагу:

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

Я думаю, що це легше перекладається на "повторення циклу 7 разів".

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


4

У Java 1.5 ви просто можете це зробити

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

тому для випадку масиву вам не потрібно хвилюватися.


4

Я не думаю, що є різниця в продуктивності. Друга форма, безумовно, легше читається, не потрібно розумно віднімати одну, щоб знайти останній номер ітерації.

EDIT: Я бачу, що інші не згодні. Для мене особисто мені подобається бачити фактичні числа індексу в структурі циклу. Можливо, це тому, що воно більше нагадує 0..6синтаксис Перла , який, наскільки я знаю, еквівалентний (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".


Мені подобається другий краще, тому що його легше читати, але чи справді перераховує це-> GetCount () кожен раз? Я зловив це , коли змінюючи це і кількість remaind те ж саме змушує мене зробити do..while this-> GetCount ()
osp70

GetCount () буде називатися кожною ітерацією в першому прикладі. Це було б названо лише один раз у другому прикладі. Якщо вам потрібно кожного разу викликати GetCount (), тоді майте його у циклі, якщо ви не намагаєтесь утримувати його зовні.
Позначити Інграма

Так, я це спробував, і ви маєте рацію, вибачте.
osp70

Яка різниця у використанні ++ i над i ++?
Рене Саарсоо

Невелике збільшення швидкості при використанні ints, але збільшення може бути більшим, якщо ви збільшуєте власні заняття. В основному ++ i збільшує фактичне значення, потім повертає фактичне значення. i ++ створює temp var, збільшує реальний var, потім повертає temp. Не потрібно створювати var із ++ i.
Позначити Інграма

4

У C ++ я віддаю перевагу використанню !=, яке можна використовувати у всіх контейнерах STL. Не всі ітератори контейнерів STL менш ніж порівнянні.


Це мене трохи лякає лише тому, що є дуже незначний зовнішній випадок, що щось може перетворити лічильник над моїм передбаченим значенням, що потім робить це нескінченним циклом. Шанси віддалені і легко виявляються - але < відчуває себе безпечніше.
Роб Аллен

2
Якщо у вашому коді є така помилка, напевно, краще зірватися та спалити, ніж мовчки продовжувати :-)

Це правильна відповідь: це викликає менший попит на ваш ітератор і швидше з’явиться, якщо у вашому коді є помилка. Аргумент для <короткозорий. Можливо, нескінченний цикл буде поганий ще в 70-х, коли ви платили за час процесора. '! =' менше шансів приховати помилку.
Девід Неме

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

4

Едсгер Дайкстра написав статтю про це ще в 1982 році, де він стверджує, що нижчий <= i <верхній:

Є найменше натуральне число. Виключення нижньої межі - як в b) і 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 <= LAST_FILLED_ARRAY_SLOT
Кріс Кадмор

2

Ще в коледжі я пам’ятаю щось про подібні дві операції, схожі за час обчислення на процесорі. Звичайно, ми говоримо вниз на рівні складання.

Однак, якщо ви говорите на C # або Java, я дійсно не думаю, що одна буде швидкішою, ніж інша. Кілька наносекунд, які ви отримаєте, швидше за все, не вартують ніякої плутанини, яку ви вводите.

Особисто я б написав код, який має сенс з точки зору впровадження бізнесу, і переконатися, що його легко читати.


2

Це підпадає безпосередньо під категорію "Зробити неправильний код невірним" .

У мовах індексації на основі нуля, таких як Java або C #, люди звикли до змін на index < count умові. Таким чином, використання цієї конвенції про дефакто зробить поодинці помилки більш очевидними.

Щодо продуктивності: будь-який хороший компілятор, який вартує сліду пам’яті, повинен відображати такі, як не-питання.


2

Як невелику сторону, коли я переглядаю масив чи іншу колекцію в .Net, я знаходжу

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

бути більш читабельним, ніж числовий цикл. Звичайно, передбачається, що власне лічильник Int сам не використовується в коді циклу. Я не знаю, чи є зміна продуктивності.


Це також вимагає, щоб ви не змінювали розмір колекції під час циклу.
Джефф Б

Так би для (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 - це швидко. Інше - це те, що він мені добре читає, і підрахунок дає мені легку вказівку на те, скільки ще разів залишилось.
kenny

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-го тестів, результати цих двох тестів поміняють, це, очевидно, є проблемою пам'яті. Однак 3-й тест - тест, де я перетворюю порядок ітерації - явно швидше.


чому ви починаєте з i = 1 у другому випадку?
Євген Кац

Грмм, напевно, дурна помилка? Я збив це досить швидко, можливо, 15 хвилин.
Девід Вейс

1

Як всі кажуть, прийнято використовувати 0-індексовані ітератори навіть для речей поза масивами. Якщо все починається з 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 обом потрібно однакова кількість циклів.
Треб

<= може бути реалізований як! (>)
JohnMcG

! (>) - це ще дві інструкції, але Треб правильний, що JLE і JL обидва використовують однакову кількість тактових циклів, тому <і <= займають однакову кількість часу.
Джейкоб Кралл
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.