Member access operators
访问其操作数的成员。
| 运算符名称 | 语法 | 可重载 | 原型示例(对于 class 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
*
)
;
|
||
标签内的C++代码
- 未翻译C++专业术语(如operator、ptrdiff_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
内置间接寻址运算符
间接运算符表达式的形式为
*
表达式
|
|||||||||
内置间接操作符的操作数必须是指向对象的指针或指向函数的指针,其结果是引用
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; // 左值可以绑定到引用 int m = *pn; // 间接寻址 + 左值到右值转换 int (*fp)() = &f; int (&fr)() = *fp; // 函数左值可以绑定到引用 [](...){}(r, m, fr); // 消除可能的“未使用变量”警告 }
内置取址运算符
取址运算符表达式的形式为
&
表达式
|
(1) | ||||||||
&
类名
::
成员名
|
(2) | ||||||||
T
的左值表达式,
operator&
会创建并返回一个具有相同 cv 限定符的
T*
类型纯右值,该指针指向操作数所指定的对象或函数。若操作数具有不完整类型,虽然可以形成指针,但如果该不完整类型恰好是定义了自身
operator
&
的类,则未指定使用内置运算符还是重载运算符。对于具有用户定义
operator
&
类型的操作数,可使用
std::addressof
来获取真实指针。
注意,与 C99 及后续 C 版本不同,一元
operator
&
应用于一元
operator
*
结果时不存在特殊情况。
|
若
expr
指名一个
显式对象成员函数
,则
expr
必须是
限定标识符
。对命名显式对象成员函数的非限定标识符应用
|
(since 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*
的指针表达式。
id-expr
是(形式上指一个
标识符表达式
,其命名了)
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 起) ),则结果为 右值 (C++11 前) 亡值 (C++11 起) ,指代 E1 该非静态数据成员。
T
,则结果为类型
T
的
右值
(C++11 前)
纯右值
(C++11 起)
,其值为该枚举项的值。
~
,且该类型与
E1
类型(忽略 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; // 依赖模板成员需要 template 关键字 int* p = T().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.f << ' ' // 错误:如果 a.f 不是 operator() 的左操作数则不合法 // // << a.B << ' ' // 错误:不允许嵌套类型 << a.RED << ' '; // 枚举项 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; // 正确:对象表达式指向 d 的 B 子对象 reinterpret_cast<B&>(d).j; // 未定义行为 }
内建成员指针访问运算符
指向成员的成员访问运算符表达式具有以下形式
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 起) ;
&
的成员函数,则程序非良构
除非该成员函数具有
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); // 基类指针可用于派生类对象 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>
的公开成员函数)
|
|
|
访问指向的元素
(
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
,不过它也可能出现在 EDSL 中,例如
boost.spirit
。
标准库类没有重载 operator - > * 。有建议认为它可以作为 智能指针接口 的组成部分,实际上在 boost.phoenix 中的执行器就采用了这种用法,但在诸如 cpp.react 这类 EDSL 中更为常见。
注释
| 功能测试宏 | 值 | 标准 | 功能 |
|---|---|---|---|
__cpp_multidimensional_subscript
|
202110L
|
(C++23) | 多维下标运算符 |
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 1213 | C++11 | 对数组右值进行下标操作会产生左值 | 重新分类为亡值 |
| CWG 1458 | C++98 |
对声明了
operator&
的不完整类类型的左值
应用
&
会导致未定义行为
|
未指定使用哪个
&
运算符
|
| CWG 1642 | C++98 | 内置指针到成员访问运算符的 右操作数 可以是左值 | 只能是右值 |
| CWG 1800 | C++98 |
对成员匿名联合体的非静态数据成员应用
&
时,
不清楚匿名联合体是否参与结果类型 |
匿名联合体
不包含在 结果类型中 |
| CWG 2614 | C++98 |
当
E2
是引用成员或枚举器时,
E1.E2 的结果不明确 |
已明确说明 |
| CWG 2725 | C++98 |
如果
E2
是静态成员函数,即使不在
operator()
的左操作数位置, E1.E2 也是良构的 |
此情况下
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 文档
关于
成员访问运算符
|