Цей матеріал є продовження уроку №18.
Область видимості і тривалість життя
Перш ніж ми почнемо, нам потрібно спочатку розібратися з двома термінами: область видимості і тривалість життя. Область видимості визначає, де можна використовувати змінну. Тривалість життя визначає, де змінна створюється і де знищується. Ці дві концепції пов’язані між собою.
Змінні, які визначені всередині блоку, називаються локальними змінними. Локальні змінні мають автоматичну тривалість життя: вони створюються (і ініціалізуються, якщо необхідно) в точці визначення і знищуються при виході з блоку. Локальні змінні мають локальну область видимості, тобто вони входять в область видимості з точки оголошення і виходять в самому кінці блоку, в якому оголошені.
Наприклад, розглянемо наступну програму:
|
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { int x(4); // змінна x створюється і ініціалізується тут double y(5.0); // змінна y створюється і ініціалізується тут return 0; } // x і y виходять з області видимості і знищуються тут |
Оскільки змінні x і y визначені всередині блоку, який є головною функцією, то вони обидва знищуються, коли main() завершує своє виконання.
Змінні, які визначені всередині вкладених блоків, знищуються, як тільки закінчується вкладений блок:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> int main() // зовнішній блок { int m(4); // змінна m створюється і ініціалізується тут { // Початок вкладеного блоку double k(5.0); // змінна k створюється і ініціалізується тут } // k виходить з області видимості і знищується тут // Змінна k не може бути використана тут, оскільки вона вже знищена! return 0; } // змінна m виходить з області видимості і знищується тут |
Такі змінні можна використовувати тільки всередині блоків, в яких вони визначені. Оскільки кожна функція має свій власний блок, то змінні з однієї функції ніяк не впливають на змінні з іншої функції:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <iostream> void someFunction() { int value(5); // value визначається тут // value можна використовувати тут } // value виходить з області видимості і знищується тут int main() { // value не можна використовувати всередині цієї функції someFunction(); // value до сих пір заборонено використовувати тут return 0; } |
У різних функціях можуть знаходитися змінні або параметри з однаковими іменами. Це добре, тому що не потрібно турбуватися про можливість виникнення конфліктів імен між двома незалежними функціями. У наступному прикладі в обох функціях є змінні x і y. Вони навіть не підозрюють про існування один одного:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> // Параметр x можна використовувати тільки всередині функції add() int add(int x, int y) // параметр x створюється тут { return x + y; } // параметр x знищується тут // Змінну x функції main() можна використовувати тільки всередині функції main() int main() { int x = 5; // змінна x створюється тут int y = 6; std::cout << add(x, y) << std::endl; // значення змінної x функції main() копіюється в змінну x функції add() return 0; } // змінна x функції main() знищується тут |
Вкладені блоки вважаються частиною зовнішнього блоку, в якому вони визначені. Отже, змінні, які визначені в зовнішньому блоці, можуть бути видимими і всередині вкладеного блоку:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> int main() { // початок зовнішнього блоку int x(5); { // початок вкладеного блоку int y(7); // Ми можемо використовувати змінні x і y тут std::cout << x << " + " << y << " = " << x + y; } // змінна y знищується тут // змінну y тут заборонено використовувати, оскільки вона вже знищена! return 0; } // змінна x знищується тут |
Приховування імен
Змінна всередині вкладеного блоку може мати те ж ім’я, що і змінна всередині зовнішнього блоку. Коли таке трапляється, то змінна у вкладеному (внутрішньому) блоці «приховує» зовнішню змінну. Це називається приховуванням імен:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> int main() { // зовнішній блок int oranges(5); // зовнішня змінна oranges if (oranges >= 5) // відноситься до зовнішньої oranges { // вкладений блок int oranges; // приховується зовнішня змінна oranges // Ідентифікатор oranges тепер відноситься до вкладеної змінної oranges. // Зовнішня змінна oranges тимчасово прихована oranges = 10; // тут ми присвоюємо значення 10 вкладеній змінній oranges (НЕ зовнішній)! std::cout << oranges << std::endl; // виводимо значення вкладеної змінної oranges } // вкладена змінна oranges знищується тут // Ідентифікатор oranges знову відноситься до зовнішньої змінної oranges std::cout << oranges << std::endl; // виводимо значення зовнішньої змінної oranges return 0; } // зовнішня змінна oranges знищується тут |
Результат виконання програми:
10
5
Тут ми спочатку оголошуємо змінну oranges в зовнішньому блоці. Потім оголошуємо другу змінну oranges, але вже у вкладеному (внутрішньому) блоці. Коли ми присвоюємо oranges значення 10, то воно відноситься до змінної у вкладеному блоці. Після виведення цього значення, внутрішня змінна oranges знищується, залишаючи зовнішню oranges з вихідним значенням (5), яке потім виводиться. Результат виконання програми був би той же, навіть якщо б ми назвали вкладену змінну по-іншому (наприклад, nbOranges).
Зверніть увагу, якби ми не визначили вкладену змінну oranges, то ідентифікатор oranges відносився б до зовнішньої змінної і значення 10 було б присвоєно зовнішній змінній:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <iostream> int main() { // зовнішній блок int oranges(5); // зовнішня змінна oranges if (oranges >= 5) // відноситься до зовнішнього oranges { // вкладений блок // Ніякого визначення внутрішньої змінної oranges тут немає oranges = 10; // це застосовується до зовнішньої змінної oranges, хоч ми і знаходимося у вкладеному блоці std::cout << oranges << std::endl; // виводимо значення зовнішньої змінної oranges } // значенням змінної oranges буде 10 навіть після того, як ми вийдемо з вкладеного блоку std::cout << oranges << std::endl; // виводимо значення змінної oranges return 0; } // змінна oranges знищується тут |
Результат виконання програми:
10
10
В обох прикладах на зовнішню змінну oranges ніяк не впливає те, що відбувається з вкладеною змінною oranges. Єдина відмінність між двома програмами — це те, до чого застосовується вираз oranges = 10.
Приховування імен — це те, чого, як правило, слід уникати, оскільки воно може бути досить заплутаним!
Правило: Уникайте використання вкладених змінних з тими ж іменами, що мають зовнішні змінні.
Область видимості змінних
Змінні повинні визначатися в максимально обмеженій області видимості. Наприклад, якщо змінна використовується тільки всередині вкладеного блоку, то вона і повинна бути визначена в ньому:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> int main() { // Не визначайте змінну x тут { // Змінна y використовується тільки всередині цього блоку, тому визначаємо її тут int x(7); std::cout << x; } // В протилежному випадку, змінна x може бути використана і тут return 0; } |
Обмежуючи область видимості, ми зменшуємо складність програми, оскільки число активних змінних зменшується. Таким чином, легше побачити, де і які змінні використовуються. Змінна, яка визначена всередині блоку, може використовуватися тільки усередині цього ж блоку (або вкладених в неї підблоків). Цим ми спрощуємо розуміння і логіку програми.
Якщо в зовнішньому блоці потрібна змінна, то її необхідно оголошувати в зовнішньому блоці:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> int main() { int y(5); // ми оголошуємо змінну y тут, тому що вона нам буде потрібна у зовнішньому блоці пізніше { int x; std::cin >> x; // Якби ми оголосили змінну y тут, безпосередньо перед її першим фактичним використанням, if (x == 4) y = 4; } // то вона була б знищена тут std::cout << y; // а змінна y нам потрібна ще тут return 0; } |
Це один з тих рідкісних випадків, коли вам може знадобитися оголосити змінну до її першого використання.
Правило: Визначайте змінні в найбільш обмеженій області видимості.
Параметри функцій
Хоча параметри функцій не визначаються всередині основного блоку (тіла) функції, в більшості випадків вони мають локальну область видимості:
|
1 2 3 4 5 6 |
int max(int x, int y) // змінні x і y визначаються тут { // Присвоюємо більше зі значень (x або y) змінній max int max = (x > y) ? x : y; // max визначається тут return max; } // x, y і max знищуються тут |
Висновки
Змінні, визначені всередині блоків, називаються локальними змінними. Вони доступні тільки всередині блоку, в якому визначені (включаючи вкладені блоки) і знищуються при завершенні виконання цього ж блоку.
Визначайте змінні в найбільш обмеженій області видимості. Якщо змінна використовується тільки всередині вкладеного блоку, то і визначати її слід всередині цього ж вкладеного блоку.
Тест
Завдання №1
Напишіть програму, яка просить користувача ввести два цілих числа: друге повинно бути більше першого. Якщо користувач введе друге число менше першого, то використовуйте блок і тимчасову змінну, щоб поміняти місцями користувацькі числа. Потім виведіть значення цих змінних. Додайте в свій код коментарі, де і яка змінна знищується.
Результат виконання програми повинен бути наступним:
Введіть число: 4
Введіть більше число: 2
Міняємо значення місцями
Менше число: 2
Більше число: 4
Підказка: Щоб використовувати кирилицю, додайте наступний рядок коду в самий початок функції main():
|
1 |
setlocale(LC_ALL, "rus"); |
Відповідь №1
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <iostream> int main() { // Використовуємо кирилицю setlocale(LC_ALL, "rus"); std::cout << "Введите число: "; int smaller; std::cin >> smaller; std::cout << "Введите большее число: "; int larger; std::cin >> larger; // Якщо користувач ввів числа не так, як потрібно if (smaller > larger) { // то міняємо місцями ці значення std::cout << "Меняем значения местами\n"; int temp = larger; larger = smaller; smaller = temp; } // temp знищується тут std::cout << "Меньшее число: " << smaller << "\n"; std::cout << "Большее число: " << larger << "\n"; return 0; } // smaller і larger знищуються тут |
Завдання №2
У чому різниця між областю видимості і тривалістю життя змінної? Яку область видимості і тривалість життя за замовчуванням мають локальні змінні (і що це означає)?
Відповідь №2
Область видимості визначає, де змінна доступна для використання. Тривалість життя змінної визначає, коли змінна створюється і коли знищується.
Локальні змінні мають локальну область видимості, доступ до них здійснюється тільки всередині блоку, в якому вони визначені.
Локальні змінні мають автоматичну тривалість життя, що означає, що вони створюються в точці визначення і знищуються в кінці блоку, в якому визначені.
