為代碼的執(zhí)行做個決定
[TOC]
和其他的編程語言一樣鹿寻,為了能夠控制程序的執(zhí)行路徑宏粤,Swift提供了我們熟悉的循環(huán)和分之判斷語句荔燎。首先我們先快速的過一遍他們的基本用法堵漱。
條件分支語句
第一個要介紹的,是if...else if...else...
莱找。這是幾乎每種語言都支持的分支表達方式酬姆,其中else if
和else
都是可選的部分嗜桌,它們可以單獨和if搭配形式各種分支條件的判斷奥溺。基本上骨宠,看到代碼浮定,我們就可以直接了解這類判斷的含義了。
var light = "red"
var action = ""
if light == "red" {
action = "stop"
}
else if light == "yellow" {
action = "caution"
}
else if light == "green" {
action = "go"
}
else {
action = "invalid"
}
在上面這個紅綠燈的代碼里层亿,我們不斷根據(jù)light的值桦卒,設置了變量action
的值,它很簡單匿又。但通常方灾,我們還是更多會使用if...else...
表示非黑即白這樣的簡單關系。
對于上面這種存在多種可能性的情況碌更,在Swift里裕偿,我們通常還是會使用switch...case...
來表示,它比if...else...
更安全痛单,也更有更好的表意:
switch light {
case "red":
action = "stop"
case "yellow":
action = "caution"
case "green":
action = "go"
default:
action = "invalid"
}
這里嘿棘,我們使用switch...case...
表達了和之前的if...else...
相同的語義。但是旭绒,它更明確的表達了當light
的值(switch
)為各種情況(case
)時鸟妙,我們應該采取哪些措施,這樣的概念挥吵。
但和C++/Java這樣語言相比重父,Swift中的switch...case...
也有一些自己獨特的地方:
首先,case
語句必須exhausitive
忽匈,也就是說坪郭,必須覆蓋switch
后面出現(xiàn)的表達式的所有情況,否則會導致編譯錯誤.
當你不需要對列出case
的其他情況作出處理時脉幢,你也要在default
分支寫上一句break
歪沃,明確表示你考慮到了其他的情況嗦锐,只是你不需要更多額外處理而已。
其次沪曙,每個case
語句不會自動“貫通”到下一個case
奕污,因此我們也無需在每個case最后一行寫break
表示結束;
最后液走,當我們要在一個case
里匹配多個條件的時候碳默,可以使用逗號把多個條件分開,以上,就是和分支條件相關的兩個最基本的場景和用法缘眶,接下來嘱根,我們了解循環(huán)。
循環(huán)控制語句
第一個要介紹的巷懈,是for element in collection/range
该抒,我們可以用它來方便的遍歷一個集合類型或者范圍:
let vowel = ["a", "e", "i", "o", "u"]
for char in vowel {
print(char)
}
// aeiou
for number in 1...10 {
print(number)
}
// 12345678910
第二個循環(huán)的方式是while
,它有前置判斷和后置判斷兩種形式顶燕,基本上保留了原汁原味的C用法:
// while
var i = 0
while i < 10 {
print(i)
i += 1
}
// do ... while
repeat {
print(i)
i -= 1
} while i > 0
在這兩類循環(huán)里凑保,我們都可以用continue
來停止執(zhí)行當前循環(huán)中的語句,立即開始下一次循環(huán)涌攻。例如欧引,打印所有的偶數(shù):
for number in 1...10 {
if number % 2 != 0 { continue }
print(number)
}
// 2 4 6 8 10
在這個例子里,如果number
是奇數(shù)恳谎,就會執(zhí)行到continue
芝此,當前循環(huán)就停止并自動進入下一次循環(huán)了。
或者因痛,我們也可以使用break
來終止整個循環(huán)婚苹。例如,值大于8時婚肆,就終止循環(huán):
for number in 1...10 {
if number > 8 { break }
print(number)
}
// 1 2 3 4 5 6 7 8
使用簡單的樣式匹配
現(xiàn)實環(huán)境中租副,我們需要的判斷條件可能比那些演示的例子復雜的多。為此较性,Swift從函數(shù)式編程中借鑒了一些樣式匹配的方式用僧,幫助我們構建表意豐富又易于維護的代碼。
匹配值的方式
為了演示各種樣式匹配的方式赞咙,我們先定義一個tuple责循,表示平面直角坐標系中的原點:
let origin = (x: 0, y: 0)
當我們要判斷某個點是否是原點的時候,最原始的方式攀操,是這樣的:
let pt1 = (x: 0, y: 0)
if pt1.x == 0 && pt1.y == 0 {
print("@Origin")
}
當然院仿,這樣判斷xy坐標是否相等并不能讓人滿意,寫起來非常麻煩。實際上歹垫,我們還可以這樣:
if case (0, 0) = pt1 {
print("@Origin")
}
我們可以用case 匹配的值 = 要檢查的對象
的方式剥汤,對要檢查的對象進行判斷。在我們的例子里排惨,判斷的就是pt1是否等于原點吭敢。
除了用在if中匹配值,我們當然也可以在switch的case分支里暮芭,匹配特定形式的值:
switch pt1 {
case (0, 0):
print("@Origin")
case (_, 0):
print("on x axis")
case (0, _):
print("on y axis")
case (-1...1, -1...1):
print("inside 2x2 square")
default:
break;
}
在上面這個例子里鹿驼,除了用case (0, 0)
表示匹配原點值之外,還可以用(_, 0)
和(0, _)
表示忽略掉_
的部分辕宏,僅對tuple中某一部分的值進行匹配畜晰,或者,在tuple的每一個成員位置瑞筐,使用range operator匹配值的某個范圍凄鼻。
除了把case
用于條件分支語句,我們還可以用于循環(huán)語句面哼,用于進一步控制循環(huán)條件野宜,例如:
let array1 = [1,1,2,2,2]
for case 2 in array1 {
print("found two")
}
在上面這個例子里扫步,當遇到數(shù)組中值為2的元素時魔策,我們向控制臺打印了一行話,因此河胎,print
一共會打印3次闯袒。
把匹配的內容綁定(value binding)到變量
除了在 case
中使用各種形式的具體值之外,我們還可以把匹配到的內容直接綁定到變量上游岳,這樣我們就可以再相應的處理代碼中直接使用它們政敢,例如:
switch pt1 {
case (let x, 0):
print("(\(x),0) is on x axis")
case (0, let y):
print("(0,\(y)) is on y axis")
default:
break;
}
在上面這個例子里,我們把之前_的部分換成了let x
和let y
胚迫,這樣喷户,同樣是匹配在坐標軸上的點,這次访锻,我們就可以在對應的case
中褪尝,直接訪問匹配到的值了。我們管這樣的形式期犬,叫做value binding
河哑。
除了直接綁定變量自身的值之外,我們還可以用類似的形式綁定enum
中的關聯(lián)值龟虎。例如璃谨,我們先定義一個表示方向的enum
enum Direction {
case north, south, east, west(abbr: String)
}
let west = Direction.west(abbr: "W")
為了演示,我們給.west
添加個了一個associated value
,表示方向的縮寫佳吞。然后拱雏,我們既可以像這樣來判斷enum
值自身:
if case .west = west {
print(west) // west("W")
}
此時,print打印的就是enum case
的值底扳。我們也可以這樣來直接綁定west
的associated value
:
if case .west(let direction) = west {
print(direction) //W
}
此時古涧,print打印出來的值,就直接是字符“W”
了花盐。當然羡滑,case
這樣的用法,在switch
的分支中算芯,也是完全可以的柒昏。
自動提取optional的值
除了綁定enum
的associated value之外,我們還可以使用case
來自動提取optional類型的非空值:
let skill: [String?] = ["Swift", nil,"PHP","JavaScript",nil]
for case let skill? in skills {
print(skill) // Swift PHP JavaScript
}
在我們的例子里熙揍,skills包含了5個元素职祷,其中兩個是nil
,當我們用case let skill?
這樣的形式來綁定optional值的時候届囚,Swift就會自動提取每一個非nil
的元素有梆,因此,print
會輸出“Swift PHP JavaScript”意系。
自動綁定類型轉換的結果
最后一類基本的樣式匹配規(guī)則是自動綁定類型轉換的結果泥耀。首先,我們創(chuàng)建一個[Any]
:
let someValue: [Any] = [1, 1.0, "One"]
當我們遍歷someValues
,并且要根據(jù)不同類型的數(shù)組元素分別做一些操作的時候蛔添,可以這樣:
for value in someValues {
switch value {
case let v as Int:
print(Interger \(v))
case let v as Double:
print(Double \(v))
case let v as String:
print(String \(v))
default:
print("Invalid value")
}
}
// Integer 1
// Double 1.0
// String One
在上面的例子中痰催,我們使用了case let Variable as Type
的方式,把類型轉換成功的結果迎瞧,綁定在了變量V上夸溶。這樣,我們就可以在對應的case
里凶硅,訪問到轉換成功的值了缝裁。
或者,如果你僅僅想判斷類型足绅,而不需要知道具體內容的話捷绑,還可以使用更簡單的is
操作符:
for value in someValues {
switch value {
case is Int:
print("Integer")
// omit for simplicity...
}
使用高級樣式匹配方式
使用where約束條件
除了使用具體的數(shù)值對循環(huán)或分支條件進行約束外,我們還可以使用where
進行更復雜的約束编检。先來看一個簡單的例子:
for i in 1...10 where i % 2 == 0 {
print(i)
}
這里我們在for
循環(huán)里使用了where
限定了進入循環(huán)的值必須是偶數(shù)胎食。我們還可以把where
用在更復雜的value binding
語句里。例如允懂,我們假設定義下面的enum
表示手機電量
enum Power {
case fullyCharges
case normal(percentage: Double)
case outOfPower
}
然后在定義一個battery
表示手機電池
let battery = Power.normal(percentage: 0.1)
這樣厕怜,我們就可以在綁定.normal
associated value的同時,使用where
進一步約束它的關聯(lián)值:
switch battery {
case .normal(let precentage) where percentage <= 0.1":
print("almost out of power")
case .normal(let percentage) where percentage >= 0.8:
print("almost fully power")
case .fullyCharges, .outOfPower:
print("Fully charged or out of power")
default:
break
}
上面的case
語句中,battery
的fullyCharges
和outOfPower
我們使用逗號分隔粥航,表示邏輯或的概念琅捏,逗號也可以用在if
中表示邏輯與的概念,例如為了處理之前電量低的情況递雀,我們還可以用if
這樣來實現(xiàn):
if case .normal(let percentage) == battery, case 0...0.1 = percentage {
print("Almost out of power")
}
在上面的代碼里柄延,第一個if case
使用value binding讀取了battery中.normal
的associated value。接下來缀程,第二個case
進一步約束了第一個case
中關聯(lián)到的值小于10%的情況搜吧。
使用tuple簡化多個條件的比較
有時,我們需要在if中同時比較多個條件杨凑。假設滤奈,我們有一對用戶名和密碼:
let username = "11@boxue.io"
let password = 11111111
當我們要同時比較這兩個值時,最普通的寫法撩满,就是在if
中使用邏輯與(&&)操作符:
if username == "11@boxue.io" && password == 11111111 {
print("correct")
}
如果你不喜歡在if中并列多個比較語句蜒程,我們還可以用case
臨時生成一個tuple,并且伺帘,讓它和username / password
進行比較:
if case ("11@boxue.io", 11111111) = (username,password) {
print("correct")
}
理解樣式匹配的實現(xiàn)方式
首先昭躺,當要匹配的變量和樣式的類型相同,并且對應的類型實現(xiàn)了Equatable
protocol時伪嫁,就直接使用對應類型的==操作符進行匹配领炫。例如我們之前比較用戶名和密碼的例子:
let username = "11@boxue.io"
let password = 11111111
if case ("11@boxue.io", 11111111) = (username, password) {
print("correct")
}
這里,就直接使用了Tuple類型的比較操作符礼殊。
其次驹吮,如果樣式和要匹配的變量類型不同针史,或對應類型沒有實現(xiàn)Equaltable
protocol時晶伦,Swift會使用~=
操作符進行比較。當這個操作符返回true
時啄枕,就認為條件匹配婚陪,否則就認為不匹配。因此频祝,為了判斷某個數(shù)值是否在某個范圍里泌参,Swift標準庫中實現(xiàn)了Range ~= Value
這種形式的比較,但是常空,卻沒有實現(xiàn)Value ~= Range
這樣的版本沽一。
因此,當我們寫成case percentage = 0...0.1
時漓糙,編譯器就會因為找不到對應的操作符而報錯了铣缠。不過,由于這個操作符是可以自定義的,因此蝗蛙,我們可以通過重載它蝇庭,來實現(xiàn)上面的功能:
func ~=<T>(value: T, pattern: ClosedRange<T>) -> Bool {
return pattern.contains(value)
}
為了能匹配不同的類型,我們把~=
定義為了一個泛型函數(shù)捡硅。它的第一個參數(shù)表示~=
的左操作數(shù)哮内,按照我們的例子,應該是一個值壮韭;第二個參數(shù)表示~=
的右操作數(shù)北发,它是一個ClosedRange<T>。在它的實現(xiàn)里喷屋,我們只要調用ClosedRange
的contains
方法鲫竞,就可以實現(xiàn)“是否包含某個值”這樣的語義了。
而當我們重載了這個~=
方法之后逼蒙,之前發(fā)生編譯錯誤的語句就可以正常工作了从绘。我們也可以通過類似的方法,為自定義類型添加各種樣式匹配規(guī)則是牢。