算术运算符

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

返回特定算术运算的结果。

运算符名 语法 可重载 原型示例(对于 class T
类内定义 类外定义
一元加 +a T T::operator+() const; T operator+(const T &a);
一元减 -a T T::operator-() const; T operator-(const T &a);
加法 a + b T T::operator+(const T2 &b) const; T operator+(const T &a, const T2 &b);
减法 a - b T T::operator-(const T2 &b) const; T operator-(const T &a, const T2 &b);
乘法 a * b T T::operator*(const T2 &b) const; T operator*(const T &a, const T2 &b);
除法 a / b T T::operator/(const T2 &b) const; T operator/(const T &a, const T2 &b);
a % b T T::operator%(const T2 &b) const; T operator%(const T &a, const T2 &b);
逐位非 ~a T T::operator~() const; T operator~(const T &a);
逐位与 a & b T T::operator&(const T2 &b) const; T operator&(const T &a, const T2 &b);
逐位或 a | b T T::operator|(const T2 &b) const; T operator|(const T &a, const T2 &b);
逐位异或 a ^ b T T::operator^(const T2 &b) const; T operator^(const T &a, const T2 &b);
逐位左移 a << b T T::operator<<(const T2 &b) const; T operator<<(const T &a, const T2 &b);
逐位右移 a >> b T T::operator>>(const T2 &b) const; T operator>>(const T &a, const T2 &b);
注意
  • 所有内建运算符都返回值,而大多数用户定义重载亦返回值,从而能以与内建版本相同的方式使用用户定义运算符。然而,用户定义重载中,能以任何类型为返回类型(包括 void )。特别是 operator<<operator>> 流插入和流释出重载返回 T&
  • T2 能为包括 T 的任何类型。

目录

[编辑] 解释

所有算术运算符计算特定算术运算的结果,并返回其结果。不修改参数。

[编辑] 转换

若传递给算术运算符的运算数是整数或无作用域枚举类型,则在任何其他行动前(但在左值到右值转换后,若可应用),运算数经历整数提升。若运算数之一拥有数组或函数类型,则应用数组到指针和函数到指针转换。

对于二元运算符(除了移位),若提升后的运算数拥有不同类型,则应用称为通常算术转换的额外隐式转换集合,目的是产生共用类型(亦可由 std::common_type 类型特性访问)。若在任何整数提升前,一个运算数有枚举类型而另一运算数拥有浮点类型或相异的枚举类型,则此行为被弃用。 (C++20 起)

  • 若任一运算数拥有有作用域枚举类型,则不进行转换:另一运算数和返回类型必须拥有相同类型
  • 否则,若任一运算数为 long double ,则转换另一运算数为 long double
  • 否则,若任一运算数为 double ,则转换另一运算数为 double
  • 否则,若任一运算数为 float ,则转换另一运算数为 float
  • 否则,运算数拥有整数类型(因为 bool 、 char 、 char16_t 、 char32_t 和无作用域枚举在此点已被提升)并应用整数转换以产生如下的共用类型:
  • 若两个运算数均为有符号或均为无符号,则转换拥有较小转换等级的运算数为拥有较大整数转换等级的运算数。
  • 否则,若无符号运算数的转换等级大于或等于有符号运算数的转换等级,则转换有符号运算数为无符号运算数的类型。
  • 否则,若有符号运算数的类型能表示无符号运算数类型的所有值,则转换无符号运算数为有符号运算数的类型。
  • 否则,转换两个运算数为有符号运算数的无符号对应版本。

转换等级boolsigned charshortintlonglong long 顺序递增。任何无符号类型的等级等于对应有符号类型的等级。 char 的等级等于 signed charunsigned char 的等级。 char16_tchar32_twchar_t 的等级等于其底层类型的等级。

[编辑] 溢出

无符号整数算术始终进行 modulo 2n
,其中 n 是该整数的位数。例如对于 unsigned int , 加一到 UINT_MAX 给出 0 ,而从 0 减一给出 UINT_MAX

有符号整数算术运算溢出(结果不符合结果类型)时,行为未定义:它可能按照表示的规则(典型为补码)回卷,可能在某些平台上或由于编译器选项(例如 GCC 和 Clang 中的 -ftrapv )陷落,或可能完全被编译器优化掉

[编辑] 浮点环境

若支持 #pragma STDC FENV_ACCESS 并设置它为 ON ,则所有浮点算术运算符服从当前浮点舍入方向,并报告 math_errhandling 中指定的浮点算术错误,除非是静态初始化器的一部分(该情况下不引发浮点异常,而舍入模式为到最接近)。

[编辑] 浮点缩略

除非支持 #pragma STDC FP_CONTRACT 并设置它为 OFF ,否则所有浮点算术都能如同中间结果拥有无限范围和精度一般进行,即允许省略舍入误差和浮点异常的优化。例如 C++ 允许以单条融合乘加 CPU 指令实现 (x*y) + z ,或把 a = x*x*x*x; 优化为 tmp = x *x; a = tmp*tmp

无关乎缩略,浮点算术的中间结果可拥有异于其所指示类型的范围和精度,见 FLT_EVAL_METHOD

正式而言, C++ 标准不在浮点运算的精度上做任何保证。

[编辑] 一元算术运算符

一元算术运算符拥有形式

+ expression (1)
- expression (2)
1) 一元加(提升)。
对于内建运算符, expression 必须拥有算术、无作用域枚举或指针类型。若运算数拥有整数或无作用域枚举类型,则整数提升在其上进行,并确定结果类型。
2) 一元减(取反)。
对于内建运算符, expression 必须拥有算术和无作用域枚举类型。整数提升在运算数上进行,并确定结果类型。

内建的一元加运算符返回其运算数的值。仅有的非无操作情形,是运算数拥有整数类型或无作用域枚举类型,而它为整数提升所更改,例如整数提升转换 charint ,或运算数是左值到右值、数组到指针或函数到指针的应用目标。

内建的一元减运算符计算提升后运算数的相反数。对于无符号 a-a 的值为 2b
-a
,其中 b 为提升后的位数。

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

A operator+(A)
T* operator+(T*)
A operator-(A)
#include <iostream>
int main()
{
    char c = 0x6a;
    int n1 = 1;
    unsigned char n2 = 1;
    unsigned int n3 = 1;
    std::cout << "char: " << c << " int: " << +c << '\n'
              << "-1, where 1 is signed: " << -n1 << '\n'
              << "-1, where 1 is unsigned char: " << -n2 << '\n'
              << "-1, where 1 is unsigned int: " << -n3 << '\n';
    char a[3];
    std::cout << "size of array: " << sizeof a << '\n'
              << "size of pointer: " << sizeof +a << '\n';
}

输出:

char: j int: 106
-1, where 1 is signed: -1
-1, where 1 is unsigned char: -1
-1, where 1 is unsigned int: 4294967295
size of array: 3
size of pointer: 8

[编辑] 加法性运算符

二元加法性运算符表达式拥有形式

lhs + rhs (1)
lhs - rhs (2)
1) 加法
对于内建运算符, lhsrhs 必须为下列之一:
  • 都拥有算术或无作用域枚举类型。此情况下,通常算术转换在两个运算数上进行,并确定结果类型。
  • 一个是指向完整对象的指针类型,另一个拥有整数或无作用域枚举类型。此情况下,结果拥有该指针的类型。
2) 减法
对于内建运算符, lhsrhs 必须为下列之一:
  • 都拥有算术或无作用域枚举类型。此情况下,通常算术转换在两个运算数上进行,并确定结果类型。
  • lhs 为指向完整对象类型的指针, rhs 拥有整数或无作用域枚举类型。此情况下,结果拥有该指针的类型。
  • 两者都是指向忽略 cv 限定符的相同完整对象类型的指针。此情况下,结果类型为 std::ptrdiff_t

对于算术或枚举类型的运算数,二元加的结果是运算数(通常算术转换后)的和,而二元减运算符的结果是第一运算数减去第二运算数(通常算术转换后)的结果,除了若实现支持 IEEE 浮点算术(见 std::numeric_limits::is_iec559 ),

  • 若运算数之一为 NaN ,则结果为 NaN
  • 无穷大减无穷大为 NaN 并引发 FE_INVALID
  • 正无穷大加负无穷大为 NaN 并引发 FE_INVALID

若任何运算数为指针,则应用下列规则:

  • 把指向非数组对象的指针当做指向大小为 1 的数组首元素的指针。
  • 若指针 P 指向数组的第 i 元素,则表达式 P+nn+PP-n 分别是指向同一数组的第 i+n 、第 i+n 和第 i-n 元素的同类型指针。指针加法的结果亦可为尾后一位置指针(即满足 P-1 指向数组末元素的指针 P )。任何其他情形(即试图生成不指向同一数组的任何元素或同一数组尾后一位置的指针)引起未定义行为。
  • 若指针 P 指向数组的第 i 元素,而指针 Q 指向同一数组的第 j 元素,则表达式 P-Q 拥有值 i-j ,若该值适合 std::ptrdiff_t 。两个运算数都必须指向同一数组的元素(或尾后一位置),否则行为未定义。若结果不适于 std::ptrdiff_t ,则行为未定义。
  • 任何情况下,若不考虑 cv 限定,若元素自身是指针则不考虑每一层的限定,而被指向类型异于数组元素类型,则指针算术的行为未定义。特别是,以指向派生对象数组元素的指向基类指针做指针算术是未定义的。
  • 若对指针加或减 0 ,则结果是不更改的指针。若两个指针指向同一对象,或都指向同一数组的尾后一位置,或都是空指针,则减法结果等于 (std::ptrdiff_t)0

这些指针算术允许指针满足随机访问迭代器 (RandomAccessIterator) 要求。

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

LR operator+(L, R)
LR operator-(L, R)
T* operator+(T*, std::ptrdiff_t)
T* operator+(std::ptrdiff_t, T*)
T* operator-(T*, std::ptrdiff_t)
std::ptrdiff_t operator-(T*, T*)

其中 LRLR 上通常算术转换的结果。

#include <iostream>
int main()
{
    char c = 2;
    unsigned int un = 2;
    int  n = -10;
    std::cout <<  " 2 + (-10), where 2 is a char    = " << c + n << '\n'
              <<  " 2 + (-10), where 2 is unsigned  = " << un + n << '\n'
              <<  " -10 - 2.12  = " << n - 2.12 << '\n';
 
    char a[4] = {'a', 'b', 'c', 'd'};
    char* p = &a[1];
    std::cout << "Pointer addition examples: " << *p << *(p + 2)
              << *(2 + p) << *(p - 1) << '\n';
    char* p2 = &a[4];
    std::cout << "Pointer difference: " << p2 - p << '\n';
}

输出:

2 + (-10), where 2 is a char    = -8
 2 + (-10), where 2 is unsigned  = 4294967288
 -10 - 2.12  = -12.12
Pointer addition examples: bdda
Pointer difference: 3

[编辑] 乘法性运算符

二元乘法性运算符表达式拥有形式

lhs * rhs (1)
lhs / rhs (2)
lhs % rhs (3)
1) 乘法
对于内建运算符, lhsrhs 必须都拥有算术或无作用域枚举类型。
2) 除法
对于内建运算符, lhsrhs 必须都拥有算术或无作用域枚举类型。
3) 余数
对于内建运算符, lhsrhs 必须都拥有整数或无作用域枚举类型。

对于所有三个运算符,通常算术转换在两个运算数上进行,并确定结果类型。

二元运算符 * 进行其运算数(通常算术转换后)的乘法,除了对于浮点乘法,

  • NaN 乘任何数给出 NaN
  • 无穷大乘零给出 NaN 并引发 FE_INVALID

二元运算符 / 将第一运算数除以第二运算数(通常算术转换后)。

对于整数运算数,它产生代数商,

以实现定义方向舍入商。

(C++11 前)

商向零截断(舍弃小数部分)。

(C++11 起)

若第二运算数为零,则行为未定义,除了若发生浮点除法且类型支持 IEEE 浮点算术(见 std::numeric_limits::is_iec559 ),则:

  • 若运算数之一为 NaN ,则结果为 NaN
  • 非零数除以 ±0.0 给出符号正确的无穷大并引发 FE_DIVBYZERO
  • 0.0 除以 0.0 给出 NaN 并引发 FE_INVALID

二元运算符 % 产生以第二运算数整除第一运算数(通常算术转换后)的余数。若商 a/b 能以结果类型表示,则 (a/b)*b + a%b == a 。若第二运算数为零,则行为未定义。若商 a/b 不能以结果类型表示,则 a/ba%b 的行为均未定义(这表示补码系统上 INT_MIN%-1 为未定义)。

注意: C++11 前,若二元运算符 % 的一或两个运算数为负,则余数符号是实现定义的,因为它取决于整数除法的舍入方向。函数 std::div 在该情况下提供良好定义的行为。

注意:对于浮点余数,见 std::remainderstd::fmod

针对用户定义运算符的重载决议中,对于每对提升后的算术类型 LARA 及每对提升后的整数类型 LIRI ,下列函数签名参与重载决议:

LRA operator*(LA, RA)
LRA operator/(LA, RA)
LRI operator%(LI, RI)

其中 LRxLxRx 上通常算术转换的结果。

#include <iostream>
int main()
{
    char c = 2;
    unsigned int un = 2;
    int  n = -10;
    std::cout <<  "2 * (-10), where 2 is a char    = " << c * n << '\n'
              <<  "2 * (-10), where 2 is unsigned  = " << un * n << '\n'
              <<  "-10 / 2.12  = " << n / 2.12 << '\n'
              <<  "-10 / 21  = " << n / 21 << '\n'
              <<  "-10 % 21  = " << n % 21 << '\n';
}

输出:

2 * (-10), where 2 is a char    = -20
2 * (-10), where 2 is unsigned  = 4294967276
-10 / 2.12  = -4.71698
-10 / 21  = 0
-10 % 21  = -10

[编辑] 逐位逻辑运算符

逐位逻辑运算符表达式拥有形式

~ rhs (1)
lhs & rhs (2)
lhs | rhs (3)
lhs ^ rhs (4)
1) 逐位非
2) 逐位与
3) 逐位或
4) 逐位异或
对于内建运算符, lhsrhs 必须都拥有整数或无作用域枚举类型。通常算术转换在两个运算数上进行并确定结果类型。

operator~ 的结果是参数值(提升后)的逐位非(一补数)。 operator& 的结果是运算数值(通常算术转换后)的逐位与。 operator| 的结果是运算数值(通常算术转换)后的逐位或。 operator^ 的结果是运算数值(通常算术转换后)的逐位异或。

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

R operator~(R)
LR operator&(L, R)
LR operator^(L, R)
LR operator|(L, R)

其中 LRLR 上通常算术转换的结果。

#include <iostream>
int main()
{
    std::cout << std::hex << std::showbase;
    uint16_t mask = 0x00f0;
    uint32_t a = 0x12345678;
    std::cout << "Value: " << a << " mask: " << mask << '\n'
              << "Setting bits: " << (a | mask) << '\n'
              << "Clearing bits: " << (a & ~mask) << '\n'
              << "Selecting bits: " << (a & mask) << '\n';
}

输出:

Value: 0x12345678 mask: 0xf0
Setting bits: 0x123456f8
Clearing bits: 0x12345608
Selecting bits: 0x70

[编辑] 移位运算符

移位运算符拥有形式

lhs << rhs (1)
lhs >> rhs (2)
1)lhs 左移 rhs
2)lhs 右移 rhs
对于内建运算符, lhsrhs 必须都拥有整数或无作用域枚举类型。在两个运算数上进行整数提升。

返回类型是提升后的左运算数类型。

对于无符号的 aa << b 的值为 a * 2b
,对返回类型的最大值加 1 取模(即进行逐位左移并舍弃被移出目标类型的位)。

对于有符号的正 a

a << b 的值为 a * 2b
,若它能以返回类型表示,否则行为未定义。

(C++14 前)

a << b 的值为 a * 2b
,若它能以返回类型的无符号版本表示(然后再转换到有符号:这使得用 1<<31 创建 INT_MIN 合法),否则行为未定义。

(C++14 起)

对于负 aa << b 的行为未定义。

对于无符号 a 和有非负值的有符号 aa >> b 的值是 a/2b
的有符号部分。对于负 aa >> b 的值是实现定义的(大部分实现中,这进行算术右移,故结果保持负数)。

任何情况下,若右运算数的值为负或大于或等于提升后左运算数中的位数,则行为未定义。

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

L operator<<(L, R)
L operator>>(L, R)
#include <iostream>
enum {ONE=1, TWO=2};
int main()
{
    std::cout << std::hex << std::showbase;
    char c = 0x10;
    unsigned long long ull = 0x123;
    std::cout << "0x123 << 1 = " << (ull << 1) << '\n'
              << "0x123 << 63 = " << (ull << 63) << '\n' // 无符号中的溢出
              << "0x10 << 10 = " << (c << 10) << '\n';   // 提升 char 到 int
    long long ll = -1000;
    std::cout << std::dec << "-1000 >> 1 = " << (ll >> ONE) << '\n';
}

输出:

0x123 << 1 = 0x246
0x123 << 63 = 0x8000000000000000
0x10 << 10 = 0x4000
-1000 >> 1 = -500

[编辑] 标准库

算术运算符为许多标准库类型重载。

[编辑] 一元算术运算符

实现一元 + 和一元 -
(std::chrono::duration 的公开成员函数) [编辑]
对复数运用一元运算符
(函数模板) [编辑]
对 valarray 的每个元素运用一元算术运算符
(std::valarray 的公开成员函数) [编辑]

[编辑] 加法性运算符

以给定的 duration 修改 time_point
(函数模板) [编辑]
连接两个字符串或者一个字符串和一个字符
(函数模板) [编辑]
推进或减小迭代器
(std::reverse_iterator 的公开成员函数)
推进或减小迭代器
(std::move_iterator 的公开成员函数)
在两个复数或一个复数和一个标量上运用复数算术运算
(函数模板) [编辑]
对两个 valarray 的每个元素或一个 valarray 和一个值运用二元运算符
(函数模板) [编辑]

[编辑] 乘法性运算符

实现以时长为参数的算术运算
(函数模板) [编辑]
在两个复数或一个复数和一个标量上运用复数算术运算
(函数模板) [编辑]
对两个 valarray 的每个元素或一个 valarray 和一个值运用二元运算符
(函数模板) [编辑]

[编辑] 逐位逻辑运算符

进行二进制与、或、异或及非
(std::bitset 的公开成员函数) [编辑]
在 bitset 上执行二元逻辑操作
(函数) [编辑]
应用一元算术运算符到 valarray 的每个元素
(std::valarray 的公开成员函数)
应用二元运算符到二个 valarray 的每个元素,或一个 valarray 和一个值
(函数模板)

[编辑] 移位运算符

应用二元运算符到二个 valarray 的每个元素,或一个 valarray 和一个值
(函数模板)
进行二进制左移和右移
(std::bitset 的公开成员函数)

[编辑] 流插入/释出运算符

整个标准库中,移位运算符常对 I/O 流( std::ios_base& 或从它派生的类之一)重载,以之为左运算数和返回类型。这种运算符被称为流插入流释出运算符:

读并取走带格式数据
(std::basic_istream 的公开成员函数) [编辑]
读并取走字符和字符数组
(函数模板) [编辑]
插入有格式数据
(std::basic_ostream 的公开成员函数) [编辑]
插入字符数据
(函数) [编辑]
复数的序列化和反序列化
(函数模板) [编辑]
执行 bitset 的流输入和输出
(函数) [编辑]
进行 string 上的流输入与输出
(函数模板) [编辑]
执行伪随机数引擎的流输入和输出
(函数) [编辑]
进行伪随机数分布上的流输入和输出
(函数模板) [编辑]

[编辑] 参阅

运算符优先级

运算符重载

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

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

特殊运算符

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

算术运算符C 文档