第 9 講:運(yùn)算符(一):算術(shù)運(yùn)算符
Part 1 運(yùn)算符是什么?
為了簡化使用 C# 程序,C# 使用一些類似于數(shù)學(xué)符號的東西來表示一些運(yùn)算,進(jìn)而使得代碼更具有可讀性。
可讀性
C# 里提供了眾多的運(yùn)算符,只有你想不到的,沒有它做不到的。大體上 C# 把運(yùn)算符分成如下若干類:
算術(shù)運(yùn)算符(
+
、-
、*
、/
、%
)和正負(fù)號(+
、-
);自增自減運(yùn)算符(
++
、--
);比較運(yùn)算符(
>
、>=
、<
、<=
、==
、!=
);邏輯運(yùn)算符(
&&
、||
、&
、|
、^
、!
)和邏輯元運(yùn)算符(true
、false
);位運(yùn)算符(
&
、|
、^
、~
、>>
、<<
);賦值運(yùn)算符(
=
、+=
、-=
、*=
、/=
、%=
、&=
、|=
、^=
、>>=
、<<=
);條件運(yùn)算符(
?:
);類型判斷運(yùn)算符(
is
、as
);溢出校驗(yàn)運(yùn)算符(
checked
、unchecked
);默認(rèn)運(yùn)算符(
default(T)
);強(qiáng)制轉(zhuǎn)換運(yùn)算符(
(T)
)。
按照順序,我們一個一個來講解。
為了保證調(diào)理清晰,我們按照運(yùn)算符的類型進(jìn)行挨個講解,一種運(yùn)算符用一篇文章來介紹。雖然內(nèi)容可能沒那么多,但是也不必放在一起,這樣文章太長了。
另外,C# 還有一些別的運(yùn)算符號,比如
.
、->
、[]
、*
、&
等等。這些運(yùn)算符具有特殊用途和用法,且并不一定代表一種“運(yùn)算”,因此我們不單獨(dú)介紹它們,而是通過貫穿教程的方式對這些內(nèi)容進(jìn)行講解;還有一些在之前的內(nèi)容也說過了,所以就不用多說了。
Part 2 沒啥可講的?我不信
算術(shù)運(yùn)算符是運(yùn)算符里最基礎(chǔ)、最容易學(xué)習(xí)的一種運(yùn)算符類型。它不需要專業(yè)編程知識就可以掌握,因?yàn)樗_實(shí)長得很像是數(shù)學(xué)運(yùn)算符。
+
、-
、*
均和數(shù)學(xué)運(yùn)算的算法沒有區(qū)別。
2-1 整數(shù)取模運(yùn)算
最后一個叫做取模運(yùn)算(%
),它雖然寫成數(shù)學(xué)的百分號,但實(shí)際上是除法運(yùn)算。不過取模運(yùn)算是將數(shù)據(jù)進(jìn)行整除,然后取出余數(shù)作為結(jié)果的一種運(yùn)算模型。它等價于下面的這個數(shù)學(xué)公式:
如果我們有式子
則有數(shù)學(xué)表達(dá)式
數(shù)學(xué)運(yùn)算符 Mod?%
,故上述式子也可以表示為
舉個例子:
c
的結(jié)果是 2。
另請注意,當(dāng)取模運(yùn)算符
%
的左側(cè)這個數(shù)我們稱為被取模數(shù)。整數(shù)取模運(yùn)算一定用的是取模運(yùn)算的兩個計算數(shù)值的絕對值。比如 -13 % 7 得當(dāng)成 13 % 7 進(jìn)行計算。而結(jié)果的正負(fù)一定取決于被取模數(shù)。換句話說,只有被取模數(shù)是負(fù)數(shù),結(jié)果才會是負(fù)數(shù);否則怎么著,結(jié)果都是正的。比如-13 % -7
的結(jié)果一定是-(13 % 7)
,即 -6。
2-2 整數(shù)除法運(yùn)算
前面我們說到了取模運(yùn)算,如果說取模運(yùn)算是取出余數(shù)的話,整數(shù)除法運(yùn)算就剛好取的是商。還是看到前面的式子 (1)。這個式子里的 是這個除法式子的商,所以整數(shù)除法運(yùn)算的結(jié)果就是這個 。
C# 里,如果除號 /
兩側(cè)的數(shù)字全部是整數(shù)的話,這個除號就應(yīng)當(dāng)看作是一個整數(shù)除法運(yùn)算。因此,對照前面的例子,3 / 4
的結(jié)果應(yīng)該就是 0,因?yàn)?3 根本不夠被 4 除。如果是 17 / 3
呢?那就是 5 了。
2-3 浮點(diǎn)數(shù)除法運(yùn)算
我們稍微換一下順序。這次浮點(diǎn)數(shù)我們先說除法運(yùn)算,然后再來講解取模運(yùn)算。
當(dāng)我們依舊使用符號 /
,但左右兩側(cè)包含浮點(diǎn)數(shù)類型的數(shù)據(jù)的時候,我們就不能套用整數(shù)的除法運(yùn)算了。因?yàn)楦↑c(diǎn)數(shù)是包含小數(shù)部分的,因此整體的結(jié)果就一定會是一個小數(shù)。
C# 里約定,當(dāng)除號 /
的左右兩側(cè)數(shù)值里,至少有一個是 float
、double
和 decimal
的其一的話,我們就認(rèn)為除法式子里包含小數(shù)。此時,就必須照著浮點(diǎn)數(shù)除法運(yùn)算的規(guī)則(就是這一部分的內(nèi)容)來計算。浮點(diǎn)數(shù)除法和數(shù)學(xué)除法沒有區(qū)別,并不是帶余除法。比如說
由于 3 和 4 均是整數(shù),因此我們無法得到小數(shù)結(jié)果。我們必須得到 0.75 的話,就需要把至少其中一邊改成小數(shù),才能得到小數(shù)結(jié)果。從這則示例里可以看到,我們將 3 強(qiáng)制轉(zhuǎn)換為 double
類型,然后得到一個浮點(diǎn)數(shù)除法運(yùn)算的結(jié)果 0.75,最后賦值給 result
變量。
稍微注意一下的是,小括號沒有必要寫這么多。按照 C# 的習(xí)慣,就算你不打括號,C# 也知道這里
(double)3 / 4
是看成((double)3) / 4
而不是(double)(3 / 4)
的。那么,它們有什么區(qū)別呢?如果是前者,那么式子就好比是
3.0 / 4
一樣。我們將 3 認(rèn)為是 3.0。雖然 3 和 3.0 在數(shù)學(xué)上沒區(qū)別,但是 C# 里因?yàn)樽置媪款愋偷膯栴},3 是int
類型(整數(shù)),而 3.0 是double
類型(浮點(diǎn)數(shù)),因此類型影響著整個運(yùn)算是整數(shù)除法還是浮點(diǎn)數(shù)除法。而如果是后者,式子就相當(dāng)于是把
3 / 4
的結(jié)果得到后,才轉(zhuǎn)換成double
。前文說到,如果除號的兩側(cè)沒有浮點(diǎn)數(shù)的話,那么結(jié)果就一定是按整數(shù)除法來計算的。那么3 / 4
的結(jié)果就必然為 0,故式子就好比是(double)0
,即 0.0,沒有意義的轉(zhuǎn)換。因此,強(qiáng)制轉(zhuǎn)換運(yùn)算符僅和它旁邊這個數(shù)值進(jìn)行“結(jié)合”。
這就是浮點(diǎn)數(shù)的除法。
2-4 浮點(diǎn)數(shù)取模運(yùn)算
C# 里除了可以對兩個整數(shù)進(jìn)行取模運(yùn)算,還可以對浮點(diǎn)數(shù)計算。不過概念稍微和整數(shù)的取模運(yùn)算有一點(diǎn)不一樣,但它和數(shù)學(xué)上的余數(shù)運(yùn)算是一樣的算法。
比如 13.3 % 6.3
。我們只需要找到一個合適的商,得到 2 這個結(jié)果;那么商乘以除數(shù)可以得到 12.6 這個結(jié)果;那么余數(shù)自然就是 13.3 - 12.6 = 0.7。
Part 3 浮點(diǎn)數(shù)運(yùn)算的精度問題
我們嘗試看一下如下的計算:
我們可以得到的結(jié)果是 0.3。但我們將 float
改成 double
:
即使我們?nèi)サ?F
你都不會得到 0.3 這個結(jié)果。實(shí)際上,后面兩個的結(jié)果的小數(shù)位很后面依然有非 0 的數(shù)據(jù)信息。這是因?yàn)槭裁茨兀?span id="s0sssss00s" class="md-plain md-expand">
的格式)。這個式子里,由于一個十進(jìn)制數(shù)要想表達(dá)成這個格式,且內(nèi)存是有限大小的,因此我們無法準(zhǔn)備表達(dá)一個數(shù)值信息,因而會丟失精度。
因此,在存儲和賦值的時候,f
和 g
就已經(jīng)不是真正的 0.1 和 0.2 了,因此結(jié)果必然不是真正的 0.3。因此,我們一定要注意浮點(diǎn)數(shù)的精度問題。
如果需要避免精度錯誤,我們需要用 decimal
類型。
下面我們來思考一個問題。為什么第一個例子(
float
類型運(yùn)算)里,結(jié)果依舊是 0.3 呢?精度會導(dǎo)致數(shù)據(jù)不準(zhǔn)確,但為什么答案依舊是準(zhǔn)確的 0.3 呢?答案是因?yàn)椋?.1 和 0.2 在存儲的時候,誤差其實(shí)很小,以至于比
float
類型的最低精度還要小,所以算出來看起來沒有問題,但是實(shí)際上答案應(yīng)當(dāng)和第二個例子里顯示的結(jié)果一致(只是誤差比精度還小,因此后續(xù)的部分被舍棄了),因?yàn)榈扔?0.300000... 的后面就全部為 0 了,自然顯示的時候就把全部的小數(shù)部分的 0 全給省略了。
Part 4 字符串的加法
由于 C# 里有一種新的數(shù)據(jù)類型(字符串),這是 C 語言里沒有的數(shù)據(jù)類型,因此它有特殊的運(yùn)算規(guī)則:字符串的加法。
字符串的加法說白了,就是字符串的拼接。
我們通過加法運(yùn)算符 +
來拼接兩個字符串。s + t
將字符串拼接起來,因此輸出的內(nèi)容就是 Hello, Sunnie!
。
本節(jié)我們學(xué)習(xí)了基本的五個運(yùn)算符。一定要注意浮點(diǎn)數(shù)和整數(shù)對除法運(yùn)算的不同行為。稍微注意一下,字符串的加法運(yùn)算。