成员访问运算符
访问其运算数的成员。
运算符名 | 语法 | 可重载 | 原型示例(对于 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) |
|
目录 |
[编辑] 解释
内建的下标运算符提供对其指针或数组运算数所指向的对象的访问。
内建的间接寻址运算符提供对其指针运算数所指向的对象或函数的访问。
内建的取地址运算符创建指向其对象或函数运算数的指针。
对象的成员和对象的成员指针运算符提供对其类运算数的数据成员或成员函数的访问。
内建的指针的成员和指针的成员指针运算符提供对其指针运算数所指向的类的数据成员或成员指针的访问。
[编辑] 内建的下标运算符
下标运算符表达式拥有形式
expr1 [ expr2 ]
|
(1) | ||||||||
expr1 [ { expr, ... } ]
|
(2) | (C++11) | |||||||
T
类型。operator[]
内建下标表达式 E1[E2] 除了求值顺序 (C++17 起)准确地等同于表达式 *(E1 + E2) ,即将指针运算数(可以是数组到指针转换的结果,而它必须指向某数组的元素或结尾后一位置)调整为指向同数组的另一元素,遵循指针算术的规则,再解引用。
应用到数组时,下标表达式为左值,若数组为左值,否则为亡值 (C++11 起)。
应用到指针时,下标表达式始终是左值。
不允许类型 T
为不完整类型,即使决不使用 T
的大小或内部结构,如在 &x[0] 中。
在对于用户定义运算符的重载决议中,对于每个对象类型 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; // 能绑定函数左值到引用 }
[编辑] 内建的取地址运算符
取址运算符表达式拥有形式
& expr
|
(1) | ||||||||
& class :: member
|
(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); // 由于转型进行重载决议 }
[编辑] 内建的成员访问运算符
成员访问运算符表达式拥有形式
expr . template (可选) id-expr
|
(1) | ||||||||
expr -> template (可选) id-expr
|
(2) | ||||||||
expr . pseudo-destructor
|
(3) | ||||||||
expr -> pseudo-destructor
|
(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 指定符,可选地有限定时,其结果为一种特殊的纯右值,它只能用作函数调用运算符的左运算数,而不能用于其他目的。所构成的函数调用表达式被称为伪析构函数调用。它不接受任何参数,返回 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
[编辑] 内建的成员指针访问运算符
通过指针的成员访问表达式拥有形式
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 的公开成员函数)
| |
提供到被管理数组的有索引访问 ( std::unique_ptr 的公开成员函数)
| |
访问指定字符 ( std::basic_string 的公开成员函数)
| |
访问指定的元素 ( std::array 的公开成员函数)
| |
访问指定的元素 ( std::deque 的公开成员函数)
| |
访问指定的元素 ( std::vector 的公开成员函数)
| |
访问或插入指定的元素 ( std::map 的公开成员函数)
| |
访问或插入指定的元素 ( std::unordered_map 的公开成员函数)
| |
按索引访问元素 ( std::reverse_iterator 的公开成员函数)
| |
按索引访问元素 ( std::move_iterator 的公开成员函数)
| |
获取/设置 valarray 数组元素、切片或掩码 ( std::valarray 的公开成员函数)
| |
返回指定的子匹配 ( std::match_results 的公开成员函数)
|
许多迭代器和智能指针类都重载了间接寻址和成员运算符
解引用指向被管理对象的指针 ( std::unique_ptr 的公开成员函数)
| |
解引用存储的指针 ( std::shared_ptr 的公开成员函数)
| |
访问被管理对象 ( std::auto_ptr 的公开成员函数)
| |
解引用迭代器 ( std::raw_storage_iterator 的公开成员函数)
| |
对递减后的底层迭代器进行解引用 ( std::reverse_iterator 的公开成员函数)
| |
空操作 ( std::back_insert_iterator 的公开成员函数)
| |
空操作 ( std::front_insert_iterator 的公开成员函数)
| |
空操作 ( std::insert_iterator 的公开成员函数)
| |
访问指向的元素 ( std::move_iterator 的公开成员函数)
| |
返回当前元素 ( std::istream_iterator 的公开成员函数)
| |
无操作 ( std::ostream_iterator 的公开成员函数)
| |
(C++11 起)(C++17 前) |
获得当前字符的副本 若 CharT 拥有成员,则访问当前字符的成员 ( std::istreambuf_iterator 的公开成员函数)
|
无操作 ( std::ostreambuf_iterator 的公开成员函数)
| |
访问当前匹配 ( std::regex_iterator 的公开成员函数)
| |
访问当前子匹配 ( std::regex_token_iterator 的公开成员函数)
|
标准库中的类都没有重载 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 转换一个类型为另一相关类型 |
成员访问运算符 的 C 文档
|