5.4 – Операторы инкремента/декремента и их побочные эффекты

Добавлено 3 мая 2021 в 18:56

Инкремент и декремент переменных

Инкремент (увеличение на 1) и декремент (уменьшение на 1) переменной настолько распространены, что имеют свои собственные операторы.

Операторы инкремента и декремента
ОператорОбозначениеПример использованияОперация
Префиксный инкремент (пре-инкремент)++++xУвеличивает x на 1 и возвращает x
Префиксный декремент (пре-декремент)----xУменьшает x на 1 и возвращает x
Постфиксный инкремент (пост-инкремент)++x++Копирует x, затем увеличивает x на 1, а затем возвращает копию
Постфиксный декремент (пост-декремент)--x--Копирует x, затем уменьшает x на 1, а затем возвращает копию

Обратите внимание, что существует две версии каждого оператора – префиксная версия (где оператор стоит перед операндом) и постфиксная версия (где оператор стоит после операнда).

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

#include <iostream>
 
int main()
{
    int x { 5 };
    int y = ++x; // x увеличивается до 6, x вычисляется в значение 6, а 6 присваивается y
 
    std::cout << x << ' ' << y;
    return 0;
}

Эта программа напечатает:

6 6

Постфиксные операторы инкремента/декремента немного сложнее. Сначала создается копия операнда. Затем операнд (не копия) увеличивается или уменьшается на единицу. И, наконец, вычисляется копия (а не оригинал). Например:

#include <iostream>
 
int main()
{
    int x { 5 };
    int y = x++; // x увеличивается до 6, копия исходного x вычисляется в значение 5, а 5 присваивается y
 
    std::cout << x << ' ' << y;
    return 0;
}

Эта программа напечатает:

6 5

Давайте подробнее рассмотрим, как работает эта строка 6. Сначала создается временная копия x, которая начинается с того же значения, что и x (5). Затем реальная переменная x увеличивается с 5 до 6. Затем возвращается копия x (которая всё еще имеет значение 5) и присваивается переменной y. Затем эта временная копия отбрасывается.

Следовательно, y заканчивается значением 5 (значение до инкремента), а x заканчивается значением 6 (значение после инкремента).

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

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

#include <iostream>
 
int main()
{
    int x{ 5 };
    int y{ 5 };
    std::cout << x << ' ' << y << '\n';
    std::cout << ++x << ' ' << --y << '\n'; // префиксная версия
    std::cout << x << ' ' << y << '\n';
    std::cout << x++ << ' ' << y-- << '\n'; // постфиксная версия
    std::cout << x << ' ' << y << '\n';
 
    return 0;
}

Эта программа в результате напечатает:

5 5
6 4
6 4
6 4
7 3

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

В 10-й строке мы выполняем постфиксные инкремент и декремент. В этой строке в std::cout отправляются копии x и y (с ранее увеличенными и уменьшенными значениями), поэтому мы не видим здесь проявлений инкремента и декремента. Эти изменения не проявятся до следующей строки, когда x и y вычисляются снова.

Лучшая практика

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

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

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

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

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

x = 5;          // оператор присваивания изменяет состояние x
++x;            // operator++ изменяет состояние x
std::cout << x; // operator<< изменяет состояние консоли

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

Однако побочные эффекты также могут привести к неожиданным результатам:

int add(int x, int y)
{
    return x + y;
}
 
int main()
{
    int x{ 5 };
    int value = add(x, ++x); // это 5 + 6, или 6 + 6?
    // Это зависит от того, в каком порядке ваш компилятор вычисляет
    // аргументы, передаваемые функции
 
    std::cout << value; // значение может быть 11 или 12, в зависимости 
                        // от того, как вычисляется строка выше!
    return 0;
}

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

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

Предупреждение


C++ не определяет порядок вычисления аргументов функции или операндов операторов.

Предупреждение


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

Теги

C++ / CppДекрементДля начинающихИнкрементОбучениеОператор (программирование)Программирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.