C++函式模板template(模板函式)
函式模板不是一個實在的函式,編譯器不能為其生成可執行程式碼。定義函式模板後只是一個對函式功能框架的描述,當它具體執行時,將根據傳遞的實際引數決定其功能。
面向物件的繼承和多型機制有效提高了程式的可重用性和可擴充性。在程式的可重用性方面,程式設計師還希望得到更多支援。舉一個最簡單的例子,
如何編寫一個通用加法函式?
第一個方法是使用函式過載, 針對每個所需相同行為的不同型別重新實現這個函式。C++的這種程式設計機制給程式設計者極大的方便,不需要為功能相似、引數不同的函式選用不同的函式名,也增強了程式的可讀性。
【 缺點】
1、 只要有新型別出現, 就要重新新增對應函式。
2、 除型別外, 所有函式的函式體都相同, 程式碼的複用率不高
3、 如果函式只 是返回值型別不同, 函式過載不能解決
4、 一個方法有問題, 所有的方法都有問題, 不好維護。
還有一個方法是使用公共基類, 將通用的程式碼放在公共的基礎類裡面,讓需要這部分功能的類去繼承它。但是這也有【 缺點】:
1、 藉助公共基類來編寫通用程式碼, 將失去型別檢查的優點;
2、 對於以後實現的許多類, 都必須繼承自 某個特定的基類, 程式碼維護更加困難。
此外還可以使用特殊的預處理程式
【 缺點】不是函式, 不進行引數型別檢測, 安全性不高
還有什麼 辦法嗎?
-----------泛型程式設計
泛型程式設計: 編寫與型別無關的邏輯程式碼, 是程式碼複用的一種手段。 模板是泛型程式設計的基礎。
泛型程式設計最初誕生於C++中,由Alexander Stepanov[2]和David Musser[3]創立。目的是為了實現C++的STL(標準模版庫)。其語言支援機制就是模板(Templates)。模板的精神其實很簡單:引數化型別。換句話說,把一個原本特定於某個型別的演算法或類當中的型別資訊抽掉,抽出來做成模板引數T。
模板是C++支援引數化多型的工具,使用模板可以使使用者為類或者函式宣告一種一般模式,使得類中的某些資料成員或者成員函式的引數、返回值取得任意型別。
眾所周知,有了“模子”後,用“模子”來批量製造陶瓷、塑料、金屬製品等就變得容易了。程式設計語言中的模板就是用來批量生成功能和形式都幾乎相同的程式碼的。有了模板,編譯器就能在需要的時候,根據模板自動生成程式的程式碼。從同一個模板自動生成的程式碼,形式幾乎是一樣的。
模板是一種對型別進行引數化的工具;通常有兩種形式:函式模板和類模板;函式模板針對僅引數型別不同的函式;類模板針對僅資料成員和成員函式型別不同的類。使用模板的目的就是能夠讓程式設計師編寫與型別無關的程式碼。比如編寫了一個交換兩個整型int 型別的swap函式,這個函式就只能實現int型,對double,字元這些型別無法實現,要實現這些型別的交換就要重新編寫另一個swap函式。使用模板的目的就是要讓這程式的實現與型別無關,比如一個swap模板函式,即可以實現int型,又可以實現double型的交換。模板可以應用於函式和類。下面分別介紹。
注意:模板的宣告或定義只能在全域性,名稱空間或類範圍內進行。即不能在區域性範圍,函式內進行,比如不能在main函式中宣告或定義一個模板。
2、函式模板
函式模板: 代表了 一個函式家族, 該函式與型別無關, 在使用時被引數化, 根據實參型別產生函式的特定型別版本。
模板函式的一般格式
template<typename Param1, typename Param2, . . . , class Paramn>
返回值型別 函式名 (引數列表)
{(函式體) . . . }
這裡的template和typename都是關鍵字,typename是用來定義模板引數關鍵字, 也可以使用class,在這裡typename 和class沒區別。 但是建議儘量使用typename。注意: 不能使用 struct代替typename。<>括號中的引數叫模板形參,模板形參和函式形參很相像,模板形參不能為空。一但聲明瞭模板函式就可以用模板函式的形參名宣告類中的成員變數和成員函式,即可以在該函式中使用內建型別的地方都可以使用模板形參名。模板形參需要呼叫該模板函式時提供的模板實參來初始化模板形參,一旦編譯器確定了實際的模板實參型別就稱他例項化了函式模板的一個例項。模板是一個藍圖, 它本身不是類或者函式, 編譯器用模板產生指定的類或者函式的特定型別版本, 產生模板特定型別的過程稱為函式模板例項化。
注意: 模板被編譯了兩次:
例項化之前,檢查模板程式碼本身,檢視是否出現語法錯誤, 如:遺漏分號;
在例項化期間,檢查模板程式碼,檢視是否所有的呼叫都有效, 如:例項化型別不支援某些函式呼叫。
【 實參推演】從函式實參確定模板形參型別和值的過程稱為模板實參推斷多個型別形參的實參必須完全匹配。
【 型別形參轉換】一般不會轉換實參以 匹配已有的例項化, 相反會產生新的例項。 編譯器只 會執行兩種轉換: 1、 const轉換: 接收const引 用或者const指標的函式可以分別用非const物件的引 用或者指標來呼叫 2、 陣列或函式到指標的轉換: 如果模板形參不是引 用型別, 則對陣列或函式型別的實參應用常規指 針轉換。 陣列實參將當做指向其第一個元素的指標, 函式實參當做指向函式型別的指標。
2、1模板引數
函式模板有兩種型別引數: 模板引數和呼叫引數
模板形參名字只能在模板形參之後到模板宣告或定義的末尾之間使用 , 遵循名字遮蔽規則
1 typedef int T; 2 template<class T> 3 void FunTest(T t) 4 { 5 cout << "t Type = " << typeid(t).name() << endl; 7 } 8 T gloab; 9 int main() { 10 FunTest(10); 11 cout << "gloab Type = " << typeid(gloab).name() << endl; 12 return 0; 13 }
模板形參的名字在同一模板形參列表中只能使用 一次
所有模板形參前面必須加上class或者typename關鍵字修飾
注意: 在函 數模板的內 部不能指定預設的模板實參。
下面模板函式宣告有問題嗎?
1 template<class T, U, typename V> 2 void f1(T, U, V) ;
3 template<class T> 4 T f2(int &T) ;
5 template<class T> 6 T f3 (T, T) ;
7 typedef int TYPENAME; 8 template<typename TYPENAME> 9 TYPENAME f4(TYPENAME) ;
2、2非模板型別引數
非模板型別形參是模板內 部定義的常量, 在需要常量表達式的時候, 可以使用非模板型別引數。例如陣列長度:
1 template<typename T,int N> 2 void FunTest(T(&_array)[N]) 3 { 4 for (int indx = 0; indx < N; ++indx) { 5 _array[indx] = 0; 6 } 7 } 8 int main() { 9 int a[5]; 10 float b[5]; 11 FunTest(a); 12 FunTest(b); 13 getchar(); 14 return 0; 15 }
1 //型別等價性 2 const int iByteCnt = 9; 3 int b[iByteCnt+1] ; 4 int a[10] ; 5 FunTest(a) ; // FunTest<int, 10> 兩個陣列等價 6 FunTest(b) ; // FunTest<int, 10> 編譯器不會合成新的函式
模板形參說明:
1、 模板形參表使用<>括起來;
2、 和函式引數表一樣,跟多個引數時必須用逗號隔開,型別可以相同也可以不相同;
3、 定義模板函式時模板形參表不能為空;
4、 模板形參可以是型別形參,也可以是非型別新參,型別形參跟在class和typename後;
5、 模板型別形參可作為型別說明符用在模板中的任何地方,與內建型別或自定義型別使用方法完全相同,可用於指定函式形參型別、返回值、區域性變數和強制型別轉換;
6、 模板形參表中,class和typename具有相同的含義,可以互換,使用typename更加直觀。但關鍵字typename是作為C++標準加入到C++中的,舊的編譯器可能不支援。
2、3模板函式過載
template<typename T>
1 int Max(const int& left, const int & right) 2 { 3 return left>right? left: right; 4 } 5 template<typename T> 6 T Max(const T& left, const T& right) 7 { 8 return left>right? left: right; 9 } 10 template<typename T> 11 T Max(const T& a, const T& b, const T& c) 12 { 13 return Max(Max(a, b) , c) ; 14 } ; 15 int main() 16 { 17 Max(10, 20, 30) ; 18 Max<>(10, 20) ; 19 Max(10, 20) ; 20 Max(10, 20. 12) ; 21 Max<int>(10. 0, 20. 0) ; 22 Max(10. 0, 20. 0) ; 23 return 0; 24 }
注意: 函式的所有過載版本的宣告都應該位於該函式被呼叫位置之前。
【 說明】
1、 一個非模板函式可以和一個同名 的函式模板同時存在, 而且該函式模板還可以被例項化為這個非模板函式。
2、 對於非模板函式和同名 函式模板, 如果其他條件都相同, 在調動時會優先調動非模板函式而不會從該模板產生出一個例項。 如果模板可以產生一個具有更好匹配的函式,那麼將選擇模板。
3、 顯式指定一個空的模板實參列表, 該語法告訴編譯器只 有模板才能來匹配這個呼叫,而且所有的模板引數都應該根據實參演繹出來。
4、 模板函式不允許自 動型別轉換, 但普通函式可以進行自 動型別轉換。
2、4模板函式特化
有時候並不總是能夠寫出對所有可能被例項化的型別都最合適的模板,在某些情況下, 通用模板定義對於某個型別可能是完全錯誤的, 或者不能編譯, 或者做一些錯誤的事情。
1 template<typename T> 2 int compare(T t1, T t2) { 3 if (t1 < t2) 4 return -1; 5 if (t1 > t2) 6 return 1; 7 return 0; 8 } 9 int main() { 10 char *pStr1 = "abcd"; 11 char *pStr2 = "1234"; 12 cout << compare(pStr1, pStr2) << endl; 13 getchar(); 14 return 0; 15 }
輸出結果是-1;但是當我們定義如下:
1 char *pStr2 = "1234"; 2 char *pStr1 = "abcd";
其他全部不變時,輸出結果依然是-1。說明這之中存在問題。分析如下:
因為直接將兩個指標變數的地址傳遞給函式模板,在比較時直接比較的是兩個地址的大小,而沒有比較指標內容,所以返回值一直是-1。顯然這個函式滿足不了我們的需求了,它支援常見int, float等型別的資料的比較,但是不支援char*(string)型別。所以我們必須對其進行特化,以讓它支援兩個字串的比較。所謂特化,就是將泛型的東東搞得具體化一些,從字面上來解釋,就是為已有的模板引數進行一些使其特殊化的指定,使得以前不受任何約束的模板引數,或受到特定的修飾(例如const或者搖身一變成為了指標之類的東東,甚至是經過別的模板類包裝之後的模板型別)或完全被指定了下來。
我們可以下面這樣來定義
1 template<> 2 int compare<const char*>(const char* const p1, const char* const p2) 3 { 4 return strcmp(p1, p2); 5 }
也可以這樣定義:
1 template < > 2 int compare(const char* left, const char* right) 3 { 4 return strcmp(p1, p2); 5 }
模板函式特化形式如下:
1、 關鍵字template後面接一對空的尖括號<>;
2、 函式名 後接模板名 和一對尖括號, 尖括號中指定這個特化定義的模板形參;
3、 函式形參表;
4、 函式體;
template<>
返回值 函式名 <Type>(引數列表)
{
// 函式體
}
特化的宣告必須與特定的模板相匹配, 否則
注意: 在模板特化版本的呼叫中 , 實參型別必須與特化版本函式的形參型別完全匹配,如果不匹配, 編譯器將為實參模板定義中例項化一個例項。函式模版的特化,當函式呼叫發現有特化後的匹配函式時,會優先呼叫特化的函式,而不再通過函式模版來進行例項化。
1 template<typename T> 2 int compare(T t1, T t2) { 3 cout << "in template<class T>..." << endl; 4 if (t1 < t2) 5 return -1; 6 if (t1 > t2) 7 return 1; 8 return 0; 9 } 10 // 這個是一個特化的函式模版 11 template<> 12 int compare<const char*>(const char* const p1, const char* const p2) 13 { 14 cout << "in special template< >..." << endl; 15 return strcmp(p1, p2); 16 } 17 // 特化的函式模版, 兩個特化的模版本質相同, 因此編譯器會報錯 18 // error: redefinition of 'int compare(T, T) [with T = const char*]'| 19 // 這個其實本質是函式過載 20 /*template < > 21 int compare(const char* left, const char* right) 22 { 23 std::cout << "in special template< >..." << std::endl; 24 25 return strcmp(left, right); 26 }*/ 27 int main() { 28 const char *pStr1 = "abcd"; 29 const char *pStr2 = "1234"; 30 char *pStr3 = "abcd"; 31 char *pStr4 = "1234"; 32 cout << compare(pStr1, pStr2) << endl; 33 cout << compare(pStr3, pStr4) << endl; 34 getchar(); 35 return 0; 36 }
輸出結果:分析如下:
注意: 特化不能出 現在模板例項的呼叫 之後, 應該在標頭檔案中 包含模板特化的宣告 , 然後使用 該特化版本的每個原始檔包含該標頭檔案。
參考:https://blog.csdn.net/low5252/article/details/94622335