iOS精品文章

Swift学习笔记--为代码的执行做个决定

2017-08-18  本文已影响82人  Jesmine阳

为代码的执行做个决定

[TOC]

和其他的编程语言一样,为了能够控制程序的执行路径,Swift提供了我们熟悉的循环和分之判断语句。首先我们先快速的过一遍他们的基本用法。

条件分支语句

第一个要介绍的,是if...else if...else...。这是几乎每种语言都支持的分支表达方式,其中else ifelse都是可选的部分,它们可以单独和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 xlet 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的值。我们也可以这样来直接绑定westassociated 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)

这样,我们就可以在绑定.normalassociated 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语句中,batteryfullyChargesoutOfPower我们使用逗号分隔,表示逻辑或的概念,逗号也可以用在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>。在它的实现里,我们只要调用ClosedRangecontains方法,就可以实现“是否包含某个值”这样的语义了。

而当我们重载了这个~=方法之后,之前发生编译错误的语句就可以正常工作了。我们也可以通过类似的方法,为自定义类型添加各种样式匹配规则。

上一篇下一篇

猜你喜欢

热点阅读