Arithmetic operators
算术运算符
算术运算符将标准的数学运算应用于其操作数。
Operator | Operator name | Example | Result |
---|---|---|---|
+ | unary plus | +a | the value of a after promotions |
- | unary minus | -a | the negative of a |
+ | addition | a + b | the addition of a and b |
- | subtraction | a - b | the subtraction of b from a |
* | product | a * b | the product of a and b |
/ | division | a / b | the division of a by b |
% | modulo | a % b | the remainder of a divided by b |
~ | bitwise NOT | ~a | the bitwise NOT of a |
& | bitwise AND | a & b | the bitwise AND of a and b |
| | bitwise OR | a | b | the bitwise OR of a and b |
^ | bitwise XOR | a ^ b | the bitwise XOR of a and b |
<< | bitwise left shift | a << b | a left shifted by b |
bitwise right shift | a >> b | a 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 |
---|
一元加号和一元减号首先将积分促销应用于它们的操作数,然后。
- 一元加值返回提升后的值
表达式的类型是升级后的类型,值类别是非左值。
笔记
当应用于,或在典型(2的补码)平台上时INT_MIN
,由于有符号整数溢出,一元减法调用未定义的行为。LONG_MINLLONG_MIN
在 C ++中,一元运算符+也可以与其他内置类型(如数组和函数)一起使用,而在 C 中则不是这样。
#include <stdio.h>
#include <complex.h>
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 必须是以下之一
- 都有算术类型,包括复杂和想象
算术加法和减法
如果两个操作数都有算术类型,那么。
- 首先,执行通常的算术转换
复数和虚数加法和减法的定义如下(注意,如果两个操作数都是虚数,则结果类型为虚数,如果一个操作数是实数而另一个虚数,则如通常的算术转换所指定的那样):
or - | u | iv | u + iv |
---|---|---|---|
x | x ± u | x ± iv | (x ± u) ± iv |
iy | ±u + iy | i(y ± v) | ±u + i(y ± v) |
x + iy | (x ± u) + iy | x + i(y ± v) | (x ± u) + i(y ± v) |
// work in progress
// note: take part of the c/language/conversion example
指针算术
- 如果指针
P
指向具有索引的数组的元素I
,则
只有在原始指针和结果指针都指向同一数组的元素或超过该数组的末尾时,才会定义行为。请注意,当p指向数组的第一个元素时执行p-1是未定义的行为,并可能在某些平台上失败。
- 如果指针
P1
指向一个数组中的索引I
(或者一个超过末尾的数组)P2
的元素并且指向具有索引的相同数组的元素J
(或者指向末尾的一个元素),那么
The behavior is defined only if the result fits in ptrdiff_t
.
为了进行指针运算,指向不是任何数组元素的对象的指针将被视为指向大小为1的数组的第一个元素的指针。
// work in progress
int n = 4, m = 3;
int a[n][m]; // VLA of 4 VLAs of 3 ints each
int (*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
因为在C中,任何具有至少一个无限部分的复数值作为无穷大,即使其另一部分是NaN,通常的算术规则也不适用于复数复数乘法。浮点操作数的其他组合遵循下表:
* | u | iv | u + iv |
---|---|---|---|
x | xu | i(xv) | (xu) + i(xv) |
iy | i(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)
/运算符的结果是一个复杂的无穷大。
- 如果第一个操作数是有限的,第二个操作数是一个复数的无穷大,那么
/运算符的结果是零。
因为在C中,任何具有至少一个无限部分的复数值作为无穷大,即使其另一部分是NaN,通常的算术规则也不适用于复数复数除法。浮点操作数的其他组合遵循下表:
/ | u | iv |
---|---|---|
x | x/u | i(−x/v) |
iy | i(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
其升高
余
二元运算符%产生第一个操作数除以第二个操作数的其余部分(在通常的算术转换后)。
余数的符号是这样定义的,即如果商a/b
可以在结果类型中表示,那么(a/b)*b + a%b == a
。
如果第二个操作数为零,则行为未定义。
如果商a/b
不能在结果类型可表示,双方的行为a/b
,并a%b
没有定义(这意味着INT_MIN%-1
是未定义2的补数系统)。
注意:余数运算符不适用于浮点类型,库函数fmod
提供该功能。
#include<stdio.h>
#include <stdio.h>
#include <complex.h>
#include <math.h>
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 <stdio.h>
#include <stdint.h>
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: 0xf0
Setting bits: 0x123456f8
Clearing bits: 0x12345608
Selecting 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 <stdio.h>
enum {ONE=1, TWO=2};
int main(void)
{
char c = 0x10;
unsigned long long ulong_num = 0x123;
printf("0x123 << 1 = %#llx\n"
"0x123 << 63 = %#llx\n" // overflow truncates high bits for unsigned numbers
"0x10 << 10 = %#x\n", // char is promoted to int
ulong_num << 1, ulong_num << 63, c << 10
long long long_num = -1000;
printf("-1000 >> 1 = %lld\n", long_num >> ONE // implementation defined
}
可能的输出:
0x123 << 1 = 0x246
0x123 << 63 = 0x8000000000000000
0x10 << 10 = 0x4000
-1000 >> 1 = -500