模板與泛型編程1(函數模板)
定義、實例化函數模板:
對於函數體完全相同,唯一差異就是參數類型的情況,我們可以定義一個通用的函數模板,而非為每個類型都定義一個新函數:
1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 template <typename T>//模板參數列表 6 int compare(const T &v1, const T &v2) { 7 if(v1 < v2) return -1; 8 if(v2 < v1) returnView Code1; 9 return 0; 10 } 11 12 int main(void) { 13 cout << compare(1, 0) << endl;//T為int 14 cout << compare(vector<int>{1, 2, 3}, vector<int>{2, 3, 4}) << endl;//T為vector<int> 15 16 return 0; 17 }
註意:模板定義以關鍵字 template 開始,後跟一個模板參數列表,裏面有一個或多個模板參數
類似於函數參數,模板參數表示在類或函數定義中用到的類型或值。當使用模板時,我們 (隱式的或顯式地) 指定模板實參,將其綁定道模板參數上。
當我們調用一個函數模板時,編譯器(通常)用函數實參來為我們推斷模板實參,並用推斷出的模板參數來為我們實例化一個特定版本的函數。
模板參數可以是模板類型參數或非類型模板參數(表示值而非類型)
模板類型參數:
前面的 compare 中 T 就是一個模板類型參數。一般來說我們可以將類型參數看作類型說明符,就像內置類型或類類型說明符一樣使用:
1 template <typename T>//類型參數 2 //View Code函數模板返回類型與參數類型相同 3 T foo(T *p) {//類型參數可硬用來指定返回類型或函數的參數類型 4 T tmp = *p;//類型參數可以用於函數體內部聲明變量或類型轉換 5 //... 6 return tmp; 7 }
註意:類型參數可用來指定返回類型、函數的參數類型、聲明變量或類型轉換
類型參數前必須使用關鍵字 class 或 typename:
// 錯誤,U 之前必須加上 class 或 typename
template <typename T, U> T calc(const T&, const U&);
//正確
template <typename T, class U> T calc(const T&, const U&);
註意:在模板參數列表中,class 和 typename 含義完全相同,可以互換使用
非類型模板參數:
一個非類型模板參數表示一個值而非一個類型。我們通過一個特定的類型名而非關鍵字 class 或 typename 來指定非類型參數。當一個模板唄實例化時,非類型參數被一個用戶提供的或編譯器推斷出的值所代替,這些值必須是常量表達式,從而允許編譯器在編譯時實時實例化模板:
1 #include <iostream> 2 #include <string.h> 3 using namespace std; 4 5 template<unsigned N, unsigned M> 6 int compare(const char (&p1)[N], const char (&p2)[M]) {//數組不能拷貝,可以引用 7 return strcmp(p1, p2); 8 } 9 10 int main(void) { 11 compare("hi", "mom"); 12 13 return 0; 14 }View Code
編譯器會實例化出如下模板:
int compare(const char (&p1)[3], const char (&p2)[4])
註意:一個非類型參數可以是一個整型,或者是一個指向對象或函數類型的指針或(左值)引用
綁定到非類型整型參數的實參必須是一個常量表達式。綁定到指針或引用非類型參數的實參必須具有靜態的生存期。指針參數也可以用 nullptr 或一個值為 0 的常量表達式來實例化
inline 和 constexpr 的函數模板:
template <typename T> inline T min(const T&, const T&);
註意:inline 或 constexpr 說明符放在模板參數列表之後,返回類型之前
模板編譯:
當編譯器遇到一個模板定義時,它並不生成代碼。只有當我們實例化(使用)模板的特定版本時,編譯器才會生成代碼。
通常,當我們調用一個函數時,編譯器只需要掌握函數的聲明。類似的,當我們使用一個類型的對象時,類定義必須是可用的。但成員函數的定義不必已經出現。因此我們可以將類定義和函數聲明放在頭文件中,而普通函數和類的成員函數的定義放在源文件中。
模板則不同:為了生成一個實例化版本,編譯器需要掌握函數模板或類模板成員函數的定義。因此,與非模板代碼不同,模板的頭文件通常既包括聲明也包括定義。
大多數編譯錯誤發生在實例化期間報告:
如,前面的 compare 函數,如果我們用一個沒有定義 < 運算符的類型實例化該函數模板,則會發生編譯錯誤
定義 find 模板:
1 #include <iostream> 2 #include <vector> 3 #include <list> 4 using namespace std; 5 6 template<typename T, typename U> 7 T find(const T &bg, const T &ed, const U &val) { 8 auto cnt = bg; 9 while(cnt != ed) { 10 if(*cnt == val) break; 11 ++cnt; 12 } 13 return cnt; 14 } 15 16 int main(void) { 17 vector<int> vt = {1, 2, 3, 4, 5}; 18 list<string> lt = {"jhhg", "fjsl", "zzz", "jfl"}; 19 20 auto cnt = find(vt.begin(), vt.end(), 1); 21 cout << *cnt << endl; 22 23 auto gg = find(lt.begin(), lt.end(), "zzz"); 24 cout << *gg << endl; 25 26 gg = find(lt.begin(), lt.end(), "aa"); 27 if(gg == lt.end()) cout << "//" << endl; 28 29 return 0; 30 }View Code
編寫接受一個數組引用參數的 print 模板:
1 #include <iostream> 2 using namespace std; 3 4 template<typename T, unsigned N> 5 void print(const T (&t)[N]) { 6 for(int i = 0; i < N; i++) { 7 cout << t[i] << " "; 8 } 9 cout << endl; 10 } 11 12 int main(void) { 13 int a[10] = {2, 2, 3}; 14 print(a); 15 16 string b[3] = {"fsl", "fj", "z"}; 17 print(b); 18 19 // char *ch = "jfk";//註意,不能傳指針,只能傳數組 20 print("fjdl"); 21 22 return 0; 23 }View Code
定義接受一個數組實參的 begin、end 模板:
1 #include <iostream> 2 #include <algorithm> 3 using namespace std; 4 5 template<typename T, unsigned N> 6 T* begin_(const T (&a)[N]) { 7 return const_cast<T*>(a);//底層const只能顯式的去除 8 } 9 10 template<typename T, unsigned N> 11 T* end_(const T (&a)[N]) { 12 return const_cast<T*>(a) + N; 13 } 14 15 template<typename T, unsigned N> 16 void print(const T (&t)[N]) { 17 for(int i = 0; i < N; i++) { 18 cout << t[i] << " "; 19 } 20 cout << endl; 21 } 22 23 int main(void) { 24 int a[10] = {7, 1, 3, 3}; 25 sort(begin_(a), end_(a)); 26 print(a); 27 28 return 0; 29 }View Code
編寫一個 constexpr 模板,返回給定數組的大小:
1 #include <iostream> 2 using namespace std; 3 4 template<typename T, unsigned N> 5 constexpr unsigned size(const T (&t)[N]) { 6 return N; 7 } 8 9 class gel{ 10 int x; 11 }; 12 13 int main(void) { 14 gel g[10]; 15 cout << size(g) << endl; 16 17 return 0; 18 }View Code
模板與泛型編程1(函數模板)