Swift5.1 语言指南(二十四) 泛型

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9739833.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

通用代码使您能够根据您定义的要求编写可以使用任何类型的灵活,可重用的函数和类型。您可以编写避免重复的代码,并以清晰,抽象的方式表达其意图。

泛型是Swift最强大的功能之一,Swift标准库的大部分内容都是使用通用代码构建的。事实上,即使你没有意识到,你也一直在语言指南中使用泛型。例如,Swift ArrayDictionary类型都是通用集合。您可以创建一个包含Int值的数组,或一个包含值的数组,或者可以创建一个String可以在Swift中创建的任何其他类型的数组。同样,您可以创建一个字典来存储任何指定类型的值,并且对该类型的限制没有限制。

泛型解决的问题

这是一个标准的非泛型函数swapTwoInts(_:_:),它可以交换两个Int值:

  1. func swapTwoInts(_ a: inout Int, _ b: inout Int) {
  2. let temporaryA = a
  3. a = b
  4. b = temporaryA
  5. }

该功能利用了在出参数交换的值ab,如在描述的In-Out参数

swapTwoInts(_:_:)函数交换binto a的原始值和ainto 的原始值b。您可以调用此函数来交换两个Int变量中的值:

  1. var someInt = 3
  2. var anotherInt = 107
  3. swapTwoInts(&someInt, &anotherInt)
  4. print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
  5. // Prints "someInt is now 107, and anotherInt is now 3"

swapTwoInts(_:_:)函数很有用,但它只能与Int值一起使用。如果要交换两个String值或两个Double值,则必须编写更多函数,例如下面显示的swapTwoStrings(_:_:)swapTwoDoubles(_:_:)函数:

  1. func swapTwoStrings(_ a: inout String, _ b: inout String) {
  2. let temporaryA = a
  3. a = b
  4. b = temporaryA
  5. }
  6. func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
  7. let temporaryA = a
  8. a = b
  9. b = temporaryA
  10. }

您可能已经注意到的尸体swapTwoInts(_:_:)swapTwoStrings(_:_:)swapTwoDoubles(_:_:)功能是相同的。唯一的区别是该值的,他们接受的类型(IntString,和Double)。

编写一个交换任何类型的两个值的单个函数更有用,也更灵活。通用代码使您可以编写这样的函数。(这些功能的通用版本定义如下。)

注意

在所有三个函数中,类型ab必须相同。如果ab不是同一类型,则无法交换它们的值。Swift是一种类型安全的语言,并且不允许(例如)类型String的变量和类型的变量Double来相互交换值。尝试这样做会导致编译时错误。

通用函数

通用函数可以使用任何类型。这是swapTwoInts(_:_:)上面函数的通用版本,称为swapTwoValues(_:_:)

  1. func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
  2. let temporaryA = a
  3. a = b
  4. b = temporaryA
  5. }

swapTwoValues(_:_:)函数体与函数体相同swapTwoInts(_:_:)。但是,第一行与swapTwoValues(_:_:)略有不同swapTwoInts(_:_:)。以下是第一行比较的方式:

  1. func swapTwoInts(_ a: inout Int, _ b: inout Int)
  2. func swapTwoValues<T>(_ a: inout T, _ b: inout T)

该函数的通用版本使用占位符的类型名(称为T,在这种情况下),而不是一个实际的类型名称(例如IntStringDouble)。占位符类型名字就不说了什么什么T必须的,但它确实说,双方ab必须是同一类型的T,不管T代表。T每次swapTwoValues(_:_:)调用函数时都会确定要使用的实际类型。

泛型函数和非泛型函数之间的另一个区别是泛型函数的name(swapTwoValues(_:_:))后面是T尖括号(<T>)内的占位符类型name ()。括号告诉Swift,它TswapTwoValues(_:_:)函数定义中的占位符类型名称。因为T是占位符,Swift不会查找名为的实际类型T

swapTwoValues(_:_:)现在可以以相同的方式调用该函数swapTwoInts,除了它可以传递任何类型的两个值,只要这两个值都是彼此相同的类型。每次swapTwoValues(_:_:)调用时,要使用的类型T都是从传递给函数的值类型推断出来的。

在下面的两个例子中,T被推断为IntString分别为:

  1. var someInt = 3
  2. var anotherInt = 107
  3. swapTwoValues(&someInt, &anotherInt)
  4. // someInt is now 107, and anotherInt is now 3
  5. var someString = "hello"
  6. var anotherString = "world"
  7. swapTwoValues(&someString, &anotherString)
  8. // someString is now "world", and anotherString is now "hello"

注意

swapTwoValues(_:_:)上面定义的函数的灵感来自一个称为swapSwift标准库的通用函数,它可以自动供您在应用程序中使用。如果您需要swapTwoValues(_:_:)在自己的代码中使用函数的行为,则可以使用Swift的现有swap(_:_:)函数,而不是提供自己的实现。

类型参数

swapTwoValues(_:_:)上面的示例中,占位符类型T类型参数的示例。类型参数指定并命名占位符类型,并在函数名称后面立即写入一对匹配的尖括号(例如<T>)之间。

一旦您指定一个类型参数,你可以用它来定义一个函数的参数(如类型a,并b在参数swapTwoValues(_:_:)功能),或作为函数的返回类型,或者作为函数体中的一个类型的注释。在每种情况下,只要调用函数,就会用实际类型替换type参数。(在swapTwoValues(_:_:)上面的例子中,T被替换Int的第一次调用函数,并与被替换String,它被称为第二时间)。

您可以通过在尖括号内写入多个类型参数名称来提供多个类型参数,以逗号分隔。

命名类型参数

在大多数情况下,类型参数具有描述性名称,例如KeyValuein 和in ,它告诉读者类型参数与其使用的泛型类型或函数之间的关系。但是,当它们之间没有有意义的关系时,这是传统的给它们命名使用单个字母,例如,和,如在上述功能。Dictionary<Key, Value>ElementArray<Element>TUVTswapTwoValues(_:_:)

注意

始终给出类型参数上面的驼峰案例名称(例如TMyTypeParameter),以表明它们是类型的占位符,而不是值。

通用类型

除了泛型函数,Swift还允许您定义自己的泛型类型。这些是可以使用任何类型的自定义类,结构和枚举,方式与Array和类似Dictionary

本节介绍如何编写名为的泛型集合类型Stack。堆栈是一组有序的值,类似于数组,但具有比Swift Array类型更受限制的操作集。数组允许在数组中的任何位置插入和删除新项。但是,堆栈允许将新项目仅附加到集合的末尾(称为新值送到堆栈)。类似地,堆栈允许仅从集合的末尾移除项目(称为从堆栈中弹出值)。

注意

类的概念由UINavigationController类用于在其导航层次结构中对视图控制器进行建模。您可以调用UINavigationControllerpushViewController(_:animated:)方法将视图控制器添加(或推送)到导航堆栈,以及从导航堆栈popViewControllerAnimated(_:)中删除(或弹出)视图控制器的方法。只要您需要严格的“后进先出”方法来管理集合,堆栈就是一个有用的集合模型。

下图显示了堆栈的推送和弹出行为:

../_images/stackPushPop_2x.png
  1. 堆栈上目前有三个值。
  2. 第四个值被推到堆栈顶部。
  3. 堆栈现在包含四个值,最新的一个位于顶部。
  4. 弹出堆栈中的顶部项目。
  5. 弹出一个值后,堆栈再次保存三个值。

以下是如何编写堆栈的非泛型版本,在本例中为一堆Int值:

  1. struct IntStack {
  2. var items = [Int]()
  3. mutating func push(_ item: Int) {
  4. items.append(item)
  5. }
  6. mutating func pop() -> Int {
  7. return items.removeLast()
  8. }
  9. }

此结构使用一个Array名为items将值存储在堆栈中的属性。Stack提供了两种方法,push以及pop在堆栈上下推送和弹出值。这些方法被标记为mutating,因为它们需要修改(或改变)结构的items数组。

但是,IntStack上面显示的类型只能与Int值一起使用。定义一个可以管理任何类型值的堆栈的泛型 Stack类会更有用。

这是相同代码的通用版本:

  1. struct Stack<Element> {
  2. var items = [Element]()
  3. mutating func push(_ item: Element) {
  4. items.append(item)
  5. }
  6. mutating func pop() -> Element {
  7. return items.removeLast()
  8. }
  9. }

请注意泛型版本Stack与非泛型版本的基本相同,但是调用的是类型参数Element而不是实际类型Int。此类型参数写在<Element>结构名称后面的一对尖括号()中。

Element为稍后提供的类型定义占位符名称。这种未来类型可以称为Element结构定义中的任何位置。在这种情况下,Element在三个地方用作占位符:

  • 创建一个名为的属性items,该属性使用类型的空数组值初始化Element
  • 指定该push(_:)方法具有一个名为的参数item,该参数必须是类型Element
  • 指定pop()方法返回的值将是type的值Element

因为它是一个通用型,Stack可用于创建一叠任何斯威夫特有效的类型,以类似的方式来ArrayDictionary

您可以Stack通过在尖括号内写入要存储在堆栈中的类型来创建新实例。例如,要创建新的字符串堆栈,请编写Stack<String>()

  1. var stackOfStrings = Stack<String>()
  2. stackOfStrings.push("uno")
  3. stackOfStrings.push("dos")
  4. stackOfStrings.push("tres")
  5. stackOfStrings.push("cuatro")
  6. // the stack now contains 4 strings

以下是stackOfStrings将这四个值推送到堆栈后的方式:

../_images/stackPushedFourStrings_2x.png

从堆栈中弹出一个值会删除并返回最高值,"cuatro"

  1. let fromTheTop = stackOfStrings.pop()
  2. // fromTheTop is equal to "cuatro", and the stack now contains 3 strings

这是堆栈弹出其最高值后的样子:

../_images/stackPoppedOneString_2x.png

扩展通用类型

扩展泛型类型时,不提供类型参数列表作为扩展定义的一部分。相反,原始类型定义中的类型参数列表在扩展的主体内可用,原始类型参数名称用于引用原始定义中的类型参数。

下面的示例扩展泛型Stack类型以添加​​一个只读的计算属性topItem,该属性返回堆栈顶部项而不从堆栈中弹出它:

  1. extension Stack {
  2. var topItem: Element? {
  3. return items.isEmpty ? nil : items[items.count - 1]
  4. }
  5. }

topItem属性返回type的可选值Element。如果堆栈为空,则topItem返回nil; 如果堆栈不为空,则topItem返回items数组中的最后一项。

请注意,此扩展名未定义类型参数列表。而是在扩展中使用Stack类型的现有类型参数名称Element来指示topItem计算属性的可选类型。

现在,topItem计算属性可以与任何Stack实例一起使用,以访问和查询其顶级项而不删除它。

  1. if let topItem = stackOfStrings.topItem {
  2. print("The top item on the stack is \(topItem).")
  3. }
  4. // Prints "The top item on the stack is tres."

泛型类型的扩展还可以包括扩展类型的实例必须满足的要求才能获得新功能,如下面的扩展通用条款中所述。

键入约束

swapTwoValues(_:_:)功能和Stack类型可以与任何类型的工作。但是,对可以与泛型函数和泛型类型一起使用的类型强制执行某些类型约束有时很有用。类型约束指定类型参数必须从特定类继承,或符合特定协议或协议组合。

例如,Swift的Dictionary类型限制了可用作字典键的类型。如字典中所述,字典键的类型必须是可清除的。也就是说,它必须提供一种使自己具有唯一可表现性的方法。Dictionary需要其密钥可以清除,以便它可以检查它是否已包含特定键的值。如果没有此要求,则Dictionary无法判断是否应插入或替换特定键的值,也无法为字典中已有的给定键找到值。

此要求由密钥类型的类型约束强制执行,该类型约束Dictionary指定密钥类型必须符合Hashable协议,即Swift标准库中定义的特殊协议。所有斯威夫特的基本类型(例如StringIntDouble,和Bool)默认情况下可哈希。

您可以在创建自定义泛型类型时定义自己的类型约束,这些约束提供了泛型编程的大部分功能。抽象概念就像Hashable根据概念特征而不是具体类型来描述类型。

键入约束语法

您可以通过在类型参数的名称后面放置单个类或协议约束来写入类型约束,并以冒号分隔,作为类型参数列表的一部分。下面显示了泛型函数的类型约束的基本语法(尽管泛型类型的语法相同):

  1. func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
  2. // function body goes here
  3. }

上面的假设函数有两个类型参数。第一个类型参数T有一个类型约束,它需要T是一个子类SomeClass。第二个类型参数U具有需要U符合协议的类型约束SomeProtocol

在行动中键入约束

这是一个非泛型函数findIndex(ofString:in:),它被赋予一个String值来查找和一个String值的数组,在其中可以找到它。该findIndex(ofString:in:)函数返回一个可选Int值,该值将是数组中第一个匹配字符串的索引(如果已找到),或者找不到nil该字符串:

  1. func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
  2. for (index, value) in array.enumerated() {
  3. if value == valueToFind {
  4. return index
  5. }
  6. }
  7. return nil
  8. }

findIndex(ofString:in:)函数可用于在字符串数组中查找字符串值:

  1. let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
  2. if let foundIndex = findIndex(ofString: "llama", in: strings) {
  3. print("The index of llama is \(foundIndex)")
  4. }
  5. // Prints "The index of llama is 2"

但是,在数组中查找值的索引的原则不仅对字符串有用。您可以通过用任何类型的值替换任何字符串的提及来编写与泛型函数相同的功能T

这里是你会如何期待的一个仿制版本findIndex(ofString:in:),叫findIndex(of:in:),被写入。请注意,此函数的返回类型仍然是Int?,因为该函数返回一个可选的索引号,而不是数组中的可选值。但请注意,此函数无法编译,原因如下所示:

  1. func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
  2. for (index, value) in array.enumerated() {
  3. if value == valueToFind {
  4. return index
  5. }
  6. }
  7. return nil
  8. }

此函数无法编译,如上所述。问题在于平等检查,“ ”。并非Swift中的每个类型都可以与等于operator()进行比较。例如,如果您创建自己的类或结构来表示复杂的数据模型,则该类或结构的“等于”的含义不是Swift可以为您猜测的。因此,无法保证此代码适用于所有可能的类型,并且在尝试编译代码时会报告相应的错误。if value == valueToFind==T

然而,一切都没有丢失。Swift标准库定义了一个名为的协议Equatable,它要求任何符合类型的实现等于operator(==)和不等于operator(!=)来比较该类型的任何两个值。Swift的所有标准类型都自动支持该Equatable协议。

任何类型都Equatable可以安全地使用该findIndex(of:in:)函数,因为它保证支持等于运算符。为了表达这一事实,Equatable在定义函数时,将类型约束写为类型参数定义的一部分:

  1. func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
  2. for (index, value) in array.enumerated() {
  3. if value == valueToFind {
  4. return index
  5. }
  6. }
  7. return nil
  8. }

单个类型参数findIndex(of:in:)写为,表示“ 符合协议的任何类型”。T: EquatableTEquatable

findIndex(of:in:)函数现在可以成功编译,并且可以与任何类型一起使用Equatable,例如:DoubleString

  1. let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
  2. // doubleIndex is an optional Int with no value, because 9.3 isn't in the array
  3. let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
  4. // stringIndex is an optional Int containing a value of 2

相关类型

在定义协议时,将一个或多个关联类型声明为协议定义的一部分有时很有用。一个相关联的类型给出了一个占位符名称到被用作协议的一部分的类型。在采用协议之前,不会指定用于该关联类型的实际类型。使用associatedtype关键字指定关联类型。

行动中的关联类型

这是一个名为的协议示例Container,它声明了一个名为的关联类型Item

  1. protocol Container {
  2. associatedtype Item
  3. mutating func append(_ item: Item)
  4. var count: Int { get }
  5. subscript(i: Int) -> Item { get }
  6. }

Container协议定义了任何容器必须提供的三个必需功能:

  • 必须可以使用方法将新项添加到容器中append(_:)
  • 必须可以通过count返回Int值的属性访问容器中项目的计数。
  • 必须能够使用带有Int索引值的下标检索容器中的每个项目。

此协议未指定应如何存储容器中的项目或允许它们使用的类型。该协议仅指定任何类型必须提供的三个功能位才能被视为a Container。符合类型可以提供附加功能,只要它满足这三个要求即可。

任何符合Container协议的类型都必须能够指定它存储的值的类型。具体来说,它必须确保只将正确类型的项添加到容器中,并且必须清楚其下标返回的项的类型。

要定义这些要求,Container协议需要一种方法来引用容器将容纳的元素的类型,而不知道特定容器的类型。该Container协议需要指定传递给任何值append(_:)方法必须具有相同的类型容器的元件的类型,以及通过所述容器的下标所返回的值将是相同的类型容器的元件的类型。

为此,Container协议声明了一个名为的相关类型Item,写为。该协议没有定义什么是 - 任何符合类型提供的信息。尽管如此,别名提供了一种方法来引用a中项目的类型,并定义与方法和下标一起使用的类型,以确保强制执行any的预期行为。associatedtype ItemItemItemContainerappend(_:)Container

这里的非泛型的版本IntStack从型通用类型的上方,适于符合Container协议:

  1. struct IntStack: Container {
  2. // original IntStack implementation
  3. var items = [Int]()
  4. mutating func push(_ item: Int) {
  5. items.append(item)
  6. }
  7. mutating func pop() -> Int {
  8. return items.removeLast()
  9. }
  10. // conformance to the Container protocol
  11. typealias Item = Int
  12. mutating func append(_ item: Int) {
  13. self.push(item)
  14. }
  15. var count: Int {
  16. return items.count
  17. }
  18. subscript(i: Int) -> Int {
  19. return items[i]
  20. }
  21. }

IntStack类型实现了Container协议的所有三个要求,并且在每种情况下都包含了该IntStack类型现有功能的一部分以满足这些要求。

而且,IntStack指定对于这个实现Container,适当Item使用是一种类型Int。将抽象类型转换为协议实现的具体类型的定义。typealias Item IntItemIntContainer

由于斯威夫特的类型推断,你实际上并不需要声明一个具体ItemInt作为定义的一部分IntStack。因为IntStack符合Container协议的所有要求,Swift可以Item简单地通过查看append(_:)方法的item参数类型和下标的返回类型来推断适当的使用。实际上,如果从上面的代码中删除该行,一切仍然有效,因为它清楚应该使用什么类型。typealias Item IntItem

您还可以使泛型Stack类型符合Container协议:

  1. struct Stack<Element>: Container {
  2. // original Stack<Element> implementation
  3. var items = [Element]()
  4. mutating func push(_ item: Element) {
  5. items.append(item)
  6. }
  7. mutating func pop() -> Element {
  8. return items.removeLast()
  9. }
  10. // conformance to the Container protocol
  11. mutating func append(_ item: Element) {
  12. self.push(item)
  13. }
  14. var count: Int {
  15. return items.count
  16. }
  17. subscript(i: Int) -> Element {
  18. return items[i]
  19. }
  20. }

这次,type参数Element用作append(_:)方法item参数的类型和下标的返回类型。因此,Swift可以推断出这ElementItem用作这个特定容器的合适类型。

扩展现有类型以指定关联类型

您可以扩展现有类型以添加协议的一致性,如添加协议与扩展的一致性中所述。这包括具有关联类型的协议。

Swift的Array类型已经提供了一个append(_:)方法,一个count属性和一个带有Int索引的下标来检索它的元素。这三种功能符合Container协议的要求。这意味着您可以通过声明采用协议来扩展Array以符合协议。您使用空扩展名执行此操作,如使用扩展名声明协议采用中所述:ContainerArray

  1. extension Array: Container {}

Array的现有append(_:)方法和下标使Swift能够推断出适用的类型Item,就像Stack上面的泛型类型一样。定义此扩展后,您可以使用任何Array作为Container

将约束添加到关联类型

您可以将类型约束添加到协议中的关联类型,以要求符合类型满足这些约束。例如,以下代码定义了一个版本,Container它要求容器中的项目是等同的。

  1. protocol Container {
  2. associatedtype Item: Equatable
  3. mutating func append(_ item: Item)
  4. var count: Int { get }
  5. subscript(i: Int) -> Item { get }
  6. }

要符合此版本Container,容器的Item类型必须符合Equatable协议。

在关联类型的约束中使用协议

协议可以作为其自身要求的一部分出现。例如,这是一个细化Container协议的协议,增加了suffix(_:)方法的要求。该suffix(_:)方法从容器的末尾返回给定数量的元素,并将它们存储在Suffix类型的实例中。

  1. protocol SuffixableContainer: Container {
  2. associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
  3. func suffix(_ size: Int) -> Suffix
  4. }

在此协议中,Suffix是一个关联类型,如上例中的Item类型ContainerSuffix有两个约束:它必须符合SuffixableContainer协议(当前正在定义的协议),其Item类型必须与容器的Item类型相同。约束Item是一个通用where子句,下面的通用Where子句的关联类型中讨论。

以下是上述通用类型Stack类型扩展,它增加了对协议的一致性:SuffixableContainer

  1. extension Stack: SuffixableContainer {
  2. func suffix(_ size: Int) -> Stack {
  3. var result = Stack()
  4. for index in (count-size)..<count {
  5. result.append(self[index])
  6. }
  7. return result
  8. }
  9. // Inferred that Suffix is Stack.
  10. }
  11. var stackOfInts = Stack<Int>()
  12. stackOfInts.append(10)
  13. stackOfInts.append(20)
  14. stackOfInts.append(30)
  15. let suffix = stackOfInts.suffix(2)
  16. // suffix contains 20 and 30

在上面的示例中,Suffix关联类型Stack也是Stack,因此后缀操作Stack返回另一个Stack。或者,符合的类型SuffixableContainer可以具有与其Suffix自身不同的类型 - 意味着后缀操作可以返回不同的类型。例如,这IntStack是添加SuffixableContainer一致性的非泛型类型的扩展,使用Stack<Int>后缀类型而不是IntStack

  1. extension IntStack: SuffixableContainer {
  2. func suffix(_ size: Int) -> Stack<Int> {
  3. var result = Stack<Int>()
  4. for index in (count-size)..<count {
  5. result.append(self[index])
  6. }
  7. return result
  8. }
  9. // Inferred that Suffix is Stack<Int>.
  10. }

通用条款

类型约束中描述的类型约束使您能够定义与泛型函数,下标或类型关联的类型参数的要求。

定义关联类型的要求也很有用。您可以通过定义通用where子句来完成此操作。通用where子句使您可以要求关联类型必须符合某个协议,或者某些类型参数和关联类型必须相同。generic where子句以where关键字开头,后跟关联类型的约束或类型和关联类型之间的相等关系。您where在类型或函数体的开始大括号之前编写一个泛型子句。

下面的示例定义了一个名为的通用函数allItemsMatch,它检查两个Container实例是否包含相同顺序的相同项。true如果所有项匹配,则该函数返回布尔值,如果不匹配,则返回值false

要检查的两个容器不必是相同类型的容器(尽管它们可以是),但它们必须保持相同类型的物品。此要求通过类型约束和通用where子句的组合表示:

  1. func allItemsMatch<C1: Container, C2: Container>
  2. (_ someContainer: C1, _ anotherContainer: C2) -> Bool
  3. where C1.Item == C2.Item, C1.Item: Equatable {
  4. // Check that both containers contain the same number of items.
  5. if someContainer.count != anotherContainer.count {
  6. return false
  7. }
  8. // Check each pair of items to see if they're equivalent.
  9. for i in 0..<someContainer.count {
  10. if someContainer[i] != anotherContainer[i] {
  11. return false
  12. }
  13. }
  14. // All items match, so return true.
  15. return true
  16. }

这个函数有两个叫做someContainer和的参数anotherContainer。该someContainer参数是类型C1,以及anotherContainer参数的类型的C2。两个C1C2都是调用函数时要确定的两种容器类型的类型参数。

以下要求放在函数的两个类型参数上:

  • C1必须符合Container协议(写为)。C1: Container
  • C2也必须符合Container协议(写作)。C2: Container
  • ItemC1必须相同ItemC2(写成)。C1.Item == C2.Item
  • Item用于C1必须符合Equatable协议(写为)。C1.Item: Equatable

第一个和第二个要求在函数的类型参数列表中定义,第三个和第四个要求在函数的generic where子句中定义。

这些要求意味着:

  • someContainer是一个类型的容器C1
  • anotherContainer是一个类型的容器C2
  • someContaineranotherContainer包含相同类型的项目。
  • someContainer可以使用不相等的运算符(!=)检查项目,以查看它们是否彼此不同。

第三和第四的要求相结合,意味着中的项目anotherContainer可以可以与检查!=经营者,因为他们是完全一样的类型中的项目someContainer

这些要求使allItemsMatch(_:_:)函数能够比较两个容器,即使它们是不同的容器类型。

allItemsMatch(_:_:)函数首先检查两个容器是否包含相同数量的项目。如果它们包含不同数量的项目,则它们无法匹配,并且函数将返回false

在进行此检查之后,该函数someContainer使用forin循环和半开放范围运算符(..<)迭代所有项目。对于每个项目,该函数检查项目someContainer是否不等于中的相应项目anotherContainer。如果两个项不相等,则两个容器不匹配,函数返回false

如果循环结束而没有找到不匹配,则两个容器匹配,函数返回true

以下是该allItemsMatch(_:_:)函数的实际运行方式:

  1. var stackOfStrings = Stack<String>()
  2. stackOfStrings.push("uno")
  3. stackOfStrings.push("dos")
  4. stackOfStrings.push("tres")
  5. var arrayOfStrings = ["uno", "dos", "tres"]
  6. if allItemsMatch(stackOfStrings, arrayOfStrings) {
  7. print("All items match.")
  8. } else {
  9. print("Not all items match.")
  10. }
  11. // Prints "All items match."

上面的示例创建了一个Stack存储String值的实例,并将三个字符串压入堆栈。该示例还创建了一个Array实例,该实例使用包含与堆栈相同的三个字符串的数组文字进行初始化。尽管堆栈和数组的类型不同,但它们都符合Container协议,并且都包含相同类型的值。因此,您可以allItemsMatch(_:_:)使用这两个容器作为参数调用该函数。在上面的示例中,allItemsMatch(_:_:)函数正确地报告两个容器中的所有项都匹配。

具有通用Where子句的扩展

您还可以使用泛型where子句作为扩展的一部分。下面的示例扩展了Stack前面示例中的通用结构以添加isTop(_:)方法。

  1. extension Stack where Element: Equatable {
  2. func isTop(_ item: Element) -> Bool {
  3. guard let topItem = items.last else {
  4. return false
  5. }
  6. return topItem == item
  7. }
  8. }

这个新isTop(_:)方法首先检查堆栈是否为空,然后将给定项目与堆栈的最顶层项目进行比较。如果您尝试在没有泛型where子句的情况下执行isTop(_:)==操作,则会出现问题:使用运算符的实现,但定义Stack不要求其项目是等同的,因此使用==运算符会导致编译时错误。使用generic where子句允许您向扩展添加新要求,以便扩展isTop(_:)仅在堆栈中的项目相等时才添加方法。

以下是该isTop(_:)方法的实际应用方式:

  1. if stackOfStrings.isTop("tres") {
  2. print("Top element is tres.")
  3. } else {
  4. print("Top element is something else.")
  5. }
  6. // Prints "Top element is tres."

如果尝试isTop(_:)在其元素不相等的堆栈上调用该方法,则会出现编译时错误。

  1. struct NotEquatable { }
  2. var notEquatableStack = Stack<NotEquatable>()
  3. let notEquatableValue = NotEquatable()
  4. notEquatableStack.push(notEquatableValue)
  5. notEquatableStack.isTop(notEquatableValue) // Error

您可以使用where带有协议扩展的泛型子句。下面的示例扩展了Container前面示例中的协议以添加startsWith(_:)方法。

  1. extension Container where Item: Equatable {
  2. func startsWith(_ item: Item) -> Bool {
  3. return count >= 1 && self[0] == item
  4. }
  5. }

startsWith(_:)方法首先确保容器至少有一个项目,然后检查容器中的第一个项目是否与给定项目匹配。只要容器的项目是等同的,这个新startsWith(_:)方法可以用于符合Container协议的任何类型,包括上面使用的堆栈和数组。

  1. if [9, 9, 9].startsWith(42) {
  2. print("Starts with 42.")
  3. } else {
  4. print("Starts with something else.")
  5. }
  6. // Prints "Starts with something else."

上例中的generic where子句要求Item符合协议,但您也可以编写where需要Item特定类型的通用子句。例如:

  1. extension Container where Item == Double {
  2. func average() -> Double {
  3. var sum = 0.0
  4. for index in 0..<count {
  5. sum += self[index]
  6. }
  7. return sum / Double(count)
  8. }
  9. }
  10. print([1260.0, 1200.0, 98.6, 37.0].average())
  11. // Prints "648.9"

此示例average()Item类型为的容器添加方法Double。它迭代容器中的项目以将它们相加,并除以容器的计数以计算平均值。它明确地将来自计数IntDouble能够做浮点除法。

您可以在where作为扩展的一部分的泛型子句中包含多个需求,就像您where在其他地方编写的泛型子句一样。使用逗号分隔列表中的每个需求。

具有通用Where子句的关联类型

您可以where在关联类型中包含通用子句。例如,假设您要创建Container包含迭代器的版本,就像Sequence协议在标准库中使用的那样。这是你写的:

  1. protocol Container {
  2. associatedtype Item
  3. mutating func append(_ item: Item)
  4. var count: Int { get }
  5. subscript(i: Int) -> Item { get }
  6. associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
  7. func makeIterator() -> Iterator
  8. }

泛型where子句Iterator要求迭代器必须遍历与容器项相同的项类型的元素,而不管迭代器的类型如何。该makeIterator()函数提供对容器迭代器的访问。

对于从其他协议继承的协议,通过where在协议声明中包含generic 子句,可以向继承的关联类型添加约束。例如,以下代码声明了一个ComparableContainer需要Item符合以下内容的协议Comparable

  1. protocol ComparableContainer: Container where Item: Comparable { }

通用下标

下标可以是通用的,也可以包含通用where子句。您在之后的尖括号内写入占位符类型名称subscript,并where在下标主体的左大括号前写一个通用子句。例如:

  1. extension Container {
  2. subscript<Indices: Sequence>(indices: Indices) -> [Item]
  3. where Indices.Iterator.Element == Int {
  4. var result = [Item]()
  5. for index in indices {
  6. result.append(self[index])
  7. }
  8. return result
  9. }
  10. }

Container协议的这个扩展添加了一个下标,它接受一系列索引并返回一个包含每个给定索引的项的数组。此通用下标受限制如下:

  • Indices尖括号中的泛型参数必须是符合Sequence标准库中的协议的类型。
  • 下标采用单个参数,indices该参数是该Indices类型的实例。
  • 泛型where子句要求序列的迭代器必须遍历类型的元素Int。这可确保序列中的索引与用于容器的索引的类型相同。

总之,这些约束意味着为indices参数传递的值是整数序列。

转载于:https://www.cnblogs.com/strengthen/p/9739833.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值