函數模板不是一個實在的函數,編譯器不能為其生成可執行代碼。定義函數模板后只是一個對函數功能框架的描述,當它具體執行時,將根據傳遞的實際參數決定其功能。
面向對象的繼承和多態機制有效提高了程序的可重用性和可擴充性。在程序的可重用性方面,程序員還希望得到更多支持。舉一個最簡單的例子,
如何編寫一個通用加法函數?
第一個方法是使用函數重載, 針對每個所需相同行為的不同類型重新實現這個函數。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