22 Generics 泛型

泛型代碼使您能夠編寫靈活的、可重用的函數(shù)和類型,這些函數(shù)和類型可以使用任何類型,取決于您定義的需求。您可以編寫避免重復(fù)的代碼,并以清晰、抽象的方式表達(dá)其意圖。

泛型是Swift最強(qiáng)大的特性之一,而且大部分Swift標(biāo)準(zhǔn)庫都是用泛型代碼構(gòu)建的。事實(shí)上,您在整個(gè)語言指南中都使用了泛型,即使您沒有意識(shí)到這一點(diǎn)。例如,Swift的數(shù)組和字典類型都是泛型集合。您可以創(chuàng)建一個(gè)包含Int值的數(shù)組,或者一個(gè)包含String值的數(shù)組,或者一個(gè)可以用Swift創(chuàng)建的任何其他類型的數(shù)組。類似地,您可以創(chuàng)建一個(gè)字典來存儲(chǔ)任何指定類型的值,并且對(duì)于該類型可以是什么沒有限制。

泛型解決的問題

下面是一個(gè)標(biāo)準(zhǔn)的沒有泛型的函數(shù)

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

這個(gè)函數(shù)是,交換兩個(gè)Int型數(shù)據(jù),那么如果是其他類型的,我們要寫相同交換方法 n 個(gè)。累死了?。。?/p>

編寫一個(gè)可以交換任意類型的兩個(gè)值的函數(shù)更有用,也更靈活。泛型代碼使您能夠編寫這樣的函數(shù)。(這些函數(shù)的通用版本定義如下。)

泛型函數(shù)

下面是一個(gè)標(biāo)準(zhǔn)的泛型函數(shù) swapTwoValues(::)

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

現(xiàn)在可以使用這個(gè)泛型函數(shù)交換兩個(gè)字符串的值了。

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(::)函數(shù)的靈感來自于一個(gè)名為swap的通用函數(shù),它是Swift標(biāo)準(zhǔn)庫的一部分,可以在應(yīng)用程序中自動(dòng)使用。如果需要在自己的代碼中使用swapTwoValues(::)函數(shù)的行為,可以使用Swift現(xiàn)有的swap(::)函數(shù),而不是提供自己的實(shí)現(xiàn)。

在上面的swapTwoValues(::)示例中,占位符類型T是類型參數(shù)的一個(gè)示例。類型參數(shù)指定并命名占位符類型,并在函數(shù)名稱之后立即在一對(duì)匹配的尖括號(hào)(如)之間寫入。

一旦指定了類型參數(shù),就可以使用它定義函數(shù)參數(shù)的類型(例如swapTwoValues(::)函數(shù)的a和b參數(shù)),或者作為函數(shù)的返回類型,或者作為函數(shù)體中的類型注釋。在每種情況下,每當(dāng)調(diào)用函數(shù)時(shí),類型參數(shù)都會(huì)替換為實(shí)際類型。(在上面的swapTwoValues(::)示例中,第一次調(diào)用函數(shù)時(shí)用Int替換T,第二次調(diào)用時(shí)用String替換T)。

您可以通過在尖括號(hào)中編寫多個(gè)類型參數(shù)名(以逗號(hào)分隔)來提供多個(gè)類型參數(shù)。

泛型

swift 允許你自定義泛型類型,可以是任何類型。

本節(jié)向您展示如何編寫名為Stack的泛型集合類型。堆棧是一組有序的值,類似于數(shù)組,但其操作集比Swift的數(shù)組類型更受限制。數(shù)組允許在數(shù)組中的任何位置插入和刪除新項(xiàng)。然而,堆棧只允許將新項(xiàng)附加到集合的末尾(稱為將新值推入堆棧)。類似地,堆棧只允許從集合的末尾刪除項(xiàng)(稱為從堆棧中彈出值)。

UINavigationController類使用堆棧的概念來為其導(dǎo)航層次結(jié)構(gòu)中的視圖控制器建模。您可以調(diào)用UINavigationController類pushViewController(:animated:)方法將視圖控制器添加(或推送)到導(dǎo)航堆棧上,并調(diào)用它的popViewControllerAnimated(:)方法從導(dǎo)航堆棧中刪除(或彈出)視圖控制器。當(dāng)您需要嚴(yán)格的“后進(jìn)先出”方法來管理集合時(shí),堆棧是一個(gè)有用的集合模型。

下圖顯示了棧的push和pop行為:

stackPushPop_2x.png
  • 1 當(dāng)前堆棧上有三個(gè)值。
  • 2 第四個(gè)值被推到堆棧的頂部。
  • 3 堆?,F(xiàn)在包含四個(gè)值,最近的值位于頂部。
  • 4 彈出堆棧中的頂部項(xiàng)。
  • 5 彈出一個(gè)值后,堆棧再次包含三個(gè)值。

這里是如何編寫堆棧的非通用版本,在這種情況下,堆棧的Int值:

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

該結(jié)構(gòu)使用名為items的數(shù)組屬性在堆棧中存儲(chǔ)值。Stack提供了兩個(gè)方法push和pop,用于在棧上和棧外推送和彈出值。這些方法被標(biāo)記為mutating,因?yàn)樗鼈冃枰薷?或修改)結(jié)構(gòu)的items數(shù)組。

但是,上面顯示的IntStack類型只能與Int值一起使用。定義一個(gè)通用堆棧類會(huì)更有用,它可以管理任何類型值的堆棧。

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

注意,Stack的泛型版本與非泛型版本本質(zhì)上是相同的,但是類型參數(shù)是Element,而不是Int的實(shí)際類型。

元素為稍后提供的類型定義占位符名稱。這種未來類型可以在結(jié)構(gòu)定義的任何位置作為元素引用。在本例中,元素被用作三個(gè)位置的占位符:

  • 要?jiǎng)?chuàng)建一個(gè)名為items的屬性,該屬性使用類型元素的值的空數(shù)組初始化
  • 要指定push(_:)方法有一個(gè)名為item的參數(shù),該參數(shù)必須Element類型
  • 指定pop()方法返回的值為Elementl類型的值

因?yàn)樗且粋€(gè)泛型類型,所以可以使用Stack在Swift中創(chuàng)建任何有效類型的堆棧,方法類似于Array和Dictionary。

你可以通過編寫要存儲(chǔ)在尖括號(hào)中的類型,可以創(chuàng)建一個(gè)新的堆棧實(shí)例。例如,要?jiǎng)?chuàng)建一個(gè)新的字符串堆棧,可以編寫堆棧():

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

下面是stackofstring將這四個(gè)值放入堆棧后的樣子:


stackPushedFourStrings_2x.png

彈出一個(gè)值從堆棧刪除并返回頂部的值,“cuatro”:

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

然后結(jié)果如下:


stackPoppedOneString_2x.png

擴(kuò)展泛型類型

當(dāng)擴(kuò)展泛型類型時(shí),不提供類型參數(shù)列表作為擴(kuò)展定義的一部分。相反,擴(kuò)展體中可以使用來自原始類型定義的類型參數(shù)列表,并且原始類型參數(shù)名稱用于引用來自原始定義的類型參數(shù)。

下面的例子擴(kuò)展了通用堆棧類型,添加了一個(gè)只讀的計(jì)算屬性topItem,它返回堆棧上的頂項(xiàng),而不從堆棧中彈出它:

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

topItem屬性返回type Element的可選值。如果堆棧為空,topItem返回nil;如果堆棧不是空的,topItem返回items數(shù)組中的最后一個(gè)項(xiàng)目。

注意,這個(gè)擴(kuò)展沒有定義類型參數(shù)列表。相反,在擴(kuò)展中使用堆棧類型的現(xiàn)有類型參數(shù)名稱Element來指示計(jì)算屬性topItem的可選類型。

計(jì)算屬性topItem現(xiàn)在可以與任何堆棧實(shí)例一起使用,在不刪除它的情況下訪問和查詢它的棧頂項(xiàng)。

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

泛型類型的擴(kuò)展還可以包括擴(kuò)展類型的實(shí)例必須滿足的需求,以便獲得新功能,如下面的泛型Where子句的擴(kuò)展中所述。

類型約束

swapTwoValues(::)函數(shù)和堆棧類型可以使用任何類型。然而,有時(shí)對(duì)可與泛型函數(shù)和泛型類型一起使用的類型強(qiáng)制某些類型約束是有用的。類型約束指定類型參數(shù)必須繼承自特定類,或符合特定協(xié)議或協(xié)議組合。

例如,Swift的字典類型對(duì)可以用作字典鍵的類型設(shè)置了限制。正如字典中所描述的,字典的鍵的類型必須是可替換的。也就是說,它必須提供一種方法使自己具有獨(dú)特的代表性。Dictionary需要它的鍵是可hashable的,這樣它就可以檢查它是否已經(jīng)包含了特定鍵的值。如果沒有這個(gè)要求,Dictionary就不能告訴它是否應(yīng)該插入或替換特定鍵的值,也不能找到字典中已經(jīng)存在的給定鍵的值。

這一要求是通過對(duì)Dictionary鍵類型的類型約束來實(shí)現(xiàn)的,它指定鍵類型必須符合Hashable協(xié)議,這是Swift標(biāo)準(zhǔn)庫中定義的一種特殊協(xié)議。所有Swift的基本類型(如String、Int、Double和Bool)在默認(rèn)情況下都是可hashable的。

在創(chuàng)建自定義泛型類型時(shí),可以定義自己的類型約束,這些約束提供了泛型編程的大部分功能。像Hashable這樣的抽象概念根據(jù)它們的概念特征來描述類型,而不是它們的具體類型。

類型約束語法

通過在類型參數(shù)的名稱后面放置一個(gè)類或協(xié)議約束(以冒號(hào)分隔)作為類型參數(shù)列表的一部分來編寫類型約束。泛型函數(shù)的類型約束的基本語法如下所示(盡管泛型函數(shù)的語法是相同的):

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

上面的假設(shè)函數(shù)有兩個(gè)類型參數(shù)。第一個(gè)類型參數(shù)T有一個(gè)類型約束,要求T是某個(gè)類的子類。第二個(gè)類型參數(shù)U有一個(gè)類型約束,要求U遵守協(xié)議SomeProtocol。

類型約束作用

這是一個(gè)名為findIndex(ofString:in:)的非泛型函數(shù),它被賦予一個(gè)要查找的字符串值和一個(gè)字符串值數(shù)組,可以在其中找到它。函數(shù)的作用是:返回一個(gè)可選的Int值,如果找到了數(shù)組中第一個(gè)匹配的字符串,它將作為索引;

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

調(diào)用

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"

但是,在數(shù)組中查找值索引的原則并不只適用于字符串。您可以編寫與泛型函數(shù)相同的功能,方法是用某種類型的T值替換任何提到的字符串。

下面是如何編寫findIndex(ofString:in:)的通用版本,稱為findIndex(of:in:)。注意,這個(gè)函數(shù)的返回類型仍然是Int?,因?yàn)楹瘮?shù)返回一個(gè)可選索引號(hào),而不是數(shù)組中的一個(gè)可選值。但是要注意,這個(gè)函數(shù)不能編譯,原因在例子之后解釋:

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

上面寫的這個(gè)函數(shù)不能編譯。問題在于相等性檢查,“if value == valueToFind”。并不是Swift中的所有類型都可以與等號(hào)操作符(==)進(jìn)行比較。例如,如果您創(chuàng)建了自己的類或結(jié)構(gòu)來表示一個(gè)復(fù)雜的數(shù)據(jù)模型,那么對(duì)于該類或結(jié)構(gòu)來說,“等于”的含義是Swift無法為您猜到的。因此,不可能保證此代碼適用于所有可能的類型T,并且在嘗試編譯代碼時(shí)報(bào)告了一個(gè)適當(dāng)?shù)腻e(cuò)誤。

然而,并非一切都已失去。Swift標(biāo)準(zhǔn)庫定義了一個(gè)名為Equatable的協(xié)議,它要求任何符合條件的類型實(shí)現(xiàn)等號(hào)操作符(==)和not等號(hào)操作符(!=)來比較該類型的任意兩個(gè)值。Swift的所有標(biāo)準(zhǔn)類型都自動(dòng)支持Equatable協(xié)議。

任何遵循Equatable協(xié)議的類型都可以安全地與findIndex(of:in:)函數(shù)一起使用,因?yàn)樗WC支持equal to操作符。為了表達(dá)這個(gè)事實(shí),當(dāng)你定義函數(shù)時(shí),你寫一個(gè)Equatable類型約束作為類型參數(shù)定義的一部分:

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

現(xiàn)在編譯成功,可以用于任何類型的等式,如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

關(guān)聯(lián)類型 Associated Types

在定義協(xié)議時(shí),聲明一個(gè)或多個(gè)關(guān)聯(lián)類型作為協(xié)議定義的一部分有時(shí)很有用。關(guān)聯(lián)類型為用作協(xié)議一部分的類型提供占位符名稱。在采用協(xié)議之前,不會(huì)指定用于關(guān)聯(lián)類型的實(shí)際類型。關(guān)聯(lián)類型使用associatedtype關(guān)鍵字指定。

下面是一個(gè)名為Container的協(xié)議示例,它聲明了一個(gè)名為Item的關(guān)聯(lián)類型:

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

Container協(xié)議定義了任何容器都必須提供的三個(gè)必需功能:

  • 必須能夠使用append(_:)方法向容器添加新項(xiàng)。
  • 必須能夠通過返回Int值的count屬性訪問容器中項(xiàng)的計(jì)數(shù)。
  • 必須能夠使用下標(biāo)檢索容器中的每一項(xiàng),下標(biāo)接受Int索引值。

該協(xié)議沒有指定容器中的項(xiàng)應(yīng)該如何存儲(chǔ)或允許存儲(chǔ)什么類型的項(xiàng)。協(xié)議只指定任何類型必須提供的三個(gè)功能位,以便將其視為容器。符合標(biāo)準(zhǔn)的類型可以提供額外的功能,只要它滿足這三個(gè)需求。

任何符合容器協(xié)議的類型都必須能夠指定它存儲(chǔ)的值的類型。具體來說,它必須確保只有正確類型的項(xiàng)被添加到容器中,并且必須清楚下標(biāo)返回的項(xiàng)的類型。

要定義這些需求,容器協(xié)議需要一種方法來引用容器將包含的元素的類型,而不需要知道特定容器的類型是什么。容器協(xié)議需要指定傳遞給append(_:)方法的任何值必須具有與容器元素類型相同的類型,并且容器下標(biāo)返回的值將具有與容器元素類型相同的類型。

為了實(shí)現(xiàn)這一點(diǎn),容器協(xié)議聲明了一個(gè)名為Item的關(guān)聯(lián)類型,它被寫成associatedtype Item。協(xié)議沒有定義什么項(xiàng)——任何符合要求的類型都可以提供這些信息。盡管如此,項(xiàng)目別名提供了一種方法來引用容器中項(xiàng)目的類型,并定義一個(gè)用于append(_:)方法和下標(biāo)的類型,以確保強(qiáng)制執(zhí)行任何容器的預(yù)期行為。

下面是來自上面泛型類型的非泛型IntStack類型的一個(gè)版本,適用于符合容器協(xié)議:

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類型實(shí)現(xiàn)了容器協(xié)議的所有三個(gè)需求,并且在每種情況下都封裝了IntStack類型現(xiàn)有功能的一部分來滿足這些需求。

此外,IntStack指定對(duì)于容器的這個(gè)實(shí)現(xiàn),使用的合適的項(xiàng)是Int類型,typealias Item = Int的定義將抽象的項(xiàng)類型轉(zhuǎn)換為容器協(xié)議的這個(gè)實(shí)現(xiàn)的具體的Int類型。

由于Swift的類型推斷,實(shí)際上不需要將Int的具體項(xiàng)聲明為IntStack定義的一部分。由于IntStack符合容器協(xié)議的所有要求,Swift只需查看append(_:)方法的Item參數(shù)的類型和下標(biāo)的返回類型,就可以推斷出要使用的適當(dāng)項(xiàng)。實(shí)際上,如果您從上面的代碼中刪除typealias Item = Int行,那么一切仍然可以工作,因?yàn)閷?duì)于Item應(yīng)該使用什么類型是很清楚的。

你也可以使用泛型類型遵循該Container協(xié)議

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]
    }
}

這一次,類型參數(shù)元素用作append(_:)方法的項(xiàng)參數(shù)的類型和下標(biāo)的返回類型。因此,Swift可以推斷元素是作為該特定容器項(xiàng)使用的適當(dāng)類型。

擴(kuò)展現(xiàn)有類型以指定關(guān)聯(lián)類型

您可以擴(kuò)展現(xiàn)有類型以向協(xié)議添加一致性,如使用擴(kuò)展添加協(xié)議一致性中所述。這包括具有關(guān)聯(lián)類型的協(xié)議。

Swift的數(shù)組類型已經(jīng)提供了一個(gè)append(_:)方法、一個(gè)count屬性和一個(gè)下標(biāo),下標(biāo)帶有一個(gè)Int索引來檢索它的元素。這三個(gè)功能符合容器協(xié)議的要求。這意味著只需聲明Array采用該協(xié)議,就可以擴(kuò)展Array以符合容器協(xié)議。您可以使用一個(gè)空擴(kuò)展來實(shí)現(xiàn)這一點(diǎn),正如在聲明使用擴(kuò)展采用協(xié)議時(shí)所述:

Array現(xiàn)有的append(_:)方法和下標(biāo)使Swift能夠推斷出要為Item使用的適當(dāng)類型,就像上面的通用堆棧類型一樣。定義此擴(kuò)展之后,可以使用任何數(shù)組作為容器。

給關(guān)聯(lián)類型添加類型約束

您可以向協(xié)議中的關(guān)聯(lián)類型添加類型約束,以要求符合標(biāo)準(zhǔn)的類型滿足這些約束。例如,下面的代碼定義了容器的一個(gè)版本,該版本要求容器中的項(xiàng)是相等的。

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

為了符合這個(gè)版本的容器,容器的項(xiàng)類型必須符合Equatable協(xié)議。

在關(guān)聯(lián)類型的約束中使用協(xié)議

協(xié)議可以作為其自身需求的一部分出現(xiàn)。例如,這里有一個(gè)協(xié)議改進(jìn)了容器協(xié)議,添加了suffix(:)方法的要求。suffix(:)方法從容器的末尾返回給定數(shù)量的元素,并將它們存儲(chǔ)在Suffix類型的實(shí)例中。

在這個(gè)協(xié)議中,Suffix 是一個(gè)關(guān)聯(lián)類型,就像上面容器示例中的項(xiàng)類型一樣。Suffix 有兩個(gè)約束:它必須符合Suffixablecontainer協(xié)議(目前正在定義的協(xié)議),并且它的項(xiàng)類型必須與容器的項(xiàng)類型相同。Item上的約束是一個(gè)泛型where子句,將在下面的泛型where子句的關(guān)聯(lián)類型中討論。

下面是上面泛型類型的堆棧類型擴(kuò)展,它增加了Suffixablecontainer協(xié)議的一致性:

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的后綴關(guān)聯(lián)類型也是Stack,因此Stack上的后綴操作返回另一個(gè)堆棧。另外,符合SuffixableContainer的類型可以具有不同于自身的后綴類型——這意味著后綴操作可以返回不同的類型。例如,這里有一個(gè)非泛型IntStack類型的擴(kuò)展,添加了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 語句

類型約束(如類型約束中所述)使您能夠?qū)εc泛型函數(shù)、下標(biāo)或類型關(guān)聯(lián)的類型參數(shù)定義需求。

為關(guān)聯(lián)類型定義需求也很有用。您可以通過定義一個(gè)通用where子句來實(shí)現(xiàn)這一點(diǎn)。泛型where子句允許您要求關(guān)聯(lián)類型必須符合某個(gè)協(xié)議,或者某些類型參數(shù)和關(guān)聯(lián)類型必須相同。泛型where子句以where關(guān)鍵字開始,然后是關(guān)聯(lián)類型的約束或類型與關(guān)聯(lián)類型之間的相等關(guān)系。在類型或函數(shù)主體的左大括號(hào)前編寫一個(gè)泛型where子句。

下面的示例定義了一個(gè)名為allItemsMatch的泛型函數(shù),該函數(shù)檢查兩個(gè)容器實(shí)例是否以相同的順序包含相同的項(xiàng)。如果所有項(xiàng)匹配,函數(shù)返回一個(gè)布爾值true;如果不匹配,返回一個(gè)值false。

要檢查的兩個(gè)容器不必是相同類型的容器(雖然可以),但是它們必須包含相同類型的項(xiàng)目。這一要求是通過類型約束和通用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
}

這個(gè)函數(shù)接受兩個(gè)參數(shù)someContainer和anotherContainer。someContainer參數(shù)類型為C1,anotherContainer參數(shù)類型為C2。C1和C2都是調(diào)用函數(shù)時(shí)要確定的兩種容器類型的類型參數(shù)。

以下是對(duì)函數(shù)的兩個(gè)類型參數(shù)的要求:

  • C1必須符合容器協(xié)議(寫為C1: Container)。
  • C2還必須符合容器協(xié)議(寫為C2: Container)。
  • C1的項(xiàng)必須與C2的項(xiàng)相同(C1.Item == C2.Item)。
  • C1項(xiàng)必須符合Equatable協(xié)議(C1.Item: Equatable)。

第一個(gè)和第二個(gè)需求在函數(shù)的類型參數(shù)列表中定義,第三和第四個(gè)需求在函數(shù)的泛型where子句中定義。

使用函數(shù)

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

上面的示例創(chuàng)建一個(gè)堆棧實(shí)例來存儲(chǔ)字符串值,并將三個(gè)字符串推入堆棧。該示例還創(chuàng)建一個(gè)數(shù)組實(shí)例,初始化后的數(shù)組文本包含與堆棧相同的三個(gè)字符串。盡管堆棧和數(shù)組屬于不同的類型,但它們都符合容器協(xié)議,并且都包含相同類型的值。因此,可以使用這兩個(gè)容器作為參數(shù)調(diào)用allItemsMatch(::)函數(shù)。在上面的例子中,allItemsMatch(::)函數(shù)正確地報(bào)告兩個(gè)容器中的所有項(xiàng)都匹配。

泛型Where 語句的擴(kuò)展

還可以使用通用where子句作為擴(kuò)展的一部分。下面的示例擴(kuò)展了前面示例中的通用堆棧結(jié)構(gòu),添加了一個(gè)isTop(_:)方法。

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

這個(gè)新的isTop(:)方法首先檢查堆棧是否為空,然后將給定的項(xiàng)與堆棧的最上面的項(xiàng)進(jìn)行比較。如果您嘗試在沒有通用where子句的情況下執(zhí)行此操作,那么就會(huì)遇到一個(gè)問題:isTop(:)的實(shí)現(xiàn)使用==操作符,但是堆棧的定義并不要求它的項(xiàng)是相等的,因此使用==操作符會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤。使用泛型where子句可以向擴(kuò)展添加新需求,因此只有當(dāng)堆棧中的項(xiàng)相等時(shí),擴(kuò)展才會(huì)添加isTop(_:)方法。

if stackOfStrings.isTop("tres") {
    print("Top element is tres.")
} else {
    print("Top element is something else.")
}
// Prints "Top element is tres."

如果嘗試在元素不相等的堆棧上調(diào)用isTop(_:)方法,將會(huì)得到編譯時(shí)錯(cuò)誤。

struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue)  // Error

可以使用帶協(xié)議擴(kuò)展的通用where子句。下面的示例擴(kuò)展了前面示例中的容器協(xié)議,添加了一個(gè)startsWith(_:)方法。

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

startsWith(:)方法首先確保容器至少有一個(gè)項(xiàng),然后檢查容器中的第一個(gè)項(xiàng)是否與給定的項(xiàng)匹配。這個(gè)新的startsWith(:)方法可以用于任何符合容器協(xié)議的類型,包括上面使用的堆棧和數(shù)組,只要容器的項(xiàng)是相等的。

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

上面示例中的泛型where子句要求Item符合協(xié)議,但是也可以編寫泛型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"

這個(gè)例子向項(xiàng)類型為Double的容器添加了一個(gè)average()方法。它遍歷容器中的項(xiàng)并將它們相加,然后除以容器的計(jì)數(shù)來計(jì)算平均值。它顯式地將計(jì)數(shù)從Int轉(zhuǎn)換為Double,以便能夠進(jìn)行浮點(diǎn)除法。

可以在擴(kuò)展的泛型where子句中包含多個(gè)需求,就像可以在其他地方編寫泛型where子句一樣。用逗號(hào)分隔列表中的每個(gè)需求。

與泛型Where子句關(guān)聯(lián)的類型

可以在關(guān)聯(lián)類型上包含泛型where子句。例如,假設(shè)您想要?jiǎng)?chuàng)建一個(gè)包含迭代器的容器版本,就像序列協(xié)議在標(biāo)準(zhǔn)庫中使用的那樣。你可以這樣寫:

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子句要求迭代器必須遍歷與容器項(xiàng)類型相同的項(xiàng)類型的元素,而不管迭代器的類型如何。函數(shù)的作用是:提供對(duì)容器迭代器的訪問。

對(duì)于從另一個(gè)協(xié)議繼承的協(xié)議,通過在協(xié)議聲明中包含泛型where子句,可以向繼承的關(guān)聯(lián)類型添加約束。例如,下面的代碼聲明了一個(gè)ComparableContainer協(xié)議,該協(xié)議要求項(xiàng)目符合Comparable:

protocol ComparableContainer: Container where Item: Comparable { }

泛型下標(biāo) Subscripts

下標(biāo)可以是泛型的,它們可以包含泛型where子句。您可以在下標(biāo)后的尖括號(hào)內(nèi)編寫占位符類型名稱,并在下標(biāo)主體的左大括號(hào)前編寫一個(gè)泛型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
    }
}

容器協(xié)議的這個(gè)擴(kuò)展添加了一個(gè)下標(biāo),它接受一系列索引,并返回一個(gè)數(shù)組,其中包含每個(gè)給定索引上的項(xiàng)。這個(gè)通用下標(biāo)的約束條件如下:

  • 尖括號(hào)中的泛型參數(shù)索引必須是符合標(biāo)準(zhǔn)庫中的序列協(xié)議的類型。
  • 下標(biāo)接受單個(gè)參數(shù)indexes,這是該索引類型的一個(gè)實(shí)例。
  • 通用where子句要求序列的迭代器必須遍歷Int類型的元素,這確保序列中的索引與容器中使用的索引類型相同。

總的來說,這些約束意味著為索引參數(shù)傳遞的值是一個(gè)整數(shù)序列。

<<返回目錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本章將會(huì)介紹 泛型所解決的問題泛型函數(shù)類型參數(shù)命名類型參數(shù)泛型類型擴(kuò)展一個(gè)泛型類型類型約束關(guān)聯(lián)類型泛型 Where...
    寒橋閱讀 713評(píng)論 0 2
  • 泛型的概念 泛型代碼可根據(jù)自定義需求,寫出適用于任何類型、靈活且可重用的函數(shù)和類型,避免重復(fù)的代碼,用一種清晰和抽...
    伯wen閱讀 462評(píng)論 0 2
  • 泛型(Generics) 泛型代碼允許你定義適用于任何類型的,符合你設(shè)置的要求的,靈活且可重用的 函數(shù)和類型。泛型...
    果啤閱讀 759評(píng)論 0 0
  • 泛型代碼 能夠根據(jù)自定義的需求,編寫出適用于任意類型、靈活可重用的函數(shù)及類型。避免代碼的重復(fù),用一種清晰和抽象的方...
    答案MK閱讀 499評(píng)論 0 0
  • 豆角燜面!
    好玩的大黑貓閱讀 203評(píng)論 1 1

友情鏈接更多精彩內(nèi)容