7.5 — Cieniowanie zmiennych (ukrywanie nazw)

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 }; // here's the outer block apples

    { // nested block
        // apples refers to outer block apples here
        std::cout << apples << '\n'; // print value of outer block apples

        int apples{ 0 }; // define apples in the scope of the nested block

        // apples now refers to the nested block apples
        // the outer block apples is temporarily hidden

        apples = 10; // this assigns value 10 to nested block apples, not outer block apples

        std::cout << apples << '\n'; // print value of nested block apples
    } // nested block apples destroyed


    std::cout << apples << '\n'; // prints value of outer block apples

    return 0;
} // outer block apples destroyed

Jeś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}; // here's the outer block apples

    { // nested block
        // apples refers to outer block apples here
        std::cout << apples << '\n'; // print value of outer block apples

        // no inner block apples defined in this example

        apples = 10; // this applies to outer block apples

        std::cout << apples << '\n'; // print value of outer block apples
    } // outer block apples retains its value even after we leave the nested block

    std::cout << apples << '\n'; // prints value of outer block apples

    return 0;
} // outer block apples destroyed

Powyż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'; // value is not shadowed here, so this refers to the global value
}

int main()
{
    int value { 7 }; // hides the global variable value (wherever local variable value is in scope)

    ++value; // increments local value, not global value

    std::cout << "local variable value: " << value << '\n';

    foo();

    return 0;
} // local value is destroyed

Ten 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 }; // hides the global variable value
    ++value; // increments local value, not global value

    --(::value); // decrements global value, not local value (parenthesis added for readability)

    std::cout << "local variable value: " << value << '\n';
    std::cout << "global variable value: " << ::value << '\n';

    return 0;
} // local value is destroyed

Ten 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.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
85 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze