本頁包含內容:
- For-In 循環
- While 循環
- 條件語句
- 控制轉移語句(Control Transfer Statements)
- 提前退出
- 檢測 API 可用性
Swift提供了多種流程控制結構,包括可以多次執行任務的while
循環,基于特定條件選擇執行不同代碼分支的if
、guard
和switch
語句,還有控制流程跳轉到其他代碼的break
和continue
語句。
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-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(_: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"
這個例子計算 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
字典元素的遍歷順序和插入順序可能不同,字典的內容在內部是無序的,所以遍歷元素時不能保證順序。
While 循環
while
循環運行一系列語句直到條件變成false
。這類循環適合使用在第一次迭代前迭代次數未知的情況下。Swift 提供兩種while
循環形式:
while
循環,每次在循環開始時計算條件是否符合;repeat-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 {// 擲骰子diceRoll += 1if diceRoll == 7 { diceRoll = 1 }// 根據點數移動square += diceRollif square < board.count {// 如果玩家還在棋盤上,順著梯子爬上去或者順著蛇滑下去square += board[square]}
}
print("Game over!")
本例中使用了最簡單的方法來模擬擲骰子。?diceRoll
的值并不是一個隨機數,而是以0
為初始值,之后每一次while
循環,diceRoll
的值增加 1 ,然后檢測是否超出了最大值。任何時候如果diceRoll
的值等于 7 時,就超過了骰子的最大值,會被重置為1
。所以diceRoll
的取值順序會一直是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]// 擲骰子diceRoll += 1if 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 = 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?構成。為了匹配某些更特定的值,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
跳出
每一個 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
語句
區間匹配
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(_:separator:terminator:)
的書寫。
請注意,這個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"
在上面的例子中,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
語句。
Continue
continue
語句告訴一個循環體立刻停止本次循環迭代,重新開始下次循環迭代。就好像在說“本次循環迭代我已經執行完了”,但是并不會離開整個循環體。
注意: 在一個帶有條件和遞增的 for 循環體中,調用
continue
語句后,迭代增量仍然會被計算求值。循環體繼續像往常一樣工作,僅僅只是循環體中的執行代碼會被跳過。
下面的例子把一個小寫字符串中的元音字母和空格字符移除,生成了一個含義模糊的短句:
let puzzleInput = "great minds think alike"
var puzzleOutput = ""
for character in puzzleInput.characters {switch character {case "a", "e", "i", "o", "u", " ":continuedefault: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(_:separator:terminator:)
函數打印該數字的描述。在這個例子中,數字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 gameLoopcase let newSquare where newSquare > finalSquare:// 超出最后一個方塊,再擲一次骰子continue gameLoopdefault:// 本次移動有效square += diceRollsquare += 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"])
// 輸出 "Hello John!"
// 輸出 "I hope the weather is nice near you."
greet(["name": "Jane", "location": "Cupertino"])
// 輸出 "Hello Jane!"
// 輸出 "I hope the weather is nice in Cupertino."
如果guard
語句的條件被滿足,則在保護語句的封閉大括號結束后繼續執行代碼。任何使用了可選綁定作為條件的一部分并被分配了值的變量或常量對于剩下的保護語句出現的代碼段是可用的。
如果條件不被滿足,在else
分支上的代碼就會被執行。這個分支必須轉移控制以退出guard
語句出現的代碼段。它可以用控制轉移語句如return
,break
,continue
或者throw
做這件事,或者調用一個不返回的方法或函數,例如fatalError()
。
相比于可以實現同樣功能的if
語句,按需使用guard
語句會提升我們代碼的可靠性。它可以使你的代碼連貫的被執行而不需要將它包在else
塊中,它可以使你用于處理違反某個條件的代碼緊鄰于對該條件進行判斷的地方。
檢測 API 可用性
Swift 有檢查 API 可用性的內置支持,這可以確保我們不會不小心地使用對于當前部署目標不可用的 API。
編譯器使用 SDK 中的可用信息來驗證我們的代碼中使用的所有 API 在項目指定的部署目標上是否可用。如果我們嘗試使用一個不可用的 API,Swift 會在編譯時報錯。
我們使用一個可用性條件在一個if
或guard
語句中去有條件的執行一段代碼,這取決于我們想要使用的 API 是否在運行時是可用的。編譯器使用從可用性條件語句中獲取的信息去驗證在代碼塊中調用的 API 是否都可用。
if #available(iOS 9, OSX 10.10, *) {// 在 iOS 使用 iOS 9 的 API, 在 OS X 使用 OS X v10.10 的 API
} else {// 使用先前版本的 iOS 和 OS X 的 API
}
以上可用性條件指定在 iOS,if
段的代碼僅僅在 iOS 9 及更高可運行;在 OS X,僅在 OS X v10.10 及更高可運行。最后一個參數,*
,是必須的并且指定在任何其他平臺上,if
段的代碼在最小可用部署目標指定項目中執行。
在它普遍的形式中,可用性條件獲取了平臺名字和版本的清單。平臺名字可以是iOS
,OSX
或watchOS
。除了特定的主板本號像 iOS 8,我們可以指定較小的版本號像iOS 8.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`
}
再次感謝~~~~~~~~~~