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++ 关键词
成员访问运算符
访问其操作数的一个成员。
运算符名 | 语法 | 可重载 | 原型示例(对于 class T ) | |
---|---|---|---|---|
类定义内 | 类定义外 | |||
数组下标 | a[b]
|
是 | R& T::operator[](const T2& b); | N/A |
间接寻址(由 a 所指向的变量)
|
*a
|
是 | R& T::operator*(); | R& operator*(T &a); |
取地址 | &a
|
是 | R* T::operator&(); | R* operator&(T &a); |
对象的成员 | a.b
|
否 | N/A | N/A |
指针的成员 | a->b
|
是 | R* T::operator->() | N/A |
对象的成员指针 | a.*b
|
否 | N/A | N/A |
指针的成员指针 | a->*b
|
是 | R* T::operator->*(R) | R* T::operator->*(T, R) |
|
解释
内建的下标 (subscript)运算符提供对其指针或数组操作数所指向的对象的访问。
内建的间接寻址 (indirection)运算符提供对其指针操作数所指向的对象或函数的访问。
内建的取地址 (address of)运算符创建指向其对象或函数操作数的指针。
对象的成员和对象的成员指针运算符提供对其对象操作数的数据成员或成员函数的访问。
内建的指针的成员和指针的成员指针运算符提供对其指针操作数所指向的类的数据成员或成员函数的访问。
内建的下标运算符
下标运算符表达式的形式为
expr1 [ expr2 ]
|
(1) | ||||||||
expr1 [ { expr, ... } ]
|
(2) | (C++11) | |||||||
T
。operator[]
内建下标表达式 E1[E2],除了求值顺序之外 (C++17 起)与表达式 *(E1 + E2) 严格等同,就是说,它遵循指针算术的规则,将指针操作数(可以是数组到指针转换的结果,但它必须指向某数组的元素或末尾后一位置)调整为指向同数组的另一元素,然后再进行解引用。
应用到数组时,若数组为左值,则 (C++11 起)下标表达式为左值,否则为亡值 (C++11 起)。
应用到指针时,下标表达式始终是左值。
不允许类型 T
为不完整类型,即使始终不会使用 T
的大小或其内部结构也是如此,如 &x[0]。
以逗号表达式为下标表达式是被弃用的。 例如, a[b, c] 被弃用而 a[(b, c)] 未被弃用。 |
(C++20 起) |
在对于用户定义运算符的重载决议中,对于每个对象类型 T
(可有 cv 限定),下列函数签名参与重载决议:
T& operator[](T*, std::ptrdiff_t); |
||
T& operator[](std::ptrdiff_t, T*); |
||
#include <iostream> int main() { int a[4] = {1, 2, 3, 4}; int* p = &a[2]; std::cout << p[1] << p[-1] << 1[p] << (-1)[p] << '\n'; }
输出:
4242
内建的间接寻址运算符
间接寻址运算符表达式的形式为
* expr
|
|||||||||
内建间接寻址运算符的操作数必须是对象指针或函数指针,其结果就是 expr 所指向的指针或函数。
指向(可有 cv 限定)的 void 的指针不能解引用。指向其他不完整类型的指针可以解引用,但产生的左值只能在允许不完整类型的语境中使用,例如初始化一个引用。
在对于用户定义运算符的重载决议中,对于每个要么为(可有 cv 限定的)对象类型要么为(未被 const 或引用限定的)函数类型的类型 T
,下列函数签名参与重载决议:
T& operator*(T*); |
||
#include <iostream> int f() { return 42; } int main() { int n = 1; int* pn = &n; int& r = *pn; // 左值可以绑定到引用 int m = *pn; // 间接寻址 + 左值到右值转换 int (*fp)() = &f; int (&fr)() = *fp; // 函数左值可以绑定到引用 }
内建的取地址运算符
取地址运算符表达式的形式为
& 表达式
|
(1) | ||||||||
& class :: 成员
|
(2) | ||||||||
T
的左值表达式时,operator&
创建并返回一个有相同 cv 限定的 T*
类型的纯右值,并指向由该操作数所代表的对象或函数。当其操作数具有不完整类型时,可以构成指针,但若该不完整类型恰好为某个定义了其自身的 operator&
的类,则其行为未定义 (C++14 前)则使用内建还是重载运算符是未指明的 (C++14 起)。对于其类型带有用户定义的 operator&
的操作数,可以使用 std::addressof 来获取真正的指针。C
类中的 T
类型的成员函数指针或数据成员指针的纯右值。注意,&member、C::member,甚至 &(C::member) 都不能用于初始化成员指针。在对于用户定义运算符的重载决议中,此运算符不引入任何额外函数签名:若存在作为可行函数的重载 operator&
,则内建的取址运算符不适用。
void f(int) {} void f(double) {} struct A { int i; }; struct B { void f(); }; int main() { int n = 1; int* pn = &n; // 指针 int* pn2 = &*pn; // pn2 == pn int A::* mp = &A::i; // 数据成员指针 void (B::*mpf)() = &B::f; // 成员函数指针 void (*pf)(int) = &f; // 根据初始化语境进行重载决议 // auto pf2 = &f; // 错误:重载函数类型有歧义 auto pf2 = static_cast<void (*)(int)>(&f); // 由于转型进行重载决议 }
内建的成员访问运算符
成员访问运算符表达式的形式为
表达式 . template (可选) 标识表达式
|
(1) | ||||||||
表达式 -> template (可选) 标识表达式
|
(2) | ||||||||
表达式 . 伪析构函数
|
(3) | ||||||||
表达式 -> 伪析构函数
|
(4) | ||||||||
T*
的表达式。两种运算符的第一个操作数都被求值,即便它并不是必须的(比如其第二个操作数指名的是静态成员)。
两个运算符的第二个操作数是 T
或 T
的某个无歧义且可访问的基类 B
的数据成员或成员函数的名字(正式的说法是标识表达式(id-expression))(如 E1.E2 或 E1->E2),并可选地有限定(如 E1.B::E2 或 E1->B::E2),可选地使用 template 歧义消解符(如 E1.template E2 或 E1->template E2)。
如果提供的是用户定义的 operator->
,则递归地对其所返回的值再次调用 operator->
,直到到达返回普通指针的 operator->
为止。之后再对这个指针采用内建语义。
对于内建类型,表达式 E1->E2 严格等价于 (*E1).E2;因此以下规则仅处理了 E1.E2 的情形。
在表达式 E1.E2 中:
E2
为静态数据成员时:
- 如果
E2
具有引用类型T&
或T&&
,则其结果为T
类型的左值,代表E2
所代表的对象或函数, - 否则,其结果为代表该静态数据成员的左值。
- 基本上,这两种情况下
E1
均被求值随即被丢弃;
E2
为非静态数据成员时:
- 如果
E2
具有引用类型T&
或T&&
,则其结果为T
类型的左值,代表E2
所代表的对象或函数, - 否则,如果
E1
为左值,则其结果为代表E1
的这个非静态数据成员的左值, - 否则(
E1
为右值 (C++17 前)亡值(可能是从纯右值实质化而来) (C++17 起)),其结果为代表E1
的这个非静态数据成员的右值 (C++11 前)亡值 (C++11 起)。
- 若
E2
不是 mutable 成员,则结果的 cv 限定性是E1
和E2
的 cv 限定性的合并,否则(E2
是 mutable 成员)为E1
和E2
的 volatile 限定性的合并;
E2
为成员枚举项时,其结果为等于 E1
的这个成员枚举项的纯右值;E1
为标量类型而 E2
为一个 ~
之后跟着代表(移除 cv 限定后)相同类型的类型名或 decltype 说明符,可选地有限定时,其结果为一种特殊的纯右值,它只能用作函数调用运算符的左操作数,而不能用于其他目的。所构成的函数调用表达式被称为伪析构函数调用(pseudo destructor call)。它不接受任何实参,返回 void ,求值 E1
后不进行任何操作 (C++20 前)结束其结果对象的生存期 (C++20 起)。这是唯一使 operator.
的左操作数具有非类类型的情形。允许进行伪析构函数调用,使得编写代码时无须了解某个给定类型是否存在析构函数成为可能。operator.
不能重载,而对于 operator->
来说,在对于用户定义运算符的重载决议中,内建运算符不引入任何额外函数签名:若存在作为可行函数的重载 operator&
,则不采用内建的 operator->
。
#include <iostream> struct P { template<typename T> static T* ptr() { return new T; } }; template<typename T> struct A { A(int n): n(n) {} int n; static int sn; int f() { return 10 + n; } static int sf() { return 4; } class B {}; enum E {RED = 1, BLUE = 2}; void g() { typedef int U; // 待决的模板成员需要关键词 template int* p = P().template ptr<U>(); p->~U(); // U 为 int,调用 int 的伪析构函数 delete p; } }; template<> int A<P>::sn = 2; int main() { A<P> a(1); std::cout << a.n << ' ' << a.sn << ' ' // A::sn 也可以工作 << a.f() << ' ' << a.sf() << ' ' // A::sf() 也可以工作 // << a.B << ' ' // 错误:不允许嵌套类型 << a.RED << ' '; // 枚举项 }
输出:
1 2 11 4 1
内建的成员指针访问运算符
通过成员指针进行的成员访问运算符表达式的形式为
lhs .* rhs
|
(1) | ||||||||
lhs ->* rhs
|
(2) | ||||||||
T
的表达式。T*
的表达式。两个运算符的第二操作数都是类型为指向 T
的(数据或函数)成员指针类型,或为指向 T
的无歧义且可访问基类 B
成员指针类型的表达式。
对于内建类型,表达式 E1->*E2 严格等价于 (*E1).*E2;因此以下规则仅处理了 E1.*E2 的情形。
在表达式 E1.*E2 中:
E2
为指向数据成员的指针时,
- 如果
E1
为左值,则其结果为代表这个成员的左值, - 否则(
E1
为右值 (C++17 前)亡值(可能是从纯右值实质化而来) (C++17 起)),其结果为代表这个数据成员的右值 (C++11 前)亡值 (C++11 起);
E2
为指向成员函数的指针时,其结果为代表这个成员函数的一种特殊的纯右值,它只能用作成员函数调用运算符的左运算数,而不能用于其他目的;E2
为空成员指针值时,其行为未定义;E1
为右值而 E2
指向带有引用限定符 & 的成员函数时,程序非良构,除非该成员函数亦为 const
限定但非 volatile
限定 (C++20 起);E1
为左值而 E2
指向带有引用限定符 && 的成员函数时,程序非良构。在对于用户定义运算符的重载决议中,对于每个类型 D
, B
, R
的组合,其中类类型 B
是与 D
相同的类或 D
的无歧义且可访问基类,而 R
是对象或函数类型,下列函数签名参与重载决议:
R& operator->*(D*, R B::*); |
||
其中两个操作数都可以是 cv 限定的,这种情况下返回类型的 cv 限定性是个操作数的 cv 限定性的合并。
#include <iostream> struct S { S(int n): mi(n) {} mutable int mi; int f(int n) { return mi + n; } }; struct D: public S { D(int n): S(n) {} }; int main() { int S::* pmi = &S::mi; int (S::* pf)(int) = &S::f; const S s(7); // s.*pmi = 10; // 错误:无法通过 mutable 进行修改 std::cout << s.*pmi << '\n'; D d(7); // 基类的指针可以在派生类对象上工作 D* pd = &d; std::cout << (d.*pf)(7) << ' ' << (pd->*pf)(8) << '\n'; }
输出:
7 14 15
标准库
许多标准容器类都重载了下标运算符
访问指定的位 ( std::bitset<N> 的公开成员函数) | |
提供到被管理数组的有索引访问 ( std::unique_ptr<T,Deleter> 的公开成员函数) | |
访问指定字符 ( std::basic_string<CharT,Traits,Allocator> 的公开成员函数) | |
访问指定的元素 ( std::array<T,N> 的公开成员函数) | |
访问指定的元素 ( std::deque<T,Allocator> 的公开成员函数) | |
访问指定的元素 ( std::vector<T,Allocator> 的公开成员函数) | |
访问或插入指定的元素 ( std::map<Key,T,Compare,Allocator> 的公开成员函数) | |
访问或插入指定的元素 ( std::unordered_map<Key,T,Hash,KeyEqual,Allocator> 的公开成员函数) | |
按索引访问元素 ( std::reverse_iterator<Iter> 的公开成员函数) | |
按索引访问元素 ( std::move_iterator<Iter> 的公开成员函数) | |
获取/设置 valarray 数组元素、切片或掩码 ( std::valarray<T> 的公开成员函数) | |
返回指定的子匹配 ( std::match_results<BidirIt,Alloc> 的公开成员函数) |
许多迭代器和智能指针类都重载了间接寻址和成员运算符
解引用指向被管理对象的指针 ( std::unique_ptr<T,Deleter> 的公开成员函数) | |
解引用存储的指针 ( std::shared_ptr<T> 的公开成员函数) | |
访问被管理对象 ( std::auto_ptr<T> 的公开成员函数) | |
解引用迭代器 ( std::raw_storage_iterator<OutputIt,T> 的公开成员函数) | |
对递减后的底层迭代器进行解引用 ( std::reverse_iterator<Iter> 的公开成员函数) | |
无操作 ( std::back_insert_iterator<Container> 的公开成员函数) | |
无操作 ( std::front_insert_iterator<Container> 的公开成员函数) | |
无操作 ( std::insert_iterator<Container> 的公开成员函数) | |
(C++20 中弃用) |
访问指向的元素 ( std::move_iterator<Iter> 的公开成员函数) |
返回当前元素 ( std::istream_iterator<T,CharT,Traits,Distance> 的公开成员函数) | |
无操作 ( std::ostream_iterator<T,CharT,Traits> 的公开成员函数) | |
(C++11 起)(C++17 前) |
获得当前字符的副本 若 CharT 拥有成员,则访问当前字符的成员 ( std::istreambuf_iterator<CharT,Traits> 的公开成员函数) |
无操作 ( std::ostreambuf_iterator<CharT,Traits> 的公开成员函数) | |
访问当前匹配 ( std::regex_iterator<BidirIt,CharT,Traits> 的公开成员函数) | |
访问当前子匹配 ( std::regex_token_iterator<BidirIt,CharT,Traits> 的公开成员函数) |
标准库中的类都没有重载 operator&
。最为人所知的重载 operator&
的例子是微软的 COM 类 CComPtr,但在如 boost.spirit 这样的 EDSL 中也会出现重载它的例子。
标准库中的类都没有重载 operator->*
。曾有建议将其作为智能指针接口的一部分,并在 boost.phoenix 中的 actor 上有实际应用,但它在如 cpp.react 这样的 EDSL 中更为常见。
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1213 | C++11 | 数组右值的下标操作导致左值 | 重分类为亡值 |
参阅
常见运算符 | ||||||
---|---|---|---|---|---|---|
赋值 | 自增 自减 |
算术 | 逻辑 | 比较 | 成员访问 | 其他 |
a = b |
++a |
+a |
!a |
a == b |
a[b] |
a(...) |
特殊运算符 | ||||||
static_cast 转换一个类型为另一相关类型 |