声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。
摘要
内容主要包括:四种命名空间的定义、四种成员使用方式、三类名字的查找,以及命名空间对重载函数匹配的影响。
将类、函数和模板等名字直接放在全局命名空间中,容易导致命名空间污染(namespace pollution),从而引发名字冲突。
命名空间(namespace),对全局命名空间进行分割,将名字隔离在不同的作用域内,从而防止名字冲突。
1 命名空间定义
命名空间的定义:namespace
+ 命名空间的名字
+ { 一系列声明和定义 }
- 对于一个声明,只要可以出现在全局作用域中,就可以放在命名空间中;
- 包括类、变量(及其初始化操作)、函数(及其定义)、模板和其他命名空间等。
- 与其它名字一样,
命名空间的名字
也必须在定义它的作用域内保持唯一; - 命名空间既可以定义在全局作用域内,也可以定义在其他命名空间内,但是不能定义在函数或类的内部。
// 每个命名空间都是一个作用域
namespace cplusplus_primer {
class Sales_data { / *... * / };
// 定义在某个命名空间中的名字,如 Sales_data
// 可以被该命名空间内的其他成员直接访问,如 operator+
// 也可以被这些成员内嵌作用域中的成员直接访问
Sales_data operator+(const Sales_data &, const Sales_data &);
class Query { /* ... */ };
} // 命名空间结束后无须分号,这一点与块类似
// 位于命名空间之外的代码,必须明确指出所用的名字属于哪个命名空间(多种方式)
cplusplus_primer::Query q = cplusplus_primer::Query("hello");
1.1 命名空间可以不连续
与其他作用域不同,命名空间可以定义在几个不同的位置。
// 如果之前没有定义过名为 nsp 的命名空间,会创建一个新的命名空间
// 否则,打开已经存在的命名空间定义,并为其添加一些新成员的声明
namespace nsp {
// declarations
}
因为命名空间的定义可以不连续,所以,可以将几个独立的接口和实现文件组成一个命名空间。此时,命名空间的组织方式,类似于管理自定义类和函数的方式:
- 将用于“定义类、声明作为类接口的函数和对象”的命名空间成员,放在头文件中;
- 将命名空间成员的定义部分,放在单独的源文件中。
这种接口和实现分离的机制,可以确保所需的函数和静态变量等只定义一次,并在任何使用的地方都可以看到其声明。
例如,将 cplusplus_primer
库定义在几个不同的文件中:
- 将
Sales_data
类及其函数的声明放在Sales_data.h
中;将Query
类的声明放在Query.h
中;以此类推。 - 对应的实现分别放在
Sales_data.cc
和Query.cc
等文件中。
// ---- Sales_data.h---
// #includes 应该出现在打开命名空间的操作之前
// 否则,会把头文件中的所有名字定义成该命名空间的成员
#include <string>
namespace cplusplus_primer {
class Sales_data { /* ... */ };
Sales_data operator+(const Sales_data &, const Sales_data &);
// Sales_data 其他接口函数的声明
}
// ---- Sales_data.cc---
#include "Sales_data.h"
namespace cplusplus_primer {
// Sales_data 成员及重载运算符等接口函数的定义
}
// ---- user.cc---
// 程序在使用 cplusplus_primer 库时,需要包含必要的头文件
#include "Sales_data.h"
int main()
{
// Sales_data.h 头文件中的名字位于命名空间 cplusplus_primer 中
using cplusplus_primer::Sales_data;
Sales_data trans1, trans2;
// ...
}
1.2 定义命名空间成员
可以在命名空间定义的外部(外层命名空间),定义该命名空间的成员:
- 对于名字的声明,必须在命名空间作用域内;
- 对于名字的定义,需要明确指出其所属的命名空间。
// 命名空间之外定义的成员,必须使用含有命名空间前缀的名字
// 和定义在类外部的类成员函数一样,形参和函数体位于函数(如 operator+)
// 所在命名空间(如 cplusplus_primer)的作用域内,成员名字(如 Sales_data)无须前缀
cplusplus_primer::Sales_data
cplusplus_primer::operator+(const Sales_data& lhs, const Sales_data& rhs) {
Sales_data ret(lhs);
// ...
}
#include "Sales_data.h"
namespace cplusplus_primer { // 重新打开命名空间 cplusplus_primer
// 如果作用域中存在合适的声明语句,
// 那么,命名空间中的代码,可以直接使用同一命名空间定义的名字,无须前缀
istream& operator>>(istream& in, Sales_data& s) { /* ... */ }
}
1.3 模板特例化
模板特例化必须定义在原始模板所属的命名空间中。
和其他命名空间名字类似,只要在命名空间中声明了特例化,就可以在命名空间外部定义它。
// 必须将模板特例化声明成原始模板所属命名空间 std 的成员
namespace std
{
template <>
struct hash<Sales_data>;
}
// 在 std 中添加了模板特例化的声明后,就可以在命名空间 std 的外部定义它
template <>
struct std::hash<Sales_data>
{
size_t operator()(const Sales_data &s) const
{
return hash<string>()(s.bookNo) ^
hash<unsigned>()(s.units_sold) ^
hash<double>()(s.revenue);
}
// ...
};
1.4 全局命名空间
定义在全局作用域中的名字(即定义在所有类、函数及命名空间之外的名字),也被定义在全局命名空间(global namespace)中。
- 全局命名空间以隐式的方式声明,在所有程序中都存在;
- 全局作用域中定义的名字,被隐式地添加到全局命名空间中;
- 作用域运算符同样可以用于全局命名空间的成员,因为全局命名空间是隐式的,所以它并没有名字。
// 表示全局命名空间中的 member_name 成员
::member_name
1.5 嵌套的命名空间
嵌套的命名空间,是指定义在其他命名空间中的命名空间:
- 嵌套的命名空间同时是一个嵌套的作用域,它嵌套在外层命名空间的作用域中;
- 嵌套的命名空间中的名字遵循常规的规则:
- 会隐藏外层命名空间声明的同名成员;
- 只在内层命名空间中有效,外层命名空间中的代码需要在名字前添加命名空间前缀才能访问。
// 将命名空间 cplusplus_primer 分割为两个嵌套的命名空间
namespace cplusplus_primer
{
// 第一个嵌套的命名空间 QueryLib:定义了库的 Query 部分
namespace QueryLib {
class Query { /* ... */ };
Query operator&(const Query &, const Query &);
// ...
}
// 第二个嵌套的命名空间 Bookstore:定义了库的 Quote 部分
namespace Bookstore {
class Quote { /* ... */ };
class Disc_quote : public Quote { /* ... */ };
// ...
}
}
// 位于外层命名空间中的代码,必须明确指出所用的名字属于哪个命名空间
cplusplus_primer::QueryLib::Query q = cplusplus_primer::QueryLib::Query("hello");
1.6 内联命名空间(C++11)
内联命名空间(inline namespace),C++11 标准引入的一种新的嵌套命名空间,内联命名空间中的名字可以被外层命名空间直接使用,无须添加表示该命名空间的前缀。
内联命名空间的定义:inline
+ namespace
+ 命名空间的名字
+ { 一系列声明和定义 }
- 在关键字
namespace
前添加关键字inline
; - 关键字
inline
必须出现在命名空间第一次定义的地方,后续再打开命名空间时,可以写inline
,也可以不写。
当应用程序的代码在一次发布和另一次发布之间发生了改变时,常常会用到内联命名空间。例如,可以把《C++ Primer》当前版本(第 5 版)的所有代码都放在一个内联命名空间中,之前版本的代码都放在非内联命名空间中:
inline namespace FifthEd {
// 该命名空间表示《C++ Primer》第 5 版(当前版本)的代码
}
namespace FifthEd { // 隐式内联
class Query_base { /* ... */ };
// 其他与 Query 有关的声明
}
// 该命名空间表示《C++ Primer》第 4 版 的代码
namespace FourthEd {
class Item_base { /* ... */ };
class Query_base { /* ... */ };
// 第 4 版用到的其他代码
}
// 命名空间 cplusplus_primer 将同时使用这两个命名空间
// 假定每个命名空间都定义在同名的头文件中,那么可以把命名空间 cplusPlus_Primer 定义成如下形式
namespace cplusplus_primer {
#include "FifthEd.h"
#include "FourthEd.h"
}
// 因为 FifthEd 是内联的,所以形如 cplusplus_primer:: 的代码可以直接获得 FifthEd 的成员
cplusplus_primer::Query_base q;
// 在使用早期版本的代码时,必须像其他嵌套的命名空间一样,加上完整的外层命名空间名字
cplusplus_primer::FourthEd::Query_base q2;
1.7 未命名的命名空间
未命名的命名空间(unnamed namespace)的定义:namespace
+ { 一系列声明 }
对于未命名的命名空间:
- 在其中定义的变量拥有静态生命周期(第一次使用前创建,直到程序结束才销毁);
- 可以在某个给定的文件内不连续,但是不能跨越多个文件(不同于其他命名空间);
- 每个文件定义自己的未命名的命名空间,位于不同文件中的未命名的命名空间彼此无关;
- 如果定义在头文件中,那么该命名空间中定义的名字,将在每个包含了该头文件的文件中对应不同的实体。
- 可以直接使用其中定义的名字(因为命名空间没有名字);
- 不能对其中定义的名字使用作用域运算符:
- 名字的作用域与该命名空间所在的作用域相同。
- 如果定义在文件的最外层作用域中,那么其中的名字必须与全局作用域中的名字有所区别;
- 嵌套在其他命名空间中时,未命名的命名空间中的成员,可以通过外层命名空间的名字来访问。
int i; // i 的全局声明
namespace {
int i;
}
// 二义性:i 的定义既出现在全局作用域中,又出现在未嵌套的未命名的命名空间中
i = 10;
namespace local {
namespace {
int i;
}
}
// 正确:定义在嵌套的未命名的命名空间中的 i 与全局作用域中的 i 不同
local::i = 42;
未命名的命名空间取代文件中的静态声明
- 在 C 语言中,通过将名字声明为
static
,可以使其对整个文件有效,并且声明为static
的全局实体在其所在的文件外不可见。- 在 C++ 语言中,通过使用未命名的命名空间,取代上述在文件中进行静态声明的做法。
2 使用命名空间成员
使用命名空间中的成员,除了通过 namespace_name::member_name
方式之外,还可以通过一些更简便的方法,例如:命名空间别名、using
声明和 using
指示。
2.1 命名空间的别名
命名空间别名(namespace alias)的形式:namespace 别名 = namespace_name;
- 可以为命名空间的名字设定一个较短的同义词/别名;
- 一个命名空间可以有多个别名(与原来的名字等价);
- 不能在命名空间定义之前声明别名;
- 可以指向一个嵌套的命名空间。
// 较长的命名空间的名字
namespace cplusplus_primer { /* ... */ };
// 设定一个更短的别名
namespace primer = cplusplus_primer;
// 一个嵌套的命名空间的别名
namespace Qlib = cplusplus_primer::QueryLib;
Qlib::Query q;
2.2 命名空间的 using
声明
using
声明(using declaration)的形式:using namespace_name::member_name;
- 一旦声明了上述语句,就可以直接访问命名空间中的名字
member_name
; - 一条声明语句只引入一个成员,每个名字都需要独立的
using
声明。
// using 声明,当使用名字 cin 时,会从命名空间 std 中获取它
using std::cin;
int main() {
int i;
cin >> i; // 正确:cin 和 std::cin 含义相同
cout << i; // 错误:没有对应的 using 声明,必须使用完整的名字
std::cout << i; // 正确:显式地从 std 中获取 cout
}
using
声明引入的名字遵循常规的作用域规则:
- 名字的可用范围从
using
声明的地方开始,到using
声明所在作用域的结束为止; - 外层作用域的同名实体会被隐藏;
using
声明可以出现在全局作用域、局部作用域、命名空间作用域以及类作用域中。- 在类作用域中,
using
声明只能指向基类成员。
- 在类作用域中,
2.3 命名空间的 using
指示
using
指示(using directive)的形式:using namespace namespace_name;
- 命名空间的名字
namespace_name
需要已经被定义; - 可以使用命名空间中名字的简写形式(不加命名空间前缀);
- 命名空间中的所有名字都是可见的;
- 简写名字的可用范围从
using
指示的地方开始,到using
指示所在作用域的结束为止; - 可以出现在全局作用域、局部作用域和命名空间作用域中;
- 不能出现在类作用域中。
- 尽量避免使用
using
指示,可能重新引发名字冲突问题; - 可以在命名空间本身的实现文件中使用
using
指示。
using namespace std; // 可以使用 std 中所有名字的简写形式(无须前缀)
int main() {
int i;
cin >> i; // 正确:cin 和 std::cin 含义相同
cout << i; // 正确:cout 和 std::cout 含义相同
}
2.3.1 using
指示与作用域
using
指示引入的名字的作用域,远比 using
声明引入的字名的作用域复杂:
-
对于
using
声明:- 就像是,为命名空间成员声明一个局部的别名,名字的作用域,与
using
声明语句本身的作用域一致; - 简单地通过别名,使名字在局部作用域内可以直接访问。
- 就像是,为命名空间成员声明一个局部的别名,名字的作用域,与
-
对于
using
指示:- 就像是,将命名空间成员提升到“包含命名空间本身和
using
指示的”最近的外层作用域中;- 通常,命名空间中会包含一些不能出现在局部作用域中的定义;因此,
using
指示一般被处理为,像是出现在最近的外层作用域中。
- 通常,命名空间中会包含一些不能出现在局部作用域中的定义;因此,
- 通过作用域提升,使整个命名空间的所有名字都可以直接访问。
- 就像是,将命名空间成员提升到“包含命名空间本身和
// 在最简单的情况下,假设命名空间 A 和函数 f 都定义在全局作用域中
namespace A {
int i, j;
}
// 通过使用 using 指示,
// 对于 f 来说,A 中的名字就像是出现在这里(全局作用域中 f 定义之前的位置)
// int i, j;
void f() {
using namespace A; // 把 A 中的名字注入到全局作用域中
cout << i * j << endl; // 使用命名空间 A 中的 i 和 j
// ...
}
2.3.2 using
指示示例
// 假设命名空间 blip 和函数 manip 定义在全局作用域中
namespace blip {
int i = 16, j = 15, k = 23;
// ...
}
int j = 0; // 正确:blip 的 j 隐藏在命名空间中
// 通过使用 using 指示,
// 对于 manip 来说,blip 的成员就像是定义在这里(与 blip 和 manip 所在的作用域相同)
// int i = 16, j = 15, k = 23;
// 当命名空间中定义的名字被注入到外层作用域之后,很有可能会与外层作用域的成员冲突
void manip()
{
// using 指示,blip 中的名字被“添加”到全局作用域中,manip 可以直接访问 blip 的所有名字
using namespace blip; // 如果使用未加限定的 j,会在 ::j 和 blip::j 之间产生冲突
++i; // 将 blip::i 设定为 17
++j; // 二义性错误:是 ::j,还是 blip::j ?
// 如果没有使用 j,那么问题会潜藏,不能被检测到
++::j; // 正确:将全局 j 设定为 1
++blip::j; // 正确:将 blip::j 设定为 16
int k = 97; // 由于作用域不同,当前局部的 k 会隐藏 blip::k
++k; // 将当前局部的 k 设定为 98
}
2.3.3 避免 using
指示
using
指示一次性注入命名空间的所有名字,用法看似简单,实则充满风险:
- 如果应用程序使用了多个不同的库,这些库中的所有名字都通过
using
指示变得可见,会重新出现全局命名空间污染的问题; - 在库更新版本后,如果引入了一个与应用程序正在使用的名字冲突的名字,正在工作的程序就会编译失败;
- 由
using
指示引发的二义性错误,只有在使用了冲突名字的地方才能被发现,这种延后的检测意味着错误的潜藏。
相比于使用 using
指示,使用 using
声明效果更好:
- 逐个引入命名空间的成员,减少引入名字的数量,增加可控性;
using
声明引起的二义性问题,在声明处就能被发现,无须等到使用名字的地方,利于检测和修改错误。
2.4 头文件与 using
声明或指示
头文件中通常不应该包含 using
声明或指示:
- 如果
using
声明或指示位于头文件的顶层作用域中,会将名字注入到所有包含该头文件的文件中,可能产生名字冲突;- 通常,头文件应该只负责定义接口部分的名字,而不定义实现部分的名字。
- 最多,只能在头文件的函数或命名空间内使用
using
声明或指示。
3 命名空间、类与作用域
3.1 命名空间中名字的查找
命名空间内部名字的查找遵循常规的查找规则:
- 由内向外依次查找每个外层作用域,直到最外层的全局命名空间;
- 外层作用域也可能是一个或多个嵌套的命名空间。
- 只有位于开放的块中,并且在使用点之前声明的名字才被考虑。
namespace A {
int i;
namespace B {
int i; // 在 B 中隐藏了 A::i
int j;
int f1() {
int j; // j 是 f1 的局部变量,隐藏了 A::B::j
return i; // 返回 B::i
}
} // 命名空间 B 结束,此后 B 中定义的名字不再可见
int f2() {
return j; // 错误:B::j 不可见,A::j 尚未被定义
}
int j = i; // 用 A::i 进行初始化
}
3.2 命名空间中类的名字查找
对于位于命名空间中的类来说,当成员函数使用某个名字时,常规的查找规则仍然适用:
- 首先在该成员函数中进行查找;
- 然后在类中(包括基类中)进行查找;
- 接着在外层作用域中进行查找。
- 外层作用域可能是一个或多个嵌套的命名空间。
除了类内部出现的成员函数定义之外,总是向上查找作用域。复习回顾:C++(一.03)类 => 6、名字查找与类的作用域
namespace A
{
int i;
int k;
class C1
{
public:
C1() : i(0), j(0) {} // 正确:初始化 C1::i 和 C1::j
int f1() { return k; } // 返回 A::k
int f2() { return h; } // 错误:h 未定义
int f3();
private:
int i; // 在 C1 中隐藏了 A::i
int j;
};
int h = i; // 用 A::i 进行初始化
}
// 对于限定符 A::C1::f3,
// 可以按照函数限定名相反的顺序,检查作用域:
// 首先,查找函数 f3 的作用域;
// 接着,查找外层类 C1 的作用域;
// 然后,查找命名空间 A 的作用域;
// 最后,查找包含 f3 定义的作用域。
int h = 999;
// 成员 f3 定义在 C1 和命名空间 A 的外部,并位于 A::h 之后
int A::C1::f3() { return h; } // 正确:返回 A::h
3.3 实参为类类型的名字查找
在给函数传递一个类类型的对象/引用/指针时,编译器除了在常规的作用域查找外,还会查找实参类(及其基类)所属的命名空间。
查找规则的这个例外,允许“从概念上讲,作为类接口一部分的”非成员函数,无须单独的 using
声明,就能被程序使用。
std::string s;
// 对于
std::cin >> s;
// 等价于
operator>>(std::cin, s);
// operator>> 函数定义在标准库 string 中,string 库又定义在命名空间 std 中
// 但是,operator>> 函数调用,并没有使用 std:: 限定符和 using 声明
// std::operator>>(std::cin, s);
// using std::operator>>;
// 当编译器看到对 operator>> 的调用时,
// 首先,在当前作用域中寻找合适的函数;
// 接着,查找输入语句的外层作用域;
// 随后,因为 >> 表达式的实参是类类型,所以编译器还会查找
// cin 和 s 的类(istream 和 string)所属的命名空间 std;
// 在查找 std 时,会找到 string 输入运算符函数。
3.4 查找与 std::move
和 std::forward
通常,如果在应用程序中定义了一个标准库中已有的名字,会是下面的某一种情况:
- 根据重载规则,确定某次调用应该执行函数的哪个版本;
- 应用程序根本就不打算执行函数的标准库版本。
然而,对于标准库的 move
和 forward
函数,则可能并非上述情况。
- 这两个函数都是模板函数,都接受单一的右值引用形参;
- 在函数模板中,右值引用形参可以匹配任何类型(下一章“C++(二.04)模板与泛型编程”会展开说明)
- 如果应用程序也定义了一个接受单一形参的
move
(或forward
)函数,那么无论该形参是什么类型,都会与标准库的版本冲突; - 另外,由于这两个函数执行的是非常特殊的类型操作,所以应用程序特意重写函数原有行为的可能性非常小;
- 因为冲突更可能发生,并且大多不是有意的,所以,在调用标准库的
move
和forward
函数时,最好使用带限定的完整版本,通过书写std::move
而非move
,明确要使用的是标准库版本。
3.5 友元声明与依赖于实参的查找
当类声明一个友元时,该友元声明并没有使友元本身可见。
然而,对于一个未声明的类或函数,如果第一次出现在友元声明中,那么它会被认为是最近的外层命名空间的成员。这条规则与依赖于实参的名字查找规则结合在一起,可能会产生意想不到的效果:
namespace A {
class C {
// 两个友元,在友元声明之外,没有其他的声明
// 这些函数隐式地成为命名空间 A 的成员
friend void f2(); // 隐式声明为 A::f2;除非另有声明,否则不会被找到
friend void f(const C &); // 隐式声明为 A::f;根据“依赖于实参的名字查找”规则,可以被找到
};
}
int main() {
A::C cobj;
// 因为 f 接受一个类类型的实参,
// 而且 f 在 C 所属的命名空间 A 中进行了隐式的声明,所以 f 能够被找到。
f(cobj); // 正确:通过在“A::C 中的友元声明”找到 A::f
// 因为 f2 没有形参,所以它无法被找到
f2(); // 错误:找不到 f2 的声明;可以直接调用 A::f2
}
4 重载与命名空间
对于重载函数的匹配过程,命名空间有两方面的影响:
- 其中一个影响非常明显:通过
using
声明或using
指示,可以向候选集中添加函数; - 另外一个影响则不太容易察觉:通过实参类所属的命名空间,也可以向候选集中添加函数。
4.1 重载与依赖于实参的查找
对于接受类类型实参的函数,在确定候选函数集时,
- 会在每个实参类(以及实参类的基类)所属的命名空间中,搜寻候选函数;
- 在这些命名空间中,所有与被调用函数同名的函数,都会被添加到候选集中。
- 即使其中某些函数在调用语句处不可见,也会被添加。
namespace NS {
class Quote { /* ... */ };
void display(const Quote &) { /* ... */ }
}
// Bulk_item 的基类声明在命名空间 NS 中
class Bulk_item : public NS::Quote { /* ... */ };
int main() {
Bulk_item book1;
// 传递给 display 的实参为类类型 Bulk_item,
// 该调用语句的候选函数,不仅需要在调用语句所在的作用域中查找,
// 还需要在 Bulk_item 及其基类 Quote 所属的命名空间中的查找,
// 命名空间 NS 中声明的函数 display(const Quote&) 会被添加到候选函数集中
display(book1);
}
4.2 重载与 using
声明
using
声明语句声明的是一个名字,而不是一个特定的函数。
- 在为一个函数书写
using
声明时,该函数的所有重载版本都会被引入到当前作用域中;- 这样可以与命名空间的接口保持一致;否则,允许引入部分版本,可能会导致预期之外的程序行为。
- 一条
using
声明语句引入的函数,会重载该声明语句所属作用域中已有的同名函数。- 如果
using
声明出现在局部作用域中,引入的名字会隐藏外层作用域的同名声明; - 如果
using
声明所在的作用域中,已经存在“与引入函数同名且同形参列表的”函数,则会引发错误; - 除上述情况外,
using
声明会为引入的名字添加额外的重载实例,并在函数匹配时加入到候选函数集。
- 如果
namespace NS {
extern void print(int);
extern void print(double);
}
// 普通的声明
void print(const std::string &);
// 这个 using 声明把 NS 中 print 函数的所有版本都引入到当前作用域中
using NS::print; // 正确:using 声明只声明一个名字
using NS::print(int); // 错误:不能指定形参列表
// 此处 print 的重载函数集合包括:
// 来自 NS 的 print(int)
// 来自 NS 的 print(double)
// 显式声明的 print(const std::string &)
void fooBar(int ival) {
print("Value: "); // 调用全局函数 print(const string &)
print(ival); // 调用 NS::print(int)
}
4.3 重载与 using
指示
using
指示将命名空间的成员提升到外层作用域中。
如果命名空间中的某个函数,与该命名空间所属作用域中的函数同名,那么命名空间中的函数会被添加到重载集合中。
与 using
声明不同的是:
- 引入一个“与已有函数同名且同形参列表的”函数,并不会产生错误;
- 对于上述情况,需要在调用函数时,指明是命名空间中的版本,还是当前作用域中的版本。
namespace NS {
extern void print(int);
extern void print(double);
extern void print(const std::string &);
}
void print(const std::string &);
// 这个 using 指示把 NS::print 添加到 print 调用的候选函数集
using namespace NS;
void fooBar(int ival) {
::print("Value: "); // 明确指出调用全局函数 print(const string &)
print(ival); // 调用 NS::print(int)
}
4.4 跨越多个 using
指示的重载
如果存在多个 using
指示,那么来自每个命名空间中的名字,都会成为候选函数集的一部分:
namespace AW {
void print(int);
}
namespace Primer {
void print(double);
}
// 通过 using 指示,使用多个命名空间中的函数创建重载函数集合
using namespace AW;
using namespace Primer;
void print(const std::string &);
// 此处 print 的重载集合如下,它们都属于 fooBar 函数中 print 调用的候选函数集
// 来自 AW 的 print(int)
// 来自 Primer 的 print(double)
// 显式声明的 print(const std::string &)
void fooBar() {
print(1); // 调用 AW::print(int)
print(3.1); // 调用 Primer::print(double)
}
参考
- [美] Stanley B.Lippman著.C++ Primer 中文版(第5版).电子工业出版社.2013.
- [美] Stephen Prata著.C++ Primer Plus(第6版)中文版.人民邮电出版社.2012.
宁静以致远,感谢 Vico 老师。