W lekcji 7.3 -- Zmienne lokalne, omawialiśmy, że zmienne lokalne to zmienne zdefiniowane wewnątrz treści funkcji. Zmienne lokalne mają zasięg blokowy (są widoczne tylko w bloku, w którym są zadeklarowane) i mają automatyczny czas trwania (są tworzone w momencie definicji i niszczone po wyjściu z bloku).
W C++ zmienne można również zadeklarować poza funkcji. Takie zmienne nazywane są zmiennymi globalnymi.
Deklarowanie zmiennych globalnych
Zgodnie z konwencją zmienne globalne deklaruje się na górze pliku, poniżej dołączeń, w globalnej przestrzeni nazw. Oto przykład definiowania zmiennej globalnej:
#include <iostream>
// Zmienne zadeklarowane poza funkcją są zmiennymi globalnymi
int g_x {}; // global variable g_x
void doSomething()
{
// zmienne globalne mogą być widoczne i używane w całym pliku
g_x = 3;
std::cout << g_x << '\n';
}
int main()
{
doSomething();
std::cout << g_x << '\n';
// zmienne globalne mogą być widoczne i używane w całym pliku
g_x = 5;
std::cout << g_x << '\n';
return 0;
}
// g_x wyprowadza tutaj poza zakresPowyższy przykład wypisuje:
3 3 5
Zakres zmiennych globalnych
Identyfikatory zadeklarowane w globalnej przestrzeni nazw mają globalny zasięg przestrzeni nazw (powszechnie nazywany zasięgiem globalnym, a czasami nieformalnie zwane zakres pliku), co oznacza, że są widoczne od momentu deklaracji aż do końca pliku w którym są zadeklarowane.
Po zadeklarowaniu zmienna globalna może być używana w dowolnym miejscu pliku od tego momentu! W powyższym przykładzie zmienna globalna g_x jest używana w obu funkcjach doSomething() i main().
Zmienne globalne można również definiować w przestrzeni nazw zdefiniowanej przez użytkownika. Oto ten sam przykład co powyżej, ale g_x został przeniesiony z zakresu globalnego do przestrzeni nazw zdefiniowanej przez użytkownika Foo:
#include <iostream>
namespace Foo // Foo jest w zakresie globalnym
{
int g_x {}; // g_x znajduje się teraz w przestrzeni nazw Foo, ale nadal jest zmienną globalną
}
void doSomething()
{
// zmienne globalne mogą być widoczne i używane w całym pliku
Foo::g_x = 3;
std::cout << Foo::g_x << '\n';
}
int main()
{
doSomething();
std::cout << Foo::g_x << '\n';
// zmienne globalne mogą być widoczne i używane w całym pliku
Foo::g_x = 5;
std::cout << Foo::g_x << '\n';
return 0;
}Chociaż identyfikator g_x jest teraz ograniczony do zakresu namespace Foo, nazwa ta jest nadal dostępna globalnie (przez Foo::g_x) i g_x jest nadal zmienną globalną.
Kluczowa informacja
Zmienne zadeklarowane w przestrzeni nazw są również globalne zmienne.
Najlepsza praktyka
Wolę definiować zmienne globalne w przestrzeni nazw, a nie w globalnej przestrzeni nazw.
Zmienne globalne mają statyczny czas trwania
Zmienne globalne są tworzone podczas uruchamiania programu (przed main() rozpoczęciem wykonywania) i niszczone po jego zakończeniu. Nazywa się to czas statyczny z czas statyczny są czasami nazywane zmiennymi statycznymi.
Nazewnictwo zmiennych globalnych
Zgodnie z konwencją niektórzy programiści poprzedzają identyfikatory zmiennych globalnych przedrostkiem „g” lub „g_”, aby wskazać, że są one globalne. Przedrostek ten służy kilku celom:
- Pomaga uniknąć kolizji nazewnictwa z innymi identyfikatorami w pliku globalnym. przestrzeni nazw.
- Pomaga zapobiegać niezamierzonemu cieniowaniu nazw (omówimy ten punkt w dalszej części lekcji 7.5 — Ukrywanie zmiennych (ukrywanie nazw)).
- Pomaga wskazać, że zmienne z prefiksami pozostają poza zakresem funkcji, a zatem wszelkie zmiany, których w nich dokonamy, również zostaną zachowane.
Zmienne globalne zdefiniowane w przestrzeni nazw zdefiniowanej przez użytkownika często pomijają prefiks (ponieważ pierwsze dwa punkty na powyższej liście nie stanowią w tym przypadku problemu i możemy wywnioskować, że zmienna jest globalna, gdy zobaczymy dołączoną na początku nazwę przestrzeni nazw). Jednak nie zaszkodzi, jeśli chcesz zachować przedrostek jako bardziej widoczne przypomnienie trzeciego punktu.
Najlepsza praktyka
Rozważ użycie przedrostka „g” lub „g_” podczas nazywania zmiennych globalnych (szczególnie tych zdefiniowanych w globalnej przestrzeni nazw), aby pomóc odróżnić je od lokalnych zmiennych i parametrów funkcji.
Nota autora
Czasami otrzymujemy opinie od czytelników z pytaniami, czy przedrostki takie jak g_ są w porządku, ponieważ powiedziano im, że przedrostki są formą notacji węgierskiej oraz „notacja węgierska jest zła”.
Sprzeciw wobec notacji węgierskiej wynika głównie ze stosowania notacji węgierskiej do kodowania typ zmiennej w nazwie zmiennej, np. nAge, gdzie n oznacza int. Nie jest to zbyt przydatne we współczesnym C++.
Jednak używanie przedrostków (zwykle g/g_, s/s_, i m/m_) do reprezentowania zakres lub czasu trwania zmiennej dodaje wartość z tych powodów zaznaczono w tej sekcji.
Inicjalizacja zmiennej globalnej
W przeciwieństwie do zmiennych lokalnych, które są domyślnie niezainicjowane, zmienne o statycznym czasie trwania są domyślnie inicjowane od zera.
Niestałe zmienne globalne mogą być opcjonalnie inicjowane:
int g_x; // no explicit initializer (zero-initialized by default)
int g_y {}; // wartość zainicjowana (w wyniku inicjalizacji zerowej)
int g_z { 1 }; // lista zainicjowana określoną wartościąStałe zmienne globalne
Podobnie jak zmienne lokalne, zmienne globalne mogą być stałe. Podobnie jak w przypadku wszystkich stałych, stałe zmienne globalne muszą zostać zainicjowane.
#include <iostream>
const int g_x; // błąd: stała zmienne muszą zostać zainicjowane
constexpr int g_w; // błąd: zmienne constexpr muszą zostać zainicjowane
const int g_y { 1 }; // const zmienna globalna g_y, zainicjowana wartością
constexpr int g_z { 2 }; // zmienna globalna constexpr g_z, zainicjowana przez wartość
void doSomething()
{
// zmienne globalne mogą być widoczne i używane w całym pliku
std::cout << g_y << '\n';
std::cout << g_z << '\n';
}
int main()
{
doSomething();
// zmienne globalne mogą być widoczne i używane w całym pliku
std::cout << g_y << '\n';
std::cout << g_z << '\n';
return 0;
}
// g_y i g_z wykraczają tutaj poza zakresPowiązana treść
Stałe globalne omówimy bardziej szczegółowo na lekcji 7.10 — Współdzielenie stałych globalnych w wielu plikach (przy użyciu zmiennych wbudowanych).
Ostrzeżenie dotyczące (niestałych) zmiennych globalnych
Nowych programistów często kusi, aby używać wielu zmiennych globalnych, ponieważ można ich używać bez konieczności jawnego przekazywania ich do każdej funkcji, która ich potrzebuje. Jednakże ogólnie należy unikać stosowania niestałych zmiennych globalnych! Omówimy dlaczego w nadchodzącej lekcji 7.8 — Dlaczego (nie stałe) zmienne globalne są złe.
Szybkie podsumowanie
// Niestałe zmienne globalne
int g_x; // definiuje niezainicjowaną zmienną globalną (zero inicjowane przez domyślnie)
int g_x {}; // definiuje jawnie zainicjowaną wartość globalną zmienną
int g_x { 1 }; // definiuje jawnie zainicjowaną zmienną globalną
// Stałe zmienne globalne
const int g_y; // błąd: zmienne const muszą zostać zainicjowane
const int g_y { 2 }; // definiuje zainicjowaną stałą globalną
// Zmienne globalne Constexpr
constexpr int g_y; // błąd: zmienne constexpr muszą zostać zainicjowane
constexpr int g_y { 3 }; // definiuje zainicjowany globalny constexpr
