> 1.0 翻譯:[vclwei](https://github.com/vclwei),?[coverxit](https://github.com/coverxit),?[NicePiao](https://github.com/NicePiao)?校對:[coverxit](https://github.com/coverxit),?[stanzhai](https://github.com/stanzhai)
>
> 2.0 翻譯+校對:[JackAlan](https://github.com/AlanMelody)
* * *
本頁包含內容:
[TOC=2]
Swift提供了類似 C 語言的流程控制結構,包括可以多次執行任務的`for`和`while`循環,基于特定條件選擇執行不同代碼分支的`if`、`guard`和`switch`語句,還有控制流程跳轉到其他代碼的`break`和`continue`語句。
除了 C 語言里面傳統的 for 循環,Swift 還增加了`for-in`循環,用來更簡單地遍歷數組(array),字典(dictionary),區間(range),字符串(string)和其他序列類型。
Swift 的`switch`語句比 C 語言中更加強大。在 C 語言中,如果某個 case 不小心漏寫了`break`,這個 case 就會貫穿至下一個 case,Swift 無需寫`break`,所以不會發生這種貫穿的情況。case 還可以匹配更多的類型模式,包括區間匹配(range matching),元組(tuple)和特定類型的描述。`switch`的 case 語句中匹配的值可以是由 case 體內部臨時的常量或者變量決定,也可以由`where`分句描述更復雜的匹配條件。
## For 循環
Swift 提供兩種`for`循環形式以來按照指定的次數多次執行一系列語句:
* `for-in`循環對一個集合里面的每個元素執行一系列語句。
* for循環,用來重復執行一系列語句直到達成特定條件達成,一般通過在每次循環完成后增加計數器的值來實現。
### For-In
你可以使用`for-in`循環來遍歷一個集合里面的所有元素,例如由數字表示的區間、數組中的元素、字符串中的字符。
下面的例子用來輸出乘 5 乘法表前面一部分內容:
~~~
for 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
~~~
例子中用來進行遍歷的元素是一組使用閉區間操作符(`...`)表示的從`1`到`5`的數字。`index`被賦值為閉區間中的第一個數字(`1`),然后循環中的語句被執行一次。在本例中,這個循環只包含一個語句,用來輸出當前`index`值所對應的乘 5 乘法表結果。該語句執行后,`index`的值被更新為閉區間中的第二個數字(`2`),之后`print(_:)`函數會再執行一次。整個過程會進行到閉區間結尾為止。
上面的例子中,`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"
~~~
這個例子計算 base 這個數的 power 次冪(本例中,是`3`的`10`次冪),從`1`(`3`的`0`次冪)開始做`3`的乘法, 進行`10`次,使用`1`到`10`的閉區間循環。這個計算并不需要知道每一次循環中計數器具體的值,只需要執行了正確的循環次數即可。下劃線符號`_`(替代循環中的變量)能夠忽略具體的值,并且不提供循環遍歷時對值的訪問。
使用`for-in`遍歷一個數組所有元素:
~~~
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!
~~~
你也可以通過遍歷一個字典來訪問它的鍵值對。遍歷字典時,字典的每項元素會以`(key, value)`元組的形式返回,你可以在`for-in`循環中使用顯式的常量名稱來解讀`(key, value)`元組。下面的例子中,字典的鍵(key)解讀為常量`animalName`,字典的值會被解讀為常量`legCount`:
~~~
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
print("\(animalName)s have \(legCount) legs")
}
// ants have 6 legs
// cats have 4 legs
// spiders have 8 legs
~~~
字典元素的遍歷順序和插入順序可能不同,字典的內容在內部是無序的,所以遍歷元素時不能保證順序。關于數組和字典,詳情參見[集合類型](http://wiki.jikexueyuan.com/project/swift/chapter2/04_Collection_Types.html)。
除了`for-in`循環,Swift 提供使用條件判斷和遞增方法的標準 C 樣式`for`循環:
~~~
for var index = 0; index < 3; ++index {
print("index is \(index)")
}
// index is 0
// index is 1
// index is 2
~~~
下面是一般情況下這種循環方式的格式:
> for?`initialization`;?`condition`;?`increment`?{?`statements`?}
和 C 語言中一樣,分號將循環的定義分為 3 個部分,不同的是,Swift 不需要使用圓括號將“initialization; condition; increment”包括起來。
這個循環執行流程如下:
1. 循環首次啟動時,初始化表達式被調用一次,用來初始化循環所需的所有常量和變量。
2. 條件表達式被調用,如果表達式調用結果為`false`,循環結束,繼續執行`for`循環關閉大括號 (`}`)之后的代碼。如果表達式調用結果為`true`,則會執行大括號內部的代碼。
3. 執行所有語句之后,執行遞增表達式。通常會增加或減少計數器的值,或者根據語句輸出來修改某一個初始化的變量。當遞增表達式運行完成后,重復執行第 2 步,條件表達式會再次執行。
在初始化表達式中聲明的常量和變量(比如`var index = 0`)只在`for`循環的生命周期里有效。如果想在循環結束后訪問`index`的值,你必須要在循環生命周期開始前聲明`index`。
~~~
var index: Int
for index = 0; index < 3; ++index {
print("index is \(index)")
}
// index is 0
// index is 1
// index is 2
print("The loop statements were executed \(index) times")
// 輸出 "The loop statements were executed 3 times
~~~
注意`index`在循環結束后最終的值是`3`而不是`2`。最后一次調用遞增表達式`++index`會將`index`設置為`3`,從而導致`index < 3`條件為`false`,并終止循環。
## While 循環
`while`循環運行一系列語句直到條件變成`false`。這類循環適合使用在第一次迭代前迭代次數未知的情況下。Swift 提供兩種`while`循環形式:
* `while`循環,每次在循環開始時計算條件是否符合;
* `do-while`循環,每次在循環結束時計算條件是否符合。
### While
`while`循環從計算單一條件開始。如果條件為`true`,會重復運行一系列語句,直到條件變為`false`。
下面是一般情況下?`while`?循環格式:
> while?`condition`?{?`statements`?}
下面的例子來玩一個叫做蛇和梯子的小游戲,也叫做滑道和梯子:

游戲的規則如下:
* 游戲盤面包括 25 個方格,游戲目標是達到或者超過第 25 個方格;
* 每一輪,你通過擲一個 6 邊的骰子來確定你移動方塊的步數,移動的路線由上圖中橫向的虛線所示;
* 如果在某輪結束,你移動到了梯子的底部,可以順著梯子爬上去;
* 如果在某輪結束,你移動到了蛇的頭部,你會順著蛇的身體滑下去。
游戲盤面可以使用一個`Int`數組來表達。數組的長度由一個`finalSquare`常量儲存,用來初始化數組和檢測最終勝利條件。游戲盤面由 26 個?`Int`?0 值初始化,而不是 25 個(由`0`到`25`,一共 26 個):
~~~
let finalSquare = 25
var board = [Int](count: finalSquare + 1, repeatedValue: 0)
~~~
一些方塊被設置成有蛇或者梯子的指定值。梯子底部的方塊是一個正值,使你可以向上移動,蛇頭處的方塊是一個負值,會讓你向下移動:
~~~
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 {
// 擲骰子
if ++diceRoll == 7 { diceRoll = 1 }
// 根據點數移動
square += diceRoll
if square < board.count {
// 如果玩家還在棋盤上,順著梯子爬上去或者順著蛇滑下去
square += board[square]
}
}
print("Game over!")
~~~
本例中使用了最簡單的方法來模擬擲骰子。?`diceRoll`的值并不是一個隨機數,而是以`0`為初始值,之后每一次`while`循環,`diceRoll`的值使用前置自增操作符(`++i`)來自增 1 ,然后檢測是否超出了最大值。`++diceRoll`調用完成_后_,返回值等于`diceRoll`自增后的值。任何時候如果`diceRoll`的值等于7時,就超過了骰子的最大值,會被重置為`1`。所以`diceRoll`的取值順序會一直是`1`,`2`,`3`,`4`,`5`,`6`,`1`,`2`。
擲完骰子后,玩家向前移動`diceRoll`個方格,如果玩家移動超過了第 25 個方格,這個時候游戲結束,相應地,代碼會在`square`增加`board[square]`的值向前或向后移動(遇到了梯子或者蛇)之前,檢測`square`的值是否小于`board`的`count`屬性。
如果沒有這個檢測(`square < board.count`),`board[square]`可能會越界訪問`board`數組,導致錯誤。例如如果`square`等于`26`, 代碼會去嘗試訪問`board[26]`,超過數組的長度。
當本輪`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 board = [Int](count: finalSquare + 1, repeatedValue: 0)
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
~~~
`repeat-while`的循環版本,循環中_第一步_就需要去檢測是否在梯子或者蛇的方塊上。沒有梯子會讓玩家直接上到第 25 個方格,所以玩家不會通過梯子直接贏得游戲。這樣在循環開始時先檢測是否踩在梯子或者蛇上是安全的。
游戲開始時,玩家在第 0 個方格上,`board[0]`一直等于 0, 不會有什么影響:
~~~
repeat {
// 順著梯子爬上去或者順著蛇滑下去
square += board[square]
// 擲骰子
if ++diceRoll == 7 { diceRoll = 1 }
// 根據點數移動
square += diceRoll
} while square < finalSquare
print("Game over!")
~~~
檢測完玩家是否踩在梯子或者蛇上之后,開始擲骰子,然后玩家向前移動`diceRoll`個方格,本輪循環結束。
循環條件(`while square < finalSquare`)和`while`方式相同,但是只會在循環結束后進行計算。在這個游戲中,`repeat-while`表現得比`while`循環更好。`repeat-while`方式會在條件判斷`square`沒有超出后直接運行`square += board[square]`,這種方式可以去掉`while`版本中的數組越界判斷。
## 條件語句
根據特定的條件執行特定的代碼通常是十分有用的,例如:當錯誤發生時,你可能想運行額外的代碼;或者,當輸入的值太大或太小時,向用戶顯示一條消息等。要實現這些功能,你就需要使用_條件語句_。
Swift 提供兩種類型的條件語句:`if`語句和`switch`語句。通常,當條件較為簡單且可能的情況很少時,使用`if`語句。而`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."
~~~
上面的例子會判斷溫度是否小于等于 32 華氏度(水的冰點)。如果是,則打印一條消息;否則,不打印任何消息,繼續執行`if`塊后面的代碼。
當然,`if`語句允許二選一,也就是當條件為`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."
~~~
顯然,這兩條分支中總有一條會被執行。由于溫度已升至 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."
~~~
在上面的例子中,額外的`if`語句用于判斷是不是特別熱。而最后的`else`語句被保留了下來,用于打印既不冷也不熱時的消息。
實際上,最后的`else`語句是可選的:
~~~
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.")
}
~~~
在這個例子中,由于既不冷也不熱,所以不會觸發`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_?構成。為了匹配某些更特定的值,Swift 提供了幾種更復雜的匹配模式,這些模式將在本節的稍后部分提到。
每一個 case 都是代碼執行的一條分支,這與`if`語句類似。與之不同的是,`switch`語句會決定哪一條分支應該被執行。
`switch`語句必須是完備的。這就是說,每一個可能的值都必須至少有一個 case 分支與之對應。在某些不可能涵蓋所有值的情況下,你可以使用默認(`default`)分支滿足該要求,這個默認分支必須在`switch`語句的最后面。
下面的例子使用`switch`語句來匹配一個名為`someCharacter`的小寫字符:
~~~
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"
~~~
在這個例子中,第一個 case 分支用于匹配五個元音,第二個 case 分支用于匹配所有的輔音。
由于為其它可能的字符寫 case 分支沒有實際的意義,因此在這個例子中使用了默認分支來處理剩下的既不是元音也不是輔音的字符——這就保證了`switch`語句的完備性。
#### 不存在隱式的貫穿(No Implicit Fallthrough)
與 C 語言和 Objective-C 中的`switch`語句不同,在 Swift 中,當匹配的 case 分支中的代碼執行完畢后,程序會終止`switch`語句,而不會繼續執行下一個 case 分支。這也就是說,不需要在 case 分支中顯式地使用`break`語句。這使得`switch`語句更安全、更易用,也避免了因忘記寫`break`語句而產生的錯誤。
> 注意: 雖然在Swift中`break`不是必須的,但你依然可以在 case 分支中的代碼執行完畢前使用`break`跳出,詳情請參見[Switch 語句中的 break](http://wiki.jikexueyuan.com/project/swift/chapter2/05_Control_Flow.html#break_in_a_switch_statement)。
每一個 case 分支都_必須_包含至少一條語句。像下面這樣書寫代碼是無效的,因為第一個 case 分支是空的:
~~~
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a":
case "A":
print("The letter A")
default:
print("Not the letter A")
}
// this will report a compile-time error
~~~
不像 C 語言里的`switch`語句,在 Swift 中,`switch`語句不會同時匹配`"a"`和`"A"`。相反的,上面的代碼會引起編譯期錯誤:`case "a": does not contain any executable statements`——這就避免了意外地從一個 case 分支貫穿到另外一個,使得代碼更安全、也更直觀。
一個 case 也可以包含多個模式,用逗號把它們分開(如果太長了也可以分行寫):
> switch?`some value to consider`?{ case?`value 1`,?`value 2`:?`statements`?}
>
> 注意: 如果想要貫穿至特定的 case 分支中,請使用`fallthrough`語句,詳情請參考[貫穿(Fallthrough)](http://wiki.jikexueyuan.com/project/swift/chapter2/05_Control_Flow.html#fallthrough)。
#### 區間匹配
case 分支的模式也可以是一個值的區間。下面的例子展示了如何使用區間匹配來輸出任意數字對應的自然語言格式:
~~~
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
var 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."
~~~
在上例中,`approximateCount`在一個`switch`聲明中被估值。每一個`case`都與之進行比較。因為`approximateCount`落在了12到100的區間,所以`naturalCount`等于`"dozens of"`值,并且此后這段執行跳出了`switch`聲明。
> 注意: 閉區間操作符(`...`)以及半開區間操作符(`..<`)功能被重載去返回`IntervalType`或`Range`。一個區間可以決定他是否包含特定的元素,就像當匹配一個`switch`聲明的`case`一樣。區間是一個連續值的集合,可以用`for-in`語句遍歷它。
#### 元組(Tuple)
我們可以使用元組在同一個`switch`語句中測試多個值。元組中的元素可以是值,也可以是區間。另外,使用下劃線(`_`)來匹配所有可能的值。
下面的例子展示了如何使用一個`(Int, Int)`類型的元組來分類下圖中的點(x, y):
~~~
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("(0, 0) is at the origin")
case (_, 0):
print("(\(somePoint.0), 0) is on the x-axis")
case (0, _):
print("(0, \(somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
print("(\(somePoint.0), \(somePoint.1)) is inside the box")
default:
print("(\(somePoint.0), \(somePoint.1)) is outside of the box")
}
// 輸出 "(1, 1) is inside the box"
~~~

在上面的例子中,`switch`語句會判斷某個點是否是原點(0, 0),是否在紅色的x軸上,是否在黃色y軸上,是否在一個以原點為中心的4x4的矩形里,或者在這個矩形外面。
不像 C 語言,Swift 允許多個 case 匹配同一個值。實際上,在這個例子中,點(0, 0)可以匹配所有_四個 case_。但是,如果存在多個匹配,那么只會執行第一個被匹配到的 case 分支。考慮點(0, 0)會首先匹配`case (0, 0)`,因此剩下的能夠匹配(0, 0)的 case 分支都會被忽視掉。
#### 值綁定(Value Bindings)
case 分支的模式允許將匹配的值綁定到一個臨時的常量或變量,這些常量或變量在該 case 分支里就可以被引用了——這種行為被稱為_值綁定_(value binding)。
下面的例子展示了如何在一個`(Int, Int)`類型的元組中使用值綁定來分類下圖中的點(x, y):
~~~
let anotherPoint = (2, 0)
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))")
}
// 輸出 "on the x-axis with an x value of 2"
~~~

在上面的例子中,`switch`語句會判斷某個點是否在紅色的x軸上,是否在黃色y軸上,或者不在坐標軸上。
這三個 case 都聲明了常量`x`和`y`的占位符,用于臨時獲取元組`anotherPoint`的一個或兩個值。第一個 case ——`case (let x, 0)`將匹配一個縱坐標為`0`的點,并把這個點的橫坐標賦給臨時的常量`x`。類似的,第二個 case ——`case (0, let y)`將匹配一個橫坐標為`0`的點,并把這個點的縱坐標賦給臨時的常量`y`。
一旦聲明了這些臨時的常量,它們就可以在其對應的 case 分支里引用。在這個例子中,它們用于簡化`print(_:)`的書寫。
請注意,這個`switch`語句不包含默認分支。這是因為最后一個 case ——`case let(x, y)`聲明了一個可以匹配余下所有值的元組。這使得`switch`語句已經完備了,因此不需要再書寫默認分支。
在上面的例子中,`x`和`y`是常量,這是因為沒有必要在其對應的 case 分支中修改它們的值。然而,它們也可以是變量——程序將會創建臨時變量,并用相應的值初始化它。修改這些變量只會影響其對應的 case 分支。
#### 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"
~~~

在上面的例子中,`switch`語句會判斷某個點是否在綠色的對角線`x == y`上,是否在紫色的對角線`x == -y`上,或者不在對角線上。
這三個 case 都聲明了常量`x`和`y`的占位符,用于臨時獲取元組`yetAnotherPoint`的兩個值。這些常量被用作`where`語句的一部分,從而創建一個動態的過濾器(filter)。當且僅當`where`語句的條件為`true`時,匹配到的 case 分支才會被執行。
就像是值綁定中的例子,由于最后一個 case 分支匹配了余下所有可能的值,`switch`語句就已經完備了,因此不需要再書寫默認分支。
## 控制轉移語句(Control Transfer Statements)
控制轉移語句改變你代碼的執行順序,通過它你可以實現代碼的跳轉。Swift有四種控制轉移語句。
* `continue`
* `break`
* `fallthrough`
* `return`
* `throw`
我們將會在下面討論`continue`、`break`和`fallthrough`語句。`return`語句將會在[函數](http://wiki.jikexueyuan.com/project/swift/chapter2/06_Functions.html)章節討論,`throw`語句會在[錯誤拋出](http://wiki.jikexueyuan.com/project/swift/chapter2/18_Error_Handling.html#throwing_errors)
### Continue
`continue`語句告訴一個循環體立刻停止本次循環迭代,重新開始下次循環迭代。就好像在說“本次循環迭代我已經執行完了”,但是并不會離開整個循環體。
> 注意: 在一個帶有條件和遞增的for循環體中,調用`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"
~~~
在上面的代碼中,只要匹配到元音字母或者空格字符,就調用`continue`語句,使本次循環迭代結束,從新開始下次循環迭代。這種行為使`switch`匹配到元音字母和空格字符時不做處理,而不是讓每一個匹配到的字符都被打印。
### Break
`break`語句會立刻結束整個控制流的執行。當你想要更早的結束一個`switch`代碼塊或者一個循環體時,你都可以使用`break`語句。
#### 循環語句中的 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).")
} else {
print("An integer value could not be found for \(numberSymbol).")
}
// 輸出 "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`語句到每個`switch`分支的末尾來阻止自動落入到下一個 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."
~~~
這個例子定義了一個`String`類型的變量`description`并且給它設置了一個初始值。函數使用`switch`邏輯來判斷`integerToDescribe`變量的值。當`integerToDescribe`的值屬于列表中的質數之一時,該函數添加一段文字在`description`后,來表明這個是數字是一個質數。然后它使用`fallthrough`關鍵字來“貫穿”到`default`分支中。`default`分支添加一段額外的文字在`description`的最后,至此`switch`代碼塊執行完了。
如果`integerToDescribe`的值不屬于列表中的任何質數,那么它不會匹配到第一個`switch`分支。而這里沒有其他特別的分支情況,所以`integerToDescribe`匹配到包含所有的`default`分支中。
當`switch`代碼塊執行完后,使用`print`函數打印該數字的描述。在這個例子中,數字`5`被準確的識別為了一個質數。
> 注意:?`fallthrough`關鍵字不會檢查它下一個將會落入執行的 case 中的匹配條件。`fallthrough`簡單地使代碼執行繼續連接到下一個 case 中的執行代碼,這和 C 語言標準中的`switch`語句特性是一樣的。
### 帶標簽的語句
在 Swift 中,你可以在循環體和`switch`代碼塊中嵌套循環體和`switch`代碼塊來創造復雜的控制流結構。然而,循環體和`switch`代碼塊兩者都可以使用`break`語句來提前結束整個方法體。因此,顯示地指明`break`語句想要終止的是哪個循環體或者`switch`代碼塊,會很有用。類似地,如果你有許多嵌套的循環體,顯示指明`continue`語句想要影響哪一個循環體也會非常有用。
為了實現這個目的,你可以使用標簽來標記一個循環體或者`switch`代碼塊,當使用`break`或者`continue`時,帶上這個標簽,可以控制該標簽代表對象的中斷或者執行。
產生一個帶標簽的語句是通過在該語句的關鍵詞的同一行前面放置一個標簽,并且該標簽后面還需帶著一個冒號。下面是一個`while`循環體的語法,同樣的規則適用于所有的循環體和`switch`代碼塊。
> `label name`: while?`condition`?{?`statements`?}
下面的例子是在一個帶有標簽的`while`循環體中調用`break`和`continue`語句,該循環體是前面章節中_蛇和梯子_的改編版本。這次,游戲增加了一條額外的規則:
* 為了獲勝,你必須_剛好_落在第 25 個方塊中。
如果某次擲骰子使你的移動超出第 25 個方塊,你必須重新擲骰子,直到你擲出的骰子數剛好使你能落在第 25 個方塊中。
游戲的棋盤和之前一樣:

`finalSquare`、`board`、`square`和`diceRoll`值被和之前一樣的方式初始化:
~~~
let finalSquare = 25
var board = [Int](count: finalSquare + 1, repeatedValue: 0)
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 {
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("Game over!")
~~~
每次循環迭代開始時擲骰子。與之前玩家擲完骰子就立即移動不同,這里使用了`switch`來考慮每次移動可能產生的結果,從而決定玩家本次是否能夠移動。
* 如果骰子數剛好使玩家移動到最終的方格里,游戲結束。`break gameLoop`語句跳轉控制去執行`while`循環體后的第一行代碼,游戲結束。
* 如果骰子數將會使玩家的移動超出最后的方格,那么這種移動是不合法的,玩家需要重新擲骰子。`continue gameLoop`語句結束本次`while`循環的迭代,開始下一次循環迭代。
* 在剩余的所有情況中,骰子數產生的都是合法的移動。玩家向前移動骰子數個方格,然后游戲邏輯再處理玩家當前是否處于蛇頭或者梯子的底部。本次循環迭代結束,控制跳轉到`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(["name": "John"])
// prints "Hello John!"
// prints "I hope the weather is nice near you."
greet(["name": "Jane", "location": "Cupertino"])
// prints "Hello Jane!"
// prints "I hope the weather is nice in Cupertino."
~~~
如果`guard`語句的條件被滿足,則在保護語句的封閉大括號結束后繼續執行代碼。任何使用了可選綁定作為條件的一部分并被分配了值的變量或常量對于剩下的保護語句出現的代碼段是可用的。
如果條件不被滿足,在`else`分支上的代碼就會被執行。這個分支必須轉移控制以退出`guard`語句出現的代碼段。它可以用控制轉移語句如`return`,`break`或`continue`做這件事,或者它調用了一個不返回的方法或函數,例如`fatalError()`。
相比于可以實現同樣功能的`if`語句,按需使用`guard`語句會提升我們代碼的可靠性。 它可以使你的代碼連貫的被執行而不需要將它包在`else`塊中,它可以使你處理違反要求的代碼接近要求。
### 檢測API是否可用
Swift 有內置支持去檢查接口的可用性的,這可以確保我們不會不小心地使用對于當前部署目標不可用的API。
編譯器使用SDK中的可用信息來驗證在我們在可用部署目標指定項目的代碼中所有的API調用。如果我們嘗試使用一個不可用的API,Swift會在編譯期報錯。
我們使用一個可用性條件在一個`if`或`guard`語句中去有條件的執行一段代碼,這取決于我們想要使用的API是否在運行時是可用的。編譯器使用從可用性條件語句中獲取的信息,這時它會去驗證在代碼塊中調用的API是否都可用。
~~~
if #available(iOS 9, OSX 10.10, *) {
// 在 iOS 使用 iOS 9 APIs , 并且在 OS X 使用 OS X v10.10 APIs
} else {
// 回滾至早前 iOS and OS X 的API
}
~~~
以上可用性條件指定在iOS,`if`段的代碼僅僅在iOS9及更高可運行;在OS X,僅在OS X v10.10及更高可運行。最后一個參數,`*`,是必須的并且指定在任何其他平臺上,`if`段的代碼在最小可用部署目標指定項目中執行。
在它普遍的形式中,可用性條件獲取了平臺名字和版本的清單。平臺名字可以是`iOS`,`OSX`或`watchOS`。除了特定的主板本號像iOS8,我們可以指定較小的版本號像iOS8.3以及 OS X v10.10.3。
~~~
if #available(`platform name` `version`, `...`, *) {
`statements to execute if the APIs are available`
} else {
`fallback statements to execute if the APIs are unavailable`
}
~~~
- 介紹
- 歡迎使用 Swift
- 關于 Swift
- Swift 初見
- Swift 版本歷史記錄
- Swift1.0 發布內容
- Swift 教程
- 基礎部分
- 基本運算符
- 字符串和字符
- 集合類型
- 控制流
- 函數
- 閉包
- 枚舉
- 類和結構體
- 屬性
- 方法
- 下標腳本
- 繼承
- 構造過程
- 析構過程
- 自動引用計數
- 可選鏈
- 錯誤處理
- 類型轉換
- 嵌套類型
- 擴展
- 協議
- 泛型
- 權限控制
- 高級操作符
- 語言參考
- 關于語言參考
- 詞法結構
- 類型
- 表達式
- 語句
- 聲明
- 特性
- 模式
- 泛型參數
- 語法總結
- 蘋果官方Blog官方翻譯
- Access Control 權限控制的黑與白
- 造個類型不是夢-白話Swift類型創建
- WWDC里面的那個“大炮打氣球”
- Swift與C語言指針友好合作
- 引用類型和值類型的恩怨
- 訪問控制和Protected
- 可選類型完美解決占位問題