swift-泛型

前言

泛型代碼讓你能根據自定義的需求,編寫出適用於任意類型的、靈活可複用的函數及類型。你可避免編寫重複的代碼,而是用一種清晰抽象的方式來表達代碼的意圖。

泛型函數

泛型函數可適用於任意類型,針對於替換兩個變量的值的例子,我們有如下方法。

例子:


func zhuanhuan( num1:inout Int, num2:inout Int){
    let type = num1
    num1=num2
    num2=type
}

var num1 = 1
var num2 = 2


zhuanhuan(num1: &num1, num2: &num2)
print("\(num1)\(num2)")

如果我們想要轉換字符串呢?再寫一個?

func zhuanhuan( num1:inout String, num2:inout String){
    let type = num1
    num1=num2
    num2=type
}

var num1 = "l"
var num2 = "k"


zhuanhuan(num1: &num1, num2: &num2)
print("\(num1)\(num2)")

是不是很麻煩! 這裏我們就用方法泛型來解決!
注意:重複的事情,我們絕不可能重複做。


func zhuanhuan<T>( num1:inout T, num2:inout T){
    let type = num1
    num1=num2
    num2=type
}

var num1 = 1
var num2 = 2


zhuanhuan(num1: &num1, num2: &num2)
print("\(num1)\(num2)")


var num11 = "l"
var num22 = "k"


zhuanhuan(num1: &num11, num2: &num22)
print("\(num11)\(num22)")

這樣我們就省略了好多行代碼,是不是很簡潔。

上面我們通過一個例子來改變num1和num2的位置,簡化之後的代碼有何不同:

1:我們在方法名後面添加了一個: ,個人理解此T就是一個佔位符,你可以使用任何字符(swift關鍵字除外),我比較喜歡用T。
2:我們發現每個 參數後的 類型都變成了 我們的佔位符 ,所以 前面的泛型是什麼 ,你後面就要是什麼。
3:我們發現,轉換整型的位置 和改變字符串的位置 用的是同一個方法 ,所以:泛型知識一個佔位符,他具體的類型,由使用者來決定。

泛型類型

除了泛型函數,Swift 還允許自定義泛型類型。這些自定義類、結構體和枚舉可以適用於任意類型,類似於 Array 和 Dictionary。

例子:

class zhan<T>{
    
    
    var Items = [T]()
    
    func insert(item:T){
        Items.append(item)
    }
    
    func remove()->T{
       return Items.removeLast()
    }
    
    
}

var hanshu = zhan<String>()
hanshu.insert(item: "ssj")
hanshu.insert(item: "sssj")
hanshu.insert(item: "ssssj")
hanshu.insert(item: "sssssj")

print(hanshu.Items)

hanshu.remove()

print(hanshu.Items)

上面我們定義了一個類,主要的功能是進行 ,添加和刪除元素。說什麼呢?感覺和泛型函數一個樣子,支持自定義泛型類型。

注意:在我們使用的過程中 我們不需要關心數據是什麼類型 ,我們只關注他某些情況下是統一的。

在我們創建zhan的實力的時候,我們給予的放心類型是String 所以:泛型知識一個佔位符,他具體的類型,由使用者來決定。

泛型擴展

當對泛型類型進行擴展時,你並不需要提供類型參數列表作爲定義的一部分。原始類型定義中聲明的類型參數列表在擴展中可以直接使用,並且這些來自原始類型中的參數名稱會被用作原始定義中類型參數的引用。

我們給上面的類,擴展一個功能:返回數組的最後一個元素。


extension zhan{
    
    func forst()->T?{
        if Items.count<=0{
            return Items[Items.count-1]
        }else{
            return nil
        }
    }
}

if let  one = hanshu.forst(){
    print(one)
}


因爲數組中可能沒有元素 所以我們允許他爲空。你並不需要在擴展的定義中提供類型參數列表。更加方便的是,原始類型定義中聲明的類型參數列表在擴展裏是可以使用的,並且這些來自原始類型中的參數名稱會被用作原始定義中類型參數的引用

類型約束

類型約束指定了一個必須繼承自指定類的類型參數,或者遵循一個特定的協議或協議構成。

基本語法

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 這裏是泛型函數的函數體部分
}

例子

用的是官方文檔的例子:該函數的主要作用是,查找某個元素的下標。若查找到,就返回index ,否則返回nil。

代碼:

非約束版本:


//定義了兩個外部參數名 均爲String類型 ,並且其中一個爲數組 因爲返回的可能是空 所以我們允許返回的爲可選型
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
    //enumerated() 函數:這個函數會返回一個新的序列,包含了初始序列裏的所有元素,以及與元素相對應的編號
    for (index, value) in array.enumerated() {
        //判斷給定的 元素所在的 下標
        if value == valueToFind {
            return index
        }
    }
    return nil
}

let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]

//強制解包 打印給定元素的index
if let foundIndex = findIndex(ofString: "llama", in: strings) {
    print("The index of llama is \(foundIndex)")
}

由上可見,只能查到指定字符串的 index 那如果我們想要返回的是 整形呢?這時我們就又用到了,佔位符了 看下面的代碼。

這裏解釋一下:本人學習的時候,不喜歡記一些 什麼字符,比如:下標函數,解包,方法,函數。 我覺得用多了 也就知道了。

約束版本:

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

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex 類型爲 Int?,其值爲 nil,因爲 9.3 不在數組中
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex 類型爲 Int?,其值爲 2

我們還是和上面一樣,把所有關於類型的關鍵字,全部換成 T,我們不管他是什麼類型,具體的交給使用者。

然後你會發現:

在這裏插入圖片描述

這個錯誤,是因爲什麼?不懂?百度翻譯!

在這裏插入圖片描述
原因是:對於swift的 結構體或者自定義類來說 swift 無法真實的知道 相等意味這什麼。

如何解決呢?

swift 標準庫,爲我們提供了 一個 Equatable 協議:該協議要求任何遵循該協議的類型必須實現等式符(==)及不等符(!=),從而能對該類型的任意兩個值進行比較。所有的 Swift 標準類型自動支持 Equatable 協議。

我們可以定義一個 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
}

爲啥要這樣寫:因爲我們需要讓 任意類型 都遵循 Equatable 協議的 T

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex 類型爲 Int?,其值爲 nil,因爲 9.3 不在數組中
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex 類型爲 Int?,其值爲 2

可以發現 現在就 可以成功運行了 其他類型 string Int double 都行!

關聯類

協議的關聯類型指的是根據使用場景的變化,如果協議中某些屬性存在“邏輯相同的而類型不同”的情況。

看下例子你就明白了:

protocol WeightCalcuAble {
    
    //爲weight 屬性定義的類型別名
    associatedtype WeightType
    
    var weight : WeightType {get}
    
}

注意:關聯類型通過 associatedtype 關鍵字來指定。

可見上面的 weight 的類型是 WeightType ,和我們原來,聲明計算屬性 差不多 只是 我們使用了一個 類型別名。

//定義手機結構體
struct MobilePhone : WeightCalcuAble{
    
    typealias WeightType = Double
    var weight : WeightType
    
}
//定義汽車結構體
struct Car : WeightCalcuAble{
    
    typealias WeightType = Int
    var weight: WeightType
    
}

let phone = MobilePhone(weight: 0.24)
let car = Car(weight: 1000)

仔細看! 三個結構體 均遵循了 WeightCalcuAble 協議 ,在我們實現的過程中 可以看到,三個結構體的 wight 屬性的 類型均爲 WeightType ,並且 我們給 WeightType 指定的類型爲 Double Int Int。

注意:一定要在遵守該協議的類型中使用typealias規定具體的類型。不然編譯器就報錯了。

這樣做的好處:如果我們僅僅因爲 屬性的返回值 不同 而去定義兩個 不同類型的協議 這樣做 顯然很重複 ,我還是那句話 重複的事情 簡單做! 根據我們的不同需求 去定義返回的類型是 我們需要做的 而不是 寫死!

Where 語句

類型約束能夠確保類型符合泛型函數或類的定義約束。

你可以在參數列表中通過where語句定義參數的約束。

你可以寫一個where語句,緊跟在在類型參數列表後面,where語句後跟一個或者多個針對關聯類型的約束,以及(或)一個或多個類型和關聯類型間的等價(equality)關係。

例子:

// Container 協議
protocol Container {
    associatedtype ItemType
    // 添加一個新元素到容器裏
    mutating func append(_ item: ItemType)
    // 獲取容器中元素的數
    var count: Int { get }
    // 通過索引值類型爲 Int 的下標檢索到容器中的每一個元素
    subscript(i: Int) -> ItemType { get }
}
 
// // 遵循Container協議的泛型TOS類型
struct Stack<Element>: Container {
    // Stack<Element> 的原始實現部分
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // Container 協議的實現部分
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}
extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}
 
func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
        
        // 檢查兩個容器含有相同數量的元素
        if someContainer.count != anotherContainer.count {
            return false
        }
        
        // 檢查每一對元素是否相等
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        
        // 所有元素都匹配,返回 true
        return true
}
var tos = Stack<String>()
tos.push("google")
tos.push("runoob")
tos.push("taobao")
 
var aos = ["google", "runoob", "taobao"]
 
if allItemsMatch(tos, aos) {
    print("匹配所有元素")
} else {
    print("元素不匹配")
}

分析:
1,定義了一個 Container 協議 ,並且裏面有關聯類 突變函數 計算屬性 下標函數。
2,定義了一個Stack 結構體 泛型:Element 並且遵循 :Container 。

allItemsMatch:的白話文篇:c1 c2 兩個佔位符分別 遵循:Container協議,並且someContainer,anotherContainer的參數類型是c1 和 c2 返回的是Bool類型的值 ,然後c1的item 必須要和 c2的item 類型相同 並且 c1的item必須遵循Equatable 協議。

因爲someContainer 和 anotherContainer 是 c1 和 c2 的容器 並且someContainer,anotherContainer 包含相同的元素,由於c1的item 又遵循了:Equatable 協議。 所以我們可以用 (!=)來判斷他們的元素是否相等。

繁雜的不說了,我一個 大直男 真不知道 如何去形容 ,有時間單獨出一篇 關於where 的所有用法! 一點點內容 我寫了三個小時 我的腦子一直在想如何去形容 如何歸類 如何讓你們明白 但是 不會說話的我 還是 不行 ,繼續加油!鍛鍊說話的能力! 內心有一種抗拒感 ,就是 你張嘴 就怕別人說你 口臭 或者 聲音不好聽 等 畏懼!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章