Swift學習(3)- 基本運算子(程式碼完善版)

Swift學習(3)- 基本運算子(程式碼完善版)

August 10, 2021·Jingyao Zhang
Jingyao Zhang

image

運算子是用來檢查、改變、合併數值的特殊符號或詞語。例如,加號+可以將兩個陣列相加(如 let i = 1 + 2)。更複雜的運算例子還有邏輯與運算子&&(如 if enteredDoorCode && passedRetinaScan)。

Swift所支援的運算子大家可能在其他語言如C語言中都見過,同時為了減少常見的程式錯誤,對它們做了一些改進。例如:指定運算子=不再有回傳值,這樣就避免了把判等運算子==誤寫成指定運算子而導致的錯誤。

算術運算子(+、-、*、/、%等)的結果會被檢查並禁止溢位,以避免變數超出其型別可承載範圍時產生異常結果。當然,也可以使用Swift的溢位運算子來進行溢位運算。

Swift還提供了C語言沒有的區間運算子,例如a..<ba...b,這讓我們可以更方便地表達一個區間內的數值。


術語

運算子分為一元、二元和三元運算子:

  • 一元運算子對單一操作對象進行操作(如 -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現在等於b的值,10
print("將b的值指定給a,現在a的值為:\(a)")
---
output: b的值指定給a,現在a的值為10

如果指定運算子的右邊是一個元組,其元素可以立即被分解成多個常數或變數。

var (x, y) = (2, 5)  // 現在x等於2,y等於5
print("元組(2, 5)被分解後,現在x等於:\(x),y等於:\(y)")
---
output: 元組(2, 5)被分解後,現在x等於2y等於5

C語言和Objective-C語言不同,Swift的指定運算子不會回傳任何值,因此以下語句是無效的:

// if x = y {
         // 此結構錯誤,因為x = y不會回傳任何值,會報錯Use of '=' in a boolean context, did you mean '=='?
// }

if x == y {
        // 此結構正常不會報錯
}

透過將if x = y標記為無效語句,Swift能幫助開發者避免將==誤寫成=這類錯誤。


算術運算子

Swift中所有的數值型別都支援基本的四則算術運算子:

  • 加法(+)
  • 減法(-)
  • 乘法(*)
  • 除法(/)
print("1 + 2等於:", 1 + 2)  // 等於3
print("5 - 3等於:", 5 - 3)  // 等於2
print("2 * 3等於:", 2 * 3)  // 等於6
print("10.0 / 2.5等於:", 10.0 / 2.5)  // 等於4.0
---
output: 1 + 2等於: 3
5 - 3等於: 2
2 * 3等於: 6
10.0 / 2.5等於: 4.0

C語言和Objective-C語言不同,Swift預設不允許數值運算出現溢位。不過可以使用Swift的溢位運算子(如a &+ b)來進行溢位運算。

加法運算子也可以用於String字串的串接。

print("Hello " + "World!")  // 等於“Hello World!”
---
output: Hello World!

餘數運算子

餘數運算子(a % b)用來計算b能容納a多少次,並回傳多出來的那部分(餘數)。

注意

餘數運算子(%)在其他語言中也稱為取模運算子。但嚴格來說,觀察該運算子對負數的操作結果,「餘數」比「取模」更貼切。

來看看餘數運算的實際運作,計算9 % 4,先計算4能容納9幾次:

remainder_operator

可以在9中放入兩個4,剩下的餘數是1

Swift中可以這樣表達。

print("9 % 4的結果是:\(9 % 4)")  // 等於1
---
output: 9 % 4的結果是:1

同樣的方法來計算 -9 % 4

print("-9 % 4的結果是:\(-9 % 4)")  // 等於-1
---
output: -9 % 4的結果是:-1

對負數b取餘時,b的符號會被忽略。這表示a % ba % -b的結果相同。

print("9 % -4的結果是:\(9 % -4),與9 % 4的結果相同。")
---
output: 9 % -4的結果是:1,與9 % 4的結果相同。

一元負號運算子

數值的正負號可以用前綴-(即一元負號運算子)來切換。

let three = 3
let minusThree = -three  // minusThree等於-3
let plusThree = -minusThree  // plusThree等於3,即「負負得正」
print("負負得正的結果是:\(plusThree)")
---
output: 負負得正的結果是:3

一元正號運算子

一元正號運算子+會原封不動地回傳運算元的值。

let minusSix = -6
let alsoMinusSix = +minusSix
print("正號運算子不會改變值,所以alsoMinusSix的值為:\(alsoMinusSix)")
---
output: 正號運算子不會改變值,所以alsoMinusSix的值為-6

雖然一元正號運算子不會改變值,但當用一元負號運算子表達負數時,也可以用一元正號運算子表達正數,讓程式碼更對稱美觀。


複合指定運算子

如同C語言,Swift也提供將其他運算子與指定運算子=結合的複合指定運算子,複合加運算+=就是其中一個例子。

var c = 2
c += 2
print("c經過複合加運算後的值是:\(c)")  // c現在是4
---
output: c經過複合加運算後的值是4

表達式a += 2a = a + 2的簡寫,複合加運算就是將加法運算和指定運算結合成一個運算子,同時完成兩個運算任務。

注意

複合指定運算子沒有回傳值。let b = a += 2這種寫法是錯誤的,這與上面提到的自增和自減運算子不同。


比較運算子(Comparison Operators)

Swift支援以下比較運算子:

  • 等於(a == b)
  • 不等於(a != b)
  • 大於(a > b)
  • 小於(a < b)
  • 大於等於(a >= b)
  • 小於等於(a <= b)

注意

Swift也提供恆等(===)和不恆等(!==)這兩個比較運算子,用來判斷兩個物件是否參考同一個實體。

每個比較運算子都會回傳一個布林值。

print("1等於1:\(1 == 1)")  // true
print("2不等於1:\(2 != 1)")  // true
print("2大於1:\(2 > 1)")  // true
print("1小於2:\(1 < 2)")  // true
print("1大於等於1:\(1 >= 1)")  // true
print("2小於等於1:\(2 <= 1)")  // false
---
output: 1等於1true
2不等於1true
2大於1true
1小於2true
1大於等於1true
2小於等於1false

比較運算常用於條件語句,例如if條件。

let name = "Jensen"
if name == "world" {
        print("Hello, World!")
} else {
        print("I'm sorry \(name), but I don't recognize you.")
}
---
output: I'm sorry Jensen, but I don't recognize you.

如果兩個元組的元素型別相同且長度相同,元組就可以比較。比較元組大小會按照從左到右、逐值比對的方式,直到發現有兩個值不等時停止。如果所有值都相等,這對元組就被視為相等。

print("元組比較1:\((1, "zebra") < (2, "apple"))")  // true  因為1小於2,所以(1, "zebra") < (2, "apple"),雖然"zebra" > "apple",但對結果沒影響
print("元組比較2:\((3, "apple") < (3, "bird"))")  // true  當第一個元素相同時,會比較第二個元素
print("元組比較3:\((4, "dog") == (4, "dog"))")  // true
---
output: 元組比較1true
元組比較2true
元組比較3true

因此,當元組中的元素都可以比較時,可以用這些運算子比較它們的大小。例如,可以比較兩個型別為(String, Int)的元組,因為Int和String型別的值都可以比較。相反,Bool不能比較,也代表含有布林型別的元組不能比較。

print("String和Int型別元素可以比較:\(("blue", -1) < ("purple", 1))")  // 正常,結果為true
//("blue", false) < ("purple", true)  // 錯誤,因為 < 不能比較布林型別,若取消註解會報錯Binary operator '<' cannot be applied to two '(String, Bool)' operands
---
output: String和Int型別元素可以比較true

注意

Swift標準函式庫僅支援最多六個元素的元組比較,如果元組元素超過六個,需自行實作比較運算子。


三元運算子

三元運算子的特別之處在於它有三個運算元,形式為條件 ? 結果1 : 結果2。它簡潔地表達「條件」成立與否時二選一的操作。如果「條件」成立,回傳「結果1」;否則回傳「結果2」。

三元運算子可以讓程式碼更簡潔。

let question = 3 > 2
let answer1 = 3, answer2 = 2
print("三元運算子的結果:\(question ? answer1 : answer2)")  // 三元運算子

if question {
        print("answer1的結果是:\(answer1)")
} else {
        print("answer2的結果是:\(answer2)")
}
---
output: 三元運算子的結果:3
answer1的結果是3

這裡有個計算表格行高的例子,如果有表頭,行高應比內容高50點;如果沒有表頭,只需高20點。

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)  // rowHeight現在是90
print("rowHeight現在的值是:\(rowHeight)")
---
output: rowHeight現在的值是90

上面的寫法比下面的程式碼更簡潔。

let contentHeights = 40
let hasHeaders = true
var rowHeights = contentHeights
if hasHeaders {
        rowHeights += 50
        print("hasHeaders = True, rowHeights: \(rowHeights)")
} else {
        rowHeights += 20
        print("hasHeaders = False, rowHeights: \(rowHeights)")
}
---
output: hasHeaders = True, rowHeights: 90

第一段程式碼用三元運算子,一行就能得到正確答案,比第二段程式碼簡潔許多,也不需要將rowHeight定義成變數,因為它的值不會在if語句中改變。

三元運算子為二選一場景提供了非常方便的表達方式。不過為了提升程式碼可讀性,應避免在一個複合語句中使用多個三元運算子。


空合運算子(Nil Coalescing Operator)

空合運算子a ?? b會對可選型別a進行空值判斷,如果a有值就解包,否則回傳預設值b。表達式a必須是Optional型別,預設值b的型別必須與a的儲存值型別一致。

空合運算子是下列程式碼的簡寫:

var d: Int? = 3
let e: Int? = nil
print("空合運算子的原始表達結果:\((d != nil ? d! : e)!)")  // d != nil ? d! : e
---
output: 空合運算子的原始表達結果:3

上述程式碼使用了三元運算子。當可選型別a的值不為空時,強制解包a!取得值;否則回傳預設值b。空合運算子??提供了更優雅的方式來封裝條件判斷與解包,讓程式碼更簡潔且可讀性更高。

注意

如果a為非空值(Non-nil),那麼b不會被計算,這就是所謂的短路求值。

下例用空合運算子,在預設顏色名稱和可選自訂顏色名稱之間做選擇。

let defaultColorName = "red"
var userDefinedColorName: String?
var colorNameToUse = userDefinedColorName ?? defaultColorName  // userDefinedColorName為nil,所以colorNameToUse為「red」
print("colorNameToUse的顏色是:\(colorNameToUse)")
---
output: colorNameToUse的顏色是red

由於userDefinedColorName為空,因此userDefinedColorName ?? defaultColorName會回傳defaultColorName的值,也就是red

如果給userDefinedColorName指定非空值,再次執行空合運算,結果會是userDefinedColorName的值,而不是預設值。

userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
print("colorNameToUse的顏色是:\(colorNameToUse)")  // userDefinedColorName非空,因此colorNameToUse為「green」
---
output: colorNameToUse的顏色是green

區間運算子(Range Operators)

Swift提供了幾種方便表達區間的區間運算子。

閉區間運算子

閉區間運算子a...b定義一個包含從ab(包含a和b)的所有值的區間。a的值不能大於b

閉區間運算子在遍歷一個區間所有值時非常有效,例如在for-in迴圈中。

for index in 1...5 {
        print("\(index) * 5 = \(index * 5)")
}
---
output: 1 * 5 = 5
2 * 5 = 10
3 * 5 = 15
4 * 5 = 20
5 * 5 = 25

半開區間運算子

半開區間運算子a..<b定義一個從ab但不包含b的區間。之所以稱為半開區間,是因為該區間包含第一個值但不包含最後一個值。

半開區間在處理從0開始的列表(如陣列)時特別方便,可以從0數到列表長度。

let names = ["Jensen", "Alex", "Davis", "Jerry"]
let count = names.count
for i in 0..<count {
        print("第\(i + 1)個人叫\(names[i])")
}
---
output: 1個人叫Jensen
2個人叫Alex
3個人叫Davis
4個人叫Jerry

陣列有4個元素,但0..<count只到3(最後一個元素的索引),因為它是半開區間。

單側區間

閉區間運算子還有另一種表達方式,可以表示往一側無限延伸的區間。例如,包含陣列從索引2到結尾所有值的區間。在這些情況下,可以省略區間運算子一側的值,這種區間稱為單側區間。

for name in names[2...] {
        print("names[2...], ", name)
}

for name in names[...2] {
        print("names[...2], \(name)")
}
---
output: names[2...],  Davis
names[2...],  Jerry
names[...2], Jensen
names[...2], Alex
names[...2], Davis

半開區間運算子也有單側表達式,並包含其終止值。就像用區間包含一個值一樣,終止值不會包含在區間內。

for name in names[..<2] {
        print("names[..<2], \(name)")
}
---
output: names[..<2], Jensen
names[..<2], Alex

單側區間不只可以用在下標,也可以用在其他情境。還可以用來檢查單側區間是否包含某個特定值。

let range = ...5
print("range.contains(7): \(range.contains(7))")  // false
print("range.contains(4): \(range.contains(4))")  // true
print("range.contains(-1): \(range.contains(-1))")  // true
---
output: range.contains(7): false
range.contains(4): true
range.contains(-1): true

注意

不能遍歷省略起始值的單側區間,因為遍歷的起點不明確。但可以遍歷省略終止值的單側區間。不過由於這種區間會無限延伸,請務必在迴圈中設置結束條件。


邏輯運算子(Logical Operators)

邏輯運算子的操作對象是布林值,Swift支援基於C語言的三個標準邏輯運算:

  • 邏輯非(!a)
  • 邏輯與(a && b)
  • 邏輯或(a || b)

邏輯非運算子

邏輯非運算子!a會將布林值反轉,truefalsefalsetrue

它是一個前置運算子,需緊跟在運算元前面且不加空格。讀作「非a」,例如:

let allowedEntry = false
if !allowedEntry {  // 當allowedEntry為false,即!allowedEntry為true時,執行程式區塊
        print("ACCESS DENIED")  // 輸出「ACCESS DENIED」
}
---
output: ACCESS DENIED

開發時選擇合適的布林常數或變數有助於程式碼可讀性,也能避免雙重邏輯非或混亂的邏輯語句。

邏輯與運算子

邏輯與運算子a && b表示只有ab都為true時,整個表達式才會是true

只要有一個為false,整個表達式就是false。事實上,如果第一個為false,就不會再計算第二個,因為後面的值已經不會影響結果,這稱為短路運算(Short-Circuit Evaluation)。

let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {  // passedRetinaScan為false,所以整個表達式為false
        print("Welcome!")
} else {
        print("ACCESS DENIED")  // 輸出"ACCESS DENIED"
}
---
output: ACCESS DENIED

邏輯或運算子

邏輯或運算子a || b是由兩個連續的|組成的中置運算子。它表示兩個邏輯表達式只要有一個為true,整個表達式就為true

同邏輯與運算子一樣,邏輯或也是「短路運算」,當左邊為true時,不會再計算右邊,因為結果已經確定。

let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {  // knowsOverridePassword為true,所以整個表達式為true
        print("Welcome!")  // 輸出「Welcome!」
} else {
        print("ACCESS DENIED")
}
---
output: Welcome!

邏輯運算子組合運算

可以組合多個邏輯運算子來表達複合邏輯。

if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
        print("Welcome!")  // 輸出「Welcome!」
} else {
        print("ACCESS DENIED")
}
---
output: Welcome!

上述例子用了多個&&||的複合邏輯。無論如何,&&||一次只能操作兩個值,所以這其實是三個簡單邏輯連續運算的結果。

注意

Swift的邏輯運算子&&和||是左結合的,這表示有多個邏輯運算子的複合表達式會優先計算最左邊的子表達式。

使用括號明確優先順序

為了讓複雜表達式更易讀,在適當地方加上括號來明確優先順序是很有效的,雖然不是必要的。在上個例子中,給第一部分加上括號會更清楚。

if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
        print("Welcome!")  // 輸出「Welcome!」
} else {
        print("ACCESS DENIED")
}
---
output: Welcome!

加上括號後,前兩個值會被視為整個邏輯表達式中的一個獨立部分。雖然有無括號結果一樣,但對閱讀程式的人來說,有括號的程式碼更清楚,可讀性和簡潔性一樣重要。

最後更新於