Swif学习之模式及模式匹配

定义

  模式代表单个值或者复合值的结构。

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

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

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

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

Swift中的模式

  • 通配符模式(Wildcard Pattern)

    通配符模式由一个下划线(_)构成,用于匹配并忽略任何值。当你想忽略被匹配的值时可以使用该模式。

    let student = School.Student(name: "karl", age: 27, address: nil)
    let teacher = School.Teacher(name: "wang", age: 50, address: "AH")
    • _ :这个值完全不重要,可有可无,是什么我都不关心。
      switch student {
        case let .Teacher(name, _, _):
          print("name: \(name)")
        default: break
      }
      这里第二个参数和第三个参数就是使用了通配符表示,不关心它是什么值,只要前面参数能匹配成功就可以,就算成立。
    • _? :这个值我虽然不用,但是它必须有值。
      switch student {
        case let .Student(name, age, _?):
          print("name: \(name)")
        default: break
      }
      这里第三个参数我使用了_?,表示这个值我不用,但是不能为nil
  • 标识符模式(Identifier Pattern)

    标识符模式匹配任何值,并将匹配的值和一个变量或常量绑定起来。例如,在下面的常量声明中,someValue 是一个标识符模式,匹配了 Int 类型的 42

    let someValue = 42

    当匹配成功时,42 被绑定(赋值)给常量 someValue

  • 值绑定模式(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)).")
    }
    // 打印“The point is at (3, 2).”

    在上面这个例子中,let 会分配到元组模式 (x, y) 中的各个标识符模式。因此,switch 语句中 case let (x, y): 和 case (let x, let y): 的匹配效果是一样的。

  • 元组模式(Tuple Pattern)

    元组模式是由逗号分隔的,具有零个或多个模式的列表,并由一对圆括号括起来。元组模式匹配相应元组类型的值。

    你可以使用类型注解去限制一个元组模式能匹配哪种元组类型。例如,在常量声明 let (x, y): (Int, Int) = (1, 2) 中的元组模式 (x, y): (Int, Int) 只匹配两个元素都是 Int 类型的元组。

    当元组模式被用于 for-in 语句或者变量和常量声明时,它仅可以包含通配符模式、标识符模式、可选模式或者其他包含这些模式的元组模式。比如下面这段代码就不正确,因为 (x, 0) 中的元素 0 是一个表达式模式:

    let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)]
    // 下面的代码是错误的
    for (x, 0) in points {
    /* ... */
    }

    只包含一个元素的元组模式的圆括号没有效果,模式只匹配这个单个元素的类型。举例来说,下面的语句是等效的:

    let a = 2 // a: Int = 2
    let (a) = 2 // a: Int = 2
    let (a): Int = 2 // a: Int = 2
  • 枚举用例模式(Enumeration Case Pattern)

    枚举用例模式匹配现有的某个枚举类型的某个用例。枚举用例模式出现在 switch 语句中的 case 标签中,以及 ifwhileguard 和 for-in 语句的 case 条件中。

    如果你准备匹配的枚举用例有任何关联的值,则相应的枚举用例模式必须指定一个包含每个关联值元素的元组模式。关于使用 switch 语句来匹配包含关联值的枚举用例的例子。

    枚举用例模式同样会匹配那些被包装成可选值的用例。简化的语法能将可选模式过滤掉。注意,由于 Optional 是枚举实现的,.none 和 .some 都会作为枚举类型的用例出现在 switch 中。

    enum SomeEnum { case left, right }
    let x: SomeEnum? = .left
    switch x {
      case .left:
        print("Turn left")
      case .right:
        print("Turn right")
      case nil:
        print("Keep going straight")
    }
    // 打印 "Turn left"
  • 可选模式(Optional Pattern)

    可选模式匹配包装在一个 Optional(Wrapped) 或者 ExplicitlyUnwrappedOptional(Wrapped) 枚举中的 Some(Wrapped) 用例中的值。可选模式由一个标识符模式和紧随其后的一个问号组成,可以像枚举用例模式一样使用。

    由于可选模式是 Optional 和 ImplicitlyUnwrappedOptional 枚举用例模式的语法糖,下面两种写法是等效的:

    let someOptional: Int? = 42
    // 使用枚举用例模式匹配
    if case .Some(let x) = someOptional {
      print(x)
    }
     
    // 使用可选模式匹配
    if case let x? = someOptional {
      print(x)
    }

    可选模式为 for-in 语句提供了一种迭代数组的简便方式,只为数组中非 nil 的元素执行循环体。

    let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5]
    // 只匹配非 nil 的元素
    for case let number? in arrayOfOptinalInts {
      print("Found a \(number)")
    }
    // Found a 2
    // Found a 3
    // Found a 5
  • 类型转换模式(Type-Casting Patterns)

    有两种类型转换模式,is 模式和 as 模式。

    protocol Animal { }
    struct Dog: Animal { }
    struct Cat: Animal { }

    var animalArray = [Animal]()
    let dog = Dog()
    let cat = Cat()
    animalArray.append(dog)
    animalArray.append(cat)

    for item in animalArray {
      switch item {
      case is Dog:print("dog")
      case let cat as Cat:print("cat")
      default:break
      }
    }

    • is 模式仅当一个值的类型在运行时和 is 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。is 模式和 is 运算符有相似表现,它们都进行类型转换,但是 is 模式没有返回类型。

    • as 模式仅当一个值的类型在运行时和 as 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成 as 模式右边指定的类型。

  • 表达式模式(Expression Pattern)

    表达式模式代表表达式的值。表达式模式只出现在 switch 语句中的 case 标签中。

    表达式模式代表的表达式会使用 Swift 标准库中的 ~= 运算符与输入表达式的值进行比较。如果 ~= 运算符返回 true,则匹配成功。默认情况下,~= 运算符使用 == 运算符来比较两个相同类型的值。它也可以将一个整型数值与一个 Range 实例中的一段整数区间做匹配,正如下面这个例子所示:

    let point = (1, 2)
    switch point {
      case (0, 0):
        print("(0, 0) is at the origin.")
      case (-2...2, -2...2):
        print("(\(point.0)\(point.1)) is near the origin.")
      default:
        print("The point is at (\(point.0)\(point.1)).")
    }
    // 打印“(1, 2) is near the origin.”

    你可以重载 ~= 运算符来提供自定义的表达式匹配行为。比如你可以重写上面的例子,将 point 表达式与字符串形式表示的点进行比较。

    // 重载 ~= 运算符对字符串和整数进行比较
    func ~=(pattern: String, value: Int) -> Bool {
      return pattern == "\(value)"
    }
    switch point {
      case ("0", "0"):
        print("(0, 0) is at the origin.")
      default:
        print("The point is at (\(point.0)\(point.1)).")
    }
    // 打印“The point is at (1, 2).”

Swift中的模式匹配

  • 1、枚举的模式匹配

    在 Swift 中使用枚举的好处是,可以把一些服务器返回的基础类型的值封装成有意义的对象。

    enum TestLevel: Int {
      case low = 0
      case middle
      case high
    }

    一个普通的枚举类型是不可比较的,有原始值的枚举类型可以做比较,比较时会根据原始值进行比较。
    使用上面的简写会分别获得 1 和 2 的原始值。
    既然已经决定把服务器返回的状态位封装成一个枚举,那么在数据结构中就不要保留它的原始值,否则你可能不得不写出一个不太好的模式匹配版本。

    对枚举做模式匹配时,如果 switch 中 case 的值覆盖了枚举类型的所有情况时,不需要 default。
  • 2、元组的模式匹配

    相比于枚举,元组中包含多个数据元素,可以匹配整个元组,也可以匹配元组中的某个数据成员,这是因为 Swift 已经为元组重载了多个版本的 ~= 操作符。

    在 Swift 中元组是匿名的,不具备复用的特性,所以经常被当作临时变量保存数据。除了保存数据,元组还可以把一个复杂的 if else 结构封装成模式匹配的格式,使得代码逻辑更加清晰。

    通过元组的封装,把一个逻辑结构的问题转换成了一个数据结构的问题,模式匹配只有一层,判断的顺序一目了然,并且每一个 case 都需要列出 limit, userName, password 三者的具体情况,不会因为 if else 层次的加深而造成逻辑的缺失。

  • 3、if 和 guard 中的模式匹配

    • 除了前面的 switch 语句,Swift 2.0 中为更多的基础语法引入了模式匹配,比如 if 和 guard 语句。

      if case 可以接受的类型 = 发起模式匹配的对象 {
        // 模式匹配成功时执行的代码段
      }

      guard case 可以接受的类型 = 发起模式匹配的对象 else {
        // 模式匹配不成功时执行的代码段
        return
      }

    • 在判断条件中对基本数据类型使用模式匹配

    • 也可以在判断条件中对元组使用模式匹配过滤无用信息,结构看起来与 switch 中的 case 的格式类似。

  • 4、for 中的模式匹配

     

    • 在循环中引入模式匹配,则循环只会处理哪些匹配的对象。

      for case 可以接受的类型 in 发起模式匹配的对象集合 {
       // 模式匹配成功时执行的代码段
      }

    • 比如现在有三个用户,我们只对管理员权限的用户进行操作。

  • 5、模式匹配中的 where 关键字

     

    • 我们可以把嵌套的逻辑结构封装成元组,但是并不是所有的逻辑结构都适合封装成元组,这时你可以保留原始的模式匹配格式,然后使用 where 关键字在其上增加逻辑判断。

    • where 语句直接附在 case 语句之后,用来为模式匹配增加匹配条件,where 的优势是保持了模式匹配格式的整齐度,where 可以用于所有的模式匹配中。

    • 使用模式匹配的写法

posted on 2021-07-16 17:26  梁飞宇  阅读(92)  评论(0)    收藏  举报