本頁包含內容:
Swift提供了類似 C 語言的流程控制結構,包括可以多次執行任務的for
和while
循環,基於特定條件選擇執行不同代碼分支的if
和switch
語句,還有控制流程跳轉到其他代碼的break
和continue
語句。
除了 C 語言裡面傳統的 for 條件遞增(for-condition-increment
)循環,Swift 還增加了for-in
循環,用來更簡單地遍歷數組(array),字典(dictionary),區間(range),字符串(string)和其他序列類型。
Swift 的switch
語句比 C 語言中更加強大。在 C 語言中,如果某個 case 不小心漏寫了break
,這個 case 就會貫穿(fallthrough)至下一個 case,Swift 無需寫break
,所以不會發生這種貫穿(fallthrough)的情況。case 還可以匹配更多的類型模式,包括區間匹配(range matching),元組(tuple)和特定類型的描述。switch
的 case 語句中匹配的值可以是由 case 體內部臨時的常量或者變量決定,也可以由where
分句描述更複雜的匹配條件。
for
循環用來按照指定的次數多次執行一系列語句。Swift 提供兩種for
循環形式:
for-in
用來遍歷一個區間(range),序列(sequence),集合(collection),系列(progression)裡面所有的元素執行一系列語句。- for條件遞增(
for-condition-increment
)語句,用來重複執行一系列語句直到達成特定條件達成,一般通過在每次循環完成後增加計數器的值來實現。
你可以使用for-in
循環來遍歷一個集合裡面的所有元素,例如由數字表示的區間、數組中的元素、字符串中的字符。
下面的例子用來輸出乘 5 乘法表前面一部分內容:
for index in 1...5 {
println("\(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
),之後println
方法會再執行一次。整個過程會進行到閉區間結尾為止。
上面的例子中,index
是一個每次循環遍歷開始時被自動賦值的常量。這種情況下,index
在使用前不需要聲明,只需要將它包含在循環的聲明中,就可以對其進行隱式聲明,而無需使用let
關鍵字聲明。
注意:
index
常量只存在於循環的生命週期裡。如果你想在循環完成後訪問index
的值,又或者想讓index
成為一個變量而不是常量,你必須在循環之前自己進行聲明。
如果你不需要知道區間內每一項的值,你可以使用下劃線(_
)替代變量名來忽略對值的訪問:
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
println("\(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 {
println("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!
你也可以通過遍歷一個字典來訪問它的鍵值對(key-value pairs)。遍歷字典時,字典的每項元素會以(key, value)
元組的形式返回,你可以在for-in
循環中使用顯式的常量名稱來解讀(key, value)
元組。下面的例子中,字典的鍵(key)解讀為常量animalName
,字典的值會被解讀為常量legCount
:
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
println("\(animalName)s have \(legCount) legs")
}
// spiders have 8 legs
// ants have 6 legs
// cats have 4 legs
字典元素的遍歷順序和插入順序可能不同,字典的內容在內部是無序的,所以遍歷元素時不能保證順序。關於數組和字典,詳情參見集合類型。
除了數組和字典,你也可以使用for-in
循環來遍歷字符串中的字符(Character
):
for character in "Hello" {
println(character)
}
// H
// e
// l
// l
// o
除了for-in
循環,Swift 提供使用條件判斷和遞增方法的標準 C 樣式for
循環:
for var index = 0; index < 3; ++index {
println("index is \(index)")
}
// index is 0
// index is 1
// index is 2
下面是一般情況下這種循環方式的格式:
for
initialization
;condition
;increment
{
statements
}
和 C 語言中一樣,分號將循環的定義分為 3 個部分,不同的是,Swift 不需要使用圓括號將「initialization; condition; increment」包括起來。
這個循環執行流程如下:
- 循環首次啟動時,初始化表達式(initialization expression)被調用一次,用來初始化循環所需的所有常量和變量。
- 條件表達式(condition expression)被調用,如果表達式調用結果為
false
,循環結束,繼續執行for
循環關閉大括號 (}
)之後的代碼。如果表達式調用結果為true
,則會執行大括號內部的代碼(statements)。 - 執行所有語句(statements)之後,執行遞增表達式(increment expression)。通常會增加或減少計數器的值,或者根據語句(statements)輸出來修改某一個初始化的變量。當遞增表達式運行完成後,重複執行第 2 步,條件表達式會再次執行。
上述描述和循環格式等同於:
initialization
whilecondition
{
statements
increment
}
在初始化表達式中聲明的常量和變量(比如var index = 0
)只在for
循環的生命週期裡有效。如果想在循環結束後訪問index
的值,你必須要在循環生命週期開始前聲明index
。
var index: Int
for index = 0; index < 3; ++index {
println("index is \(index)")
}
// index is 0
// index is 1
// index is 2
println("The loop statements were executed \(index) times")
// 輸出 "The loop statements were executed 3 times
注意index
在循環結束後最終的值是3
而不是2
。最後一次調用遞增表達式++index
會將index
設置為3
,從而導致index < 3
條件為false
,並終止循環。
while
循環運行一系列語句直到條件變成false
。這類循環適合使用在第一次迭代前迭代次數未知的情況下。Swift 提供兩種while
循環形式:
while
循環,每次在循環開始時計算條件是否符合;do-while
循環,每次在循環結束時計算條件是否符合。
while
循環從計算單一條件開始。如果條件為true
,會重複運行一系列語句,直到條件變為false
。
下面是一般情況下 while
循環格式:
while
condition
{
statements
}
下面的例子來玩一個叫做_蛇和梯子(Snakes and Ladders)的小遊戲,也叫做_滑道和梯子(Chutes and Ladders):
遊戲的規則如下:
- 遊戲盤面包括 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]
}
}
println("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
循環開始時,我們並不知道遊戲的長度或者循環的次數,只有在達成指定條件時循環才會結束。
while
循環的另外一種形式是do-while
,它和while
的區別是在判斷循環條件之前,先執行一次循環的代碼塊,然後重複循環直到條件為false
。
下面是一般情況下 do-while
循環的格式:
do {
statements
} whilecondition
還是蛇和梯子的遊戲,使用do-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
do-while
的循環版本,循環中_第一步_就需要去檢測是否在梯子或者蛇的方塊上。沒有梯子會讓玩家直接上到第 25 個方格,所以玩家不會通過梯子直接贏得遊戲。這樣在循環開始時先檢測是否踩在梯子或者蛇上是安全的。
遊戲開始時,玩家在第 0 個方格上,board[0]
一直等於 0, 不會有什麼影響:
do {
// 順著梯子爬上去或者順著蛇滑下去
square += board[square]
// 擲骰子
if ++diceRoll == 7 { diceRoll = 1 }
// 根據點數移動
square += diceRoll
} while square < finalSquare
println("Game over!")
檢測完玩家是否踩在梯子或者蛇上之後,開始擲骰子,然後玩家向前移動diceRoll
個方格,本輪循環結束。
循環條件(while square < finalSquare
)和while
方式相同,但是只會在循環結束後進行計算。在這個遊戲中,do-while
表現得比while
循環更好。do-while
方式會在條件判斷square
沒有超出後直接運行square += board[square]
,這種方式可以去掉while
版本中的數組越界判斷。
根據特定的條件執行特定的代碼通常是十分有用的,例如:當錯誤發生時,你可能想運行額外的代碼;或者,當輸入的值太大或太小時,向用戶顯示一條消息等。要實現這些功能,你就需要使用條件語句。
Swift 提供兩種類型的條件語句:if
語句和switch
語句。通常,當條件較為簡單且可能的情況很少時,使用if
語句。而switch
語句更適用於條件較複雜、可能情況較多且需要用到模式匹配(pattern-matching)的情境。
if
語句最簡單的形式就是只包含一個條件,當且僅當該條件為true
時,才執行相關代碼:
var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
println("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 {
println("It's very cold. Consider wearing a scarf.")
} else {
println("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 {
println("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
println("It's really warm. Don't forget to wear sunscreen.")
} else {
println("It's not that cold. Wear a t-shirt.")
}
// 輸出 "It's really warm. Don't forget to wear sunscreen."
在上面的例子中,額外的if
語句用於判斷是不是特別熱。而最後的else
語句被保留了下來,用於打印既不冷也不熱時的消息。
實際上,最後的else
語句是可選的:
temperatureInFahrenheit = 72
if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
println("It's really warm. Don't forget to wear sunscreen.")
}
在這個例子中,由於既不冷也不熱,所以不會觸發if
或else if
分支,也就不會打印任何消息。
switch
語句會嘗試把某個值與若干個模式(pattern)進行匹配。根據第一個匹配成功的模式,switch
語句會執行對應的代碼。當有可能的情況較多時,通常用switch
語句替換if
語句。
switch
語句最簡單的形式就是把某個值與一個或若干個相同類型的值作比較:
switch
some value to consider
{
casevalue 1
:
respond to value 1
casevalue 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":
println("\(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":
println("\(someCharacter) is a consonant")
default:
println("\(someCharacter) is not a vowel or a consonant")
}
// 輸出 "e is a vowel"
在這個例子中,第一個 case 分支用於匹配五個元音,第二個 case 分支用於匹配所有的輔音。
由於為其它可能的字符寫 case 分支沒有實際的意義,因此在這個例子中使用了默認分支來處理剩下的既不是元音也不是輔音的字符——這就保證了switch
語句的完備性。
與 C 語言和 Objective-C 中的switch
語句不同,在 Swift 中,當匹配的 case 分支中的代碼執行完畢後,程序會終止switch
語句,而不會繼續執行下一個 case 分支。這也就是說,不需要在 case 分支中顯式地使用break
語句。這使得switch
語句更安全、更易用,也避免了因忘記寫break
語句而產生的錯誤。
注意:
你依然可以在 case 分支中的代碼執行完畢前跳出,詳情請參考Switch 語句中的 break。
每一個 case 分支都必須包含至少一條語句。像下面這樣書寫代碼是無效的,因為第一個 case 分支是空的:
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a":
case "A":
println("The letter A")
default:
println("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
{
casevalue 1
,
value 2
:
statements
}
注意:
如果想要貫穿至特定的 case 分支中,請使用fallthrough
語句,詳情請參考貫穿(Fallthrough)。
case 分支的模式也可以是一個值的區間。下面的例子展示了如何使用區間匹配來輸出任意數字對應的自然語言格式:
let count = 3_000_000_000_000
let countedThings = "stars in the Milky Way"
var naturalCount: String
switch count {
case 0:
naturalCount = "no"
case 1...3:
naturalCount = "a few"
case 4...9:
naturalCount = "several"
case 10...99:
naturalCount = "tens of"
case 100...999:
naturalCount = "hundreds of"
case 1000...999_999:
naturalCount = "thousands of"
default:
naturalCount = "millions and millions of"
}
println("There are \(naturalCount) \(countedThings).")
// 輸出 "There are millions and millions of stars in the Milky Way."
你可以使用元組在同一個switch
語句中測試多個值。元組中的元素可以是值,也可以是區間。另外,使用下劃線(_
)來匹配所有可能的值。
下面的例子展示了如何使用一個(Int, Int)
類型的元組來分類下圖中的點(x, y):
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
println("(0, 0) is at the origin")
case (_, 0):
println("(\(somePoint.0), 0) is on the x-axis")
case (0, _):
println("(0, \(somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
println("(\(somePoint.0), \(somePoint.1)) is inside the box")
default:
println("(\(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 分支都會被忽視掉。
case 分支的模式允許將匹配的值綁定到一個臨時的常量或變量,這些常量或變量在該 case 分支裡就可以被引用了——這種行為被稱為值綁定(value binding)。
下面的例子展示了如何在一個(Int, Int)
類型的元組中使用值綁定來分類下圖中的點(x, y):
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
println("on the x-axis with an x value of \(x)")
case (0, let y):
println("on the y-axis with a y value of \(y)")
case let (x, y):
println("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 分支裡引用。在這個例子中,它們用於簡化println
的書寫。
請注意,這個switch
語句不包含默認分支。這是因為最後一個 case ——case let(x, y)
聲明了一個可以匹配餘下所有值的元組。這使得switch
語句已經完備了,因此不需要再書寫默認分支。
在上面的例子中,x
和y
是常量,這是因為沒有必要在其對應的 case 分支中修改它們的值。然而,它們也可以是變量——程序將會創建臨時變量,並用相應的值初始化它。修改這些變量只會影響其對應的 case 分支。
case 分支的模式可以使用where
語句來判斷額外的條件。
下面的例子把下圖中的點(x, y)進行了分類:
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
println("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
println("(\(x), \(y)) is on the line x == -y")
case let (x, y):
println("(\(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
語句就已經完備了,因此不需要再書寫默認分支。
控制轉移語句改變你代碼的執行順序,通過它你可以實現代碼的跳轉。Swift有四種控制轉移語句。
- continue
- break
- fallthrough
- return
我們將會在下面討論continue
、break
和fallthrough
語句。return
語句將會在函數章節討論。
continue
語句告訴一個循環體立刻停止本次循環迭代,重新開始下次循環迭代。就好像在說「本次循環迭代我已經執行完了」,但是並不會離開整個循環體。
注意:
在一個for條件遞增(for-condition-increment
)循環體中,在調用continue
語句後,迭代增量仍然會被計算求值。循環體繼續像往常一樣工作,僅僅只是循環體中的執行代碼會被跳過。
下面的例子把一個小寫字符串中的元音字母和空格字符移除,生成了一個含義模糊的短句:
let puzzleInput = "great minds think alike"
var puzzleOutput = ""
for character in puzzleInput {
switch character {
case "a", "e", "i", "o", "u", " ":
continue
default:
puzzleOutput += character
}
}
println(puzzleOutput)
// 輸出 "grtmndsthnklk"
在上面的代碼中,只要匹配到元音字母或者空格字符,就調用continue
語句,使本次循環迭代結束,從新開始下次循環迭代。這種行為使switch
匹配到元音字母和空格字符時不做處理,而不是讓每一個匹配到的字符都被打印。
break
語句會立刻結束整個控制流的執行。當你想要更早的結束一個switch
代碼塊或者一個循環體時,你都可以使用break
語句。
當在一個循環體中使用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 {
println("The integer value of \(numberSymbol) is \(integerValue).")
} else {
println("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
語句。
Swift 中的switch
不會從上一個 case 分支落入到下一個 case 分支中。相反,只要第一個匹配到的 case 分支完成了它需要執行的語句,整個switch
代碼塊完成了它的執行。相比之下,C 語言要求你顯示的插入break
語句到每個switch
分支的末尾來阻止自動落入到下一個 case 分支中。Swift 的這種避免默認落入到下一個分支中的特性意味著它的switch
功能要比 C 語言的更加清晰和可預測,可以避免無意識地執行多個 case 分支從而引發的錯誤。
如果你確實需要 C 風格的貫穿(fallthrough)的特性,你可以在每個需要該特性的 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."
}
println(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
代碼塊執行完後,使用println
函數打印該數字的描述。在這個例子中,數字5
被準確的識別為了一個質數。
注意:
fallthrough
關鍵字不會檢查它下一個將會落入執行的 case 中的匹配條件。fallthrough
簡單地使代碼執行繼續連接到下一個 case 中的執行代碼,這和 C 語言標準中的switch
語句特性是一樣的。
在 Swift 中,你可以在循環體和switch
代碼塊中嵌套循環體和switch
代碼塊來創造複雜的控制流結構。然而,循環體和switch
代碼塊兩者都可以使用break
語句來提前結束整個方法體。因此,顯示地指明break
語句想要終止的是哪個循環體或者switch
代碼塊,會很有用。類似地,如果你有許多嵌套的循環體,顯示指明continue
語句想要影響哪一個循環體也會非常有用。
為了實現這個目的,你可以使用標籤來標記一個循環體或者switch
代碼塊,當使用break
或者continue
時,帶上這個標籤,可以控制該標籤代表對象的中斷或者執行。
產生一個帶標籤的語句是通過在該語句的關鍵詞的同一行前面放置一個標籤,並且該標籤後面還需帶著一個冒號。下面是一個while
循環體的語法,同樣的規則適用於所有的循環體和switch
代碼塊。
label name
: whilecondition
{
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]
}
}
println("Game over!")
每次循環迭代開始時擲骰子。與之前玩家擲完骰子就立即移動不同,這裡使用了switch
來考慮每次移動可能產生的結果,從而決定玩家本次是否能夠移動。
- 如果骰子數剛好使玩家移動到最終的方格裡,遊戲結束。
break gameLoop
語句跳轉控制去執行while
循環體後的第一行代碼,遊戲結束。 - 如果骰子數將會使玩家的移動超出最後的方格,那麼這種移動是不合法的,玩家需要重新擲骰子。
continue gameLoop
語句結束本次while
循環的迭代,開始下一次循環迭代。 - 在剩餘的所有情況中,骰子數產生的都是合法的移動。玩家向前移動骰子數個方格,然後遊戲邏輯再處理玩家當前是否處於蛇頭或者梯子的底部。本次循環迭代結束,控制跳轉到
while
循環體的條件判斷語句處,再決定是否能夠繼續執行下次循環迭代。
注意:
如果上述的break
語句沒有使用gameLoop
標籤,那麼它將會中斷switch
代碼塊而不是while
循環體。使用gameLoop
標籤清晰的表明了break
想要中斷的是哪個代碼塊。 同時請注意,當調用continue gameLoop
去跳轉到下一次循環迭代時,這裡使用gameLoop
標籤並不是嚴格必須的。因為在這個遊戲中,只有一個循環體,所以continue
語句會影響到哪個循環體是沒有歧義的。然而,continue
語句使用gameLoop
標籤也是沒有危害的。這樣做符合標籤的使用規則,同時參照旁邊的break gameLoop
,能夠使遊戲的邏輯更加清晰和易於理解。