Swift學習(7)- 控制流程(程式碼完善版)
Swift
提供了多種流程控制結構,包括可重複執行任務的 while
迴圈、根據特定條件選擇執行不同程式碼分支的 if
、guard
和 switch
敘述,以及控制流程跳轉到其他程式碼位置的 break
和 continue
敘述。
Swift
也提供了 for-in
迴圈,用來更簡單地遍歷陣列(Array)、字典(Dictionary)、區間(Range)、字串(String)及其他序列型別。
Swift
的 switch
敘述比許多 C 類語言更為強大。case
可以匹配許多不同的模式,包括範圍匹配、元組(tuple)和特定型別匹配。switch
敘述的 case
中匹配的值可以宣告為暫時常數或變數,在 case
作用域內使用,也可以搭配 where
來描述更複雜的匹配條件。
For-In 迴圈
可以使用 for-in
迴圈來遍歷集合中的所有元素,例如陣列中的元素、區間內的數字或字串中的字元。
以下範例使用 for-in
遍歷一個陣列中的所有元素:
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
} // Hello, Anna! // Hello, Alex! // Hello, Brian! // Hello, Jack!
---
output: Hello, Anna!
Hello, Alex!
Hello, Brian!
Hello, Jack!
也可以透過遍歷字典來存取它的鍵值對。遍歷字典時,字典的每個元素會以 (Key, Value)
元組的形式回傳,可以在 for-in
迴圈中使用明確的常數名稱來解構該元組。下例中,字典的鍵會被宣告為 animalName
常數,字典的值會被宣告為 legCount
常數:
let numberOfLegs = ["Spider": 8, "Ant": 6 , "Cat": 4]
for (animalName, legCount) in numberOfLegs {
print("\(animalName) has \(legCount) legs!")
} // Cat has 4 legs! // Ant has 6 legs! // Spider has 8 legs!
---
output: Ant has 6 legs!
Cat has 4 legs!
Spider has 8 legs!
字典的內容理論上是無序的,遍歷元素時的順序是無法保證的。將元素插入字典的順序並不會決定它們被遍歷的順序。
for-in
迴圈還可以使用數字區間。下例用來輸出乘法表的一部分內容。
for index in 1...5 { // 預設迴圈中的 index 是常數,其值不可在迴圈中更改。若要使用變數可寫 for var index in 1...5 {}
print("\(index) times 5 is \(index * 5)")
} // 1 times 5 is 5 // 2 times 5 is 10 // 3 times 5 is 15 // 4 times 5 is 20 // 5 times 5 is 25
---
output: 1 times 5 is 5
2 times 5 is 10
3 times 5 is 15
4 times 5 is 20
5 times 5 is 25
範例中用來遍歷的元素是使用閉區間運算子 ...
表示從 1 到 5 的數字區間。index
被賦值為閉區間中的第一個數字(1),然後執行一次迴圈中的敘述。在本例中,這個迴圈只包含一個敘述,用來輸出目前 index
值所對應的 5 的乘法表結果。該敘述執行後,index
的值會更新為閉區間中的第二個數字(2),之後 print(_:separator:terminator:)
函式會再執行一次。整個過程會進行到閉區間的結尾為止。
上述範例中,index
是每次迴圈開始時自動賦值的常數。這種情況下,index
在使用前不需要宣告,只需將其包含在迴圈的宣告中,就能隱式宣告,而無需使用 let
關鍵字。
如果不需要區間序列內的每一項值,可以使用底線 _
取代變數名稱來忽略這個值:
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
print("\(base) to the power of \(power) is \(answer).") // 輸出 "3 to the power of 10 is 59049."
---
output: 3 to the power of 10 is 59049.
這個範例計算 base
這個數的 power
次方(本例為 3 的 10 次方),從 1(3 的 0 次方)開始做 3 的乘法,進行 10 次,使用 1 到 10 的閉區間迴圈。這個計算並不需要知道每次迴圈中計數器的具體值,只需執行正確的迴圈次數即可。底線符號 _
(取代迴圈中的變數)能夠忽略目前值,不提供迴圈遍歷時對值的存取。
在某些情況下,可能不想使用包含兩個端點的閉區間。想像一下,在手錶上繪製分鐘的刻度線。總共 60 個刻度,從 0 開始。可以使用半開區間運算子 ..<
來表示一個左閉右開的區間。
let minutes = 60
for tickMark in 0..<minutes {
// 每分鐘都繪製一個刻度線
}
有些使用者可能在其 UI 中需要較少的刻度,他們可以每 5 分鐘作為一個刻度,使用 stride(from:to:by:)
函式跳過不需要的標記。
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
// 每 5 分鐘繪製一個刻度線(0,5,10,15...50,55)
}
也可以在閉區間使用 stride(from:through:by:)
函式達到同樣效果:
let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
// 每 3 小時繪製一個刻度線(3,6,9,12)
}
以上範例使用 for-in
迴圈來遍歷區間、陣列、字典和字串。你也可以用它來遍歷任何集合,包括實作了 Sequence
協定的自訂類別或集合型別。
使用 for-in
迴圈遍歷集合 Set
let fruitSet: Set<String> = ["Apple", "Orange", "Peach"]
for fruit in fruitSet.sorted() {
print("I love \(fruit).")
}
---
output: I love Apple.
I love Orange.
I love Peach.
While 迴圈
while
迴圈會一直執行一段敘述直到條件變成 false
。這類迴圈適合用在第一次迭代前,迭代次數未知的情境。Swift
提供兩種 while
迴圈形式:
while
迴圈,每次在迴圈開始時判斷條件是否成立;repeat-while
迴圈,每次在迴圈結束時判斷條件是否成立;
while
while
迴圈從判斷一個條件開始,如果條件為 true
,會重複執行一段敘述,直到條件為 false
。
以下是 while
迴圈的一般格式:
/*
while condition {
statements
}
*/
以下範例來玩一個叫做蛇與梯子(也叫滑道與梯子)的遊戲:
遊戲規則如下:
- 遊戲盤面包含 25 個格子,遊戲目標是到達或超過第 25 格;
- 每一輪,你透過擲一顆六面骰來決定移動步數,移動路徑如上圖橫向虛線所示;
- 如果在某輪結束時,你移動到梯子的底部,可以順著梯子往上爬;
- 如果在某輪結束時,你移動到蛇的頭部,你會順著蛇的身體滑下去。
遊戲盤面可以用一個 Int
陣列來表示,陣列長度由一個 finalSquare
常數儲存,用來初始化陣列並檢查最終勝利條件。遊戲盤面由 26 個 Int
0 值初始化,而不是 25 個(由 0 到 25,一共 26 個):
let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
// 有些格子被設定為特定值來表示有蛇或有梯子。梯子底部的格子是正值,讓你往上移動,蛇頭處的格子是負值,會讓你往下移動:
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
3 號格子是梯子的底部,會讓你往上移動到 11 號格子,使用 board[03]
等於 +08
來表示 11 和 3 之間的差值。為了對齊語句,這裡使用了一元正運算子 +i
和一元負運算子 -i
,並且小於 10 的數字都用 0 補齊,這些語法技巧並非必要,只是讓程式碼看起來更整齊。
玩家從左下角編號為 0 的格子開始遊戲,玩家第一次擲骰後才會進入遊戲盤面:
var square = 0
var diceRoll = 0
while square < finalSquare {
// 擲骰子
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
// 根據點數移動
square += diceRoll
if square < board.count {
// 順著梯子往上或順著蛇滑下去
square += board[square]
}
print(square, terminator: " ")
}
print("Game Over!")
---
output: 1 11 4 8 13 8 18 20 23 27 Game Over!
本例中用最簡單的方法模擬擲骰。diceRoll
的值不是隨機數,而是以 0 為初始值,每次 while
迴圈時,diceRoll
增加 1,然後檢查是否超過最大值。當 diceRoll
等於 7 時,就超過骰子的最大值,會重設為 1。所以 diceRoll
的取值順序會一直是 1,2,3,4,5,6,1,2 等。
擲完骰子後,玩家往前移動 diceRoll
格,如果玩家移動超過第 25 格,這時遊戲就會結束。為了處理這種情況,程式會先判斷 square
是否小於 board.count
,只有小於時才會在 board[square]
上加到 square
來往前或往後移動。
注意
如果沒有這個檢查(square < board.count),board[square] 可能會越界存取
board
陣列導致執行時錯誤。當本輪
while
迴圈執行完畢,會再檢查迴圈條件是否需要再執行一次。如果玩家移動到或超過第 25 格,迴圈條件結果為false
,此時遊戲結束。while
迴圈很適合這種情境,因為在while
迴圈開始時,我們並不知道遊戲要跑多久,只有達成指定條件時迴圈才會結束。
Repeat-While
while
迴圈的另一種形式是 repeat-while
,它和 while
的差別在於會先執行一次迴圈區塊,然後才判斷條件,直到條件為 false
。
注意
Swift 的 repeat-while 迴圈和其他語言中的 do-while 迴圈類似。
以下是 repeat-while
迴圈的一般格式:
/*
repeat {
statements
} while condition
*/
還是蛇與梯子的遊戲,使用 repeat-while
迴圈來取代 while
迴圈,finalSquare
、board
、square
和 diceRoll
的值初始化方式與 while
迴圈相同:
let FinalSquare = 25
var boards = [Int](repeating: 0, count: FinalSquare + 1)
boards[03] = +08; boards[06] = +11; boards[09] = +09; boards[10] = +02
boards[14] = -10; boards[19] = -11; boards[22] = -02; boards[24] = -08
var Square = 0
var DiceRoll = 0
repeat-while
版本的迴圈,第一步就要檢查是否在梯子或蛇的格子上,沒有梯子會讓玩家直接到第 25 格,所以玩家不會透過梯子直接贏得遊戲,因此在迴圈開始時先檢查是否踩在梯子或蛇上是安全的。
遊戲開始時,玩家在第 0 格,board[0] 一直等於 0,不會有影響:
repeat {
// 順著梯子往上或順著蛇滑下去
Square += boards[Square]
// 擲骰子
DiceRoll += 1
if DiceRoll == 7 { DiceRoll = 1 }
// 根據點數移動
Square += DiceRoll
print(Square, terminator: " ")
} while Square < FinalSquare
print("Game Over!")
---
output: 1 3 14 8 13 19 9 20 23 27 Game Over!
檢查完玩家是否踩在梯子或蛇上後,開始擲骰,然後玩家往前移動 diceRoll
格,本輪迴圈結束。
迴圈條件(while Square < FinalSquare)和 while
方式相同,但只會在迴圈結束後進行判斷。在這個遊戲中,repeat-while
表現得比 while
迴圈更好。repeat-while
方式會在條件判斷 Square
沒有超出後直接執行 Square += boards[Square]
,這種方式比前面 while
版本可省去陣列越界的檢查。
條件敘述
根據特定條件執行程式碼通常非常有用。當發生錯誤時,開發者可能想執行額外程式碼;或者,當值太大或太小時,向使用者顯示訊息。要實現這些功能,就需要使用條件敘述。
Swift
提供兩種條件敘述:if
敘述和 switch
敘述。通常,當條件簡單且可能情況很少時,使用 if
敘述。而 switch
敘述更適合條件複雜、有更多排列組合時。並且 switch
在需要用到模式匹配(Pattern-Matching)時會更有用。
If
if
敘述最簡單的形式就是只包含一個條件,只有該條件為 true
時,才執行相關程式碼:
var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
print("It's very cold! Consider wearing a scarf.")
} // 輸出 "It's very cold! Consider wearing a scarf."
---
output: It's very cold! Consider wearing a scarf.
上述範例會判斷溫度是否小於等於 32 華氏度(水的冰點)。如果是,則印出一則訊息;否則,不印出任何訊息,繼續執行 if 區塊後的程式碼。
當然,if
敘述允許二選一執行,稱為 else
子句。也就是當條件為 false
時,執行 else
區塊:
temperatureInFahrenheit = 40
if temperatureInFahrenheit <= 32 {
print("It's very cold! Consider wearing a scarf.")
} else {
print("It's not that cold. Wear a t-shirt.")
} // 輸出 "It's not that cold. Wear a t-shirt."
---
output: It's not that cold. Wear a t-shirt.
顯然,這兩個分支中總有一個會被執行。由於溫度已升至 40 華氏度,不算太冷,沒必要再圍圍巾。因此,else
分支就被觸發了。
可以把多個 if
敘述串接在一起,來實現更多分支:
temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 {
print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
print("It's really warm. Don't forget to wear sunscreen.")
} else {
print("It's not that cold. Wear a t-shirt.")
} // 輸出 "It's really warm. Don't forget to wear sunscreen."
---
output: It's really warm. Don't forget to wear sunscreen.
在上述範例中,額外的 if
敘述用來判斷是不是特別熱。而最後的 else
敘述被保留下來,用於印出既不冷也不熱時的訊息。
事實上,當不需要完整判斷所有情況時,最後的 else
敘述是可選的:
temperatureInFahrenheit = 72
if temperatureInFahrenheit <= 32 {
print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
print("It's really warm. Don't forget to wear sunscreen.")
}
在這個範例中,由於既不冷也不熱,所以不會觸發 if 或 else if 分支,也就不會印出任何訊息。
Switch
switch
敘述會嘗試將某個值與若干個模式(pattern)進行匹配,根據第一個匹配成功的模式,switch
敘述會執行對應的程式碼。當可能的情況較多時,通常用 switch
敘述取代 if
敘述。
switch
敘述最簡單的形式就是將某個值與一個或多個相同型別的值做比較:
/*
switch some value to consider {
case value 1:
respond to value 1
case value 2, value 3:
respond to value 2 or 3
default:
otherwise, do something else
}
*/
switch
敘述由多個 case
組成,每個由 case
關鍵字開始。為了匹配更特定的值,Swift
提供了幾種方法來進行更複雜的模式匹配,這些模式將在本節稍後介紹。
與 if
敘述類似,每個 case
都是程式碼執行的一個分支,switch
敘述會決定哪個分支應該被執行,這個流程稱為根據給定值切換(Switching)。
switch
敘述必須是完備的。也就是說,每個可能的值都必須至少有一個 case
分支與之對應。在某些無法涵蓋所有值的情況下,可以使用預設(default)分支來涵蓋其他所有沒有對應的值,這個預設分支必須在 switch
敘述的最後面。
下例使用 switch
敘述匹配一個名為 someCharacter
的小寫字元:
let someCharacter: Character = "z"
switch someCharacter {
case "a":
print("The 1st letter of the alphabet.")
case "z":
print("The last letter of the alphabet.")
default:
print("Some other character.")
} // 輸出 "The last letter of the alphabet."
---
output: The last letter of the alphabet.
在這個範例中,第一個 case
分支用來匹配第一個英文字母 a
,第二個 case
分支用來匹配最後一個字母 z
。因為 switch
敘述必須有一個 case
分支用來覆蓋所有可能的字元,而不僅僅是所有英文字母,所以 switch
敘述使用 default
分支來匹配除了 a
和 z
以外的所有值,這個分支保證了 switch
敘述的完備性。
不存在隱式貫穿
與 C 和 Objective-C 中的 switch
敘述不同,在 Swift 中,當匹配的 case
分支中的程式碼執行完畢後,程式會終止 switch
敘述,而不會繼續執行下一個 case
分支。也就是說,不需要在 case
分支中明確使用 break
敘述,這讓 switch
敘述更安全、更易用,也避免了漏寫 break
導致多個 case
分支被執行的錯誤。
注意
雖然在 Swift 中 break 不是必須的,但仍可在 case 分支中的程式碼執行完畢前使用 break 跳出。
每個 case
分支都必須包含至少一條敘述。像下面這樣寫是無效的,因為第一個 case
分支是空的:
let anotherCharacter: Character = "a"
switch anotherCharacter {
//case "a": // 無效,這個分支下沒有敘述,若取消註解則報錯 'case' label in a 'switch' should have at least one executable statement
case "A":
print("The letter A.")
default:
print("Not the letter A.")
}
---
output: Not the letter A.
不像 C 語言裡的 switch
敘述,在 Swift 中,switch
不會同時匹配 “A” 和 “a”。避免了意外從一個 case
分支貫穿到另一個,使程式碼更安全、也更直觀。
若要讓單一 case
同時匹配 “a” 和 “A”,可以將這兩個值組合成一個複合匹配,並用逗號分隔。
let AnotherCharacter: Character = "a"
switch AnotherCharacter {
case "A", "a":
print("The letter A.")
default:
print("Not the letter A.") // 輸出 "The letter A."
}
---
output: The letter A.
為了可讀性,複合匹配也可以寫成多行形式。
注意
若想要明確貫穿 case 分支,請使用 fallthrough 敘述
區間匹配
case
分支的模式也可以是一個值的區間,下例展示如何使用區間匹配來輸出任意數字對應的自然語言格式:
let approximateCount = 62
let countedThings = "Moons orbiting Saturn"
let naturalCount: String
switch approximateCount {
case 0:
naturalCount = "no"
case 1..<5:
naturalCount = "a few"
case 5..<12:
naturalCount = "several"
case 12..<100:
naturalCount = "dozens of"
case 100..<1000:
naturalCount = "hundreds of"
default:
naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).") // 輸出 "There are dozens of Moons orbiting Saturn."
---
output: There are dozens of Moons orbiting Saturn.
在上述範例中,approximateCount
在一個 switch
敘述中被評估。每個 case
都與之比較。因為 approximateCount
落在 12 和 100 的區間,所以 naturalCount
等於 “dozens of”,並且此後執行跳出 switch
敘述。
元組
可以使用元組在同一個 switch
敘述中測試多個值,元組中的元素可以是值,也可以是區間。另外,使用底線 _
來匹配所有可能的值。
下例展示如何使用一個 (Int, Int) 型別的元組來分類下圖中的點 (x, y):
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("\(somePoint) is at the origin.")
case (_, 0):
print("\(somePoint) is at the x-axis.")
case (0, _):
print("\(somePoint) is at the y-axis.")
case (-2...2, -2...2):
print("\(somePoint) is inside the box.")
default:
print("\(somePoint) is outside of the box.")
} // 輸出 "(1, 1) is inside the box."
---
output: (1, 1) is inside the box.
在上述範例中,switch
敘述會判斷某個點是否是原點 (0, 0)、是否在紅色的 x 軸上、是否在橘色的 y 軸上、是否在以原點為中心的 4x4 藍色矩形內,或在這個矩形外。
不像 C 語言,Swift 允許多個 case
匹配同一個值。實際上,在這個範例中,點 (0, 0) 可以匹配所有四個 case
。但若有多個匹配,只會執行第一個被匹配到的 case
分支。考慮點 (0, 0) 會首先匹配 case(0, 0)
,因此剩下能匹配的分支都會被忽略。
值綁定(Value Bindings)
case
分支允許將匹配的值宣告為暫時常數或變數,並在 case
分支體內使用,這種行為稱為值綁定(Value Binding),因為匹配的值在 case
分支體內,與暫時的常數或變數綁定。
下例將下圖中的點 (x, y),用 (Int, Int) 型別的元組表示,然後分類顯示:
let anotherPoint = (2, 1)
switch anotherPoint {
case (let x, 0):
print("On the x-axis with an x value of \(x).")
case (0, let y):
print("On the y-axis with a y value of \(y).")
case let (x, y):
print("Somewhere else at (\(x), \(y)).")
} // Somewhere else at (2, 1).
---
output: Somewhere else at (2, 1).
在上述範例中,switch
敘述會判斷某個點是否在紅色的 x 軸上、是否在橘色的 y 軸上,或不在座標軸上。
這三個 case
都宣告了常數 x
和 y
的占位符,用於暫時取得元組 anotherPoint
的一個或兩個值,第一個 case-->case(let x, 0)
會匹配縱座標為 0 的點,並把該點的橫座標賦值給暫時常數 x
。類似地,第二個 case-->case(0, let y)
會匹配橫座標為 0 的點,並把該點的縱座標賦值給暫時常數 y
。
一旦宣告了這些暫時常數,它們就可以在對應的 case
分支裡使用,在這個範例中,它們用於印出給定點的類型。
請注意,這個 switch
敘述不包含預設分支,因為最後一個 case-->case let (x, y)
宣告了一個可以匹配所有剩餘值的元組,這讓 switch
敘述已經完備,因此不需要再寫預設分支。
where
case
分支的模式可以使用 where
敘述來判斷額外條件。
下例將下圖的點 (x, y) 進行分類:
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
print("(\(x), \(y)) is on the line x == y.")
case let (x, y) where x == -y:
print("(\(x), \(y)) is on the line x == -y." )
case let (x, y):
print("(\(x), \(y)) is just some arbitrary point.")
} // 輸出 "(1, -1) is on the line x == -y."
---
output: (1, -1) is on the line x == -y.
在上述範例中,switch
敘述會判斷某個點是否在綠色的對角線 x == y 上、是否在紫色的對角線 x == -y 上,或不在對角線上。
這三個 case
都宣告了常數 x
和 y
的占位符,用於暫時取得元組 yetAnotherPoint
的兩個值,這兩個常數被用作 where
敘述的一部分,從而建立一個動態的過濾器(filter)。當且僅當 where
敘述的條件為 true
時,匹配到的 case
分支才會被執行。
就像值綁定的範例,由於最後一個 case
分支匹配了所有剩餘可能的值,switch
敘述就已經完備,因此不需要再寫預設分支。
複合型 Cases
當多個條件可以用同一種方式處理時,可以將這幾種可能放在同一個 case
後面,並用逗號隔開。當 case
後的任一模式匹配時,這條分支就會被匹配。若匹配列表過長,也可分行書寫。
let SomeCharacter: Character = "e"
switch SomeCharacter {
case "a", "e", "i", "o", "u":
print("\(SomeCharacter) is a vowel.")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n",
"p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
print("\(SomeCharacter) is a consonant.")
default:
print("\(SomeCharacter) is not a vowel or a consonant.")
} // 印出 "e is a vowel."
---
output: e is a vowel.
這個 switch
敘述中的第一個 case
,匹配了英文中的五個小寫母音字母。相似地,第二個 case
匹配了英文中所有小寫子音字母。最後,default
分支匹配了其他所有字元。
複合匹配同樣可以包含值綁定,複合匹配裡所有的匹配模式,都必須包含相同的值綁定,且每個綁定都必須取得相同型別的值。這保證了,無論複合匹配的哪個模式發生匹配,分支體內的程式碼都能取得綁定的值,且型別一致。
let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
print("On the axis, \(distance) from the origin.")
default:
print("Not on an axis.")
} // 輸出 "On the axis, 9 from the origin."
---
output: On the axis, 9 from the origin.
上述 case
有兩種模式:(let distance, 0)
匹配在 x 軸上的值,(0, let distance)
匹配在 y 軸上的值。這兩種模式都綁定了 distance
,且在兩種模式下都是整數型別。這表示分支體內的程式碼,只要 case
匹配,都可以取得 distance
的值。
控制轉移敘述
控制轉移敘述會改變程式碼執行的順序,透過它可以實現程式碼的跳轉。Swift
有五種控制轉移敘述:
- continue
- break
- fallthrough
- return
- throw
以下將介紹 continue
、break
和 fallthrough
敘述。return
敘述會在函式章節討論,throw
敘述會在錯誤拋出章節討論。
Continue
continue
敘述會讓迴圈體立刻停止本次迴圈,重新開始下次迴圈。就像在說「本次迴圈我已經執行完了」,但不會離開整個迴圈體。
下例將一個小寫字串中的母音字母和空白字元移除,產生一個意義模糊的短句:
let puzzleInput = "Great minds think alike"
var puzzleOutput = ""
for character in puzzleInput {
switch character {
case "a", "e", "i", "o", "u", " ":
continue
default:
puzzleOutput.append(character)
}
}
print(puzzleOutput) // 輸出 "Grtmndsthnklk"
---
output: Grtmndsthnklk
在上述程式碼中,只要匹配到母音字母或空白字元,就呼叫 continue
敘述,使本次迴圈結束,重新開始下次迴圈。這種行為讓 switch
匹配到母音字母和空白字元時不做處理,而不是讓每個匹配到的字元都被印出。
Break
break
敘述會立刻結束整個控制流程的執行。break
可以在 switch
或迴圈敘述中使用,用來提前結束 switch
或迴圈。
迴圈中的 Break
當在迴圈體中使用 break
時,會立刻中斷該迴圈體的執行,然後跳到表示迴圈體結束的大括號 }
後的第一行程式碼。不會再有本次迴圈的程式碼被執行,也不會再有下次的迴圈產生。
Switch 敘述中的 Break
當在 switch
區塊中使用 break
時,會立刻中斷該 switch
區塊的執行,並跳到表示 switch
區塊結束的大括號 }
後的第一行程式碼。
這個特性可以用來匹配或忽略一個或多個分支,因為 Swift 的 switch
需要包含所有分支且不允許有空分支,有時為了讓意圖更明確,需要特意匹配或忽略某個分支。當想忽略某個分支時,可以在該分支內寫上 break
敘述,當那個分支被匹配到時,分支內的 break
敘述會立即結束 switch
區塊。
注意
當一個 switch 分支僅包含註解時,會報編譯時錯誤。註解不是程式碼敘述,也不能讓 switch 分支達到被忽略的效果。應該使用 break 來忽略某個分支。
下例透過 switch
來判斷一個 Character
值是否代表下列四種語言之一。為了簡潔,數個值被包含在同一個分支情況中。
let numberSymbol: Character = "三" // 簡體中文裡的數字 3
var possibleIntegerValue: Int?
switch numberSymbol {
case "1", "١", "一", "๑":
possibleIntegerValue = 1
case "2", "٢", "二", "๒":
possibleIntegerValue = 2
case "3", "٣", "三", "๓":
possibleIntegerValue = 3
case "4", "٤", "四", "๔":
possibleIntegerValue = 4
default:
break
}
if let integerValue = possibleIntegerValue {
print("The integer value of \(numberSymbol) is \(integerValue).") // 輸出 "The integer value of 三 is 3."
} else {
print("An integer value could not be found for \(numberSymbol).")
}
---
output: The integer value of 三 is 3.
這個範例檢查 numberSymbol
是否為拉丁、阿拉伯、中文或泰文中的 1 到 4 之一。如果被匹配到,該 switch
分支會給 Int?
型別變數 possibleIntegerValue
設定一個整數值。
當 switch
區塊執行完後,接下來的程式碼會用可選綁定判斷 possibleIntegerValue
是否曾被設定過值。因為是可選型別,possibleIntegerValue
有一個隱含的初始值 nil
,所以只有當 possibleIntegerValue
曾被 switch
區塊前四個分支中的某一個設定過值時,可選綁定才會成功。
在上述範例中,想要把 Character
所有可能性都列舉出來是不現實的,所以使用 default
分支來包含所有上面沒匹配到的字元。由於這個 default
分支不需要執行任何動作,所以只寫了一條 break
敘述。一旦落入 default
分支後,break
敘述就完成該分支的所有程式碼操作,程式繼續往下,開始執行 if let
敘述。
貫穿(Fallthrough)
在 Swift 裡,switch
敘述不會從上一個 case
分支跳到下一個 case
分支。相反,只要第一個匹配到的 case
分支完成它需要執行的敘述,整個 switch
區塊就完成執行。相比之下,C 語言要求明確插入 break
敘述到每個 case
分支末尾來阻止自動落入下一個 case
分支。Swift 這種避免預設落入下一個分支的特性讓 switch
功能比 C 語言更清晰、可預測,可避免無意間執行多個 case
分支而引發錯誤。
如果確實需要 C 語言風格的貫穿特性,可以在每個需要該特性的 case
分支中使用 fallthrough
關鍵字,下例使用 fallthrough
來建立一個數字的描述語句:
let integerToDescribe = 5
var description = "The number \(integerToDescribe) is "
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
description += "a prime number, and also "
fallthrough
default:
description += "an integer."
}
print(description) // 印出 "The number 5 is a prime number, and also an integer."
---
output: The number 5 is a prime number, and also an integer.
這個範例定義了一個 String
型別變數 description
並給它初始值。函式使用 switch
邏輯判斷 integerToDescribe
變數的值。當 integerToDescribe
的值屬於列表中的質數之一時,該函式在 description
後加上一段文字,表示這個數字是質數,然後使用 fallthrough
關鍵字「貫穿」到 default
分支。default
分支在 description
最後加上額外文字,至此 switch
區塊執行完畢。
如果 integerToDescribe
的值不屬於列表中的任何質數,則不會匹配到第一個 switch
分支,而這裡沒有其他特別分支,因此會匹配到 default
分支。
當 switch
區塊執行完後,使用 print(_:separator:terminator:)
函式印出該數字的描述,在這個範例中,數字 5 被正確識別為質數。
注意
fallthrough 關鍵字不會檢查它下個將落入執行的 case 的匹配條件,fallthrough 只是單純讓程式繼續連接到下一個 case 程式碼,這和 C 語言標準中的 switch 敘述特性一樣。
帶標籤的敘述
在 Swift 中,可以在迴圈體和條件敘述中巢狀迴圈體和條件敘述來建立複雜的控制流程結構。而且,迴圈體和條件敘述都可以使用 break
敘述來提前結束整個區塊。因此,明確指明 break
敘述想要終止的是哪個迴圈體或條件敘述會很有用。類似地,如果有很多巢狀迴圈體,明確指明 continue
敘述想要影響哪個迴圈體也會很有用。
為了達成這個目的,可以使用標籤(Statement Label)來標記一個迴圈體或條件敘述,對於條件敘述,可以使用 break
加標籤的方式,來結束這個被標記的敘述。對於迴圈敘述,可以使用 break
或 continue
加標籤,來結束或繼續這條被標記的敘述。
宣告帶標籤的敘述是透過在該敘述的關鍵字同一行前面放置一個標籤,作為這個敘述的前導關鍵字,且該標籤後面跟著冒號。以下是針對 while
迴圈體的標籤語法,同樣規則適用於所有迴圈體和條件敘述。
/*
label name: while condition {
statements
}
*/
下例是前面章節中蛇與梯子的適配版本,在此版本中,將使用帶有標籤的 while
迴圈體呼叫 break
和 continue
敘述。這次,遊戲增加了一個額外規則:
- 為了獲勝,必須剛好落在第 25 格
- 如果某次擲骰使得移動超出第 25 格,必須重新擲骰,直到擲出的骰子數剛好能落在第 25 格。遊戲棋盤和之前一樣。
finalSQUARE
、BOARD
、SQUARE
和 diceROLL
的值與之前一樣初始化:
let finalSQUARE = 25
var BOARD = [Int](repeating: 0, count: finalSQUARE + 1)
BOARD[03] = +08; BOARD[06] = +11; BOARD[09] = +09; BOARD[10] = +02
BOARD[14] = -10; BOARD[19] = -11; BOARD[22] = -02; BOARD[24] = -08
var SQUARE = 0
var diceROLL = 0
這個版本的遊戲使用 while
迴圈和 switch
敘述來實作遊戲邏輯。while
迴圈體有一個標籤名 gameLoop
,表明它是遊戲主迴圈。
該 while
迴圈體的條件判斷敘述是 while SQUARE != finalSQUARE
,這表示必須剛好落在第 25 格。
gameLoop: while SQUARE != finalSQUARE {
diceROLL += 1
if diceROLL == 7 { diceROLL = 1 }
switch SQUARE + diceROLL {
case finalSQUARE:
break gameLoop
case let newSquare where newSquare > finalSQUARE:
continue gameLoop
default:
SQUARE += diceROLL
SQUARE += BOARD[SQUARE]
}
print(SQUARE, terminator: " ")
}
print("Game Over!")
---
output: 1 11 4 8 13 8 18 20 23 16 18 21 Game Over!
每次迴圈迭代開始時擲骰。與之前玩家擲完骰就立即移動不同,這裡使用 switch
敘述來考慮每次移動可能產生的結果,從而決定玩家本次是否能移動。
- 如果骰子數剛好讓玩家移動到最終格,遊戲結束。
break gameLoop
敘述跳轉控制去執行while
迴圈體後的第一行程式碼,代表遊戲結束。 - 如果骰子數會讓玩家移動超過最後一格,這種移動不合法,玩家需重新擲骰。
continue gameLoop
敘述結束本次while
迴圈,開始下次迴圈。 - 其餘所有情況,骰子數產生的都是合法移動。玩家往前移動
diceROLL
格,然後遊戲邏輯再處理玩家目前是否在蛇頭或梯子底部。接著本次迴圈結束,控制跳轉到while
迴圈體的條件判斷敘述,再決定是否繼續執行下次迴圈。
注意
如果上述 break 敘述沒有使用 gameLoop 標籤,則會中斷 switch 敘述而非 while 迴圈。使用 gameLoop 標籤清楚表明 break 想要中斷的是哪個區塊。
同時請注意,當呼叫 continue gameLoop 跳到下次迴圈時,這裡使用 gameLoop 標籤並非嚴格必要。因為在這個遊戲中,只有一個迴圈體,所以 continue 敘述會影響哪個迴圈體是沒有歧義的。然而 continue 敘述使用 gameLoop 標籤也沒有壞處。這麼做符合標籤使用規則,同時參照旁邊的 break gameLoop,能讓遊戲邏輯更清楚易懂。
提前退出
像 if
敘述一樣,guard
的執行取決於布林運算式的布林值。我們可以使用 guard
敘述來要求條件必須為真時,才執行 guard
敘述後的程式碼。不同於 if
敘述,guard
敘述總是有一個 else
子句,如果條件不為真則執行 else
區塊中的程式碼。
func greet(person: [String: String]) {
guard let name = person["name"] else {
return
}
print("Hello \(name)!")
guard let location = person["location"] else {
print("I hope the weather is nice near you.")
return
}
print("I hope the weather is nice in \(location).")
}
greet(person: ["name": "John"]) // 輸出 "Hello John!" // 輸出 "I hope the weather is nice near you."
greet(person: ["name": "Jensen", "location": "Hefei"])
---
output: Hello John!
I hope the weather is nice near you.
如果 guard
敘述的條件被滿足,則繼續執行 guard
敘述大括號後的程式碼。將變數或常數的可選綁定作為 guard
敘述的條件,都可以保護 guard
敘述後的程式碼。
如果條件不被滿足,在 else
分支上的程式碼就會被執行。這個分支必須轉移控制以退出 guard
敘述出現的程式碼區塊。它可以用控制轉移敘述如 return
、break
、continue
或 throw
來達成,或呼叫一個不會回傳的方法或函式,例如 fatalError()
。
相較於可以實現同樣功能的 if
敘述,按需求使用 guard
敘述會提升程式碼可讀性。它可以讓程式碼連貫執行而不需將它們包在 else
區塊中,也可以讓你在緊鄰條件判斷處處理違規情況。
檢查 API 可用性
Swift
內建支援檢查 API 可用性,這可確保不會在目前部署機器上,不小心使用了不可用的 API。
編譯器會使用 SDK 中的可用資訊來驗證程式碼中使用的所有 API 項目在指定的部署目標上是否可用。如果嘗試使用不可用的 API,Swift 會在編譯時報錯。
在 if
或 guard
敘述中使用可用性條件(Availability Condition)來有條件地執行一段程式碼,以在執行時判斷呼叫的 API 是否可用。編譯器會使用從可用性條件敘述取得的資訊來驗證,在這個程式碼區塊中呼叫的 API 是否可用。
if #available(iOS 10, macOS 10.12, *) {
// 在 iOS 使用 iOS 10 的 API,在 macOS 使用 macOS 10.12 的 API
} else {
// 使用先前版本的 iOS 和 macOS 的 API
}
以上可用性條件的指定,if
敘述的程式碼區塊僅在 iOS 10 或 macOS 10.12 以及更高版本才能執行,最後一個參數 *
是必須的,用於指定在所有其他平台中,如果版本號高於你指定的最低版本,if
敘述的程式碼區塊將會執行。
在一般形式中,可用性條件使用一個平台名稱和版本的列表。平台名稱可以是 iOS
、macOS
、watchOS
和 tvOS
,除了指定像 iOS 8
或 macOS 10.10
的大版本號,也可以指定像 iOS 11.2.6
及 macOS 10.13.3
的小版本號。
/*
if #available(平台名稱 版本號, ..., *) {
APIs 可用,敘述將執行
} else {
APIs 不可用,敘述將不執行
}
*/