第05章 C++语言专题(二.03)命名空间

声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。

摘要

内容主要包括:四种命名空间的定义、四种成员使用方式、三类名字的查找,以及命名空间对重载函数匹配的影响。


将类、函数和模板等名字直接放在全局命名空间中,容易导致命名空间污染(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.ccQuery.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 声明可以出现在全局作用域、局部作用域、命名空间作用域以及类作用域中。

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::movestd::forward

通常,如果在应用程序中定义了一个标准库中已有的名字,会是下面的某一种情况:

  • 根据重载规则,确定某次调用应该执行函数的哪个版本;
  • 应用程序根本就不打算执行函数的标准库版本。

然而,对于标准库的 moveforward 函数,则可能并非上述情况。

  • 这两个函数都是模板函数,都接受单一的右值引用形参;
  • 如果应用程序也定义了一个接受单一形参的 move(或 forward)函数,那么无论该形参是什么类型,都会与标准库的版本冲突;
  • 另外,由于这两个函数执行的是非常特殊的类型操作,所以应用程序特意重写函数原有行为的可能性非常小;
  • 因为冲突更可能发生,并且大多不是有意的,所以,在调用标准库的 moveforward 函数时,最好使用带限定的完整版本,通过书写 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)
}

参考

  1. [美] Stanley B.Lippman著.C++ Primer 中文版(第5版).电子工业出版社.2013.
  2. [美] Stephen Prata著.C++ Primer Plus(第6版)中文版.人民邮电出版社.2012.

宁静以致远,感谢 Vico 老师。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值