寫在前面:
0. 所謂泛型編程就是獨立於任何特定類型的方式編寫代碼,使用泛型程序時,需要提供具體陳旭實例所操作的類型或者值。我們經常用到STL容器、迭代器、和算法都是泛型編程的例子;
普通定義形式
templateint func(const T &a1, const T &a2) { ... } template inline int func(const T &a1, const T&a2) { ... } tempalte T1 func(const T2 &t2, const T3 &t3) { ... } //調用方法 func (i, log);
如果類型的定義順序與調用順序不一樣的話, 則需要在申明的時候制定類型順序;
tempalteT3 func(const T1 &t1, T2 &t2) { ... } //調用方法 func (12, 34);
//定義方式 template模板形參表不能為空; 由編譯器根據實參列表實例化模板, 模板在實例化的時候會對參數類型進行語法檢查; 作用域由聲明之後直到模板聲明或定義的末尾處使用; 模板形參的名字只能在同一模板形參表中使用一次; 聲明和定義的模板形參名稱可以不一樣; 模板類型形參可以作為類型說明符用在模板中任何地方,與內置類型說明符或類類型說明符的使用方式完全相同; 對模板的非類型形參而言, 求值結果相同的表達式將認為是等價的,調用相同的實例; 編寫模板代碼時,對實參類型的要求盡可能少;-class Queue { ... ... } //使用方法 Queue qi;
templatevoid func(T (&parm)[N]) { //此時,N將直接用值代替 }
發現錯誤一般分為三個階段:
1. 編譯模板定義本身,可以檢測模板本身的語法問題,例如漏掉分號,拼寫錯誤等;
2. 編譯器見到模板的使用時,檢測參數個數、類型是否合法;
3. 模板實例化期間,檢測類型相關的錯誤;
類模板在引用實例類模板類類型時實例化,函數模板在調用它或者用它對函數指針進程初始化或者賦值時實例化,在使用函數模板時,編譯器通常會為我們推斷模板實參;
當編譯器看到模板定義時,不立即產生代碼, 只有在看到模板調用時,編譯器才會產生對應的實例,類型相關的錯誤才會被檢查出來。
通常情況下,實例化一個對象或者調用一個函數時,編譯器不需要看到函數或者類的定義,只有在連接的時候才會去關心類或者函數的定義。但是模板不一樣, 編譯器在實例化模板時,必須看到模板的定義才會編譯通過。
//header file #ifndef xx_H_ #define XX_H_ templateint func(T &t1, T&t2); #include "oo.cpp" //模板定義文件 #endif //oo.cpp template int func(T &t1, T&t2) { ... }
templateret-type Queue ::member_func_name { //define }
templateclass Screen { public: Screen():{} private: std::string screen; std::string::size_type cursor; std::string::size_type height, width; } //實例化方法,參數必須是編譯時常量表達式 Screen<24, 80> hp2621;
templateclass Bar { friend class FooBar; ... }
FooBar 的成員可以訪問Bar類任意實例的private &protected 成員。
一般模板友元關系templateclass Bar { template friend class Fool; template friend void templ_fcnt(const T&); ... }
表示Fool和templ_fcnt的任意實例都可以訪問Bar的任意實例的private和protected成員。
特定模板友元關系模板類只授權對特定友元實例的訪問權
templateclass Foo2; template class Bar { friend class Foo2 ; friend void templ_fcnt (char *const &); }
更通用的形式
templateclass Foo2; template void templ_fcnt(const T&); template class Bar { friend class Foo2 ; friend void templ_fcnt (const Type &); ... }
這樣每個類型的類模板實例與對應的類型友元建立了一一映射關系。
- 聲明依賴性
如果模板類授權給所有友元實例訪問private和protected成員時, 編譯器將友元聲明當做類或者函數的聲明對待;但是如果指定到特定類型時,必須在前面聲明類或者函數。參考上面特定模板友元關系 和 一般友元關系 的聲明。
同時,如果沒有提前告訴編譯器該友元是一個模板,編譯器則認為友元是一個普通非模板函數或者非模板類。
這個名字確實有點繞, 其本質意思就是模板類的成員函數也希望有自己的參數類型,看如下例子:
templateclass Queue { public: template Queue(It begin, It end): head(0), tail(0) { copy_elems(beg, end); } template void assign(Iter , Iter); private: template void copy_elems(Iter, Iter); }
在類模板的外部定義模板成員,必須包含類模板的形參和模板成員的模板形參:
template//類模板的形參 tmeplate //成員模板形參 void Queue ::assign(Iter begin, Iter end) { ... }
與其他成員一樣,成員模板也只有在被使用的時候才會實例化。
templateclass Bar { public: static std::size_t count(){return ctr}; private: static std::size_t ctr; }
實例化原則是:相同類型的實例共享一個static成員,例如Bar 類型的實例共享一個static 成員ctr,Bar 類型的實例共享一個static成員ctr;
- 使用方法
Bar初始化方法bar1, bar2; size_t ct = Bar ::count();
templatesize_t Foo ::ctr = 0;
由於模板的定義中,其操作都是依賴實例化的類型是否支持該操作或者操作的結果與預期是否相匹配,例如:
templateint compare(const Type& t1, const Type &t2) { if(t1 > t2) return 1; if(t1 < t2> return -1; return 0; }
在上面的例子中,如果用char* 去實例化模板時,函數將比較兩個指針,很明顯與預期的記過不相吻合。此時可以通過模板特例話來解決。
函數模板特例化形式如下:
- 關鍵字template 後面接一對空的尖括號(<>);
- 在接末班嗎和一堆尖括號,尖括號中制定這個特化定義的模板形參;
- 函數形參表;
- 函數體。
例如:
template <> int compare(const char *t1, const char *t2) { return strcmp(t1, t2); }
如果有多個模板形參,則依次排列即可。
template <> class Queue{ ... }
需要在類特化的外部定義普通成員函數時,成員之前不能加 template<>標記:
void Queue特化成員而不特化類::push(const char* val) { ... }
template <> void Queue::push(const char* const &val) { ... } template<> void Queue ::pop() { ... }
現在,類類型Queue
templateclass tem { ... }; //partial specialization :fixes T2 as int and allows T2 to vary. template class tem { }
使用方法:
temfoo; //調用普通的類模板 tem bar; //調用偏例化版本
函數模板可以重載:可以定義有相同名字但形參數據或類型不同的多個函數模板, 也可以定義與函數模板有相同名字的普通非模板函數。
不過從實踐來看,設計既包含函數模板又包含非模板函數的重載函數集合是困難的,因為坑你會使函數的用戶感覺到奇怪,定義函數模板特化幾乎總是比使用非模板版本更好。