泛型歷史和概述
泛型發(fā)展
- 泛型程序最早出現(xiàn)1970年代的CLU和Ada語(yǔ)言中,后來(lái)被許多基于對(duì)象和面向?qū)ο蟮恼Z(yǔ)言所采用,包括BETA、 C++、D和Eiffel等。1993年C++在3.0版中引入的模版技術(shù)就屬于泛型編程,1994年7月ANSI/ISO C++標(biāo)準(zhǔn)委 員會(huì)通過(guò)的STL更是泛型編程的集大成者,它已被納入1998年9月的C++標(biāo)準(zhǔn)之中。Java于2004年9月在J2SE 5.0(JDK 1.5)中開始使用泛型技術(shù);C# 2.0和Visual Basic .NET 2005也于2005年11月采用了在微軟.NET框 架2.0版中所引入的泛型方法。
- 1971年,Dave Musser首先提出并推廣了泛型編程的理論,但是主要局限于軟件開發(fā)和計(jì)算機(jī)代數(shù)領(lǐng)域。 1979年,Alexander Stepanov開始研究泛型編程,認(rèn)識(shí)到泛型編程的巨大潛力,提出了STL的體系結(jié)構(gòu)。
- 1993年11月,受貝爾實(shí)驗(yàn)室的Andrew Koening 的邀請(qǐng),Stepanov在ANSI/ISO C++標(biāo)準(zhǔn)委員會(huì)的會(huì)議上, 介紹了泛型編程的理論和他們的工作。Stepanov和Meng Lee按委員會(huì)的要求,于1994年3月提出了STL的草 案。委員會(huì)提出了一些修改意見(jiàn),其中最重要的是對(duì)關(guān)聯(lián)容器的擴(kuò)充,由Musser完成了擴(kuò)充部分的實(shí)現(xiàn)工作。 1994年7月,ANSI/ISO C++標(biāo)準(zhǔn)委員會(huì)終于通過(guò)了修改后的STL方案。
C++泛型
- 面對(duì)對(duì)象庫(kù)
MFC - 模板庫(kù)
STL
Boost
template vs generic
模板是C++泛型編程的基礎(chǔ)。
泛型更來(lái)指一種編程思想。
為什么需要泛型
-
下面的 swapTwoInts(::) 是一個(gè)標(biāo)準(zhǔn)的非泛型函數(shù),用于交換兩個(gè) Int 值
01 -
如果你想交換兩個(gè) String 值,或者兩個(gè) Double 值,你只能再寫更多的函數(shù),比如下面的 swapTwoStrings(::) 和 swapTwoDoubles(::) 函數(shù):
02 swapTwoInts(::) 、 swapTwoStrings(::) 、 swapTwoDoubles(::) 函數(shù)體是一樣的。 唯一的區(qū)別是它們接收值類型不同( Int 、 String 和 Double )。
泛型函數(shù)
泛型函數(shù)定義
- 泛型函數(shù)可以用于任何類型。這里是上面提到的 swapTwoInts(::) 函數(shù)的泛型版本,叫做 swapTwoValues(::)
03
類型形式參數(shù)
- 上面的 swapTwoValues(::) 中,占位符類型 T 就是一個(gè)類型形式參數(shù)的例子。類型形式參數(shù)指 定并且命名一個(gè)占位符類型,緊挨著寫在函數(shù)名后面的一對(duì)尖括號(hào)里(比如 <T> )。
- 一旦你指定了一個(gè)類型形式參數(shù),你就可以用它定義一個(gè)函數(shù)形式參數(shù)(比如 swapTwoValues(::) 函數(shù)中的形式參數(shù) a 和 b )的類型,或者用它做函數(shù)返回值類型,或者做 函數(shù)體中類型標(biāo)注。在不同情況下,用調(diào)用函數(shù)時(shí)的實(shí)際類型來(lái)替換類型形式參數(shù)。(上面的 swapTwoValues(::) 例子中,第一次調(diào)用函數(shù)的時(shí)候用 Int 替換了 T ,第二次調(diào)用是用 String 替換的。)
- 你可以通過(guò)在尖括號(hào)里寫多個(gè)用逗號(hào)隔開的類型形式參數(shù)名,來(lái)提供更多類型形式參數(shù)。
命名類型形式參數(shù)
- 大多數(shù)情況下,類型形式參數(shù)的名字要有描述性,比如 Dictionary<Key, Value> 中的 Key 和 Value ,借此告知讀者類型形式參數(shù)和泛型類型、泛型用到的函數(shù)之間的關(guān)系。但是,他們之 間的關(guān)系沒(méi)有意義時(shí),一般按慣例用單個(gè)字母命名,比如 T 、 U 、 V ,比如上面的 swapTwoValues(::) 函數(shù)中的 T 。
- 類型形式參數(shù)永遠(yuǎn)用大寫開頭的駝峰命名法(比如 T 和 MyTypeParameter )命名,以指明它 們是一個(gè)類型的占位符,不是一個(gè)值。
泛型類型
-
除了泛型函數(shù),Swift允許你定義自己的泛型類型。它們是可以用于任意類型的自定義類、結(jié)構(gòu) 體、枚舉,和 Array 、 Dictionary 方式類似。
04
IntStack

泛型Stack



擴(kuò)展泛型類型
-
當(dāng)你擴(kuò)展一個(gè)泛型類型時(shí),不需要在擴(kuò)展的定義中提供類型形式參數(shù)列表。原始類型定義的類 型形式參數(shù)列表在擴(kuò)展體里仍然有效,并且原始類型形式參數(shù)列表名稱也用于擴(kuò)展類型形式參 數(shù)。
09
類型約束
- swapTwoValues(::) 函數(shù)和 Stack 類型可以用于任意類型。但是,有時(shí)在用于泛型函數(shù)的類型和 泛型類型上,強(qiáng)制其遵循特定的類型約束很有用。類型約束指出一個(gè)類型形式參數(shù)必須繼承自特定 類,或者遵循一個(gè)特定的協(xié)議、組合協(xié)議。
- 例如,Swift的 Dictionary 類型在可以用于字典中鍵的類型上設(shè)置了一個(gè)限制。如字典中描述的一 樣,字典鍵的類型必須是是可哈希的。也就是說(shuō),它必須提供一種使其可以唯一表示的方法。 Dictionary 需要它的鍵是可哈希的,以便它可以檢查字典中是否包含一個(gè)特定鍵的值。沒(méi)有了這個(gè)要 求, Dictionary 不能區(qū)分該插入還是替換一個(gè)指定鍵的值,也不能在字典中查找已經(jīng)給定的鍵的值。
-
在一個(gè)類型形式參數(shù)名稱后面放置一個(gè)類或者協(xié)議作為形式參數(shù)列表的一部分,并用冒號(hào)隔 開,以寫出一個(gè)類型約束。下面展示了一個(gè)泛型函數(shù)類型約束的基本語(yǔ)法(和泛型類型的語(yǔ)法 相同):
10
類型約束的應(yīng)用
-
這是一個(gè)叫做 findIndex(ofString:in:) 的非泛型函數(shù),在給定的 String 值數(shù)組中查找給定的 String 值。 findIndex(ofString:in:) 函數(shù)返回一個(gè)可選的 Int 值,如果找到了給定字符串,它 會(huì)返回?cái)?shù)組中第一個(gè)匹配的字符串的索引值,如果找不到給定字符串就返回 nil :
11 -
這里寫出了一個(gè)叫做 findIndex(of:in:) 的函數(shù),可能是你期望的 findIndex(ofString:in:) 函數(shù)的一個(gè) 泛型版本。注意,函數(shù)的返回值仍然是 Int? ,因?yàn)楹瘮?shù)返回一個(gè)可選的索引數(shù)字,而不是數(shù)組里的一 個(gè)可選的值。這個(gè)函數(shù)沒(méi)有編譯
12 Swift 標(biāo)準(zhǔn)庫(kù)中定義了一個(gè)叫做 Equatable 的協(xié)議,要求遵循其協(xié)議的類型要實(shí)現(xiàn)相等操作符( == )和不 等操作符( != ),用于比較該類型的任意兩個(gè)值。所有Swift標(biāo)準(zhǔn)庫(kù)中的類型自動(dòng)支持 Equatable 協(xié)議。
-
任何 Equatable 的類型都能安全地用于 findIndex(of:in:) 函數(shù),因?yàn)榭梢员WC那些類型支持相等操作符。 為了表達(dá)這個(gè)事實(shí),當(dāng)你定義函數(shù)時(shí)將 Equatable 類型約束作為類型形式參數(shù)定義的一部分書寫:
13
關(guān)聯(lián)類型
- 定義一個(gè)協(xié)議時(shí),有時(shí)在協(xié)議定義里聲明一個(gè)或多個(gè)關(guān)聯(lián)類型是很有用的。關(guān)聯(lián)類型給協(xié)議中 用到的類型一個(gè)占位符名稱。直到采納協(xié)議時(shí),才指定用于該關(guān)聯(lián)類型的實(shí)際類型。關(guān)聯(lián)類型 通過(guò) associatedtype 關(guān)鍵字指定。
關(guān)聯(lián)類型的應(yīng)用
- 這個(gè)協(xié)議沒(méi)有指定元素如何儲(chǔ)存在容器中,也沒(méi)指定允許存入容器的元素類型。協(xié)議僅僅指定了想成為一個(gè) Container 的類型,必須提 供的三種功能。遵循該協(xié)議的類型可以提供其他功能,只要滿足這三個(gè)要求即可。
- 任何遵循 Container 協(xié)議的類型必須能指定其存儲(chǔ)值的類型。尤其是它必須保證只有正確類型的元素才能添加到容器中,而且該類型下 標(biāo)返回的元素類型必須是正確的。
-
為了定義這些要求, Container 協(xié)議需要一種在不知道容器具體類型的情況下,引用該容器將存儲(chǔ)的元素類型的方法。 Container 協(xié)議 需要指定所有傳給 append(_:) 方法的值必須和容器里元素的值類型是一樣的,而且容器下標(biāo)返回的值也是和容器里元素的值類型相同。
14


關(guān)聯(lián)類型的約束
-
你可以在協(xié)議里給關(guān)聯(lián)類型添加約束來(lái)要求遵循的類型滿足約束。
17
在關(guān)聯(lián)類型約束里使用協(xié)議
-
協(xié)議可以作為它自身的要求出現(xiàn)。
18
泛型 where 子句
- 如類型約束中描述的一樣,類型約束允許你在泛型函數(shù)或泛型類型相關(guān)的類型形式參數(shù)上定義要求。
- 類型約束在為關(guān)聯(lián)類型定義要求時(shí)也很有用。通過(guò)定義一個(gè)泛型 Where 子句來(lái)實(shí)現(xiàn)。泛型 Where 子句讓你能 夠要求一個(gè)關(guān)聯(lián)類型必須遵循指定的協(xié)議,或者指定的類型形式參數(shù)和關(guān)聯(lián)類型必須相同。泛型 Where 子句 以 Where 關(guān)鍵字開頭,后接關(guān)聯(lián)類型的約束或類型和關(guān)聯(lián)類型一致的關(guān)系。泛型 Where 子句寫在一個(gè)類型或 函數(shù)體的左半個(gè)大括號(hào)前面。
C1 必須遵循 Container 協(xié)議(寫作 C1: Container );
C2 也必須遵循 Container 協(xié)議(寫作 C2: Container );
C1 的 ItemType 必須和 C2 的 ItemType 相同(寫作 C1.ItemType == C2.ItemType ); C1 的 ItemType 必須遵循 Equatable 協(xié)議(寫作 C1.ItemType: Equatable )。

someContainer 是一個(gè) C1 類型的容器;
anotherContainer 是一個(gè) C2 類型的容器;
someContainer 和 anotherContainer 中的元素類型相同; someContainer 中的元素可以通過(guò)不等操作符( != )檢查它們是否不一樣。
帶有泛型 Where 子句的擴(kuò)展
-
你同時(shí)也可以使用泛型的 where 子句來(lái)作為擴(kuò)展的一部分。
20


關(guān)聯(lián)類型的泛型 Where 子句
-
你可以在關(guān)聯(lián)類型中包含一個(gè)泛型 where 子句。比如說(shuō),假定你想要做一個(gè)包含遍歷器的 Container ,比如標(biāo)準(zhǔn)庫(kù)中 Sequence 協(xié)議那樣。
23
泛型下標(biāo)
- 下標(biāo)可以是泛型,它們可以包含泛型 where 分句。你可以在 subscript 后用尖括號(hào)來(lái)寫類型占 位符,你還可以在下標(biāo)代碼塊花括號(hào)前寫泛型 where 分句。
- 在尖括號(hào)中的泛型形式參數(shù) Indices 必須是遵循標(biāo)準(zhǔn)庫(kù)中 Sequence 協(xié)議的某類型;
- 下標(biāo)接收單個(gè)形式參數(shù), indices ,它是一個(gè) Indices 類型的實(shí)例;
- 泛型 where 分句要求序列的遍歷器必須遍歷 Int 類型的元素。這就保證了序列中的索引都是作為容器索引的相同類型。
-
合在一起,這些限定意味著傳入的 indices 形式參數(shù)是一個(gè)整數(shù)的序列。
24
泛型編程思維
- 面向過(guò)程的編程,可以將常用代碼段封裝在一個(gè)函數(shù)中,然后通過(guò)函數(shù)調(diào)用來(lái)達(dá)到目標(biāo)代碼重用的 目的。面向?qū)ο蟮姆椒?,則可以通過(guò)類的繼承來(lái)實(shí)現(xiàn)(對(duì)象的目標(biāo))代碼的重用。
- 如果需要寫一個(gè)可用于不同數(shù)據(jù)類型的算法,可以采用的方法有:
面向過(guò)程——對(duì)源代碼進(jìn)行復(fù)制和修改,生成不同數(shù)據(jù)類型版本的算法函數(shù),調(diào)用時(shí)需要對(duì)數(shù)據(jù) 類型進(jìn)行手工的判斷;
面向?qū)ο蟆梢栽谝粋€(gè)類中,編寫多個(gè)同名函數(shù),它們的算法一致,但是所處理數(shù)據(jù)的類型不 同,當(dāng)然函數(shù)的輸入?yún)?shù)類型也不同,可通過(guò)函數(shù)重載來(lái)自動(dòng)調(diào)用對(duì)應(yīng)數(shù)據(jù)類型版本的函數(shù)。
前面兩種方法都需編寫了多個(gè)相同算法的不同函數(shù),不能做到代碼重用。它們二者之間的主要差 別,只是調(diào)用的方便與否。
如果采用泛型編程(例如可采用以類型作為參數(shù)的傳統(tǒng)C++的模板技術(shù)),就可以做到源代碼級(jí) 的重用:
泛型編程——編寫以類型作為參數(shù)的一個(gè)模板函數(shù),在調(diào)用時(shí)再將參數(shù)實(shí)例化為具體的數(shù)據(jù)類 型。泛型編程是一種面向算法的多態(tài)技術(shù)。
在計(jì)算機(jī)科學(xué)中,泛型(generic)是一種允許一個(gè)值取不同數(shù)據(jù)類型(所謂多態(tài))的技術(shù),強(qiáng) 調(diào)使用這種技術(shù)的編程風(fēng)格被稱為泛型編程(generic programming通用編程/類屬編程)。
泛型編程研究對(duì)軟件組件的系統(tǒng)化組織。目標(biāo)是推出一種針對(duì)算法、數(shù)據(jù)結(jié)構(gòu)和內(nèi)存分配機(jī)制的 分類方法,以及其他能夠帶來(lái)高度可重用性、模塊化和可用性的軟件工具。
與針對(duì)問(wèn)題和數(shù)據(jù)的面向?qū)ο蟮姆椒ú煌?,泛型編程中?qiáng)調(diào)的是算法。是一類通用的參數(shù)化算法, 它們對(duì)各種數(shù)據(jù)類型和各種數(shù)據(jù)結(jié)構(gòu)都能以相同的方式進(jìn)行工作,從而實(shí)現(xiàn)源代碼級(jí)的軟件重用。
例如,不管(容器)是數(shù)組、隊(duì)列、鏈表、還是堆棧,不管里面的元素(類型)是字符、整數(shù)、浮 點(diǎn)數(shù)、還是對(duì)象,都可以使用同樣的(迭代器)方法來(lái)遍歷容器內(nèi)的所有元素、獲取指定元素的 值、添加或刪除元素,從而實(shí)現(xiàn)排序、檢索、復(fù)制、合并等各種操作和算法。
泛型編程的通用化算法,是建立在各種抽象化基礎(chǔ)之上的:利用參數(shù)化模版來(lái)達(dá)到數(shù)據(jù)類型的抽象 化、利用容器和迭代器來(lái)達(dá)到數(shù)據(jù)結(jié)構(gòu)的抽象化、利用分配器和適配器來(lái)達(dá)到存儲(chǔ)分配和界面接口 的抽象化。














