成员访问运算符
访问其操作数的一个成员。
运算符名 | 语法 | 可重载 | 原型示例(对于 class T ) | |||
---|---|---|---|---|---|---|
类定义内 | 类定义外 | |||||
下标 | a[b]
|
是 | R& T::operator[](S b);
|
N/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->*(S b); | R& operator->*(T a, S b); | ||
|
解释
内建的下标 (subscript)运算符提供对其指针或数组操作数所指向的对象的访问。
内建的间接寻址 (indirection)运算符提供对其指针操作数所指向的对象或函数的访问。
内建的取地址 (address of)运算符创建指向其对象或函数操作数的指针。
对象的成员和指向对象的成员的指针运算符提供对其对象操作数的数据成员或成员函数的访问。
内建的指针的成员和指向指针的成员的指针运算符提供对其指针操作数所指向的类的数据成员或成员函数的访问。
内建的下标运算符
下标运算符表达式的形式为
表达式1 [ 表达式2 ]
|
(1) | ||||||||
表达式1 [ { 表达式, ... } ]
|
(2) | (C++11) | |||||||
表达式1 [ 表达式2 , expr, ... ]
|
(3) | (C++23) | |||||||
T
。 表达式2 不能为无括号的逗号表达式。 (C++23 起)operator[]
。operator[]
。内建下标表达式 E1[E2] 除了求值顺序之外 (C++17 起)与表达式 *(E1 + E2) 严格等同,就是说,它遵循指针算术的规则,将指针操作数(可以是数组到指针转换的结果,但它必须指向某数组的元素或末尾后一位置)调整为指向同数组的另一元素,然后再进行解引用。
应用到数组时,若数组为左值,则 (C++11 起)下标表达式为左值,否则为亡值 (C++11 起)。
应用到指针时,下标表达式始终是左值。
类型 T
不得为不完整类型,即使始终不会使用 T
的大小或其内部结构也是如此,如 &x[0]。
以无括号的逗号表达式为下标运算符的第二(右)操作数是被弃用的。 例如, a[b, c] 被弃用而 a[(b, c)] 未被弃用。 |
(C++20 起) (C++23 前) |
无括号的逗号表达式不能为下标运算符的第二(右)参数。例如 a[b, c] 要么非良构要么等价于 a.operator[](b, c)。 为将逗号表达式用作下标需要括号,例如 a[(b, c)]。 |
(C++23 起) |
在对于用户定义运算符的重载决议中,对于每个对象类型 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'; std::map<std::pair<int, int>, std::string> m; m[{1, 2}] = "abc"; // 使用 [{...}] 版本 }
输出:
4242
内建的间接寻址运算符
间接寻址运算符表达式的形式为
* 表达式
|
|||||||||
内建间接寻址运算符的操作数必须是对象指针或函数指针,其结果就是 表达式 所指向的指针或函数。
指向(可有 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) | ||||||||
& 类 :: 成员
|
(2) | ||||||||
T
的左值表达式时,operator&
创建并返回一个有相同 cv 限定的 T*
类型的纯右值,并指向由该操作数所代表的对象或函数。当其操作数具有不完整类型时,可以构成指针,但若该不完整类型恰好为某个定义了其自身的 operator&
的类,则使用内建还是重载运算符是未指明的。对于其类型带有用户定义的 operator&
的操作数,可以使用 std::addressof 来获取真正的指针。注意,这和 C99 以及其后的 C 语言版本不同,不存在对一元 operator*
运算符的运算结果应用一元 operator&
运算符的特殊情况。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
后结束其结果对象的生存期。这是唯一使 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
内建的成员指针访问运算符
通过成员指针进行的成员访问运算符表达式的形式为
左操作数 .* 右操作数
|
(1) | ||||||||
左操作数 ->* 右操作数
|
(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> 的公开成员函数) | |
(C++11) |
访问指定的元素 ( std::array<T,N> 的公开成员函数) |
访问指定的元素 ( std::deque<T,Allocator> 的公开成员函数) | |
访问指定的元素 ( std::vector<T,Allocator> 的公开成员函数) | |
访问或插入指定的元素 ( std::map<Key,T,Compare,Allocator> 的公开成员函数) | |
(C++11) |
访问或插入指定的元素 ( std::unordered_map<Key,T,Hash,KeyEqual,Allocator> 的公开成员函数) |
按索引访问元素 ( std::reverse_iterator<Iter> 的公开成员函数) | |
(C++11) |
按索引访问元素 ( 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++11)(C++11)(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 | 数组右值的下标操作导致左值 | 重分类为亡值 |
CWG 1458 | C++98 | 应用 & 到声明 operator& 的不完整类型左值导致未定义行为 | 未指明使用哪个 & |
参阅
常见运算符 | ||||||
---|---|---|---|---|---|---|
赋值 | 自增 自减 |
算术 | 逻辑 | 比较 | 成员访问 | 其他 |
a = b |
++a |
+a |
!a |
a == b |
a[b] |
a(...) |
特殊运算符 | ||||||
static_cast 转换一个类型为另一相关类型 |