C++ 参考手册
- C++11
- C++14
- C++17
- C++20
- C++ 编译器支持情况表
- 独立与宿主实现
- C++ 语言
- 变量模板(C++14 起)
- 整数字面量
- 聚合初始化
- 比较运算符
- 默认比较(C++20 起)
- 转义序列
- for 循环
- while 循环
- 用户定义转换
- SFINAE
- 主函数
- ASCII 码表
- 标识符
- 类型
- 内存模型
- 对象
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 类
- 运算符重载
- 模板
- 异常
- 事务性内存
- 占位符类型说明符 (C++11 起)
- decltype 说明符
- 函数声明
- final 说明符 (C++11 起)
- override 说明符(C++11 起)
- 引用声明
- 移动构造函数
- 移动赋值运算符
- 枚举声明
- constexpr 说明符(C++11 起)
- 列表初始化 (C++11 起)
- 构造函数与成员初始化器列表
- using 声明
- nullptr,指针字面量
- 基础类型
- 类型别名,别名模版 (C++11 起)
- 形参包
- 联合体声明
- 字符串字面量
- 用户定义字面量 (C++11 起)
- 属性说明符序列(C++11 起)
- Lambda 表达式 (C++11 起)
- noexcept 说明符 (C++11 起)
- noexcept 运算符 (C++11 起)
- alignof 运算符(C++11 起)
- alignas 说明符 (C++11 起)
- 存储类说明符
- 基于范围的 for 循环 (C++11 起)
- static_assert 声明
- 隐式转换
- 代用运算符表示
- 自增/自减运算符
- 折叠表达式(C++17 起)
- 类模板实参推导(C++17 起)
- 模板形参与模板实参
- if 语句
- inline 说明符
- 结构化绑定声明 (C++17 起)
- switch 语句
- 字符字面量
- 命名空间
- 求值顺序
- 复制消除
- consteval 说明符 (C++20 起)
- constinit 说明符 (C++20 起)
- 协程 (C++20)
- 模块 (C++20 起)
- 约束与概念 (C++20 起)
- new 表达式
- do-while 循环
- continue 语句
- break 语句
- goto 语句
- return 语句
- 动态异常说明
- throw 表达式
- try 块
- 命名空间别名
- 类声明
- cv(const 与 volatile)类型限定符
- 默认初始化
- 值初始化(C++03 起)
- 零初始化
- 复制初始化
- 直接初始化
- 常量初始化
- 引用初始化
- 值类别
- C++ 运算符优先级
- 布尔字面量
- 浮点字面量
- typedef 说明符
- 显式类型转换
- static_cast 转换
- dynamic_cast 转换
- const_cast 转换
- reinterpret_cast 转换
- delete 表达式
- 构造函数与成员初始化器列表
- this 指针
- 访问说明符
- 友元声明
- virtual 函数说明符
- explicit 说明符
- 静态成员
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 类模板
- 函数模板
- 显式(全)模板特化
- 汇编声明
- C++ 的历史
- 作用域
- 生存期
- 定义与单一定义规则(ODR)
- 名字查找
- 有限定的名字查找
- 无限定的名字查找
- 如同规则
- 未定义行为
- 翻译阶段
- 常量表达式
- 赋值运算符
- 算术运算符
- 逻辑运算符
- 成员访问运算符
- 其他运算符
- sizeof 运算符
- typeid 运算符
- 指针声明
- 数组声明
- 语言链接
- 详述类型说明符
- 默认实参
- 变长实参
- 实参依赖查找
- 重载决议
- 重载函数的地址
- 注入类名
- 非静态数据成员
- 非静态成员函数
- 嵌套类
- 派生类
- 空基类优化
- 抽象类
- 位域
- 转换构造函数
- 成员模板
- 模板实参推导
- 部分模板特化
- sizeof... 运算符
- 待决名
- 函数 try 块
- 扩充命名空间 std
- 字母缩写
- RAII
- 三/五/零之法则
- PImpl
- 零开销原则
- 类型
- 隐式转换
- 注释
- C++ 关键词
- 预处理器
- C++ 标准库头文件
- 具名要求
- 功能特性测试 (C++20)
- 工具库
- 类型支持(基本类型、RTTI、类型特性)
- 概念库 (C++20)
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 容器库
- 迭代器库
- 范围库 (C++20)
- 算法库
- 数值库
- 输入/输出库
- 文件系统库
- 本地化库
- 正则表达式库
- 原子操作库
- 线程支持库
- 实验性 C++ 特性
- 有用的资源
- 索引
- std 符号索引
- 协程支持 (C++20)
- C++ 关键词
命名空间
命名空间提供了在大项目中避免名字冲突的一种方法。
声明于命名空间块内的符号被放入一个具名的作用域中,避免这些符号被误认为其他作用域中的同名符号。
允许具有相同名字的多个命名空间块。这些块中的所有声明声明于该具名作用域。
语法
namespace 命名空间名 { 声明序列 }
|
(1) | ||||||||
inline namespace 命名空间名 { 声明序列 }
|
(2) | (C++11 起) | |||||||
namespace { 声明序列 }
|
(3) | ||||||||
命名空间名:: 名字
|
(4) | ||||||||
using namespace 命名空间名;
|
(5) | ||||||||
using 命名空间名:: 名字;
|
(6) | ||||||||
namespace 名字 = 有限定命名空间 ;
|
(7) | ||||||||
namespace 命名空间名:: inline (C++20 起)(可选) 名字 { 声明序列 }
|
(8) | (C++17 起) | |||||||
namespace A::B::inline C { ... } 等价于 namespace A::B { inline namespace C { ... } }。inline 可出现于除首个以外的每个命名空间名之前:namespace A::inline B::C {} 等价于 namespace A { inline namespace B { namespace C {} } }。 |
(C++20 起) |
解释
命名空间
inline (可选) namespace attr(可选) 标识符 { 命名空间体 }
|
|||||||||
inline
|
- | 若存在,则令此命名空间为内联命名空间(见下文)。若原初命名空间定义不使用 inline ,则不能出现在扩展命名空间定义上使用
|
attr(C++17) | - | 任意数量的属性的可选序列 |
标识符 | - | 要么是先前未使用过的标识符,该情况下这是原初命名空间定义,要么是命名空间名,该情况下这是扩展命名空间定义,要么是 :: 所分隔的外围命名空间说明符,以 标识符 结束,该情况下这是嵌套命名空间定义 (C++17 起) |
命名空间体 | - | 任何种类(包含类、函数定义和嵌套命名空间等)的声明的可能为空的序列 |
命名空间定义只允许在命名空间作用域,包括全局作用域中出现。
为了重打开一个既存的命名空间(正式而言,作为扩展命名空间定义),对命名空间定义中所用的 标识符 的查找必须解析为一个命名空间名(而非命名空间别名),该名字被声明为某个外围命名空间或外围命名空间中的内联命名空间的成员。
出现于 命名空间体(包含嵌套命名空间定义)中的声明所引入的所有名字,都成为命名空间 标识符 的成员,无论此命名空间定义是原初命名空间定义(引入 标识符 者),还是扩展命名空间定义(“重打开”已定义命名空间者)。
声明于命名空间体内的命名空间成员,可以在其外部用显式限定进行定义或再声明
namespace Q { namespace V { // V 是 Q 的成员,且完全在 Q 内定义 // namespace Q::V { // C++17 中对上述二行的替代写法 class C { void m(); }; // C 是 V 的成员且完全定义于 V 内 // C::m 仅声明 void f(); // f 是 V 的成员,但只在此声明 } void V::f() // V 的成员 f 的 V 外定义 // f 的外围命名空间仍是全局命名空间、Q 与 Q::V { extern void h(); // 这声明 ::Q::V::h } void V::C::m() // V::C::m 的命名空间(及类)外定义 // 外围命名空间是全局命名空间、Q 与 Q::V { } }
命名空间外的定义和再声明仅在声明点后,仅在命名空间作用域,且仅在包围原命名空间的命名空间(包括全局命名空间)中允许出现,而且它们必须使用限定标识语法 (C++14 起)。
namespace Q { namespace V { // V 的原初命名空间定义 void f(); // Q::V::f 的声明 } void V::f() {} // OK void V::g() {} // 错误:g() 仍不是 V 的成员 namespace V { // V 的扩展命名空间定义 void g(); // Q::V::g 的声明 } } namespace R { // 不是 Q 的外围命名空间 void Q::V::g() {} // 错误:不能在 R 内定义 Q::V::g } void Q::V::g() {} // OK:全局命名空间包围 Q
在非局部类 X 中由友元声明所引入的名字,会成为 X 的最内层外围命名空间的成员,但它们对于通常的名字查找(无限定或有限定)不可见,除非在命名空间作用域提供与之匹配的声明,不论在类之前还是之后均可。这种名字可通过 ADL 找到,其中同时考虑命名空间和类。
这种友元声明,在确定名字是否与先前声明的名字冲突时,只考虑最内层外围命名空间。
void h(int); namespace A { class X { friend void f(X); // A::f 是友元 class Y { friend void g(); // A::g 是友元 friend void h(int); // A::h 是友元,与 ::h 不冲突 }; }; // A::f、A::g 与 A::h 在命名空间作用域不可见 // 虽然它们是命名空间 A 的成员 X x; void g() { // A::g 的定义 f(x); // A::X::f 通过 ADL 找到 } void f(X) {} // A::f 的定义 void h(int) {} // A::h 的定义 // A::f 、 A::g 与 A::h 现在在命名空间作用域可见 // 而且它们亦是 A::X 与 A::X::Y 的友元 }
内联命名空间内联命名空间是在其原初命名空间定义中使用了可选的关键词 许多情况下(列于下方),内联命名空间的成员都被当做如同它们是其外围命名空间的成员一样。这项性质是传递性的:若命名空间 N 包含内联命名空间 M,它又继而包含内联命名空间 O,则 O 的成员能按如同它们是 M 或 N 的成员一样使用。
{ // C++14 中,std::literals 及其成员命名空间是内联的 using namespace std::string_literals; // 令来自 std::literals::string_literals // 的 operator""s 可见 auto str = "abc"s; } { using namespace std::literals; // 令 // std::literals::string_literals::operator""s 与 // std::literals::chrono_literals::operator""s 均可见 auto str = "abc"s; auto min = 60s; } { using std::operator""s; // 令 std::literals::string_literals::operator""s 与 // std::literals::chrono_literals::operator""s 均可见 auto str = "abc"s; auto min = 60s; } 注意:上述关于特化的规则允许建立库版本:库模板的不同实现可定义于不同内联命名空间,同时仍然允许用户通过主模板的显式特化来扩充父命名空间。 |
(C++11 起) |
无名命名空间
无名命名空间定义是具有下列形式的命名空间定义
inline (可选) namespace attr(可选) { 命名空间体 }
|
|||||||||
attr(C++17) | - | 任意数量的属性的可选序列 |
此定义被当做一个拥有独有名字的命名空间定义,与当前作用域中指名此无名命名空间的一条 using 指令。
namespace { int i; // 定义 ::(独有)::i } void f() { i++; // 自增 ::(独有)::i } namespace A { namespace { int i; // A::(独有)::i int j; // A::(独有)::j } void g() { i++; } // A::(独有)::i++ } using namespace A; // 从 A 引入所有名称到全局命名空间 void h() { i++; // 错误:::(独有)::i 与 ::A::(独有)::i 均在作用域中 A::i++; // OK:自增 A::(独有)::i j++; // OK:自增 A::(独有)::j }
虽然无名命名空间中的名字可以声明为具有外部连接,但绝对无法从其他翻译单元访问它们,因为其命名空间名是独有的。 |
(C++11 前) |
无名命名空间以及所有直接或间接在无名命名空间内声明的命名空间,都具有内部连接,这表示声明于无名命名空间内的任何名字都具有内部连接。 |
(C++11 起) |
using 声明
引入定义于别处的名称到此 using 声明出现的声明性区域。
using typename (可选) 嵌套名说明符 无限定标识 ;
|
(C++17 前) | ||||||||
using 声明符列表 ;
|
(C++17 起) | ||||||||
嵌套名说明符 | - | 名字与作用域解析运算符 :: 的序列,以作用域解析运算符结束。单个 :: 代表全局命名空间。
|
无限定标识 | - | 标识表达式 |
typename | - | 当 using 声明向类模板引入基类的成员类型时,关键词 typename 可在必要时用于解析待决名
|
声明符列表 | - | 一或多个形式为 typename (可选) 嵌套名说明符 无限定标识 的声明符的逗号分隔列表。声明符可以后随省略号以指示包展开,但这种形式只在派生类定义中有意义
|
using 声明可用于将命名空间的成员引入到其他命名空间和块作用域中,或将基类的成员引入到派生类定义中。
拥有多于一个 using 声明符的 using 声明,等价于对应的单个 using 声明符的 using 声明的序列。 |
(C++17 起) |
对于在派生类定义中的用法,见 using 声明。
由 using 声明引入到命名空间作用域的名字,可以同任何其他名字一样使用,包含从其他作用域进行的有限定查找:
void f(); namespace A { void g(); } namespace X { using ::f; // 全局 f 现在作为 ::X::f 可见 using A::g; // A::g 现在作为 ::X::g 可见 using A::g, A::g; // (C++17) OK:命名空间作用域允许双重声明 } void h() { X::f(); // 调用 ::f X::g(); // 调用 A::g }
在用 using 声明从命名空间采取成员后,若该命名空间被扩充并引入了同名的额外声明,则这些额外声明不会通过该 using 声明变为可见(与 using 指令相反)。一个例外是 using 声明指名类模板时:后面引入的部分特化实际上是可见的,因为其查找是通过主模板达成的。
namespace A { void f(int); } using A::f; // ::f 现在是 A::f(int) 的同义词 namespace A { // 命名空间扩展 void f(char); // 不更改 ::f 的含义 } void foo() { f('a'); // 调用 f(int),即使 f(char) 存在。 } void bar() { using A::f; // 此 f 是 A::f(int) 与 A::f(char) 的同义词 f('a'); // 调用 f(char) }
using 声明不能指名 模板标识、命名空间或有作用域枚举项。using 声明中的每个声明符引入一个且只有一个名字,例如枚举 的 using 声明不引入其任何枚举项。
针对相同名字的常规声明的所有制约,隐藏和重载规则,均适用于 using 声明:
namespace A { int x; } namespace B { int i; struct g { }; struct x { }; void f(int); void f(double); void g(char); // OK:函数名 g 隐藏类 g } void func() { int i; using B::i; // 错误:两次声明 i void f(char); using B::f; // OK:f(char)、f(int)、f(double) 是重载 f(3.5); // 调用 B::f(double) using B::g; g('a'); // 调用 B::g(char) struct g g1; // 声明 g1 拥有类型 struct B::g using B::x; using A::x; // OK :隐藏 B::x x = 99; // 赋值给 A::x struct x x1; // 声明 x1 拥有类型 struct B::x }
若用 using 声明引入函数,则声明拥有相同名字和形参列表的函数是非良构的(除非是同一函数的声明)。若用 using 声明引入函数模板,则声明拥有相同名字、形参类型列表、返回类型及模板形参列表的函数模板是非良构的。两个 using 声明可以引入拥有相同名字和形参列表的函数,但若试图调用该函数,则程序非良构。
namespace B { void f(int); void f(double); } namespace C { void f(int); void f(double); void f(char); } void h() { using B::f; // 引入 B::f(int) 、 B::f(double) using C::f; // 引入 C::f(int) 、 C::f(double) 及 C::f(char) f('h'); // 调用 C::f(char) f(1); // 错误:B::f(int) 或 C::f(int) ? void f(int); // 错误:f(int) 与 C::f(int) 及 B::f(int) 冲突 }
若某个实体被声明,但未在某内层命名空间中定义,然后在外层命名空间中通过 using 声明予以声明,然后在外层命名空间中再出现拥有相同非限定名的定义,则该定义是外层命名空间的成员,且与 using 声明冲突: namespace X { namespace M { void g(); // 声明,但不定义 X::M::g() } using M::g; void g(); // 错误:试图声明与 X::M::g() 冲突的 X::g } 更一般地,出现于任何命名空间作用域中并用无限定标识符引入名字的声明,始终向它所在的命名空间中引入一个成员,而并非向任何其他命名空间引入。例外情况是对定义于内联命名空间的主模板进行的显式实例化和显式特化:因为它们不引入新名字,它们在外围命名空间中可以使用无限定标识。 |
(C++14 起) |
using 指令
using 指令是拥有下列语法的块声明:
attr(可选) using namespace 嵌套名说明符(可选) 命名空间名 ;
|
(1) | ||||||||
attr(C++11) | - | 应用到此 using 指令的任意数量的属性 |
嵌套名说明符 | - | 名字与作用域解析运算符 :: 的序列,以作用域解析运算符结束。单个 :: 代表全局命名空间。
|
命名空间名 | - | 命名空间名。查找此名时,查找只考虑命名空间声明 |
using 指令仅在命名空间作用域和块作用域中允许出现。从某个 using 指令之后到该指令出现的作用域结尾为止,以任何名字的无限定名字查找的视点,来自 命名空间名 的每个名字均可见,如同它声明于同时包含该 using 指令和 命名空间名 两者的最接近外围命名空间一样。
using 指令不向其所出现的声明性区域添加任何名字(不同于 using 声明),因而并不妨碍再声明相同的名字。
using 指令对于无限定查找是传递性的:若作用域包含指名 命名空间名 的 using 指令,而它自身包含对某 命名空间名-2 的 using 指令,则效果如同第二个命名空间中的 using 指令出现在第一个之中一样。这些传递性命名空间的出现顺序并不影响名字查找。
namespace A { int i; } namespace B { int i; int j; namespace C { namespace D { using namespace A; // 注入所有来自 A 的名称到全局命名空间 int j; int k; int a = i; // i 是 B::i,因为 B::i 隐藏 A::i } using namespace D; // 注入来自 D 的名称到 C // 注入来自 A 的名称到全局命名空间 int k = 89; // 声明与用 using 引入者等同的名称 OK int l = k; // 歧义:C::k 或 D::k int m = i; // OK:B::i 隐藏 A::i int n = j; // OK:D::j 隐藏 B::j } }
在使用 using 指令指名某命名空间后,若该命名空间被扩充并向其添加了额外的成员和/或 using 指令,则这些额外成员和额外的命名空间通过该 using 指令可见(与 using 声明相反)
namespace D { int d1; void f(char); } using namespace D; // 引入 D::d1、D::f、D::d2、D::f, // E::e 及 E::f 到全局命名空间! int d1; // OK:声明时与 D::d1 不冲突 namespace E { int e; void f(int); } namespace D { // 命名空间扩展 int d2; using namespace E; // 传递性 using 指令 void f(int); } void f() { d1++; // 错误:歧义:::d1 或 D::d1? ::d1++; // OK D::d1++; // OK d2++; // OK,d2 是 D::d2 e++; // OK:e 是 E::e,因为传递性 using f(1); // 错误:歧义:D::f(int) 或 E::f(int)? f('a'); // OK:仅有的 f(char) 是 D::f(char) }
注解
在任何命名空间作用域中的 using 指令 using namespace std;
,将命名空间 std
中的所有名字都引入到全局命名空间中(因为全局命名空间是同时包含 std
和任何用户声明命名空间的最近命名空间),这可能导致不合预期的名字冲突。通常认为,在头文件的文件作用域中采用它或其他的 using 指令是不良的实践。
示例
此例展示如何用命名空间创建已命名于 std
命名空间的类。
#include <vector> namespace vec { template< typename T > class vector { // ... }; } // of vec int main() { std::vector<int> v1; // 标准 vector。 vec::vector<int> v2; // 用户定义 vector。 v1 = v2; // 错误:v1 与 v2 是不同类型的对象。 { using namespace std; vector<int> v3; // 同 std::vector v1 = v3; // OK } { using vec::vector; vector<int> v4; // 同 vec::vector v2 = v4; // OK } return 0; }
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1838 | C++14 | 外层命名空间的非限定定义,曾能定义声明但不定义于另一命名空间的实体,并用 using 拉入 | 非限定定义始终指涉其命名空间 |
参阅
命名空间别名 | 为既存命名空间创建一个别名 |