成员访问运算符

来自cppreference.com
< cpp‎ | language
 
 
 
表达式
概述
值类别(左值 lvalue、右值 rvalue、亡值 xvalue)
求值顺序(序列点)
常量表达式
潜在求值表达式
初等表达式
lambda 表达式(C++11)
字面量
整数字面量
浮点字面量
布尔字面量
字符字面量,包含转义序列
字符串字面量
空指针字面量(C++11)
用户定义字面量(C++11)
运算符
赋值运算符a=ba+=ba-=ba*=ba/=ba%=ba&=ba|=ba^=ba<<=ba>>=b
自增与自减++a--aa++a--
算术运算符+a-aa+ba-ba*ba/ba%b~aa&ba|ba^ba<<ba>>b
逻辑运算符a||ba&&b!a
比较运算符a==ba!=ba<ba>ba<=ba>=ba<=>b(C++20)
成员访问运算符a[b]*a&aa->ba.ba->*ba.*b
其他运算符a(...)a,ba?b:c
new 表达式
delete 表达式
throw 表达式
alignof
sizeof
sizeof...(C++11)
typeid
noexcept(C++11)
折叠表达式(C++17)
运算符的代用表示
优先级和结合性
运算符重载
默认比较(C++20)
类型转换
隐式转换
一般算术转换
const_cast
static_cast
reinterpret_cast
dynamic_cast
显式转换 (T)a, T(a)
用户定义转换
 

访问操作数的一个成员。

运算符名 语法 可重载 原型示例(对于 class T
类定义内 类定义外
下标 a[b]

a[...]

(C++23 起)
R& T::operator[](S b);

R& T::operator[](S1 s1, ...);

(C++23 起)
不适用
间接寻址 *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);
注解
  • 与大多数用户定义的重载相同,返回类型应当与内建运算符所提供的返回类型相匹配,以便用户定义的运算符可以和内建运算符以相同的方式使用。不过,在用户定义的运算符重载中,任何类型都可以作为它的返回类型(包括 void)。一个例外是 operator->,它必须返回一个指针或者另一个带有重载的 operator-> 的类以使它真正可用。

解释

内建的下标(subscript)运算符提供对它的指针数组操作数所指向的对象的访问。

内建的间接寻址(indirection)运算符提供对它的指针操作数所指向的对象或函数的访问。

内建的取地址(address of)运算符创建指向它的对象或函数操作数的指针。

对象的成员 和指向对象的成员的指针 运算符提供对它的对象操作数的数据成员或成员函数的访问。

内建的指针的成员'  和指向指针的成员的指针 运算符提供对它的指针操作数所指向的类的数据成员或成员函数的访问。

内建的下标运算符

下标运算符表达式的形式为

表达式1 [表达式2 ] (1)
表达式1 [{表达式 , ...}] (2) (C++11 起)
表达式1 [表达式2 , 表达式 , ...] (3) (C++23 起)
1) 对于内建运算符,表达式之一(表达式1表达式2)必须是“T 的数组”类型的泛左值或“T 的指针”类型的纯右值,而另一表达式(分别是 表达式2表达式1)必须是无作用域枚举或整型类型的纯右值。此表达式的结果类型是 T表达式2 不能是无括号的逗号表达式 (C++23 起)
2) 方括号中有花括号环绕列表的形式只能用于调用重载的 operator[]
3) 方括号中有逗号分隔的表达式列表的形式只能用于调用重载的 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>
#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)
1) 当操作数是某个对象或函数类型 T 的左值表达式时,operator& 创建并返回一个有相同 cv 限定的 T* 类型的纯右值,并指向由该操作数所代表的对象或函数。当它的操作数具有不完整类型时,可以构成指针,但如果该不完整类型恰好是某个定义了它自身的 operator& 的类,那么使用内建还是重载运算符是未指明的。对于类型带有用户定义的 operator& 的操作数,可以使用 std::addressof 来获取真正的指针。注意,这和 C99 以及之后的 C 语言版本不同,不存在对一元 operator* 运算符的运算结果应用一元 operator& 运算符的特殊情况。
当它的操作数是重载函数的名字时,仅当根据它的语境可以解析这个重载时才能取得它的地址。详细说明参见重载函数的地址

如果表达式 命名了显式对象成员函数,那么表达式 必须是有限定标识符。对命名了显式对象成员函数的无限定标识符应用 & 非良构。

(C++23 起)
2) 当它的操作数是显式对象成员函数以外的 (C++23 起)非静态或变体成员的限定名(比如 &C::member)时,它的结果是 C 类中的 T 类型的成员函数指针数据成员指针的纯右值。注意,&memberC::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)
1) 第一操作数必须是完整类类型 T 的表达式。
2) 第一操作数必须是指向完整类类型的指针 T* 的表达式。
3,4) 第一操作数必须是标量类型表达式(见下文)。

两种运算符的第一个操作数都被求值,即便它并不是必须的(比如它的第二个操作数指名的是静态成员)。

两个运算符的第二个操作数是 TT 的某个无歧义且可访问的基类 B 的数据成员或成员函数的名字(正式的说法是标识表达式)(如 E1.E2E1->E2),并可选地有限定(如 E1.B::E2E1->B::E2),并可以使用 template 歧义消解符(如 E1.template E2E1->template E2)。

如果提供的是用户定义的 operator->,那么递归地对它的所返回的值再次调用 operator->,直到到达返回普通指针的 operator-> 为止。之后再对这个指针采用内建语义。

对于内建类型,表达式 E1->E2(*E1).E2 严格等价;因此以下规则只处理 E1.E2 的情形。

在表达式 E1.E2 中:

1)E2静态数据成员时:
  • 如果 E2 具有引用类型 T& T&& (C++11 起),那么它的结果是 T 类型的左值,代表该引用绑定到的对象或函数,
  • 否则,给定 E2 的类型为 T,它的结果是 T 类型的代表该静态数据成员的左值。
基本上,这两种情况下 E1 均被求值,随即被丢弃;
2)E2非静态数据成员时:
  • 如果 E2 具有引用类型 T& T&& (C++11 起),那么它的结果是 T 类型的左值,代表 E1 的对应引用成员绑定到的对象或函数,
  • 否则,如果 E1 是左值,那么它的结果是代表 E1 的这个非静态数据成员的左值,
  • 否则(E1右值 (C++17 前)亡值(可能是从纯右值实质化而来) (C++17 起)),它的结果是代表 E1 的这个非静态数据成员的右值 (C++11 前)亡值 (C++11 起)
如果 E2 不是 mutable 成员,那么结果的 cv 限定性E1E2 的 cv 限定性的合并,否则(E2 是 mutable 成员)是 E1E2 的 volatile 限定性的合并;
3)E2静态成员函数时,它的结果是代表该静态成员函数的左值。基本上,这种情况下 E1 被求值,随即被丢弃;
4)E2 是(包括析构函数在内的)非静态成员函数时,它的结果是代表 E1 的这个非静态成员函数的一种特殊的纯右值,它只能用作成员函数调用运算符的左操作数,而不能用于其他目的;
5)E2 是成员枚举项时,给定 E2 的类型为 T,它的结果是 T 类型的右值 (C++11 前)纯右值 (C++11 起),该结果的值是该枚举项的值;
6)E2嵌套类型时,程序非良构(无法编译);
7)E1标量类型 (ScalarType) 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;
 
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)
1) 左操作数 必须是类类型 T 的表达式。
2) 左操作数 必须是指向类类型指针 T* 的表达式。

两个运算符的第二操作数都是类型是指向 T 的(数据函数)成员指针类型,或者是指向 T 的无歧义且可访问基类 B 成员指针类型的表达式。

对于内建类型,表达式 E1->*E2 严格等价于 (*E1).*E2;因此以下规则只处理了 E1.*E2 的情形。

在表达式 E1.*E2 中:

1)E2 是指向数据成员的指针时,
  • 如果 E1 是左值,那么它的结果是代表这个成员的左值,
  • 否则(E1右值 (C++17 前)亡值(可能是从纯右值实质化而来) (C++17 起)),它的结果是代表这个数据成员的右值 (C++11 前)亡值 (C++11 起)
2)E2 是指向成员函数的指针时,它的结果是代表这个成员函数的一种特殊的纯右值,它只能用作成员函数调用运算符的左运算数,而不能用于其他目的;
3) cv 限定性的规则与对象的成员运算符相同,但有一条额外规则:指代 mutable 成员的成员指针不能用于改动 const 对象中的这个成员;
4)E2 是空成员指针值时,行为未定义;
5)E1动态类型并不包含 E2 所指代的成员时,行为未定义;
6)E1 是右值而 E2 指向带有引用限定符 & 的成员函数时,程序非良构,除非该成员函数的 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> 的公开成员函数)
访问指定的元素
(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> 的公开成员函数)
(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 += b
a -= b
a *= b
a /= b
a %= b
a &= b
a |= b
a ^= b
a <<= b
a >>= b

++a
--a
a++
a--

+a
-a
a + b
a - b
a * b
a / b
a % b
~a
a & b
a | b
a ^ b
a << b
a >> b

!a
a && b
a || b

a == b
a != b
a < b
a > b
a <= b
a >= b
a <=> b

a[b]
*a
&a
a->b
a.b
a->*b
a.*b

函数调用
a(...)
逗号
a, b
条件
a ? b : c
特殊运算符

static_cast 转换一个类型为另一相关类型
dynamic_cast 在继承层级中转换
const_cast 添加或移除 cv 限定符
reinterpret_cast 转换类型到无关类型
C 风格转型static_castconst_castreinterpret_cast 的混合转换一个类型到另一类型
new 创建有动态存储期的对象
delete 销毁先前由 new 表达式创建的对象,并释放其所拥有的内存区域
sizeof 查询类型的大小
sizeof... 查询形参包的大小(C++11 起)
typeid 查询类型的类型信息
noexcept 查询表达式是否能抛出异常(C++11 起)
alignof 查询类型的对齐要求(C++11 起)