author: aofall, greyqz, Ir1d, Link-cute, Marcythm, ouuan, Shen-Linwood, sshwy, StudyingFather

## 算术运算符

| 运算符       | 功能  |
| --------- | --- |
|  `+` （单目） | 正   |
|  `-` （单目） | 负   |
|  `*` （双目） | 乘法  |
|  `/`      | 除法  |
|  `%`      | 取模  |
|  `+` （双目） | 加法  |
|  `-` （双目） | 减法  |

??? note "单目与双目运算符"
    单目运算符（又称一元运算符）指被操作对象只有一个的运算符，而双目运算符（又称二元运算符）的被操作对象有两个．例如 `1 + 2` 中加号就是双目运算符，它有 `1` 和 `2` 两个被操作数．此外 C++ 中还有唯一的一个三目运算符 `?:` ．

算术运算符中有两个单目运算符（正、负）以及五个双目运算符（乘法、除法、取模、加法、减法），其中单目运算符的优先级最高．

其中取模运算符 `%` 意为计算两个整数相除得到的余数，即求余数．

而 `-` 为双目运算符时做减法运算符，如 `2-1` ；为单目运算符时做负值运算符，如 `-1` ．

使用方法如下

 `op=x-y*z` 

得到的 `op` 的运算值遵循数学中加减乘除的优先规律，首先进行优先级高的运算，同优先级按运算的结合性运算，括号提高优先级．

### 算术运算中的类型转换

对于双目算术运算符，当参与运算的两个变量类型相同时，不发生 [类型转换](./var.md#类型转换)，运算结果将会用参与运算的变量的类型容纳，否则会发生类型转换，以使两个变量的类型一致．转换的规则参见 [类型转换](./var.md#类型转换)．

例如，对于一个整型（`int`）变量 $x$ 和另一个双精度浮点型（`double`）类型变量 $y$：

-  `x/3` 的结果将会是整型；
-  `x/3.0` 的结果将会是双精度浮点型；
-  `x/y` 的结果将会是双精度浮点型；
-  `x*1/3` 的结果将会是整型；
-  `x*1.0/3` 的结果将会是双精度浮点型；

## 位操作符

另请参阅：[位运算](../math/bit.md#位运算)．

| 运算符       | 功能   |
| --------- | ---- |
|  `~`      | 逐位非  |
|  `&` （双目） | 逐位与  |
|  `|`      | 逐位或  |
|  `^`      | 逐位异或 |
|  `<<`     | 逐位左移 |
|  `>>`     | 逐位右移 |

位操作的意义请参考 [位操作](../math/bit.md) 页面．需要注意的是，位操作的优先级低于算术运算符（除了取反），而按位与、按位或及异或低于比较运算符（详见 [C++ 运算符优先级总表](#c-运算符优先级总表)），所以使用时需多加注意，在必要时添加括号．

移位运算中如果出现如下情况，则其行为未定义：

1.  右操作数（即移位数）为负值；
2.  右操作数大于等于左操作数的位数；

例如，对于 `int32_t` 类型的变量 `a`，`a<<-1` 和 `a<<32` 都是未定义的．

对于带符号非负数的左移操作，需要确保移位后的结果能被原数的类型容纳，否则行为也是未定义的．[^note1]对一个负数执行左移操作也未定义．[^note2]

对于右移操作，右侧多余的位将会被舍弃，而左侧较为复杂：对于无符号数，会在左侧补 $0$[^note3]；而对于有符号数，则会用最高位的数（其实就是符号位，非负数为 $0$，负数为 $1$）补齐[^note4]．

## 自增/自减 运算符

有时我们需要让变量进行增加 1（自增）或者减少 1（自减），这时自增运算符 `++` 和自减运算符 `--` 就派上用场了．

自增/自减运算符可放在变量前或变量后面，在变量前称为前缀，在变量后称为后缀，单独使用时前缀后缀无需特别区别，如果需要用到表达式的值则需注意，具体可看下面的例子．详细情况可参考 [引用](./reference.md) 介绍的例子部分．

```cpp
i = 100;

op1 = i++;  // op1 = 100，先 op1 = i，然后 i = i + 1

i = 100;

op2 = ++i;  // op2 = 101，先 i = i + 1，然后赋值 op2

i = 100;

op3 = i--;  // op3 = 100，先赋值 op3，然后 i = i - 1

i = 100;

op4 = --i;  // op4 = 99，先 i = i - 1，然后赋值 op4
```

## 复合赋值运算符

复合赋值运算符实际上是表达式的缩写形式．可分为复合算术运算符 `+=`、`-=`、`*=`、`/=`、`%=` 和复合位操作符 `&=`、`|=`、`^=`、`<<=`、`>>=`．

例如，`op = op + 2` 可写为 `op += 2`，`op = op - 2` 可写为 `op -= 2`，`op= op * 2` 可写为 `op *= 2`．

## 条件运算符

条件运算符可以看作 `if` 语句的简写，`a ? b : c` 中如果表达式 `a` 成立，那么这个条件表达式的结果是 `b`，否则条件表达式的结果是 `c`．
## 比较运算符

| 运算符    | 功能   |
| ------ | ---- |
|  `>`   | 大于   |
|  `>=`  | 大于等于 |
|  `<`   | 小于   |
|  `<=`  | 小于等于 |
|  `==`  | 等于   |
|  `!=`  | 不等于  |

其中特别需要注意的是要将等于运算符 `==` 和赋值运算符 `=` 区分开来，这在判断语句中尤为重要．

 `if (op=1)` 与 `if (op==1)` 看起来类似，但实际功能却相差甚远．第一条语句是在对 op 进行赋值，若赋值为非 0 时为真值，表达式的条件始终是满足的，无法达到判断的作用；而第二条语句才是对 `op` 的值进行判断．

## 逻辑运算符

| 运算符    | 功能  |
| ------ | --- |
|  `&&`  | 逻辑与 |
|  `||`  | 逻辑或 |
|  `!`   | 逻辑非 |

```cpp
Result = op1 && op2;  // 当 op1 与 op2 都为真时则 Result 为真

Result = op1 || op2;  // 当 op1 或 op2 其中一个为真时则 Result 为真

Result = !op1;  // 当 op1 为假时则 Result 为真
```

**内建的**运算符 `&&` 和 `||` 进行短路求值（若在求值第一个操作数后结果已知，则不求值第二个），重载的运算符无此特性，并始终对两个操作数都进行求值．

## 逗号运算符

逗号运算符可将多个表达式分隔开来，被分隔开的表达式按从左至右的顺序依次计算，整个表达式的值是最后的表达式的值．逗号表达式的优先级在所有运算符中的优先级是 **最低** 的．

```cpp
exp1, exp2, exp3;  // 最后的值为 exp3 的运算结果．

Result = 1 + 2, 3 + 4, 5 + 6;
//得到 Result 的值为 3 而不是 11，因为赋值运算符 "="
//的优先级比逗号运算符高，先进行了赋值运算才进行逗号运算．

Result = (1 + 2, 3 + 4, 5 + 6);

// 若要让 Result 的值得到逗号运算的结果则应将整个表达式用括号提高优先级，此时
// Result 的值才为 11．
```

## 成员访问运算符

| 运算符       | 功能       |
| --------- | -------- |
|  `[]`     | 数组下标     |
|  `.`      | 对象成员     |
|  `&` （单目） | 取地址/获取引用 |
|  `*` （单目） | 间接寻址/解引用 |
|  `->`     | 指针成员     |

这些运算符用来访问对象的成员或者内存，除了最后一个运算符外上述运算符都可被重载．与 `&` ， `*` 和 `->` 相关的内容请阅读 [指针](./pointer.md) 和 [引用](./reference.md) 教程．这里还省略了两个很少用到的运算符 `.*` 和 `->*` ，其具体用法可以参见 [C++ 语言手册](https://zh.cppreference.com/w/cpp/language/operator_member_access) ．

```cpp
auto result1 = v[1];  // 获取v中下标为2的对象
auto result2 = p.q;   // 获取p对象的q成员
auto result3 = p -> q;  // 获取p指针指向的对象的q成员，等价于 (*p).q
auto result4 = &v;      // 获取指向v的指针
auto result5 = *v;      // 获取v指针指向的对象
```

## C++ 运算符优先级总表

来自 [C++ 运算符优先级 - cppreference](https://zh.cppreference.com/w/cpp/language/operator_precedence) ，有修改．

|          运算符         |    描述    |                              例子                              | 可重载性 |
| :------------------: | :------: | :----------------------------------------------------------: | :--: |
|       **第一级别**       |          |                                                              |      |
|         `::`         |  作用域解析符  |                       `Class::age = 2;`                      | 不可重载 |
|       **第二级别**       |          |                                                              |      |
|         `++`         |  后自增运算符  |           `for (int i = 0; i < 10; i++) cout << i;`          |  可重载 |
|         `--`         |  后自减运算符  |           `for (int i = 10; i > 0; i--) cout << i;`          |  可重载 |
|   `type()  type{}`   |  强制类型转换  |           `unsigned int a = unsigned(3.14);`                | 可重载 |
|         `()`         |   函数调用   |                        `isdigit('1')`                        |  可重载 |
|         `[]`         |  数组数据获取  |                        `array[4] = 2;`                       |  可重载 |
|          `.`         |  对象型成员调用 |                        `obj.age = 34;`                       | 不可重载 |
|         `->`         |  指针型成员调用 |                       `ptr->age = 34;`                       |  可重载 |
|   **第三级别** （从右向左结合）  |          |                                                              |      |
|         `++`         |  前自增运算符  |             `for (i = 0; i < 10; ++i) cout << i;`            |  可重载 |
|         `--`         |  前自减运算符  |             `for (i = 10; i > 0; --i) cout << i;`            |  可重载 |
|          `+`         |    正号    |                         `int i = +1;`                        |  可重载 |
|          `-`         |    负号    |                         `int i = -1;`                        |  可重载 |
|          `!`         |   逻辑取反   |                        `if (!done) …`                       |  可重载 |
|          `~`         |   按位取反   |                       `flags = ~flags;`                      |  可重载 |
|       `(type)`       |  C 风格强制类型转换  |                 `int i = (int) floatNum;`             |  可重载 |
|          `*`         |   指针取值   |                     `int data = *intPtr;`                    |  可重载 |
|          `&`         |   值取指针   |                    `int *intPtr = &data;`                    |  可重载 |
|       `sizeof`       |  返回类型内存  |    `int size = sizeof floatNum; int size = sizeof(float);`   | 不可重载 |
|         `new`        | 动态元素内存分配 |  `long *pVar = new long; MyClass *ptr = new MyClass(args);`  |  可重载 |
|       `new []`       | 动态数组内存分配 |                 `long *array = new long[n];`                 |  可重载 |
|       `delete`       | 动态析构元素内存 |                        `delete pVar;`                        |  可重载 |
|      `delete []`     | 动态析构数组内存 |                      `delete [] array;`                      |  可重载 |
|       **第四级别**    |          |                                                              |      |
|         `.*`         |  类对象成员引用 |                       `obj.*var = 24;`                       | 不可重载 |
|         `->*`        |  类指针成员引用 |                       `ptr->*var = 24;`                      |  可重载 |
|       **第五级别**    |          |                                                              |      |
|          `*`         |    乘法    |                       `int i = 2 * 4;`                       |  可重载 |
|          `/`         |    除法    |                    `float f = 10.0 / 3.0;`                   |  可重载 |
|          `%`         | 取余数（模运算） |                      `int rem = 4 % 3;`                      |  可重载 |
|       **第六级别**    |          |                                                              |      |
|          `+`         |    加法    |                       `int i = 2 + 3;`                       |  可重载 |
|          `-`         |    减法    |                       `int i = 5 - 1;`                       |  可重载 |
|       **第七级别**    |          |                                                              |      |
|         `<<`         |    位左移   |                    `int flags = 33 << 1;`                    |  可重载 |
|         `>>`         |    位右移   |                    `int flags = 33 >> 1;`                    |  可重载 |
|       **第八级别**     |          |                                                              |      |
|         `<=>`         | 三路比较运算符  |                `if ((i <=> 42) < 0) ...`                      |  可重载 |
|       **第九级别**     |          |                                                              |      |
|          `<`         |    小于    |                      `if (i < 42) ...`                      |  可重载 |
|         `<=`         |   小于等于   |                      `if (i <= 42) ...`                     |  可重载 |
|          `>`         |    大于    |                      `if (i > 42) ...`                      |  可重载 |
|         `>=`         |   大于等于   |                      `if (i >= 42) ...`                     |  可重载 |
|       **第十级别**       |          |                                                              |      |
|         `==`         |    等于    |                      `if (i == 42) ...`                     |  可重载 |
|         `!=`         |    不等于   |                      `if (i != 42) ...`                     |  可重载 |
|       **第十一级别**      |          |                                                              |      |
|          `&`         |   位与运算   |                     `flags = flags & 42;`                    |  可重载 |
|       **第十二级别**      |          |                                                              |      |
|          `^`         |   位异或运算  |                     `flags = flags ^ 42;`                    |  可重载 |
|       **第十三级别**      |          |                                                              |      |
|          `|`         |   位或运算   |                     `flags = flags | 42;`                    |  可重载 |
|       **第十四级别**      |          |                                                              |      |
|         `&&`         |   逻辑与运算  |              `if (conditionA && conditionB) ...`             |  可重载 |
|   **第十五级别**         |          |                                                              |      |
|         `||`         |   逻辑或运算  |              `if (conditionA || conditionB) ...`             |  可重载 |
|   **第十六级别** （从右向左结合） |          |                                                              |      |
|         `? :`        |   条件运算符  |                   `int i = a > b ? a : b;`                   | 不可重载 |
|        `throw`       |   异常抛出   |                  `throw EClass("Message");`                  | 不可重载 |
|          `=`         |    赋值    |                         `int a = b;`                         |  可重载 |
|         `+=`         |   加赋值运算  |                           `a += 3;`                          |  可重载 |
|         `-=`         |   减赋值运算  |                           `b -= 4;`                          |  可重载 |
|         `*=`         |   乘赋值运算  |                           `a *= 5;`                          |  可重载 |
|         `/=`         |   除赋值运算  |                           `a /= 2;`                          |  可重载 |
|         `%=`         |   模赋值运算  |                           `a %= 3;`                          |  可重载 |
|         `<<=`        |  位左移赋值运算 |                        `flags <<= 2;`                        |  可重载 |
|         `>>=`        |  位右移赋值运算 |                        `flags >>= 2;`                        |  可重载 |
|         `&=`         |  位与赋值运算  |                     `flags &= new_flags;`                    |  可重载 |
|         `^=`         |  位异或赋值运算 |                     `flags ^= new_flags;`                    |  可重载 |
|         `|=`         |  位或赋值运算  |                     `flags |= new_flags;`                    |  可重载 |
|       **第十七级别**      |          |                                                              |      |
|          `,`         |   逗号分隔符  |          `for (i = 0, j = 0; i < 10; i++, j++) ...`          |  可重载 |

需要注意的是，表中并未列出 `const_cast`、`static_cast`、`dynamic_cast`、`reinterpret_cast`、`typeid`、`sizeof...`、`noexcept` 及 `alignof` 等运算符，因为它们的使用形式与函数调用相同，不会出现歧义．

## 参考资料与注释

[^note1]: C++20 前，若原值为带符号类型，且移位后的结果能被原类型的无符号版本容纳，则将该结果 [转换](../lang/var.md#类型转换) 为相应的带符号值，否则行为未定义；无符号数的左移则舍弃移出结果类型的位．C++20 起，规定 `a << b` 为 $a\cdot 2^b$ 在模 $2^N$ 下的值（$N$ 为结果类型的位宽），即无论是带符号数还是无符号数，左移均直接舍弃移出结果类型的位（即 [算术左移/逻辑左移](../math/bit.md#移位)）．

[^note2]: C++20 前．C++20 起的行为参见[^note1]．

[^note3]: 即 [逻辑右移](../math/bit.md#移位)．

[^note4]: 即 [算术右移](../math/bit.md#移位)．C++20 前，带符号的右移是依实现定义的，在大多数实现中，均采用算术右移．C++20 起，规定 `a >> b` 为 $\lfloor a/2^b\rfloor$，所以带符号数右移运算是算术右移．
