成员访问运算符
访问操作数的一个成员。
运算符名 | 语法 | 可重载 | 原型示例(对于 class T) | |||||
---|---|---|---|---|---|---|---|---|
类定义内 | 类定义外 | |||||||
下标 | a[b]
|
是 | R& T::operator[](S b);
|
不适用 | ||||
间接寻址 | *a
|
是 | R& T::operator*(); | R& operator*(T a); | ||||
取地址 | &a
|
是 | R* T::operator&(); | R* operator&(T a); | ||||
对象的成员 | a.b
|
否 | 不适用 | 不适用 | ||||
指针的成员 | a->b
|
是 | R* T::operator->() | 不适用 | ||||
指向对象的成员的指针 | a.*b
|
否 | 不适用 | 不适用 | ||||
指向指针的成员的指针 | 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 , 表达式 , ... ]
|
(3) | (C++23 起) | |||||||
T
的数组”类型的泛左值或“T
的指针”类型的纯右值,而另一表达式(分别是 表达式2 或 表达式1)必须是无作用域枚举或整型类型的纯右值。此表达式的结果类型是 T
。表达式2 不能是无括号的逗号表达式。 (C++23 起)内建下标表达式 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> #include <map> #include <string> 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++23 起) |
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
的数据成员或成员函数的名字(正式的说法是标识表达式)(如 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 具有引用类型
T&
或T&&
(C++11 起),那么它的结果是T
类型的左值,代表该引用绑定到的对象或函数, - 否则,给定 E2 的类型为
T
,它的结果是T
类型的代表该静态数据成员的左值。
- 如果 E2 具有引用类型
T&
或T&&
(C++11 起),那么它的结果是T
类型的左值,代表 E1 的对应引用成员绑定到的对象或函数, - 否则,如果 E1 是左值,那么它的结果是代表 E1 的这个非静态数据成员的左值,
- 否则(E1 是右值 (C++17 前)亡值(可能是从纯右值实质化而来) (C++17 起)),它的结果是代表 E1 的这个非静态数据成员的右值 (C++11 前)亡值 (C++11 起)。
T
,它的结果是 T
类型的右值 (C++11 前)纯右值 (C++11 起),该结果的值是该枚举项的值;~
之后跟着代表(移除 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; struct UPtrWrapper { std::unique_ptr<std::string> uPtr; std::unique_ptr<std::string>& operator->() { return uPtr; } }; int main() { A<P> a(1); std::cout << a.n << ' ' << a.sn << ' ' // A::sn 也可以 << a.f() << ' ' << a.sf() << ' ' // A::sf() 也可以 // << a.B << ' ' // 错误:不允许嵌套类型 << a.RED << ' '; // 枚举项 UPtrWrapper uPtrWrap{std::make_unique<std::string>("wrapped")}; assert(uPtrWrap->data() == uPtrWrap.operator->().operator->()->data()); }
输出:
1 2 11 4 1
内建的成员指针访问运算符
通过成员指针进行的成员访问运算符表达式的形式为
左操作数 .* 右操作数
|
(1) | ||||||||
左操作数 ->* 右操作数
|
(2) | ||||||||
T
的表达式。T*
的表达式。两个运算符的第二操作数都是类型是指向 T
的(数据或函数)成员指针类型,或者是指向 T
的无歧义且可访问基类 B
成员指针类型的表达式。
对于内建类型,表达式 E1->*E2 严格等价于 (*E1).*E2;因此以下规则只处理了 E1.*E2 的情形。
在表达式 E1.*E2 中:
- 如果 E1 是左值,那么它的结果是代表这个成员的左值,
- 否则(E1 是右值 (C++17 前)亡值(可能是从纯右值实质化而来) (C++17 起)),它的结果是代表这个数据成员的右值 (C++11 前)亡值 (C++11 起);
&
的成员函数时,程序非良构,除非该成员函数的 cv 限定符有 const 而没有 volatile (C++20 起);
7) 当 E1 是左值而 E2 指向带有引用限定符
&& 的成员函数时,程序非良构。 |
(C++11 起) |
在对于用户定义运算符的重载决议中,对于每个类型 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> 的公开成员函数) | |
获得当前字符的副本 ( 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++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1213 | C++11 | 数组右值的下标操作导致左值 | 重分类为亡值 |
CWG 1458 | C++98 | 应用 & 到声明 operator& 的不完整类型左值导致未定义行为
|
未指明使用哪个 &
|
CWG 1800 | C++98 | 当应用 & 到成员匿名联合体的非静态数据成员时,不明确该匿名联合体是否会参与结果类型 |
结果类型不含匿名联合体 |
CWG 2614 | C++98 | E2 是引用成员或枚举项的情况下 E1.E2 的结果不明确 | 使之明确 |
参阅
常见运算符 | ||||||
---|---|---|---|---|---|---|
赋值 | 自增/自减 | 算术 | 逻辑 | 比较 | 成员访问 | 其他 |
a = b |
++a |
+a |
!a |
a == b |
a[b] |
函数调用 |
a(...) | ||||||
逗号 | ||||||
a, b | ||||||
条件 | ||||||
a ? b : c | ||||||
特殊运算符 | ||||||
static_cast 转换一个类型为另一相关类型 |