算术运算符

来自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>=b
成员访问运算符a[b]*a&aa->ba.ba->*ba.*b
其他运算符a(...)a,ba?b:c
运算符的替代表示形式
优先级和结合性
折叠表达式(C++17)
new 表达式
delete 表达式
throw 表达式
alignof
sizeof
sizeof...(C++11)
typeid
noexcept(C++11)
运算符重载
类型转换
隐式转换
const_cast
static_cast
reinterpret_cast
dynamic_cast
显式转换 (T)aT(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 在内的任何类型。

目录

[编辑] 解释

所有的算术运算符均计算某个特定算术运算的结果,并返回这个结果。其实参均不被改动。

[编辑] 转换

如果传递给算术运算符的操作数具有整型或无作用域枚举类型,则在任何其他动作之前(并在左值向右值转换(若适用)之后),将对该操作数实施整型提升。如果操作数具有数组或函数类型,则对其实施数组向指针和函数向指针转换。

对于(除位移外的)二元运算符,若其提升后的操作数具有不同类型,则实施额外的一组隐式转换,它们称为一般算术转换(usual arithmetic conversion),其目的为产生一个公共类型(common type)(也可以通过 std::common_type 类型特征来得到它)。

  • 如果有任何操作数具有有作用域枚举类型,则不会进行转换:另一个操作数和其返回值必须为相同类型。
  • 否则,如果有任何操作数为 long double,则另一个操作数被转换为 long double
  • 否则,如果有任何操作数为 double,则另一个操作数被转换为 double
  • 否则,如果有任何操作数为 float,则另一个操作数被转换为 float
  • 否则,操作数就具有整数类型(因为 bool、char、char16_t、char32_t、wchar_t,和无作用域枚举此时均已经被提升),并按如下实施整型转换以产生公共类型:
  • 如果两个操作数均有符号或均无符号,则具有较小转换等级(conversion rank)的操作数被转换为具有较大整数转换等级的操作数。
  • 否则,如果无符号操作数的转换等级大于或等于有符号操作数的转换等级,则有符号操作数被转换为无符号操作数的类型。
  • 否则,如果有符号操作数的类型可以表示无符号操作数的所有值,则无符号操作数被转换为有符号操作数的类型。
  • 否则,两个操作数均被转换为有符号操作数的类型对应的无符号类型。

上述的转换等级(conversion rank)依序增加: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),它可能触发陷阱(trap),或者也可能完全被编译器所优化掉

[编辑] 浮点环境

如果 #pragma STDC FENV_ACCESS 受到支持且被设为 ON,则所有的浮点算术运算符均遵循当前的浮点舍入方向,并根据 math_errhandling 所指定的方式报告浮点算术错误,除非它是某个静态初始化式的一部分(这种情况下不会引发浮点异常,而舍入模式为向最近值舍入)。

[编辑] 浮点紧缩

除非 #pragma STDC FP_CONTRACT 受到支持并被设为 OFF,否则所有的浮点算术的执行都如同其中间结果均具有无穷范围和精度来进行,就是说,允许实施忽略其舍入误差和浮点异常的各种优化措施。例如,C++ 允许将 (x*y) + z 实现为单个的融合乘加 CPU 指令,或者将 a = x*x*x*x; 优化为 tmp = x *x; a = tmp*tmp

浮点算术的中间结果所具有的范围和精度可以同其类型所指定的不同,但这与浮点紧缩无关,参见 FLT_EVAL_METHOD

正式地说,C++ 标准对浮点运算的精度没有做出任何保证。

[编辑] 一元算术运算符

对于每个提升后的算术类型 A 和每个类型 T,有如下函数签名参与重载解析:

A operator+(A)
T* operator+(T*)
A operator-(A)

内建的一元加运算符返回其操作数的值。唯一使其不是空操作的场合是其操作数具有整型类型或无作用域枚举类型,此时它由整型提升所改动,例如,它会将 char 转换为 int,或者其操作数需要进行左值向右值,数组向指针,或函数向指针转换。

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

#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

[编辑] 加法性运算符

对于每对提升后的算术类型 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*)

其中 LR 为对 LR 进行一般算术转换后的结果。

对于算术或枚举类型的操作数,二元加的结果为(经过一般算术转换后的)两个操作数的和,而二元减运算符的结果则是(经过一般算术转换后)从第一个操作数中减去第二个之后的结果,但若该类型支持 IEEE 浮点算术(参见 std::numeric_limits::is_iec559),则

  • 当任何一个操作数为 NaN 时其结果为 NaN
  • 无穷(infinity)减去无穷为 NaN 并引发 FE_INVALID
  • 无穷加上负无穷为 NaN 并引发 FE_INVALID

当有任何操作数为指针时,适用以下规则:

  • 指向非数组对象的指针被当成是指向大小为一的数组的第一个元素的指针。
  • 如果指针 P 指向数组的第 i 个元素,则表达式 P+nn+PP-n 为相同类型的指针并分别指向同一个数组的第 i+n、第 i+n 和第 i-n 个元素。指针加法的结果还可以是越过末尾(one-past-the-end)指针(亦即使得表达式 P-1 指向该数组的最后一个元素的指针 P)。任何其他的情形(亦即视图产生并不指向同一个数组的某个元素或越过末尾的指针)将造成未定义行为。
  • 如果指针 P 指向数组的第 i 个元素,而指针 Q 指向同一个数组的第 j 个元素,则表达式 P-Q 的值为 i-j,但其值应当能存入 std::ptrdiff_t 之中。这两个操作数必须都指向同一个数组的元素(或越过末尾),否则其行为未定义。如果其结果无法存入 std::ptrdiff_t 之中,则其行为未定义。
  • 无论哪种情况,如果在忽略了(当元素自身也为指针时,每一层的)cv 限定性之后,所指向的类型和数组元素类型不同,则指针算术的行为未定义。尤其是,在指向某个派生类对象数组的元素的基类指针上所进行的指针算术是未定义的。
  • 当在指针上加上或减去值 0 时,其结果为该指针值,未有改动。如果两个指针指向相同对象,或都为同一个数组的越过末尾,或都为空指针,则对其运用减法的结果等于 (std::ptrdiff_t)0

这些指针算术运算符,允许指针符合 RandomAccessIterator 概念。

#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

[编辑] 乘法性运算符

对于每对提升后的算术类型 LARA,以及对于每对提升后的整型类型 LIRI,有如下函数签名参与重载解析:

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

其中 LRx 为对 LxRx 进行一般算术转换后的结果。

二元运算符 * 对其(经过一般算术转换后的)操作数实施乘法,但对于浮点乘法,有

  • 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

#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

[编辑] 按位逻辑运算符

对于每对提升后的整型类型 LR,有如下函数签名参与重载解析:

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

其中 LR 为对 LR 进行一般算术转换后的结果。

运算符 ~ 的结果为其(经过提升后的)实参的按位取反(一的补码)值。运算符 & 的结果为其(经过一般算术转换之后的)两个操作数的按位与值。运算符 | 的结果为其(经过一般算术转换之后的)两个操作数的按位或值。运算符 ^ 的结果为其(经过一般算术转换之后的)两个操作数的按位异或值。

#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

输出:

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

[编辑] 移位运算符

对于每对提升后的整型类型 LR,有如下函数签名参与重载解析:

L operator<<(L, R)
L operator>>(L, R)

内建的移位运算符的操作数具有整型类型或者无作用域枚举类型。求值之前两个操作数都进行整型提升。返回类型为整型提升后的左操作数的类型。

对于无符号的正数 aa << b 的值为 a * 2b
根据返回类型的最大值加一取模缩减之后的值(就是说,实施按位左移并丢弃移出目标类型的位)。

对于有符号的正数 a

a << b 的值为 a * 2b
且它应当可以由返回类型表示,否则其行为未定义。
(C++14 前)
a << b 的值为 a * 2b
且它应当可以由返回类型的无符号版本所表示(并随后被转换为有符号版本:这使得将 INT_MIN 设为 1<<31 是合法的),否则其行为未定义。
(C++14 起)

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

对于无符号 a 和具有非负值的有符号 aa >> b 的值为 a/2b
的整数部分。对于负数 aa >> b 的值是由实现定义的(在大多数实现中,这会实施算术右移,其结果保持为负数)。

无论哪种情况,当右操作数是负数或者大于或等于提升后的左操作数的位数时,其行为未定义。

#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 的公开成员函数) [edit]
对复数运用一元运算符
(函数模板) [edit]
对 valarray 的每个元素运用一元算术运算符
(std::valarray 的公开成员函数) [edit]

[编辑] 加法性运算符

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

[编辑] 乘法性运算符

以时间间隔为参数进行算术运算
原文:
implements arithmetic operations with durations as arguments
这段文字是通过 Google Translate 自动翻译生成的。
您可以帮助我们检查、纠正翻译中的错误。详情请点击这里

(函数模板) [edit]
在两个复数或一个复数和一个标量上运用复数算术运算
(函数模板) [edit]
对两个 valarray 的每个元素或一个 valarray 和一个值运用二元运算符
(函数模板) [edit]

[编辑] 按位逻辑运算符

执行二元与、或、异或和取反运算
(std::bitset 的公开成员函数) [edit]
在 bitset 上执行二元逻辑操作
(函数) [edit]
对 valarray 的每个元素运用一元算术运算符
(std::valarray 的公开成员函数)
对两个 valarray 的每个元素或一个 valarray 和一个值运用二元运算符
(函数模板)

[编辑] 移位运算符

对两个 valarray 的每个元素或一个 valarray 和一个值运用二元运算符
(函数模板)
实施二元左移和右移
(std::bitset 的公开成员函数)

[编辑] 流的插入/抽取运算符

整个标准库中,通常会提供以 I/O 流(std::ios_base& 或其派生类之一)作为左操作数和返回类型的移位运算符的重载。这种运算符被称为流插入流抽取运算符:

抽取带格式的数据
(std::basic_istream 的公开成员函数) [edit]
抽取字符和字符数组
(函数模板) [edit]
插入格式化的数据
(std::basic_ostream 的公开成员函数) [edit]
插入字符数据
(函数) [edit]
复数的序列化和反序列化
(函数模板) [edit]
执行 bitset 的流输入和输出
(函数) [edit]
执行字符串的流输入和输出
(函数模板) [edit]
执行伪随机数引擎的流输入和输出
(函数) [edit]
执行伪随机数分布的流输入和输出
(函数) [edit]

[编辑] 另请参阅

运算符的优先级

运算符重载

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

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->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 回收内存
sizeof 查询类型的大小
sizeof... 查询形参包的大小(C++11 起)
typeid 查询类型的类型信息
noexcept 检查表达式是否会抛出异常(C++11 起)
alignof 查询类型的对齐要求(C++11 起)