C++(19):命名空间

多个库将名字放置在全局命名空间中将引发命名空间污染

命名空间可以用来防止名字冲突,它分割了全局命名空间,其中每个命名空间是一个作用域。通过在某个命名空间中定义库的名字,库的作者(以及用户)可以避免全局名字固有的限制。

命名空间定义

一个命名空间的定义包含两部分:关键字namespace + 命名空间名字。

在命名空间名字后面是一系列由花括号括起来的声明和定义。只要能出现在全局作用域中的声明就能置于命名空间内,主要包括:类、变量(及其初始化操作)、函数(及其定义)、模板和其他命名空间。

cpp 复制代码
namespace cplusplus_primer{
    class Sales_data{/*...*/};
}  //无须分号

命名空间的名字也必须在定义它的作用域内保持唯一。

命名空间既可以定义在全局作用域内,也可以定义在其他命名空间中,但是不能定义在函数或类的内部。

命名空间作用域后面无须分号。

每个命名空间都是一个作用域

命名空间中的每个名字都必须表示该空间内的唯一实体:不同命名空间的作用域不同,所以在不同命名空间内可以有相同名字的成员。

定义在某个命名空间中的名字可以被该命名空间内的其他成员直接访问,也可以被这些成员内嵌作用域中的任何单位访问。位于该命名空间之外的代码则必须明确指出所用的名字属于哪个命名空间。

cpp 复制代码
cplusplus_primer::Query q = cplusplus_primer::Query("hello")

命名空间可以不是连续的

cpp 复制代码
//命名空间定义
namespace nsp{
	//相关声明
}

命名空间可以定义在几个不同的部分。

命名空间的一部分成员的作用是定义类,以及声明作为类接口的函数及对象,则这些成员应该置于头文件中,这些头文件将被包含在使用了这些成员的文件中。

命名空间成员的定义部分则置于另外的源文件中。

定义多个类型不相关的命名空间应该使用单独的文件分别表示每个类型(或关联类型构成的集合)。

定义命名空间成员

假定作用域中存在合适的声明语句,则命名空间中的代码可以使用同一命名空间定义的名字的简写形式。

cpp 复制代码
#include "Sales_data.h"
namespace cplusplus primer{	//重新打开命名空间cplusplus_primer
	//命名空间中定义的成员可以直接使用名字,此时无须前缀
	std::istream&
	operator>>(std::istream& in,Sales_data& s){/* ...* /)}
}

也可以在命名空间定义的外部定义该命名空间的成员。命名空间对于名字的声明必须在作用域内,同时该名字的定义需要明确指出其所属的命名空间:

cpp 复制代码
//命名空间之外定义的成员必须使用含有前缀的名字
cplusplus primer:: Sales_data
cplusplus_primer::operator+(const Sales_data& lhs, const Sales data& rhs){
	sales_data ret (lhs);
	// ...
}

模板特例化

模板特例化必须定义在原始模板所属的命名空间中。只要在命名空间中声明了特例化,就可以在命名空间外部定义它了。

全局命名空间

全局作用域中定义的名字(即在所有类、函数及命名空间之外定义的名字)也就是定义在全局命名空间中。

全局命名空间以隐式的方式声明,并且在所有程序中都存在。

全局作用域中定义的名字被隐式地添加到全局命名空间中。

作用域运算符同样可以用于全局作用域成员:

cpp 复制代码
::member_name // 表示全局命名空间中地一个成员

嵌套的命名空间

嵌套的命名空间是指定义在其他命名空间中的命名空间。

内层命名空间声明的名字将隐藏外层命名空间声明的同名成员。在嵌套的命名空间中定义的名字只在内层命名空间中有效,外层命名空间中的代码要想访问它必须在名字前添加限定符。

cpp 复制代码
cplusplus_primer::QueryLib::Query

内联命名空间

和普通的嵌套命名空间不同,内联命名空间中的名字可以被外层命名空间直接使用。

无须在内联命名空间的名字前添加表示该命名空间的前缀,通过外层命名空间的名字就可以直接访问它。

定义内联命名空间的方式是在关键字namespace前添加关键字inline

cpp 复制代码
inline namespace FifthEd{
	//该命名空间表示本书第5版的代码
}
namespace FifthEd{	//隐式内联
	class Query base {/* ...*/};
	//其他与 Query有关的声明
}

关键字inline必须出现在命名空间第一次定义的地方,后续再打开命名空间的时候可以写inline,也可以不写。

未命名的命名空间

未命名的命名空间是指关键字namespace后紧跟花括号括起来的一系列声明语句。
未命名的命名空间中定义的变量拥有静态生命周期:它们在第一次使用前创建,并且直到程序结束才销毁。

一个未命名的命名空间可以在某个给定的文件内不连续,但是不能跨越多个文件。

如果两个文件都含有未命名的命名空间,则这两个空间相互无关。在这两个未命名的命名空间中可以定义相同的名字,并且这些定义表示的是不同实体。

如果一个头文件定义了未命名的命名空间,则该命名空间中定义的名字将在每个包含了该头文件的文件中对应不同实体。

使用命名空间

命名空间的别名

命名空间的别名可以为命名空间的名字设定一个短得多的同义词。

cpp 复制代码
namespace cplusplus_primer {/* ...*/};
//可以为其设定一个短得多的同义词;
namespace primer = cplusplus_primer;

//命名空间的别名也可以指向一个嵌套的命名空间:
namespace Qlib = cplusplus_primer::QueryLib;
Qlib::Query q;

using

一条using声明语句一次只引入命名空间的一个成员。
using的有效范围从using声明的地方开始,一直到using声明所在的作用域结束为止。这有助于减少代码中的冗余,提高可读性。

cpp 复制代码
namespace MyNamespace {
    int value = 42;
    void Print() {
        std::cout << "Hello from MyNamespace" << std::endl;
    }
}

int main() {
    using MyNamespace::value; // 引入命名空间中的变量
    using MyNamespace::Print; // 引入命名空间中的函数

    std::cout << value << std::endl; // 可以直接使用value,不需要MyNamespace::
    Print(); // 可以直接使用Print,不需要MyNamespace::

    return 0;
}

using指示以关键字using开始,后面是关键字namespace 以及命名空间的名字。

using 指示不能用在类的作用域内。但是 using 声明是可以的。

using 指示令引入的命名空间成员的作用域提升到包含命名空间本身和 using 指示的最近作用域。可以使用该命名空间内的所有名称,而无需使用限定符。

cpp 复制代码
namespace MyNamespace {
    int value = 42;
    void Print() {
        std::cout << "Hello from MyNamespace" << std::endl;
    }
}

int main() {
    using namespace MyNamespace; // 引入整个命名空间

    std::cout << value << std::endl; // 可以直接使用value,不需要MyNamespace::
    Print(); // 可以直接使用Print,不需要MyNamespace::

    return 0;
}

头文件如果在其顶层作用域中含有 using 指示或 using 声明,则会将名字注入到所有包含了该头文件的文件中。

因此头文件最多只能在它的函数或命名空间内使用 using 指示或 using 声明。

避免使用 using 指示:一般不要使用 using 指示,很危险。

using 指示的用处:在命名空间本身的实现文件中可以使用 using 指示。

类、命名空间与作用域

对命名空间内部名字的查找遵循常规的查找规则:即由内向外依次查找每个外层作用域。

外层作用域也可能是一个或多个嵌套的命名空间,直到最外层的全局命名空间查找过程终止。只有位于开放的块中且在使用点之前声明的名字才被考虑。

实参相关的查找与类类型形参

实参相关的查找与类类型形参

cpp 复制代码
std::string s;
std::cin >> s;

重载操作符 >> 定义在标准库 string 中,string 定义在命名空间 std 中。但是不用 std:: 限定符就可以调用 >> 操作符。

这是因为:给一个函数传递一个类类型的对象时,除了在常规的作用域查找外还会查找实参类所属的命名空间。

>>操作符的形参(iostream 和 string)是类类型的,所以编译器还会查找 iostreamstring 所属的命名空间。

查找与 std::move 和 std::forward

moveforward 都是模板函数,在标准库的定义中它们都接受一个右值引用的函数形参。在函数模板中,右值引用形参可以匹配任何类型。

如果用户自己也定义了一个 move 函数并且它只接受一个参数,那么必然会与标准库的 move 函数冲突。forward 函数也是如此。所以 moveforward 的名字冲突非常频繁,使用时应该写 std::movestd::forward

重载与命名空间

与实参相关的查找与重载

对于接受类类型实参的函数来说,其名字查找将在实参类所属的命名空间中进行。

重载与 using 声明

using 声明语句声明的是一个名字,而非一个特定的函数。当我们为函数书写using声明时,该函数的所有版本都被引入到当前作用域中。

重载与 using 指示

using指示将命名空间的成员提升到外层作用域中,如果命名空间的某个函数与该命名空间所属作用域的函数同名,则命名空间的函数将被添加到重载集合中。

using声明不同的是,对于using 指示来说,引入一个与已有函数形参列表完全相同的函数并不会产生错误。只需要调用时指明是哪个版本即可。

跨越多个 using 指示的重载

如果存在多个命名空间,则来自每个命名空间的名字都将成为候选函数集的一部分。

相关推荐
芊寻(嵌入式)10 分钟前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
獨枭12 分钟前
C++ 项目中使用 .dll 和 .def 文件的操作指南
c++
霁月风15 分钟前
设计模式——观察者模式
c++·观察者模式·设计模式
橘色的喵16 分钟前
C++编程:避免因编译优化引发的多线程死锁问题
c++·多线程·memory·死锁·内存屏障·内存栅栏·memory barrier
一颗松鼠19 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
有梦想的咸鱼_21 分钟前
go实现并发安全hashtable 拉链法
开发语言·golang·哈希算法
海阔天空_201326 分钟前
Python pyautogui库:自动化操作的强大工具
运维·开发语言·python·青少年编程·自动化
天下皆白_唯我独黑33 分钟前
php 使用qrcode制作二维码图片
开发语言·php
夜雨翦春韭37 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
小远yyds39 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js