Пост-инкремент и прединкремент в цикле " for " производят один и тот же выход [дубликат]


этот вопрос уже есть ответ здесь:

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

вот код:

for(i=0; i<5; i++) {
    printf("%d", i);
}

for(i=0; i<5; ++i) {
    printf("%d", i);
}

Я получаю тот же результат для обоих - для петель. Я что-то упустил?

12 161

12 ответов:

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

++i с шагом i и вычисляет новое значение i.

i++ возвращает старое значение i, и с шагом i.

причина, по которой это не имеет значения в цикле for, заключается в том, что поток управления работает примерно вот так:

    Ну, это просто. Выше for петли семантически эквивалентны

    int i = 0;
    while(i < 5) {
        printf("%d", i);
        i++;
    }
    

    и

    int i = 0;
    while(i < 5) {
        printf("%d", i);
        ++i;
    }
    

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

    обратите внимание, что будет разница, если цикл был переписан как

    int i = 0;
    int j = i;
    while(j < 5) {
        printf("%d", i);
        j = ++i;
    }
    
    int i = 0;
    int j = i;
    while(j < 5) {
        printf("%d", i);
        j = i++;
    }
    

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

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

    однако, под капотом есть разница: пост-инкремент i++ нужно создать временную переменную для сохранения исходного значения i, затем выполняет приращение и возвращает временную переменную. Предварительное приращение ++i не создает временную переменную. Конечно, любая приличная настройка оптимизации должна быть в состоянии оптимизировать это, когда объект является чем-то простым, как int, но помните, что ++ - операторы перегружены в более сложных классах, таких как итераторы. Поскольку два перегруженных метода могут иметь различные операции (можно было бы вывести " Эй, я предварительно увеличен! например, для stdout) компилятор не может сказать, эквивалентны ли методы, когда возвращаемое значение не используется (в основном потому, что такой компилятор решит неразрешимое проблема останова), он должен использовать более дорогую версию пост-инкремента, если вы пишете myiterator++.

    три причины, почему вы должны преинкремента:

    1. вам не придется думать о может ли переменная / объект иметь перегруженный метод постинкрементации (например, в функции шаблона) и относиться к нему по-другому (или забыть относиться к нему по-другому).
    2. последовательный код выглядит лучше.
    3. когда кто-то спрашивает вас "почему вы преинкремента?- ты получишь возможность рассказать им о проблеме остановки и теоретические пределы оптимизации компилятора. :)

    это один из моих любимых вопросов на собеседовании. Сначала я объясню ответ, а потом скажу, почему мне нравится этот вопрос.

    устранение:

    ответ заключается в том, что оба фрагмента печатают числа от 0 до 4 включительно. Это потому что for() цикл обычно эквивалентен a while() петли:

    for (INITIALIZER; CONDITION; OPERATION) {
        do_stuff();
    }
    

    можно написать:

    INITIALIZER;
    while(CONDITION) {
        do_stuff();
        OPERATION;
    }
    

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


    Edit: спасибо Джейсону за то, что этот for() до while() эквивалентности делает не удерживайте, если цикл содержит операторы управления (например,continue) что бы предотвратить OPERATION от выполнения в while() петли. OPERATION и всегда выполняется непосредственно перед следующей итерацией a for() петли.


    почему это хороший вопрос интервью

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

    но удивительно (для меня), многие кандидаты говорят мне петлю с после инкремента будут напечатаны числа от 0 до 4, а цикл предварительного инкремента будет печатать от 0 до 5 или от 1 до 5. Они, как правило, объяснить разницу между пре - и пост-приращение правильно, но они не понимают механику for() петли.

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

    в этот момент большинство кандидатов осознают свою ошибку и находят правильный ответ. Но у меня был один, который настаивал на том, что его первоначальный ответ был правильным, а затем изменил способ перевода for() до while(). Это было захватывающее интервью, но мы не сделали предложение!

    надеюсь, что это поможет!

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

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

          set i = 0
    test: if i >= 5 goto done
          call printf,"%d",i
          set i = i + 1
          goto test
    done: nop
    

    пост-инкремент будет иметь по крайней мере еще один шаг, но было бы тривиально оптимизировать прочь

          set i = 0
    test: if i >= 5 goto done
          call printf,"%d",i
          set j = i   // store value of i for later increment
          set i = j + 1  // oops, we're incrementing right-away
          goto test
    done: nop
    

    Если вы написали это так, то это будет иметь значение:

    for(i=0; i<5; i=j++) {
        printf("%d",i);
    }
    

    будет повторяться еще раз, чем если бы написано так:

    for(i=0; i<5; i=++j) {
        printf("%d",i);
    }
    

    вы можете прочитать ответ Google для него здесь: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Preincrement_and_Predecrement

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

    редактировать:

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

    вы можете проверить это с помощью такого цикла:

    for (int i = 0; i < 5; cout << "we still not incremented here: " << i << endl, i++)
    {
        cout << "inside loop body: " << i << endl;
    }
    

    оба i++ и ++i выполняется после printf ("%d", i) выполняется в каждый раз, так что нет никакой разницы.

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

    пост-инкремент или пре-инкремент имеет значение в таких ситуациях:

    int j = ++i;
    int k = i++;
    f(i++);
    g(++i);
    

    где вы предоставляете некоторое значение, назначая или передавая аргумент. Вы не делаете ни того, ни другого в своем for петли. Он только увеличивается. Пост - и пре - не имеет смысла есть!

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

    есть разница, если:

    int main()
    {
      for(int i(0); i<2; printf("i = post increment in loop %d\n", i++))
      {
        cout << "inside post incement = " << i << endl;
      }
    
    
      for(int i(0); i<2; printf("i = pre increment in loop %d\n",++i))
      {
        cout << "inside pre incement = " << i << endl;
      }
    
      return 0;
    }
    

    результат:

    внутри post incement = 0

    i = пост инкремент в цикле 0

    внутри post incement = 1

    i = пост инкремент в цикле 1

    второй цикл:

    внутри pre incement = 0

    i = предварительное приращение в цикле 1

    внутри pre incement = 1

    i = предварительное приращение в цикле 2

    компиляторы переводят

    for (a; b; c)
    {
        ...
    }
    

    до

    a;
    while(b)
    {
        ...
     end:
        c;
    }
    

    Так что в вашем случае (post/pre - increment) это не имеет значения.

    EDIT: continues просто заменяются на goto end;