運算符是檢查、改變、合并值的特殊符號或短語。例如,加號(+
)將兩個數(shù)相加(如 let i = 1 + 2
)。更復(fù)雜的運算例子包括邏輯與運算符 &&
(如 if enteredDoorCode && passedRetinaScan
)。
Swift 支持大部分標(biāo)準(zhǔn) C 語言的運算符,且為了減少常見編碼錯誤做了部分改進。如:賦值符(=
)不再有返回值,這樣就消除了手誤將判等運算符(==
)寫成賦值符導(dǎo)致代碼錯誤的缺陷。算術(shù)運算符(+
,-
,*
,/
,%
等)的結(jié)果會被檢測并禁止值溢出,以此來避免保存變量時由于變量大于或小于其類型所能承載的范圍時導(dǎo)致的異常結(jié)果。當(dāng)然允許你使用 Swift 的溢出運算符來實現(xiàn)溢出。詳情參見 溢出運算符。
Swift 還提供了 C 語言沒有的區(qū)間運算符,例如 a..<b
或 a...b
,這方便我們表達一個區(qū)間內(nèi)的數(shù)值。
本章節(jié)只描述了 Swift 中的基本運算符,高級運算符 這章會包含 Swift 中的高級運算符,及如何自定義運算符,及如何進行自定義類型的運算符重載。
運算符分為一元、二元和三元運算符:
-a
)。一元運算符分前置運算符和后置運算符,前置運算符需緊跟在操作對象之前(如 !b
),后置運算符需緊跟在操作對象之后(如 c!
)。2 + 3
),是中置的,因為它們出現(xiàn)在兩個操作對象之間。a ? b : c
)。受運算符影響的值叫操作數(shù),在表達式 1 + 2
中,加號 +
是二元運算符,它的兩個操作數(shù)是值 1
和 2
。
賦值運算符(a = b
),表示用 b
的值來初始化或更新 a
的值:
let b = 10
var a = 5
a = b
// a 現(xiàn)在等于 10
如果賦值的右邊是一個多元組,它的元素可以馬上被分解成多個常量或變量:
let (x, y) = (1, 2)
// 現(xiàn)在 x 等于 1,y 等于 2
與 C 語言和 Objective-C 不同,Swift 的賦值操作并不返回任何值。所以下面語句是無效的:
if x = y {
// 此句錯誤,因為 x = y 并不返回任何值
}
通過將 if x = y
標(biāo)記為無效語句,Swift 能幫你避免把 (==
)錯寫成(=
)這類錯誤的出現(xiàn)。
Swift 中所有數(shù)值類型都支持了基本的四則算術(shù)運算符:
+
)-
)*
)/
)1 + 2 // 等于 3
5 - 3 // 等于 2
2 * 3 // 等于 6
10.0 / 2.5 // 等于 4.0
與 C 語言和 Objective-C 不同的是,Swift 默認(rèn)情況下不允許在數(shù)值運算中出現(xiàn)溢出情況。但是你可以使用 Swift 的溢出運算符來實現(xiàn)溢出運算(如 a &+ b
)。詳情參見 溢出運算符。
加法運算符也可用于 String
的拼接:
"hello, " + "world" // 等于 "hello, world"
求余運算符(a % b
)是計算 b
的多少倍剛剛好可以容入 a
,返回多出來的那部分(余數(shù))。
注意
求余運算符(
%
)在其他語言也叫取模運算符。但是嚴(yán)格說來,我們看該運算符對負(fù)數(shù)的操作結(jié)果,「求余」比「取?!垢线m些。
我們來談?wù)勅∮嗍窃趺椿厥?,計?9 % 4
,你先計算出 4
的多少倍會剛好可以容入 9
中:
你可以在 9
中放入兩個 4
,那余數(shù)是 1(用橙色標(biāo)出)。
在 Swift 中可以表達為:
9 % 4 // 等于 1
為了得到 a % b
的結(jié)果,%
計算了以下等式,并輸出 余數(shù)
作為結(jié)果:
a = (b × 倍數(shù)) + 余數(shù)
當(dāng) 倍數(shù)
取最大值的時候,就會剛好可以容入 a
中。
把 9
和 4
代入等式中,我們得 1
:
9 = (4 × 2) + 1
同樣的方法,我們來計算 -9 % 4
:
-9 % 4 // 等于 -1
把 -9
和 4
代入等式,-2
是取到的最大整數(shù):
-9 = (4 × -2) + -1
余數(shù)是 -1
。
在對負(fù)數(shù) b
求余時,b
的符號會被忽略。這意味著 a % b
和 a % -b
的結(jié)果是相同的。
數(shù)值的正負(fù)號可以使用前綴 -
(即一元負(fù)號符)來切換:
let three = 3
let minusThree = -three // minusThree 等于 -3
let plusThree = -minusThree // plusThree 等于 3, 或 "負(fù)負(fù)3"
一元負(fù)號符(-
)寫在操作數(shù)之前,中間沒有空格。
一元正號符(+
)不做任何改變地返回操作數(shù)的值:
let minusSix = -6
let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6
雖然一元正號符什么都不會改變,但當(dāng)你在使用一元負(fù)號來表達負(fù)數(shù)時,你可以使用一元正號來表達正數(shù),如此你的代碼會具有對稱美。
如同 C 語言,Swift 也提供把其他運算符和賦值運算(=
)組合的組合賦值運算符,組合加運算(+=
)是其中一個例子:
var a = 1
a += 2
// a 現(xiàn)在是 3
表達式 a += 2
是 a = a + 2
的簡寫,一個組合加運算就是把加法運算和賦值運算組合成進一個運算符里,同時完成兩個運算任務(wù)。
注意
復(fù)合賦值運算沒有返回值,
let b = a += 2
這類代碼是錯誤。這不同于上面提到的自增和自減運算符。
更多 Swift 標(biāo)準(zhǔn)庫運算符的信息,請看 運算符聲明。 ?
所有標(biāo)準(zhǔn) C 語言中的比較運算符都可以在 Swift 中使用:
a == b
)a != b
)a > b
)a < b
)a >= b
)a <= b
)注意
Swift 也提供恒等(
===
)和不恒等(!==
)這兩個比較符來判斷兩個對象是否引用同一個對象實例。更多細(xì)節(jié)在 類與結(jié)構(gòu) 章節(jié)的 Identity Operators 部分。
每個比較運算都返回了一個標(biāo)識表達式是否成立的布爾值:
1 == 1 // true, 因為 1 等于 1
2 != 1 // true, 因為 2 不等于 1
2 > 1 // true, 因為 2 大于 1
1 < 2 // true, 因為 1 小于2
1 >= 1 // true, 因為 1 大于等于 1
2 <= 1 // false, 因為 2 并不小于等于 1
比較運算多用于條件語句,如 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”
關(guān)于 if
語句,請看 控制流。
如果兩個元組的元素相同,且長度相同的話,元組就可以被比較。比較元組大小會按照從左到右、逐值比較的方式,直到發(fā)現(xiàn)有兩個值不等時停止。如果所有的值都相等,那么這一對元組我們就稱它們是相等的。例如:
(1, "zebra") < (2, "apple") // true,因為 1 小于 2
(3, "apple") < (3, "bird") // true,因為 3 等于 3,但是 apple 小于 bird
(4, "dog") == (4, "dog") // true,因為 4 等于 4,dog 等于 dog
在上面的例子中,你可以看到,在第一行中從左到右的比較行為。因為 1
小于 2
,所以 (1, "zebra")
小于 (2, "apple")
,不管元組剩下的值如何。所以 "zebra"
大于 "apple"
對結(jié)果沒有任何影響,因為元組的比較結(jié)果已經(jīng)被第一個元素決定了。不過,當(dāng)元組的第一個元素相同時候,第二個元素將會用作比較-第二行和第三行代碼就發(fā)生了這樣的比較。
當(dāng)元組中的元素都可以被比較時,你也可以使用這些運算符來比較它們的大小。例如,像下面展示的代碼,你可以比較兩個類型為 (String, Int)
的元組,因為 Int
和 String
類型的值可以比較。相反,Bool
不能被比較,也意味著存有布爾類型的元組不能被比較。
("blue", -1) < ("purple", 1) // 正常,比較的結(jié)果為 true
("blue", false) < ("purple", true) // 錯誤,因為 < 不能比較布爾類型
注意
Swift 標(biāo)準(zhǔn)庫只能比較七個以內(nèi)元素的元組比較函數(shù)。如果你的元組元素超過七個時,你需要自己實現(xiàn)比較運算符。
三元運算符的特殊在于它是有三個操作數(shù)的運算符,它的形式是 問題 ? 答案 1 : 答案 2
。它簡潔地表達根據(jù) 問題
成立與否作出二選一的操作。如果 問題
成立,返回 答案 1
的結(jié)果;反之返回 答案 2
的結(jié)果。
三元運算符是以下代碼的縮寫形式:
if question {
answer1
} else {
answer2
}
這里有個計算表格行高的例子。如果有表頭,那行高應(yīng)比內(nèi)容高度要高出 50 點;如果沒有表頭,只需高出 20 點:
let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
// rowHeight 現(xiàn)在是 90
上面的寫法比下面的代碼更簡潔:
let contentHeight = 40
let hasHeader = true
var rowHeight = contentHeight
if hasHeader {
rowHeight = rowHeight + 50
} else {
rowHeight = rowHeight + 20
}
// rowHeight 現(xiàn)在是 90
第一段代碼例子使用了三元運算,所以一行代碼就能讓我們得到正確答案。這比第二段代碼簡潔得多,無需將 rowHeight
定義成變量,因為它的值無需在 if
語句中改變。
三元運算為二選一場景提供了一個非常便捷的表達形式。不過需要注意的是,濫用三元運算符會降低代碼可讀性。所以我們應(yīng)避免在一個復(fù)合語句中使用多個三元運算符。
空合運算符(a ?? b
)將對可選類型 a
進行空判斷,如果 a
包含一個值就進行解包,否則就返回一個默認(rèn)值 b
。表達式 a
必須是 Optional 類型。默認(rèn)值 b
的類型必須要和 a
存儲值的類型保持一致。
空合運算符是對以下代碼的簡短表達方法:
a != nil ? a! : b
上述代碼使用了三元運算符。當(dāng)可選類型 a
的值不為空時,進行強制解封(a!
),訪問 a
中的值;反之返回默認(rèn)值 b
。無疑空合運算符(??
)提供了一種更為優(yōu)雅的方式去封裝條件判斷和解封兩種行為,顯得簡潔以及更具可讀性。
注意
如果
a
為非空值(non-nil
),那么值b
將不會被計算。這也就是所謂的短路求值。
下文例子采用空合運算符,實現(xiàn)了在默認(rèn)顏色名和可選自定義顏色名之間抉擇:
let defaultColorName = "red"
var userDefinedColorName: String? //默認(rèn)值為 nil
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 的值為空,所以 colorNameToUse 的值為 "red"
userDefinedColorName
變量被定義為一個可選的 String
類型,默認(rèn)值為 nil
。由于 userDefinedColorName
是一個可選類型,我們可以使用空合運算符去判斷其值。在上一個例子中,通過空合運算符為一個名為 colorNameToUse
的變量賦予一個字符串類型初始值。 由于 userDefinedColorName
值為空,因此表達式 userDefinedColorName ?? defaultColorName
返回 defaultColorName
的值,即 red
。
如果你分配一個非空值(non-nil
)給 userDefinedColorName
,再次執(zhí)行空合運算,運算結(jié)果為封包在 userDefaultColorName
中的值,而非默認(rèn)值。
userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 非空,因此 colorNameToUse 的值為 "green"
Swift 提供了幾種方便表達一個區(qū)間的值的區(qū)間運算符。
閉區(qū)間運算符(a...b
)定義一個包含從 a
到 b
(包括 a
和 b
)的所有值的區(qū)間。a
的值不能超過 b
。
閉區(qū)間運算符在迭代一個區(qū)間的所有值時是非常有用的,如在 for-in
循環(huán)中:
for index in 1...5 {
print("\(index) * 5 = \(index * 5)")
}
// 1 * 5 = 5
// 2 * 5 = 10
// 3 * 5 = 15
// 4 * 5 = 20
// 5 * 5 = 25
關(guān)于 for-in
循環(huán),請看 控制流。
半開區(qū)間運算符(a..<b
)定義一個從 a
到 b
但不包括 b
的區(qū)間。 之所以稱為半開區(qū)間,是因為該區(qū)間包含第一個值而不包括最后的值。
半開區(qū)間的實用性在于當(dāng)你使用一個從 0 開始的列表(如數(shù)組)時,非常方便地從0數(shù)到列表的長度。
let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
print("第 \(i + 1) 個人叫 \(names[i])")
}
// 第 1 個人叫 Anna
// 第 2 個人叫 Alex
// 第 3 個人叫 Brian
// 第 4 個人叫 Jack
數(shù)組有 4 個元素,但 0..<count
只數(shù)到3(最后一個元素的下標(biāo)),因為它是半開區(qū)間。關(guān)于數(shù)組,請查閱 數(shù)組。
閉區(qū)間操作符有另一個表達形式,可以表達往一側(cè)無限延伸的區(qū)間 —— 例如,一個包含了數(shù)組從索引 2 到結(jié)尾的所有值的區(qū)間。在這些情況下,你可以省略掉區(qū)間操作符一側(cè)的值。這種區(qū)間叫做單側(cè)區(qū)間,因為操作符只有一側(cè)有值。例如:
for name in names[2...] {
print(name)
}
// Brian
// Jack
for name in names[...2] {
print(name)
}
// Anna
// Alex
// Brian
半開區(qū)間操作符也有單側(cè)表達形式,附帶上它的最終值。就像你使用區(qū)間去包含一個值,最終值并不會落在區(qū)間內(nèi)。例如:
for name in names[..<2] {
print(name)
}
// Anna
// Alex
單側(cè)區(qū)間不止可以在下標(biāo)里使用,也可以在別的情境下使用。你不能遍歷省略了初始值的單側(cè)區(qū)間,因為遍歷的開端并不明顯。你可以遍歷一個省略最終值的單側(cè)區(qū)間;然而,由于這種區(qū)間無限延伸的特性,請保證你在循環(huán)里有一個結(jié)束循環(huán)的分支。你也可以查看一個單側(cè)區(qū)間是否包含某個特定的值,就像下面展示的那樣。
let range = ...5
range.contains(7) // false
range.contains(4) // true
range.contains(-1) // true
邏輯運算符的操作對象是邏輯布爾值。Swift 支持基于 C 語言的三個標(biāo)準(zhǔn)邏輯運算。
!a
)a && b
)a || b
)邏輯非運算符(!a
)對一個布爾值取反,使得 true
變 false
,false
變 true
。
它是一個前置運算符,需緊跟在操作數(shù)之前,且不加空格。讀作 非 a
,例子如下:
let allowedEntry = false
if !allowedEntry {
print("ACCESS DENIED")
}
// 輸出“ACCESS DENIED”
if !allowedEntry
語句可以讀作「如果非 allowedEntry」,接下一行代碼只有在「非 allowedEntry」為 true
,即 allowEntry
為 false
時被執(zhí)行。
在示例代碼中,小心地選擇布爾常量或變量有助于代碼的可讀性,并且避免使用雙重邏輯非運算,或混亂的邏輯語句。
邏輯與運算符(a && b
)表達了只有 a
和 b
的值都為 true
時,整個表達式的值才會是 true
。
只要任意一個值為 false
,整個表達式的值就為 false
。事實上,如果第一個值為 false
,那么是不去計算第二個值的,因為它已經(jīng)不可能影響整個表達式的結(jié)果了。這被稱做短路計算(short-circuit evaluation)。
以下例子,只有兩個 Bool
值都為 true
的時候才允許進入 if:
let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 輸出“ACCESS DENIED”
邏輯或運算符(a || b
)是一個由兩個連續(xù)的 |
組成的中置運算符。它表示了兩個邏輯表達式的其中一個為 true
,整個表達式就為 true
。
同邏輯與運算符類似,邏輯或也是「短路計算」的,當(dāng)左端的表達式為 true
時,將不計算右邊的表達式了,因為它不可能改變整個表達式的值了。
以下示例代碼中,第一個布爾值(hasDoorKey
)為 false
,但第二個值(knowsOverridePassword
)為 true
,所以整個表達是 true
,于是允許進入:
let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 輸出“Welcome!”
我們可以組合多個邏輯運算符來表達一個復(fù)合邏輯:
if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 輸出“Welcome!”
這個例子使用了含多個 &&
和 ||
的復(fù)合邏輯。但無論怎樣,&&
和 ||
始終只能操作兩個值。所以這實際是三個簡單邏輯連續(xù)操作的結(jié)果。我們來解讀一下:
如果我們輸入了正確的密碼并通過了視網(wǎng)膜掃描,或者我們有一把有效的鑰匙,又或者我們知道緊急情況下重置的密碼,我們就能把門打開進入。
前兩種情況,我們都不滿足,所以前兩個簡單邏輯的結(jié)果是 false
,但是我們是知道緊急情況下重置的密碼的,所以整個復(fù)雜表達式的值還是 true
。
注意
Swift 邏輯操作符
&&
和||
是左結(jié)合的,這意味著擁有多元邏輯操作符的復(fù)合表達式優(yōu)先計算最左邊的子表達式。
為了一個復(fù)雜表達式更容易讀懂,在合適的地方使用括號來明確優(yōu)先級是很有效的,雖然它并非必要的。在上個關(guān)于門的權(quán)限的例子中,我們給第一個部分加個括號,使它看起來邏輯更明確:
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 輸出“Welcome!”
這括號使得前兩個值被看成整個邏輯表達中獨立的一個部分。雖然有括號和沒括號的輸出結(jié)果是一樣的,但對于讀代碼的人來說有括號的代碼更清晰。可讀性比簡潔性更重要,請在可以讓你代碼變清晰的地方加個括號吧!