Każdy blok definiuje swój własny obszar zasięgu. Co więc się stanie, gdy w zagnieżdżonym bloku mamy zmienną, która ma taką samą nazwę jak zmienna w bloku zewnętrznym? Kiedy tak się dzieje, zagnieżdżona zmienna „ukrywa” zmienną zewnętrzną w obszarach, w których obydwie wchodzą w zakres. To się nazywa ukrywanie imienia lub cieniowanie.
Zacienianie zmiennych lokalnych
#include <iostream>
int main()
{ // outer block
int apples { 5 }; // oto zewnętrzne jabłka bloku
{ // nested block
// jabłka odnosi się tutaj do jabłek z zewnętrznego bloku
std::cout << apples << '\n'; // wydrukuj wartość zewnętrznego bloku jabłka
int apples{ 0 }; // zdefiniuj jabłka w zakresie zagnieżdżonego bloku
// jabłka odnosi się teraz do zagnieżdżonych jabłek blokowych
// zewnętrzny blok jabłek jest tymczasowo ukryty
apples = 10; // to przypisuje wartość 10 do zagnieżdżonych jabłek blokowych, a nie zewnętrznych jabłek blokowych
std::cout << apples << '\n'; // wydrukuj wartość zagnieżdżonego bloku jabłka
} // zniszczenie zagnieżdżonych jabłek blokowych
std::cout << apples << '\n'; // wypisuje wartość zewnętrznych bloków jabłek
return 0;
} // zewnętrzne jabłka blokowe zniszczoneJeśli uruchomisz ten program, wyświetli się:
5 10 5
W powyższym programie najpierw deklarujemy zmienną o nazwie apples w bloku zewnętrznym. Zmienna ta jest widoczna w obrębie wewnętrznego bloku, co możemy zobaczyć drukując jej wartość (5). Następnie deklarujemy inną zmienną (również o nazwie apples) w zagnieżdżonym bloku. Od tego miejsca do końca bloku nazwa apples odnosi się do zagnieżdżonego bloku apples, a nie zewnętrzny blok apples.
Zatem, kiedy przypisujemy wartość 10 Do apples, przypisujemy go do zagnieżdżonego bloku apples. Po wydrukowaniu tej wartości (10), zagnieżdżony blok kończy się i zagnieżdżony blok apples jest zniszczony. Istnienie i wartość bloku zewnętrznego apples nie ma to wpływu i udowadniamy to, drukując wartość zewnętrznego bloku apples (5).
Należy pamiętać, że jeśli zagnieżdżony blok apples nie została zdefiniowana, nazwa apples w zagnieżdżonym bloku nadal będzie odnosić się do bloku zewnętrznego apples, a więc przypisanie wartości 10 Do apples dotyczyłoby bloku zewnętrznego apples:
#include <iostream>
int main()
{ // outer block
int apples{5}; // oto zewnętrzne jabłka bloku
{ // nested block
// jabłka odnosi się tutaj do jabłek z zewnętrznego bloku
std::cout << apples << '\n'; // wydrukuj wartość zewnętrznego bloku jabłka
// brak zdefiniowanych jabłek bloków wewnętrznych w tym przykładzie
apples = 10; // dotyczy to jabłek z zewnętrznych bloków
std::cout << apples << '\n'; // wydrukuj wartość zewnętrznego bloku jabłka
} // zewnętrzny blok jabłek zachowuje swoją wartość nawet po opuszczeniu zagnieżdżonego bloku
std::cout << apples << '\n'; // wypisuje wartość zewnętrznych bloków jabłek
return 0;
} // zewnętrzne jabłka blokowe zniszczonePowyższy program wypisuje:
5 10 10
Wewnątrz zagnieżdżonego bloku nie ma możliwości bezpośredniego dostępu do cieniowanej zmiennej z bloku zewnętrznego.
Zacienianie zmiennych globalnych
Podobnie jak zmienne w bloku zagnieżdżonym mogą przesłaniać zmienne w bloku zewnętrznym, zmienne lokalne o tej samej nazwie co zmienna globalna będą przesłaniać zmienną globalną wszędzie tam, gdzie zmienna lokalna znajduje się w zasięgu:
#include <iostream>
int value { 5 }; // global variable
void foo()
{
std::cout << "global variable value: " << value << '\n'; // wartość nie jest tutaj cieniowana, więc odnosi się to do wartości globalnej
}
int main()
{
int value { 7 }; // ukrywa wartość zmiennej globalnej (gdziekolwiek w zakresie znajduje się wartość zmiennej lokalnej)
++value; // zwiększa wartość lokalną, a nie globalną
std::cout << "local variable value: " << value << '\n';
foo();
return 0;
} // wartość lokalna jest niszczonaTen kod wypisuje:
local variable value: 8 global variable value: 5
Ponieważ jednak zmienne globalne są częścią globalnej przestrzeni nazw, możemy użyć operatora zasięgu (::) bez przedrostka, aby poinformować kompilator, że mamy na myśli zmienną globalną, a nie zmienną lokalną.
#include <iostream>
int value { 5 }; // global variable
int main()
{
int value { 7 }; // ukrywa zmienną globalną wartość
++value; // zwiększa wartość lokalną, a nie globalną
--(::value); // zmniejsza wartość globalną, a nie lokalną (nawias dodano dla czytelności)
std::cout << "local variable value: " << value << '\n';
std::cout << "global variable value: " << ::value << '\n';
return 0;
} // wartość lokalna jest niszczonaTen kod wypisuje:
local variable value: 8 global variable value: 4
Unikaj zmiennego cieniowania
Ogólnie rzecz biorąc, należy unikać przesłaniania zmiennych lokalnych, ponieważ może to prowadzić do niezamierzonych błędów w przypadku użycia lub modyfikacji niewłaściwej zmiennej. Niektóre kompilatory wyświetlają ostrzeżenie, gdy zmienna jest cieniowana.
Z tego samego powodu, dla którego zalecamy unikanie cieniowania zmiennych lokalnych, zalecamy również unikanie cieniowania zmiennych globalnych. Można tego w prosty sposób uniknąć, jeśli wszystkie nazwy globalne używają przedrostka „g_”.
Najlepsza praktyka
Unikaj zmiennego cieniowania.
Dla użytkowników gcc
GCC i Clang wspierają flagę -Wshadow który wygeneruje ostrzeżenia, jeśli zmienna jest cieniowana. Istnieje kilka podwariantów tej flagi (-Wshadow=global, -Wshadow=local, i -Wshadow=compatible-local. Skonsultuj się z Dokumentacja GCC w celu wyjaśnienia różnic.
W programie Visual Studio takie ostrzeżenia są domyślnie włączone.

