Swift: 泛型

泛型的概念

泛型代碼可根據(jù)自定義需求,寫(xiě)出適用于任何類(lèi)型、靈活且可重用的函數(shù)和類(lèi)型,避免重復(fù)的代碼,用一種清晰和抽象的思維表達(dá)代碼的意思

泛型函數(shù)

示例:

/// 交換變量的值
func exchange<T>(_ one: inout T, _ two: inout T) {
    (one, two) = (two, one)
}

var a = 10.0
var b = 20.0

print("a = \(a), b = \(b)")     // a = 10.0, b = 20.0
exchange(&a, &b)               // 交換a和b的值
print("a = \(a), b = \(b)")     // a = 20.0, b = 10.0

var c = "hellow"
var d = "world"

print("a = \(c), b = \(d)")     // a = hellow, b = world
exchange(&c, &d)               // 交換c和d的值
print("a = \(c), b = \(d)")     // a = world, b = hellow
  • 上述代碼中, exchange(_:_:)函數(shù)就是一個(gè)泛型函數(shù)

  • <T>中的<T>是一個(gè)占位類(lèi)型, 在定義過(guò)程中不確定具體的類(lèi)型, 只有在函數(shù)調(diào)用時(shí), 根據(jù)傳入的值的類(lèi)型, 來(lái)推斷出T的具體類(lèi)型

    • exchange(&a, &b)TDouble類(lèi)型
    • exchange(&c, &d)TString類(lèi)型

注意: 泛型函數(shù)在調(diào)用的時(shí)候, 會(huì)根據(jù)傳入的值推斷出對(duì)應(yīng)的類(lèi)型

  • 泛型函數(shù)格式:
func 函數(shù)名<占位類(lèi)型列表>(參數(shù)列表) {
    // 函數(shù)體
}
  • 注意:
    • 參數(shù)列表中, 占位類(lèi)型列表中的占位類(lèi)型必須在參數(shù)列表中使用
    • 如果參數(shù)列表中, 多個(gè)參數(shù)都屬于相同的占位類(lèi)型, 那么這些參數(shù)必需傳入一致的類(lèi)型數(shù)據(jù), 例如: 全部傳入Int, String, 或Double
    • 占位類(lèi)型列表可以有多個(gè)占位類(lèi)型, 使用逗號(hào)分開(kāi)

類(lèi)型參數(shù)

  • exchange(_:_:)函數(shù)中, 占位類(lèi)型T就是一個(gè)類(lèi)型參數(shù)的例子, 即: T是一個(gè)類(lèi)型參數(shù)
  • 類(lèi)型參數(shù)指定并命名一個(gè)占位類(lèi)型, 并且緊隨在函數(shù)名后面, 使用一對(duì)尖括號(hào)括起來(lái)(例如: <T>)
  • 一旦一個(gè)類(lèi)型參數(shù)被指定, 就可以如下使用:
    • 用來(lái)定義一個(gè)函數(shù)的參數(shù)類(lèi)型(例如: exchange(_:_:)中的one和two的類(lèi)型)
    • 做為函數(shù)的返回值
    • 函數(shù)主體中的注釋類(lèi)型
  • 類(lèi)型參數(shù)的定義過(guò)程中不會(huì)代表任何具體的類(lèi)型, 只是一個(gè)占位, 當(dāng)函數(shù)被調(diào)用時(shí), 會(huì)根據(jù)傳入的值的類(lèi)型, 推斷出具體類(lèi)型, 例如上面的DoubleStirng替換掉T
  • 參數(shù)類(lèi)型可以同時(shí)存在多個(gè), 并用逗號(hào)分開(kāi), 例如: <T, U, S>為三個(gè)類(lèi)型參數(shù)(占位類(lèi)型), 名稱(chēng)分別為參數(shù)類(lèi)型T, 參數(shù)類(lèi)型U, 參數(shù)類(lèi)型S
綜上有泛型函數(shù)格式如下:
func 函數(shù)名<類(lèi)型參數(shù)列表>(參數(shù)列表) {
   // 函數(shù)體
}

泛型類(lèi)型

  • 泛型函數(shù)是在函數(shù)名的后面緊跟著類(lèi)型參數(shù)列表, 而泛型類(lèi)型就是在定義的類(lèi)型的時(shí)候, 在類(lèi)型名后面緊跟類(lèi)型參數(shù)列表
示例
  • 泛型類(lèi):
泛型類(lèi): 
class GenericClass<Element> {
    // 集合
    var items = [Element]()
    // 壓棧
    func push(_ item: Element) {
        items.append(item)
    }
    // 出棧
    func pop() -> Element? {
        return items.isEmpty ? nil : items.removeLast()
    }
}
  • 泛型結(jié)構(gòu)體
泛型結(jié)構(gòu)體:
struct GenericStruct<Element> {
    // 集合
    var items = [Element]()
    // 壓棧
    mutating func push(_ item: Element) {
        items.append(item)
    }
    // 出棧
    mutating func pop() -> Element? {
        return items.isEmpty ? nil : items.removeLast()
    }
}
  • 泛型枚舉:
泛型枚舉:
enum GenericEnum<Element> {
    case none
    case some(Element)
}
  • 上面的 GenericClass(類(lèi)), GenericStruct(結(jié)構(gòu)體), GenericEnum(枚舉)都是泛型類(lèi)型, 在類(lèi)型名后緊跟著泛型的類(lèi)型參數(shù) <Element>
  • 泛型類(lèi)型使用的時(shí)候, 需要指定類(lèi)型參數(shù)的具體類(lèi)型, 下面以結(jié)構(gòu)體GenericStruct為例:
// 創(chuàng)建GenericStruct類(lèi)型的機(jī)構(gòu)體變量struct
// 指定類(lèi)型參數(shù)為 Int
var struct = GenericStruct<Int>()
// 使用struct時(shí), push(_:), pop()方法使用 類(lèi)型參數(shù)的地方 都會(huì)替換為Int類(lèi)型
struct.push(1)
struct.push(2)
struct.push(3)
struct.push("4")   // 報(bào)錯(cuò): 因?yàn)閜ush(_:)接收的參數(shù)類(lèi)型已經(jīng)被替換成Int

let result = struct.pop()   // result = 3
給泛型類(lèi)型添加分類(lèi)(extension)
  • 泛型類(lèi)型添加分類(lèi)時(shí), 定義中不可以增加新的類(lèi)型參數(shù), 也不需要寫(xiě)已有類(lèi)型參數(shù)(編譯器也不允許寫(xiě))
  • 錯(cuò)誤寫(xiě)法:
錯(cuò)誤一: 分類(lèi)的定義中不可以增加新的類(lèi)型參數(shù)
extension GenericClass<T> { }
錯(cuò)誤二: 分類(lèi)的定義中不需要寫(xiě)已經(jīng)有的類(lèi)型參數(shù)
extension GenericClass<Element> { }
  • 下面的代碼是正確寫(xiě)法, 在分類(lèi)中可以使用類(lèi)型定義時(shí)有的類(lèi)型參數(shù):
extension GenericClass {
    // 使用已有的 類(lèi)型參數(shù): Element 做為返回值
    func element(at index: Int) -> Element? {
        if index < items.count {
            return items[index]
        }else {
            return nil
        }
    }
}

雖然在分類(lèi)中無(wú)法定義新的類(lèi)型參數(shù), 但是可以在分類(lèi)新定義的方法中引入其他的類(lèi)型參數(shù)

extension GenericClass {
   func exchange<T>(one: inout T, two: inout T) {
       (one, two) = (two, one)
   }
}

泛型約束

  • 上述所有代碼中, 不論是泛型函數(shù)中的 類(lèi)型參數(shù)T , 還是泛型類(lèi)型中的 類(lèi)型參數(shù)Element , 都可以在調(diào)用時(shí)指定任意一個(gè)具體的類(lèi)型做為替換, 這是因?yàn)槲也](méi)有給這些參數(shù)類(lèi)型添加任何的約束
那么什么是 參數(shù)類(lèi)型添加約束呢?

就拿我們經(jīng)常使用的Dictionary為例, 我們知道Dictionary的定義中有兩個(gè)參數(shù)類(lèi)型, 分別為 KeyValue , 而且在給Dictionary添加元素的時(shí)候, key的值都是唯一的,即Dictionary根據(jù)Key的值來(lái)判斷是修改還是增加元素, 而Swift中的Dictionary是根據(jù)Key的哈希值來(lái)判斷唯一性的, 也就是說(shuō)DictionaryKey值必須是可哈希的, 所以Dictionary的類(lèi)型參數(shù)Key有一個(gè)約束, 那就是 可哈希的值

  • 下面是Dictionary定義的代碼部分, 這里過(guò)濾里面的實(shí)現(xiàn)部分, 其中的where會(huì)在后面講解
public struct Dictionary<Key, Value> : Collection, ExpressibleByDictionaryLiteral where Key : Hashable{}
類(lèi)型約束語(yǔ)法
  • 在定義一個(gè)類(lèi)型參數(shù)時(shí), 在類(lèi)型參數(shù)后面放置一個(gè)類(lèi)名或者協(xié)議名, 并用冒號(hào)分開(kāi), 來(lái)定義類(lèi)型參數(shù)類(lèi)型約束, 他們將成為類(lèi)型參數(shù)列表的一部分
  • 示例:
泛型函數(shù)添加類(lèi)型約束
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 這里是反省函數(shù)的函數(shù)體部分
}

泛型類(lèi)型添加類(lèi)型約束
class GenericClass<T: SomeClass, U: SomeProtocol> {
    // 類(lèi)的實(shí)現(xiàn)部分
}
  • 上面的 泛型函數(shù)泛型類(lèi)型 都分別有兩個(gè)類(lèi)型參數(shù) TU, T有一個(gè)類(lèi)型約束: 必須是SomeClass類(lèi)的子類(lèi); U有一個(gè)類(lèi)型約束: 必須遵守SomeProtocol協(xié)議的類(lèi)型
類(lèi)型約束實(shí)踐
  • 現(xiàn)在有一個(gè)非泛型函數(shù)findIndex(ofString:in:), 該函數(shù)的功能是在一個(gè)String數(shù)組中查找給定的String值的索引, 若找到匹配的String值, 會(huì)返回該String值在String數(shù)組中的索引, 否則返回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:)函數(shù)可以用于查找字符串?dāng)?shù)組中某個(gè)字符串的索引值
let strings = ["a", "b", "c", "d", "e"]
if let foundIndex = findIndex(of: "c", in: strings) {
    print("c 的索引值是 \(foundIndex)")
}
// 打印: c 的索引值是 2
  • 我們知道, findIndex(ofString:in:)函數(shù)目前只能查找字符串在數(shù)組中的索引值, 用處不是很大。不過(guò), 我們可以用占位類(lèi)型T替換掉String類(lèi)型來(lái)寫(xiě)出具有相同功能的泛型函數(shù)findIndex(of:in:)
  • 下面就是使用占位類(lèi)型T替換掉String類(lèi)型的代碼:
func findIndex<T>(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
  • 這段代碼看上去可以查找任意的具體類(lèi)型在該類(lèi)型數(shù)組中的索引值, 但是在Xcode中并不能正常運(yùn)行

  • 這是因?yàn)樵赟wift中, 想要比較兩個(gè)值是否相等, 那么這兩個(gè)值的類(lèi)型必須實(shí)現(xiàn)了Equatable協(xié)議才可以

  • 對(duì)于未實(shí)現(xiàn)Equatable協(xié)議的類(lèi)型, 比如我們自定義的類(lèi)和結(jié)構(gòu)體的實(shí)例, 是不能直接使用 == 來(lái)比較的, 因?yàn)檫@些實(shí)例并不知道"相等"意味著什么, 是部分內(nèi)容相等才相等, 還是完全相等才算相等, 而Equatable就是用來(lái)說(shuō)明"相等"意味著什么的

  • 因?yàn)橹挥凶袷?code>Equatable協(xié)議的類(lèi)型才能進(jìn)行相等判斷, 所以上述可以被替換成為任意類(lèi)型的T就不能符合要求, 所以我們需要給T加上一個(gè)類(lèi)型約束: 想要替換占位類(lèi)型T的具體類(lèi)型, 必須遵守Equatable協(xié)議

  • 任何遵守Equatable協(xié)議的類(lèi)型都可以在findIndex(of:in:)中正常運(yùn)行, 代碼如下:

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:)唯一的類(lèi)型參數(shù)叫做T: Equatable, 即: 任意符合Equatable協(xié)議的類(lèi)型T

關(guān)聯(lián)類(lèi)型(泛型協(xié)議)

  • 類(lèi)、結(jié)構(gòu)體和枚舉的泛型類(lèi)型中, 將類(lèi)型參數(shù)列表放在了類(lèi)型名的后面, 而在泛型協(xié)議中卻不能這樣寫(xiě)

  • 泛型協(xié)議的寫(xiě)法與泛型類(lèi)型有所不同, 需要使用 associatedtype 關(guān)鍵字來(lái)指定類(lèi)型參數(shù)

  • 泛型協(xié)議中的類(lèi)型參數(shù)又被稱(chēng)為關(guān)聯(lián)類(lèi)型, 其代表的實(shí)際類(lèi)型在協(xié)議被采納時(shí)才會(huì)被指定

  • 下面一段代碼就是有關(guān)聯(lián)類(lèi)型的協(xié)議:

protocol Container {
    associatedtype ItemType
    mutating func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}
  • 協(xié)議Container定義了三個(gè)任何采納該協(xié)議的類(lèi)型必須提供的功能

    • 必須可以通過(guò)append(_:)方法添加一個(gè)類(lèi)型為ItemType的新元素到容器里

    • 必須可以通過(guò)count屬性獲取容器中元素的數(shù)量, 并返回一個(gè)Int值

    • 必須可以通過(guò)索引值類(lèi)型為Int的下標(biāo)檢索到容器中的每一個(gè)類(lèi)型為ItemType的元素

  • 協(xié)議中無(wú)法定義ItemType的具體類(lèi)型, 而任何遵從Container協(xié)議的類(lèi)型都必須指定關(guān)聯(lián)類(lèi)型 ItemType 的具體類(lèi)型

  • 下面的是一個(gè)非泛型的IntStack結(jié)構(gòu)體, 采納并符合了Container協(xié)議, 實(shí)現(xiàn)了Container協(xié)議的是三個(gè)要求:

struct IntStack: Container {
    // 集合數(shù)組, 用于存放元素
    var items = [Int]()
  
    // Container協(xié)議部分
    typealias ItemType = Int  // 通過(guò)關(guān)鍵字 typealias 指定ItemType的類(lèi)型為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)Container的要求時(shí), 指定了ItemType的類(lèi)型為Int, 即typealias ItemType = Int, 從而將Container協(xié)議中抽象的ItemType類(lèi)型轉(zhuǎn)換為具體的Int類(lèi)型

  • 由于Swift的類(lèi)型推斷, 實(shí)際上不用再IntStack中特意的聲明ItemType的類(lèi)型為Int也可以, 這是因?yàn)?code>IntStack符合Container協(xié)議的所有要求, 并且在方法中也將ItemType寫(xiě)成了Int類(lèi)型, 這樣Swift就可以推斷出ItemType的類(lèi)型為Int, 事實(shí)上, 在代碼中刪除typeealias IntType = Int這一行, 一切依舊可以正常工作

struct IntStack: Container {

    var items = [Int]()

    mutating func append(item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}
  • 上面的代碼自動(dòng)推斷出 IntType 的類(lèi)型是 Int

即: 對(duì)于泛型協(xié)議, 當(dāng)有類(lèi)型遵從該協(xié)議的時(shí)候, 只需要給未確定具體類(lèi)型的關(guān)聯(lián)類(lèi)型所參與的所有方法中, 都給出唯一指定類(lèi)型時(shí), 并不需要特意聲明該關(guān)聯(lián)類(lèi)型的聲明也能正常運(yùn)行, 原因就是Swift的自動(dòng)推斷

  • 也可以讓泛型Stack結(jié)構(gòu)體遵從Container協(xié)議
struct Stack<Element>: Container {
    var items = [Element]()
    // 由于所有需要關(guān)聯(lián)類(lèi)型的地方都指定了明確類(lèi)型, 就不需要在特意的聲明關(guān)聯(lián)類(lèi)型具體是什么類(lèi)型了, 這里自動(dòng)推斷出 ItemType的類(lèi)型是Stack對(duì)象創(chuàng)建時(shí)指定的泛型具體類(lèi)型
    // typealias ItemType = Element
    mutating func append(item: Element) {
        self.items.append(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}
通過(guò)擴(kuò)展一個(gè)存在的類(lèi)型來(lái)指定關(guān)聯(lián)類(lèi)型
  • 對(duì)于一個(gè)已經(jīng)存在的類(lèi)型, 并且在定義中沒(méi)有遵守泛型協(xié)議, 我們可以在它的extension中遵守需要的泛型協(xié)議, 并且在該擴(kuò)展中 也可以自動(dòng)推導(dǎo)ItemType的類(lèi)型, 并不需要寫(xiě)typealias ItemType = Element
struct Stack<Element> {
    var items = [Element]()
}
// 通過(guò)擴(kuò)展遵從泛型協(xié)議 Container
extension Stack: Container {
    // 這一行可以不寫(xiě)
    // typealias ItemType = Element

    mutating func append(item: Element) {
        self.items.append(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

泛型 Where 語(yǔ)句

  • 上面的敘述中, 類(lèi)型約束讓我們能夠?yàn)?code>泛型函數(shù)或泛型類(lèi)型類(lèi)型參數(shù)定義一些強(qiáng)制要求
  • 除了類(lèi)型約束以外, 還有一種方法給泛型函數(shù)泛型類(lèi)型類(lèi)型參數(shù)定義約束, 那就是where子句
  • 現(xiàn)有如下方法:
func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
  • 該函數(shù)就是類(lèi)型約束中使用的, 獲取一個(gè)元素, 在該元素?cái)?shù)組中的索引值的函數(shù), 在占位類(lèi)型T后 使用: Equatable對(duì)T進(jìn)行了類(lèi)型約束。
  • 現(xiàn)在可以將該函數(shù)使用where子句變形為下面代碼:
func findIndex<T>(of valueToFind: T, in array: [T]) -> Int? where T : Equatable {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
  • 變形后的代碼, 將類(lèi)型約束提取出來(lái), 放在了返回值的后面, 使用where子句來(lái)表達(dá)該約束, 這在語(yǔ)法上沒(méi)有任何問(wèn)題, 并且變形后的函數(shù)依然可以正常使用

  • 當(dāng)然, 如果僅僅是給類(lèi)型參數(shù)添加類(lèi)型約束, 僅僅需要第一種方式就可以了。 實(shí)際上where子句還有另外一個(gè)用法, 即: where子句除了給類(lèi)型參數(shù)添加類(lèi)型約束外, 還可以給關(guān)聯(lián)類(lèi)型添加約束

  • 通過(guò)下面的代碼示例進(jìn)行講解where子句給關(guān)聯(lián)類(lèi)型添加約束的用法
// 容器協(xié)議
protocol Container {
    associatedtype ItemType
    
    mutating func append(item: ItemType)
}

func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool {
    // 檢查兩個(gè)容器包含相同數(shù)量的元素
    if someContainer.count != anotherContainer.count {
        return false
    }
    // 檢查每個(gè)元素是否相等
    for i in 0..<someContainer.count {
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    return true
}
  • 上述代碼中, allItemsMatch(_:_:)函數(shù)是一個(gè)泛型函數(shù), 作用是判斷兩個(gè)都遵守了Container協(xié)議的容器C1, C2中所有的元素是否在位置和值上完全相等
  • 由于C1和C2是兩個(gè)不同的占位類(lèi)型, 所以C1和C2可以是兩個(gè)不同的類(lèi)型
// 遵守Container協(xié)議的類(lèi)型Stack1
class Stack1<Element>: Container {
    var items = [ItemType]()
    
    typealias ItemType = Element
    
    func append(item: Element) {
        items.append(item)
    }
}

// 遵守Container協(xié)議的類(lèi)型Stack2
class Stack2<Element>: Container {
    var items = [ItemType]()
    
    typealias ItemType = Element
    
    func append(item: Element) {
        items.append(item)
    }
}
  • 上面定義了都遵守Container協(xié)議的兩個(gè)類(lèi)型Stack1Stack2, 我們將使用Stack1Stack2的實(shí)例對(duì)象進(jìn)行比較

  • 根據(jù)allItemsMatch(_:_:)的作用可以判斷出, 兩個(gè)容器Stack1Stack2只有在元素類(lèi)型(ItemType)類(lèi)型一致的情況下才能判斷元素是否相等, 但是這個(gè)約束使用類(lèi)型約束中的方法無(wú)法添加, 所以就有了下面的寫(xiě)法:

// 這里只考慮定義部分, 不考慮實(shí)現(xiàn)部分
func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType {//函數(shù)體}
  • 這句代碼中, 使用了where語(yǔ)句對(duì)C1C2的關(guān)聯(lián)類(lèi)型進(jìn)行了約束, 即C1C2ItemType

有了這個(gè)where子句后, 只有Stack1Stack2中的元素類(lèi)型必須一致才能使用該函數(shù)

  • 當(dāng)然僅僅有類(lèi)型相等判斷是不夠的, 容器中的元素還必須遵守Equatable才行, 即C1.ItemType == C2.ItemType并且C1.ItemType: Equatable, 所以allItemsMatch(_:_:)函數(shù)的完整代碼如下:
func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
    // 檢查兩個(gè)容器包含相同數(shù)量的元素
    if someContainer.count != anotherContainer.count {
        return false
    }
    // 檢查每個(gè)元素是否相等
    for i in 0..<someContainer.count {
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    return true
}
  • 在where子句中, 使用逗號(hào)分隔多個(gè)約束

類(lèi)型的定義中也可以使用where子句添加約束, 用法與在泛型函數(shù)中一樣, 都寫(xiě)在定義的后面, 大括號(hào){}的前面

  • 示例:
class Stack<Element>: Container where Stack.ItemType : Equatable {
    var items = [ItemType]()
    
    typealias ItemType = Element
    
    func append(_ item: Element) {
        items.append(item)
    }
}
具有泛型where子句的擴(kuò)展
  • 你可以使用泛型where子句做為擴(kuò)展的一部分, 基于以前的例子, 下面的示例擴(kuò)展了泛型Stack結(jié)構(gòu)體, 添加一個(gè)isTop:方法
extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        return items.last == item
    }
}
  • 以下是 isTop(_:) 方法的調(diào)用方式:
if stackOfStrings.isTop("c") {
    print("Top element is c.")
} else {
    print("Top element is something else.")
}
// 打印 "Top element is c."
  • 如果嘗試在包含的元素不符合Equatable協(xié)議的棧上調(diào)用isTop(_:)方法, 則會(huì)收到編譯時(shí)錯(cuò)誤
struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue)  // 報(bào)錯(cuò)
  • 你可以使用where子句擴(kuò)展一個(gè)協(xié)議, 基于以前的實(shí)例, 下面的實(shí)例擴(kuò)展了Container協(xié)議, 添加一個(gè)startsWith(:_)方法
extension Container where ItemType: Equatable {
    func startWith(_ item: ItemType) -> Bool {
        return count >= 1 && self[0] == item
    }
}

extension Array: Container{}

let array = ["a", "b", "c"]

if array.startWith("a") {
    print("array 第一個(gè)元素是 a")
}else {
    print("array 第一個(gè)元素不是 a")
}
// 打印 array 第一個(gè)元素是 a
  • 除了給泛型類(lèi)型泛型協(xié)議分類(lèi)中添加上述的where子句外, 還可以直接約束ItemType的具體類(lèi)型:
extension Container where ItemType == Double {
    func average() -> Double {
        var sum = 0.0
        for i in 0..<count {
            sum += self[i]
        }
        return sum / Double(count)
    }
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// 打印 "648.9"
  • 只有容器中的元素為Double類(lèi)型時(shí), 才可以調(diào)用該方法
具有泛型 Where 子句的關(guān)聯(lián)類(lèi)型
  • 除了上述使用方法外, where子句還可以直接在泛型協(xié)議的定義中, 直接給泛型協(xié)議參數(shù)類(lèi)型添加約束
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
}
  • 上述代碼Container協(xié)議中, 通過(guò)associatedtype Iterator定義了一個(gè)類(lèi)型參數(shù)Iterator, 并使用類(lèi)型約束, 使Iterator必須遵從IteratorProtocol協(xié)議, 又使用where子句, 約束了Iterator對(duì)應(yīng)IteratorProtocol協(xié)議的類(lèi)型參數(shù)的類(lèi)型必須和associatedtype Item類(lèi)型一致

  • 一個(gè)協(xié)議繼承了另一個(gè)協(xié)議, 你通過(guò)在協(xié)議聲明的時(shí)候, 包含泛型Where子句, 來(lái)添加一個(gè)約束到被繼承協(xié)議的關(guān)聯(lián)類(lèi)型, 例如, 下面的代碼聲明了一個(gè)ComparableContainer協(xié)議, 他要求所有的Item必須是Comparable的

protocol ComparableContainer: Container where Item: Comparable {}
最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 本章將會(huì)介紹 泛型所解決的問(wèn)題泛型函數(shù)類(lèi)型參數(shù)命名類(lèi)型參數(shù)泛型類(lèi)型擴(kuò)展一個(gè)泛型類(lèi)型類(lèi)型約束關(guān)聯(lián)類(lèi)型泛型 Where...
    寒橋閱讀 713評(píng)論 0 2
  • 136.泛型 泛型代碼讓你可以寫(xiě)出靈活,可重用的函數(shù)和類(lèi)型,它們可以使用任何類(lèi)型,受你定義的需求的約束。你可以寫(xiě)出...
    無(wú)灃閱讀 1,656評(píng)論 0 4
  • 泛型代碼可以確保你寫(xiě)出靈活的,可重用的函數(shù)和定義出任何你所確定好的需求的類(lèi)型。你的可以寫(xiě)出避免重復(fù)的代碼,并且用一...
    iOS_Developer閱讀 880評(píng)論 0 0
  • 泛型(Generics) 泛型代碼允許你定義適用于任何類(lèi)型的,符合你設(shè)置的要求的,靈活且可重用的 函數(shù)和類(lèi)型。泛型...
    果啤閱讀 758評(píng)論 0 0
  • 說(shuō)來(lái)慚愧,從五六歲開(kāi)始讀書(shū)識(shí)字至今,時(shí)間不可謂不短。但拋開(kāi)各類(lèi)考證考級(jí)的被動(dòng)性讀書(shū),自己總感覺(jué)從讀書(shū)中受益了...
    多能干細(xì)胞閱讀 208評(píng)論 0 1

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