打算給公司的同事上課,以便推廣 C++ 模板,寫了以下教程。希望能看到各種意見,以便完善它。
C++ 模板基礎(chǔ)談
1. 什么是模板
模板定義:模板就是實現(xiàn)代碼重用機(jī)制的一種工具,它可以實現(xiàn)類型參數(shù)化,即把類型定義為參數(shù),從而實現(xiàn)了真正的代碼可重用性。
我們知道,C++ 是一種“強(qiáng)類型”的語言,也就是說一個變量,編譯器必須確切的知道它的類型,而模板就是構(gòu)建在這個強(qiáng)類型語言基礎(chǔ)上的泛型系統(tǒng)。
2. 模板的語法
模板函數(shù)
template< typename {類型參數(shù)名稱}, [ int {Name}=...][, ...] >
{函數(shù)定義}
模板類
template< typename ... , [ int {Name}=...] >
class ...
模板的參數(shù)可以是類型,或者是一個 int 型的值(或者可以轉(zhuǎn)換為int 型的,比如 bool)。
3. 模板的使用
顯式類型參數(shù):對于模板函數(shù),在函數(shù)名后添加 < {類型參數(shù)表} >。對于模板類,在類后添加 < {類型參數(shù)表} >
隱式類型參數(shù):對于模板函數(shù),如果類型參數(shù)可以推導(dǎo),那么可以省略類型參數(shù)表
舉個例子:
template< typename T >
T max( T a, T b )
{
return a < b ? b : a;
}
這個 max 函數(shù)就是一個模板函數(shù),它可以傳入一個 “類型”的參數(shù),以便實現(xiàn)任意類型求最大值的效果。假設(shè)我們這樣使用它:
int x=5, y=10;
int z=max<int>( x, y );
這時候發(fā)生了什么呢?我們傳入的“類型參數(shù)”是int,因此編譯器在編譯這段代碼時會使用 int 來構(gòu)造一個新函數(shù):
int max( int a, int b )
{
return a < b ? b : a;
}
后面的事就和編譯普通的函數(shù)一樣了,C++編譯器繼續(xù)使用強(qiáng)類型系統(tǒng)編譯這個函數(shù),由強(qiáng)類型系統(tǒng)來檢查這個函數(shù)是否正確。
這個過程叫做模板的“特化”,它發(fā)生在編譯期,當(dāng)編譯器發(fā)現(xiàn)模板函數(shù)、模板類被使用(注意,不是定義)的時候進(jìn)行的。這個系統(tǒng)實際上比較像宏,但是比宏更為智能。
很明顯,編譯器必須知道模板如何特化這個函數(shù),因此模板函數(shù)的實現(xiàn),必須在“使用點”之前,因此模板庫只能通過頭文件庫的形式來提供。
4. 模板的類型推導(dǎo)
對于函數(shù),編譯器是知道傳入?yún)?shù)的類型的,比如上面的max,max< ? >( x, y ),由于第一個參數(shù) x 是 int 類型的,那么 ? 這里需要填寫什么呢?
我們可以很明顯的推斷出應(yīng)該是 "int",否則,后面的強(qiáng)類型系統(tǒng)將無法編譯這個函數(shù)。編譯器同樣知道 x 的類型,因此它也能推導(dǎo)出“類型參數(shù)”,這時候我們調(diào)用時就可省略模板參數(shù)了。
這個推導(dǎo)是按順序來的,因此如果上面的 y 是其他類型,? 仍然會被推導(dǎo)為 int,如果y無法隱性轉(zhuǎn)換為int,強(qiáng)類型編譯時就會報錯。
5. 類型推導(dǎo)的隱式類型轉(zhuǎn)換
在決定模板參數(shù)類型前,編譯器執(zhí)行下列隱式類型轉(zhuǎn)換:
左值變換
修飾字轉(zhuǎn)換
派生類到基類的轉(zhuǎn)換
見《C++ Primer》([注2],P500)對此主題的完備討論。
簡而言之,編譯器削弱了某些類型屬性,例如我們例子中的引用類型的左值屬性。舉例來說,編譯器用值類型實例化函數(shù)模板,而不是用相應(yīng)的引用類型。
同樣地,它用指針類型實例化函數(shù)模板,而不是相應(yīng)的數(shù)組類型。
它去除const修飾,絕不會用const類型實例化函數(shù)模板,總是用相應(yīng)的非 const類型,不過對于指針來說,指針和 const 指針是不同的類型。
底線是:自動模板參數(shù)推導(dǎo)包含類型轉(zhuǎn)換,并且在編譯器自動決定模板參數(shù)時某些類型屬性將丟失。這些類型屬性可以在使用顯式函數(shù)模板參數(shù)申明時得以保留。
6. 模板的偏特化
如果我們打算給模板函數(shù)(類)的某個特定類型寫一個函數(shù),就需要用到模板的偏特化,比如我們打算用 long 類型調(diào)用 max 的時候,返回小的值(原諒我舉了不恰當(dāng)?shù)睦樱?br>template<> // 這代表了下面是一個模板函數(shù)
long max<long>( long a, long b ) // 對于 vc 來說,這里的 <long> 是可以省略的
{
return a > b ? b : a;
}
實際上,所謂偏特化,就是代替編譯器完成了對指定類型的特化工作,現(xiàn)代的模板庫中,大量的使用了這個技巧。
7. 仿函數(shù)
仿函數(shù)這個詞經(jīng)常會出現(xiàn)在模板庫里(比如 STL),那么什么是仿函數(shù)呢?
顧名思義:仿函數(shù)就是能像函數(shù)一樣工作的東西,請原諒我用東西這樣一個代詞,下面我會慢慢解釋。
void dosome( int i )
這個 dosome 是一個函數(shù),我們可以這樣來使用它: dosome(5);
那么,有什么東西可以像這樣工作么?
答案1:重載了 () 操作符的對象,比如:
struct DoSome
{
void operator()( int i );
}
DoSome dosome;
這里類(對 C++ 來說,struct 和類是相同的) 重載了 () 操作符,因此它的實例 dosome 可以這樣用 dosome(5); 和上面的函數(shù)調(diào)用一模一樣,不是么?所以 dosome 就是一個仿函數(shù)了。
實際上還有答案2:
函數(shù)指針指向的對象。
typedef void( *DoSomePtr )( int );
typedef void( DoSome )( int );
DoSomePtr *ptr=&func;
DoSome& dosome=*ptr;
dosome(5); // 這里又和函數(shù)調(diào)用一模一樣了。
當(dāng)然,答案3 成員函數(shù)指針指向的成員函數(shù)就是意料之中的答案了。
8. 仿函數(shù)的用處
不管是對象還是函數(shù)指針等等,它們都是可以被作為參數(shù)傳遞,或者被作為變量保存的。因此我們就可以把一個仿函數(shù)傳遞給一個函數(shù),由這個函數(shù)根據(jù)需要來調(diào)用這個仿函數(shù)(有點類似回調(diào))。
STL 模板庫中,大量使用了這種技巧,來實現(xiàn)庫的“靈活”。
比如:
for_each, 它的源代碼大致如下:
template< typename Iterator, typename Functor >
void for_each( Iterator begin, Iterator end, Fucntor func )
{
for( ; begin!=end; begin++ )
func( *begin );
}
這個 for 循環(huán)遍歷了容器中的每一個元素,對每個元素調(diào)用了仿函數(shù) func,這樣就實現(xiàn)了 對“每個元素做同樣的事”這樣一種編程的思想。
特別的,如果仿函數(shù)是一個對象,這個對象是可以有成員變量的,這就讓 仿函數(shù)有了“狀態(tài)”,從而實現(xiàn)了更高的靈活性。
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請
點擊舉報。