Пост-инкремент и предварительный инкремент в цикле 'for' производят одинаковый вывод

Следующие для циклов производят идентичные результаты, даже если используется приращение постов и другое предварительное приращение.

Вот код:

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

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

Я получаю тот же вывод для обоих циклов for. Я что-то пропустил?

Ответ 1

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

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

i++ оценивает старое значение i и увеличивает i.

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

  • проверить условие
  • если оно ложно, завершение
  • если это правда, выполните тело
  • выполнить шаг инкрементации

Поскольку (1) и (4) разделены, можно использовать либо предварительный, либо пост-инкремент.

Ответ 2

Ну, это просто. Вышеуказанные циклы 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 до приращения.

Ответ 3

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

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

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

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

Ответ 4

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

Решение:

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

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

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

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

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


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


Почему это хороший вопрос о собеседовании

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

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

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

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

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

Ответ 5

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

Возможно, было бы полезно подумать о том, как реализован цикл 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

Ответ 6

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

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

Повторялся бы, если бы это было написано так:

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

Ответ 7

Здесь вы можете прочитать ответ 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;
}

Ответ 8

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

Пост-приращение или предварительный приращение имеют значение в таких ситуациях:

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

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

Ответ 9

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

Ответ 10

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

Ответ 11

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

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

Результат:

внутри postmentment = 0

i = пост-приращение в цикле 0

внутри postmentment = 1

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

Второй цикл:

внутри prementment = 0

i = pre increment в цикле 1

внутри prementment = 1

i = pre increment в цикле 2

Ответ 12

Составители переводят

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

к

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

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

EDIT: продолжение просто заменяется на goto end;