★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9739833.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
通用代码使您能够根据您定义的要求编写可以使用任何类型的灵活,可重用的函数和类型。您可以编写避免重复的代码,并以清晰,抽象的方式表达其意图。
泛型是Swift最强大的功能之一,Swift标准库的大部分内容都是使用通用代码构建的。事实上,即使你没有意识到,你也一直在语言指南中使用泛型。例如,Swift Array
和Dictionary
类型都是通用集合。您可以创建一个包含Int
值的数组,或一个包含值的数组,或者可以创建一个String
可以在Swift中创建的任何其他类型的数组。同样,您可以创建一个字典来存储任何指定类型的值,并且对该类型的限制没有限制。
泛型解决的问题
这是一个标准的非泛型函数swapTwoInts(_:_:)
,它可以交换两个Int
值:
- func swapTwoInts(_ a: inout Int, _ b: inout Int) {
- let temporaryA = a
- a = b
- b = temporaryA
- }
该功能利用了在出参数交换的值a
和b
,如在描述的In-Out参数。
该swapTwoInts(_:_:)
函数交换b
into a
的原始值和a
into 的原始值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(_:_:)
和swapTwoDoubles(_:_:)
函数:
- 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(_:_:)
调用函数时都会确定要使用的实际类型。
泛型函数和非泛型函数之间的另一个区别是泛型函数的name(swapTwoValues(_:_:)
)后面是T
尖括号(<T>
)内的占位符类型name ()。括号告诉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
是类型参数的示例。类型参数指定并命名占位符类型,并在函数名称后面立即写入一对匹配的尖括号(例如<T>
)之间。
一旦您指定一个类型参数,你可以用它来定义一个函数的参数(如类型a
,并b
在参数swapTwoValues(_:_:)
功能),或作为函数的返回类型,或者作为函数体中的一个类型的注释。在每种情况下,只要调用函数,就会用实际类型替换type参数。(在swapTwoValues(_:_:)
上面的例子中,T
被替换Int
的第一次调用函数,并与被替换String
,它被称为第二时间)。
您可以通过在尖括号内写入多个类型参数名称来提供多个类型参数,以逗号分隔。
命名类型参数
在大多数情况下,类型参数具有描述性名称,例如Key
和Value
in 和in ,它告诉读者类型参数与其使用的泛型类型或函数之间的关系。但是,当它们之间没有有意义的关系时,这是传统的给它们命名使用单个字母,例如,和,如在上述功能。Dictionary<Key, Value>
Element
Array<Element>
T
U
V
T
swapTwoValues(_:_:)
注意
始终给出类型参数上面的驼峰案例名称(例如T
和MyTypeParameter
),以表明它们是类型的占位符,而不是值。
通用类型
除了泛型函数,Swift还允许您定义自己的泛型类型。这些是可以使用任何类型的自定义类,结构和枚举,方式与Array
和类似Dictionary
。
本节介绍如何编写名为的泛型集合类型Stack
。堆栈是一组有序的值,类似于数组,但具有比Swift Array
类型更受限制的操作集。数组允许在数组中的任何位置插入和删除新项。但是,堆栈允许将新项目仅附加到集合的末尾(称为将新值推送到堆栈)。类似地,堆栈允许仅从集合的末尾移除项目(称为从堆栈中弹出值)。
注意
类的概念由UINavigationController
类用于在其导航层次结构中对视图控制器进行建模。您可以调用UINavigationController
类pushViewController(_:animated:)
方法将视图控制器添加(或推送)到导航堆栈,以及从导航堆栈popViewControllerAnimated(_:)
中删除(或弹出)视图控制器的方法。只要您需要严格的“后进先出”方法来管理集合,堆栈就是一个有用的集合模型。
下图显示了堆栈的推送和弹出行为:
![../_images/stackPushPop_2x.png](https://docs.swift.org/swift-book/_images/stackPushPop_2x.png)
- 堆栈上目前有三个值。
- 第四个值被推到堆栈顶部。
- 堆栈现在包含四个值,最新的一个位于顶部。
- 弹出堆栈中的顶部项目。
- 弹出一个值后,堆栈再次保存三个值。
以下是如何编写堆栈的非泛型版本,在本例中为一堆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()
方法返回的值将是type的值Element
因为它是一个通用型,Stack
可用于创建一叠任何斯威夫特有效的类型,以类似的方式来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
将这四个值推送到堆栈后的方式:
![../_images/stackPushedFourStrings_2x.png](https://docs.swift.org/swift-book/_images/stackPushedFourStrings_2x.png)
从堆栈中弹出一个值会删除并返回最高值,"cuatro"
:
- let fromTheTop = stackOfStrings.pop()
- // fromTheTop is equal to "cuatro", and the stack now contains 3 strings
这是堆栈弹出其最高值后的样子:
![../_images/stackPoppedOneString_2x.png](https://docs.swift.org/swift-book/_images/stackPoppedOneString_2x.png)
扩展通用类型
扩展泛型类型时,不提供类型参数列表作为扩展定义的一部分。相反,原始类型定义中的类型参数列表在扩展的主体内可用,原始类型参数名称用于引用原始定义中的类型参数。
下面的示例扩展泛型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
根据概念特征而不是具体类型来描述类型。
键入约束语法
您可以通过在类型参数的名称后面放置单个类或协议约束来写入类型约束,并以冒号分隔,作为类型参数列表的一部分。下面显示了泛型函数的类型约束的基本语法(尽管泛型类型的语法相同):
- 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中的每个类型都可以与等于operator()进行比较。例如,如果您创建自己的类或结构来表示复杂的数据模型,则该类或结构的“等于”的含义不是Swift可以为您猜测的。因此,无法保证此代码适用于所有可能的类型,并且在尝试编译代码时会报告相应的错误。if value == valueToFind
==
T
然而,一切都没有丢失。Swift标准库定义了一个名为的协议Equatable
,它要求任何符合类型的实现等于operator(==
)和不等于operator(!=
)来比较该类型的任何两个值。Swift的所有标准类型都自动支持该Equatable
协议。
任何类型都Equatable
可以安全地使用该findIndex(of:in:)
函数,因为它保证支持等于运算符。为了表达这一事实,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
,写为。该协议没有定义什么是 - 任何符合类型提供的信息。尽管如此,别名提供了一种方法来引用a中项目的类型,并定义与方法和下标一起使用的类型,以确保强制执行any的预期行为。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 {}
Array的现有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
关联类型Stack
也是Stack
,因此后缀操作Stack
返回另一个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>.
- }
通用条款
类型约束中描述的类型约束使您能够定义与泛型函数,下标或类型关联的类型参数的要求。
定义关联类型的要求也很有用。您可以通过定义通用where子句来完成此操作。通用where
子句使您可以要求关联类型必须符合某个协议,或者某些类型参数和关联类型必须相同。generic 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
第一个和第二个要求在函数的类型参数列表中定义,第三个和第四个要求在函数的generic where
子句中定义。
这些要求意味着:
someContainer
是一个类型的容器C1
。anotherContainer
是一个类型的容器C2
。someContainer
并anotherContainer
包含相同类型的项目。someContainer
可以使用不相等的运算符(!=
)检查项目,以查看它们是否彼此不同。
第三和第四的要求相结合,意味着中的项目anotherContainer
可以也可以与检查!=
经营者,因为他们是完全一样的类型中的项目someContainer
。
这些要求使allItemsMatch(_:_:)
函数能够比较两个容器,即使它们是不同的容器类型。
该allItemsMatch(_:_:)
函数首先检查两个容器是否包含相同数量的项目。如果它们包含不同数量的项目,则它们无法匹配,并且函数将返回false
。
在进行此检查之后,该函数someContainer
使用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"]
- 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
不要求其项目是等同的,因此使用==
运算符会导致编译时错误。使用generic 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."
上例中的generic where
子句要求Item
符合协议,但您也可以编写where
需要Item
特定类型的通用子句。例如:
- 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
包含迭代器的版本,就像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
在协议声明中包含generic 子句,可以向继承的关联类型添加约束。例如,以下代码声明了一个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
子句要求序列的迭代器必须遍历类型的元素Int
。这可确保序列中的索引与用于容器的索引的类型相同。
总之,这些约束意味着为indices
参数传递的值是整数序列。