Ми вже знаємо, що змінні, оголошені всередині блоку, називаються локальними. Вони мають локальну область видимості (використовуються тільки всередині блоку, в якому оголошені) і автоматичну тривалість життя (створюються в точці визначення і знищуються в кінці блоку).
Глобальними називаються змінні, які оголошені поза блоком. Вони мають статичну тривалість життя, тобто створюються при запуску програми і знищуються при її завершенні. Глобальні змінні мають глобальну область видимості (або “файлову область видимості”), тобто їх можна використовувати в будь-якому місці файлу, в якому вони оголошені.
Визначення глобальних змінних
Зазвичай глобальні змінні оголошують у верхній частині коду, нижче директив #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).

Велике вам дякую за проведенну роботу з перекладом! Так матеріал НАБАГАТО краще засвоюється!