泛型 Where 语句
类型约束 让你能够为泛型函数、下标、类型的类型参数定义一些强制要求。
对关联类型添加约束通常是非常有用的。你可以通过定义一个泛型 where
子句来实现。通过泛型 where
子句让关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 where
关键字紧跟在类型参数列表后面来定义 where
子句,where
子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where
子句。
下面的例子定义了一个名为 allItemsMatch
的泛型函数,用来检查两个 Container
实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 true
,否则返回 false
。
被检查的两个 Container
可以不是相同类型的容器(虽然它们可以相同),但它们必须拥有相同类型的元素。这个要求通过一个类型约束以及一个 where
子句来表示:
funcallItemsMatch<C1:Container, C2:Container> (_ someContainer: C1, _ anotherContainer: C2) ->Bool where C1.Item == C2.Item, C1.Item:Equatable { // 检查两个容器含有相同数量的元素 if someContainer.count!= anotherContainer.count { returnfalse } // 检查每一对元素是否相等 for i in0..<someContainer.count { if someContainer[i]!= anotherContainer[i] { returnfalse } } // 所有元素都匹配,返回 true returntrue }
这个函数接受 someContainer
和 anotherContainer
两个参数。参数 someContainer
的类型为 C1
,参数 anotherContainer
的类型为 C2
。C1
和 C2
是容器的两个占位类型参数,函数被调用时才能确定它们的具体类型。
这个函数的类型参数列表还定义了对两个类型参数的要求:
-
C1
必须符合Container
协议(写作C1: Container
)。 -
C2
必须符合Container
协议(写作C2: Container
)。 -
C1
的Item
必须和C2
的Item
类型相同(写作C1.Item == C2.Item
)。 -
C1
的Item
必须符合Equatable
协议(写作C1.Item: Equatable
)。
前两个要求定义在函数的类型形式参数列表里,后两个要求定义在了函数的泛型 where
分句中。
这些要求意味着:
-
someContainer
是一个C1
类型的容器。 -
anotherContainer
是一个C2
类型的容器。 -
someContainer
和anotherContainer
包含相同类型的元素。 -
someContainer
中的元素可以通过不等于操作符(!=)来检查它们是否相同。
第三个和第四个要求结合起来意味着 anotherContainer
中的元素也可以通过 !=
操作符来比较,因为它们和 someContainer
中的元素类型相同。
这些要求让 allItemsMatch(_:_:)
函数能够比较两个容器,即使它们的容器类型不同。
allItemsMatch(_:_:)
函数首先检查两个容器元素个数是否相同,如果元素个数不同,那么一定不匹配,函数就会返回 false
。
进行这项检查之后,通过 for-in
循环和半闭区间操作符(..<
)来迭代每个元素,检查 someContainer
中的元素是否不等于 anotherContainer
中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回 false。
如果循环体结束后未发现任何不匹配的情况,表明两个容器匹配,函数返回 true
。
下面是 allItemsMatch(_:_:)
函数的示例:
var stackOfStrings = Stack<String>() stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") var arrayOfStrings = ["uno", "dos", "tres"] ifallItemsMatch(stackOfStrings, arrayOfStrings) { print("All items match.") } else { print("Not all items match.") } // 打印“All items match.”
上面的例子创建 Stack
实例来存储 String
值,然后将三个字符串压栈。这个例子还通过数组字面量创建了一个 Array
实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从 Container
协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 allItemsMatch(_:_:)
函数。在上面的例子中,allItemsMatch(_:_:)
函数正确地显示了这两个容器中的所有元素都是相互匹配的。
具有泛型 Where 子句的扩展
你也可以使用泛型 where
子句作为扩展的一部分。基于以前的例子,下面的示例扩展了泛型 Stack
结构体,添加一个 isTop(_:)
方法。
extensionStackwhereElement:Equatable { funcisTop(_item: Element) ->Bool { guardlet topItem = items.lastelse { returnfalse } return topItem == item } }
这个新的 isTop(_:)
方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 where
子句,会有一个问题:在 isTop(_:)
里面使用了 ==
运算符,但是 Stack
的定义没有要求它的元素是符合 Equatable
协议的,所以使用 ==
运算符导致编译时错误。使用泛型 where
子句可以为扩展添加新的条件,因此只有当栈中的元素符合 Equatable
协议时,扩展才会添加 isTop(_:)
方法。
以下是 isTop(_:)
方法的调用方式:
if stackOfStrings.isTop("tres") { print("Top element is tres.") } else { print("Top element is something else.") } // 打印“Top element is tres.”
如果尝试在其元素不符合 Equatable
协议的栈上调用 isTop(_:)
方法,则会收到编译时错误。
structNotEquatable { } var notEquatableStack = Stack<NotEquatable>() let notEquatableValue =NotEquatable() notEquatableStack.push(notEquatableValue) notEquatableStack.isTop(notEquatableValue)// 报错
你可以使用泛型 where
子句去扩展一个协议。基于以前的示例,下面的示例扩展了 Container
协议,添加一个 startsWith(_:)
方法。
extensionContainerwhere Item:Equatable { funcstartsWith(_item: Item) ->Bool { return count >=1&& self[0]== item } }
这个 startsWith(_:)
方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何符合 Container
协议的类型都可以使用这个新的 startsWith(_:)
方法,包括上面使用的栈和数组,只要容器的元素是符合 Equatable
协议的。
if[9, 9, 9].startsWith(42) { print("Starts with 42.") } else { print("Starts with something else.") } // 打印“Starts with something else.”
上述示例中的泛型 where
子句要求 Item
遵循协议,但也可以编写一个泛型 where
子句去要求 Item
为特定类型。例如:
extensionContainerwhere Item ==Double { funcaverage() ->Double { var sum =0.0 for index in0..<count { sum += self[index] } return sum /Double(count) } } print([1260.0, 1200.0, 98.6, 37.0].average()) // 打印“648.9”
此示例将一个 average()
方法添加到 Item
类型为 Double
的容器中。此方法遍历容器中的元素将其累加,并除以容器的数量计算平均值。它将数量从 Int
转换为 Double
确保能够进行浮点除法。
就像可以在其他地方写泛型 where
子句一样,你可以在一个泛型 where
子句中包含多个条件作为扩展的一部分。用逗号分隔列表中的每个条件。
包含上下文关系的 where 分句
当你使用泛型时,可以为没有独立类型约束的声明添加 where
分句。例如,你可以使用 where
分句为泛型添加下标,或为扩展方法添加泛型约束。Container
结构体是个泛型,下面的例子通过 where
分句让新的方法声明其调用所需要满足的类型约束。
extensionContainer { funcaverage() ->Doublewhere Item ==Int { var sum =0.0 for index in0..<count { sum +=Double(self[index]) } return sum /Double(count) } funcendsWith(_item: Item) ->Boolwhere Item:Equatable { return count >=1&& self[count-1]== item } } let numbers = [1260, 1200, 98, 37] print(numbers.average()) // 输出 "648.75" print(numbers.endsWith(37)) // 输出 "true"
例子中,当 Item
是整型时为 Container
添加 average()
方法,当 Item
遵循 Equatable
时添加 endsWith(_:)
方法。两个方法都通过 where
分句对 Container
中定义的泛型 Item
进行了约束。
如果不使用包含上下文关系的 where
分句,需要写两个扩展,并为每个扩展分别加上 where
分句。下面的例子和上面的具有相同效果。
extensionContainerwhere Item ==Int { funcaverage() ->Double { var sum =0.0 for index in0..<count { sum +=Double(self[index]) } return sum /Double(count) } } extensionContainerwhere Item:Equatable { funcendsWith(_item: Item) ->Bool { return count >=1&& self[count-1]== item } }
在包含上下文关系的 where
分句的例子中,由于每个方法的 where
分句各自声明了需要满足的条件,因此 average()
和 endsWith(_:)
的实现能放在同一个扩展里。而将 where
分句放在扩展进行声明也能起到同样的效果,但每一个扩展只能有一个必备条件。
具有泛型 Where 子句的关联类型
你可以在关联类型后面加上具有泛型 where
的子句。例如,建立一个包含迭代器(Iterator
)的容器,就像是标准库中使用的 Sequence
协议那样。你应该这么写:
protocolContainer { associatedtype Item mutatingfuncappend(_item: Item) var count: Int { get } subscript(i: Int) -> Item { get } associatedtype Iterator:IteratorProtocolwhereIterator.Element== Item funcmakeIterator() ->Iterator }
迭代器(Iterator
)的泛型 where
子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。makeIterator()
则提供了容器的迭代器的访问接口。
一个协议继承了另一个协议,你通过在协议声明的时候,包含泛型 where
子句,来添加了一个约束到被继承协议的关联类型。例如,下面的代码声明了一个 ComparableContainer
协议,它要求所有的 Item
必须是 Comparable
的。
protocolComparableContainer:Container where Item:Comparable { }
泛型下标
下标可以是泛型,它们能够包含泛型 where
子句。你可以在 subscript
后用尖括号来写占位符类型,你还可以在下标代码块花括号前写 where
子句。例如:
extensionContainer { subscript<Indices:Sequence>(indices: Indices) -> [Item] whereIndices.Iterator.Element==Int { var result: [Item] = [] for index in indices { result.append(self[index]) } return result } }
这个 Container
协议的扩展添加了一个下标方法,接收一个索引的集合,返回每一个索引所在的值的数组。这个泛型下标的约束如下:
- 在尖括号中的泛型参数
Indices
,必须是符合标准库中的Sequence
协议的类型。 - 下标使用的单一的参数,
indices
,必须是Indices
的实例。 - 泛型
where
子句要求Sequence(Indices)
的迭代器,其所有的元素都是Int
类型。这样就能确保在序列(Sequence
)中的索引和容器(Container
)里面的索引类型是一致的。
综合一下,这些约束意味着,传入到 indices
下标,是一个整型的序列。