6.5 – Затенение переменных (скрытие имен)
Каждый блок определяет свою собственную область видимости. Итак, что происходит, когда у нас есть переменная внутри вложенного блока, имя которой совпадает с именем переменной во внешнем блоке? Когда это происходит, вложенная переменная «скрывает» внешнюю переменную в областях, где они обе находятся в области видимости. Это называется скрытием имен или затенением.
Затенение локальных переменных
#include <iostream>
int main()
{ // внешний блок
int apples { 5 }; // здесь apples внешнего блока
{ // вложенный блок
// apples здесь относится к apples внешнего блока
std::cout << apples << '\n'; // выводим значение apples внешнего блока
int apples{ }; // определяем apples в области видимости вложенного блока
// apples теперь относятся к apples вложенного блока
// переменная apples внешнего блока временно скрыта
apples = 10; // this assigns value 10 to nested block apples, not outer block apples
std::cout << apples << '\n'; // выводим значение apples вложенного блока
} // apples вложенного блока уничтожается
std::cout << apples << '\n'; // выводим значение apples внешнего блока
return 0;
} // apples внешнего блока уничтожается
Если вы запустите эту программу, она напечатает:
5
10
5
В приведенной выше программе мы сначала объявляем переменную с именем apples
во внешнем блоке. Эта переменная видна во внутреннем блоке, что мы можем увидеть, распечатав ее значение (5). Затем мы объявляем другую переменную (также с именем apples
) во вложенном блоке. С этого момента и до конца блока имя apples
относится к apples
вложенного блока, а не к apples
внешнего блока.
Таким образом, когда мы присваиваем apples
значение 10, мы присваиваем его переменной вложенного блока. После печати этого значения (10) переменная apples
вложенного блока уничтожается. Существование и значение переменной apples
внешнего блока не затрагивается, и мы доказываем это, печатая значение apples
внешнего блока (5).
Обратите внимание, что если бы переменная apples
вложенного блока не была определена, имя apples
во вложенном блоке относилось бы к apples
внешнего блока, и поэтому присвоение значения 10 применилось бы к переменной apples
внешнего блока:
#include <iostream>
int main()
{ // внешний блок
int apples{5}; // здесь apples внешнего блока
{ // вложенный блок
// apples здесь относится к apples внешнего блока
std::cout << apples << '\n'; // выводим значение apples внешнего блока
// в этом примере во внутреннем блоке apples не определена
apples = 10; // это относится к apples внешнего блока
std::cout << apples << '\n'; // выводим значение apples внешнего блока
} // apples внешнего блока сохраняет свое значение даже после того,
// как мы выходим из вложенного блока
std::cout << apples << '\n'; // выводим значение apples внешнего блока
return 0;
} // apples внешнего блока уничтожается
Приведенная выше программа напечатает:
5
10
10
Находясь внутри вложенного блока, нет возможности напрямую получить доступ к затененной переменной внешнего блока.
Затенение глобальных переменных
Подобно тому, как переменные во вложенном блоке могут затенять переменные внешнего блока, локальные переменные с тем же именем, что и глобальная переменная, будут затенять глобальную переменную везде, где локальная переменная находится в области видимости:
#include <iostream>
int value { 5 }; // глобальная переменная
void foo()
{
// value здесь не затенено, поэтому это относится к глобальной value
std::cout << "global variable value: " << value << '\n';
}
int main()
{
int value { 7 }; // скрывает глобальную переменную value до конца этого блока
++value; // инкрементирует локальную value, а не глобальную
std::cout << "local variable value: " << value << '\n';
foo();
return 0;
} // локальная переменная value уничтожается
Этот код напечатает:
local variable value: 8
global variable value: 5
Однако, поскольку глобальные переменные являются частью глобального пространства имен, мы можем использовать оператор области видимости (::
) без префикса, чтобы сообщить компилятору, что мы имеем в виду глобальную переменную, а не локальную.
#include <iostream>
int value { 5 }; // глобальная переменная
int main()
{
int value { 7 }; // скрывает глобальную переменную value
++value; // инкрементирует локальную value, а не глобальную
--(::value); // декрементирует глобальную value, а не локальную
// (скобки добавлены для читаемости)
std::cout << "local variable value: " << value << '\n';
std::cout << "global variable value: " << ::value << '\n';
return 0;
} // локальная переменная value уничтожается
Этот код напечатает:
local variable value: 8
global variable value: 4
Избегайте затенения переменных
Как правило, следует избегать затенения локальных переменных, поскольку это может привести к непреднамеренным ошибкам при использовании или изменении неправильной переменной. Некоторые компиляторы выдают предупреждение, когда переменная затеняется.
По той же причине, по которой мы рекомендуем избегать затенения локальных переменных, мы также рекомендуем избегать затенения глобальных переменных. Этого легко избежать, если все ваши глобальные имена используют префикс "g_".
Лучшая практика
Избегайте затенения переменных.