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