Урок 40. Инкремент. Декремент. Побочные эффекты

   | 

   | 

 Обновлено 5 Мар 2017  | 

 4833

 ǀ   9 

Операции инкремента (увеличение на 1) и декремента (уменьшение на 1) переменной настолько используемые, что у них есть свои собственные операторы в C++. Так еще и две версии: префикс и постфикс.

Оператор Символ Пример Операция
Префиксный инкремент (пре-инкремент) ++ ++x Инкремент x, затем вычисление x
Префиксный декремент (пре-декремент) −− −−x Декремент x, затем вычисление x
Постфиксный инкремент (пост-инкремент) ++ x++ Вычисление x, затем инкремент x
Постфиксный декремент (пост-декремент) −− x−− Вычисление x, затем декремент x

Операторы инкремента/декремента версии префикс — просты. Значение переменной х сначала увеличивается или уменьшается, а затем уже вычисляется. Например:

А вот с операторами инкремента/декремента версии постфикс несколько сложнее. Компилятор создает временную копию переменной х, увеличивает или уменьшает оригинальный х (не копию), а затем вычисляет копию х. Только после этого копия удаляется.

Рассмотрим детальнее. Во-первых, компилятор создает временную копию х, которая имеет то же значение, что и оригинал (5). Затем увеличивается первоначальный х с 5 до 6. После компилятор вычисляет временную копию, значение которой — 5, и присваивает это значение переменной у. Затем копия удаляется. Следовательно, в конце, переменная у = 5, а переменная х = 6.

Вот еще один пример, показывающий разницу между этими версиями:

Результат:

5 5
6 4
6 4
6 4
7 3

В третьей строчке, х и у увеличиваются/уменьшаются на единицу непосредственно перед обработкой компилятора, так что сразу выводятся их новые значения. На пятой строчке, временные копии (х = 6, у = 4) отправляются в cout, а уже только потом оригинальные х и у увеличиваются/уменьшаются на единицу. Именно поэтому изменения значений операторов версии постфикс не видно до следующей строки.

Версия префикс увеличивает/уменьшает значения перед обработкой компилятора, версия постфикс — после.

Правило: Используйте префиксный инкремент и префиксный декремент, вместо постфиксного инкремента и постфиксного декремента. Версии префикс не только более производительны, но и вероятность возникновения ошибок и проблем с ними намного меньше.  

Побочные эффекты

Функция или выражение имеет побочный эффект (side effect), если она/оно изменяет состояние чего-либо, делает ввод/вывод или вызывает другие функции, которые имеют побочные эффекты.



В большинстве случаев, они полезны:

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

Также, side effects могут приводить и к неожиданным результатам:

C++ не определяет порядок, в котором вычисляются аргументы функции. Если левый аргумент будет вычисляться первым, то add(5, 6), а результат — 11. Если правый аргумент будет вычисляться первым, то add(6, 6), а результат — 12! А проблема кроется то только в побочном эффекте одного из аргументов функции add().

Вот еще пример:

Каков результат этой программы? Ответ: не определено. Если инкремент х выполнится до присваивания, то ответ — 1. Если же после, то ответ — 2.

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

Правило: Не используйте переменную с побочным эффектом больше одного раза в одном стейтменте. Если вы это сделаете, то результат будет не определен.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (53 оценок, среднее: 4,85 из 5)
Загрузка...
Подписаться на обновления:

Комментариев: 9

  1. Константин:

    Привет, Юр!
    Пожалуйста дай примеры "непоняток" помимо ин/декрементов. Спасибо!

  2. artem:

    Насчет правила не использовать постфиксный инкремент и постфиксный декремент: Когда изучал циклы for, while и do while везде использовали именно постфиксный инкремент и декремент, даже при разработке мини-игр.

    1. Юрий Юрий:

      То, как писать код — дело каждого лично. В уроках рассматриваются аспекты, которые следует знать и понимать.

  3. Иван:

    А приоритет у '++' выше ведь, чем у '='? Почему будет неоднозначность?

    1. Li4ik Li4ik:

      Да, приоритет у "++" выше, нежели в "=". И у всех компиляторах сначала выполнится операция инкремента, результат же зависит от того, в каком направлении будет выполняться следующая операция, т.е. операция присваивания. В случае с:

      Сначала выполняется операция инкремента, так как у неё приоритет выше — получим:

      И здесь уже важно то, в каком направлении будет выполняться операция присваивания, либо x++ (значение 2) будет присваиваться переменной х — результат 2, либо переменная х (значение 1) будет присваиваться x++ — результат 1. То есть, либо справа налево, либо слева направо. А это уже вопрос отдельно к каждому компилятору и его разработчику. Но операция инкремента (++) выполняется первой в любом случае.

      1. Сергей:

        Юрий, почему происходит нарушение "логики", если у нас программа выполняется сверху вниз, то сначала присваивается переменная, потом происходит инкремент, а потом она (переменная) выводится на экран? Разве на экран не должно выводиться инкрементированное число?

        1. Li4ik Li4ik:

          В статье об этом ведь рассказывается. Есть две версии инкремента: постфикс и префикс.

          В версии префикс число сразу увеличивается и выводиться уже увеличенное:

          В версии постфикс значение сначала присваивается, затем вычисляется (увеличивается):

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

          Если бы версии постфикс и префикс вычислялись одинаково, то зачем тогда нужны были бы две версии? Сделали бы один общий инкремент/декремент, да й делов то. Но есть случаи, где нужно использовать версии постфикс, есть случаи, где нужно использовать версии префикс — поэтому есть разделение на версии.

          Никакого нарушения логики нет, просто нужно понимать разницу, установленную языком С++, между версиями постфикс/префикс операций инкремента/декремента.

  4. Anton:

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

    1. Li4ik Li4ik:

      В первой части урока объясняется "инкремент" и "декремент". Во второй части речь идёт о другом понятии — "побочные эффекты". Какое здесь опровержение и чего? Опровержение не выполнять инкремент и декремент или что? Есть префикс, есть постфикс, есть побочные эффекты. Китайского рандома нет.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

ПОДПИСЫВАЙТЕСЬ

НА КАНАЛ RAVESLI В TELEGRAM

@ravesli

ПОДПИСАТЬСЯ БЕСПЛАТНО