★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ ➤微信公众号:山青咏芝(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标准库都是使用泛型代码构建的。实际上,即使您没有意识到,您在整个《语言指南》中也一直在使用泛型。例如,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 ,容器的
|
请发表评论