本章术语:
- 命名空间污染(namespace pollution):多个库将名字放置在全局命名空间中
- 命名空间(namespace):分割了全局命名空间,其中每个命名空间是一个作用域
- 全局命名空间(global namespace):在所有类、函数及命名空间之外定义的名字
- 内联命名空间(inline namespace):内联命名空间中的名字可以被外层命名空间直接使用
- 未命名的命名空间(unnamed namespace):关键字 namespace 后面紧跟花括号,而不指明命名空间的名字
- 命名空间的别名(namespace alias):使用 using 声明给命名空间起一个短名字
- using 声明(using declaration):起别名
- using 指示(using directive):不用添加任何前缀限定符
namespace <命名空间的名字>
{
// 类
// 变量(及其初始化)
// 函数(及其定义)
// 模板
// 其他命名空间
} // 无需分号
命名空间既可以定义在全局作用域内,也可以定义在其他命名空间中,但是不能定义在函数或类的内部。
每个命名空间都是一个作用域。 定义在某个命名空间中的名字可以被该命名空间内的其他成员直接访问,也可以被这些成员内嵌作用域中的任何单位访问。 位于该命名空间之外的代码必须明确指出所用的名字属于哪个命名空间。
命名空间可以是不连续的。
- 命名空间的一部分成员的作用是定义类,以及声明作为类接口的函数及对象,这些成员应该置于头文件中
- 命名空间成员的定义部分位于另外的原文件中
在一个命名空间中的非内联函数、静态数据成员、变量只能定义一次。
通常不把 #include
放在命名空间内部。
可以在命名空间内部定义成员,此时不需要指出成员所属命名空间;如果在命名空间外部定义成员,则需要指出该成员所在的命名空间。
只要在命名空间中声明了模板特例化,就能在命名空间外部定义它。
访问全局命名空间中的成员:::member_name
(全局命名空间没有名字)
嵌套的命名空间是定义在其他命名空间中的命名空间。 内层命名空间声明的名字将隐藏外层命名空间声明的同名成员; 外层命名空间中的代码想要访问内层命名空间中的成员必须在名字前添加作用域运算符。
在外层命名空间中,可以直接使用内联命名空间中的名字,而不使用作用域运算符。
未命名的命名空间在第一次使用前创建,直到程序结束才销毁。
一个未命名的命名空间可以在某个文件内不连续,但是不能跨越多个文件。
两个文件的未命名的命名空间互不相关。
定义在未命名的命名空间中的名字可以直接使用。
int i; // 全局命名空间中的名字
namespace {
int i; // 错误:全局命名空间和嵌套的未命名的命名空间中同名变量具有二义性
}
namespace local {
namespace {
int i; // 正确:可以使用 local::i 访问该成员,与全局命名空间中的 i 是不同的对象
}
}
在 C 语言中,通过声明全局 static 变量使名字仅在本文件中可见;在 C++ 中,应该使用未命名的命名空间来达到同样的效果。
命名空间别名:
using primer = cplusplus_primer; // 给 cplusplus_primer 取一个短别名
using Qlib = cplusplus_primer::QueryLib; // 嵌套的命名空间也可以取别名
一个命名空间可以有好几个同义词或别名,所有别名都与命名空间原来的名字等价。
using 声明引入的名字遵循与往常一样的作用域规则。
using 声明可以出现在全局作用域、局部作用域、命名空间作用域以及类作用域。 类作用域中的 using 声明语句只能指向基类成员。
使用 using 指示的名字在使用时无需添加任何前缀限定符:
using namespace <命名空间>; // 将命名空间中的全部成员注入当前作用域中
// 容易发生名字冲突,出现二义性
// 发生冲突时必须使用作用域运算符显式指明冲突名字的命名空间
头文件最多只能在它的函数或命名空间内使用 using 指示或 using 声明。
避免使用 using 指示,应该使用 using 声明来减少注入到命名空间中的名字数量。
namespace A
{
int i;
namespace B
{
int i; // 隐藏了 A::i
int j;
int f1()
{
int j; // 隐藏了 A::B::j
return i; // 返回 A::B::i
}
} // 命名空间结束,B 中的名字不再可见
int f2()
{
return j; // 错误:A 中没有定义 j
}
int j = i; // 用 A::i 初始化 A::j ,f2要想使用 j 就必须得在 f2 之前定义 j
}
namespace C
{
int i;
int k;
class D
{
private:
int i; // 隐藏了 C::i
int j;
public:
D() : i(0), j(0) { } // 初始化 D::i 和 D::j
int f1() { return k; } // 返回 C::k
int f2() { return h; } // 错误:h 在之前从未定义
int f3();
int h = i; // 用 C::i 初始化 C::h
}
}
int C::D::f3() { return h; } // h 在本行之前出现定义,返回 C::h
当我们给函数传递一个类类型的对象时,除了在常规的作用域查找以外,还会查找实参类所属的命名空间。这使得我们在调用运算符时不必指明命名空间。
为了避免冲突,应该使用 std::move
和 std::forward
函数。
在类中声明的友元类或友元函数会被隐式地声明为离它最近的外层命名空间的成员:
namespace A
{
class C
{
// 下面两个函数除了友元声明之外没有其他声明
// 那么这两个函数会被隐式地声明为命名空间 A 的成员
friend void f1(); // 没有类类型形参,除非另有声明,否则不会被找到
friend void f2(const C &); // 形参是类类型,可以根据实参类所属的命名空间找到该函数
};
}
int main()
{
A::C cobj;
f1(); // 错误:f1 没有声明
f2(cobj); // 正确:从 cobj 的类型 C 类所属的命名空间 A 找到了隐式声明的成员 f2
}
程序将在被调用函数的每个实参类以及实参类的基类所属的命名空间中搜寻候选函数。
在这些命名空间中,所有与被调用函数同名的函数都将被添加到候选集当中,即使其中某些函数在调用语句处不可见。
当我们为函数书写 using 声明时,该函数的所有版本都被引入到当前作用域中:
using NS::print; // NS 命名空间中的所有 print 的重载函数都被引入到当前作用域
using NS::print(int); // 错误:using 声明只能声明名字,不能添加形参列表
- 一个 using 声明引入的函数将重载该声明语句所属作用域中已有的其他重名函数
- 如果 using 声明出现在局部作用域中,则引入的名字将隐藏外层作用域的相关声明
- 如果 using 声明所在的作用域中已经有一个函数与新引入的函数同名且形参列表相同,则该 using 声明将引发错误
- using 声明将为引入的名字添加额外的重载实例,并最终扩充候选函数集的规模
如果 using 指示的命名空间的某个函数与当前命名空间所属作用域的函数同名,则 using 指示的命名空间的函数将被添加到重载集合中。
与 using 声明不同的是:使用 using 指示引入一个与已有同名函数形参列表完全相同的函数并不会产生错误,只要指明调用的是 using 指示的命名空间中的函数版本还是当前作用域的版本即可。
如果存在多个 using 指示,则来自每个命名空间的同名函数都会成为候选函数集的一部分。