Swift系列二十六 - 模式匹配

一、字面量(Literal)

了解模式之前,我们先看下什么是字面量。

var age = 10
var isShow = false
var name = "Jack"

上面代码中的10false"Jack"就是字面量。

1.1. 字面量类型

常见字面量的默认类型:

public typealias IntegerLiteralType = Int
public typealias FloatLiteralType = Double
public typealias BooleanLiteralType = Bool
public typealias StringLiteralType = String

可以通过typealias修改字面量的默认类型(一般也没必要修改):

public typealias FloatLiteralType = Float
public typealias IntegerLiteralType = UInt8
var age = 10 // UInt8类型
var height = 20.0 // Float类型

Swift自带的绝大部分类型,都支持直接通过字面量进行初始化(不需要直接调用初始化器):

Bool、Int、Float、Double、String、Array、Dictionary、Set、Optional

1.2. 字面量协议

Swift自带类型之所以能够通过字面量初始化,是因为它们遵守了对应的协议。

Bool : ExpressibleByBooleanLiteral
Int : ExpressibleByIntegerLiteral
Float、Double : ExpressibleByIntegerLiteral、ExpressibleByFloatLiteral
Dictionary : ExpressibleByDictionaryLiteral
String : ExpressibleByStringLiteral
Array、Set : ExpressibleByArrayLiteral
Optinal : ExpressibleByNilLiteral

示例代码:

var b: Bool = false // ExpressibleByBooleanLiteral
var i: Int = 10 // ExpressibleByIntegerLiteral
var f0: Float = 10 // ExpressibleByIntegerLiteral
var f1: Float = 10.0 // ExpressibleByFloatLiteral
var d0: Double = 10 // ExpressibleByIntegerLiteral
var d1: Double = 10.0 // ExpressibleByFloatLiteral
var s: String = "idbeny" // ExpressibleByStringLiteral
var arr: Array = [1, 2, 3] // ExpressibleByArrayLiteral
var set: Set = [1, 2, 3] // ExpressibleByArrayLiteral
var dict: Dictionary = ["name" : "daben"] // ExpressibleByDictionaryLiteral
var o: Optional<Int> = nil // ExpressibleByNilLiteral

1.3. 字面量协议应用

示例代码一:

Bool类型直接赋值给Int类型的变量,会直接报错:

如果要不报错,只需要给Int添加一个遵守协议的扩展即可:

extension Int : ExpressibleByBooleanLiteral {
    public init(booleanLiteral value: Bool) {
        self = value ? 1 : 0
    }
}
var num: Int = true
print(num) // 输出:1

示例代码二:

class Student: ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByStringLiteral, CustomStringConvertible {
    var name: String = ""
    var score: Double = 0
    required init(floatLiteral value: Double) {
        self.score = value
    }
    required init(integerLiteral value: Int) {
        self.score = Double(value)
    }
    required init(stringLiteral value: String) {
        self.name = value
    }
    // 支持Unicode和特殊字符
    required init(unicodeScalarLiteral value: String) {
        self.name = value
    }
    required init(extendedGraphemeClusterLiteral value: String) {
        self.name = value
    }
    var description: String {
        "name=\(name), score=\(score)"
    }
}
var stu: Student = 90
print(stu) // 输出:name=, score=90.0

stu = 98.5
print(stu) // 输出:name=, score=98.5

stu = "idbeny"
print(stu) // 输出:name=idbeny, score=0.0

示例代码三:

struct Point {
    var x = 0.0, y = 0.0
}
extension Point : ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral {
    init(arrayLiteral elements: Double...) {
        guard elements.count > 0 else {
            return
        }
        self.x = elements[0]
        guard elements.count > 1 else {
            return
        }
        self.y = elements[1]
    }
    init(dictionaryLiteral elements: (String, Double)...) {
        for (k, v) in elements {
            if k == "x" {
                self.x = v
            } else if k == "y" {
                self.y = v
            }
        }
    }
}
var p: Point = [5.12, 10.24]
print(p) // 输出:Point(x: 5.12, y: 10.24)

p = ["x" : 11, "y" : 22]
print(p) // 输出:Point(x: 11.0, y: 22.0)

二、模式(Pattern)

模式是用于匹配的规则,比如switchcase、捕捉错误的catchif/guard/while/for语句的条件等。

Swift中的模式有:

  • 通配符模式(Wildcard Pattern)
  • 标识符模式(Identifier Pattern)
  • 值绑定模式(Value-Binding Pattern)
  • 元组模式(Tuple Pattern)
  • 枚举Case模式(Enumeration Pattern)
  • 可选模式(Optional Pattern)
  • 类型转换模式(Type-Casting Pattern)
  • 表达式模式(Expression Pattern)

2.1. 通配符模式

  • _匹配任何值
  • _?匹配非nil

示例代码:

enum Life {
    case human(name: String, age: Int?)
    case animal(name: String, age: Int?)
}
func check(_ life: Life) {
    switch life {
    case .human(let name, _):
        print("human", name)
    case .animal(let name, _?):
        print("animal", name)
    default:
        print("other")
    }
}
check(.human(name: "Rose", age: 20)) // 输出:human Rose
check(.human(name: "Jack", age: nil)) // 输出:human Jack
check(.animal(name: "Dog", age: 5)) // 输出:animal Dog
check(.animal(name: "Cat", age: nil)) // 输出:other

case .human(let name, _):中的_就是匹配任意值。

case .animal(let name, _?):中的_?就是匹配非nil值。

2.2. 标识符模式

给对应的变量、常量名赋值。

var age = 10
let name = "idbeny"

2.3. 值绑定模式

把对应位置的值绑定到变量/常量上。

let point = (3, 2)
switch point {
case let (x, y):
    print("x:\(x), y:\(y)")
}
// 输出:x:3, y:2

2.4. 元组模式

本质也是值绑定和通配符模式。

示例代码一(数组):

let points = [(0, 0), (1, 0), (2, 0)]
for (x, _) in points {
    print(x)
}
/*
 输出 :
 0
 1
 2
 */

示例代码二(case):

let name: String? = "idbeny"
let age = 18
let info: Any = [1, 2]
switch (name, age, info) {
case (_?, _, _ as String):
    print("case")
default:
    print("default")
}
// 输出:default

示例代码三(字典):

var scores = ["jack" : 98, "rose" : 100, "kate" : 86]
for (name, score) in scores {
    print(name, score)
}
/*
 输出:
 jack 98
 kate 86
 rose 100
 */

2.5. 枚举Case模式

if case语句等价于只有1个caseswitch语句。

示例代码一:

let age = 2
func test() {
    // 原来的写法
    if age >= 0 && age <= 9 {
        print("[0, 9]")
    }
    // 枚举case模式
    if case 0...9  = age {
        print("[0, 9]")
    }

    guard case 0...9 = age else { return }
    print("[0, 9]")
}
test()
/*
 输出:
 [0, 9]
 [0, 9]
 [0, 9]
 */

if case 0...9 = age可以理解为是拿出age的值和case后面的条件进行匹配。

下面的代码完全等价上面示例代码的if case

switch age {
case 0...9:
    print("[0, 9]")
default:
    break
}
// 输出:[0, 9]

示例代码二:

let age = 2
let ages: [Int?] = [2, 3, nil, 5]
for case nil in ages {
    print("有nil值")
    break
}
// 输出:有nil值

示例代码三:

let points = [(1, 0), (2, 1), (3, 0)]
for case let (x, 0) in points {
    print(x)
}
// 输出:1  3

// 错误写法:
// for (x, 0) in points {
//     print(x)
// }

2.6. 可选模式

示例代码一:

let age: Int? = 42
if case .some(let x) = age {
    print(x)
}
// 输出:42

// x?代表非空
if case let x? = age {
    print(x)
}
// 输出:42

示例代码二:

let ages: [Int?] = [nil, 2, 3, nil, 5]
for case let age? in ages {
    print(age)
}
/*
 输出:
 2
 3
 5
 */

上面示例二的代码和下面的代码等效:

for item in ages {
    // 可选项绑定
    if let age = item {
        print(age)
    }
}
/*
 输出:
 2
 3
 5
 */

示例代码三:

func check(_ num: Int?) {
    switch num {
    case 2?:
        print("2")
    case 4?:
        print("4")
    case 6?:
        print("6")
    case _?:
        print("other")
    case _:
        print("nil")
    }
}
check(4) // 输出:4
check(8) // 输出:other
check(nil) // 输出:nil

2.7. 类型转换模式

主要是isas的用法。

示例代码一:

let num: Any = 6
switch num {
case is Int:
    print("is Int", num)
default:
    break
}
// 输出:is Int 6

case is Int仅仅是判断num是否为Int类型,编译器不会自动强转,依然认为numAny类型。

如果需要强转并且判断类型,可以使用as

switch num {
case let n as Int:
    print("as Int", n)
default:
    break
}
// 输出:as Int 6

此时的nInt类型,但是num依然是Any类型。如果num不是Int类型就会跳过当前case,匹配下一个case

示例代码二:

class Animal {
    func eat() {
        print(type(of: self), "eat")
    }
}
class Dog : Animal {
    func run() {
        print(type(of: self), "run")
    }
}
class Cat : Animal {
    func jump() {
        print(type(of: self), "jump")
    }
}
func check(_ animal: Animal) {
    switch animal {
    case let dog as Dog:
        dog.eat()
        dog.run()
    case is Cat:
        animal.eat()
    default:
        break
    }
}
check(Dog())
/*
 输出:
 Dog eat
 Dog run
 */

check(Cat())
/*
 输出:
 Cat eat
 */

上面的示例中,如果匹配case is Cat时,怎样才能执行jump方法?因为animalAnimal类型。

可以对animal进行强制转换:

(animal as? Cat)?.jump()

2.8. 表达式模式

表达式模式用在case中:

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.

通过汇编查看上面示例代码,发现示例程序是用~=运算符做匹配:

其实,在Swift中,一些复杂switch匹配会用到~=运算符,但并不是所有的switch都是用到该运算符。

可以通过重载运算符,自定义表达式模式的匹配规则。

2.8.1. 自定义表达式模式

示例代码一:

struct Student {
    var score = 0, name = ""
    static func ~= (pattern: Int, value: Student) -> Bool {
        value.score >= pattern
    }
    static func ~= (pattern: ClosedRange<Int>, value: Student) -> Bool {
        pattern.contains(value.score)
    }
    static func ~= (pattern: Range<Int>, value: Student) -> Bool {
        pattern.contains(value.score)
    }
}

使用示例代码一:

var stu = Student(score: 72, name: "idbeny")
switch stu {
case 100: print(">=100")
case 90: print(">=90")
case 80..<90: print("[80, 90)")
case 60...79: print("[60, 79]")
case 0: print(">=0")
default: break
}
// 输出:[60, 79]

stu是怎么和IntRang进行匹配的呢?重写~=运算符。

基本上是固定写法(返回值必须是Bool):

// pattern: case后面的类型
// value: switch后面的类型
static func ~= (pattern: Int, value: Student) -> Bool {
    
}

示例代码二(if case本质就是switch):

if case 60 = stu {
    print(">=60")
}
// 输出:>=60

示例代码三:

var info = (Student(score: 70, name: "daben"), "及格")
switch info {
case let (60, text):
    print(text)
default:
    break
}
// 输出:及格

示例代码四:

extension String {
    static func ~= (pattern: (String) -> Bool, value: String) -> Bool {
        pattern(value)
    }
}

func hasPrefix(_ prefix: String) -> ((String) -> Bool) {
    { $0.hasPrefix(prefix) }
}
func hasSuffix(_ suffix: String) -> ((String) -> Bool) {
    { $0.hasSuffix(suffix) }
}

var str = "idbeny"
switch str {
case hasPrefix("i"), hasSuffix("y"):
    print("以i开头 或 以y结尾")
default:
    break
}
// 输出:以i开头 或 以y结尾

示例代码五:

func isEven(_ i: Int) -> Bool {
    i % 2 == 0
}
func isOdd(_ i: Int) -> Bool {
    i % 2 != 0
}

extension Int {
    static func ~= (pattern: (Int) -> Bool, value: Int) -> Bool {
        pattern(value)
    }
}

var age = 10
switch age {
case isEven:
    print("偶数")
case isOdd:
    print("奇数")
default:
    print("其他")
}
// 输出:偶数

还可以定义更多自定义操作符:

prefix operator ~>
prefix operator ~>=
prefix operator ~<
prefix operator ~<=
prefix func ~> (_ i: Int) -> ((Int) -> Bool) { { $0 > i } }
prefix func ~>= (_ i: Int) -> ((Int) -> Bool) { { $0 >= i } }
prefix func ~< (_ i: Int) -> ((Int) -> Bool) { { $0 < i } }
prefix func ~<= (_ i: Int) -> ((Int) -> Bool) { { $0 <= i } }

switch age {
case ~>=0, ~<=10:
    print("1")
case ~>10, ~<20:
    print("2")
default:
    break
}
// 输出:1

>, =, >=, <, <=运算符都是中缀运算符,为了不影响原有的运算符特性,在原有运算符前面加一个~符号成为一个新的运算符。

2.8.2. where

可以使用where为模式匹配增加匹配条件。

示例代码一(case):

var data = (10, "Jack")
switch data {
case let (age, _) where age > 10:
    print(data.1, "age>10")
case let (age, _) where age > 0:
    print(data.1, "age>0")
default:
    break
}
// 输出:Jack age>0

示例代码二(for):

var ages = [10, 20, 12, 55, 30]
for age in ages where age > 20 {
    print(age)
}
// 输出:55 30

示例代码三(protocol):

protocol Stackable {
    associatedtype Element
}
protocol Container {
    associatedtype Stack : Stackable where Stack.Element : Equatable
}

示例代码四(func):

func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable {
    return false
}

示例代码五(extension):

extension Container where Self.Stack.Element : Hashable { }

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值