運算符是檢查、改變、合并值的特殊符号或短語。例如,加号( + )将兩個數相加(如 let i = 1 + 2)。 更複雜的運算例子包括邏輯與運算符 && (如 if enteredDoorCode && passedRetinaScan )。
Swift 支援大部分标準 C 語言的運算符,且改進許多特性來減少正常編碼錯誤。如:指派符( = )不傳回值,以 防止把想要判斷相等運算符( == )的地方寫成指派符導緻的錯誤。算術運算符( + , - , * , / , % 等)會 檢測并不允許值溢出,以此來避免儲存變量時由于變量大于或小于其類型所能承載的範圍時導緻的異常結果。當然允許你使用 Swift 的溢出運算符來實作溢出。
Swift 還提供了 C 語言沒有的表達兩數之間的值的區間運算符( a..<b 和 a...b),這友善我們表達一個區間内的數值。
本章節隻描述了 Swift 中的基本運算符,進階運算符這章會包含 Swift 中的進階運算符,及如何自定義運算符,及如何進行自定義類型的運算符重載。
術語
運算符分為一進制、二進制和三元運算符:
一進制運算符對單一操作對象操作(如 -a)。一進制運算符分别前置運算符和後置運算符,前置運算符需緊跟在操作對象之前(如 !b),後置運算符需緊跟在操作對象之後(如 c!)。
二進制運算符操作對象有兩個操作對象(如 2 + 3),是中置的,因為它們出現在兩個操作對象之間。
三元運算符操作三個操作對象,和 C 語言一樣,swift 隻有一個三元運算符,就是三目運算符(a ? b : c)。
受運算符影響的值叫做操作數,在表達式 1 + 2 中,加号 + 是二進制運算符,它的兩個操作數是值 1 和 2.
指派運算符
指派運算符 (a = b),表示用 b 的值來初始化或更新 a 的值。
let b = 10
var a = 5
a = b
// a 現在等于 10
如果指派的右邊是一個多元組,它的元素可以馬上被分解成多個常量或變量:
let (x, y) = (1, 2)
// 現在 x 等于 1 ,y 等于 2
與 C 語言和 Objective-C 不同,swift 的指派操作并不傳回任何值。是以在 if 語句中使用是錯誤的:
if x = y {
// 此句錯誤,因為x = y 并不傳回任何值
}
這個特性使你無法把(==)錯寫成(=),由于 if x = y 是錯誤代碼,swift 能幫你避免此類錯誤發生。
算數運算符
swift 中所有數值類型都支援了基本的四則算術運算符:
加法(+)
減法(-)
乘法(*)
除法(/)
與 C 語言和 Objective-C 不同的是,swift 預設情況下不允許在數值運算中出現溢出情況。但是你可以使用 swift 的溢出運算符來實作溢出運算(如 a &+ b)。
加法運算符也可用于 string 的拼接:
"hello, " + "world" // 等于 "hello,world"
求餘運算符
求餘運算符(a % b)是計算 b 的多少倍剛剛好可以容入 a, 傳回多出來的那部分(餘數)。
注意:
求餘運算符(%)在其他語言中叫取模運算符。然而嚴格說來,我們看該運算符對負數的操作結果,求餘比取模更合适些。
來談談取餘是怎麼回事,計算 9 % 4 ,你先計算出 4 的多少倍會剛好可以容入 9 中:
可以在 9 中放入兩個 4 ,那餘數是 1 。
在 swift 中可以表達為:
9 % 4 // 等于1
為了得到 a % b 的結果, % 計算了以下等式,并輸出 餘數 作為結果。
a = (b * 倍數) + 餘數
當倍數取最大值的時候,就會剛好可以容入 a 中。
把 9 和 4 帶入等式中,我們得到 1 :
9 = (4 * 2) + 1
同樣的方法,我們計算 -9 % 4:
-9 % 4 // 等于 -1
把 -9 和 4 代入等式,-2 是取到的最大整數:
-9 = (4 * -2) + -1
餘數是 -1。
在對負數 b 求餘時,b 的符号會被忽略。這意味着 a % b 和 a % -b 的結果是一樣的。
一進制負号運算符
數值的正負号可以使用字首 - (即一進制負号符)來切換:
let three = 3
let minusThree = -three // minusThree 等于 -3
let plusThree = -minusThree // plusThree 等于 3,或 “負負3”
一進制負号符(-) 寫在操縱數之前,中間沒有空格。
一進制正号運算符
一進制正号符(+)不做任何改變的傳回操作數的值:
let minusSix = -6
let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6
雖然一進制正号符什麼都不會改變,但當你在使用一進制負号來表達負數時,你可以使用一進制正号來表達正數。
組合指派運算符
如同 C 語言,swift 也提供把其他運算符和指派運算符(=)組合的組合指派運算符,組合加運算(+=)是其中一個例子:
var a = 1
a += 2
// a 現在是 3
表達式 a += 2 是 a = a + 2 的簡寫,一個組合加運算就是把加法運算和指派運算組合成進一個運算符裡,同時完成兩個運算任務。
注意:
複合指派運算沒有傳回值,let b = a += 2 這類代碼有錯誤。這不同于上面提到的自增和自減運算符。
比較運算符(comparison Operators)
所有标準 C 語言中的比較運算符都可以在 swift 中使用:
等于(==)
不等于(!=)
大于(>)
小于(<)
大于等于(>=)
小于等于(<=)
注意:
swift 也提供恒等(===)和不恒等(!==)這兩個比較符類判斷兩個對象是否引用同一個對象執行個體。
每個比較運算符都傳回了一個辨別表達式是否成立的布爾值:
1 == 1 // true
2 != 1 // true
2 < 1 // false
...
比較運算符多用于條件語句中,如 if 條件:
let name = "world"
if name == "world" {
print("hello,world")
} else {
print("I\'m sorry \(name), but I don\'t recognize you")
}
// 輸出 "hello, world", 因為 ‘name’ 就是等于 “world”
當元組中的值可以比較時,你也可以使用這些運算符來比較它們的大小。例如,因為 Int 和 String 類型的值可以比較,是以類型為(Int, String)的元組也可以比較。相反,Bool 不能被比較,也意味着存在有布爾類型的元組不能被比較。
比較元組大小會按照從左至右、逐值比較的方式,直到發現有兩個不等時停止。如果所有的值都相等,那麼這一對元組我們就稱它們是相等的。例如:
(1, "zebra")< (2, "apple") // ture 2 > 1
(3, "apple") < (3, "bird") // true apple < bird
(4, "dog") == (4, "dog") // ture
在上面的例子中,你可以看到,在第一行中從左至右的比較行為。因為 1 小于 2,是以(1, "zebra")小于 (2,"apple"), 不管元組剩下的值如何。是以 “zebra” 小于 "apple" 沒有任何影響,因為元組的比較已經被第一個元素決定了。不過,當元組的第一個元素相同時候,第二個元素将會用作比較,第二行和第三行代碼就發生了這樣的比較。
注意:
swift 标準庫隻能比較七個以内元素的元組比較函數。如果你的元組元素超過七個時,你需要自己實作比較運算符。
三目運算符(Ternary Conditional Operator)
三目運算符的特殊在于它是有三個操作數的運算符,它的形式是 問題?答案1: 答案2。它簡潔地表達根據 問題 成立與否作出二選一的操作。如果問題成立,傳回答案1的結果,反之傳回 答案2 的結果。
三目運算符是以下代碼的縮寫形式:
if question {
answer1
} else {
answer2
}
三目運算符提供有效率且便捷的方式來表達二選一的選擇。需要注意的事,過度使用三目運算符會使簡潔的代碼變的難懂。我們應避免在一個組合語句中使用多個三目運算符。
空合運算符
空合運算符(a ?? b)将對可選類型 a 進行空判斷,如果 a 包含一個值就進行解封,否則就傳回一個預設值 b。表達式 a 必須是 Optional 類型。預設值 b 的類型必須要和 a 存儲值的類型保持一緻。
空合運算符是對以下代碼的簡短表達方法:
a != nil? a!: b
上述代碼使用了三目運算符。當可選類型 a 的值不為空時,進行強制解封(a!),通路 a 中的值;反之傳回預設值 b。無疑空合運算(??)提供了一種更為優雅的方式去封裝條件判斷和解封兩種行為,顯得簡潔以及更具可讀性。
注意:
如果 a 為非空值(non-nil),那麼值 b 将不會被計算。這也就是所謂的短路求值。
下文例子采用空合運算符,實作了在預設顔色名和可選自定義顔色名之間抉擇:
let defaultColorName = "red"
var userDefinedColorName: String? // 預設值是 nil
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 的值為空,是以 colorNameToUse 的值為 “red”
userDefinedColorName 變量被定義為一個可選的 String 類型,預設值為 nil。由于 userDefinedColorName 是一個可選類型,我們可以使用空合運算符去判斷其值。在上一個例子中,通過空合運算符為一個名為 colorNameToUse 的變量賦予一個字元串類型初始值。由于 userDefinedColorName 值為空,是以表達式 userDefinedColorName ?? defaultColorName 傳回 defaultColorName 的值,即 red。
另一情況,配置設定一個非空值(non-nil)給 userDefinedColorName,再次執行空合運算,運算結果為封包在 userDefaultColorName 中的值,而非預設值。
userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
區間運算符(Range Operators)
swift 提供了兩個友善表達一個區間的值的區間運算符。
閉區間運算符
閉區間運算符(a...b)定義了一個從 a 到 b (包括 a 和 b )的所有值的區間。a 的值不能超過 b。閉區間運算符在疊代一個區間的所有值時是非常有用的,如在 for-in 循環中:
for index in 1...5 {
print("\(index) * 5 = \(index * 5)")
}
半開區間運算符
半開區間運算符(a..<b)定義一個從 a 到 b 但不包括 b 的區間。之是以稱為半開區間,是因為該區間包含第一個值而不包括最後一個值。
半開區間的實用性在于當你使用一個從 0 開始的清單(如數組)時,非常友善地從 0 數到清單的長度。
let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..< count {
print("第 \(i + 1) 個人叫\(names[i])")
}
數組有 4 個元素,但 0..<count 隻數到 3 (最後一個元素的下标),因為它是半開區間。
邏輯運算符
邏輯運算符的操作對象是邏輯布爾值。swift 支援基于 C 語言的三個标準邏輯運算符。
邏輯非 (!a)
邏輯與 (a && b)
邏輯或 (a || b)
邏輯非運算符
邏輯非運算符(!a)對一個布爾值取反,使得 true 變 false ,false 變 true。
它是一個前置運算符,需緊跟在操作數之前,且不加空格。讀作 非a, 例子如下:
let allowedEntry = false
if !allowedEntry {
print("ACCESS DENIED")
}
if !allowedEntry 語句可以讀作 如果 非 allowedEntry ,接下一行代碼隻有在 非allowedEntry 為 true, 即 allowEntry 為 false 時被執行。
在執行個體代碼中,小心地選擇布爾常量或變量有助于代碼的可讀性,并且避免使用雙重邏輯非運算,或混亂的邏輯語句。
邏輯與運算
邏輯與運算符(a && b)表達了隻有 a 和 b 的值都為 true 時,整個表達式的值才會是 true。
隻要任意一個值為 false,整個表達式的值就為 false。事實上,如果第一個值為 false,那麼是不去計算第二個值的。因為它已經不可能影響整個表達式的結果了。這種被稱作短路運算(short-circuit evaluation)。
邏輯或運算符
邏輯或運算符(a || b)是一個由兩個 || 組成的中置運算符。它表示了兩個邏輯表達式的其中一個為 true ,整個表達式就為 true。
同邏輯與運算符類似,邏輯或也是 短路計算 的,當左端的表達式為 true 時,将不計算右邊的表達式了,因為它不可能改變整個表達式的值了。
邏輯運算符組合計算
我們可以組合多個邏輯運算符來表達一個複合邏輯:
if enteredDoorCode && passedRetinaScan || hasDoorKey || KnowOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
這個例子使用了含多個 && 和 || 的複合邏輯。但無論怎樣,&& 和 || 始終隻能操作兩個值。是以這實際是三個簡單邏輯連續操作的結果。
swift 邏輯操作符 && 和 || 是左結合的,這意味着擁有多元邏輯操作符的複合表達式優先計算最左邊的子表達式。
使用括号來明确優先級
為了一個複雜表達式更容易讀懂,在合适的地方使用括号來明确優先級是很有效的,雖然它并非必要的,在上個關于門的權限的例子中,我們給第一個部分加個括号,使它看起來邏輯更明确:
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
這括号使得前兩個值被看成整個邏輯表達中獨立的一個部分。雖然有括号和沒括号的輸出結果是一樣的,但對于讀代碼的人來說有括号的代碼更清晰。可讀性比簡潔性更重要。
END