成员访问运算符
访问其操作数的成员。
运算符名称 | 语法 | 可重载 | 原型示例(对于 类 T) | |
---|---|---|---|---|
在类定义内 | 在类定义外 | |||
下标 | a[b] | 是 | R& T::operator[](S b); | 不适用 |
a[...] (自 C++23 起) | R& T::operator[](...); | |||
间接寻址 | *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); |
|
目录 |
[编辑] 说明
内置的*下标*运算符提供对 指针 或 数组 操作数指向的对象的访问。
内置的*间接寻址*运算符提供对指针操作数指向的对象或函数的访问。
内置的*取地址*运算符创建一个指向对象或函数操作数的指针。
*对象的成员*和*指向对象成员的指针*运算符提供对对象操作数的数据成员或成员函数的访问。
内置的*指针的成员*和*指向指针成员的指针*运算符提供对指针操作数指向的类的成员或成员函数的访问。
[编辑] 内置下标运算符
下标运算符表达式的形式为
expr1 [ expr2 ] |
(1) | ||||||||
expr1 [{ expr , ... }] |
(2) | (自 C++11 起) | |||||||
expr1 [ expr2 , expr , ... ] |
(3) | (自 C++23 起) | |||||||
T
数组”类型的左值或“指向 T
的指针”类型的纯右值,而另一个表达式(分别为 expr2 或 expr1)必须是无作用域枚举或整型的纯右值。此表达式的结果类型为 T
。 expr2 不能是非括号括起来的 逗号表达式。(自 C++23 起)内置下标表达式 E1[E2] 与表达式 *(E1 + E2) 完全相同,除了其值类别(见下文) 和 求值顺序(自 C++17 起):指针操作数(可能是数组到指针转换的结果,并且必须指向某个数组的元素或末尾之后的一个位置)被调整为指向同一数组的另一个元素,遵循 指针算术 的规则,然后被解引用。
当应用于数组时,如果数组是左值,则下标表达式是 左值;如果不是,则是 临终值(自 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"; // uses the [{...}] version }
输出
4242
[编辑] 内置间接寻址运算符
间接寻址运算符表达式的形式为
* expr |
|||||||||
内置间接寻址运算符的操作数必须是指向对象的指针或指向函数的指针,结果是引用 expr 所指向的对象或函数的左值。如果 expr 实际上没有指向对象或函数,则行为未定义(typeid
指定的情况除外)。
不能解引用指向(可能具有 cv 限定符的)void 的指针。可以解引用指向其他不完整类型的指针,但结果左值只能在允许使用不完整类型左值的上下文中使用,例如初始化引用时。
在针对用户定义运算符的 重载决议 中,对于每个对象类型 T
(可能具有 cv 限定符)或函数类型(没有 const 或引用限定符),以下函数签名参与重载决议
T& operator*(T*); |
||
#include <iostream> int f() { return 42; } int main() { int n = 1; int* pn = &n; int& r = *pn; // lvalue can be bound to a reference int m = *pn; // indirection + lvalue-to-rvalue conversion int (*fp)() = &f; int (&fr)() = *fp; // function lvalue can be bound to a reference [](...){}(r, m, fr); // removes possible "unused variable" warnings }
[编辑] 内置取地址运算符
取地址运算符表达式的形式为
& expr |
(1) | ||||||||
& class :: member |
(2) | ||||||||
T
的左值表达式,则 operator&
会创建一个类型为 T*
的右值,并返回该右值,该右值具有相同的 cv 限定符,并指向操作数指定的对象或函数。如果操作数具有不完整类型,则可以形成指针,但如果该不完整类型恰好是定义了自己的 operator& 的类,则使用内置运算符还是重载运算符是未指定的。对于具有用户定义的 operator& 的类型的操作数,可以使用 std::addressof 来获取真正的指针。请注意,与 C99 及更高版本的 C 不同,对于应用于一元 operator* 结果的一元 operator&,没有特殊情况。
如果 expr 命名了一个 显式对象成员函数,则 expr 必须是一个 限定标识符。将 |
(自 C++23 起) |
T
的右值 指向成员函数的指针 或 指向数据成员的指针,该指针位于类 C
中。请注意,&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; // pointer int* pn2 = &*pn; // pn2 == pn int A::* mp = &A::i; // pointer to data member void (B::*mpf)() = &B::f; // pointer to member function void (*pf)(int) = &f; // overload resolution due to initialization context // auto pf2 = &f; // error: ambiguous overloaded function type auto pf2 = static_cast<void (*)(int)>(&f); // overload resolution due to cast }
[编辑] 内置成员访问运算符
成员访问运算符表达式的形式为
expr .template (可选) id-expr |
(1) | ||||||||
expr ->template (可选) id-expr |
(2) | ||||||||
expr . pseudo-destructor |
(3) | ||||||||
expr -> pseudo-destructor |
(4) | ||||||||
T*
的指针表达式。id-expr 是数据成员或成员函数的名称(形式上,是命名数据成员或成员函数的标识符表达式) 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)xvalue(可以从 prvalue物化)(C++17 起)),则结果是右值(截至 C++11)xvalue(C++11 起),表示 E1 的该非静态数据成员。
T
,则结果是右值(截至 C++11)纯右值(C++11 起),类型为 T
,其值为枚举器的值。~
后跟类型名称或decltype 说明符,表示相同的类型(减去 cv 限定),可以选择限定,则结果是一种特殊的纯右值,只能用作函数调用运算符的左操作数,而不能用于其他目的operator. 不能被重载,并且对于 operator->,在针对用户定义运算符的重载解析中,内置运算符不会引入任何其他函数签名:如果存在作为可行函数的重载 operator->,则内置 operator-> 不适用。
#include <cassert> #include <iostream> #include <memory> 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; // keyword template needed for a dependent template member int* p = T().template ptr<U>(); p->~U(); // U is int, calls int's pseudo destructor 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 also works << a.f() << ' ' << a.sf() << ' ' // A::sf() also works // << &a.f << ' ' // error: ill-formed if a.f is not the // left-hand operand of operator() // << a.B << ' ' // error: nested type not allowed << a.RED << ' '; // enumerator UPtrWrapper uPtrWrap{std::make_unique<std::string>("wrapped")}; assert(uPtrWrap->data() == uPtrWrap.operator->().operator->()->data()); }
输出
1 2 11 4 1
如果 E2 是非静态成员,并且 E1 的结果是一个类型与 E1 的类型不相似的对象,则行为未定义。
struct A { int i; }; struct B { int j; }; struct D : A, B {}; void f() { D d; static_cast<B&>(d).j; // OK, object expression designates the B subobject of d reinterpret_cast<B&>(d).j; // undefined behavior }
[编辑] 内置指向成员的访问运算符
通过指向成员的指针进行成员访问的运算符表达式具有以下形式
lhs .* rhs |
(1) | ||||||||
lhs ->* rhs |
(2) | ||||||||
T
的表达式。T*
的指针类型的表达式。rhs 必须是指向 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; // error: cannot modify through mutable std::cout << s.*pmi << '\n'; D d(7); // base pointers work with derived object 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>` 的公共成员函数) | |
(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
,尽管它也可能出现在 EDSLs 中,例如 boost.spirit。
没有标准库类重载 operator->*。有人建议它可以作为 智能指针接口 的一部分,并且实际上在 boost.phoenix 中的 actors 中以这种身份使用,但在 EDSLs 中更常见,例如 cpp.react。
[编辑] 注释
特性测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__cpp_multidimensional_subscript |
202110L | (C++23) | 多维下标运算符 |
[编辑] 缺陷报告
以下行为变更缺陷报告已追溯应用于先前发布的 C++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确行为 |
---|---|---|---|
CWG 1213 | C++11 | 对数组右值使用下标运算符会导致左值 | 重新分类为右值 |
CWG 1458 | C++98 | 对声明了 operator& 的不完整类类型的左值应用 & 会导致未定义行为 |
未指明 使用哪个 & |
CWG 1642 | C++98 | 内置成员指针访问运算符中的 rhs 可以是左值 | 只能是右值 |
CWG 1800 | C++98 | 当对 成员匿名联合体的非静态数据成员应用 & 时,不清楚匿名联合体是否参与结果类型 |
匿名联合体 不包含在 结果类型中 |
CWG 2614 | C++98 | 如果 E2 是引用成员或枚举器,则 E1.E2 的结果不明确 | 已明确 |
CWG 2725 | C++98 | 如果 E2 是静态成员函数,则 E1.E2 是良构的 即使它不是 operator() 的左操作数 |
E1.E2 是非良构的 在这种情况下 |
CWG 2748 | C++98 | 如果 E1 是 空指针且 E2 引用静态成员,则 E1->E2 的行为不明确 |
在这种情况下,行为是 未定义的 |
CWG 2813 | C++98 | 如果 E1.E2 命名静态成员或枚举器,则 E1 不是弃值表达式 |
它是 |
CWG 2823 | C++98 | 如果 expr 不指向对象或函数,则 *expr 的行为不明确 |
已明确 |
[编辑] 另请参阅
常用运算符 | ||||||
---|---|---|---|---|---|---|
赋值 | 递增 递减 |
算术 | 逻辑 | 比较 | 成员 访问 |
其他 |
a = b |
++a |
+a |
!a |
a == b |
a[...] |
函数调用 |
a(...) | ||||||
逗号 | ||||||
a, b | ||||||
条件 | ||||||
a ? b : c | ||||||
特殊运算符 | ||||||
static_cast 将一种类型转换为另一种相关类型 |
C 文档 中的 成员访问运算符
|