Ми вже знаємо, що змінні, оголошені всередині блоку, називаються локальними. Вони мають локальну область видимості (використовуються тільки всередині блоку, в якому оголошені) і автоматичну тривалість життя (створюються в точці визначення і знищуються в кінці блоку).
Глобальними називаються змінні, які оголошені поза блоком. Вони мають статичну тривалість життя, тобто створюються при запуску програми і знищуються при її завершенні. Глобальні змінні мають глобальну область видимості (або “файлову область видимості”), тобто їх можна використовувати в будь-якому місці файлу, в якому вони оголошені.
Визначення глобальних змінних
Зазвичай глобальні змінні оголошують у верхній частині коду, нижче директив #include, але вище будь-якого іншого коду, наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> // Змінні, оголошені поза блоком, є глобальними змінними int g_x; // глобальна змінна g_x const int g_y(3); // глобальна змінна g_y void doSomething() { // Глобальні змінні можна використовувати в будь-якому місці програми g_x = 4; std::cout << g_y << "\n"; } int main() { doSomething(); // Глобальні змінні можна використовувати в будь-якомі місці програми g_x = 7; std::cout << g_y << "\n"; return 0; } |
Подібно до того, як змінні у внутрішньому блоці приховують змінні з тими ж іменами в зовнішньому блоці, локальні змінні приховують глобальні змінні з однаковими іменами всередині блоку, в якому вони визначені. Однак за допомогою оператора дозволу області видимості (::
) компілятору можна повідомити, яку версію змінної ви хочете використовувати: глобальну чи локальну. Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> int value(4); // глобальна змінна int main() { int value = 8; // ця змінна (локальна) приховує значення глобальної змінної value++; // збільшується локальна змінна value, а не глобальна ::value--; // зменшується глобальна змінна value, а не локальна std::cout << "Global value: " << ::value << "\n"; std::cout << "Local value: " << value << "\n"; return 0; } // локальна змінна знищується |
Результат виконання програми:
Global value: 3
Local value: 9
Використовувати однакові імена для локальних і глобальних змінних — це прямий шлях до проблем і помилок, тому подібне робити не рекомендується. Багато розробників додають до глобальних змінних префікс g_
(“g” від англ. “global”). Таким чином можна вбити відразу двох зайців: визначити глобальні змінні і уникнути конфліктів імен з локальними змінними.
Ключові слова static і extern
В якості доповнення до області видимості і тривалості життя, змінні мають ще одну властивість — зв’язок. Зв’язок змінної визначає, чи належать декілька згадок одного ідентифікатора до однієї і тієї ж змінної чи ні.
Змінна без зв’язків — це змінна з локальною областю видимості, яка відноситься тільки до блоку, в якому вона визначена. Це звичайні локальні змінні. Дві змінні з однаковими іменами, але визначені в різних функціях, не мають ніякого зв’язку — кожна з них вважається незалежною бойовою одиницею.
Змінна, що має внутрішні зв’язки, називається внутрішньою змінною (або “статичною змінною”). Вона може використовуватися в будь-якому місці файлу, в якому визначена, але не відноситься до чого-небудь поза цим файлом.
Змінна, що має зовнішні зв’язки, називається зовнішньою змінною. Вона може використовуватися як в файлі, в якому визначена, так і в інших файлах.
Якщо ви хочете зробити глобальну змінну внутрішньою (яку можна використовувати тільки всередині одного файлу) — використовуйте ключове слово static:
1 2 3 4 5 6 7 8 |
#include <iostream> static int g_x; // g_x - це статична глобальна змінна і її можна використовувати тільки всередині цього файлу int main() { return 0; } |
Аналогічно, якщо ви хочете зробити глобальну змінну зовнішньою (яку можна використовувати в будь-якому файлі програми) — використовуйте ключове слово extern:
1 2 3 4 5 6 7 8 |
#include <iostream> extern double g_y(9.8); // g_y - це зовнішня глобальна змінна і її можна використовувати і в інших файлах програми int main() { return 0; } |
За замовчуванням, неконстантні змінні, оголошені поза блоком, вважаються зовнішніми. Однак константні змінні, оголошені поза блоком, вважаються внутрішніми.
Попередні оголошення змінних з використанням extern
З уроку №23 ми вже знаємо, що для використання функцій, які визначені в іншому файлі, потрібно використовувати попередні оголошення.
Аналогічно, щоб використовувати зовнішню глобальну змінну, яка була оголошена в іншому файлі, потрібно записати попереднє оголошення змінної з використанням ключового слова extern
(без значення для ініціалізації). Наприклад:
global.cpp:
1 2 3 4 |
// Визначаємо дві глобальні змінні int g_m; // неконстантні змінні мають зовнішній зв'язок за замовчуванням int g_n(3); // неконстантні глобальні змінні мають зовнішній зв'язок за замовчуванням // g_m і g_n можна використовувати в будь-якому місці цього файлу |
main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> extern int g_m; // попереднє оголошення g_m. Тепер g_m можна використовувати в будь-якому місці цього файлу int main() { extern int g_n; // попереднє оголошення g_n. Тепер g_n можна використовувати тільки всередині main() g_m = 4; std::cout << g_n; // повинно вивести 3 return 0; } |
Якщо попереднє оголошення знаходиться поза блоком, то воно застосовується до всього файлу. Якщо ж всередині блоку, то воно застосовується тільки до нього.
Якщо змінна оголошена за допомогою ключового слова static
, то отримати доступ до неї за допомогою попереднього оголошення не вийде, наприклад:
constants.cpp:
1 |
static const double g_gravity(9.8); |
main.cpp:
1 2 3 4 5 6 7 8 9 |
#include <iostream> extern const double g_gravity; // не знайде g_gravity в constants.cpp, оскільки змінна g_gravity є внутрішньою змінною int main() { std:: cout << g_gravity; // викличе помилку компіляції, так як змінна g_gravity не була визначена для використання в main.cpp return 0; } |
Зверніть увагу, якщо ви хочете оголосити неініціалізовану неконстантну глобальну змінну, то не використовуйте ключове слово extern
, інакше C++ буде думати, що ви намагаєтеся записати попереднє оголошення.
Зв’язки функцій
Функції мають такі ж властивості зв’язку, що і змінні. За замовчуванням вони мають зовнішній зв’язок, який можна змінити на внутрішній за допомогою ключового слова static
:
1 2 3 4 5 6 |
// Ця функція визначена як static і може використовуватися тільки всередині цього файлу. // Спроби доступу до неї через прототип функції не будуть успішними static int add(int a, int b) { return a + b; } |
Попередні оголошення функцій не потребують ключового слова extern
. Компілятор може визначити сам (по тілу функції): визначаєте ви функцію чи пишете її прототип.
Файлова область видимості vs. Глобальна область видимості
Терміни “файлова область видимості” і “глобальна область видимості”, як правило, викликають подив, і це частково пояснюється їх неофіційним використанням. У теорії, в мові C++ всі глобальні змінні мають файлову область видимості. Однак по факту, термін “файлова область видимості” частіше застосовується до внутрішніх глобальних змінних, а “глобальна область видимості” до зовнішніх глобальних змінних.
Наприклад, розглянемо наступну програму:
global.cpp:
1 |
int g_y(3); // зовнішній зв'язок за замовчуванням |
main.cpp:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> extern int g_y; // попереднє оголошення g_y. Тепер g_y можна використовувати в будь-якомі місці цього файлу int main() { std::cout << g_y; // повинно вивести 3 return 0; } |
Змінна g_y
має файлову область видимості всередині global.cpp. Доступ до цієї змінної поза файлом global.cpp відсутній. Зверніть увагу, хоч ця змінна і використовується в main.cpp, сам main.cpp не бачить її, він бачить тільки попереднє оголошення g_y
(яке також має файлову область видимості). Лінкер відповідає за зв’язування визначення g_y
в global.cpp з використанням g_y
в main.cpp.
Глобальні символьні константи
На уроці про символьні константи, ми визначали їх наступним чином:
constants.h:
1 2 3 4 5 6 7 8 9 10 11 12 |
#ifndef CONSTANTS_H #define CONSTANTS_H // Визначаємо окремий простір імен для зберігання констант namespace Constants { const double pi(3.14159); const double avogadro(6.0221413e23); const double my_gravity(9.2); // ... інші константи } #endif |
Хоча це просто і відмінно підходить для невеликих програм, але кожен раз, коли constants.h підключається в інший файл, кожна з цих змінних копіюється в цей файл. Таким чином, якщо constants.h підключити в 20 різних файлів, то кожна зі змінних продублюється 20 разів. Header guards не зупинять це, тому що вони тільки запобігають підключенню заголовку більше одного разу в один файл. Дублювання змінних насправді не є проблемою (оскільки константи часто не займають багато пам’яті), але зміна значення однієї константи потребує перекомпіляції кожного файлу, в якому вона використовується, що може призвести до великих витрат часу в більших проектах.
Уникнути цієї проблеми можна, перетворивши ці константи в константні глобальні змінні, і змінивши заголовки тільки для зберігання попередніх оголошень змінних. Наприклад:
constants.cpp:
1 2 3 4 5 6 7 |
namespace Constants { // Фактичні глобальні змінні extern const double pi(3.14159); extern const double avogadro(6.0221413e23); extern const double my_gravity(9.2); } |
constants.h:
1 2 3 4 5 6 7 8 9 10 11 12 |
#ifndef CONSTANTS_H #define CONSTANTS_H namespace Constants { // Тільки попередні оголошення extern const double pi; extern const double avogadro; extern const double my_gravity; } #endif |
Їх використання в коді залишається незмінним:
1 2 3 4 5 |
#include "constants.h" //... double circumference = 2 * radius * Constants::pi; //... |
Тепер визначення символьних констант виконується тільки один раз (в constants.cpp). Будь-які зміни, зроблені в constants.cpp, потребують перекомпіляції тільки (одного) цього файлу.
Але є і зворотна сторона медалі: такі константи більше не будуть вважатися константами часу компіляції і, тому, не зможуть використовуватися будь-де, де буде потрібна константа такого типу.
Оскільки глобальні символьні константи повинні знаходитися в окремому просторі імен і бути доступними тільки для читання, то використовувати префікс g_
вже не обов’язково.
Примітка: У початківців часто виникає спокуса використовувати просто безліч глобальних змінних, оскільки з ними легко працювати, особливо коли задіяно багато функцій. Проте, цього слід уникати! Чому? Про це ми поговоримо на наступному уроці.
Висновки
Підсумуємо вищесказане:
Глобальні змінні мають глобальну область видимості і можуть використовуватися в будь-якому місці програми. Подібно до функцій, ви повинні використовувати попередні оголошення (з ключовим словом extern
), щоб використовувати глобальну змінну, визначену в іншому файлі.
За замовчуванням, глобальні неконстантні змінні мають зовнішній зв’язок. Ви можете використати ключове слово static
, щоб зробити їх внутрішніми.
За замовчуванням, глобальні константні змінні мають внутрішній зв’язок. Ви можете використати ключове слово extern
, щоб зробити їх зовнішніми.
Використовуйте префікс g_
для ідентифікації ваших неконстантних глобальних змінних.
Тест
У чому різниця між областю видимості, тривалістю життя і зв’язком змінних? Які типи тривалості життя, області видимості і зв’язку мають глобальні змінні?
Відповідь
Область видимості визначає, де змінна доступна для використання. Тривалість життя визначає, де змінна створюється і де знищується. Зв’язок визначає, чи може змінна використовуватися в іншому файлі чи ні.
Глобальні змінні мають глобальну область видимості (або “файлову область видимості”), що означає, що вони доступні з точки оголошення до кінця файлу, в якому оголошені.
Глобальні змінні мають статичну тривалість життя, що означає, що вони створюються при запуску програми і знищуються при її завершенні.
Глобальні змінні можуть мати або внутрішній, або зовнішній зв’язок (що можна змінити через використання ключових слів static
і extern
).
Велике вам дякую за проведенну роботу з перекладом! Так матеріал НАБАГАТО краще засвоюється!