懶得1步1步走樓梯,因而有了電梯;懶得走路,因而他們制造出了汽車、火車、飛機;懶得去計算,因而發(fā)現(xiàn)了計算器;懶得重復寫代碼,因而有了C++當中的泛型編程!
固然,上面那段話是我瞎掰的,真實情況可能完全不1樣,不過卻也能夠很好地引出今天所要講的內(nèi)容---C++中的泛型編程。其它的話也不多說了,開始進入正題吧!今天主要分析1下在泛型編程中的:1、模板函數(shù)&模板形參&函數(shù)重載 2、模板類 3、模板特化
等概念,最后再說1說模板分離編譯。
在沒有學習泛型編程,也不知道模板這個概念的時候,如果有人讓你寫1個通用的計算公式,具體1點就比如讓你實現(xiàn)1個通用的加法函數(shù),你會怎樣寫?通常我們會有以下幾種解決方法:
第1種,也是在沒有學習泛型編程的時候最容易想到的方法,使用函數(shù)重載。
既然第1種方法不好,那末我們來看1看第2種方法,使用公共基類,將通用的代碼放在公共的基礎類里面,這類方法也很容易理解,這里就不舉例子了,值得1提的是,這類方法也有缺點。首先:借助公共基類來編寫通用代碼,將失去類型檢查的優(yōu)點,其次:對以后實現(xiàn)的許多類,都必須繼承自某個特定的基類,代碼保護更加困難。
還有1種方法就是:用特殊的預處理程序,如:
不過這類方法局限性太大,而且我們不太提倡使用宏替換的方式,本身宏就有很多的缺點,雖然看上去簡潔明了,但是它既不進行參數(shù)類型檢測,同時它也不容易保護,安全性不高,總之,在C++當中能不使用宏的時候就盡可能不使用,最好多用const或inline關鍵字來起到宏的效果。
基于以上種種限制或說是缺點,C++當中的泛型編程也就應運而生。甚么是泛型編程呢?我們首先來了解1下甚么是泛型編程,簡單來概括1下:編寫與類型無關的邏輯代碼,是代碼復用的1種手段。換句話說泛型編程就是以獨立于任何特定類型的方式編寫代碼,而模板是泛型編程的基礎。同時又有1個概念模板,甚么是模板呢?來看1張圖吧:
函數(shù)模板
下面我們來1個1個分析,首先是函數(shù)模板,甚么是函數(shù)模板呢?函數(shù)模板:代表了1個函數(shù)家族,該函數(shù)與類型無關,在使用時被參數(shù)化,根據(jù)實參類型產(chǎn)生函數(shù)的特定類型版本。既然函數(shù)模板是1個獨立于類型的函數(shù),可以產(chǎn)生函數(shù)的特定類型版本的東西,那末它怎樣樣使用呢?
首先我們來看1下函數(shù)模板的格式:
typename是用來定義模板參數(shù)關鍵字,也能夠使用class。但是建議盡可能使用typename。注意:不能使用struct代替typename。
另外值得1提的是:模板函數(shù)也能夠定義為inline函數(shù)
說完了格式,自然我們需要用例子來簡單說明1下:
上面是1個簡單地使用函數(shù)模板的做加法運算的例子,總共4個輸出內(nèi)容,前面兩個沒有甚么問題。不過這里有個實參推演的概念。
實參推演:從函數(shù)實參肯定模板形參類型和值的進程稱為模板實參推斷,但是需要注意的是如果有多個參數(shù),多個類型形參的實參必須完全匹配。否則編譯就會出錯。
第3個輸出語句如果34.12前面沒有(int),結(jié)果編譯通不過,緣由是:你傳遞兩個不同類型參數(shù)給了函數(shù)模板,但是函數(shù)模板沒法肯定模板參數(shù)T的類型,所以編譯報錯。
但是為何是在編譯期間出錯了呢?換句話說就是函數(shù)模板在編譯期間究竟做了甚么事情呢?其實函數(shù)模板的編譯可以分為兩個進程(也能夠認為模板被編譯了兩次):
第1次在實例化之前,檢查模板代碼本身,查看是不是出現(xiàn)語法毛病,不過只是簡單地檢查,如:遺漏分號。
第2次在實例化期間,檢查模板代碼,查看是不是所有的調(diào)用都有效,如:實例化類型不支持某些函數(shù)調(diào)用。
(上面又提出了實例化的概念,模板是1個藍圖,它本身不是類或函數(shù),編譯器用模板產(chǎn)生指定的類或函數(shù)的特定類型版本,產(chǎn)生模板特定類型的進程稱為函數(shù)模板實例化)
如果我們這時候候在main函數(shù)之上再添加1個函數(shù):
那末在這時候候我們會想這次編譯器會去調(diào)用哪個函數(shù)呢,是通用類型轉(zhuǎn)化進而去調(diào)用我們后面定義的Add函數(shù),還是會調(diào)用函數(shù)模板產(chǎn)生1個新的函數(shù)呢?在這里會不會進行類型形參轉(zhuǎn)換呢?
1般不會轉(zhuǎn)換實參以匹配已有的實例化,相反會產(chǎn)生新的實例。
編譯器只會履行兩種轉(zhuǎn)換:
1、const轉(zhuǎn)換:接收const援用或const指針的函數(shù)可以分別用非const對象的援用或指針來調(diào)用
2、數(shù)組或函數(shù)到指針的轉(zhuǎn)換:如果模板形參不是援用類型,則對數(shù)組或函數(shù)類型的實參利用常規(guī)指針轉(zhuǎn)換。數(shù)組實參將當作指向其第1個元素的指針,函數(shù)實參當作指向函數(shù)類型的指針。
所以上面的問題很容易得出答案了,編譯器不會將1.2和2.5轉(zhuǎn)換為int型,從而調(diào)用已有的Add版本,而是重新合成1個double的版本,固然條件是能夠生成這么1個模板函數(shù)。如果這個模板函數(shù)沒法生成的話,那末只能調(diào)用已有的版本了。
函數(shù)模板有兩種類型參數(shù):模板參數(shù)和調(diào)用參數(shù),而模板參數(shù)又可以1分為2:
關于模板參數(shù),又有以下需要注意的地方:
1、模板形參名字只能在模板形參以后到模板聲明或定義的末尾之間使用,遵守名字屏蔽規(guī)則。
2、模板形參的名字在同1模板形參列表中只能使用1次
3、所有模板形參前面必須加上class或typename關鍵字修飾,(需要注意:在函數(shù)模板的內(nèi)部不能指定缺省的模板實參)
接著說1說非模板類型參數(shù)的概念。
甚么是非模板類型參數(shù)呢?非模板類型形參是模板內(nèi)部定義的常量,在需要常量表達式的時候,可使用非模板類型參數(shù)。比如我們可以將數(shù)組的長度指定為非模板類型參數(shù),來看下面1張圖:
在很多時候我們會遇到關于類型等價性的概念,甚么叫類型等價性呢?
1、模板形參表使用<>括起來
2、和函數(shù)參數(shù)表1樣,跟多個參數(shù)時必須用逗號隔開,類型可以相同也能夠不相同
3、模板形參表不能為空
4、模板形參可以是類型形參,也能夠是非類型新參,類型形參跟在class和typename后
5、模板類型形參可作為類型說明符用在模板中的任何地方,與內(nèi)置類型或自定義類型使用方法完全相同,可用于指定函數(shù)形參類型、返回值、局部變量和強迫類型轉(zhuǎn)換
6、模板形參表中,class和typename具有相同的含義,可以互換,使用typename更加直觀。但關鍵字typename是作為C++標準加入到C++中的,舊的編譯器可能不支持。
接下來就是模板函數(shù)重載,在這里我偷個懶,參考了1下網(wǎng)上的資料:
一樣做1個總結(jié):
1、1個非模板函數(shù)可以和1個同名的函數(shù)模板同時存在,而且該函數(shù)模板還可以被實例化為這個非模板函數(shù)。
2、對非模板函數(shù)和同名函數(shù)模板,如果其他條件都相同,在調(diào)動時會優(yōu)先調(diào)動非模板函數(shù)而不會從該模板產(chǎn)生出1個實例。如果模板可以產(chǎn)生1個具有更好匹配的函數(shù),
那末將選擇模板。
3、顯式指定1個空的模板實參列表,該語法告知編譯器只有模板才能來匹配這個調(diào)用,而且所有的模板參數(shù)都應當根據(jù)實參演繹出來。
4、模板函數(shù)不允許自動類型轉(zhuǎn)換,但普通函數(shù)可以進行自動類型轉(zhuǎn)換。
最后來了解1下模板函數(shù)特化
有時候其實不總是能夠?qū)懗鰧λ锌赡鼙粚嵗念愋投甲钸m合的模板,在某些情況下,通用模板定義對某個類型多是完全毛病的,或不能編譯,或做1些毛病的事情。
可以下面這樣來定義
模板函數(shù)特化情勢以下:
1、關鍵字template后面接1對空的尖括號<>
2、再接模板名和1對尖括號,尖括號中指定這個特化定義的模板形參
3、函數(shù)形參表
4、函數(shù)體
另外以下這兩點也需要注意1下:1、在模板特化版本的調(diào)用中,實參類型必須與特化版本函數(shù)的形參類型完全匹配,如果不匹配,編譯器將為實參模板定義中實例化1個實例。2、特化不能出現(xiàn)在模板實例的調(diào)用以后,應當在頭文件中包括模板特化的聲明,然后使用該特化版本的每一個源文件包括該頭文件。
模板類
進入到我們的模板類,來看1下怎樣使用模板類(以順序表作為例子):
普通順序表:
因此可以總結(jié)出模板類的1般格式:
【模板類的實例化】
只要有1種不同的類型,編譯器就會實例化出1個對應的類。
其實這里可以引出1個關于適配器的概念,有興趣的可以去了解1下。
模板的分離編譯
1般來說,類模版不能分離編譯,緣由得從模板的實例化入手。
1)以分離情勢寫出的模版類(以tem.h和tem.cpp為例,另外還有主函數(shù)main.cpp),在編譯main.cpp時由于只能看到模板聲明而看不到實現(xiàn),因此不會創(chuàng)建新的類型,但此時不會報錯,由于編譯器認為模板定義在其它文件中,就把問題留給鏈接程序處理。
2)編譯器在編譯tem.cpp時可以解析模板定義并檢查語法,但不能生成成員函數(shù)的代碼。由于要生成代碼,需要知道模板參數(shù),即需要1個類型,而不是模板本身。
3)這樣,鏈接程序在main.cpp 或 tem.cpp中都找不到新類型的定義,因而報出無定義成員的毛病。另外,實例化是惰性的,只有用到該函數(shù)時才會去對模版中的定義進行實例化。
所以模板在分離編譯的進程當中會產(chǎn)生鏈接出錯,通常是提示沒法解析的外部命令,有以下兩種解決方法:
1. 在模板頭文件 xxx.h 里面顯示實例化->模板類的定義后面添加 template class 名字<類型 >; 1般不推薦這類方法,1方面老編譯器可能不支持,另外一方面實例化依賴調(diào)用者。(不推薦)
2. 將聲明和定義放到1個文件 "xxx.hpp" 里面,推薦使用這類方法。
如果對模板的分離編譯感興趣,傳送門:http://blog.csdn.net/pongba/article/details/19130
最后總結(jié)1下模板
【優(yōu)點】
模板復用了代碼,節(jié)省資源,更快的迭代開發(fā),C++的標準模板庫(STL)因此而產(chǎn)生。
增強了代碼的靈活性。
【缺點】
模板讓代碼變得混亂復雜,不容易保護,編譯代碼時間變長。
出現(xiàn)模板編譯毛病時,毛病信息非常混亂,不容易定位毛病。