控制流
Swift 提供了多種流程控制結(jié)構(gòu)荔仁,包括可以多次執(zhí)行任務(wù)的 while
循環(huán)伍宦,基于特定條件選擇執(zhí)行不同代碼分支的 if
、guard
和 switch
語句乏梁,還有控制流程跳轉(zhuǎn)到其他代碼位置的 break
和 continue
語句次洼。
Swift 還提供了 for-in
循環(huán),用來更簡單地遍歷數(shù)組(Array)掌呜,字典(Dictionary)滓玖,區(qū)間(Range),字符串(String)和其他序列類型质蕉。
Swift 的 switch
語句比許多類 C 語言要更加強(qiáng)大势篡。case 還可以匹配很多不同的模式,包括范圍匹配模暗,元組(tuple)和特定類型匹配禁悠。switch
語句的 case 中匹配的值可以聲明為臨時常量或變量,在 case 作用域內(nèi)使用兑宇,也可以配合 where
來描述更復(fù)雜的匹配條件碍侦。
For-In 循環(huán)
你可以使用 for-in
循環(huán)來遍歷一個集合中的所有元素,例如數(shù)組中的元素、范圍內(nèi)的數(shù)字或者字符串中的字符瓷产。
以下例子使用 for-in
遍歷一個數(shù)組所有元素:
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!
你也可以通過遍歷一個字典來訪問它的鍵值對站玄。遍歷字典時,字典的每項(xiàng)元素會以 (key, value)
元組的形式返回濒旦,你可以在 for-in
循環(huán)中使用顯式的常量名稱來解讀 (key, value)
元組株旷。下面的例子中,字典的鍵會聲明為 animalName
常量尔邓,字典的值會聲明為 legCount
常量:
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
print("\(animalName)s have \(legCount) legs")
}
// cats have 4 legs
// ants have 6 legs
// spiders have 8 legs
字典的內(nèi)容理論上是無序的晾剖,遍歷元素時的順序是無法確定的。將元素插入字典的順序并不會決定它們被遍歷的順序梯嗽。關(guān)于數(shù)組和字典的細(xì)節(jié)齿尽,參見 集合類型。
for-in
循環(huán)還可以使用數(shù)字范圍灯节。下面的例子用來輸出乘法表的一部分內(nèi)容:
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
例子中用來進(jìn)行遍歷的元素是使用閉區(qū)間操作符(...
)表示的從 1
到 5
的數(shù)字區(qū)間循头。index
被賦值為閉區(qū)間中的第一個數(shù)字(1
),然后循環(huán)中的語句被執(zhí)行一次炎疆。在本例中贷岸,這個循環(huán)只包含一個語句,用來輸出當(dāng)前 index
值所對應(yīng)的乘 5 乘法表的結(jié)果磷雇。該語句執(zhí)行后偿警,index
的值被更新為閉區(qū)間中的第二個數(shù)字(2
),之后 print(_:separator:terminator:)
函數(shù)會再執(zhí)行一次唯笙。整個過程會進(jìn)行到閉區(qū)間結(jié)尾為止螟蒸。
上面的例子中,index
是一個每次循環(huán)遍歷開始時被自動賦值的常量崩掘。這種情況下七嫌,index
在使用前不需要聲明,只需要將它包含在循環(huán)的聲明中苞慢,就可以對其進(jìn)行隱式聲明诵原,而無需使用 let
關(guān)鍵字聲明。
如果你不需要區(qū)間序列內(nèi)每一項(xiàng)的值挽放,你可以使用下劃線(_
)替代變量名來忽略這個值:
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”
這個例子計(jì)算 base 這個數(shù)的 power 次冪(本例中绍赛,是 3
的 10
次冪),從 1
(3
的 0
次冪)開始做 3
的乘法辑畦, 進(jìn)行 10
次吗蚌,使用 1
到 10
的閉區(qū)間循環(huán)。這個計(jì)算并不需要知道每一次循環(huán)中計(jì)數(shù)器具體的值纯出,只需要執(zhí)行了正確的循環(huán)次數(shù)即可蚯妇。下劃線符號 _
(替代循環(huán)中的變量)能夠忽略當(dāng)前值敷燎,并且不提供循環(huán)遍歷時對值的訪問。
在某些情況下箩言,你可能不想使用包括兩個端點(diǎn)的閉區(qū)間硬贯。想象一下,你在一個手表上繪制分鐘的刻度線陨收〕纬桑總共 60
個刻度,從 0
分開始畏吓。使用半開區(qū)間運(yùn)算符(..<
)來表示一個左閉右開的區(qū)間。有關(guān)區(qū)間的更多信息卫漫,請參閱 區(qū)間運(yùn)算符菲饼。
let minutes = 60
for tickMark in 0..<minutes {
// 每一分鐘都渲染一個刻度線(60次)
}
一些用戶可能在其 UI 中可能需要較少的刻度。他們可以每 5 分鐘作為一個刻度列赎。使用 stride(from:to:by:)
函數(shù)跳過不需要的標(biāo)記宏悦。
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
// 每5分鐘渲染一個刻度線(0, 5, 10, 15 ... 45, 50, 55)
}
可以在閉區(qū)間使用 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
循環(huán)來遍歷范圍、數(shù)組包吝、字典和字符串饼煞。你可以用它來遍歷任何的集合,包括實(shí)現(xiàn)了 Sequence 協(xié)議的自定義類或集合類型诗越。
While 循環(huán)
while
循環(huán)會一直運(yùn)行一段語句直到條件變成 false
砖瞧。這類循環(huán)適合使用在第一次迭代前,迭代次數(shù)未知的情況下嚷狞。Swift 提供兩種 while
循環(huán)形式:
while
循環(huán)块促,每次在循環(huán)開始時計(jì)算條件是否符合;repeat-while
循環(huán)床未,每次在循環(huán)結(jié)束時計(jì)算條件是否符合竭翠。
While
while
循環(huán)從計(jì)算一個條件開始。如果條件為 true
薇搁,會重復(fù)運(yùn)行一段語句斋扰,直到條件變?yōu)?false
。
下面是 while
循環(huán)的一般格式:
while condition {
statements
}
下面的例子來玩一個叫做蛇和梯子(也叫做滑道和梯子)的小游戲:
游戲的規(guī)則如下:
游戲盤面包括 25 個方格啃洋,游戲目標(biāo)是達(dá)到或者超過第 25 個方格传货;
每一輪,你通過擲一個六面體骰子來確定你移動方塊的步數(shù)宏娄,移動的路線由上圖中橫向的虛線所示损离;
如果在某輪結(jié)束,你移動到了梯子的底部绝编,可以順著梯子爬上去僻澎;
如果在某輪結(jié)束貌踏,你移動到了蛇的頭部,你會順著蛇的身體滑下去窟勃。
游戲盤面可以使用一個 Int
數(shù)組來表達(dá)祖乳。數(shù)組的長度由一個 finalSquare
常量儲存,用來初始化數(shù)組和檢測最終勝利條件秉氧。游戲盤面由 26 個 Int
0 值初始化眷昆,而不是 25 個(由 0
到 25
,一共 26 個):
let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
一些方格被設(shè)置成特定的值來表示有蛇或者梯子汁咏。梯子底部的方格是一個正值亚斋,使你可以向上移動,蛇頭處的方格是一個負(fù)值攘滩,會讓你向下移動:
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
之間的差值)漂问。為了對齊語句赖瞒,這里使用了一元正運(yùn)算符(+i
)和一元負(fù)運(yùn)算符(-i
),并且小于 10 的數(shù)字都使用 0 補(bǔ)齊(這些語法的技巧不是必要的蚤假,只是為了讓代碼看起來更加整潔)栏饮。
玩家由左下角空白處編號為 0 的方格開始游戲。玩家第一次擲骰子后才會進(jìn)入游戲盤面:
var square = 0
var diceRoll = 0
while square < finalSquare {
// 擲骰子
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
// 根據(jù)點(diǎn)數(shù)移動
square += diceRoll
if square < board.count {
// 如果玩家還在棋盤上磷仰,順著梯子爬上去或者順著蛇滑下去
square += board[square]
}
}
print("Game over!")
本例中使用了最簡單的方法來模擬擲骰子袍嬉。diceRoll
的值并不是一個隨機(jī)數(shù),而是以 0
為初始值灶平,之后每一次 while
循環(huán)冬竟,diceRoll
的值增加 1 ,然后檢測是否超出了最大值民逼。當(dāng) diceRoll
的值等于 7 時泵殴,就超過了骰子的最大值,會被重置為 1
拼苍。所以 diceRoll
的取值順序會一直是 1
笑诅,2
,3
疮鲫,4
吆你,5
,6
俊犯,1
妇多,2
等。
擲完骰子后燕侠,玩家向前移動 diceRoll
個方格者祖,如果玩家移動超過了第 25 個方格立莉,這個時候游戲?qū)Y(jié)束贞盯,為了應(yīng)對這種情況亲桦,代碼會首先判斷 square
的值是否小于 board
的 count
屬性宽堆,只有小于才會在 board[square]
上增加 square
血巍,來向前或向后移動(遇到了梯子或者蛇)。
注意
如果沒有這個檢測(
square < board.count
)恋日,board[square]
可能會越界訪問board
數(shù)組祟偷,導(dǎo)致運(yùn)行時錯誤芹橡。
當(dāng)本輪 while
循環(huán)運(yùn)行完畢讥耗,會再檢測循環(huán)條件是否需要再運(yùn)行一次循環(huán)有勾。如果玩家移動到或者超過第 25 個方格,循環(huán)條件結(jié)果為 false
古程,此時游戲結(jié)束蔼卡。
while
循環(huán)比較適合本例中的這種情況,因?yàn)樵?while
循環(huán)開始時籍琳,我們并不知道游戲要跑多久,只有在達(dá)成指定條件時循環(huán)才會結(jié)束贷祈。
Repeat-While
while
循環(huán)的另外一種形式是 repeat-while
趋急,它和 while
的區(qū)別是在判斷循環(huán)條件之前,先執(zhí)行一次循環(huán)的代碼塊势誊。然后重復(fù)循環(huán)直到條件為 false
呜达。
注意
Swift 語言的
repeat-while
循環(huán)和其他語言中的do-while
循環(huán)是類似的。
下面是 repeat-while
循環(huán)的一般格式:
repeat {
statements
} while condition
還是蛇和梯子的游戲粟耻,使用 repeat-while
循環(huán)來替代 while
循環(huán)查近。finalSquare
、board
挤忙、square
和 diceRoll
的值初始化同 while
循環(huán)時一樣:
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
repeat-while
的循環(huán)版本霜威,循環(huán)中第一步就需要去檢測是否在梯子或者蛇的方塊上。沒有梯子會讓玩家直接上到第 25 個方格册烈,所以玩家不會通過梯子直接贏得游戲戈泼。這樣在循環(huán)開始時先檢測是否踩在梯子或者蛇上是安全的。
游戲開始時赏僧,玩家在第 0 個方格上大猛,board[0]
一直等于 0, 不會有什么影響:
repeat {
// 順著梯子爬上去或者順著蛇滑下去
square += board[square]
// 擲骰子
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
// 根據(jù)點(diǎn)數(shù)移動
square += diceRoll
} while square < finalSquare
print("Game over!")
檢測完玩家是否踩在梯子或者蛇上之后淀零,開始擲骰子挽绩,然后玩家向前移動 diceRoll
個方格,本輪循環(huán)結(jié)束驾中。
循環(huán)條件(while square < finalSquare
)和 while
方式相同唉堪,但是只會在循環(huán)結(jié)束后進(jìn)行計(jì)算模聋。在這個游戲中,repeat-while
表現(xiàn)得比 while
循環(huán)更好巨坊。repeat-while
方式會在條件判斷 square
沒有超出后直接運(yùn)行 square += board[square]
撬槽,這種方式可以比起前面 while
循環(huán)的版本,可以省去數(shù)組越界的檢查趾撵。
條件語句
根據(jù)特定的條件執(zhí)行特定的代碼通常是十分有用的侄柔。當(dāng)錯誤發(fā)生時,你可能想運(yùn)行額外的代碼占调;或者暂题,當(dāng)值太大或太小時,向用戶顯示一條消息究珊。要實(shí)現(xiàn)這些功能薪者,你就需要使用條件語句。
Swift 提供兩種類型的條件語句:if
語句和 switch
語句剿涮。通常言津,當(dāng)條件較為簡單且可能的情況很少時,使用 if
語句取试。而 switch
語句更適用于條件較復(fù)雜悬槽、有更多排列組合的時候。并且 switch
在需要用到模式匹配(pattern-matching)的情況下會更有用瞬浓。
If
if
語句最簡單的形式就是只包含一個條件初婆,只有該條件為 true
時,才執(zhí)行相關(guān)代碼:
var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
print("It's very cold. Consider wearing a scarf.")
}
// 輸出“It's very cold. Consider wearing a scarf.”
上面的例子會判斷溫度是否小于等于 32 華氏度(水的冰點(diǎn))猿棉。如果是磅叛,則打印一條消息;否則萨赁,不打印任何消息弊琴,繼續(xù)執(zhí)行 if
塊后面的代碼。
當(dāng)然杖爽,if
語句允許二選一執(zhí)行访雪,叫做 else
從句。也就是當(dāng)條件為 false
時掂林,執(zhí)行 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.”
顯然臣缀,這兩條分支中總有一條會被執(zhí)行。由于溫度已升至 40 華氏度泻帮,不算太冷精置,沒必要再圍圍巾。因此锣杂,else
分支就被觸發(fā)了脂倦。
你可以把多個 if
語句鏈接在一起番宁,來實(shí)現(xiàn)更多分支:
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
語句被保留了下來蝶押,用于打印既不冷也不熱時的消息。
實(shí)際上火欧,當(dāng)不需要完整判斷情況的時候棋电,最后的 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.")
}
在這個例子中,由于既不冷也不熱苇侵,所以不會觸發(fā) if
或 else if
分支赶盔,也就不會打印任何消息。
Switch
switch
語句會嘗試把某個值與若干個模式(pattern)進(jìn)行匹配榆浓。根據(jù)第一個匹配成功的模式于未,switch
語句會執(zhí)行對應(yīng)的代碼。當(dāng)有可能的情況較多時陡鹃,通常用 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 構(gòu)成,每個由 case
關(guān)鍵字開始萍鲸。為了匹配某些更特定的值闷叉,Swift 提供了幾種方法來進(jìn)行更復(fù)雜的模式匹配,這些模式將在本節(jié)的稍后部分提到猿推。
與 if
語句類似片习,每一個 case 都是代碼執(zhí)行的一條分支捌肴。switch
語句會決定哪一條分支應(yīng)該被執(zhí)行蹬叭,這個流程被稱作根據(jù)給定的值切換(switching)。
switch
語句必須是完備的状知。這就是說秽五,每一個可能的值都必須至少有一個 case 分支與之對應(yīng)。在某些不可能涵蓋所有值的情況下饥悴,你可以使用默認(rèn)(default
)分支來涵蓋其它所有沒有對應(yīng)的值坦喘,這個默認(rèn)分支必須在 switch
語句的最后面。
下面的例子使用 switch
語句來匹配一個名為 someCharacter
的小寫字符:
let someCharacter: Character = "z"
switch someCharacter {
case "a":
print("The first letter of the alphabet")
case "z":
print("The last letter of the alphabet")
default:
print("Some other character")
}
// 輸出“The last letter of the alphabet”
在這個例子中西设,第一個 case 分支用于匹配第一個英文字母 a
瓣铣,第二個 case 分支用于匹配最后一個字母 z
。因?yàn)?switch
語句必須有一個 case 分支用于覆蓋所有可能的字符贷揽,而不僅僅是所有的英文字母棠笑,所以 switch 語句使用 default
分支來匹配除了 a
和 z
外的所有值,這個分支保證了 switch 語句的完備性禽绪。
不存在隱式的貫穿
與 C 和 Objective-C 中的 switch
語句不同蓖救,在 Swift 中洪规,當(dāng)匹配的 case 分支中的代碼執(zhí)行完畢后,程序會終止 switch
語句循捺,而不會繼續(xù)執(zhí)行下一個 case 分支斩例。這也就是說,不需要在 case 分支中顯式地使用 break
語句从橘。這使得 switch
語句更安全念赶、更易用,也避免了漏寫 break
語句導(dǎo)致多個語言被執(zhí)行的錯誤洋满。
注意
雖然在 Swift 中
break
不是必須的晶乔,但你依然可以在 case 分支中的代碼執(zhí)行完畢前使用break
跳出,詳情請參見 Switch 語句中的 break牺勾。
每一個 case 分支都必須包含至少一條語句正罢。像下面這樣書寫代碼是無效的,因?yàn)榈谝粋€ case 分支是空的:
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // 無效驻民,這個分支下面沒有語句
case "A":
print("The letter A")
default:
print("Not the letter A")
}
// 這段代碼會報(bào)編譯錯誤
不像 C 語言里的 switch
語句翻具,在 Swift 中,switch
語句不會一起匹配 "a"
和 "A"
回还。相反的裆泳,上面的代碼會引起編譯期錯誤:case "a": 不包含任何可執(zhí)行語句
——這就避免了意外地從一個 case 分支貫穿到另外一個,使得代碼更安全柠硕、也更直觀工禾。
為了讓單個 case 同時匹配 a
和 A
,可以將這個兩個值組合成一個復(fù)合匹配蝗柔,并且用逗號分開:
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
print("The letter A")
default:
print("Not the letter A")
}
// 輸出“The letter A”
為了可讀性闻葵,符合匹配可以寫成多行形式,詳情請參考稍后講解的復(fù)合匹配癣丧。
注意
如果想要顯式貫穿 case 分支槽畔,請使用
fallthrough
語句,詳情請參考 貫穿胁编。
區(qū)間匹配
case 分支的模式也可以是一個值的區(qū)間厢钧。下面的例子展示了如何使用區(qū)間匹配來輸出任意數(shù)字對應(yīng)的自然語言格式:
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.”
在上例中,approximateCount
在一個 switch
聲明中被評估嬉橙。每一個 case
都與之進(jìn)行比較早直。因?yàn)?approximateCount
落在了 12 到 100 的區(qū)間,所以 naturalCount
等于 "dozens of"
值市框,并且此后的執(zhí)行跳出了 switch
語句霞扬。
元組
我們可以使用元組在同一個 switch
語句中測試多個值。元組中的元素可以是值,也可以是區(qū)間祥得。另外兔沃,使用下劃線(_
)來匹配所有可能的值。
下面的例子展示了如何使用一個 (Int, Int)
類型的元組來分類下圖中的點(diǎn) (x, y):
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("\(somePoint) is at the origin")
case (_, 0):
print("\(somePoint) is on the x-axis")
case (0, _):
print("\(somePoint) is on 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”
在上面的例子中级及,switch
語句會判斷某個點(diǎn)是否是原點(diǎn) (0, 0)乒疏,是否在紅色的 x 軸上,是否在橘黃色的 y 軸上饮焦,是否在一個以原點(diǎn)為中心的4x4的藍(lán)色矩形里怕吴,或者在這個矩形外面。
不像 C 語言县踢,Swift 允許多個 case 匹配同一個值转绷。實(shí)際上,在這個例子中硼啤,點(diǎn) (0, 0)可以匹配所有四個 case议经。但是,如果存在多個匹配谴返,那么只會執(zhí)行第一個被匹配到的 case 分支煞肾。考慮點(diǎn) (0, 0)會首先匹配 case (0, 0)
嗓袱,因此剩下的能夠匹配的分支都會被忽視掉籍救。
值綁定(Value Bindings)
case 分支允許將匹配的值聲明為臨時常量或變量,并且在 case 分支體內(nèi)使用 —— 這種行為被稱為值綁定(value binding)渠抹,因?yàn)槠ヅ涞闹翟?case 分支體內(nèi)蝙昙,與臨時的常量或變量綁定。
下面的例子將下圖中的點(diǎn) (x, y)梧却,使用 (Int, Int)
類型的元組表示奇颠,然后分類表示:
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
語句會判斷某個點(diǎn)是否在紅色的 x 軸上篮幢,是否在橘黃色的 y 軸上大刊,或者不在坐標(biāo)軸上为迈。
這三個 case 都聲明了常量 x
和 y
的占位符三椿,用于臨時獲取元組 anotherPoint
的一個或兩個值。第一個 case ——case (let x, 0)
將匹配一個縱坐標(biāo)為 0
的點(diǎn)葫辐,并把這個點(diǎn)的橫坐標(biāo)賦給臨時的常量 x
搜锰。類似的,第二個 case ——case (0, let y)
將匹配一個橫坐標(biāo)為 0
的點(diǎn)耿战,并把這個點(diǎn)的縱坐標(biāo)賦給臨時的常量 y
蛋叼。
一旦聲明了這些臨時的常量,它們就可以在其對應(yīng)的 case 分支里使用。在這個例子中狈涮,它們用于打印給定點(diǎn)的類型狐胎。
請注意,這個 switch
語句不包含默認(rèn)分支歌馍。這是因?yàn)樽詈笠粋€ case ——case let(x, y)
聲明了一個可以匹配余下所有值的元組握巢。這使得 switch
語句已經(jīng)完備了,因此不需要再書寫默認(rèn)分支松却。
Where
case 分支的模式可以使用 where
語句來判斷額外的條件暴浦。
下面的例子把下圖中的點(diǎn) (x, y)進(jìn)行了分類:
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
語句會判斷某個點(diǎn)是否在綠色的對角線 x == y
上晓锻,是否在紫色的對角線 x == -y
上歌焦,或者不在對角線上。
這三個 case 都聲明了常量 x
和 y
的占位符砚哆,用于臨時獲取元組 yetAnotherPoint
的兩個值独撇。這兩個常量被用作 where
語句的一部分,從而創(chuàng)建一個動態(tài)的過濾器(filter)躁锁。當(dāng)且僅當(dāng) where
語句的條件為 true
時券勺,匹配到的 case 分支才會被執(zhí)行。
就像是值綁定中的例子灿里,由于最后一個 case 分支匹配了余下所有可能的值关炼,switch
語句就已經(jīng)完備了,因此不需要再書寫默認(rèn)分支匣吊。
復(fù)合型 Cases
當(dāng)多個條件可以使用同一種方法來處理時儒拂,可以將這幾種可能放在同一個 case
后面,并且用逗號隔開色鸳。當(dāng) 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”
這個 switch
語句中的第一個 case,匹配了英語中的五個小寫元音字母吏砂。相似的撵儿,第二個 case 匹配了英語中所有的小寫輔音字母。最終狐血,default
分支匹配了其它所有字符淀歇。
復(fù)合匹配同樣可以包含值綁定。復(fù)合匹配里所有的匹配模式匈织,都必須包含相同的值綁定浪默。并且每一個綁定都必須獲取到相同類型的值牡直。這保證了,無論復(fù)合匹配中的哪個模式發(fā)生了匹配纳决,分支體內(nèi)的代碼碰逸,都能獲取到綁定的值,并且綁定的值都有一樣的類型阔加。
let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
print("On an axis, \(distance) from the origin")
default:
print("Not on an axis")
}
// 輸出“On an axis, 9 from the origin”
上面的 case 有兩個模式:(let distance, 0)
匹配了在 x 軸上的值花竞,(0, let distance)
匹配了在 y 軸上的值。兩個模式都綁定了 distance
掸哑,并且 distance
在兩種模式下约急,都是整型——這意味著分支體內(nèi)的代碼,只要 case 匹配苗分,都可以獲取到 distance
值厌蔽。
控制轉(zhuǎn)移語句
控制轉(zhuǎn)移語句改變你代碼的執(zhí)行順序,通過它可以實(shí)現(xiàn)代碼的跳轉(zhuǎn)摔癣。Swift 有五種控制轉(zhuǎn)移語句:
continue
break
fallthrough
return
throw
我們將會在下面討論 continue
奴饮、break
和 fallthrough
語句。return
語句將會在 函數(shù) 章節(jié)討論择浊,throw
語句會在 錯誤拋出 章節(jié)討論戴卜。
Continue
continue
語句告訴一個循環(huán)體立刻停止本次循環(huán),重新開始下次循環(huán)琢岩。就好像在說“本次循環(huán)我已經(jīng)執(zhí)行完了”投剥,但是并不會離開整個循環(huán)體。
下面的例子把一個小寫字符串中的元音字母和空格字符移除担孔,生成了一個含義模糊的短句:
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”
在上面的代碼中江锨,只要匹配到元音字母或者空格字符,就調(diào)用 continue
語句糕篇,使本次循環(huán)結(jié)束啄育,重新開始下次循環(huán)。這種行為使 switch
匹配到元音字母和空格字符時不做處理拌消,而不是讓每一個匹配到的字符都被打印挑豌。
Break
break
語句會立刻結(jié)束整個控制流的執(zhí)行。break
可以在 switch
或循環(huán)語句中使用墩崩,用來提前結(jié)束 switch
或循環(huán)語句氓英。
循環(huán)語句中的 break
當(dāng)在一個循環(huán)體中使用 break
時,會立刻中斷該循環(huán)體的執(zhí)行泰鸡,然后跳轉(zhuǎn)到表示循環(huán)體結(jié)束的大括號(}
)后的第一行代碼债蓝。不會再有本次循環(huán)的代碼被執(zhí)行壳鹤,也不會再有下次的循環(huán)產(chǎn)生盛龄。
Switch 語句中的 break
當(dāng)在一個 switch
代碼塊中使用 break
時,會立即中斷該 switch
代碼塊的執(zhí)行,并且跳轉(zhuǎn)到表示 switch
代碼塊結(jié)束的大括號(}
)后的第一行代碼余舶。
這種特性可以被用來匹配或者忽略一個或多個分支啊鸭。因?yàn)?Swift 的 switch
需要包含所有的分支而且不允許有為空的分支,有時為了使你的意圖更明顯匿值,需要特意匹配或者忽略某個分支赠制。那么當(dāng)你想忽略某個分支時,可以在該分支內(nèi)寫上 break
語句挟憔。當(dāng)那個分支被匹配到時钟些,分支內(nèi)的 break
語句立即結(jié)束 switch
代碼塊。
注意
當(dāng)一個
switch
分支僅僅包含注釋時绊谭,會被報(bào)編譯時錯誤政恍。注釋不是代碼語句而且也不能讓switch
分支達(dá)到被忽略的效果。你應(yīng)該使用break
來忽略某個分支达传。
下面的例子通過 switch
來判斷一個 Character
值是否代表下面四種語言之一篙耗。為了簡潔,多個值被包含在了同一個分支情況中宪赶。
let numberSymbol: Character = "三" // 簡體中文里的數(shù)字 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
設(shè)置一個整數(shù)值。
當(dāng) switch
代碼塊執(zhí)行完后欲主,接下來的代碼通過使用可選綁定來判斷 possibleIntegerValue
是否曾經(jīng)被設(shè)置過值追他。因?yàn)槭强蛇x類型的緣故,possibleIntegerValue
有一個隱式的初始值 nil
岛蚤,所以僅僅當(dāng) possibleIntegerValue
曾被 switch
代碼塊的前四個分支中的某個設(shè)置過一個值時邑狸,可選的綁定才會被判定為成功。
在上面的例子中涤妒,想要把 Character
所有的的可能性都枚舉出來是不現(xiàn)實(shí)的单雾,所以使用 default
分支來包含所有上面沒有匹配到字符的情況。由于這個 default
分支不需要執(zhí)行任何動作她紫,所以它只寫了一條 break
語句硅堆。一旦落入到 default
分支中后,break
語句就完成了該分支的所有代碼操作贿讹,代碼繼續(xù)向下渐逃,開始執(zhí)行 if let
語句。
貫穿(Fallthrough)
在 Swift 里民褂,switch
語句不會從上一個 case 分支跳轉(zhuǎn)到下一個 case 分支中茄菊。相反疯潭,只要第一個匹配到的 case 分支完成了它需要執(zhí)行的語句,整個 switch
代碼塊完成了它的執(zhí)行面殖。相比之下竖哩,C 語言要求你顯式地插入 break
語句到每個 case 分支的末尾來阻止自動落入到下一個 case 分支中。Swift 的這種避免默認(rèn)落入到下一個分支中的特性意味著它的 switch
功能要比 C 語言的更加清晰和可預(yù)測脊僚,可以避免無意識地執(zhí)行多個 case 分支從而引發(fā)的錯誤相叁。
如果你確實(shí)需要 C 風(fēng)格的貫穿的特性,你可以在每個需要該特性的 case 分支中使用 fallthrough
關(guān)鍵字辽幌。下面的例子使用 fallthrough
來創(chuàng)建一個數(shù)字的描述語句增淹。
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
并且給它設(shè)置了一個初始值。函數(shù)使用 switch
邏輯來判斷 integerToDescribe
變量的值乌企。當(dāng) integerToDescribe
的值屬于列表中的質(zhì)數(shù)之一時埠通,該函數(shù)在 description
后添加一段文字,來表明這個數(shù)字是一個質(zhì)數(shù)逛犹。然后它使用 fallthrough
關(guān)鍵字來“貫穿”到 default
分支中端辱。default
分支在 description
的最后添加一段額外的文字,至此 switch
代碼塊執(zhí)行完了虽画。
如果 integerToDescribe
的值不屬于列表中的任何質(zhì)數(shù)舞蔽,那么它不會匹配到第一個 switch
分支。而這里沒有其他特別的分支情況码撰,所以 integerToDescribe
匹配到 default
分支中渗柿。
當(dāng) switch
代碼塊執(zhí)行完后,使用 print(_:separator:terminator:)
函數(shù)打印該數(shù)字的描述脖岛。在這個例子中朵栖,數(shù)字 5
被準(zhǔn)確的識別為了一個質(zhì)數(shù)。
注意
fallthrough
關(guān)鍵字不會檢查它下一個將會落入執(zhí)行的 case 中的匹配條件柴梆。fallthrough
簡單地使代碼繼續(xù)連接到下一個 case 中的代碼陨溅,這和 C 語言標(biāo)準(zhǔn)中的switch
語句特性是一樣的。
帶標(biāo)簽的語句
在 Swift 中绍在,你可以在循環(huán)體和條件語句中嵌套循環(huán)體和條件語句來創(chuàng)造復(fù)雜的控制流結(jié)構(gòu)门扇。并且,循環(huán)體和條件語句都可以使用 break
語句來提前結(jié)束整個代碼塊偿渡。因此臼寄,顯式地指明 break
語句想要終止的是哪個循環(huán)體或者條件語句,會很有用溜宽。類似地吉拳,如果你有許多嵌套的循環(huán)體,顯式指明 continue
語句想要影響哪一個循環(huán)體也會非常有用适揉。
為了實(shí)現(xiàn)這個目的留攒,你可以使用標(biāo)簽(statement label)來標(biāo)記一個循環(huán)體或者條件語句煤惩,對于一個條件語句,你可以使用 break
加標(biāo)簽的方式稼跳,來結(jié)束這個被標(biāo)記的語句盟庞。對于一個循環(huán)語句吃沪,你可以使用 break
或者 continue
加標(biāo)簽汤善,來結(jié)束或者繼續(xù)這條被標(biāo)記語句的執(zhí)行。
聲明一個帶標(biāo)簽的語句是通過在該語句的關(guān)鍵詞的同一行前面放置一個標(biāo)簽票彪,作為這個語句的前導(dǎo)關(guān)鍵字(introducer keyword)红淡,并且該標(biāo)簽后面跟隨一個冒號。下面是一個針對 while
循環(huán)體的標(biāo)簽語法降铸,同樣的規(guī)則適用于所有的循環(huán)體和條件語句在旱。
label name: while condition {
statements
}
下面的例子是前面章節(jié)中蛇和梯子的適配版本,在此版本中推掸,我們將使用一個帶有標(biāo)簽的 while
循環(huán)體中調(diào)用 break
和 continue
語句桶蝎。這次,游戲增加了一條額外的規(guī)則:
- 為了獲勝谅畅,你必須剛好落在第 25 個方塊中登渣。
如果某次擲骰子使你的移動超出第 25 個方塊,你必須重新擲骰子毡泻,直到你擲出的骰子數(shù)剛好使你能落在第 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
循環(huán)和 switch
語句來實(shí)現(xiàn)游戲的邏輯呻顽。while
循環(huán)有一個標(biāo)簽名 gameLoop
,來表明它是游戲的主循環(huán)丹墨。
該 while
循環(huán)體的條件判斷語句是 while square != finalSquare
廊遍,這表明你必須剛好落在方格25中。
gameLoop: while square != finalSquare {
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
switch square + diceRoll {
case finalSquare:
// 骰子數(shù)剛好使玩家移動到最終的方格里贩挣,游戲結(jié)束昧碉。
break gameLoop
case let newSquare where newSquare > finalSquare:
// 骰子數(shù)將會使玩家的移動超出最后的方格,那么這種移動是不合法的揽惹,玩家需要重新擲骰子
continue gameLoop
default:
// 合法移動被饿,做正常的處理
square += diceRoll
square += board[square]
}
}
print("Game over!")
每次循環(huán)迭代開始時擲骰子。與之前玩家擲完骰子就立即移動不同搪搏,這里使用了 switch
語句來考慮每次移動可能產(chǎn)生的結(jié)果狭握,從而決定玩家本次是否能夠移動。
如果骰子數(shù)剛好使玩家移動到最終的方格里疯溺,游戲結(jié)束论颅。
break gameLoop
語句跳轉(zhuǎn)控制去執(zhí)行while
循環(huán)體后的第一行代碼哎垦,意味著游戲結(jié)束。如果骰子數(shù)將會使玩家的移動超出最后的方格恃疯,那么這種移動是不合法的漏设,玩家需要重新擲骰子。
continue gameLoop
語句結(jié)束本次while
循環(huán)今妄,開始下一次循環(huán)郑口。在剩余的所有情況中,骰子數(shù)產(chǎn)生的都是合法的移動盾鳞。玩家向前移動
diceRoll
個方格犬性,然后游戲邏輯再處理玩家當(dāng)前是否處于蛇頭或者梯子的底部。接著本次循環(huán)結(jié)束腾仅,控制跳轉(zhuǎn)到while
循環(huán)體的條件判斷語句處乒裆,再決定是否需要繼續(xù)執(zhí)行下次循環(huán)。
注意
如果上述的
break
語句沒有使用gameLoop
標(biāo)簽推励,那么它將會中斷switch
語句而不是while
循環(huán)鹤耍。使用gameLoop
標(biāo)簽清晰的表明了break
想要中斷的是哪個代碼塊。同時請注意验辞,當(dāng)調(diào)用
continue gameLoop
去跳轉(zhuǎn)到下一次循環(huán)迭代時稿黄,這里使用gameLoop
標(biāo)簽并不是嚴(yán)格必須的。因?yàn)樵谶@個游戲中受神,只有一個循環(huán)體抛猖,所以continue
語句會影響到哪個循環(huán)體是沒有歧義的。然而鼻听,continue
語句使用gameLoop
標(biāo)簽也是沒有危害的财著。這樣做符合標(biāo)簽的使用規(guī)則,同時參照旁邊的break gameLoop
撑碴,能夠使游戲的邏輯更加清晰和易于理解撑教。
提前退出
像 if
語句一樣,guard
的執(zhí)行取決于一個表達(dá)式的布爾值醉拓。我們可以使用 guard
語句來要求條件必須為真時伟姐,以執(zhí)行 guard
語句后的代碼。不同于 if
語句亿卤,一個 guard
語句總是有一個 else
從句愤兵,如果條件不為真則執(zhí)行 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": "Jane", "location": "Cupertino"])
// 輸出“Hello Jane!”
// 輸出“I hope the weather is nice in Cupertino.”
如果 guard
語句的條件被滿足排吴,則繼續(xù)執(zhí)行 guard
語句大括號后的代碼秆乳。將變量或者常量的可選綁定作為 guard
語句的條件,都可以保護(hù) guard
語句后面的代碼。
如果條件不被滿足屹堰,在 else
分支上的代碼就會被執(zhí)行肛冶。這個分支必須轉(zhuǎn)移控制以退出 guard
語句出現(xiàn)的代碼段。它可以用控制轉(zhuǎn)移語句如 return
扯键、break
睦袖、continue
或者 throw
做這件事,或者調(diào)用一個不返回的方法或函數(shù)荣刑,例如 fatalError()
馅笙。
相比于可以實(shí)現(xiàn)同樣功能的 if
語句,按需使用 guard
語句會提升我們代碼的可讀性嘶摊。它可以使你的代碼連貫的被執(zhí)行而不需要將它包在 else
塊中延蟹,它可以使你在緊鄰條件判斷的地方评矩,處理違規(guī)的情況叶堆。
檢測 API 可用性
Swift 內(nèi)置支持檢查 API 可用性,這可以確保我們不會在當(dāng)前部署機(jī)器上斥杜,不小心地使用了不可用的 API虱颗。
編譯器使用 SDK 中的可用信息來驗(yàn)證我們的代碼中使用的所有 API 在項(xiàng)目指定的部署目標(biāo)上是否可用。如果我們嘗試使用一個不可用的 API蔗喂,Swift 會在編譯時報(bào)錯。
我們在 if
或 guard
語句中使用 可用性條件(availability condition)
去有條件的執(zhí)行一段代碼,來在運(yùn)行時判斷調(diào)用的 API 是否可用舔稀。編譯器使用從可用性條件語句中獲取的信息去驗(yàn)證露泊,在這個代碼塊中調(diào)用的 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 及更高版本才運(yùn)行宣赔。最后一個參數(shù),*
瞪浸,是必須的儒将,用于指定在所有其它平臺中,如果版本號高于你的設(shè)備指定的最低版本对蒲,if 語句的代碼塊將會運(yùn)行钩蚊。
在它一般的形式中,可用性條件使用了一個平臺名字和版本的列表蹈矮。平臺名字可以是 iOS
砰逻,macOS
,watchOS
和 tvOS
——請?jiān)L問 特性 來獲取完整列表泛鸟。除了指定像 iOS 8 或 macOS 10.10 的大版本號蝠咆,也可以指定像 iOS 11.2.6 以及 macOS 10.13.3 的小版本號。
if #available(平臺名稱 版本號, ..., *) {
APIs 可用谈况,語句將執(zhí)行
} else {
APIs 不可用勺美,語句將不執(zhí)行
}
繼續(xù)閱讀 Swift - 函數(shù)