• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

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

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(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(_:)方法从导航堆栈中删除(或弹出)视图控制器。每当您需要严格的“后进先出”方法来管理集合时,堆栈都是有用的集合模型。

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

  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将这四个值压入堆栈后样子:

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

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

弹出顶部值后,堆栈的外观如下:

扩展通用类型

扩展通用类型时,不提供类型参数列表作为扩展定义的一部分。而是在扩展的正文中提供原始类型定义的类型参数列表,并且原始类型参数名称用于引用原始定义的类型参数。

以下示例扩展了泛型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,容器的


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap