赋值运算符

来自cppreference.com
< cpp‎ | language


 
 
C++ 语言
 
 

赋值运算符修改对象的值。

运算符名 语法 可重载 原型示例(对于 class T
类内定义 类外定义
简单赋值 a = b T& T::operator =(const T2& b); 不适用
加法赋值 a += b T& T::operator +=(const T2& b); T& operator +=(T& a, const T2& b);
减法赋值 a -= b T& T::operator -=(const T2& b); T& operator -=(T& a, const T2& b);
乘法赋值 a *= b T& T::operator *=(const T2& b); T& operator *=(T& a, const T2& b);
除法赋值 a /= b T& T::operator /=(const T2& b); T& operator /=(T& a, const T2& b);
取模赋值 a %= b T& T::operator %=(const T2& b); T& operator %=(T& a, const T2& b);
逐位与赋值 a &= b T& T::operator &=(const T2& b); T& operator &=(T& a, const T2& b);
逐位或赋值 a |= b T& T::operator |=(const T2& b); T& operator |=(T& a, const T2& b);
逐位异或赋值 a ^= b T& T::operator ^=(const T2& b); T& operator ^=(T& a, const T2& b);
逐位左移赋值 a <<= b T& T::operator <<=(const T2& b); T& operator <<=(T& a, const T2& b);
逐位右移赋值 a >>= b T& T::operator >>=(const T2& b); T& operator >>=(T& a, const T2& b);
注意
  • 所有内建赋值运算符都返回 *this,而大多数用户定义重载也会返回 *this,从而能以与内建版本相同的方式使用用户定义运算符。然而,用户定义运算符重载中,返回类型可以是任意类型(包括 void)。
  • T2 可以是包含 T 在内的任何类型。

定义

复制赋值 以 b 内容的副本替换对象 a 的内容(不修改 b)。对于类类型,这会在一种特殊成员函数中进行,描述见复制赋值运算符

移动赋值 以 b 的内容替换对象 a 的内容,并尽可能避免复制(可以修改 b)。对于类类型,这会在一种特殊成员函数中进行,描述见移动赋值运算符

(C++11 起)

对于非类类型,对复制与移动赋值不加以区分,它们都被称作直接赋值

复合赋值 以 a 的值和 b 的值间的二元运算结果替换对象 a 的内容。

赋值运算符语法

赋值表达式的形式为

目标表达式 = 新值 (1)
目标表达式 运算符 新值 (2)
目标表达式 - 要被赋值的表达式[1]
运算符 - *=/=%=+=-=<<=>>=&=^=|= 之一
新值 - 要赋值给目标的表达式[2] (C++11 前)初始化器子句 (C++11 起)
  1. 目标表达式 的优先级必须高于赋值表达式的优先级。
  2. 新值 不能是逗号表达式,因为它的优先级更低。
1) 简单赋值表达式
2) 复合赋值表达式

如果新值 不是表达式,那么赋值表达式不会匹配重载的复合赋值运算符。

(C++11 起)

内建的简单赋值运算符

对于内建的简单赋值,目标表达式 指代的对象会通过以新值 的结果进行覆盖而被修改。目标表达式 必须是可修改的左值。

内建的简单赋值是类型与目标表达式 相同,并且指代目标表达式 的左值。如果目标表达式 是位域,那么结果也是位域。

从表达式赋值

如果新值 是表达式,那么它会隐式转换目标表达式 具有的类型的无 cv 限定版本。在目标表达式 位域无法表示该表达式的值的情况下,位域结果值由实现定义。

如果左右操作数标识的对象之间有重叠,那么行为未定义(除非二者严格重叠且类型相同)。

如果目标表达式 的类型具有 volatile 限定,那么该赋值被弃用,除非该(可被圆括号包围的)赋值表达式是弃值表达式不求值操作数

(C++20 起)


从非表达式初始化器子句赋值

只有在以下情况下,新值 才可以不是表达式:

  • 目标表达式 具有标量类型 T,并且新值 为空或者只有一个元素。此时给定以 T t = 新值  声明和初始化的虚设变量 t,那么 x = 新值  等价于 x = t
  • 目标表达式 具有类类型。此时新值 会作为实参传递给重载决议选择的赋值运算符函数。
#include <complex>
 
std::complex<double> z;
z = {1, 2};  // 表示 z.operator=({1, 2})
z += {1, 2}; // 表示 z.operator+=({1, 2})
 
int a, b;
a = b = {1}; // 表示 a = b = 1;
a = {1} = b; // 语法错误
(C++11 起)

针对用户定义运算符的重载决议中,对于每个类型 T,下列函数签名参与重载决议:

T*& operator=(T*&, T*);
T*volatile & operator=(T*volatile &, T*);

对于每个枚举或成员指针类型 T(可有 volatile 限定),下列函数签名参与重载决议:

T& operator=(T&, T );

对于每对 A1A2,其中 A1 是算术类型(可有 volatile 限定)而 A2 是提升后的算术类型,下列函数签名参与重载决议:

A1& operator=(A1&, A2);

内建的复合赋值运算符

每个内建复合赋值运算符表达式 目标表达式 运算符 = 新值 的行为和表达式 目标表达式 = 目标表达式 运算符 新值 完全一致,除了目标表达式 只会求值一次。

内建的简单赋值运算符对目标表达式 和新值 的要求依然使用。另外:

  • 对于 +=-=目标表达式 的类型必须是算术类型或指向(可有 cv 限定的)完整定义的对象类型的指针。
  • 对于所有其他复合赋值运算符,目标表达式 的类型必须是算术类型。

针对用户定义运算符的重载决议中,对每对 A1A2,其中 A1 是算术类型(可有 volatile 限定)而 A2 是提升后的算术类型,下列函数签名参与重载决议:

A1& operator*=(A1&, A2);
A1& operator/=(A1&, A2);
A1& operator+=(A1&, A2);
A1& operator-=(A1&, A2);

对于每对 I1I2,其中 I1 是整型类型(可有 volatile 限定)而 I2 是提升后的整型类型,下列函数签名参与重载决议:

I1& operator%=(I1&, I2);
I1& operator<<=(I1&, I2);
I1& operator>>=(I1&, I2);
I1& operator&=(I1&, I2);
I1& operator^=(I1&, I2);
I1& operator|=(I1&, I2);

对于每个可有 cv 限定的对象类型 T,下列函数签名参与重载决议:

T*& operator+=(T*&, std::ptrdiff_t);
T*& operator-=(T*&, std::ptrdiff_t);
T*volatile & operator+=(T*volatile &, std::ptrdiff_t);
T*volatile & operator-=(T*volatile &, std::ptrdiff_t);

示例

#include <iostream>
 
int main()
{
    int n = 0;        // 不是赋值
 
    n = 1;            // 直接赋值
    std::cout << n << ' ';
 
    n = {};           // 零初始化,然后赋值
    std::cout << n << ' ';
 
    n = 'a';          // 整型提升,然后赋值
    std::cout << n << ' ';
 
    n = {'b'};        // 显式转型,然后赋值
    std::cout << n << ' ';
 
    n = 1.0;          // 浮点转换,然后赋值
    std::cout << n << ' ';
 
//  n = {1.0};        // 编译错误(窄化转换)
 
    int& r = n;       // 不是赋值
    r = 2;            // 通过引用赋值
    std::cout << n << ' ';
 
    int* p;
    p = &n;           // 直接赋值
    p = nullptr;      // 空指针转换,然后赋值
    std::cout << p << ' ';
 
    struct { int a; std::string s; } obj;
    obj = {1, "abc"}; // 从花括号初始化器列表赋值
    std::cout << obj.a << ':' << obj.s << '\n';
}

可能的输出:

1 0 97 98 1 2 (nil) 1:abc

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 1527 C++11 只有在赋值由用户定义的赋值运算符定义的情况下才能将初始化列表赋给类类型对象 移除用户定义赋值限制
CWG 1538 C++11 =E1 = {E2} 曾与 =E1 = T(E2)TE1 的类型)等价,这会引入 C 风格转型 现在与 E1 = T{E2} 等价
CWG 2654 C++20 volatile 类型的复合赋值运算符被以不一致的方式弃用 它们都不会被弃用
CWG 2768 C++11 从非表达式初始化器子句对标量值进行赋值会进行直接列表初始化 改为进行复制列表初始化
P2327R1 C++20 volatile 类型的逐位复合赋值运算符对一些平台有用但被弃用 不弃用它们

参阅

运算符优先级

运算符重载

常见运算符
赋值 自增/自减 算术 逻辑 比较 成员访问 其他

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 起)