На цьому уроці ми розглянемо, що таке інкремент та декремент в мові С++, а також розберемося з таким поняттям, як «побічні ефекти».
Інкремент та декремент
Операції інкременту (збільшення на 1) і декременту (зменшення на 1) змінних настільки поширені, що у них є свої власні оператори в мові C++. Крім того, кожен з цих операторів має дві версії застосування: префікс і постфікс.
| Оператор | Символ | Приклад | Операція |
| Префіксний інкремент (пре-інкремент) | ++ | ++x | Інкремент x, а потім обчислення x |
| Префіксний декремент (пре-декремент) | −− | −−x | Декремент x, а потім обчислення x |
| Постфіксний інкремент (пост-інкремент) | ++ | x++ | Обчислення x, а потім інкремент x |
| Постфіксний декремент (пост-декремент) | −− | x−− | Обчислення x, а потім декремент x |
З операторами інкременту/декременту версії префікс все просто. Значення змінної х спочатку збільшується/зменшується, а потім вже обчислюється, наприклад:
|
1 2 |
int x = 5; int y = ++x; // x = 6 і 6 присвоюється змінній y |
А ось з операторами інкременту/декременту версії постфікс трохи складніше. Компілятор створює тимчасову копію змінної х, збільшує або зменшує оригінальний х (НЕ копію), а потім повертає копію. Тільки після повернення копія х видаляється, наприклад:
|
1 2 |
int x = 5; int y = x++; // x = 6, але змінній y присвоюється 5 |
Розглянемо вищенаведений код детально. По-перше, компілятор створює тимчасову копію х, яка має таке ж значення, що і оригінал (5). Потім збільшується початковий х з 5 до 6. Після цього компілятор повертає тимчасову копію, значенням якої є 5, і присвоює її змінній y. Тільки після цього копія x знищується. Отже, у вищенаведеному прикладі ми отримаємо у = 5 і х = 6.
Ось ще один приклад, який показує різницю між версіями префікс і постфікс:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> int main() { int x = 5, y = 5; std::cout << x << " " << y << std::endl; std::cout << ++x << " " << --y << std::endl; // версія префікс std::cout << x << " " << y << std::endl; std::cout << x++ << " " << y-- << std::endl; // версія постфікс std::cout << x << " " << y << std::endl; return 0; } |
Результат виконання програми:
5 5
6 4
6 4
6 4
7 3
У рядку №7 змінні х і у збільшуються/зменшуються на одиницю безпосередньо перед обробкою компілятором, так що відразу виводяться їх нові значення. А в рядку №9 тимчасові копії (х = 6, у = 4) відправляються в cout, а вже тільки потім оригінальні х і у збільшуються/зменшуються на одиницю. Саме тому зміни значень змінних після виконання операторів версії постфікс не видно до наступного рядка.
Версія префікс збільшує/зменшує значення змінних перед обробкою компілятором, версія постфікс — після обробки компілятором.
Правило: Використовуйте префіксний інкремент і префіксний декремент замість постфіксного інкременту і постфіксного декременту. Версії префікс не тільки продуктивніші, але і помилок з ними (за статистикою) менше.
Побічні ефекти
Функція або вираз має побічний ефект, якщо вона/він змінює стан чого-небудь, виконує ввід/вивід або викликає інші функції, які мають побічні ефекти.
У більшості випадкі побічні ефекти є корисними:
|
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { int x = 5; ++x; std::cout << x; return 0; } |
У вищенаведеному прикладі оператор присвоювання має побічний ефект, який проявляється в зміні значення змінної х. Оператор ++ має побічний ефект інкременту змінної х. Вивід х має побічний ефект внесення змін в консольне вікно.
Також побічні ефекти можуть приводити і до несподіваних результатів:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> 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().
Ось ще один приклад:
|
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { int x = 1; x = x++; std::cout << x; return 0; } |
Який результат виконання цієї програми? Якщо інкремент х виконається до операції присвоювання, то відповідь — 1. Якщо ж після операції присвоювання, то відповідь — 2.
Є й інші випадки, в яких мова C++ не визначає порядок обробки даних, тому в різних компіляторах можуть бути різні результати. Але навіть в тих випадках, коли мова C++ і уточнює порядок обробки даних, деякі компілятори все одно обчислюють змінні з побічними ефектами некоректно. Цього всього можна уникнути, якщо використовувати змінні з побічними ефектами не більше одного разу в одному стейтменті.
Правило: Не використовуйте змінну з побічним ефектом більше одного разу в одному стейтменті.
