7-5 变量遮蔽(名称隐藏)
每个代码块都定义自己的作用域区域。那么当嵌套代码块中的变量与外部代码块中的变量同名时会发生什么?此时,嵌套变量会在两者同时处于作用域内的区域中“隐藏”外部变量。这种现象称为名称隐藏name hiding或遮蔽shadowing。
局部变量的遮蔽
#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
运行此程序时,它会输出:

在上面的程序中,我们首先在外层代码块中声明了一个名为apples的变量。该变量在内层代码块中可见,通过打印其值(5)可以验证这一点。随后我们在嵌套代码块中声明了另一个同名变量(同样命名为apples)。从此时起直至代码块结束,名称apples都指向嵌套代码块中的apples,而非外层代码块中的apples。
因此,当我们将值10赋给apples时,实际赋值对象是嵌套块中的apples。打印该值(10)后,嵌套块结束且嵌套块apples被销毁。外层块apples的存在与值不受影响,通过打印外层块apples的值(5)可验证此结论。
需注意:若未定义嵌套块 apples,嵌套块中的 apples 仍将指向外层块 apples,此时将 10 赋值给 apples 操作将作用于外层块 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
上述程序输出:

在嵌套代码块内部时,无法直接访问外部代码块中被遮蔽的变量。
全局变量的遮蔽
类似于嵌套代码块中的变量可以遮蔽外部代码块中的变量,与全局变量同名的局部变量将在其作用域内遮蔽全局变量:
#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
此代码输出:

然而,由于全局变量属于全局命名空间的一部分,我们可以使用范围运算符(::)而不加前缀,以此告知编译器我们指的是全局变量而非局部变量。
#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
此代码输出:

避免变量遮蔽
通常应避免局部变量遮蔽,否则可能导致意外错误——即使用或修改了错误的变量。部分编译器在变量被遮蔽时会发出警告。
基于避免局部变量遮蔽的相同原因,我们同样建议避免全局变量遮蔽。若所有全局名称均使用“g_”前缀,则可轻松规避此问题。
最佳实践
避免变量遮蔽。
针对 gcc 用户
GCC 和 Clang 支持 -Wshadow 选项,当变量被遮蔽时会触发警告。该选项包含多个子变体(-Wshadow=global、-Wshadow=local 和 -Wshadow=compatible-local)。具体差异请参阅 GCC 文档说明。
Visual Studio 默认启用此类警告。

浙公网安备 33010602011771号