Swift5.4 语言指南(二十四) 泛型
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9739833.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
通用代码使您可以编写灵活,可重用的函数和类型,这些函数和类型可根据您定义的要求与任何类型一起使用。您可以编写避免重复的代码,并以清晰抽象的方式表达其意图。
泛型是Swift最强大的功能之一,许多Swift标准库都是使用泛型代码构建的。实际上,即使您没有意识到,您在整个《语言指南》中也一直在使用泛型。例如,SwiftArray
和Dictionary
type都是通用集合。您可以创建一个保存Int
值的数组,或者一个保存值的数组String
,或者实际上是可以在Swift中创建的任何其他类型的数组。同样,您可以创建一个字典来存储任何指定类型的值,并且对该类型可以没有任何限制。
泛型解决的问题
这是一个称为的标准非泛型函数swapTwoInts(_:_:)
,它交换两个Int
值:
- func swapTwoInts(_ a: inout Int, _ b: inout Int) {
- let temporaryA = a
- a = b
- b = temporaryA
- }
如In-Out Parameters中所述,此函数利用in-out参数交换a
and的值。b
该swapTwoInts(_:_:)
功能交换原值b
为a
,和原来的值a
进入b
。您可以调用此函数来交换两个Int
变量中的值:
- var someInt = 3
- var anotherInt = 107
- swapTwoInts(&someInt, &anotherInt)
- print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
- // Prints "someInt is now 107, and anotherInt is now 3"
该swapTwoInts(_:_:)
函数很有用,但只能与Int
值一起使用。如果要交换两个String
值或两个Double
值,则必须编写更多函数,例如下面所示的swapTwoStrings(_:_:)
andswapTwoDoubles(_:_:)
函数:
- func swapTwoStrings(_ a: inout String, _ b: inout String) {
- let temporaryA = a
- a = b
- b = temporaryA
- }
- func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
- let temporaryA = a
- a = b
- b = temporaryA
- }
您可能已经注意到的尸体swapTwoInts(_:_:)
,swapTwoStrings(_:_:)
和swapTwoDoubles(_:_:)
功能是相同的。唯一的区别是该值的,他们接受的类型(Int
,String
,和Double
)。
编写交换任何类型的两个值的单个函数更有用,而且也更灵活。通用代码使您可以编写此类功能。(下面定义了这些功能的通用版本。)
笔记
在所有这三个功能,类型的a
和b
必须相同。如果a
和b
不是同一类型,则无法交换它们的值。Swift是一种类型安全的语言,并且不允许(例如)类型String
变量和类型变量Double
彼此交换值。尝试这样做会导致编译时错误。
泛型函数
泛型函数可以使用任何类型。这是swapTwoInts(_:_:)
上面的函数的通用版本,称为swapTwoValues(_:_:)
:
- func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
- let temporaryA = a
- a = b
- b = temporaryA
- }
所述的主体swapTwoValues(_:_:)
的功能是相同的身体swapTwoInts(_:_:)
功能。但是,的第一行与swapTwoValues(_:_:)
略有不同swapTwoInts(_:_:)
。以下是第一行的比较:
- func swapTwoInts(_ a: inout Int, _ b: inout Int)
- func swapTwoValues<T>(_ a: inout T, _ b: inout T)
该函数的通用版本使用占位符的类型名(称为T
,在这种情况下),而不是一个实际的类型名称(例如Int
,String
或Double
)。占位符类型名字就不说了什么什么T
必须的,但它确实说,双方a
并b
必须是同一类型的T
,不管T
代表。T
每次swapTwoValues(_:_:)
调用该函数时,都会确定要使用的实际类型。
泛型函数和非泛型函数之间的另一个区别是,在尖括号()中,泛型函数的名称(swapTwoValues(_:_:)
)后跟占位符类型名称T
(<T>
)。方括号告诉Swift这T
是swapTwoValues(_:_:)
函数定义内的占位符类型名称。因为T
是占位符,所以Swift不会查找称为的实际类型T
。
swapTwoValues(_:_:)
现在可以以与相同的方式调用该函数swapTwoInts
,除了可以传递任何类型的两个值(只要这两个值彼此的类型相同)即可。每次swapTwoValues(_:_:)
调用时,T
从传递给函数的值的类型中推断出要使用的类型。
在下面的两个示例中,分别T
推断为Int
和String
:
- var someInt = 3
- var anotherInt = 107
- swapTwoValues(&someInt, &anotherInt)
- // someInt is now 107, and anotherInt is now 3
- var someString = "hello"
- var anotherString = "world"
- swapTwoValues(&someString, &anotherString)
- // someString is now "world", and anotherString is now "hello"
笔记
swapTwoValues(_:_:)
上面定义的函数是受称为的通用函数启发的,该函数swap
是Swift标准库的一部分,可自动在您的应用程序中使用。如果您需要swapTwoValues(_:_:)
在自己的代码中使用该函数的行为,则可以使用Swift的现有swap(_:_:)
函数,而不必提供自己的实现。
类型参数
在swapTwoValues(_:_:)
上面的示例中,占位符类型T
是type参数的示例。类型参数指定并命名一个占位符类型,并在函数名称后立即写入一对匹配的尖括号(例如<T>
)之间。
一旦指定了类型参数,就可以使用它来定义函数参数的类型(例如函数的a
和b
参数swapTwoValues(_:_:)
),或者作为函数的返回类型,或者作为函数体内的类型注释。在每种情况下,每当调用函数时,type参数都将替换为实际类型。(在swapTwoValues(_:_:)
上面的例子中,T
被替换Int
的第一次调用函数,并与被替换String
,它被称为第二时间)。
通过在尖括号中用逗号分隔多个类型参数名称,可以提供多个类型参数。
命名类型参数
在大多数情况下,类型参数具有描述性名称,如Key
and Value
in和in ,它告诉读者类型参数与它所使用的泛型类型或函数之间的关系。但是,当它们之间没有有意义的关系时,这是传统的给它们命名使用单个字母,例如,和,如在上述功能。Dictionary<Key, Value>
Element
Array<Element>
T
U
V
T
swapTwoValues(_:_:)
笔记
始终为类型参数提供驼峰式的大写名称(例如T
和MyTypeParameter
),以表明它们是类型的占位符,而不是值。
通用类型
除了通用函数,Swift还使您能够定义自己的通用类型。这些是可以与任何类型一起使用的自定义类,结构和枚举,类似于Array
和的方式Dictionary
。
本节说明如何编写称为的通用集合类型Stack
。堆栈是一组有序的值,类似于数组,但是操作集比Swift的Array
类型受限制。数组允许在数组中的任何位置插入和删除新项目。但是,堆栈允许将新项目仅附加到集合的末尾(称为将新值压入堆栈)。同样,堆栈仅允许从集合末尾删除项目(称为从堆栈弹出值)。
笔记
UINavigationController
该类使用堆栈的概念在其导航层次结构中对视图控制器进行建模。您调用UINavigationController
classpushViewController(_:animated:)
方法将视图控制器添加(或推送)到导航堆栈上,并调用其popViewControllerAnimated(_:)
方法从导航堆栈中删除(或弹出)视图控制器。每当您需要严格的“后进先出”方法来管理集合时,堆栈都是有用的集合模型。
下图显示了堆栈的推入和弹出行为:
- 当前在堆栈上有三个值。
- 第四个值被压入堆栈的顶部。
- 堆栈现在包含四个值,最近的一个在顶部。
- 弹出堆栈中的第一项。
- 弹出一个值后,堆栈再次保存三个值。
这是编写非通用版本堆栈的方法,在这种情况下,是针对Int
值堆栈的:
- struct IntStack {
- var items = [Int]()
- mutating func push(_ item: Int) {
- items.append(item)
- }
- mutating func pop() -> Int {
- return items.removeLast()
- }
- }
此结构使用一个Array
称为的属性items
将值存储在堆栈中。Stack
提供了push
和的两种方法,pop
用于将值压入和弹出堆栈。这些方法被标记为mutating
,因为它们需要修改(或变异)结构的items
数组。
但是,IntStack
上面显示的类型只能与Int
值一起使用。定义可以管理任何类型的值的堆栈的泛型 Stack
类将更加有用。
这是相同代码的通用版本:
- struct Stack<Element> {
- var items = [Element]()
- mutating func push(_ item: Element) {
- items.append(item)
- }
- mutating func pop() -> Element {
- return items.removeLast()
- }
- }
请注意,的通用版本Stack
与非通用版本基本相同,但是使用的类型参数Element
代替的实际类型Int
。该类型参数<Element>
立即写在结构名称后的一对尖括号()中。
Element
为以后要提供的类型定义一个占位符名称。可以Element
在结构定义内的任何地方引用此未来类型。在这种情况下,Element
在三个地方用作占位符:
- 要创建一个名为的属性
items
,该属性将使用一个类型为空的值数组进行初始化Element
- 要指定该
push(_:)
方法具有一个名为的单个参数item
,该参数必须为类型Element
- 指定
pop()
方法返回的值将是类型的值Element
由于它是通用类型,Stack
因此可以用来在Swift中创建任何有效类型的堆栈,类似于Array
和的方式Dictionary
。
Stack
通过编写要存储在堆栈中尖括号内的类型来创建新实例。例如,要创建新的字符串堆栈,请编写Stack<String>()
:
- var stackOfStrings = Stack<String>()
- stackOfStrings.push("uno")
- stackOfStrings.push("dos")
- stackOfStrings.push("tres")
- stackOfStrings.push("cuatro")
- // the stack now contains 4 strings
这是stackOfStrings
将这四个值压入堆栈后的样子:
从堆栈中弹出一个值将删除并返回最高值"cuatro"
:
- let fromTheTop = stackOfStrings.pop()
- // fromTheTop is equal to "cuatro", and the stack now contains 3 strings
弹出顶部值后,堆栈的外观如下:
扩展通用类型
扩展通用类型时,不提供类型参数列表作为扩展定义的一部分。而是在扩展的正文中提供原始类型定义的类型参数列表,并且原始类型参数名称用于引用原始定义的类型参数。
以下示例扩展了泛型Stack
类型,以添加名为的只读计算属性topItem
,该属性将返回堆栈中的顶层项目,而不将其从堆栈中弹出:
- extension Stack {
- var topItem: Element? {
- return items.isEmpty ? nil : items[items.count - 1]
- }
- }
该topItem
属性返回type的可选值Element
。如果堆栈为空,则topItem
返回nil
;如果堆栈不为空,则topItem
返回items
数组中的最后一项。
请注意,此扩展未定义类型参数列表。而是在扩展名中使用该Stack
类型的现有类型参数名称Element
来指示topItem
计算属性的可选类型。
topItem
现在,可以将计算所得的属性与任何Stack
实例一起使用,以访问和查询其顶层项目而无需将其删除。
- if let topItem = stackOfStrings.topItem {
- print("The top item on the stack is \(topItem).")
- }
- // Prints "The top item on the stack is tres."
通用类型的扩展还可以包括扩展类型的实例必须满足的条件才能获得新功能,如下面带有“通用位置”子句的扩展中所讨论的。
类型约束
该swapTwoValues(_:_:)
功能和Stack
类型可以与任何类型的工作。但是,有时在可与泛型函数和泛型类型一起使用的类型上强制使用某些类型约束非常有用。类型约束指定类型参数必须从特定的类继承,或符合特定的协议或协议组成。
例如,Swift的Dictionary
类型对可用作字典键的类型施加了限制。如字典中所述,字典键的类型必须是可哈希的。也就是说,它必须提供一种使其自身具有唯一代表性的方法。Dictionary
需要它的键是可哈希的,以便它可以检查它是否已经包含特定键的值。如果没有此要求,Dictionary
就无法确定是否应该为特定键插入或替换一个值,也无法为字典中已经存在的给定键找到一个值。
此要求是由的键类型上的类型约束来强制执行的,该约束Dictionary
指定键类型必须符合Hashable
协议(在Swift标准库中定义的特殊协议)。所有斯威夫特的基本类型(例如String
,Int
,Double
,和Bool
)默认情况下可哈希。有关使自己的自定义类型符合Hashable
协议的信息,请参阅《符合哈希协议》。
您可以在创建自定义泛型类型时定义自己的类型约束,这些约束提供了泛型编程的许多功能。诸如抽象概念之类的Hashable
特征是根据类型的概念特征而不是具体类型来表征类型。
类型约束语法
通过将单个类或协议约束放置在类型参数名称之后(用冒号分隔)作为类型参数列表的一部分,可以编写类型约束。泛型函数的类型约束的基本语法如下所示(尽管泛型类型的语法相同):
- func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
- // function body goes here
- }
上面的假设函数有两个类型参数。第一个类型参数T
的类型约束必须T
为的子类SomeClass
。第二种类型的参数U
具有类型约束,该约束必须U
符合协议SomeProtocol
。
操作中的类型约束
这是一个称为的非泛型函数findIndex(ofString:in:)
,为该函数提供了一个String
查找值和一个String
在其中查找值的值数组。该findIndex(ofString:in:)
函数返回一个可选Int
值,该值将是数组中第一个匹配字符串的索引(如果找到),或者nil
如果找不到该字符串:
- func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
- for (index, value) in array.enumerated() {
- if value == valueToFind {
- return index
- }
- }
- return nil
- }
该findIndex(ofString:in:)
函数可用于在字符串数组中查找字符串值:
- let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
- if let foundIndex = findIndex(ofString: "llama", in: strings) {
- print("The index of llama is \(foundIndex)")
- }
- // Prints "The index of llama is 2"
但是,在数组中查找值索引的原则不仅对字符串有用。您可以通过使用某种类型的值替换对字符串的任何提及来编写与通用函数相同的功能T
。
您可能会期望这样编写的通用版本findIndex(ofString:in:)
,即findIndex(of:in:)
。请注意,此函数的返回类型仍为Int?
,因为该函数返回一个可选的索引号,而不是数组中的一个可选值。请注意,但由于示例后说明的原因,该函数无法编译:
- func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
- for (index, value) in array.enumerated() {
- if value == valueToFind {
- return index
- }
- }
- return nil
- }
此函数未按上面的说明进行编译。问题在于相等性检查“ ”。并非Swift中的每种类型都可以与等于运算符()进行比较。例如,如果您创建自己的类或结构来表示复杂的数据模型,则该类或结构的“等于”的含义不是Swift可以为您猜测的。因此,无法保证此代码适用于每种可能的类型,并且在尝试编译代码时会报告相应的错误。if value == valueToFind
==
T
但是,一切并没有丢失。Swift标准库定义了一个称为的协议Equatable
,该协议需要任何符合条件的类型来实现等于操作符(==
)和不等于操作符(!=
),以比较该类型的任何两个值。Swift的所有标准类型都自动支持该Equatable
协议。
Equatable
该findIndex(of:in:)
函数可以安全地使用任何类型,因为可以保证支持equal运算符。为了表达这一事实,Equatable
在定义函数时,您将类型约束写入作为类型参数定义的一部分:
- func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
- for (index, value) in array.enumerated() {
- if value == valueToFind {
- return index
- }
- }
- return nil
- }
的单一类型参数findIndex(of:in:)
写为,表示“符合协议的任何类型。”T: Equatable
T
Equatable
该findIndex(of:in:)
功能现在编译成功,可以与任何类型的的使用Equatable
,如Double
或String
:
- let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
- // doubleIndex is an optional Int with no value, because 9.3 isn't in the array
- let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
- // stringIndex is an optional Int containing a value of 2
关联类型
定义协议时,有时将一个或多个关联类型声明为协议定义的一部分很有用。一个相关联的类型给出了一个占位符名称向被用作协议的一部分的类型。直到采用该协议,才指定用于该关联类型的实际类型。关联的类型用associatedtype
关键字指定。
实际中的关联类型
这是一个名为的协议示例,该协议Container
声明了一个关联类型Item
:
- protocol Container {
- associatedtype Item
- mutating func append(_ item: Item)
- var count: Int { get }
- subscript(i: Int) -> Item { get }
- }
该Container
协议定义了任何容器都必须提供的三个必需功能:
- 必须有可能使用方法将新项目添加到容器中
append(_:)
。 - 必须有可能通过
count
返回Int
值的属性来访问容器中的项目计数。 - 必须有可能使用带有
Int
索引值的下标检索容器中的每个项目。
该协议未指定容器中项目的存储方式或允许的类型。该协议仅指定任何类型必须提供的三位功能才能被视为a Container
。只要符合这三个要求,符合类型就可以提供其他功能。
符合Container
协议的任何类型都必须能够指定其存储的值的类型。具体来说,它必须确保仅将正确类型的项目添加到容器中,并且必须清楚其下标返回的项目的类型。
为了定义这些要求,Container
协议需要一种方法来引用容器将要容纳的元素的类型,而无需知道特定容器的类型。该Container
协议需要指定传递给任何值append(_:)
方法必须具有相同的类型容器的元件的类型,以及通过所述容器的下标所返回的值将是相同的类型容器的元件的类型。
为此,该Container
协议声明了一个关联类型Item
,称为。该协议没有定义什么-信息留给任何符合条件的类型提供。尽管如此,别名还是提供了一种方法来引用中的项目类型,并定义与该方法和下标一起使用的类型,以确保强制实施任何行为。associatedtype Item
Item
Item
Container
append(_:)
Container
这里的非泛型的版本IntStack
从型通用类型的上方,适于符合Container
协议:
- struct IntStack: Container {
- // original IntStack implementation
- var items = [Int]()
- mutating func push(_ item: Int) {
- items.append(item)
- }
- mutating func pop() -> Int {
- return items.removeLast()
- }
- // conformance to the Container protocol
- typealias Item = Int
- mutating func append(_ item: Int) {
- self.push(item)
- }
- var count: Int {
- return items.count
- }
- subscript(i: Int) -> Int {
- return items[i]
- }
- }
该IntStack
类型实现了Container
协议的所有三个要求,并且在每种情况下都包装了该IntStack
类型的现有功能的一部分,以满足这些要求。
此外,IntStack
指定,对于的此实现Container
,适合Item
使用的类型Int
。的定义匝抽象类型的成的具体类型对于本实施的协议。typealias Item = Int
Item
Int
Container
由于斯威夫特的类型推断,你实际上并不需要声明一个具体Item
的Int
作为定义的一部分IntStack
。因为IntStack
符合Container
协议的所有要求,所以Swift可以Item
简单地通过查看append(_:)
方法item
参数的类型和下标的返回类型来推断适当的使用方法。确实,如果您从上面的代码中删除了该行,那么一切仍然可以进行,因为很明显应该使用哪种类型。typealias Item = Int
Item
您还可以使泛型Stack
类型符合Container
协议:
- struct Stack<Element>: Container {
- // original Stack<Element> implementation
- var items = [Element]()
- mutating func push(_ item: Element) {
- items.append(item)
- }
- mutating func pop() -> Element {
- return items.removeLast()
- }
- // conformance to the Container protocol
- mutating func append(_ item: Element) {
- self.push(item)
- }
- var count: Int {
- return items.count
- }
- subscript(i: Int) -> Element {
- return items[i]
- }
- }
这次,将type参数Element
用作append(_:)
方法item
参数的类型和下标的返回类型。因此,Swift可以推断出Element
适合作为Item
此特定容器使用的适当类型。
扩展现有类型以指定关联类型
您可以扩展现有类型以添加对协议的一致性,如使用扩展添加协议一致性中所述。这包括具有关联类型的协议。
Swift的Array
类型已经提供了一个append(_:)
方法,一个count
属性和一个带有Int
索引的下标,以检索其元素。这三个功能符合Container
协议的要求。这意味着您可以简单地通过声明采用该协议来扩展Array
以符合该协议。您可以使用一个空的扩展名来执行此操作,如使用扩展名声明协议采用中所述:Container
Array
- extension Array: Container {}
数组的现有append(_:)
方法和下标使Swift能够推断要用于的适当类型Item
,就像Stack
上面的泛型类型一样。定义此扩展名后,您可以将任何Array
用作Container
。
将约束添加到关联类型
您可以将类型约束添加到协议中的关联类型,以要求符合条件的类型满足这些约束。例如,以下代码定义了一个版本,Container
该版本要求容器中的项目是相等的。
- protocol Container {
- associatedtype Item: Equatable
- mutating func append(_ item: Item)
- var count: Int { get }
- subscript(i: Int) -> Item { get }
- }
要符合此版本的Container
,容器的Item
类型必须符合Equatable
协议。
在关联类型的约束中使用协议
协议可以作为其自身要求的一部分出现。例如,这是一个完善协议的Container
协议,增加了suffix(_:)
方法的要求。该suffix(_:)
方法从容器的末尾返回给定数量的元素,并将它们存储在该Suffix
类型的实例中。
- protocol SuffixableContainer: Container {
- associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
- func suffix(_ size: Int) -> Suffix
- }
在此协议中,Suffix
是关联的类型,如上面示例中的Item
类型Container
。Suffix
有两个约束:它必须符合SuffixableContainer
协议(当前正在定义的协议),并且其Item
类型必须与容器的Item
类型相同。上的约束Item
是通用where
子句,下面将在“关联类型与通用where子句”中进行讨论。
这是上述“通用类型”中Stack
类型的扩展,它增加了对协议的一致性:SuffixableContainer
- extension Stack: SuffixableContainer {
- func suffix(_ size: Int) -> Stack {
- var result = Stack()
- for index in (count-size)..<count {
- result.append(self[index])
- }
- return result
- }
- // Inferred that Suffix is Stack.
- }
- var stackOfInts = Stack<Int>()
- stackOfInts.append(10)
- stackOfInts.append(20)
- stackOfInts.append(30)
- let suffix = stackOfInts.suffix(2)
- // suffix contains 20 and 30
在上面的示例中,Suffix
for的关联类型Stack
也是Stack
,因此on的后缀操作Stack
返回another Stack
。或者,符合的类型SuffixableContainer
可以具有与其Suffix
自身不同的类型,这意味着后缀操作可以返回不同的类型。例如,这是对非通用IntStack
类型的扩展,它添加了SuffixableContainer
一致性,使用Stack<Int>
其后缀类型代替IntStack
:
- extension IntStack: SuffixableContainer {
- func suffix(_ size: Int) -> Stack<Int> {
- var result = Stack<Int>()
- for index in (count-size)..<count {
- result.append(self[index])
- }
- return result
- }
- // Inferred that Suffix is Stack<Int>.
- }
通用条款
如Type Constraints中所述,类型约束使您可以定义与通用函数,下标或类型关联的类型参数的要求。
定义关联类型的要求也很有用。您可以通过定义泛型where子句来做到这一点。通用where
子句使您可以要求关联类型必须符合某种协议,或者某些类型参数和关联类型必须相同。泛型where
子句以where
关键字开头,后跟关联类型的约束或类型与关联类型之间的相等关系。您where
可以在类型或函数的主体的大括号前写一个通用子句。
下面的示例定义了一个称为的通用函数allItemsMatch
,该函数检查两个Container
实例是否包含相同顺序的相同项目。true
如果所有项目都匹配,则该函数返回一个布尔值,如果不匹配,则返回一个值false
。
两个要检查的容器不必是相同类型的容器(尽管可以是),但是它们必须容纳相同类型的项目。通过类型约束和通用where
子句的组合来表达此要求:
- func allItemsMatch<C1: Container, C2: Container>
- (_ someContainer: C1, _ anotherContainer: C2) -> Bool
- where C1.Item == C2.Item, C1.Item: Equatable {
- // Check that both containers contain the same number of items.
- if someContainer.count != anotherContainer.count {
- return false
- }
- // Check each pair of items to see if they're equivalent.
- for i in 0..<someContainer.count {
- if someContainer[i] != anotherContainer[i] {
- return false
- }
- }
- // All items match, so return true.
- return true
- }
此函数接受两个称为someContainer
和的参数anotherContainer
。该someContainer
参数是类型C1
,以及anotherContainer
参数的类型的C2
。两个C1
和C2
是当函数调用以确定两种类型的容器类型参数。
对函数的两个类型参数有以下要求:
C1
必须符合Container
协议(写为)。C1: Container
C2
还必须符合Container
协议(写为)。C2: Container
- 该
Item
对C1
必须相同Item
的C2
(写成)。C1.Item == C2.Item
- 在
Item
用于C1
必须符合Equatable
协议(写为)。C1.Item: Equatable
第一个和第二个需求在函数的类型参数列表中定义,而第三个和第四个需求在函数的泛型where
子句中定义。
这些要求意味着:
someContainer
是类型的容器C1
。anotherContainer
是类型的容器C2
。someContainer
并anotherContainer
包含相同类型的项目。someContainer
可以使用不相等的运算符(!=
)检查其中的项目,以查看它们是否彼此不同。
第三和第四的要求相结合,意味着中的项目anotherContainer
可以也可以与检查!=
经营者,因为他们是完全一样的类型中的项目someContainer
。
这些要求使该allItemsMatch(_:_:)
功能可以比较两个容器,即使它们是不同的容器类型也是如此。
该allItemsMatch(_:_:)
功能首先检查两个容器是否包含相同数量的物品。如果它们包含不同数量的项目,则无法匹配它们,函数将返回false
。
进行此检查后,该函数someContainer
使用for
-in
循环和半开范围运算符(..<
)遍历所有项。对于每个项目,该函数都会检查from中的项目someContainer
是否不等于中的对应项目anotherContainer
。如果两个项目不相等,则两个容器不匹配,函数返回false
。
如果循环结束而未找到不匹配项,则两个容器匹配,函数返回true
。
该allItemsMatch(_:_:)
函数的工作方式如下:
- var stackOfStrings = Stack<String>()
- stackOfStrings.push("uno")
- stackOfStrings.push("dos")
- stackOfStrings.push("tres")
- var arrayOfStrings = ["uno", "dos", "tres"]
- if allItemsMatch(stackOfStrings, arrayOfStrings) {
- print("All items match.")
- } else {
- print("Not all items match.")
- }
- // Prints "All items match."
上面的示例创建一个Stack
实例来存储String
值,并将三个字符串压入堆栈。该示例还创建了一个Array
实例,该实例使用数组文字初始化,该文字包含与堆栈相同的三个字符串。即使堆栈和数组的类型不同,它们都符合Container
协议,并且都包含相同类型的值。因此,您可以allItemsMatch(_:_:)
使用这两个容器作为参数来调用该函数。在上面的示例中,该allItemsMatch(_:_:)
函数正确地报告了两个容器中的所有项目都匹配。
具有通用Where子句的扩展
您也可以将通用where
子句用作扩展的一部分。下面的示例扩展了Stack
先前示例的泛型结构,以添加一个isTop(_:)
方法。
- extension Stack where Element: Equatable {
- func isTop(_ item: Element) -> Bool {
- guard let topItem = items.last else {
- return false
- }
- return topItem == item
- }
- }
此新isTop(_:)
方法首先检查堆栈是否为空,然后将给定的项目与堆栈的最高项目进行比较。如果您尝试在没有泛型where
子句的情况下执行isTop(_:)
此==
操作,则会遇到一个问题:的实现使用了操作符,但是对的定义Stack
并不要求其项是相等的,因此使用==
操作符会导致编译时错误。使用泛型where
子句可让您向扩展添加新要求,以便扩展isTop(_:)
仅在堆栈中的项目可相等时才添加方法。
该isTop(_:)
方法的实际效果如下:
- if stackOfStrings.isTop("tres") {
- print("Top element is tres.")
- } else {
- print("Top element is something else.")
- }
- // Prints "Top element is tres."
如果尝试isTop(_:)
在元素不可相等的堆栈上调用该方法,则会收到编译时错误。
- struct NotEquatable { }
- var notEquatableStack = Stack<NotEquatable>()
- let notEquatableValue = NotEquatable()
- notEquatableStack.push(notEquatableValue)
- notEquatableStack.isTop(notEquatableValue) // Error
您可以使用where
带有协议扩展名的通用子句。下面的示例Container
从先前的示例扩展了协议,以添加startsWith(_:)
方法。
- extension Container where Item: Equatable {
- func startsWith(_ item: Item) -> Bool {
- return count >= 1 && self[0] == item
- }
- }
该startsWith(_:)
方法首先确保容器中至少有一个项目,然后检查容器中的第一个项目是否与给定的项目匹配。只要容器的项目是相等的,此新startsWith(_:)
方法就可以用于任何符合Container
协议的类型,包括上面使用的堆栈和数组。
- if [9, 9, 9].startsWith(42) {
- print("Starts with 42.")
- } else {
- print("Starts with something else.")
- }
- // Prints "Starts with something else."
where
上面示例中的generic子句要求Item
符合协议,但是您也可以编写where
需要Item
为特定类型的generic子句。例如:
- extension Container where Item == Double {
- func average() -> Double {
- var sum = 0.0
- for index in 0..<count {
- sum += self[index]
- }
- return sum / Double(count)
- }
- }
- print([1260.0, 1200.0, 98.6, 37.0].average())
- // Prints "648.9"
本示例将average()
方法添加到Item
类型为的容器中Double
。它对容器中的项目进行迭代以将其相加,然后除以容器的数量以计算平均值。它将计数从显式转换Int
为Double
,以便能够进行浮点除法。
您可以在where
扩展的一部分的通用子句中包含多个需求,就像您where
在其他地方编写的通用子句一样。用逗号分隔列表中的每个要求。
上下文相关条款
where
当您已经在泛型类型的上下文中工作时,可以将泛型子句作为声明的一部分编写,该声明没有自己的泛型类型约束。例如,您可以where
在通用类型的下标或通用类型扩展的方法上编写通用子句。该Container
结构是通用的,where
下面示例中的子句指定必须满足哪些类型约束才能使这些新方法在容器上可用。
- extension Container {
- func average() -> Double where Item == Int {
- var sum = 0.0
- for index in 0..<count {
- sum += Double(self[index])
- }
- return sum / Double(count)
- }
- func endsWith(_ item: Item) -> Bool where Item: Equatable {
- return count >= 1 && self[count-1] == item
- }
- }
- let numbers = [1260, 1200, 98, 37]
- print(numbers.average())
- // Prints "648.75"
- print(numbers.endsWith(37))
- // Prints "true"
此示例在项为整数时向添加一个方法,而当项为等值时向该示例添加一个average()
方法。这两个函数都包含一个通用子句,该子句将类型约束从的原始声明添加到通用类型参数。Container
endsWith(_:)
where
Item
Container
如果要在不使用上下文where
子句的情况下编写此代码,请编写两个扩展,每个通用where
子句一个。上面的示例和下面的示例具有相同的行为。
- extension Container where Item == Int {
- func average() -> Double {
- var sum = 0.0
- for index in 0..<count {
- sum += Double(self[index])
- }
- return sum / Double(count)
- }
- }
- extension Container where Item: Equatable {
- func endsWith(_ item: Item) -> Bool {
- return count >= 1 && self[count-1] == item
- }
- }
在使用上下文where
子句的该示例的版本中,average()
和的实现endsWith(_:)
都在同一扩展中,因为每种方法的通用where
子句都说明了使该方法可用时需要满足的要求。将这些需求移至扩展的通用where
子句可使这些方法在相同情况下可用,但每个需求都需要一个扩展。
具有通用Where子句的关联类型
您可以where
在关联的类型上包括通用子句。例如,假设您要创建一个Container
包含迭代器的版本,如Sequence
协议在标准库中使用的那样。这是您的写法:
- protocol Container {
- associatedtype Item
- mutating func append(_ item: Item)
- var count: Int { get }
- subscript(i: Int) -> Item { get }
- associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
- func makeIterator() -> Iterator
- }
上的泛型where
子句Iterator
要求迭代器必须遍历与容器项目相同的项目类型的元素,而不管迭代器的类型如何。该makeIterator()
函数提供对容器的迭代器的访问。
对于从另一个协议继承的协议,可以通过where
在协议声明中包含泛型子句来向继承的关联类型添加约束。例如,以下代码声明了ComparableContainer
必须Item
符合的协议Comparable
:
- protocol ComparableContainer: Container where Item: Comparable { }
通用下标
下标可以是通用的,并且可以包含通用where
子句。您在后的尖括号内写占位符类型名称subscript
,并where
在下标正文的左花括号前写一个通用子句。例如:
- extension Container {
- subscript<Indices: Sequence>(indices: Indices) -> [Item]
- where Indices.Iterator.Element == Int {
- var result = [Item]()
- for index in indices {
- result.append(self[index])
- }
- return result
- }
- }
该Container
协议的扩展添加了一个下标,该下标采用一个索引序列,并返回一个包含每个给定索引项的数组。该通用下标受以下约束:
Indices
尖括号中的通用参数必须是符合Sequence
标准库协议的类型。- 下标采用单个参数,
indices
它是该Indices
类型的实例。 - 泛型
where
子句要求序列的迭代器必须遍历type的元素Int
。这样可以确保序列中的索引与用于容器的索引具有相同的类型。
综上所述,这些约束意味着为indices
参数传递的值是整数序列。