为有牺牲多壮志,敢教日月换新天。

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

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

热烈欢迎,请直接点击!!!

进入博主App Store主页,下载使用各个作品!!!

注:博主将坚持每月上线一个新app!!!

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

泛型是Swift最强大的功能之一,许多Swift标准库都是使用泛型代码构建的。实际上,即使您没有意识到,您在整个《语言指南》中也一直在使用泛型例如,SwiftArrayDictionarytype都是通用集合。您可以创建一个保存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. }

In-Out Parameters中所述,此函数利用in-out参数交换aand的值b

swapTwoInts(_:_:)功能交换原值ba,和原来的值a进入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(_:_:)andswapTwoDoubles(_:_:)函数:

  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(_:_:)调用函数都会确定要使用的实际类型

泛型函数和非泛型函数之间的另一个区别是,在尖括号(中,泛型函数的名称(swapTwoValues(_:_:))后跟占位符类型名称T<T>)。方括号告诉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(_:_:)上面定义函数是受称为的通用函数启发的,该函数swap是Swift标准库的一部分,可自动在您的应用程序中使用。如果您需要swapTwoValues(_:_:)在自己的代码中使用函数的行为,则可以使用Swift的现有swap(_:_:)函数,而不必提供自己的实现。

类型参数

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

一旦指定了类型参数,就可以使用它来定义函数参数的类型(例如函数的ab参数swapTwoValues(_:_:)),或者作为函数的返回类型,或者作为函数体内的类型注释。在每种情况下,每当调用函数时,type参数都将替换为实际类型。(在swapTwoValues(_:_:)上面的例子中,T被替换Int的第一次调用函数,并与被替换String,它被称为第二时间)。

通过在尖括号中用逗号分隔多个类型参数名称,可以提供多个类型参数。

命名类型参数

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

笔记

始终为类型参数提供驼峰式的大写名称(例如TMyTypeParameter),以表明它们是类型的占位符,而不是值。

通用类型

除了通用函数,Swift还使您能够定义自己的通用类型这些是可以与任何类型一起使用的自定义类,结构和枚举,类似于Array和的方式Dictionary

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

笔记

UINavigationController该类使用堆栈的概念在其导航层次结构中对视图控制器进行建模。您调用UINavigationControllerclasspushViewController(_: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()方法返回的值将是类型的值Element

由于它是通用类型,Stack因此可以用来在Swift中创建任何有效类型的堆栈,类似于Array和的方式Dictionary

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协议的信息,请参阅《符合哈希协议》

您可以在创建自定义泛型类型时定义自己的类型约束,这些约束提供了泛型编程的许多功能。诸如抽象概念之类的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中的每种类型都可以与等于运算符(进行比较例如,如果您创建自己的类或结构来表示复杂的数据模型,则该类或结构的“等于”的含义不是Swift可以为您猜测的。因此,无法保证此代码适用于每种可能的类型,并且在尝试编译代码时会报告相应的错误。if value == valueToFind==T

但是,一切并没有丢失。Swift标准库定义了一个称为的协议Equatable,该协议需要任何符合条件的类型来实现等于操作符(==)和不等于操作符(!=),以比较该类型的任何两个值。Swift的所有标准类型都自动支持该Equatable协议。

EquatablefindIndex(of:in:)函数可以安全地使用任何类型,因为可以保证支持equal运算符。为了表达这一事实,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,称为该协议没有定义什么-信息留给任何符合条件的类型提供。尽管如此,别名还是提供了一种方法来引用中的项目类型,并定义与该方法和下标一起使用的类型,以确保强制实施任何行为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可以推断出Element适合作为Item此特定容器使用的适当类型

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

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

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

  1. extension Array: Container {}

数组的现有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

在上面的示例中,Suffixfor关联类型Stack也是Stack,因此on的后缀操作Stack返回another 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. }

通用条款

Type Constraints中所述,类型约束使您可以定义与通用函数,下标或类型关联的类型参数的要求。

定义关联类型的要求也很有用。您可以通过定义泛型where子句来做到这一点通用where子句使您可以要求关联类型必须符合某种协议,或者某些类型参数和关联类型必须相同。泛型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和的参数anotherContainersomeContainer参数是类型C1,以及anotherContainer参数的类型的C2两个C1C2是当函数调用以确定两种类型的容器类型参数。

对函数的两个类型参数有以下要求:

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

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

这些要求意味着:

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

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

这些要求使该allItemsMatch(_:_:)功能可以比较两个容器,即使它们是不同的容器类型也是如此。

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

进行此检查后,该函数someContainer使用for-in循环和半开范围运算符(..<遍历所有项对于每个项目,该函数都会检查from中的项目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并不要求其项是相等的,因此使用==操作符会导致编译时错误。使用泛型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."

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

  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结构是通用的,where下面示例中子句指定必须满足哪些类型约束才能使这些新方法在容器上可用。

  1. extension Container {
  2. func average() -> Double where Item == Int {
  3. var sum = 0.0
  4. for index in 0..<count {
  5. sum += Double(self[index])
  6. }
  7. return sum / Double(count)
  8. }
  9. func endsWith(_ item: Item) -> Bool where Item: Equatable {
  10. return count >= 1 && self[count-1] == item
  11. }
  12. }
  13. let numbers = [1260, 1200, 98, 37]
  14. print(numbers.average())
  15. // Prints "648.75"
  16. print(numbers.endsWith(37))
  17. // Prints "true"

此示例在项为整数时向添加一个方法,而当项为等值时向该示例添加一个average()方法这两个函数都包含一个通用子句,该子句将类型约束从的原始声明添加到通用类型参数ContainerendsWith(_:)whereItemContainer

如果要在不使用上下文where子句的情况下编写此代码,请编写两个扩展,每个通用where子句一个。上面的示例和下面的示例具有相同的行为。

  1. extension Container where Item == Int {
  2. func average() -> Double {
  3. var sum = 0.0
  4. for index in 0..<count {
  5. sum += Double(self[index])
  6. }
  7. return sum / Double(count)
  8. }
  9. }
  10. extension Container where Item: Equatable {
  11. func endsWith(_ item: Item) -> Bool {
  12. return count >= 1 && self[count-1] == item
  13. }
  14. }

在使用上下文where子句的该示例的版本中average()的实现endsWith(_:)都在同一扩展中,因为每种方法的通用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在协议声明中包含泛型子句来向继承的关联类型添加约束例如,以下代码声明了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子句要求序列的迭代器必须遍历type的元素Int这样可以确保序列中的索引与用于容器的索引具有相同的类型。

综上所述,这些约束意味着为indices参数传递的值是整数序列。

posted @ 2018-10-03 15:11  为敢技术  阅读(601)  评论(0编辑  收藏  举报