Swift 語言的基本運算子

xslidian發表於2014-06-04

運算子operator)是用於檢查、更改或組合值的特殊符號或短語。例如,加法運算子(+)求兩個數字的加和(用例 let i = 1 + 2)。更復雜的例子包括邏輯與(logical AND)運算子 &&(用例 if 已輸入門禁密碼 && 已通過視網膜掃描) 以及自增運算子 ++i,後者是將 i 儲存的值加上 1 的便捷寫法。

Swift 支援標準 C 的大多數運算子,並改進了部分行為特性,以避免常見的編碼錯誤。賦值運算子(=)不會返回值,這樣可避免在打算使用等於運算子(==)的地方誤用前者。算術運算子(+-*/% 等)會偵測並阻止值溢位,可避免處理超出可儲存值域範圍的數時出現意料之外的結果。如果需要支援溢位,可以選用 Swift 的溢位運算子,詳見 溢位運算子

與 C 語言不同,Swift 允許對浮點數求餘(%)。Swift 還提供了兩種 C 語言沒有的區間運算子(a..ba...b),作為表達值域範圍的便捷寫法。

本章講解 Swift 中的常用運算子。高階運算子 一章涉及了 Swift 的高階運算子,並講解了如何自定義運算子,以及讓標準運算子支援自定義型別的運算。

運算子相關的術語

運算子分為一元、二元以及三元運算子:

  • 一元運算子unary operator)對單個目標進行運算(如 -a)。一元字首運算子(unary prefix operator)置於運算目標之前(如 !b),而一元后綴運算子(unary postfix operator)緊跟目標之後(如 i++)。

  • 二元運算子binary operator)對兩個目標進行運算(如 2 + 3),它們屬於中綴infix)運算子,因為出現在運算對應的兩個目標之間。

  • 三元運算子ternary operator)對三個目標進行運算。與 C 語言一樣,Swift 只有一個三元運算子:三元條件運算子(即 a ? b : c)。

運算子操作的值稱為運算數operands)。在表示式 1 + 2 中,+ 符號是二元運算子,它的兩個運算數為值 1與值 2

賦值運算子

賦值運算子assignment operatora = b)用 b 的值初始化或更新 a 的值:

  1. let b = 10
  2. var a = 5
  3. a = b
  4. // a 的值現在等於 10

如果賦值語句的右側是包含多個值的元組,其元素可一次性提取為多個常量或變數:

  1. let (x, y) = (1, 2)
  2. // x 等於 1,y 等於 2

與 C / Objective-C 語言的賦值運算子不同,Swift 語言的運算子本身不會返回值。因此下面的語句不正確:

  1. if x = y {
  2. // 語句無效,因為“x = y”不會返回值
  3. }

該特性可避免在希望使用等於運算子(==)的地方誤用賦值運算子(=)。通過不承認 if x = y 的有效性,Swift 將幫助你避免程式碼中出現這樣的錯誤。

算術運算子

Swift 支援對所有數字型別使用四種標準的算術運算子arithmetic operator):

  • 加法(+
  • 減法(-
  • 乘法(*
  • 除法(/

    1. 1 + 2 // 等於 3
    2. 5 - 3 // 等於 2
    3. 2 * 3 // 等於 6
    4. 10.0 / 2.5 // 等於 4.0

與 C / Objective-C 語言的算術運算子不同,Swift 的算術運算子預設不允許值溢位。若要支援溢位特性,可以使用 Swift 的溢位運算子(如 a &+ b)。參見 溢位運算子

加法運算子還支援 String 連線:

  1. "hello, " + "world" // 等於 "hello, world"

可以將兩個 Character 值相加,或將一個 Character 值與一個 String 值相加,得到新的 String 值:

  1. let : Character = "?"
  2. let : Character = "?"
  3. let 汪哞 = +
  4. // 汪哞 等於 "??"

參見 字串及字元的連線

求餘運算子

求餘運算子remainder operatora % b)求出) a 能裝得下多少個整數的 b,並返回剩餘的空間大小(即整除後的餘數 remainder)。

注:求餘運算子(%)在其他語言中也稱作求模運算子modulo operator)。但對負數的運算結果顯示,Swift 語言的實現是嚴格的求餘操作,而非求模操作。

求餘運算子的原理如下。 要計算 9 % 4,首先要求出 9 裡面能裝得下多少個 4

影像:整數的餘數

如圖顯示,9 裡面能裝得下兩個 4,因此餘數為 1(橙色部分)。

Swift 語言的寫法為:

  1. 9 % 4 // 等於 1

要求出 a % b 的結果,% 運算子會計算下面的式子,並將 餘數 作為輸出結果返回:

a = (b × 某個乘數) + 餘數

其中 某個乘數a 中能裝下 b 的最大數目。

94 代入等式,可求得:

9 = (4 × 2) + 1

a 為負值時,求餘數的方法不變:

  1. -9 % 4 // 等於 -1

-94 代入等式,可求得:

-9 = (4 × -2) + -1

得到的餘數值為 -1

b 為負值時,b 的符號將被忽略。因此 a % ba % -b 將總得到相同的結果。

浮點數的餘數計算

與 C / Objective-C 的餘數運算子不同,Swift 的餘數運算子還能對浮點數進行計算:

  1. 8 % 2.5 // 等於 0.5

上例中,8 除以 2.5 等於 3,餘數為 0.5,因此餘數運算子返回 Double0.5

影像:浮點數的餘數

自增與自減運算子

與 C 語言類似,Swift 也提供了自增運算子increment operator++)與自減運算子decrement operator--),作為將數字變數的值加上或減去 1 的便捷寫法。這兩個運算子可以對任何整型或浮點型的變數使用。

  1. var i = 0
  2. ++i // i 現在等於 1

每當呼叫 ++i 時,i 的值就會增加 1。通常來說,++i 就是 i = i + 1 的便捷寫法。類似地,--i 也相當於 i = i - 1

++-- 兩對符號可以作為字首或字尾運算子使用。++ii++ 均可用來將 i 的值加 1。類似地,--ii-- 均可用來將 i 的值減去 1

注意這些運算子不僅會改變 i 的值,還會返回一個值。如果你只需要自增或自減後的值存放在 i 中,可以忽略運算子的返回值。但如果你確實要用到返回值,要注意字首及字尾運算子返回值的差異,規則如下:

  • 如果運算子寫在變數的前面,則先改變變數值,再返回其值。

  • 如果運算子寫在變數的後面,則先返回原值後改變變數值

例如:

  1. var a = 0
  2. let b = ++a
  3. // a 與 b 現在均為 1
  4. let c = a++
  5. // a 現在為 2,但 c 被設為自增前的值 1

在上例中,let b = ++a 先增加 a 的值,然後才返回它的值。因此 ab 都等於新的值 1

但是,let c = a++ 先返回 a 的原值,然後才增加 a 的值。即 c 得到了原值 1,然後 a 被更新為新值 2

除非你需要利用 i++ 的這一特性,建議你在所有情況下都使用 ++i--i,因為它們的動作符合傳統思維的期望:先修改 i 的值,然後返回改變後的結果。

一元減號運算子

可以在數值前面加上 - 來切換符號。這個字首運算子 - 就稱為一元減號運算子unary minus operator):

  1. let = 3
  2. let 負三 = - // 負三 等於 -3
  3. let 正三 = -負三 // 正三 等於 3,即“負負三”

一元減號運算子(-)直接加在所要操作的值之前,無需任何空格。

一元加號運算子

一元加號運算子unary plus operator+)直接返回所操作的值,不作任何處理:

  1. let 負六 = -6
  2. let 還是負六 = +負六 // 還是負六 等於 -6

儘管一元加號運算子實際上不作任何運算,程式碼中仍然可以用它提供一些語義資訊,與表示負數的一元減號運算子形成對比。

組合賦值運算子

與 C 語言類似,Swift 也提供組合賦值運算子compound assignment operator),可將賦值運算子(=)與其他運算組合起來使用。例如加法賦值運算子addition assignment operator+=):

  1. var a = 1
  2. a += 2
  3. // a 現在等於 3

表示式 a += 2a = a + 2 的便捷寫法。加法與賦值操作被組合到一起,單個運算子就可以完成兩項操作,非常高效。

注:組合賦值運算子不返回值。因此不能寫成如 let b = a += 2。該行為與前文提到的自增/自減運算子不同。

組合運算子的完整列表可在 語言參考:表示式 一節找到。

比較運算子

Swift 支援標準 C 的全部比較運算子comparison operator):

  • 等於(a == b

  • 不等於(a != b

  • 大於(a > b

  • 小於(a < b

  • 大於或等於(a >= b

  • 小於或等於(a <= b

注:Swift 還提供了兩個鑑別運算子identity operator===!==),可用來測試兩個物件引用是否指向同一個物件例項。詳情請見 類與結構 一節。

每個比較運算子都會返回一個 Bool 值,告知語句是否成立:

  1. 1 == 1 // true,因為 1 等於 1
  2. 2 != 1 // true,因為 2 不等於 1
  3. 2 > 1 // true,因為 2 大於 1
  4. 1 < 2 // true,因為 1 小於 2
  5. 1 >= 1 // true,因為 1 大於或等於 1
  6. 2 <= 1 // false,因為 2 既不小於又不等於 1

通常在條件語句中使用比較運算子,如在 if 語句中:

  1. let name = "world"
  2. if name == "world" {
  3. println("hello, world")
  4. } else {
  5. println("不好意思啊,\(name),俺不認得你")
  6. }
  7. // 輸出 "hello, world",因為 name 的確等於 "world"

關於 if 語句的更多情況,請見 流程控制

三元條件運算子

三元條件運算子ternary conditional operator)是一種特殊的運算子,由三部分組成,形式為 問題 ? 回答1 : 回答2。它根據 問題 成立與否,從兩個表示式中取出一個並求值。如果 問題 成立,則求 回答1 的結果並返回其值;否則求出 回答2 的結果並返回其值。

三元條件運算子是下述程式碼的便捷寫法:

  1. if 問題 {
  2.     回答1
  3. } else {
  4.     回答2
  5. }

下面的例子將計算表格某行的顯示高度。如果該行有表頭,則行高應比內容高度高 50 個畫素;如果沒有表頭,則只高出 20 個畫素:

  1. let 內容高度 = 40
  2. let 有表頭 = true
  3. let 行高 = 內容高度 + (有表頭 ? 50 : 20)
  4. // 行高 等於 90

上例是下列程式碼的便捷寫法:

  1. let 內容高度 = 40
  2. let 有表頭 = true
  3. var 行高 = 內容高度
  4. if 有表頭 {
  5. 行高 = 行高 + 50
  6. } else {
  7. 行高 = 行高 + 20
  8. }
  9. // 行高 等於 90

使用三元條件運算子的例子說明,可以僅用一行程式碼就將 行高 設為正確的值。這比不用三元運算子的第二個例子簡明得多,並且 行高 無需定義為變數,因為不再需要用 if 語句修改其值。

三元條件運算子提供了二選一的高效寫法。 但使用三元條件運算子應小心。如果過度使用,其簡明性可能導致程式碼難以閱讀。應避免將多個三元條件運算子組合在同一個語句中。

區間運算子

Swift 包含兩個區間運算子range operator),是表達值域範圍的便捷寫法。

閉區間運算子

閉區間運算子closed range operatora...b)定義了從 ab 的區間範圍,包括 ab 兩個值。

閉區間運算子在需要遍歷某區間內所有值時有用,如在 for-in 迴圈中使用:

  1. for i in 1...5 {
  2. println("\(i) 乘以 5 得 \(i * 5)")
  3. }
  4. // 1 乘以 5 得 5
  5. // 2 乘以 5 得 10
  6. // 3 乘以 5 得 15
  7. // 4 乘以 5 得 20
  8. // 5 乘以 5 得 25

關於 for-in 迴圈的更多資訊,請見 流程控制

半閉區間運算子

半閉區間運算子half-closed range operatora..b)定義了從 ab 的區間,但不含 b。說是半閉,是因為第一個值包含在區間以內,但最後一個值不在區間內。

半閉區間在處理從 0 開始計數的列表時有用,如遍歷陣列,可從 0 數到列表的長度(但不包括長度值本身):

  1. let 名單 = ["Anna", "Alex", "Brian", "Jack"]
  2. let 人數 = 名單.count
  3. for i in 0..人數 {
  4. println("第 \(i + 1) 個人叫 \(名單[i])")
  5. }
  6. // 第 1 個人叫 Anna
  7. // 第 2 個人叫 Alex
  8. // 第 3 個人叫 Brian
  9. // 第 4 個人叫 Jack

注意陣列包含四個物件,但因為是半閉區間,所以 0..人數 只數到 3(陣列中最後一個元素的索引號)。陣列的詳細情況請見 陣列

邏輯運算子

邏輯運算子logical operator)操作或組合布林邏輯值 truefalse。Swift 支援 C 及其衍生語言的三種標準邏輯運算子:

  • 邏輯非(!a

  • 邏輯與(a && b

  • 邏輯或(a || b

邏輯非運算子

邏輯非運算子logical NOT operator!a)將布林值取反,即 true 變為 false,而 false 變為 true

邏輯非運算子屬於字首運算子,置於所操作的值之前,不加任何空格。可以理解為“非 a”(not a),如下例所示:

  1. let 允許進入 = false
  2. if !允許進入 {
  3.     println("ACCESS DENIED")
  4. }
  5. // 輸出 "ACCESS DENIED"(拒絕訪問)

程式碼中的 if !允許進入 可以解釋成“如果不允許進入(if not 允許進入)”。下面一行程式碼僅當“不允許進入”成立時才會執行;即 允許進入false 時才執行。

正如該例所展示的,布林值常量及變數的名稱應謹慎選擇,方可確保程式碼簡明又具可讀性,同時避免出現雙重否定或引起混淆的邏輯語句。

邏輯與運算子

邏輯與運算子logical AND operatora && b)用於構造這樣的邏輯表示式:運算子兩側的值均為 true,整個表示式的求值結果才為 true

如果有一個值為 false,整個表示式便為 false。事實上,如果第一個值false,第二個值將被直接忽略,而不會進一步對它求值,因為不論它為何值,整個表示式的值都不可能等於 true。這稱為短路求值short-circuit evaluation)。

下例考察兩個 Bool 值,僅當兩值均為 true 時才允許訪問:

  1. let 已輸入門禁密碼 = true
  2. let 已通過視網膜掃描 = false
  3. if 已輸入門禁密碼 && 已通過視網膜掃描 {
  4.     println("Welcome!")
  5. } else {
  6.     println("ACCESS DENIED")
  7. }
  8. // 輸出 "ACCESS DENIED"

邏輯或運算子

邏輯或運算子logical OR operatora || b)屬於中綴運算子,由兩個連續的豎線構成。它用來構造這樣的表示式:整個表示式為 true 的條件為,兩個值中至少有一個true

與前文的邏輯與運算子一樣,邏輯或運算子在考察它的兩個表示式時,也採用“短路求值”演算法。只要邏輯或表示式左側為 true,其右側將不再求值,因為這時右側的值對整個表示式的總體結果不再有影響。

下例中,第一個 Bool 值(有鑰匙)為 false,但第二個值(知道備用密碼)為 true。因為有一個值為 true,所以整個表示式的求值結果也為 true,因此允許訪問:

  1. let 有鑰匙 = false
  2. let 知道備用密碼 = true
  3. if 有鑰匙 || 知道備用密碼 {
  4.     println("Welcome!")
  5. } else {
  6.     println("ACCESS DENIED")
  7. }
  8. // 輸出 "Welcome!"

組合使用邏輯運算子

多個邏輯運算子可以組合起來,構成更長的組合表示式:

  1. if 已輸入門禁密碼 && 已通過視網膜掃描 || 有鑰匙 || 知道備用密碼 {
  2.     println("Welcome!")
  3. } else {
  4.     println("ACCESS DENIED")
  5. }
  6. // 輸出 "Welcome!"

該例用到了多個 &&|| 運算子,構造了一條更長的組合表示式。不過,&&|| 運算子操作的仍然是兩個值,因此該組合表示式實際上是由三個較短的表示式連立而成的。它可以這樣理解:

如果我們輸入了正確的門禁密碼、並且通過了視網膜掃描;或者如果我們有門鑰匙;或者如果我們知道緊急的備用密碼,則允許訪問。

根據 已輸入門禁密碼已通過視網膜掃描有鑰匙 三個常量,前兩個小表示式的值均為 false。不過我們知道緊急的備用密碼,因此整個組合表示式的求值結果仍然為 true

顯式括號

有時雖然從語法上看,括號並不是必需的,加上括號卻很有用,它可以讓複雜表示式的意圖更顯而易見。 在上面門禁的例子中,為組合表示式的第一部分加上括號,可使其意圖更明顯:

  1. if (已輸入門禁密碼 && 已通過視網膜掃描) || 有鑰匙 || 知道備用密碼 {
  2. println("Welcome!")
  3. } else {
  4. println("ACCESS DENIED")
  5. }
  6. // 輸出 "Welcome!"

括號將前兩個值與其他值分隔開來,使其構成整體邏輯中一種可能狀態的意圖更為明顯。組合表示式的結果不會改變,但對讀者而言,整體意圖更加清晰。可讀性總是優先於簡潔度;應儘可能在合適的地方使用括號,使你的意圖更加明晰。

相關文章