美文网首页
22 Generics 泛型

22 Generics 泛型

作者: 微笑中的你 | 来源:发表于2019-08-21 20:46 被阅读0次

    泛型代码使您能够编写灵活的、可重用的函数和类型,这些函数和类型可以使用任何类型,取决于您定义的需求。您可以编写避免重复的代码,并以清晰、抽象的方式表达其意图。

    泛型是Swift最强大的特性之一,而且大部分Swift标准库都是用泛型代码构建的。事实上,您在整个语言指南中都使用了泛型,即使您没有意识到这一点。例如,Swift的数组和字典类型都是泛型集合。您可以创建一个包含Int值的数组,或者一个包含String值的数组,或者一个可以用Swift创建的任何其他类型的数组。类似地,您可以创建一个字典来存储任何指定类型的值,并且对于该类型可以是什么没有限制。

    泛型解决的问题

    下面是一个标准的没有泛型的函数

    func swapTwoInts(_ a: inout Int, _ b: inout Int) {
        let temporaryA = a
        a = b
        b = temporaryA
    }
    

    这个函数是,交换两个Int型数据,那么如果是其他类型的,我们要写相同交换方法 n 个。累死了!!!

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

    泛型函数

    下面是一个标准的泛型函数 swapTwoValues(::)

    func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
        let temporaryA = a
        a = b
        b = temporaryA
    }
    

    现在可以使用这个泛型函数交换两个字符串的值了。

    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是类型参数的一个示例。类型参数指定并命名占位符类型,并在函数名称之后立即在一对匹配的尖括号(如)之间写入。

    一旦指定了类型参数,就可以使用它定义函数参数的类型(例如swapTwoValues(::)函数的a和b参数),或者作为函数的返回类型,或者作为函数体中的类型注释。在每种情况下,每当调用函数时,类型参数都会替换为实际类型。(在上面的swapTwoValues(::)示例中,第一次调用函数时用Int替换T,第二次调用时用String替换T)。

    您可以通过在尖括号中编写多个类型参数名(以逗号分隔)来提供多个类型参数。

    泛型

    swift 允许你自定义泛型类型,可以是任何类型。

    本节向您展示如何编写名为Stack的泛型集合类型。堆栈是一组有序的值,类似于数组,但其操作集比Swift的数组类型更受限制。数组允许在数组中的任何位置插入和删除新项。然而,堆栈只允许将新项附加到集合的末尾(称为将新值推入堆栈)。类似地,堆栈只允许从集合的末尾删除项(称为从堆栈中弹出值)。

    UINavigationController类使用堆栈的概念来为其导航层次结构中的视图控制器建模。您可以调用UINavigationController类pushViewController(:animated:)方法将视图控制器添加(或推送)到导航堆栈上,并调用它的popViewControllerAnimated(:)方法从导航堆栈中删除(或弹出)视图控制器。当您需要严格的“后进先出”方法来管理集合时,堆栈是一个有用的集合模型。

    下图显示了栈的push和pop行为:

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

    这里是如何编写堆栈的非通用版本,在这种情况下,堆栈的Int值:

    struct IntStack {
        var items = [Int]()
        mutating func push(_ item: Int) {
            items.append(item)
        }
        mutating func pop() -> Int {
            return items.removeLast()
        }
    }
    

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

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

    struct Stack<Element> {
        var items = [Element]()
        mutating func push(_ item: Element) {
            items.append(item)
        }
        mutating func pop() -> Element {
            return items.removeLast()
        }
    }
    

    注意,Stack的泛型版本与非泛型版本本质上是相同的,但是类型参数是Element,而不是Int的实际类型。

    元素为稍后提供的类型定义占位符名称。这种未来类型可以在结构定义的任何位置作为元素引用。在本例中,元素被用作三个位置的占位符:

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

    因为它是一个泛型类型,所以可以使用Stack在Swift中创建任何有效类型的堆栈,方法类似于Array和Dictionary。

    你可以通过编写要存储在尖括号中的类型,可以创建一个新的堆栈实例。例如,要创建一个新的字符串堆栈,可以编写堆栈():

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

    下面是stackofstring将这四个值放入堆栈后的样子:


    stackPushedFourStrings_2x.png

    弹出一个值从堆栈删除并返回顶部的值,“cuatro”:

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

    然后结果如下:


    stackPoppedOneString_2x.png

    扩展泛型类型

    当扩展泛型类型时,不提供类型参数列表作为扩展定义的一部分。相反,扩展体中可以使用来自原始类型定义的类型参数列表,并且原始类型参数名称用于引用来自原始定义的类型参数。

    下面的例子扩展了通用堆栈类型,添加了一个只读的计算属性topItem,它返回堆栈上的顶项,而不从堆栈中弹出它:

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

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

    注意,这个扩展没有定义类型参数列表。相反,在扩展中使用堆栈类型的现有类型参数名称Element来指示计算属性topItem的可选类型。

    计算属性topItem现在可以与任何堆栈实例一起使用,在不删除它的情况下访问和查询它的栈顶项。

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

    泛型类型的扩展还可以包括扩展类型的实例必须满足的需求,以便获得新功能,如下面的泛型Where子句的扩展中所述。

    类型约束

    swapTwoValues(::)函数和堆栈类型可以使用任何类型。然而,有时对可与泛型函数和泛型类型一起使用的类型强制某些类型约束是有用的。类型约束指定类型参数必须继承自特定类,或符合特定协议或协议组合。

    例如,Swift的字典类型对可以用作字典键的类型设置了限制。正如字典中所描述的,字典的键的类型必须是可替换的。也就是说,它必须提供一种方法使自己具有独特的代表性。Dictionary需要它的键是可hashable的,这样它就可以检查它是否已经包含了特定键的值。如果没有这个要求,Dictionary就不能告诉它是否应该插入或替换特定键的值,也不能找到字典中已经存在的给定键的值。

    这一要求是通过对Dictionary键类型的类型约束来实现的,它指定键类型必须符合Hashable协议,这是Swift标准库中定义的一种特殊协议。所有Swift的基本类型(如String、Int、Double和Bool)在默认情况下都是可hashable的。

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

    类型约束语法

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

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

    上面的假设函数有两个类型参数。第一个类型参数T有一个类型约束,要求T是某个类的子类。第二个类型参数U有一个类型约束,要求U遵守协议SomeProtocol。

    类型约束作用

    这是一个名为findIndex(ofString:in:)的非泛型函数,它被赋予一个要查找的字符串值和一个字符串值数组,可以在其中找到它。函数的作用是:返回一个可选的Int值,如果找到了数组中第一个匹配的字符串,它将作为索引;

    func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
        for (index, value) in array.enumerated() {
            if value == valueToFind {
                return index
            }
        }
        return nil
    }
    

    调用

    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
    }
    

    上面写的这个函数不能编译。问题在于相等性检查,“if value == valueToFind”。并不是Swift中的所有类型都可以与等号操作符(==)进行比较。例如,如果您创建了自己的类或结构来表示一个复杂的数据模型,那么对于该类或结构来说,“等于”的含义是Swift无法为您猜到的。因此,不可能保证此代码适用于所有可能的类型T,并且在尝试编译代码时报告了一个适当的错误。

    然而,并非一切都已失去。Swift标准库定义了一个名为Equatable的协议,它要求任何符合条件的类型实现等号操作符(==)和not等号操作符(!=)来比较该类型的任意两个值。Swift的所有标准类型都自动支持Equatable协议。

    任何遵循Equatable协议的类型都可以安全地与findIndex(of:in:)函数一起使用,因为它保证支持equal to操作符。为了表达这个事实,当你定义函数时,你写一个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
    }
    

    现在编译成功,可以用于任何类型的等式,如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
    

    关联类型 Associated Types

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

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

    protocol Container {
        associatedtype Item
        mutating func append(_ item: Item)
        var count: Int { get }
        subscript(i: Int) -> Item { get }
    }
    

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

    • 必须能够使用append(_:)方法向容器添加新项。
    • 必须能够通过返回Int值的count属性访问容器中项的计数。
    • 必须能够使用下标检索容器中的每一项,下标接受Int索引值。

    该协议没有指定容器中的项应该如何存储或允许存储什么类型的项。协议只指定任何类型必须提供的三个功能位,以便将其视为容器。符合标准的类型可以提供额外的功能,只要它满足这三个需求。

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

    要定义这些需求,容器协议需要一种方法来引用容器将包含的元素的类型,而不需要知道特定容器的类型是什么。容器协议需要指定传递给append(_:)方法的任何值必须具有与容器元素类型相同的类型,并且容器下标返回的值将具有与容器元素类型相同的类型。

    为了实现这一点,容器协议声明了一个名为Item的关联类型,它被写成associatedtype Item。协议没有定义什么项——任何符合要求的类型都可以提供这些信息。尽管如此,项目别名提供了一种方法来引用容器中项目的类型,并定义一个用于append(_:)方法和下标的类型,以确保强制执行任何容器的预期行为。

    下面是来自上面泛型类型的非泛型IntStack类型的一个版本,适用于符合容器协议:

    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类型实现了容器协议的所有三个需求,并且在每种情况下都封装了IntStack类型现有功能的一部分来满足这些需求。

    此外,IntStack指定对于容器的这个实现,使用的合适的项是Int类型,typealias Item = Int的定义将抽象的项类型转换为容器协议的这个实现的具体的Int类型。

    由于Swift的类型推断,实际上不需要将Int的具体项声明为IntStack定义的一部分。由于IntStack符合容器协议的所有要求,Swift只需查看append(_:)方法的Item参数的类型和下标的返回类型,就可以推断出要使用的适当项。实际上,如果您从上面的代码中删除typealias Item = Int行,那么一切仍然可以工作,因为对于Item应该使用什么类型是很清楚的。

    你也可以使用泛型类型遵循该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]
        }
    }
    

    这一次,类型参数元素用作append(_:)方法的项参数的类型和下标的返回类型。因此,Swift可以推断元素是作为该特定容器项使用的适当类型。

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

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

    Swift的数组类型已经提供了一个append(_:)方法、一个count属性和一个下标,下标带有一个Int索引来检索它的元素。这三个功能符合容器协议的要求。这意味着只需声明Array采用该协议,就可以扩展Array以符合容器协议。您可以使用一个空扩展来实现这一点,正如在声明使用扩展采用协议时所述:

    Array现有的append(_:)方法和下标使Swift能够推断出要为Item使用的适当类型,就像上面的通用堆栈类型一样。定义此扩展之后,可以使用任何数组作为容器。

    给关联类型添加类型约束

    您可以向协议中的关联类型添加类型约束,以要求符合标准的类型满足这些约束。例如,下面的代码定义了容器的一个版本,该版本要求容器中的项是相等的。

    protocol Container {
        associatedtype Item: Equatable
        mutating func append(_ item: Item)
        var count: Int { get }
        subscript(i: Int) -> Item { get }
    }
    

    为了符合这个版本的容器,容器的项类型必须符合Equatable协议。

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

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

    在这个协议中,Suffix 是一个关联类型,就像上面容器示例中的项类型一样。Suffix 有两个约束:它必须符合Suffixablecontainer协议(目前正在定义的协议),并且它的项类型必须与容器的项类型相同。Item上的约束是一个泛型where子句,将在下面的泛型where子句的关联类型中讨论。

    下面是上面泛型类型的堆栈类型扩展,它增加了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
    

    在上面的示例中,Stack的后缀关联类型也是Stack,因此Stack上的后缀操作返回另一个堆栈。另外,符合SuffixableContainer的类型可以具有不同于自身的后缀类型——这意味着后缀操作可以返回不同的类型。例如,这里有一个非泛型IntStack类型的扩展,添加了SuffixableContainer一致性,使用Stack作为后缀类型,而不是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子句来实现这一点。泛型where子句允许您要求关联类型必须符合某个协议,或者某些类型参数和关联类型必须相同。泛型where子句以where关键字开始,然后是关联类型的约束或类型与关联类型之间的相等关系。在类型或函数主体的左大括号前编写一个泛型where子句。

    下面的示例定义了一个名为allItemsMatch的泛型函数,该函数检查两个容器实例是否以相同的顺序包含相同的项。如果所有项匹配,函数返回一个布尔值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必须符合容器协议(写为C1: Container)。
    • C2还必须符合容器协议(写为C2: Container)。
    • C1的项必须与C2的项相同(C1.Item == C2.Item)。
    • C1项必须符合Equatable协议(C1.Item: Equatable)。

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

    使用函数

    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."
    

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

    泛型Where 语句的扩展

    还可以使用通用where子句作为扩展的一部分。下面的示例扩展了前面示例中的通用堆栈结构,添加了一个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(:)的实现使用==操作符,但是堆栈的定义并不要求它的项是相等的,因此使用==操作符会导致编译时错误。使用泛型where子句可以向扩展添加新需求,因此只有当堆栈中的项相等时,扩展才会添加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子句。下面的示例扩展了前面示例中的容器协议,添加了一个startsWith(_:)方法。

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

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

    if [9, 9, 9].startsWith(42) {
        print("Starts with 42.")
    } else {
        print("Starts with something else.")
    }
    // Prints "Starts with something else."
    

    上面示例中的泛型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"
    

    这个例子向项类型为Double的容器添加了一个average()方法。它遍历容器中的项并将它们相加,然后除以容器的计数来计算平均值。它显式地将计数从Int转换为Double,以便能够进行浮点除法。

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

    与泛型Where子句关联的类型

    可以在关联类型上包含泛型where子句。例如,假设您想要创建一个包含迭代器的容器版本,就像序列协议在标准库中使用的那样。你可以这样写:

    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子句要求迭代器必须遍历与容器项类型相同的项类型的元素,而不管迭代器的类型如何。函数的作用是:提供对容器迭代器的访问。

    对于从另一个协议继承的协议,通过在协议声明中包含泛型where子句,可以向继承的关联类型添加约束。例如,下面的代码声明了一个ComparableContainer协议,该协议要求项目符合Comparable:

    protocol ComparableContainer: Container where Item: Comparable { }
    
    

    泛型下标 Subscripts

    下标可以是泛型的,它们可以包含泛型where子句。您可以在下标后的尖括号内编写占位符类型名称,并在下标主体的左大括号前编写一个泛型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
        }
    }
    

    容器协议的这个扩展添加了一个下标,它接受一系列索引,并返回一个数组,其中包含每个给定索引上的项。这个通用下标的约束条件如下:

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

    总的来说,这些约束意味着为索引参数传递的值是一个整数序列。

    <<返回目录

    相关文章

      网友评论

          本文标题:22 Generics 泛型

          本文链接:https://www.haomeiwen.com/subject/tfxlcctx.html