第 9 讲:运算符(一):算术运算符
Part 1 运算符是什么?
为了简化使用 C# 程序,C# 使用一些类似于数学符号的东西来表示一些运算,进而使得代码更具有可读性。
可读性
C# 里提供了众多的运算符,只有你想不到的,没有它做不到的。大体上 C# 把运算符分成如下若干类:
算术运算符(
+
、-
、*
、/
、%
)和正负号(+
、-
);自增自减运算符(
++
、--
);比较运算符(
>
、>=
、<
、<=
、==
、!=
);逻辑运算符(
&&
、||
、&
、|
、^
、!
)和逻辑元运算符(true
、false
);位运算符(
&
、|
、^
、~
、>>
、<<
);赋值运算符(
=
、+=
、-=
、*=
、/=
、%=
、&=
、|=
、^=
、>>=
、<<=
);条件运算符(
?:
);类型判断运算符(
is
、as
);溢出校验运算符(
checked
、unchecked
);默认运算符(
default(T)
);强制转换运算符(
(T)
)。
按照顺序,我们一个一个来讲解。
为了保证调理清晰,我们按照运算符的类型进行挨个讲解,一种运算符用一篇文章来介绍。虽然内容可能没那么多,但是也不必放在一起,这样文章太长了。
另外,C# 还有一些别的运算符号,比如
.
、->
、[]
、*
、&
等等。这些运算符具有特殊用途和用法,且并不一定代表一种“运算”,因此我们不单独介绍它们,而是通过贯穿教程的方式对这些内容进行讲解;还有一些在之前的内容也说过了,所以就不用多说了。
Part 2 没啥可讲的?我不信
算术运算符是运算符里最基础、最容易学习的一种运算符类型。它不需要专业编程知识就可以掌握,因为它确实长得很像是数学运算符。
+
、-
、*
均和数学运算的算法没有区别。
2-1 整数取模运算
最后一个叫做取模运算(%
),它虽然写成数学的百分号,但实际上是除法运算。不过取模运算是将数据进行整除,然后取出余数作为结果的一种运算模型。它等价于下面的这个数学公式:
如果我们有式子
则有数学表达式
数学运算符 Mod %
,故上述式子也可以表示为
举个例子:
c
的结果是 2。
另请注意,当取模运算符
%
的左侧这个数我们称为被取模数。整数取模运算一定用的是取模运算的两个计算数值的绝对值。比如 -13 % 7 得当成 13 % 7 进行计算。而结果的正负一定取决于被取模数。换句话说,只有被取模数是负数,结果才会是负数;否则怎么着,结果都是正的。比如-13 % -7
的结果一定是-(13 % 7)
,即 -6。
2-2 整数除法运算
前面我们说到了取模运算,如果说取模运算是取出余数的话,整数除法运算就刚好取的是商。还是看到前面的式子 (1)。这个式子里的 是这个除法式子的商,所以整数除法运算的结果就是这个 。
C# 里,如果除号 /
两侧的数字全部是整数的话,这个除号就应当看作是一个整数除法运算。因此,对照前面的例子,3 / 4
的结果应该就是 0,因为 3 根本不够被 4 除。如果是 17 / 3
呢?那就是 5 了。
2-3 浮点数除法运算
我们稍微换一下顺序。这次浮点数我们先说除法运算,然后再来讲解取模运算。
当我们依旧使用符号 /
,但左右两侧包含浮点数类型的数据的时候,我们就不能套用整数的除法运算了。因为浮点数是包含小数部分的,因此整体的结果就一定会是一个小数。
C# 里约定,当除号 /
的左右两侧数值里,至少有一个是 float
、double
和 decimal
的其一的话,我们就认为除法式子里包含小数。此时,就必须照着浮点数除法运算的规则(就是这一部分的内容)来计算。浮点数除法和数学除法没有区别,并不是带余除法。比如说
由于 3 和 4 均是整数,因此我们无法得到小数结果。我们必须得到 0.75 的话,就需要把至少其中一边改成小数,才能得到小数结果。从这则示例里可以看到,我们将 3 强制转换为 double
类型,然后得到一个浮点数除法运算的结果 0.75,最后赋值给 result
变量。
稍微注意一下的是,小括号没有必要写这么多。按照 C# 的习惯,就算你不打括号,C# 也知道这里
(double)3 / 4
是看成((double)3) / 4
而不是(double)(3 / 4)
的。那么,它们有什么区别呢?如果是前者,那么式子就好比是
3.0 / 4
一样。我们将 3 认为是 3.0。虽然 3 和 3.0 在数学上没区别,但是 C# 里因为字面量类型的问题,3 是int
类型(整数),而 3.0 是double
类型(浮点数),因此类型影响着整个运算是整数除法还是浮点数除法。而如果是后者,式子就相当于是把
3 / 4
的结果得到后,才转换成double
。前文说到,如果除号的两侧没有浮点数的话,那么结果就一定是按整数除法来计算的。那么3 / 4
的结果就必然为 0,故式子就好比是(double)0
,即 0.0,没有意义的转换。因此,强制转换运算符仅和它旁边这个数值进行“结合”。
这就是浮点数的除法。
2-4 浮点数取模运算
C# 里除了可以对两个整数进行取模运算,还可以对浮点数计算。不过概念稍微和整数的取模运算有一点不一样,但它和数学上的余数运算是一样的算法。
比如 13.3 % 6.3
。我们只需要找到一个合适的商,得到 2 这个结果;那么商乘以除数可以得到 12.6 这个结果;那么余数自然就是 13.3 - 12.6 = 0.7。
Part 3 浮点数运算的精度问题
我们尝试看一下如下的计算:
我们可以得到的结果是 0.3。但我们将 float
改成 double
:
即使我们去掉 F
你都不会得到 0.3 这个结果。实际上,后面两个的结果的小数位很后面依然有非 0 的数据信息。这是因为什么呢?
的格式)。这个式子里,由于一个十进制数要想表达成这个格式,且内存是有限大小的,因此我们无法准备表达一个数值信息,因而会丢失精度。
因此,在存储和赋值的时候,f
和 g
就已经不是真正的 0.1 和 0.2 了,因此结果必然不是真正的 0.3。因此,我们一定要注意浮点数的精度问题。
如果需要避免精度错误,我们需要用 decimal
类型。
下面我们来思考一个问题。为什么第一个例子(
float
类型运算)里,结果依旧是 0.3 呢?精度会导致数据不准确,但为什么答案依旧是准确的 0.3 呢?答案是因为,0.1 和 0.2 在存储的时候,误差其实很小,以至于比
float
类型的最低精度还要小,所以算出来看起来没有问题,但是实际上答案应当和第二个例子里显示的结果一致(只是误差比精度还小,因此后续的部分被舍弃了),因为等于 0.300000... 的后面就全部为 0 了,自然显示的时候就把全部的小数部分的 0 全给省略了。
Part 4 字符串的加法
由于 C# 里有一种新的数据类型(字符串),这是 C 语言里没有的数据类型,因此它有特殊的运算规则:字符串的加法。
字符串的加法说白了,就是字符串的拼接。
我们通过加法运算符 +
来拼接两个字符串。s + t
将字符串拼接起来,因此输出的内容就是 Hello, Sunnie!
。
本节我们学习了基本的五个运算符。一定要注意浮点数和整数对除法运算的不同行为。稍微注意一下,字符串的加法运算。