C++官方参考链接:Name visibility - C++ Tutorials (cplusplus.com)
命名的可见性
作用域
命名实体,如变量、函数和复合类型,在C++中使用之前需要声明。该声明在程序中的位置会影响它的可见性:
在任何块之外声明的实体具有全局作用域,这意味着它的名称在代码的任何地方都有效。而在块内声明的实体,如函数或选择性语句,具有块作用域,并且只在声明它的特定块内可见,而在块外则不可见。
具有块作用域的变量称为局部变量。
例如,在函数体中声明的变量是一个局部变量,它扩展到函数的结束处(即,直到关闭函数定义的大括号}),但不扩展到函数的外部:
int foo; // global variable
int some_function ()
{
int bar; // local variable
bar = 0;
}
int other_function ()
{
foo = 1; // ok: foo is a global variable
bar = 2; // wrong: bar is not visible from this function
}
在每个作用域中,名称只能表示一个实体。例如,同一个作用域内不能有两个名称相同的变量:
int some_function ()
{
int x;
x = 0;
double x; // wrong: name already used in this scope
x = 0.0;
}
具有块作用域的实体的可见性一直延伸到块的结束,包括内部块。然而,内部块,因为它是一个不同的块,可以重新利用存在于外部作用域中的命名来引用不同的实体;在这种情况下,命名将只在内部块中引用不同的实体,而在外部隐藏它所命名的实体。在它外部时,它仍然指向原来的实体。例如:
// inner block scopes
#include
using namespace std;
int main () {
int x = 10;
int y = 20;
{
int x; // ok, inner scope.
x = 50; // sets value to inner x
y = 50; // sets value to (outer) y
cout << "inner block:\n";
cout << "x: " << x << '\n';
cout << "y: " << y << '\n';
}
cout << "outer block:\n";
cout << "x: " << x << '\n';
cout << "y: " << y << '\n';
return 0;
}
注意,y没有隐藏在内部块中,因此访问y仍然访问外部变量。
在引入块的声明中声明的变量,例如在循环和条件中声明的函数形参和变量(例如在for或if上声明的变量)是它们所引入块的局部变量。
命名空间
在特定作用域中,只有一个实体可以以特定名称存在。对于局部名称来说,这很少是一个问题,因为块往往相对较短,名称有特定的用途,如命名计数器变量,一个实参等。
但是,非局部名称带来了更多名称冲突的可能性,特别是考虑到库可能声明许多函数、类型和变量,这些函数、类型和变量在本质上都不是局部的,其中一些是泛型的。
命名空间允许我们将具有全局作用域的命名实体分组到更窄的作用域中,为它们提供命名空间作用域。这允许将程序的元素组织到通过命名引用的不同逻辑范围中。
声明命名空间的语法是:
namespace identifier
{
named_entities
}
其中identifier是任何有效的标识符,named_entities是包含在命名空间中的变量、类型和函数集。例如:
namespace myNamespace
{
int a, b;
}
在本例中,变量a和b是在名为myNamespace的命名空间中声明的普通变量。
这些变量可以通过它们的标识符(a或b)从它们的命名空间内正常访问,但如果从myNamespace命名空间外部访问,则必须使用作用域操作符::对它们进行适当的限定。例如,要从myNamespace外部访问前面的变量,它们应该像这样限定:
myNamespace::a
myNamespace::b
命名空间对于避免名称冲突特别有用。例如:
// namespaces
#include
using namespace std;
namespace foo
{
int value() { return 5; }
}
namespace bar
{
const double pi = 3.1416;
double value() { return 2*pi; }
}
int main () {
cout << foo::value() << '\n';
cout << bar::value() << '\n';
cout << bar::pi << '\n';
return 0;
}
在本例中,有两个具有相同名称的函数:value。一个在命名空间foo中定义,另一个在bar中定义。由于命名空间,不会发生重定义错误。
还要注意如何从命名空间bar中以不限定的方式访问pi(就像pi),而在main中再次访问它,但这里它需要限定为bar::pi。
命名空间可以被拆分:代码的两段可以在同一个命名空间中声明:
namespace foo { int a; }
namespace bar { int b; }
namespace foo { int c; }
这声明了三个变量:a和c在命名空间foo中,而b在命名空间bar中。命名空间甚至可以跨不同的翻译单元扩展(例如,跨不同的源代码文件)。
using
关键字using将名称引入当前声明性区域(如块),从而避免需要限定命名。例如:
// using
#include
using namespace std;
namespace first
{
int x = 5;
int y = 10;
}
namespace second
{
double x = 3.1416;
double y = 2.7183;
}
int main () {
using first::x;
using second::y;
cout << x << '\n';
cout << y << '\n';
cout << first::y << '\n';
cout << second::x << '\n';
return 0;
}
注意在main中,变量x(没有任何命名限定符)是如何指向first::x的,而y是指向second::y的,正如using声明所指定的那样。变量first::y和second::x仍然可以被访问,但需要完全限定名。
关键字using也可以用作引入整个命名空间的指令:
// using
#include
using namespace std;
namespace first
{
int x = 5;
int y = 10;
}
namespace second
{
double x = 3.1416;
double y = 2.7183;
}
int main () {
using namespace first;
cout << x << '\n';
cout << y << '\n';
cout << second::x << '\n';
cout << second::y << '\n';
return 0;
}
在本例中,通过声明我们使用命名空间first,所有不带命名限定符的x和y的直接使用也将在命名空间first中查找。
using和using namespace只在声明它们的同一块中有效,如果直接在全局作用域中使用,则只在整个源代码文件中有效。例如,可以先使用一个命名空间的对象,然后再使用另一个命名空间的对象,方法是将代码分成不同的块:
// using namespace example
#include
using namespace std;
namespace first
{
int x = 5;
}
namespace second
{
double x = 3.1416;
}
int main () {
{
using namespace first;
cout << x << '\n';
}
{
using namespace second;
cout << x << '\n';
}
return 0;
}
命名空间别名
现有命名空间可以用新名称别名,语法如下:
namespace new_name = current_name;
std命名空间
标准C++库的所有实体(变量、类型、常量和函数)都在std命名空间中声明。事实上,这些教程中的大多数例子都包含以下一行:
using namespace std;
这在代码中引入了std命名空间的所有命名的直接可见性。在这些教程中这样做是为了方便理解和缩短示例的长度,但是许多程序员更喜欢程序中使用限定的标准库的每个元素。例如,代替:
cout << "Hello world!";
相反,常见的是:
std::cout << "Hello world!";
无论std命名空间中的元素是通过using声明引入的,还是在每次使用时都是完全限定的,都不会以任何方式改变结果程序的行为或效率。这主要是风格偏好的问题,尽管对于混合库的项目,显式限定往往是首选。
存储类
具有全局作用域或命名空间作用域的变量的存储为程序的整个存储期分配。这被称为静态存储,它与局部变量(在块中声明的变量)的存储形成对比。它们使用的是所谓的自动存储。局部变量的存储仅在声明它们的块期间可用;在此之后,相同的存储可以用于其他函数的局部变量,或用于其他用途。
但是静态存储变量和自动存储变量之间还有另一个实质性的区别:
-带有静态存储的变量(如全局变量)如果没有显式初始化,则会自动初始化为0。
-具有自动存储功能的变量(如局部变量)如果没有显式初始化,则不进行初始化,因此具有未确定的值。
例如:
// static vs automatic storage
#include
using namespace std;
int x;
int main ()
{
int y;
cout << x << '\n';
cout << y << '\n';
return 0;
}
实际的输出可能不同,但只有x的值保证为0。y实际上可以包含几乎任何值(包括0)。