3.8 模式

原文链接: https://docs.swift.org/latest/documentation/the-swift-programming-language/patterns/

3.8 模式

匹配并解构值。

模式表示单个值或复合值的结构。例如,元组 (1, 2) 的结构是两个元素组成的逗号分隔列表。由于模式表示的是值的结构、而不是某个特定的值,你可以用各种值来与它匹配。例如,模式 (x, y) 可以匹配元组 (1, 2),也可以匹配任何其他二元元组。除了用值来匹配模式,你还可以提取复合值的部分或全部内容,把每一部分绑定到常量或变量名。

在 Swift 中,模式基本上有两种:成功匹配任何值的模式,以及在运行时可能无法匹配指定值的模式。

第一种模式用于在简单的变量、常量和可选绑定中解构值,包括通配符模式、标识符模式,以及包含它们的任何值绑定模式或元组模式。你可以为这些模式指定类型标注,把它们限制为只匹配某种类型的值。

第二种模式用于完整的模式匹配,此时要匹配的值在运行时可能并不存在,包括枚举成员模式、可选模式、表达式模式和类型转换模式。你在 switch 语句的 case 标签中、do 语句的 catch 子句中,或者 if、while、guard、for-in 语句的 case 条件中使用这些模式。

Grammar of a pattern:

pattern → wildcard-pattern type-annotation?
pattern → identifier-pattern type-annotation?
pattern → value-binding-pattern
pattern → tuple-pattern type-annotation?
pattern → enum-case-pattern
pattern → optional-pattern
pattern → type-casting-pattern
pattern → expression-pattern

通配符模式

通配符模式匹配并忽略任何值,由一个下划线(_)构成。当你不关心被匹配的值时,就使用通配符模式。例如,下面的代码遍历闭区间 1...3,在循环的每一轮都忽略该范围的当前值:

1
2
3
for _ in 1...3 {
    // 做三次某件事。
}

Grammar of a wildcard pattern:

wildcard-pattern → _

标识符模式

标识符模式匹配任何值,并把匹配到的值绑定到变量或常量的名字上。例如在下面这个常量声明中,someValue 就是一个标识符模式,它匹配 Int 类型的值 42:

1
let someValue = 42

匹配成功时,值 42 被绑定(赋值)到常量名 someValue 上。

当变量或常量声明左侧的模式是标识符模式时,该标识符模式隐含地是值绑定模式的一个子模式。

Grammar of an identifier pattern:

identifier-pattern → identifier

值绑定模式

值绑定模式把匹配到的值绑定到变量或常量的名字上。把匹配值绑定到常量名的值绑定模式以 let 关键字开头;绑定到变量名的以 var 关键字开头。

值绑定模式中的标识符模式会把新的具名变量或常量绑定到与它们匹配的值上。例如,你可以分解元组的元素,并把每个元素的值绑定到相应的标识符模式:

1
2
3
4
5
6
7
let point = (3, 2)
switch point {
// 把 x 和 y 绑定到 point 的两个元素。
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): 匹配相同的值。

Grammar of a value-binding pattern:

value-binding-pattern → var pattern | let pattern

元组模式

元组模式是由零个或多个模式组成的逗号分隔列表,并括在圆括号中。元组模式匹配相应元组类型的值。

你可以用类型标注把元组模式限制为匹配某些种类的元组类型。例如,常量声明 let (x, y): (Int, Int) = (1, 2) 中的元组模式 (x, y): (Int, Int) 只匹配两个元素都是 Int 类型的元组类型。

当元组模式用作 for-in 语句或变量、常量声明中的模式时,它只能包含通配符模式、标识符模式、可选模式,或者包含这些模式的其他元组模式。例如,下面的代码不合法,因为元组模式 (x, 0) 中的元素 0 是表达式模式:

1
2
3
4
5
let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)]
// 这段代码不合法。
for (x, 0) in points {
    /* ... */
}

只包含一个元素的元组模式外面的圆括号没有实际效果,该模式匹配这个唯一元素类型的值。例如,下面几种写法是等价的:

1
2
3
let a = 2        // a: Int = 2
let (a) = 2      // a: Int = 2
let (a): Int = 2 // a: Int = 2

Grammar of a tuple pattern:

tuple-pattern → ( tuple-pattern-element-list? )
tuple-pattern-element-list → tuple-pattern-element | tuple-pattern-element , tuple-pattern-element-list
tuple-pattern-element → pattern | identifier : pattern

枚举成员模式

枚举成员模式匹配某个已有枚举类型的成员。枚举成员模式出现在 switch 语句的 case 标签中,以及 if、while、guard 和 for-in 语句的 case 条件中。

如果你试图匹配的枚举成员带有任何关联值,相应的枚举成员模式必须指定一个元组模式,其中为每个关联值各包含一个元素。关于用 switch 语句匹配带关联值枚举成员的例子,参见关联值。

枚举成员模式还可以匹配包裹在可选值中的该成员的值。这种简化语法让你可以省略可选模式。注意,由于 Optional 是以枚举实现的,.none 和 .some 可以与枚举类型的各个成员出现在同一个 switch 中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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"。

Grammar of an enumeration case pattern:

enum-case-pattern → type-identifier? . enum-case-name tuple-pattern?

可选模式

可选模式匹配包裹在 Optional<Wrapped> 枚举 some(Wrapped) 成员里的值。可选模式由一个标识符模式紧跟一个问号构成,出现的位置与枚举成员模式相同。

由于可选模式是 Optional 枚举成员模式的语法糖,下面两种写法是等价的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let someOptional: Int? = 42
// 用枚举成员模式匹配。
if case .some(let x) = someOptional {
    print(x)
}

// 用可选模式匹配。
if case let x? = someOptional {
    print(x)
}

可选模式提供了一种便利的方式,在 for-in 语句中遍历可选值数组,只对非 nil 元素执行循环体。

1
2
3
4
5
6
7
8
let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5]
// 只匹配非 nil 的值。
for case let number? in arrayOfOptionalInts {
    print("Found a \(number)")
}
// Found a 2
// Found a 3
// Found a 5

Grammar of an optional pattern:

optional-pattern → identifier-pattern ?

类型转换模式

有两种类型转换模式:is 模式和 as 模式。is 模式只出现在 switch 语句的 case 标签中。is 和 as 模式的形式如下:

1
2
is <#type#>
<#pattern#> as <#type#>

如果某个值在运行时的类型与 is 模式右侧指定的类型相同,或者是该类型的子类,is 模式就匹配该值。is 模式的行为与 is 运算符类似:两者都执行类型转换,但都丢弃转换得到的类型。

如果某个值在运行时的类型与 as 模式右侧指定的类型相同,或者是该类型的子类,as 模式就匹配该值。如果匹配成功,匹配到的值的类型会被转换成 as 模式右侧指定的模式。

关于用 switch 语句配合 is 和 as 模式匹配值的例子,参见 Any 与 AnyObject 的类型转换。

Grammar of a type casting pattern:

type-casting-pattern → is-pattern | as-pattern
is-pattern → is type
as-pattern → pattern as type

表达式模式

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

表达式模式所表示的表达式,会与输入表达式的值用 Swift 标准库中的模式匹配运算符(~=)进行比较。如果 ~= 运算符返回 true,匹配就成功。默认情况下,~= 运算符用 == 运算符比较两个同类型的值;它也可以把一个值与一个取值区间匹配,只要检查该值是否落在该区间内,如下例所示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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 表达式比较。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 重载 ~= 运算符,让字符串与整数匹配。
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)."。

Grammar of an expression pattern:

expression-pattern → expression