Swift(二十五)泛型

泛型代碼可以讓你寫出根據(jù)自我需求定義、適用于任何類型的锡宋,靈活且可重用的函數(shù)和類型儡湾。它可以讓你避免重復(fù)的代碼,用一種清晰和抽象的方式來表達代碼的意圖执俩。

泛型是 Swift 強大特征中的其中一個徐钠,許多 Swift 標準庫是通過泛型代碼構(gòu)建出來的。事實上役首,泛型的使用貫穿了整本語言手冊敷鸦,只是你沒有發(fā)現(xiàn)而已濒旦。例如,Swift 的數(shù)組和字典類型都是泛型集。你可以創(chuàng)建一個Int數(shù)組辆亏,也可創(chuàng)建一個String數(shù)組,或者甚至于可以是任何其他 Swift 的類型數(shù)據(jù)數(shù)組又活。同樣的兽泄,你也可以創(chuàng)建存儲任何指定類型的字典(dictionary),而且這些類型可以是沒有限制的乏屯。
定義一個交換量販int值得方法根时, 注意inout關(guān)鍵字,代表函數(shù)內(nèi)部可以修改函數(shù)外部的參數(shù)值

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

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(::)與swapTwoDoubles(::)如下功能:

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
}

您可能已經(jīng)注意到swapTwoInts(::)校辩,swapTwoStrings(::)和swapTwoDoubles(::)功能是相同的。唯一的區(qū)別是該值的辆童,他們接受的類型(Int宜咒,String,和Double)把鉴。

但實際應(yīng)用中通常需要一個用處更強大并且盡可能的考慮到更多的靈活性單個函數(shù)故黑,可以用來交換兩個任何類型值,很幸運的是庭砍,泛型代碼幫你解決了這種問題场晶。(一個這種泛型函數(shù)后面已經(jīng)定義好了。)

注意: 在所有三個函數(shù)中怠缸,a和b的類型是一樣的诗轻。如果a和b不是相同的類型,那它們倆就不能互換值揭北。Swift 是類型安全的語言扳炬,所以它不允許一個String類型的變量和一個Double類型的變量互相交換值。如果一定要做搔体,Swift 將報編譯錯誤鞠柄。

泛型函數(shù)

泛型函數(shù)可以工作于任何類型,這里是一個上面swapTwoInts函數(shù)的泛型版本嫉柴,用于交換兩個值:

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

swapTwoValues函數(shù)主體和swapTwoInts函數(shù)是一樣的厌杜,它只在第一行稍微有那么一點點不同于swapTwoInts,如下所示:

func swapTwoInts(_ a: inout Int, _ b: inout Int)

func swapTwoValues<T>(_ a: inout T, _ b: inout T)

這個函數(shù)的泛型版本使用了占位類型名字(通常此情況下用字母T來表示)來代替實際類型名(如In计螺、String或Doubl)夯尽。占位類型名沒有提示T必須是什么類型,但是它提示了a和b必須是同一類型T登馒,而不管T表示什么類型匙握。只有swapTwoValues函數(shù)在每次調(diào)用時所傳入的實際類型才能決定T所代表的類型。

另外一個不同之處在于這個泛型函數(shù)名后面跟著的展位類型名字(T)是用尖括號括起來的()陈轿。這個尖括號告訴 Swift 那個T是swapTwoValues函數(shù)所定義的一個類型圈纺。因為T是一個占位命名類型,Swift 不會去查找命名為T的實際類型麦射。

swapTwoValues函數(shù)除了要求傳入的兩個任何類型值是同一類型外蛾娶,也可以作為swapTwoInts函數(shù)被調(diào)用。每次swapTwoValues被調(diào)用潜秋,T所代表的類型值都會傳給函數(shù)蛔琅。

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" 

泛型類型

通常在泛型函數(shù)中,Swift 允許你定義你自己的泛型類型峻呛。這些自定義類罗售、結(jié)構(gòu)體和枚舉作用于任何類型辜窑,如同Array和Dictionary的用法。
這里展示了如何寫一個非泛型版本的棧寨躁,Int值型的棧:

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

上面所展現(xiàn)的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的泛型版本基本上和非泛型版本相同所禀,但是泛型版本的占位類型參數(shù)為Element代替了實際Int類型。這種類型參數(shù)包含在一對尖括號里(< Element >)话肖,緊隨在結(jié)構(gòu)體名字后面北秽。
T定義了一個名為“某種類型Element”的節(jié)點提供給后來用葡幸。這種將來類型可以在結(jié)構(gòu)體的定義里任何地方表示為“ Element”最筒。在這種情況下,Element在如下三個地方被用作節(jié)點:

  • 創(chuàng)建一個名為items的屬性床蜘,使用空的Element類型值數(shù)組對其進行初始化;
  • 指定一個包含一個參數(shù)名為item的push方法蔑水,該參數(shù)必須是Element類型丹擎;
  • 指定一個pop方法的返回值榜苫,該返回值將是一個Element類型值护戳。

當創(chuàng)建一個新單例并初始化時, 通過用一對緊隨在類型名后的尖括號里寫出實際指定棧用到類型垂睬,創(chuàng)建一個Stack實例媳荒,同創(chuàng)建Array和Dictionary一樣:
您創(chuàng)建一個新的Stack寫入存儲在尖括號內(nèi)堆棧中的類型實例。例如驹饺,創(chuàng)建一個新的字符串堆棧钳枕,你寫的Stack<String>():

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

從彈出堆棧中刪除一個值,并返回最高值赏壹,"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屬性返回類型的可選值Element田柔。如果堆棧是空的俐巴,topItem返回nil; 如果棧不為空,topItem將返回在最后一個項目items數(shù)組硬爆。

請注意欣舵,這個擴展沒有定義類型參數(shù)列表。相反缀磕,Stack類型的現(xiàn)有類型的參數(shù)名缘圈,Element時,在擴展中用于指示可選類型的topItem計算屬性袜蚕。

在topItem計算財產(chǎn)現(xiàn)在可以與任何使用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函數(shù)和Stack類型可以作用于任何類型,不過牲剃,有的時候?qū)κ褂迷诜盒秃瘮?shù)和泛型類型上的類型強制約束為某種特定類型是非常有用的遣疯。類型約束指定了一個必須繼承自指定類的類型參數(shù),或者遵循一個特定的協(xié)議或協(xié)議構(gòu)成凿傅。
類型約束語法
你可以寫一個在一個類型參數(shù)名后面的類型約束缠犀,通過冒號分割,來作為類型參數(shù)鏈的一部分聪舒。這種作用于泛型函數(shù)的類型約束的基礎(chǔ)語法如下所示(和泛型類型的語法相同):

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

上面這個假定函數(shù)有兩個類型參數(shù)辨液。第一個類型參數(shù)T,有一個需要T必須是SomeClass子類的類型約束箱残;第二個類型參數(shù)U滔迈,有一個需要U必須遵循SomeProtocol協(xié)議的類型約束。

類型約束行為
這里有個名為findStringIndex的非泛型函數(shù)被辑,該函數(shù)功能是去查找包含一給定String值的數(shù)組燎悍。若查找到匹配的字符串,findStringIndex函數(shù)返回該字符串在數(shù)組中的索引值(Int)敷待,反之則返回nil:

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

findStringIndex函數(shù)可以作用于查找一字符串數(shù)組中的某個字符串:

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ù)findIndex勾哩,用某個類型T值替換掉提到的字符串。

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

上面所寫的函數(shù)不會編譯举哟。這個問題的位置在等式的檢查上思劳,“if value == valueToFind”。不是所有的 Swift 中的類型都可以用等式符(==)進行比較妨猩。例如潜叛,如果你創(chuàng)建一個你自己的類或結(jié)構(gòu)體來表示一個復(fù)雜的數(shù)據(jù)模型,那么 Swift 沒法猜到對于這個類或結(jié)構(gòu)體而言“等于”的意思。正因如此威兜,這部分代碼不能可能保證工作于每個可能的類型T销斟,當你試圖編譯這部分代碼時估計會出現(xiàn)相應(yīng)的錯誤。

不過椒舵,所有的這些并不會讓我們無從下手蚂踊。Swift 標準庫中定義了一個Equatable協(xié)議,該協(xié)議要求任何遵循的類型實現(xiàn)等式符(==)和不等符(!=)對任何兩個該類型進行比較笔宿。所有的 Swift 標準類型自動支持Equatable協(xié)議犁钟。

任何Equatable類型都可以安全的使用在findIndex函數(shù)中,因為其保證支持等式操作泼橘。為了說明這個事實涝动,當你定義一個函數(shù)時,你可以寫一個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
}

findIndex中這個單個類型參數(shù)寫做:T: Equatable炬灭,也就意味著“任何T類型都遵循Equatable協(xié)議”醋粟。

findIndex函數(shù)現(xiàn)在則可以成功的編譯過,并且作用于任何遵循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 is not in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昔穴,一起剝皮案震驚了整個濱河市镰官,隨后出現(xiàn)的幾起案子提前,更是在濱河造成了極大的恐慌,老刑警劉巖泳唠,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狈网,死亡現(xiàn)場離奇詭異,居然都是意外死亡笨腥,警方通過查閱死者的電腦和手機拓哺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脖母,“玉大人士鸥,你說我怎么就攤上這事∽患叮” “怎么了烤礁?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肥照。 經(jīng)常有香客問我脚仔,道長,這世上最難降的妖魔是什么舆绎? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任鲤脏,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘猎醇。我一直安慰自己窥突,他們只是感情好,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布硫嘶。 她就那樣靜靜地躺著波岛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪音半。 梳的紋絲不亂的頭發(fā)上则拷,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音曹鸠,去河邊找鬼煌茬。 笑死,一個胖子當著我的面吹牛彻桃,可吹牛的內(nèi)容都是我干的坛善。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼邻眷,長吁一口氣:“原來是場噩夢啊……” “哼眠屎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肆饶,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤改衩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后驯镊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體葫督,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年板惑,在試婚紗的時候發(fā)現(xiàn)自己被綠了橄镜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡冯乘,死狀恐怖洽胶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情裆馒,我是刑警寧澤姊氓,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站领追,受9級特大地震影響他膳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绒窑,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一棕孙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦蟀俊、人聲如沸钦铺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矛洞。三九已至,卻和暖如春烫映,著一層夾襖步出監(jiān)牢的瞬間沼本,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工锭沟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抽兆,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓族淮,卻偏偏與公主長得像辫红,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子祝辣,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 本章將會介紹 泛型所解決的問題泛型函數(shù)類型參數(shù)命名類型參數(shù)泛型類型擴展一個泛型類型類型約束關(guān)聯(lián)類型泛型 Where...
    寒橋閱讀 634評論 0 2
  • 泛型(Generics) 泛型代碼允許你定義適用于任何類型的,符合你設(shè)置的要求的,靈活且可重用的 函數(shù)和類型贴妻。泛型...
    果啤閱讀 671評論 0 0
  • 136.泛型 泛型代碼讓你可以寫出靈活,可重用的函數(shù)和類型,它們可以使用任何類型,受你定義的需求的約束。你可以寫出...
    無灃閱讀 1,466評論 0 4
  • 泛型的概念 泛型代碼可根據(jù)自定義需求蝙斜,寫出適用于任何類型名惩、靈活且可重用的函數(shù)和類型,避免重復(fù)的代碼乍炉,用一種清晰和抽...
    伯wen閱讀 400評論 0 2
  • 聽說绢片,家中的狗死了滤馍,是被車撞的岛琼。 疼痛來的很快,想必結(jié)束也很快巢株, 可是悲傷槐瑞,留給了活著的—— 生活就是這樣造孽。 ...
    S_Faint閱讀 551評論 1 1