1.模板的做用ios
[1]模板分爲函數模板和類模板,函數模版是用來生成函數的實例,類模版是用來生成類的實例。算法
[2]一個模版就是一個類或函數的藍圖或者說是公式。當咱們調用template時,編譯器會使用實參的類型來肯定綁定到模版參數T上的類型,以後編譯器利用推斷出的模版參數來實例化一個特定版本的函數,這個過程被稱之爲實例化。數組
[3]編譯器遇到一個模版的定義時,並不會產生代碼,只有當咱們實例化出模版的一個特定版本的時候,編譯器纔會產生代碼。app
[4]須要保證傳遞給模版的實參支持模版的全部操做(例如傳入的是addr類型,則在類模板中操做"<"運算符時,addr類型須要實現重載這個運算符),以及這些操做在模版中正確工做。函數
函數模板:一個函數模版就是一個公式,可用來生成指定類型的函數版本,例如須要編寫一個函數比較兩個值,若是沒有模板,則須要爲各類類型(例如int,double,string等)各自編寫一個比較函數。性能
類模板:類模板是用來生成類的模型,和函數模板不一樣的是,編譯器不能爲類模板推斷模板參數類型,必須提供實參列表。這個是顯然的,由於函數是直接調用,傳入的參數能夠推斷出類型,可是類須要定義,若是不指定類型,則是不行的。ui
2.函數模板spa
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #include <vector> 5 using namespace std; 6 7 template <typename T> int compare(const T &v1, const T &v2){ 8 if(v1 < v2) 9 return -1; 10 if(v2 < v1) 11 return 1; 12 return 0; 13 } 14 15 template <typename T> inline int inline_compare(const T &v1, const T &v2){ 16 if(v1 < v2) 17 return -1; 18 if(v2 < v1) 19 return 1; 20 return 0; 21 } 22 23 template <char len> int str_compare(const char (&v1)[len], const char (&v2)[len]){ //len就是一個非類型參數 24 return strcmp(v1, v2); 25 } 26 27 int main(int argc, char *argv[]) 28 { 29 vector<int> ve1(10, 1), ve2(10, 2); 30 31 printf("compare: %d\n", compare(3, 2)); //常規使用 32 printf("compare float: %d\n", compare<int>(30.1, 20.1)); //顯式使用,這裏會把參數強轉爲int 33 printf("compare vector: %d\n", inline_compare(ve1, ve2)); 34 printf("inline_compare: %d\n", inline_compare(6, 7)); 35 printf("str_compare: %d\n", str_compare("hello", "abcde")); 36 return 0; 37 }
[1]<typename T>是模板參數列表,相似函數參數列表,函數參數列表定義了特定類型的局部變量,在運行時,提供實參來初始化形參,而模板參數列表定義了模板函數中用到類型或值,使用時,也是用實參(參數是類型或者值)來綁定到模板參數上。這意思就是模板的參數包括兩個東西,首先是根據傳入的參數類型實例化出實例,而後用這個實例執行傳入的參數值。typename能夠用class代替,沒有任何區別。指針
[2]模板參數通常都是用typename定義的類型,也能夠用非類型參數,非類型參數的意思就是說這個參數不是傳入類型,而是傳入一個值,非類型參數的值的類型能夠是整型、指針或引用,總之這個值必須是常量表達式(其實就是和普通函數中最常規的傳參同樣),而且這個參數須要有靜態生存期(全局變量有靜態生存期)。上面例子的第23行。code
[3]當調用一個函數模板時,編譯器會用函數實參來推斷模板實參,而後用推斷出的模板參數來實例化一個特定版本的函數。就是說傳入不一樣的類型編譯器實例化出不一樣的特定函數,模板的使用能夠是隱式調用(compare(30, 20)),也能夠顯式的調用(compare<int>(30.1, 20.1)),這個的意思就是實例化能夠是讓編譯器根據參數自行推斷出實例,也能夠指定參數類型肯定實例,可是有些狀況時必需要指定的,好比返回值的類型和參數列表中的類型都不一樣時,參考下面的例子:
/* 這裏的T_RET類型只在返回的時候使用,因此使用的時候必須指定T_RET的類型。 這意思就是說,例如: int sum = fun(1, 2); 這樣實例化fun時,T_RET和T類型都沒有顯式的傳入類型,可是編譯器能夠根據參數一、2推斷出T爲int型,而T_RET卻沒法根據傳入的參數推斷出是什麼類型。 */ template <typename T_RET, typename T> T_RET fun(const T& v1, const T& v2) { return v1 + v2; } int sum = fun<int, int>(1, 2);//正確,顯示調用,推斷出的實例是fun(int, int) int sum = fun<int>(1, 2); //正確,推斷出的實例是fun(int, int),T_RET是指定的int類型,T是根據實參一、2推斷的 int sum = fun(1, 2); //錯誤,編譯器沒法推斷出T_RET是什麼類型
[4]由於上面的上面的例子用"<"和">"比較兩個值,因此傳入的類型須要實現這兩個運算符。
[5]函數模版能夠聲明爲inline或是constexpr,置於模版參數列表以後,函數返回類型以前。上面的上面的例子第15行。
3.類模板
[1]一個類模板的每一個實例都會造成一個獨立的類,類和類之間沒有任何關聯。
[2]和普通類同樣,能夠在類內部和外部定義類模板的成員函數,模板內的成員函數隱式聲明爲inline函數,默認狀況下,對於一個實例化的類模板,其成員只有在使用時才被實例化。
[3]在類模板本身的做用域中,能夠直接使用模板名而不提供實參。(就是說在類模板做用域中,my_vector<T> 和my_vector 是能夠替換的)
[4]類模板中的static成員,這些成員由相同類型的實例共享,例如my_vector<int> i1,i2,i3; 則i一、i二、i3共享類中的static成員。
[5]在模板中使用類的類型成員,這個意思就是在模板裏面,要使用傳入的類型參數裏面的類型,例如傳入的參數是myclass(定義:my_vector<myclass> cl_vector;),若是要用myclass類裏面的類型(例如myclass裏面定義了一個類mylittleclass類)來定義一個變量,則方法是typename T::mylittleclass *p;這裏主要是用到了typename關鍵字。
[6]模板的默認實參,模板能夠設置默認實參,template <class T = int> class my_vector{},使用時能夠不指定實參,可是須要寫上尖括號(my_vector<> int_vector)。
[7]類模板的成員模板,意思就是類裏面的函數也是一個模板,成員模板不能是虛函數,參考下面的例子:
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #include <vector> 5 using namespace std; 6 7 template <typename T, int size=100> class my_vector{ 8 public: 9 void dump_int(); 10 void dump_str(); 11 12 my_vector() { 13 m_size = size; 14 } 15 template <typename It> my_vector(It a, It b); //構造函數是一個函數模板 16 17 private: 18 int m_size; 19 vector<T> m_data; 20 }; 21
22 //構造函數的實現 23 template <typename T, int size> template <typename It> my_vector<T, size>::my_vector(It a, It b){ 24 while(a != b){ 25 m_data.push_back(*a); 26 a++; 27 } 28 } 29 30 template <typename T, int size> void my_vector<T, size>::dump_int() 31 { 32 typename vector<T>::iterator iter; 33 for (iter=m_data.begin();iter!=m_data.end();iter++) 34 printf("\t%d\n", *iter); 35 36 return ; 37 } 38 39 template <typename T, int size> void my_vector<T, size>::dump_str() 40 { 41 typename vector<T>::iterator iter; 42 for (iter=m_data.begin();iter!=m_data.end();iter++) 43 printf("\t%s\n", (*iter).c_str()); 44 45 return ; 46 } 47 48 int main(int argc, char *argv[]) 49 { 50 vector<int> ia; 51 ia.push_back(1000); 52 ia.push_back(2000); 53 ia.push_back(3000); 54 ia.push_back(4000); 55 my_vector<int> int_vector10(ia.begin(), ia.end()); 56 printf("int_vector10:\n"); 57 int_vector10.dump_int(); 58 59 vector<string> is; 60 is.push_back("aa"); 61 is.push_back("bb"); 62 is.push_back("cc"); 63 is.push_back("dd"); 64 my_vector<string> int_vector11(is.begin(), is.end()); 65 printf("int_vector11:\n"); 66 int_vector11.dump_str(); 67 return 0; 68 }
輸出:
4.相關特性
[1]控制實例化的時機,由於當模板使用的時候纔會實例化,若是實例化大量相同模板會影響性能,因此能夠提早經過顯式實例化來避免這種狀況。只有在模板定義時纔會實例化,這個和普通的函數聲明和定義是同樣的。可是有地方要注意,定義的時候的實例化會實例化全部成員,包括inline函數(正常的實例化是隻實例化用到的成員),這就要求在實例化定義中,所用類型必須能做用於模版的全部成員函數(例如定義了template class my_vector<int>,則int類型要知足my_vector中全部的成員函數處理)。
1 template_build.cpp 2 template class my_vector<int>; //定義,而且會實例化 3 template int compare(const int &, const int &); //定義,而且會實例化 4 5 application.cpp 6 extern template class my_vector<int>; 7 extern template int compare(const int &, const int &); 8 9 //這時候使用,編譯器就不用再實例化了 10 my_vector<int> int_v; 11 compare(1, 2);
[2]函數模板參數的類型轉換,正常使用模板時,會根據傳入的參數類型實例化出不一樣的實例,若是傳入的類型不匹配則會發生編譯錯誤(下面例子中的fun_c(s1, s2); )。可是有兩種狀況不會發生編譯錯誤,一種是非const對象的引用或者指針(實參)到const對象的引用或者指針(形參),另外一種是形參不是引用類型時,數組名能夠轉爲一個指向首元素的指針,函數名能夠轉爲一個指向函數類型的指針。這個意思就是說,定義的模板函數的參數是const T &,則能夠傳給這個參數 T &類型(自動轉爲const)。定義的模板函數的參數是int *,則能夠傳給這個參數array(array定義:int array[10];)。另外對於下面的fun_a(s1, s2);,這個是一個值傳遞,跟const沒有任何關係,由於函數不操做s1,s2自己,操做的是參數副本。
對於模板參數T &,則傳入的實參必須是一個左值(例如不能是一個常量),若是實參是const則推斷出的形參T也是const。
對於模板參數const T &,則傳入的實參能夠是任意值,當傳入的實參是const時,因爲模板形參T自己就是const,因此推斷出的T是不帶const的。
以上意思就是說形參爲const引用的能夠傳入常量;傳入的參數是什麼類型,推斷出的T就是什麼類型,當形參自己就是const時,推斷出的T不帶const;非const的引用的實參能夠自動轉爲const引用的形參,而反過來就不行。
1 template <typename T> T fun_a(T v1, T v2) {return v1;}; 2 template <typename T> T fun_b(const T& v1, const T& v2){return v1;}; 3 template <typename T> T fun_c(T&v1, T&v2){return v1;}; 4 5 int main(){ 6 string s1("s1"); 7 const string s2("s2"); 8 9 fun_a(s1, s2); //這個會實例化出fun_a(string, string); 傳值 10 fun_a(s2, s2); //這個仍是會實例化出fun_a(string, string); 由於這是值傳遞,const是忽略的 11 12 fun_b(s1, s2); //非const能夠自動轉爲const 13 fun_b(s2, s2); //這個T推斷出的類型是string ,由於形參自己就是const 14 15 fun_c(s1, s2); //錯誤,const不能自動轉爲非const 16 fun_c(s2, s2); //這個T推斷出的類型是const string 17 return 0; 18 }
[3]函數模版也能夠有用普通類型定義的參數,不使用T而使用int等等,這些參數能夠進行正常的類型轉換。意思就是說函數模板的參數能夠是普通類型,例如template<typename T > int &my_vector(const T &a, int &b),正常的類型轉換的意思就是 my_vector("s1",12),12能夠正常轉換爲int類型。
[4]尾置返回類型,這個意思就是返回的類型編譯器推斷不出,而且不用手工指定的一種方法,例以下面的例子,函數應該返回*begin類型,固然能夠改下模板參數,手動指定一下返回類型,可是這裏能夠用decltype函數獲得返回的類型。方法就是把返回類型設置爲auto,而後用decltype獲取到返回類型。正常的表達應該是template <typename It> decltype(*begin) fun(It begin, It end){},可是編譯器在遇到參數列表以前,decltype(*begin)裏面的begin是不存在的,因此就用的是下面這種格式。
1 template <typename It> auto fun(It begin, It end) -> decltype(*begin) 2 { 3 return *begin; 4 } 5 6 int tmp; 7 vector<int> v; 8 9 v.push_back(1); 10 v.push_back(2); 11 12 tmp = fun(v.begin(), v.end());
[5]函數指針的處理,正常狀況下,一個函數指針指向一個函數,這裏一個函數指針能夠指向一個模板的實例,這個模板實例化時的參數是根據函數指針肯定的。
1 template <typename T> int fun(const T&, const T&); 2 int (*p)(const int &, const int &) = fun; // p 指向模板的實例fun(const int &, const int &) 3 int (*p)(const int &, const int &) = fun<int>; // 和上面的是一個意思
[6]函數模板的重載,函數模版能夠被另外一個模版或者普通非模版函數重載,這個和普通重載是同樣的。匹配規則:非模板函數優先於模板函數實例,若是沒有非模板函數,則選擇一個更加特例化的實例。
1 void fun_a(int &v1){printf("call fun_a.\n");} 2 template <typename T> void fun_a(const T &v1) {printf("call template fun_a(const T &v1).\n");}; 3 template <typename T> void fun_a(T *v1){printf("call template fun_a(T *v1).\n");}; 4 5 int main(){ 6 int a = 5; 7 const int b = 5; 8 9 fun_a(a); //調用函數fun_a 10 fun_a(b); //調用模板函數fun_a(const T &v1),由於b不是指針 11 fun_a(&b); //調用模板函數fun_a(T *v1),由於這個更特例化,這個模板實例只能接受指針,而(const T &v1)能夠接受除指針外的其餘類型 12 return 0; 13 }
5.模板的特例化
[1]函數模板的特例化:有時候,模板函數處理傳入的類型時,沒辦法進行處理,例如比較函數中傳入字符串指針類型,這種狀況就須要使用特例化,另外要清楚的是,特例化就是實現了此模板的一個實例,而非重載模板。特例化的方式就是template後面接空的<>,而且把參數列表的T替換爲要實例化的類型。
1 template <typename T> void fun_a(const T &v1) {printf("call template fun_a(const T &v1).\n");}; 2 3 //此處實例化了fun_a的一個實例,參數類型是const char * 4 template <> void fun_a(const char * const &v1){printf("call template fun_a(const char * const &v1).\n");}; 5 6 int main() 7 { 8 const char *a = NULL; 9 fun_a(a); 10 return 0; 11 }
[2]類模板的特例化:這個就是實例化出一個特定的類型的類,例以下面的例子特例化出一個int類型參數的類,而且是部分特例化,這個和函數是相似的,template後面的尖括號保留不須要特例的化的參數,特例化的參數放到模板名後面。和函數不一樣的是,函數模板參數不能部分特例化。另外能夠只特例化類中的成員函數。
#include <iostream> #include <stdio.h> #include <string.h> #include <vector> using namespace std; template <typename T, int size=100> class my_vector{ public: my_vector() { m_size = size; printf("call my_vector\n"); } void fun(){printf("call my_vector fun()\n");}; private: int m_size; vector<T> m_data; }; //特例化參數類型爲string的成員函數 template<> void my_vector<string>::fun(){ printf("call my_vector<string> fun()\n"); return ; } // 特例化參數類型爲int的實例 template<int size> class my_vector<int, size>{ public: my_vector() { m_size = size; printf("call my_vector<int, size>\n"); } void fun(){printf("call my_vector<int, size> fun()\n");}; private: int m_size; vector<int> m_data; }; int main(int argc, char *argv[]) { my_vector<int> int_vector1; printf("--------------------------\n"); my_vector<string> str_vector1; str_vector1.fun(); return 0; }
輸出:
6.trait
當函數,類或者一些封裝的通用算法中的某些部分會由於數據類型不一樣而致使處理或邏輯不一樣(而咱們又不但願由於數據類型的差別而修改算法自己的封裝時),traits會是一種很好的解決方案。這個意思就是說模板函數中須要根據不一樣的傳入參數類型,進行不一樣的邏輯處理。例如一個計算總和的模板函數,須要根據傳入的參數類型來設置總和的類型,例如傳入的類型是int,則總和類型應該爲long,傳入的是char,則總和類型應該爲int。這個能夠經過增長模板參數來解決(例如增長一個參template <typename T,typename SUM_T>),可是這裏能夠用trait處理。
具體作法就是增長一個基礎模板,而後特例化這個基礎模板,而後把這個基礎模板應用到咱們的函數模板中。另外特例化中的方法是爲了初始化,因此使用statis會比較合適,固然不用statis也是能夠的。固然還能夠返回一個靜態變量來完成初始化。這個沒什麼區別,只是爲了初始化。
這樣作的好處就是函數模板仍是一個,只是增長一些基礎模板的特例化實例,參考下面的例子:
#include <iostream> #include <stdio.h> #include <string.h> #include <vector> using namespace std; //基礎模板 template<typename T> class base_traits; //基礎模板特例化int template<> class base_traits<int>{ public: typedef long sum_type; static sum_type zero() { printf("call base_traits<int> zero.\n"); return 0; } }; //基礎模板特例化char template<> class base_traits<char> { public: typedef int sum_type; static sum_type zero() { printf("call base_traits<char> zero.\n"); return 0; } }; //計算總和的模板函數 template <typename T> inline typename base_traits<T>::sum_type sum (T const * beg, T const * end) { typedef typename base_traits<T>::sum_type sum_type; sum_type total = base_traits<T>::zero(); while (beg != end) { total += *beg; ++beg; } return total; } int main(int argc, char *argv[]) { int num[] = {1,2,3,4,5}; printf("int sum:%d\n", sum(&num[0], &num[5])); char cnum[] = {11,12,13,14,15}; printf("char sum:%d\n", sum(&cnum[0], &cnum[5])); return 1; }
輸出: