Пост-инкремент и прединкремент в цикле " for " производят один и тот же выход [дубликат]
этот вопрос уже есть ответ здесь:
- разница между i++ и ++i в цикле? 21 ответов
следующие циклы for дают идентичные результаты, даже если один использует постинкремент, а другой-прединкремент.
вот код:
for(i=0; i<5; i++) {
printf("%d", i);
}
for(i=0; i<5; ++i) {
printf("%d", i);
}
Я получаю тот же результат для обоих - для петель. Я что-то упустил?
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++
.три причины, почему вы должны преинкремента:
- вам не придется думать о может ли переменная / объект иметь перегруженный метод постинкрементации (например, в функции шаблона) и относиться к нему по-другому (или забыть относиться к нему по-другому).
- последовательный код выглядит лучше.
- когда кто-то спрашивает вас "почему вы преинкремента?- ты получишь возможность рассказать им о проблеме остановки и теоретические пределы оптимизации компилятора. :)
это один из моих любимых вопросов на собеседовании. Сначала я объясню ответ, а потом скажу, почему мне нравится этот вопрос.
устранение:
ответ заключается в том, что оба фрагмента печатают числа от 0 до 4 включительно. Это потому что
for()
цикл обычно эквивалентен awhile()
петли:for (INITIALIZER; CONDITION; OPERATION) { do_stuff(); }
можно написать:
INITIALIZER; while(CONDITION) { do_stuff(); OPERATION; }
вы можете видеть, что операция всегда сделано в нижняя часть петли. В таком виде должно быть понятно, что
i++
и++i
будет иметь тот же эффект: они оба инкрементаi
и игнорировать результат. Новое значениеi
не тестируется до начала следующей итерации, в верхней части цикла.
Edit: спасибо Джейсону за то, что этот
for()
доwhile()
эквивалентности делает не удерживайте, если цикл содержит операторы управления (например,continue
) что бы предотвратитьOPERATION
от выполнения вwhile()
петли.OPERATION
и всегда выполняется непосредственно перед следующей итерацией afor()
петли.
почему это хороший вопрос интервью
во-первых, это занимает всего минуту или две, если кандидат говорит правильный ответ сразу, так что мы можем перейти прямо к следующему вопросу.
но удивительно (для меня), многие кандидаты говорят мне петлю с после инкремента будут напечатаны числа от 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