2.7 命名空间
在 C++ 中,命名空间是一种用来方便管理名字,避免名字冲突的机制。
全局命名空间
Section titled “全局命名空间”对于每个源文件,在所有的作用域之外,有一个全局的作用域,这个全局的作用域称为全局命名空间。源文件中其他的作用域,例如函数、复合语句、for 语句、if 语句、switch 语句等,都是在全局命名空间之内,可以访问全局命名空间中的名字。
在全局命名空间中,不能出现多数的语句,但是可以出现声明语句、函数定义、函数前向声明等。例如下面是 main.cpp 文件的一个例子:
// 文件 main.cpp 开始
int a = 1; // 可以在全局命名空间中声明一个整数类型的对象 a
int add_a(int x); // 可以前向声明 add_a 函数
int main() { // 可以定义 main 函数 a = add_a(2); return 0;}
int add_a(int x) { // 可以定义 add_a 函数 return a + x;}
a = 2; // 错误:在全局命名空间中不能有这样的语句1 + a; // 错误:在全局命名空间中不能有这样的语句
// 文件 main.cpp 结束命名空间与有限定名字
Section titled “命名空间与有限定名字”在 C++ 中提供了命名空间的机制,可以用来避免名字冲突。命名空间的声明形式是:
namespace name { // 在这里进行声明和定义}这里,namespace 是一个关键字,指示这是一个命名空间的声明。name 是命名空间的名字,可以是任意符合语法的标识符。
命名空间的声明必须在命名空间中,而不能在语句带来的的作用域内。
类似于全局命名空间,声明的命名空间仍然不能出现多数的语句,但是可以出现声明语句、函数定义、函数前向声明等。
命名空间不是对象,也没有值,它只用于组织程序中的名字。
在命名空间中声明的名字,只在命名空间内部有效。例如:
namespace A { int a = 1;}
int b = a; // 错误:a 在这里是不可见的但区别于函数、复合语句产生的作用域,命名空间内的名字可以通过有限定名字的方式访问。
有限定名字的形式是
namespace_name :: name中间的 :: 是作用域解析运算符,用来指示名字 name 是在 namespace_name 命名空间中的。例如:
namespace A { int a = 1;}
int b = A::a; // 通过有限定名字访问 A 命名空间中的 a有限定名字作为表达式时,是一个基本表达式。
命名空间的嵌套
Section titled “命名空间的嵌套”除了在全局命名空间中声明命名空间,也可以在命名空间中声明命名空间,这样就形成了命名空间的嵌套。例如:
namespace A { int a = 1; namespace B { int b = 2; }}对这样嵌套的命名空间可以这样访问:
int c = A::B::b; // 通过有限定名字访问 A 命名空间中的 B 命名空间中的 b内联命名空间
Section titled “内联命名空间”在声明命名空间时,可以使用 inline 关键字,将命名空间声明为内联命名空间。
namespace A { inline namespace B { int b = 2; }}内联命名空间内的名字可以直接通过其外部的命名空间访问,不需要有限定名字。例如:
int c = A::b; // 通过有限定名字访问 A 命名空间中的 B 命名空间中的 b这个直接访问的机制是传递的,例如:
namespace A { inline namespace B { inline namespace C { int c = 3; } }}
int d = A::c; // 通过有限定名字访问 A 命名空间中的 C 命名空间中的 c相同名字的命名空间
Section titled “相同名字的命名空间”命名空间本身可以具有相同的名字,不会产生冲突。例如:
namespace A { int a = 1;}
namespace A { int b = 2;}这样的两个命名空间 A 可以认为是同一个命名空间。这样的机制可以用来分割命名空间的声明。
此外,如果要声明一个已经声明过的内层命名空间,可以通过 :: 限定来声明。例如:
namespace A { namespace B { int b = 2; }}
namespace A::B { int c = 3;}上面 c 的声明等价于
namespace A { namespace B { int c = 3; }}using 声明
Section titled “using 声明”有时候,我们并不想每次都通过有限定名字的方式访问命名空间中的名字,尤其是名字太长,也没啥冲突的时候。
在 C++ 中,可以使用 using 声明来引入命名空间中的名字。using 声明的形式是:
using namespace_name :: name;这里,using 是一个关键字,指示这是一个 using 声明。namespace_name 是命名空间的名字,name 是命名空间中的名字。
整个 using 声明组成了一个声明语句。using 声明可以在任何作用域内使用。
以 using 声明引入名字后,在这个作用域剩下的范围内,使用 name 就如同使用 namespace_name :: name 一样。例如:
namespace A { int a = 1;}
void inc_a() { using A::a; // 引入 A 命名空间中的 a a += 1; // 直接使用 a,不需要 A::a}注意,using 声明引入的名字,必须要在引入之后,且只在引入的作用域内有效。例如:
namespace A { int a = 1;}
void inc_a() { a += 1; // 错误:a 在这里是不可见的 using A::a; // 引入 A 命名空间中的 a}
a += 1; // 错误:a 在这里是不可见的有限定访问全局命名空间
Section titled “有限定访问全局命名空间”有的时候,我们会想要引入名字后,访问一个全局命名空间中的名字。这时候,可以使用前面没有命名空间名字的 :: 作用域解析运算符。例如:
int a = 1;namespace A { int a = 2;}
void inc_a() { using A::a; // 引入 A 命名空间中的 a ::a += 1; // ::a 是全局命名空间中的 a,而非 A::a}引入多个名字
Section titled “引入多个名字”using 声明可以一次引入多个名字。例如:
namespace A { int a = 1; int b = 2;}
void inc_a() { using A::a, A::b; // 引入 A 命名空间中的 a 和 b a += 1; b += 1;}这等价于使用了两个 using 声明,分别引入 A::a 和 A::b。
在命名空间中使用 using 声明
Section titled “在命名空间中使用 using 声明”在命名空间中,可以使用 using 声明来引入其他命名空间中的名字。例如:
namespace A { int a = 1;}
namespace B { using A::a; // 在 B 命名空间中引入 A 命名空间中的 a int b = a;}
int c = B::b; // 可以访问 B 命名空间中的 bint d = B::a; // 可以访问 B 命名空间中引入的 A 命名空间中的 ausing namespace 指令
Section titled “using namespace 指令”有时候,我们会想要引入一个命名空间中的所有名字。这时候,可以使用 using namespace 指令。using namespace 指令的形式是:
using namespace namespace_name;这里,using 是一个关键字,指示这是一个 using 指令。namespace_name 是命名空间的名字。
完整的 using namespace 指令组成了一个声明语句。using namespace 指令可以在任何作用域内使用。
using namespace 指令引入的名字,可以在引入的作用域内直接使用,不需要有限定名字。例如:
namespace A { int a = 1; int b = 2;
void inc_a() { a += 1; b += 1; }}
int inc_a_then_sum() { using namespace A; // 引入 A 命名空间中的所有名字
inc_a(); // 可以直接调用 inc_a return a + b; // 可以直接使用 a 和 b}在命名空间中使用 using namespace 指令
Section titled “在命名空间中使用 using namespace 指令”在命名空间中,可以使用 using namespace 指令来引入其他命名空间中的所有名字。例如:
namespace A { int a = 1; int b = 2;}
namespace B { using namespace A; // 在 B 命名空间中引入 A 命名空间中的所有名字}
int c = B::a; // 可以访问 B 命名空间中引入的 A 命名空间中的 aint d = B::b; // 可以访问 B 命名空间中引入的 A 命名空间中的 b命名空间别名
Section titled “命名空间别名”命名空间别名的形式是:
namespace name = namespace_name;其中,name 是一个标识符,表示新引入的别名。namespace_name 是需要起别名的命名空间名字,可以是有限定的名字。
这可以用于简化过长的名字,或者嵌套过深的名字。例如:
namespace A { namespace B { namespace C { int c = 3; } }}
namespace ABC = A::B::C;
int d = ABC::c; // 可以访问 A::B::C 命名空间中的 c此外,命名空间别名可以产生类似引入其他命名空间的效果。例如:
namespace A { int a = 1;}
namespace B { namespace C = A; // 在 B 命名空间中引入 A 命名空间}
int b = B::C::a; // 可以访问 A 命名空间中的 a标准库命名空间
Section titled “标准库命名空间”在 C++ 中,规定了一个特殊的命名空间 std,用来存放标准库中的名字。在 C++ 标准库中的名字,都在 std 命名空间中。例如:
import std;
int main() { std::println("Hello, World!"); // 使用 std 命名空间中的 println 函数}标准库命名空间在源文件中默认已经声明,所以可以直接使用。但根据预处理指令的影响,std 命名空间中具有的名字会有所不同。
这里,std::println 中的 std 是标准库命名空间,println 是 std 命名空间中的一个函数,它的作用是根据参数,输出一行文本。
虽然,已经声明的命名空间可以再次声明,不会产生冲突。即,程序员可以写 namespace std {},不会产生问题。
但是,C++ 规定,程序员不应当在 std 命名空间中添加声明。类似于保留的标识符,std可以理解为一个保留为标准库使用的命名空间。
这样的规定是为了避免程序员干扰标准库的功能,产生不可预测的结果。