算术运算符将标准的数学运算应用于其操作数。
OperatorOperator nameExampleResult+unary plus+athe value of a after promotions
-unary minus-athe negative of a
+additiona + bthe addition of a and b
-subtractiona - bthe subtraction of b from a
*producta * bthe product of a and b
/divisiona / bthe division of a by b
%moduloa % bthe remainder of a divided by b
~bitwise NOT~athe bitwise NOT of a
&bitwise ANDa & bthe bitwise AND of a and b
|bitwise ORa | bthe bitwise OR of a and b
^bitwise XORa ^ bthe bitwise XOR of a and b
<
bitwise right shifta >> ba right shifted by b
溢出
无符号整数运算总是以模2n 来执行
其中 n 是该特定整数中的位数。例如unsigned int,添加一个UINT_MAX给出0,并从0给出中减去一个UINT_MAX。
当有符号整数算术运算溢出(结果不适合结果类型)时,行为是未定义的:它可能会根据表示规则(通常是2的补码)进行回绕,它可能陷入某些平台或由于编译器选项(例如-ftrapv在 GCC 和 Clang中),或者可以由编译器完全优化。
浮点环境
如果 #pragma STDC FENV_ACCESS设置为ON,所有浮点算术运算符服从当前浮点舍入方向并报告 math_errhandling 中指定的浮点算术错误,除非部分静态初始化程序(在这种情况下不引发浮点异常并舍入)模式是最近的)。
浮点收缩
除非#pragma STDC FP_CONTRACT设置为OFF,否则 所有浮点运算都可以像中间结果具有无限范围和精度一样执行,即忽略舍入错误和浮点异常(如果表达式完全按写入方式计算)时可以观察到的优化。例如,允许(x*y) + z使用单个融合乘加CPU指令或优化a = x*x*x*x;as执行tmp = x*x; a = tmp*tmp。
与浮点运算无关,浮点运算的中间结果的范围和精度可能与其类型所指示的不同,请参阅FLT_EVAL_METHOD。
一元算术
一元算术运算符表达式具有这种形式。
expression(1)expression(2)
1)一元加(促销)
2)一元减号(否定)
其中
expression-expression of any arithmetic type
一元加号和一元减号首先将积分促销应用于它们的操作数,然后。
一元加值返回提升后的值
一元减号返回提升后的值的负数(除了 NaN 的负数是另一个 NaN)
表达式的类型是升级后的类型,值类别是非左值。
笔记
当应用于,或在典型(2的补码)平台上时INT_MIN,由于有符号整数溢出,一元减法调用未定义的行为。LONG_MINLLONG_MIN
在 C ++中,一元运算符+也可以与其他内置类型(如数组和函数)一起使用,而在 C 中则不是这样。
#include #include int main(void){
char c = 'a'; printf("sizeof char: %zu sizeof int: %zu\n", sizeof c, sizeof +c);
printf("-1, where 1 is signed: %d\n", -1); printf("-1, where 1 is unsigned: %u\n", -1u);
double complex z = 1 + 2*I; printf("-(1+2i) = %.1f%+.1f\n", creal(-z), cimag(-z));}
可能的输出:
sizeof char: 1 sizeof int: 4-1, where 1 is signed: -1-1, where 1 is unsigned: 4294967295-(1+2i) = -1.0-2.0
添加操作符
二元附加算术运算符表达式具有这种形式。
lhs + rhs(1)lhs - rhs(2)
1)另外:lhs 和 rhs 必须是以下之一
都有算术类型,包括复杂和想象
一个是完成对象类型的指针,另一个是整数类型
2)减法:lhs 和 rhs 必须是以下之一
都有算术类型,包括复杂和想象
lhs具有完成对象类型的指针,rhs具有整数类型
都是指向兼容类型的完整对象的指针,忽略限定符
算术加法和减法
如果两个操作数都有算术类型,那么。
首先,执行通常的算术转换
然后,按照通常的数学规则(对于减法,将rhs从lhs中减去),对转换后的操作数的值进行相加或相减,除了如果一个操作数是 NaN,那么结果是NaN
无穷大减去无穷大是 NaN 并且FE_INVALID被提升
无限加上负的无穷大是 NaN 并且FE_INVALID被提升
复数和虚数加法和减法的定义如下(注意,如果两个操作数都是虚数,则结果类型为虚数,如果一个操作数是实数而另一个虚数,则如通常的算术转换所指定的那样):
or -uivu + ivxx ± ux ± iv(x ± u) ± iv
iy±u + iyi(y ± v)±u + i(y ± v)
x + iy(x ± u) + iyx + i(y ± v)(x ± u) + i(y ± v)
// work in progress// note: take part of the c/language/conversion example
指针算术
如果指针P指向具有索引的数组的元素I,则P+N并且N+P是指向具有索引的相同数组的元素的指针I+N
P-N 是一个指向索引{tt | IN}}的相同数组元素的指针
只有在原始指针和结果指针都指向同一数组的元素或超过该数组的末尾时,才会定义行为。请注意,当p指向数组的第一个元素时执行p-1是未定义的行为,并可能在某些平台上失败。
如果指针P1指向一个数组中的索引I(或者一个超过末尾的数组)P2的元素并且指向具有索引的相同数组的元素J(或者指向末尾的一个元素),那么P1-P2具有等于的值J-I和类型ptrdiff_t(这是一个有符号的整数类型,通常是可以声明的最大对象的大小的一半)
The behavior is defined only if the result fits in ptrdiff_t.
为了进行指针运算,指向不是任何数组元素的对象的指针将被视为指向大小为1的数组的第一个元素的指针。
// work in progressint n = 4, m = 3;int a[n][m]; // VLA of 4 VLAs of 3 ints eachint (*p)[m] = a; // p == &a[0] p = p + 1; // p == &a[1] (pointer arithmetic works with VLAs just the same)(*p)[2] = 99; // changes a[1][2]
乘法运算符
二进制乘法算术运算符表达式具有这种形式。
lhs * rhs(1)lhs / rhs(2)
lhs % rhs(3)
1)乘法。lhs 和 rhs 必须有算术类型
2)division。lhs 和 rhs 必须有算术类型
3)余数。lhs 和 rhs 必须具有整数类型
首先,执行通常的算术转换。然后...
Multiplication
二进制运算符*在通常的算术定义之后执行其操作数的乘法(在通常的算术转换之后),除此之外。
如果一个操作数是 NaN,则结果是 NaN
如果无穷大乘以零,就会产生NaN并被FE_INVALID提升
无穷乘以非零乘法给无穷(即使对于复杂的参数)
因为在C中,任何具有至少一个无限部分的复数值作为无穷大,即使其另一部分是NaN,通常的算术规则也不适用于复数复数乘法。浮点操作数的其他组合遵循下表:
*uivu + ivxxui(xv)(xu) + i(xv)
iyi(yu)−yv(−yv) + i(yu)
x + iy(xu) + i(yu)(−yv) + i(xv)special rules
除无限处理外,不允许复数乘法溢出中间结果,除非 #pragma STDC CX_LIMITED_RANGE设置为ON,在这种情况下,可以按照(x + iy)×(u + iv)=(xu-yv)+ i (yu + xv),因为程序员承担限制操作数范围和处理无穷大的责任。
尽管不允许不适当的溢出,但复杂的乘法可能会引发虚假的浮点异常(否则实现非溢出版本非常困难)。
Division
除了那个之外,二元运算符/第一操作数除以第二操作数(在通常的算术转换之后)遵循通常的算术定义。
当通常的算术转换后的类型是一个整数类型时,结果是代数商(不是分数),在实现定义的方向上舍入(直到C99)向零截断(因为C99)
如果一个操作数是NaN,则结果是NaN
如果第一个操作数是复数无穷大,第二个操作数是有限的,那么
/运算符的结果是一个复杂的无穷大。
如果第一个操作数是有限的,第二个操作数是一个复数的无穷大,那么
/运算符的结果是零。
因为在C中,任何具有至少一个无限部分的复数值作为无穷大,即使其另一部分是NaN,通常的算术规则也不适用于复数复数除法。浮点操作数的其他组合遵循下表:
/uivxx/ui(−x/v)
iyi(y/u)y/v
x + iy(x/u) + i(y/u)(y/v) + i(−x/v)
除无限处理外,复数除法不允许溢出中间结果,除非 #pragma STDC CX_LIMITED_RANGE设置为ON,在这种情况下,可以按照(x + iy)/(u + iv)=(xu + yv)+ i (宇-15)/(U2
+ v2
),因为程序员承担限制操作数范围和处理无穷大的责任。
尽管不允许不适当的溢出,复杂的划分可能会引发虚假的浮点异常(否则实现非溢出版本非常困难)。
如果第二个操作数为零,则行为是未定义的,但是如果支持 IEEE 浮点算术并且发生浮点除法,那么。
将非零数字除以±0.0会给出正确符号的无穷大,并将FE_DIVBYZERO其升高
将0.0除0.0得到NaN并FE_INVALID升高
余
二元运算符%产生第一个操作数除以第二个操作数的其余部分(在通常的算术转换后)。
余数的符号是这样定义的,即如果商a/b可以在结果类型中表示,那么(a/b)*b + a%b == a。
如果第二个操作数为零,则行为未定义。
如果商a/b不能在结果类型可表示,双方的行为a/b,并a%b没有定义(这意味着INT_MIN%-1是未定义2的补数系统)。
注意:余数运算符不适用于浮点类型,库函数fmod提供该功能。
#include#include #include #include int main(void){
// TODO simpler cases, take some from C++
double complex z = (1 + 0*I) * (INFINITY + I*INFINITY);// textbook formula would give// (1+i0)(∞+i∞) ⇒ (1×∞ – 0×∞) + i(0×∞+1×∞) ⇒ NaN + I*NaN// but C gives a complex infinity printf("%f + i*%f\n", creal(z), cimag(z)); // textbook formula would give// cexp(∞+iNaN) ⇒ exp(∞)×(cis(NaN)) ⇒ NaN + I*NaN// but C gives ±∞+i*nan
double complex y = cexp(INFINITY + I*NAN); printf("%f + i*%f\n", creal(y), cimag(y)); }
可能的输出:
inf + i*inf
inf + i*nan
按位逻辑
按位算术运算符表达式具有这种形式。
~ rhs(1)lhs & rhs(2)
lhs | rhs(3)
lhs ^ rhs(4)
1) bitwise NOT
2) bitwise AND
3) bitwise OR
4) bitwise XOR
其中
lhs, rhs-expressions of integer type
首先,运营商&,^和| 对两个操作数执行通常的算术转换操作符〜对其唯一的操作数执行整数提升。
然后,相应的二进制逻辑运算符按位进行应用; 也就是说,根据逻辑操作(NOT,AND,OR 或 XOR)将结果的每一位置1或清零,并将其应用于操作数的相应位。
注意:位运算符通常用于操作位集和位掩码。
注意:对于无符号类型(推广后),表达式〜E等于结果类型可表示的最大值减去E的原始值。
#include #include int main(void){
uint16_t mask = 0x00f0;
uint32_t a = 0x12345678; printf("Value: %#x mask: %#x\n" "Setting bits: %#x\n" "Clearing bits: %#x\n" "Selecting bits: %#x\n",
a,mask,(a|mask),(a&~mask),(a&mask));}
可能的输出:
Value: 0x12345678 mask: 0xf0Setting bits: 0x123456f8Clearing bits: 0x12345608Selecting bits: 0x70
移位操作
按位移运算符表达式具有这种形式。
lhs << rhs(1)lhs >> rhs(2)
1)左移 rhs 位
2)通过 rhs 位右移 lhs
其中
lhs, rhs-expressions of integer type
首先,对每个操作数单独执行整数升级(注意:这不同于其他二进制算术运算符,它们都执行常规算术转换)。结果的类型是促销后的 lhs 类型。
对于无符号的 lhs,其值LHS << RHS是 LHS * 2RHS 的值
返回类型的模数最大值减1加(即,执行按位左移,并且丢弃从目标类型移出的位)。对于有符号的 lhs,其值为LHS << RHSLHS * 2RHS
如果它在升级类型的 lhs 中可表示,否则行为是未定义的。
对于无符号的 lhs 和带有非负值的带符号的 lhs,其值LHS >> RHS是 LHS / 2RHS 的整数部分
对于负数LHS,值LHS >> RHS是实现定义的,在大多数实现中,这将执行算术右移(以便结果保持负值)。因此,在大多数实现中,向右移位一个符号LHS将使用原始符号位填充新的较高位(即,如果它是非负的则为0,如果是负的则为0)。
在任何情况下,如果 rhs 为负数或者大于或等于升级的 lhs 中的位数,则行为是不确定的。
#include enum {ONE=1, TWO=2};int main(void){
char c = 0x10;
unsigned long long ulong_num = 0x123; printf("0x123 <
ulong_num <
long long long_num = -1000; printf("-1000 >> 1 = %lld\n", long_num >> ONE); // implementation defined}
可能的输出:
0x123 <> 1 = -500