Swift学习笔记--为代码的执行做个决定
为代码的执行做个决定
[TOC]
和其他的编程语言一样,为了能够控制程序的执行路径,Swift提供了我们熟悉的循环和分之判断语句。首先我们先快速的过一遍他们的基本用法。
条件分支语句
第一个要介绍的,是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"
}
在上面这个红绿灯的代码里,我们不断根据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
后面出现的表达式的所有情况,否则会导致编译错误.
当你不需要对列出case
的其他情况作出处理时,你也要在default
分支写上一句break
,明确表示你考虑到了其他的情况,只是你不需要更多额外处理而已。
其次,每个case
语句不会自动“贯通”到下一个case
,因此我们也无需在每个case最后一行写break
表示结束;
最后,当我们要在一个case
里匹配多个条件的时候,可以使用逗号把多个条件分开,以上,就是和分支条件相关的两个最基本的场景和用法,接下来,我们了解循环。
循环控制语句
第一个要介绍的,是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
第二个循环的方式是while
,它有前置判断和后置判断两种形式,基本上保留了原汁原味的C用法:
// while
var i = 0
while i < 10 {
print(i)
i += 1
}
// do ... while
repeat {
print(i)
i -= 1
} while i > 0
在这两类循环里,我们都可以用continue
来停止执行当前循环中的语句,立即开始下一次循环。例如,打印所有的偶数:
for number in 1...10 {
if number % 2 != 0 { continue }
print(number)
}
// 2 4 6 8 10
在这个例子里,如果number
是奇数,就会执行到continue
,当前循环就停止并自动进入下一次循环了。
或者,我们也可以使用break
来终止整个循环。例如,值大于8时,就终止循环:
for number in 1...10 {
if number > 8 { break }
print(number)
}
// 1 2 3 4 5 6 7 8
使用简单的样式匹配
现实环境中,我们需要的判断条件可能比那些演示的例子复杂的多。为此,Swift从函数式编程中借鉴了一些样式匹配的方式,帮助我们构建表意丰富又易于维护的代码。
匹配值的方式
为了演示各种样式匹配的方式,我们先定义一个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
用于条件分支语句,我们还可以用于循环语句,用于进一步控制循环条件,例如:
let array1 = [1,1,2,2,2]
for case 2 in array1 {
print("found two")
}
在上面这个例子里,当遇到数组中值为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
中的关联值。例如,我们先定义一个表示方向的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”。
自动绑定类型转换的结果
最后一类基本的样式匹配规则是自动绑定类型转换的结果。首先,我们创建一个[Any]
:
let someValue: [Any] = [1, 1.0, "One"]
当我们遍历someValues
,并且要根据不同类型的数组元素分别做一些操作的时候,可以这样:
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约束条件
除了使用具体的数值对循环或分支条件进行约束外,我们还可以使用where
进行更复杂的约束。先来看一个简单的例子:
for i in 1...10 where i % 2 == 0 {
print(i)
}
这里我们在for
循环里使用了where
限定了进入循环的值必须是偶数。我们还可以把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
进一步约束它的关联值:
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
这样来实现:
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
中关联到的值小于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")
}
理解样式匹配的实现方式
首先,当要匹配的变量和样式的类型相同,并且对应的类型实现了Equatable
protocol时,就直接使用对应类型的==操作符进行匹配。例如我们之前比较用户名和密码的例子:
let username = "11@boxue.io"
let password = 11111111
if case ("11@boxue.io", 11111111) = (username, password) {
print("correct")
}
这里,就直接使用了Tuple类型的比较操作符。
其次,如果样式和要匹配的变量类型不同,或对应类型没有实现Equaltable
protocol时,Swift会使用~=
操作符进行比较。当这个操作符返回true
时,就认为条件匹配,否则就认为不匹配。因此,为了判断某个数值是否在某个范围里,Swift标准库中实现了Range ~= Value
这种形式的比较,但是,却没有实现Value ~= Range
这样的版本。
因此,当我们写成case percentage = 0...0.1
时,编译器就会因为找不到对应的操作符而报错了。不过,由于这个操作符是可以自定义的,因此,我们可以通过重载它,来实现上面的功能:
func ~=<T>(value: T, pattern: ClosedRange<T>) -> Bool {
return pattern.contains(value)
}
为了能匹配不同的类型,我们把~=
定义为了一个泛型函数。它的第一个参数表示~=
的左操作数,按照我们的例子,应该是一个值;第二个参数表示~=
的右操作数,它是一个ClosedRange<T>。在它的实现里,我们只要调用ClosedRange
的contains
方法,就可以实现“是否包含某个值”这样的语义了。
而当我们重载了这个~=
方法之后,之前发生编译错误的语句就可以正常工作了。我们也可以通过类似的方法,为自定义类型添加各种样式匹配规则。