SwiftNote-模式 & 模式匹配

347 阅读5分钟

模式

模式代表单个值或者复合值的结构。 例如,元组 (1, 2) 的机构是由逗号分割,包含两个元素的列表。因为模式代表的是一种值的结构而不是特定的某个值,你可以利用模式来匹配各种各样的值。比如 (x, y) 可以匹配元组 (1, 2) ,以及任何包含两个元素的元组。除了利用模式匹配一个值以外,你可以从复合值中提取出部分或者全部值,然后分别把各个部分的值和一个常量或者变量绑定起来。

模式匹配

Swift 中的模式分为两类:一种是成功匹配任何类型的值,另一种是在运行时匹配某个特定的值时可能会失败。

第一类模式用于解构简单变量、常量和可选绑定的值。此类模式包括通配符模式、标识模式,以及包含前两种模式的值绑定模式和元组模式。你可以为这类模式制定一个类型标注,从而限制它们只能匹配某种特定类型的值。

  • 通配符模式(Wildcard Pattern): 通配符模式有一个下划线 _ 构成,用于匹配并忽略任何值:

    for _ in 0...3 {
        // do something
    }
    
  • 标识符模式(Identifier Pattern) 标识符模式匹配任何值,并将匹配的值和一个变量或常量绑定起来

    let someValue = 42
    
  • 值绑定模式(Value-Binding Pattern) 值绑定模式把匹配到的值绑定给一个变量或常量。把匹配到的值绑定给常量时用 let,绑定给变量时用 var

    let point = (3, 2)
    switch point {
    // 将 point 中的元素绑定到 x 和 y
    case let (x, y):
        print("the point is at (\(x), \(y))")
    default:
        print("blablabla...")
    }
    
  • 元组模式(Tuple Pattern) 元组模式是由逗号分割的,具有零个或多个模式的列表,并由一对圆括号括起来。元组模式匹配相应元组类型的值。

    for (x, y) in points where y == 0 {
        print("point is (\(x), \(y))")
    }
    // 控制台输出结果:
    // point is (0, 0)
    // point is (1, 0)
    // point is (2, 0)
    

第二类模式用于全模式匹配,这种情况你试图匹配的值在运行时可能不存在。此类模式包含枚举用例模式、可选模式、表达式模式和类型转换模式。你在 switch 语句的 case 标签中, do 语句的 catch 子句中,或者在 if、while、guard 和 for-in 语句的 case 条件句中使用这类模式。

  • 枚举用例模式(Enumeration Pattern) 枚举用例模式匹配现有的某个枚举用例的某个用例。枚举用例模式出现在 switch 语句中的 case 标签中, 以及 if、while、guard 和 for-in 语句的 case 条件中。

  • 可选项模式(Optional Pattern) 可选项模式匹配 Optional 枚举在 some(Wrapped) 中包装的值。 可选项模式为 for-in 语句提供了一种迭代数组的简便方式,只为数组中非 nil 的元素执行循环体。

    let someOptional: Int? = 42
    // 枚举模式匹配(match using an enumeration case pattern)
    if case .some(let x) = someOptional {
        print(x)
    }
    // 控制台输出结果:42 
    
    // 可选项匹配(match using an optional pattern)
    if case let x? = someOptional {
        print(x)
    }
    // 控制台输出结果:42 
    
    let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 4]
    // 非空值匹配(match only non-nil values)
    for case let number? in arrayOfOptionalInts {
        print("Found a \(number)")
    }
    // 控制台输出结果:
    // Found a 2
    // Found a 3
    // Found a 4
    
  • 类型转换模式(Type-Casting Pattern) 类型转换模式有两种:is 模式 和 as 模式。is 模式只出现在 switch 语句的 case 标签中。

    • is 模式仅当一个值的类型在运行时和 is 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。is 模式和 is 运算符有相似的表现,都进行类型转换,但是 is 模式没有返回类型。
    • as 模式仅当一个值的类型在运行时和 as 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成 as 模式右边指定的类型。
    protocol Animal {
        var name: String { get }
    }
    
    struct Dog: Animal {
        var name: String {
            return "dog"
        }
        var runSpeed: Int
    }
    
    struct Bird: Animal {
        var name: String {
            return "bird"
        }
        var flightHeight: Int
    }
    
    struct Fish: Animal {
        var name: String {
            return "fish"
        }
        var depth: Int
    }
    let animals: [Any] = [Dog(runSpeed: 55),
                          Bird(flightHeight: 2000), 
                          Fish(depth: 100)]
    for animal in animals {
        switch animal {
        case let dog as Dog:
            print("\(dog.name) can run \(dog.runSpeed)")
        case let fish as Fish:
            print("\(fish.name) can dive depth \(fish.depth)")
        case is Bird:
            print("bird can fly!")
        default:
            print("unknown animal!")
        }
    }
    // 控制台输出结果:
    dog can run 55
    bird can fly!
    fish can dive depth 100
    
  • 表达式模式(Expression Pattern) 表达式模式代表表达式的值。表达式模式只出现在 switch 语句中的 case 标签中。 表达式模式代表的表达式会使用 Swift 标准库中的 ~= 运算符与输入表达式的值进行比较。如果 ~= 运算符返回 true 则匹配成功。默认情况下 ~= 运算符使用 == 运算符来比较两个想同类型的值。它也可以将一个整型数值与一个 Range 实例中的一段整数区间做匹配。

    let point = (1, 2)
    switch point {
    case (0, 0):
        print("(0, 0) is at origin.")
    case (-2...2, -2...2):
        print("(\(point.0), \(point.1)) is inside the box.")
    default:
        print("the point is at (\(point.0), \(point.1))")
    }
    // 控制台输出结果:
    // (1, 2) is inside the box.
    

自定义类型默认也是无法进行表达式模式匹配的,也需要重载 ~= 运算符。

struct Employee {
    var salary: Float
}
// 重载 ~= 运算符
// pattern: 匹配模式, 这里采用 Range 匹配
// value: 要进行匹配的数据, 这里是 Employee 结构体
func ~= (pattern: Range<Float>, value: Employee) -> Bool {
    return pattern.contains(value.salary)
}
let e = Employee(salary: 9999)
switch e {
case 0.0..<1000:
    print("吃不饱饭")
case 1000.0..<5000:
    print("小康社会")
case 5000.0..<10000:
    print("生活很滋润")
default:
    break
}
// 控制台输出结果:生活很滋润