算术运算符(C# 参考)
以下运算符对数值类型的操作数执行算术运算:
对于整型类型,这些运算符(除 ++
和 --
运算符以外)是为 int
、uint
、long
和 ulong
类型定义的。 如果操作数都是其他整型类型(sbyte
、byte
、short
、ushort
或 char
),它们的值将转换为 int
类型,这也是一个运算的结果类型。 如果操作数是不同的整型类型或浮点类型,它们的值将转换为最接近的包含类型(如果存在该类型)。 有关详细信息,请参阅 C# 语言规范的数值提升部分。 ++
和 --
运算符是为所有整型和浮点数值类型以及 char 类型定义的。 复合赋值表达式的结果类型是左侧操作数的类型。
增量运算符 ++
一元增量运算符 ++
按 1 递增其操作数。 操作数必须是变量、属性访问或索引器访问。
增量运算符以两种形式进行支持:后缀增量运算符 x++
和前缀增量运算符 ++x
。
后缀递增运算符
x++
的结果是此操作前的 x
的值,如以下示例所示:
int i = 3;
Console.WriteLine(i); // output: 3
Console.WriteLine(i++); // output: 3
Console.WriteLine(i); // output: 4
前缀增量运算符
++x
的结果是此操作后的 x
的值,如以下示例所示:
double a = 1.5;
Console.WriteLine(a); // output: 1.5
Console.WriteLine(++a); // output: 2.5
Console.WriteLine(a); // output: 2.5
减量运算符 --
一元减量运算符 --
按 1 递减其操作数。 操作数必须是变量、属性访问或索引器访问。
减量运算符以两种形式进行支持:后缀减量运算符x--
和前缀减量运算符 --x
。
后缀递减运算符
x--
的结果是此操作前的 x
的值,如以下示例所示:
int i = 3;
Console.WriteLine(i); // output: 3
Console.WriteLine(i--); // output: 3
Console.WriteLine(i); // output: 2
前缀减量运算符
--x
的结果是此操作后的 x
的值,如以下示例所示:
double a = 1.5;
Console.WriteLine(a); // output: 1.5
Console.WriteLine(--a); // output: 0.5
Console.WriteLine(a); // output: 0.5
一元加和减运算符
一元 +
运算符返回其操作数的值。 一元 -
运算符对其操作数的数值取负。
Console.WriteLine(+4); // output: 4
Console.WriteLine(-4); // output: -4
Console.WriteLine(-(-4)); // output: 4
uint a = 5;
var b = -a;
Console.WriteLine(b); // output: -5
Console.WriteLine(b.GetType()); // output: System.Int64
Console.WriteLine(-double.NaN); // output: NaN
ulong 类型不支持一元 -
运算符。
乘法运算符 *
乘法运算符 *
计算其操作数的乘积:
Console.WriteLine(5 * 2); // output: 10
Console.WriteLine(0.5 * 2.5); // output: 1.25
Console.WriteLine(0.1m * 23.4m); // output: 2.34
一元 *
运算符是指针间接运算符。
除法运算符 /
除法运算符 /
用它的左侧操作数除以右侧操作数。
整数除法
对于整数类型的操作数,/
运算符的结果为整数类型,并且等于两个操作数之商向零舍入后的结果:
Console.WriteLine(13 / 5); // output: 2
Console.WriteLine(-13 / 5); // output: -2
Console.WriteLine(13 / -5); // output: -2
Console.WriteLine(-13 / -5); // output: 2
若要获取浮点数形式的两个操作数之商,请使用 float
、double
或 decimal
类型:
Console.WriteLine(13 / 5.0); // output: 2.6
int a = 13;
int b = 5;
Console.WriteLine((double)a / b); // output: 2.6
浮点除法
对于 float
、double
和 decimal
类型,/
运算符的结果为两个操作数之商:
Console.WriteLine(16.8f / 4.1f); // output: 4.097561
Console.WriteLine(16.8d / 4.1d); // output: 4.09756097560976
Console.WriteLine(16.8m / 4.1m); // output: 4.0975609756097560975609756098
如果操作数之一为 decimal
,那么另一个操作数不得为 float
和 double
,因为 float
和 double
都无法隐式转换为 decimal
。 必须将 float
或 double
操作数显式转换为 decimal
类型。 如需详细了解数值类型之间的转换,请参阅内置数值转换。
余数运算符 %
余数运算符 %
计算左侧操作数除以右侧操作数后的余数。
整数余数
对于整数类型的操作数,a % b
的结果是 a - (a / b) * b
得出的值。 非零余数的符号与左侧操作数的符号相同,如下例所示:
Console.WriteLine(5 % 4); // output: 1
Console.WriteLine(5 % -4); // output: 1
Console.WriteLine(-5 % 4); // output: -1
Console.WriteLine(-5 % -4); // output: -1
使用 Math.DivRem 方法计算整数除法和余数结果。
浮点余数
对于 float
和 double
操作数,有限的 x
和 y
的 x % y
的结果是值 z
,因此
z
(如果不为零)的符号与x
的符号相同。z
的绝对值是|x| - n * |y|
得出的值,其中n
是小于或等于|x| / |y|
的最大可能整数,|x|
和|y|
分别是x
和y
的绝对值。
注意
计算余数的此方法类似于用于整数操作数的方法,但与 IEEE 754 规范不同。 如果需要符合 IEEE 754 规范的余数运算,请使用 Math.IEEERemainder 方法。
有关非限定操作数的 %
运算符行为的信息,请参阅 C# 语言规范的余数运算符章节。
对于 decimal
操作数,余数运算符 %
等效于 System.Decimal 类型的余数运算符。
以下示例演示了具有浮动操作数的余数运算符的行为:
Console.WriteLine(-5.2f % 2.0f); // output: -1.2
Console.WriteLine(5.9 % 3.1); // output: 2.8
Console.WriteLine(5.9m % 3.1m); // output: 2.8
加法运算符 +
加法运算符 +
计算其操作数的和。
Console.WriteLine(5 + 4); // output: 9
Console.WriteLine(5 + 4.3); // output: 9.3
Console.WriteLine(5.1m + 4.2m); // output: 9.3
还可以将 +
运算符用于字符串串联和委托组合。 有关详细信息,请参阅 +
和 +=
运算符一文。
减法运算符 -
减法运算符 -
从其左侧操作数中减去其右侧操作数:
Console.WriteLine(47 - 3); // output: 44
Console.WriteLine(5 - 4.3); // output: 0.7
Console.WriteLine(7.5m - 2.3m); // output: 5.2
还可以使用 -
运算符删除委托。 有关详细信息,请参阅 -
和 -=
运算符一文。
复合赋值
对于二元运算符 op
,窗体的复合赋值表达式
x op= y
等效于
x = x op y
不同的是 x
只计算一次。
以下示例使用算术运算符演示了复合赋值的用法:
int a = 5;
a += 9;
Console.WriteLine(a); // output: 14
a -= 4;
Console.WriteLine(a); // output: 10
a *= 2;
Console.WriteLine(a); // output: 20
a /= 4;
Console.WriteLine(a); // output: 5
a %= 3;
Console.WriteLine(a); // output: 2
由于数值提升,op
运算的结果可能不会隐式转换为 x
的 T
类型。 在这种情况下,如果 op
是预定义的运算符并且运算的结果可以显式转换为 x
的类型 T
,则形式为 x op= y
的复合赋值表达式等效于 x = (T)(x op y)
,但 x
仅计算一次。 以下示例演示了该行为:
byte a = 200;
byte b = 100;
var c = a + b;
Console.WriteLine(c.GetType()); // output: System.Int32
Console.WriteLine(c); // output: 300
a += b;
Console.WriteLine(a); // output: 44
在前面的示例中,值 44
是将值 300
转换为 byte
类型的结果。
注意
在检查的溢出检查上下文中,前面的示例将引发 OverflowException。 有关详细信息,请参阅整数算术溢出部分。
你还可以使用 +=
和 -=
运算符分别订阅和取消订阅事件。 有关详细信息,请参阅如何订阅和取消订阅事件。
运算符优先级和关联性
以下列表对优先级由高到低的顺序对算术运算符进行排序:
- 后缀增量
x++
和减量x--
运算符 - 前缀增量
++x
和减量--x
以及一元+
和-
运算符 - 乘法
*
、/
和%
运算符 - 加法
+
和-
运算符
二元算数运算符是左结合运算符。 也就是说,具有相同优先级的运算符按从左至右的顺序计算。
使用括号 ()
更改由运算符优先级和关联性决定的计算顺序。
Console.WriteLine(2 + 2 * 2); // output: 6
Console.WriteLine((2 + 2) * 2); // output: 8
Console.WriteLine(9 / 5 / 2); // output: 0
Console.WriteLine(9 / (5 / 2)); // output: 4
如需了解按优先级排序的完整 C# 运算符列表,请参阅 C# 运算符一文中的运算符优先级部分。
算术溢出和被零除
当算术运算的结果超出所涉及数值类型的可能有限值范围时,算术运算符的行为取决于其操作数的类型。
整数算术溢出
整数被零除总是引发 DivideByZeroException。
如果发生整数算术溢出,溢出检查上下文(可能已检查或未检查)将控制引发的行为:
- 在已检查的上下文中,如果在常数表达式中发生溢出,则会发生编译时错误。 否则,当在运行时执行此运算时,则引发 OverflowException。
- 在未检查的上下文中,会通过丢弃任何不适应目标类型的高序位来将结果截断。
在使用 checked 和 unchecked 语句时,可以使用 checked
和 unchecked
运算符来控制溢出检查上下文,在该上下文中将计算一个表达式:
int a = int.MaxValue;
int b = 3;
Console.WriteLine(unchecked(a + b)); // output: -2147483646
try
{
int d = checked(a + b);
}
catch(OverflowException)
{
Console.WriteLine($"Overflow occurred when adding {a} to {b}.");
}
默认情况下,算术运算发生在 unchecked 上下文中。
浮点运算溢出
使用 float
和 double
类型的算术运算永远不会引发异常。 使用这些类型的算术运算的结果可能是表示无穷大和非数字的特殊值之一:
double a = 1.0 / 0.0;
Console.WriteLine(a); // output: Infinity
Console.WriteLine(double.IsInfinity(a)); // output: True
Console.WriteLine(double.MaxValue + double.MaxValue); // output: Infinity
double b = 0.0 / 0.0;
Console.WriteLine(b); // output: NaN
Console.WriteLine(double.IsNaN(b)); // output: True
对于 decimal
类型的操作数,算术溢出始终会引发 OverflowException。 被零除总是引发 DivideByZeroException。
舍入误差
由于实数和浮点运算的浮点表达形式的常规限制,在使用浮点类型的计算中可能会发生舍入误差。 也就是说,表达式得出的结果可能与预期的数学运算结果不同。 以下示例演示了几种此类情况:
Console.WriteLine(.41f % .2f); // output: 0.00999999
double a = 0.1;
double b = 3 * a;
Console.WriteLine(b == 0.3); // output: False
Console.WriteLine(b - 0.3); // output: 5.55111512312578E-17
decimal c = 1 / 3.0m;
decimal d = 3 * c;
Console.WriteLine(d == 1.0m); // output: False
Console.WriteLine(d); // output: 0.9999999999999999999999999999
有关详细信息,请参阅 System.Double、System.Single 或 System.Decimal 参考页上的注解。
运算符可重载性
用户定义类型可以重载一元(++
、--
、+
和 -
)和二元(*
、/
、%
、+
和 -
)算术运算符。 重载二元运算符时,对应的复合赋值运算符也会隐式重载。 用户定义类型不能显式重载复合赋值运算符。
已检查的用户定义的运算符
从 C# 11 开始,重载算术运算符时,可以使用 checked
关键字定义该运算符的已检查版本。 下面的示例演示如何执行此操作:
public record struct Point(int X, int Y)
{
public static Point operator checked +(Point left, Point right)
{
checked
{
return new Point(left.X + right.X, left.Y + right.Y);
}
}
public static Point operator +(Point left, Point right)
{
return new Point(left.X + right.X, left.Y + right.Y);
}
}
定义已检查的运算符时,还必须定义不带 checked
修饰符的相应运算符。 checked 运算符在已检查的上下文中调用;不带 checked
修饰符的运算符在未检查的上下文中调用。 如果你只提供了不带 checked
修饰符的运算符,则会在 checked
和 unchecked
上下文中调用该运算符。
定义这两个版本的运算符时,预期其行为仅在操作结果过大而无法在结果类型中表示时有所不同,如下所示:
- 已检查的运算符引发 OverflowException。
- 不带
checked
修饰符的运算符返回表示截断结果的实例。
有关内置算术运算符行为差异的信息,请参阅算术溢出和除以零部分。
仅当重载以下任意运算符时,才能使用 checked
修饰符:
- 一元
++
、--
和-
运算符 - 二元
*
、/
、+
和-
运算符 - 显式转换运算符
备注
checked
修饰符的存在不影响 checked 运算符正文中的溢出检查上下文。 默认上下文由 CheckForOverflowUnderflow 编译器选项的值定义。 使用 checked
和 unchecked
语句显式指定溢出检查上下文,如本部分开头的示例所示。
C# 语言规范
有关更多信息,请参阅 C# 语言规范的以下部分: