Namespaces
Variants

Member access operators

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

访问其操作数的成员。

运算符名称 语法 可重载 原型示例(对于 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 ) ;
注释
  • 与大多数用户定义的重载一样,返回类型应与内置运算符提供的返回类型匹配,以便 用户定义运算符 可以像内置运算符一样使用。但是,在用户定义的运算符重载中,可以使用任何类型作为返回类型(包括 void )。一个例外是 operator - > ,它必须返回指针或另一个重载了 operator - > 的类才能实际使用。

目录

说明

内置的 下标 运算符提供对由 指针 数组 操作数所指向对象的访问。

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

内置的 取址 运算符创建一个指向对象或函数操作数的指针。

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

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

内置下标运算符

下标运算符表达式的形式

expr1  [ expr2  ] (1)
expr1  [{ expr  , ... }] (2) (C++11 起)
expr1  [ expr2  , expr  , ... ] (3) (C++23 起)
1) 对于内置运算符,其中一个表达式( expr1 expr2 )必须是“ T 的数组”类型的泛左值或“指向 T 的指针”类型的纯右值,而另一个表达式(分别为 expr2 expr1 )必须是无作用域枚举或整数类型的纯右值。此表达式的结果具有 T 类型。 expr2 不能是未加括号的 逗号表达式 (C++23 起)
2) 方括号内使用花括号包围列表的形式仅用于调用重载的 operator [ ]
3) 方括号内使用逗号分隔表达式列表的形式仅用于调用重载的 operator [ ]

内置的下标表达式 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 * ) ;
说明: - 保留了所有HTML标签和属性 - 未翻译 标签内的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)
1) 若操作数是某对象或函数类型 T 的左值表达式, operator& 会创建并返回一个具有相同 cv 限定符的 T* 类型纯右值,该指针指向操作数所指定的对象或函数。若操作数具有不完整类型,虽然可以形成指针,但如果该不完整类型恰好是定义了自身 operator & 的类,则未指定使用内置运算符还是重载运算符。对于具有用户定义 operator & 类型的操作数,可使用 std::addressof 来获取真实指针。 注意,与 C99 及后续 C 版本不同,一元 operator & 应用于一元 operator * 结果时不存在特殊情况。
若操作数是重载函数的名称,则仅当能通过上下文解析重载时才能获取其地址。详见 重载函数地址

expr 指名一个 显式对象成员函数 ,则 expr 必须是 限定标识符 。对命名显式对象成员函数的非限定标识符应用 & 运算符属于病式结构。

(since C++23)
2) 若操作数是非静态或 变体成员 的限定名称 (除 显式对象成员函数 外) (C++23 起) ,例如 & C :: member ,则结果为指向类 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)
1) expr 必须是完整类类型 T 的表达式。
id-expr 命名的是 静态成员 枚举项 ,则 expr 弃值表达式
2) expr 必须是指向完整类类型 T* 的指针表达式。
3,4) expr 必须是标量类型的表达式(见下文)。

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 中:

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 起) ),则结果为 右值 (C++11 前) 亡值 (C++11 起) ,指代 E1 该非静态数据成员。
E2 不是 mutable 成员,则结果的 cv-qualification E1 E2 的 cv 限定符的并集;否则(若 E2 是 mutable 成员),它是 E1 E2 的 volatile 限定符的并集。
3) E2 是重载集(包含一个或多个 静态成员函数 非静态成员函数 ),则 E1. E2 必须是 成员函数调用运算符 的(可能带括号的)左操作数,且通过 函数重载决议 选择 E2 所指代的函数,随后:
  • E2 静态成员函数 ,则结果为指代该静态成员函数的左值。本质上,此情况下 E1 会被求值并丢弃。
  • 否则( E2 非静态成员函数 ),结果为指代 E1 中该非静态成员函数的纯右值。
4) E2 是成员枚举项,给定 E2 的类型为 T ,则结果为类型 T 右值 (C++11 前) 纯右值 (C++11 起) ,其值为该枚举项的值。
5) 如果 E2 嵌套类型 ,则程序非良构。
6) E1 具有 ScalarType ,且 E2 为后接 类型名称 decltype 说明符 (限定符可省略)的 ~ ,且该类型与 E1 类型(忽略 cv 限定符)相同,则结果为一种特殊类型的纯右值,该值仅可用作函数调用运算符的左操作数,不可用于其他用途
最终生成的函数调用表达式被称为 伪析构函数调用 。该表达式不接收参数,返回 void 类型,对 E1 进行求值,并终止其结果对象的生命周期。这是 operator. 的左操作数具有非类类型的唯一情形。允许伪析构函数调用使得我们能够编写代码而无需知晓特定类型是否存在析构函数。

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)
1) lhs 必须是类类型 T 的表达式。
2) lhs 必须是一个指向类类型 T* 的指针表达式。

rhs 必须是类型为指向 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限定规则与对象成员操作符相同,但增加一条额外规则:指向可变成员的成员指针不能用于修改常量对象中的该成员;
4) E2 为空成员指针值,则行为未定义;
5) 若结果 E1 是类型与 E1 类型不 相似 的对象,或其 最终派生对象 不包含 E2 所引用的成员,则行为未定义;
6) E1 为右值且 E2 指向带有引用限定符 & 的成员函数,则程序非良构 除非该成员函数具有 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 + = 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 [ ... ]
* 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_cast const_cast reinterpret_cast 将一种类型转换为另一种类型
new 创建具有动态存储期的对象
delete 销毁先前由 new 表达式创建的对象并释放获取的内存区域
sizeof 查询类型的大小
sizeof... 查询 参数包 的大小 (自 C++11 起)
typeid 查询类型的类型信息
noexcept 检查表达式是否能抛出异常 (自 C++11 起)
alignof 查询类型的对齐要求 (自 C++11 起)

C 文档 关于 成员访问运算符