Технические причины форматирования при увеличении на 1 в цикле "for"?


во всем интернете, примеры кода имеют for петли, которые выглядят так:

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

в то время как я использовал следующий формат:

for(int i = 0; i != 5; ++i)

Я делаю это, потому что я считаю, что это более эффективно, но действительно ли это имеет значение в большинстве случаев?

30 53

30 ответов:

все любят свои микро-оптимизации, но это не будет иметь значения, насколько я могу видеть. Я скомпилировал две вариации с g++ для процессоров Intel без каких-либо причудливых оптимизаций, и результаты для

for(int i = 0; i < 5; i++)
    movl , -12(%ebp)
    jmp L2
L3:
    leal    -12(%ebp), %eax
    incl    (%eax)
L2:
    cmpl    , -12(%ebp)
    jle L3

for(int i = 0; i != 5; ++i)
    movl    , -12(%ebp)
    jmp L7
L8:
    leal    -12(%ebp), %eax
    incl    (%eax)
L7:
    cmpl    , -12(%ebp)
    jne L8

Я думаю jle и jne следует перевести на одинаково быстрые инструкции на большинстве архитектур. Так что для производительности, вы не следует проводить различие между ними. В общем, я бы согласился, что первый немного безопаснее, и я также думаю, что более распространен.


EDIT (2 года спустя): поскольку эта тема недавно снова получила много внимания, я хотел бы добавить, что на этот вопрос будет трудно ответить вообще. Какие версии кода являются более эффективными, специально не определяется C-Standard [PDF] (и то же самое относится к C++ и, вероятно, также для C# ).

раздел 5.1.2.3 выполнение программы

§1

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

что касается вкуса, то я пишу

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

Если по какой-то причине i скачки до 50 в цикле, ваша версия будет петля навсегда. Элемент i < 5 это проверка на вменяемость.

форму

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

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

это также немного безопаснее в ситуациях, когда вы изменяете i внутри цикла или используете инкремент, отличный от 1. Но это мелочь. Лучше всего тщательно разработать свой цикл и добавить некоторые утверждения, чтобы поймать сломанные предположения ранний.

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

Это зависит от языка.

тексты на C++ часто предлагают второй формат, так как он будет работать с итераторами, которые можно сравнить (!= ) непосредственно, но не с большим или меньшим условием. Также pre increment может быть быстрее, чем post increment, поскольку нет необходимости в копии переменной для сравнения - однако оптимизаторы могут справиться с этим.

для целых чисел любая форма работает. Общая идиома для C является первой, в то время как для C++ это второй.

для использования C# и Java я бы foreach зацикливался на всех вещах.

В C++ также есть функция std::for_each, требующая использования функтора, который для простых случаев, вероятно, более сложен, чем любой пример здесь, и повышение FOR_EACH, которое может выглядеть как C# foreach, но является сложным внутри.

Что касается использования ++i вместо i++, это не имеет значения для большинства компиляторов, однако ++i может быть более эффективным, чем I++ при использовании в качестве итератора.

там на самом деле четыре перестановки на то, что вы даете. К вашим двоим:

for(int i = 0; i < 5; i++)
for(int i = 0; i != 5; ++i)

мы можем добавить:

for(int i = 0; i < 5; ++i)
for(int i = 0; i != 5; i++)

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

в некоторых случаях имеет больше смысла для конкретного ума с конкретным случаем думать о "меньше" или "не равно" в зависимости от причины, по которой мы выбрали 0 и 5, но даже тогда то, что делает одно очевидным для одного кодера, не может быть с другим.

более абстрактно, это формы:

for(someType i = start; i < end; i++)
for(someType i = start; i != end; ++i)
for(someType i = start; i < end; ++i)
for(someType i = start; i != end; i++)

очевидная разница здесь в том, что в двух случаях someType должен иметь значение < а в остальном это должно иметь значение для !=. Типы, для которых != определена и < не совсем обычны, включая довольно много объектов итератора в C++ (и потенциально в C#, где тот же подход, что и итераторы STL, возможен и иногда полезен, но не как идиоматический, напрямую поддерживаемый общими библиотеками, а также часто полезный, поскольку есть конкурирующие идиомы с более прямой поддержкой). Стоит отметить, что подход STL специально разработан таким образом, чтобы включать указатели в набор допустимых типов итераторов. Если вы привыкли использовать STL, вы будете рассмотрим формы с != гораздо более идиоматично, даже если применяется к целым числам. Лично мне было достаточно очень небольшого воздействия на него, чтобы сделать его моим инстинктом.

С другой стороны, при определении <, а не != было бы реже, это применимо к случаям, когда либо мы заменяем инкремент другим увеличением i's значение, или где i может быть изменен в пределах цикла.

Итак, есть определенные случаи с обеих сторон, где один или другой подход-единственный.

теперь ++i vs i++. Опять же с целыми числами и при вызове непосредственно, а не через функцию, которая возвращает результат (и шансы даже тогда) практический результат будет точно таким же.

в некоторых языках C-стиля (без перегрузки операторов) целые числа и указатели являются единственными случаями. Мы могли бы просто искусственно придумать случай, когда приращение вызывается через функцию просто чтобы изменить то, как это происходит, и, скорее всего, компилятор все равно превратит их в одно и то же.

C++ и C# позволяют нам переопределить их. Как правило, префикс ++ работает как функция, которая делает:

val = OneMoreThan(val);//whatever OneMoreThan means in the context.
//note that we assigned something back to val here.
return val;

и постфикс ++ работает как функция, которая делает:

SomeType copy = Clone(val);
val = OneMoreThan(val);
return copy;

ни C++, ни C# не соответствуют вышеизложенному идеально (я совершенно сознательно сделал свой псевдокод не совпадающим), но в любом случае может быть копия или, возможно, два сделал. Это может быть или не быть дорогим. Это может быть или не быть предотвратимым (в C++ мы часто можем полностью избежать его для префиксной формы, возвращая this и в постфиксе возвращая void). Он может или не может быть оптимизирован до нуля, но остается, что это может быть более эффективным, чтобы сделать ++i чем i++ в некоторых случаях.

в частности, есть небольшая возможность небольшого улучшения производительности с ++i, и с большим классом он может даже быть значительным, но запрещать кому-то переопределять в C++, чтобы у них были совершенно разные значения (довольно плохая идея), как правило, невозможно, чтобы это было наоборот. Таким образом, получение привычки отдавать предпочтение префиксу над постфиксом означает, что вы можете получить улучшение один раз в тысячу, но не проиграете, поэтому это привычка, в которую часто попадают кодеры C++.

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

я переключился на использование != около 20+ лет назад после прочтения книги Дейкстры называется "дисциплина программирования". В своей книге Дейкстра заметил, что слабее условия продолжения приводят к сильнее пост-условия в конструкции петли.

например, если мы изменим вашу конструкцию, чтобы выставить i после цикла постусловие первого цикла будет i >= 5, в то время как пост-условие второго цикла является много сильнее i == 5. Это лучше для рассуждения о программе в формальных терминах инвариантов цикла, постусловий и самых слабых предварительных условий.

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

тем не менее, я думал, что проведу простой тест и получу некоторые твердые данные о том, какой из четырех циклов работает быстрее всего. Я на средний по характеристикам компьютер, компиляции с помощью javac 1.7.0.

моя программа делает цикл for, повторяя 2,000,000 раз за ничто (так как не заболеть интересными данными с тем, сколько времени требуется, чтобы сделать все, что находится в цикле for). Он использует все четыре типа, предложенные выше, и умножает результаты, повторяя 1000 раз, чтобы получить среднее значение.

собственно код:

public class EfficiencyTest
{
public static int iterations = 1000;

public static long postIncLessThan() {
    long startTime = 0;
    long endTime = 0;
    startTime = System.nanoTime();
    for (int i=0; i < 2000000; i++) {}
    endTime = System.nanoTime();
    return endTime - startTime;
}

public static long postIncNotEqual() {
    long startTime = 0;
    long endTime = 0;
    startTime = System.nanoTime();
    for (int i=0; i != 2000000; i++) {}
    endTime = System.nanoTime();
    return endTime - startTime;
}

public static long preIncLessThan() {
    long startTime = 0;
    long endTime = 0;
    startTime = System.nanoTime();
    for (int i=0; i < 2000000; ++i) {}
    endTime = System.nanoTime();
    return endTime - startTime;
}

public static long preIncNotEqual() {
    long startTime = 0;
    long endTime = 0;
    startTime = System.nanoTime();
    for (int i=0; i != 2000000; ++i) {}
    endTime = System.nanoTime();
    return endTime - startTime;
}

public static void analyseResults(long[] data) {
    long max = 0;
    long min = Long.MAX_VALUE;
    long total = 0;
    for (int i=0; i<iterations; i++) {
        max = (max > data[i]) ? max : data[i];
        min = (data[i] > min) ? min : data[i];
        total += data[i];
    }
    long average = total/iterations;

    System.out.print("max: " + (max) + "ns, min: " + (min) + "ns");
    System.out.println("\tAverage: " + (average) + "ns");
}

public static void main(String[] args) {
    long[] postIncLessThanResults = new long [iterations];
    long[] postIncNotEqualResults = new long [iterations];
    long[] preIncLessThanResults = new long [iterations];
    long[] preIncNotEqualResults = new long [iterations];

    for (int i=0; i<iterations; i++) {
        postIncLessThanResults[i] = postIncLessThan();
        postIncNotEqualResults[i] = postIncNotEqual();
        preIncLessThanResults[i] = preIncLessThan();
        preIncNotEqualResults[i] = preIncNotEqual();
    }
    System.out.println("Post increment, less than test");
    analyseResults(postIncLessThanResults);

    System.out.println("Post increment, inequality test");
    analyseResults(postIncNotEqualResults);

    System.out.println("Pre increment, less than test");
    analyseResults(preIncLessThanResults);

    System.out.println("Pre increment, inequality test");
    analyseResults(preIncNotEqualResults);
    }
}

Извините, если я скопировал это неправильно!

результаты подавили меня-тестирование i < maxValue заняло около 1,39 МС на цикл, будь то с помощью пре-или пост-инкрементов, но i != maxValue взял 1.05 МС. Это это или 24.5% сбережения или 32.5% потери времени, в зависимости от того, как вы на это смотрите.

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

Я думаю, что все равно буду придерживаться тестирования меньше, чем, хотя!

Edit

Я также протестировал декрементирование i и обнаружил, что это действительно не влияет на время, которое требуется - for (int i = 2000000; i != 0; i--) и for (int i = 0; i != 2000000; i++) оба принимают такой же отрезок времени, как и for (int i = 2000000; i > 0; i--) и for (int i = 0; i < 2000000; i++).

Я решил перечислить наиболее информативные ответы, так как этот вопрос становится немного переполненным.

DenverCoder8стендовая маркировка явно заслуживает некоторого признания, а также скомпилированные версии петель от Лукас. Тим Гы показал различия между приращением pre & post в то время как User377178 выделил некоторые плюсы и минусы Цепкий Технарь написал об оптимизации цикла в целом и стоит упомянуть.

там у вас есть мои лучшие 5 ответов.

  1. DenverCoder8
  2. Лукас
  3. Тим Гы
  4. User377178
  5. Цепкий Технарь

В общем коде вы должны предпочесть версию с != оператор, так как он требует только ваш i на одинаково-сопоставимо, в то время как < версия требует, чтобы это было реляционно-сопоставимо. Последнее является более сильным требованием, чем первое. Как правило, вы должны избегать более сильных требований, когда более слабое требование вполне достаточно.

сказав, что в вашем конкретном случае, если int i оба будут работать одинаково ну и не будет никакой разницы в производительности.

Я бы никогда не сделал этого:

for(int i = 0; i != 5; ++i)

Я != 5 оставляет его открытым для возможности того, что я никогда не буду 5. Слишком легко пропустить его и запустить либо в бесконечный цикл, либо в ошибку доступа к массиву.

++i

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

Я думаю, что у Дугласа Крокфорда есть лучшее предложение, и это не использовать ++ или -- вообще. Это может просто стать слишком запутанным (может быть, не в цикле, но определенно в других местах) время от времени, и так же легко написать i = i + 1. Он думает, что это просто плохая привычка, и я вроде как согласен, увидев какой-то ужасный "оптимизированный" код.

Я думаю, что Крокфорд получает это с эти операторы вы можете заставить людей писать такие вещи, как:

var x = 0;
var y = x++;

y = ++x * (Math.pow(++y, 2) * 3) * ++x;

alert(x * y);

//ответ 54 кстати.

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

Я работал в компании, которая производит программное обеспечение для ответственных систем, и одним из правил было то, что цикл должен заканчиваться "

  1. ваша управляющая переменная может перейти к более высокому значению по какой-либо проблеме hw или некоторой памяти вторжение;

  2. в обслуживании можно увеличить значение итератора внутри цикла или сделать что-то вроде "i += 2", и это заставит ваш цикл катиться навсегда;

  3. Если по какой-то причине ваш тип итератора изменяется с "int" на "float" (я не знаю, почему кто-то это сделает, но в любом случае...) точное сравнение для плавающих точек является плохой практикой.

(стандарт кодирования MISRA C++ (для критической безопасности системы) также скажите вам, чтобы вы предпочли "

о ++i или i++, я бы предпочел использовать ++i.нет никакой разницы для этого, когда вы работаете с базовыми типами, но когда вы используете итератор STL, предварительное создание более эффективно. Поэтому я всегда использую предрождество, чтобы привыкнуть к нему.

для записи эквивалент cobol цикла "for": -

    PERFORM VARYING VAR1 
            FROM +1 BY +1
            UNTIL VAR1 > +100
  *      SOME VERBOSE COBOL STATEMENTS HERE
    END-PERFORM.

или

PERFORM ANOTHER-PARAGRAPH
        VARYING VAR2 BY +1 
        UNTIL TERMINATING-CONDITION
        WITH TEST AFTER.

есть много вариантов на этот счет. Основной gotcha для людей, чьи умы не были повреждены длительным воздействием COBOL является, по умолчанию,UNTIL на самом деле означает WHILE т. е. тест выполняется в верхней части цикла, перед увеличением переменной цикла и перед обработкой тела цикла. Вам нужно "WITH TEST AFTER", чтобы сделать его правильным UNTIL.

второй менее читаем, Я думаю (хотя бы потому, что "стандартная" практика кажется первой).

числовые литералы, посыпанные в вашем коде? От стыда...

возвращаясь в нужное русло, Дональд Кнут однажды сказал

мы должны забыть о мелких эффективности, скажем, около 97% время: преждевременная оптимизация является корень всех зол.

Итак, это действительно сводится к тому, что легче разобрать

Так... принимая во внимание оба вышеизложенного, какой из следующих программисту проще разбирать?

for (int i = 0; i < myArray.Length; ++i)

for (int i = 0; i != myArray.Length; ++i)

Edit: я знаю, что массивы в C# реализуют систему.Коллекции.Интерфейс IList, но это не обязательно верно на других языках.

по поводу читабельности. Будучи программистом C#, который любит Ruby, я недавно написал метод расширения для int, который позволяет использовать следующий синтаксис (как в Ruby):

4.Times(x => MyAction(x));

подведем итоги плюсов и минусов обоих вариантов

плюсы !=

  • когда int заменяется некоторым итератором или типом, переданным через аргумент шаблона, есть больше шансов, что он будет работать, он будет делать то, что ожидается, и будет более эффективным.
  • будет "петля навсегда", если что-то непредсказуемое произойдет с переменной i, позволяющей обнаруживать ошибки

плюсы

  • как говорят другие, так же эффективен, как и другой с простыми типами
  • он не будет работать "навсегда", если i увеличивается в цикле или 5 заменяется некоторым выражением, которое изменяется во время выполнения цикла
  • будет работать с типом поплавка
  • более читаемый-вопрос привыкания к

мои выводы:

  1. возможно != версия должна использоваться в большинстве случаев, когда i дискретно, и это так же, как и другая сторона сравнение не предназначено для изменения в цикле.

  2. в то время как наличие было бы явным признаком того, что i имеет простой тип (или оценивает простой тип) и условие не является простым: i или условие дополнительно изменяется в цикле и / или параллельной обработке.

похоже, никто не заявил причина почему исторически оператор предрождества,++i, был предпочтен над постфиксом i++, для небольших петель.

рассмотрим типичную реализацию префикса (increment and fetch) и постфикса (fetch and increment):

// prefix form: increment and fetch
UPInt& UPInt::operator++()
{
   *this += 1;      // increment
   return *this;    // fetch
}

// posfix form: fetch and increment
const UPInt UPInt::operator++(int)
{
   const UPInt oldValue = *this;
   ++(*this);
   return oldValue;
} 

обратите внимание, что операция префикса может быть выполнена на месте, где в качестве постфикса требуется другая переменная для отслеживания старого значения. Если вы не не знаю, почему это так, рассмотрим следующие:

int a = 0;
int b = a++; // b = 0, the old value, a = 1 

в небольшом цикле это дополнительное распределение, требуемое постфиксом, теоретически может сделать его медленнее, и поэтому логика старой школы-префикс более эффективна. Таким образом, многие программисты на C/C++ придерживались привычки использовать префиксную форму.

однако в другом месте отмечается тот факт, что современные компиляторы умны. Они замечают, что при использовании постфиксной формы в цикле for возвращаемое значение постфикса это не нужно. Таким образом, нет необходимости отслеживать старое значение, и его можно оптимизировать, оставив тот же машинный код, который вы получите от использования префиксной формы.

хорошо... это нормально, пока вы не измените i внутри вашего цикла. Реальный" лучший " синтаксис для этого полностью зависит от вашего желаемого результата.

Если ваш индекс не был int, но вместо этого (скажем) класс C++, то это было бы возможно для второго примера, чтобы быть более эффективным.

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

  • Если ваш цикл for критичен по производительности и не выполняет тяжелых вычислений, так что арифметика индекса действительно имеет значение, вы почти наверняка должны реструктурировать свой код, чтобы делать больше работы на каждом проходе цикла.

когда я впервые начал программировать на C, я использовал ++i form in for loops просто потому, что компилятор C, который я использовал в то время, не делал большой оптимизации и в этом случае генерировал бы немного более эффективный код.

Теперь я использую ++i форма, потому что она читается как "инкремент i", тогда как i++ читается как "я увеличен", и любой учитель английского языка скажет вам, чтобы избежать пассивного голоса.

суть в том, чтобы делать все, что кажется более читаемым вы.

Я думаю, что в конце концов все сводится к личным предпочтениям.
Мне нравится идея

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

over

for(int i = 0; i != 5; ++i)

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

мы можем использовать еще один трюк для этого.

for (i = 5; i > 0; i--)

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

в конечном счете, решающим фактором в отношении того, что более эффективно, является не язык и не компилятор, а, скорее,оборудование. Если вы пишете код для встроенного микроконтроллера, такого как 8051, подсчет вверх против отсчета вниз, больше или меньше, чем против не равно, и увеличение против уменьшения, can сделать разницу в производительности, в пределах очень ограниченном шкала времени ваших петель.

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

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

Of много однако большее значение, чем оптимизация того, как ваш цикл for итераций, циклов и разрывов, изменяет что он делает на каждой итерации. Если вызывая цикл for одна дополнительная инструкция сохраняет две или более - инструкции внутри каждой итерации, сделать! Вы получите чистый выигрыш в один или несколько циклов! Для действительно оптимального кода Вы должны взвесить последствия полной оптимизации того, как цикл for повторяется над тем, что происходит на каждой итерации.

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

Я вижу много ответов, используя конкретный код, который был опубликован, и целое число. Однако вопрос был специфическим для "for loops", а не конкретным, упомянутым в исходном сообщении.

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

недавно я запустил инструмент статического анализа в большом проекте (вероятно, около 1-2 миллионов строк кода), и он обнаружил около 80 случаев, когда постфикс использовался в случае, когда префикс обеспечивал бы преимущество в скорости. В большинстве этих случаев преимущество было небольшим, потому что размер контейнера или количество петель обычно были бы небольшими, но в других случаях он может перебрать 500+ пунктов.

в зависимости от типа объекта увеличивается/уменьшается, когда постфикс происходит копия также может произойти. Мне было бы любопытно узнать, сколько компиляторов обнаружит случай, когда используется постфикс, когда его значение не указано, и, следовательно, копия не может быть использована. Будет ли он генерировать код в этом случае для префикса вместо этого? Даже инструмент статического анализа упомянул, что некоторые из тех 80 случаев, которые он нашел, могут быть оптимизированы в любом случае, но зачем рисковать и позволять компилятору решать? Я не нахожу префиксный оператор вообще запутанным при использовании в одиночку, он только становится бременем для чтения, когда он начинает использоваться, встроенный, как часть логического оператора:

int i = 5;
i = ++i * 3;

необходимость думать о приоритете оператора не должна быть необходимой с простой логикой.

int i = 5;
i++;
i *= 3;

конечно, код выше занимает дополнительную строку, но он читает более четко. Но с циклом for изменяемая переменная является его собственный оператор, поэтому вам не нужно беспокоиться о том, является ли он префиксом или постфиксом, как и в блоке кода выше,i++ один, так мало мысли требуется о том, что будет с ним, так что этот блок кода ниже, вероятно, так же читаем:

int i = 5;
++i;
i *= 3;

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

просто мои два цента.

на многих архитектурах гораздо проще проверить, является ли что-то нулевым, что ли это какое-то другое произвольное целое число, поэтому, если вы действительно хотите оптимизировать черт из чего-то, когда это возможно count вниз, а не вверх (вот пример на чипах ARM).

В общем, это действительно зависит от того, как вы думаете о числах и подсчета голосов. Я делаю много DSP и математики, поэтому подсчет от 0 до N-1 для меня более естественен, вы можете быть разные в этом отношении.

Фортран и Бейсик для цикла, реализованного < (на самом деле <=) для положительных приращений. Не уверен, что КОБОЛ сделал, но я подозреваю, что это было похоже. Таким образом, этот подход был "естественным" для дизайнеров и пользователей "новых" языков, таких как C.

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

первый пункт выше, является вероятной причиной стиль начал, вторая главная причина это продолжается.

Я помню один сегмент кода, где я получал увеличение на 2 вместо 1 из-за какой-то ошибки, и это заставляло его идти в бесконечном цикле. Поэтому лучше иметь этот цикл, как показано в первом варианте. Это также более читаемо. Потому что я != 5 и i

это не очень хороший подход, чтобы использовать как != 5. Но

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

является более эффективным, чем

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

потому что i++ сначала выполняет операцию копирования. Для получения подробной информации вы можете посмотреть перегрузку оператора в C++.