c++模板


1.模板的作用

  [1]模板分為函數模板和類模板,函數模版是用來生成函數的實例,類模版是用來生成類的實例。

  [2]一個模版就是一個類或函數的藍圖或者說是公式。當我們調用template時,編譯器會使用實參的類型來確定綁定到模版參數T上的類型,之后編譯器利用推斷出的模版參數來實例化一個特定版本的函數,這個過程被稱之為實例化。

  [3]編譯器遇到一個模版的定義時,並不會產生代碼,只有當我們實例化出模版的一個特定版本的時候,編譯器才會產生代碼。

  [4]需要保證傳遞給模版的實參支持模版的所有操作(例如傳入的是addr類型,則在類模板中操作"<"運算符時,addr類型需要實現重載這個運算符),以及這些操作在模版中正確工作。

  函數模板:一個函數模版就是一個公式,可用來生成指定類型的函數版本,例如需要編寫一個函數比較兩個值,如果沒有模板,則需要為各種類型(例如int,double,string等)各自編寫一個比較函數。

  類模板:類模板是用來生成類的模型,和函數模板不同的是,編譯器不能為類模板推斷模板參數類型,必須提供實參列表。這個是顯然的,因為函數是直接調用,傳入的參數可以推斷出類型,但是類需要定義,如果不指定類型,則是不行的。

2.函數模板

 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行。

  [3]當調用一個函數模板時,編譯器會用函數實參來推斷模板實參,然后用推斷出的模板參數來實例化一個特定版本的函數。就是說傳入不同的類型編譯器實例化出不同的特定函數,模板的使用可以是隱式調用(compare(30, 20)),也可以顯式的調用(compare<int>(30.1, 20.1)),這個的意思就是實例化可以是讓編譯器根據參數自行推斷出實例,也可以指定參數類型確定實例,但是有些情況時必須要指定的,比如返回值的類型和參數列表中的類型都不同時,參考下面的例子:

/*
    這里的T_RET類型只在返回的時候使用,所以使用的時候必須指定T_RET的類型。
    這意思就是說,例如:
        int sum = fun(1, 2);
    這樣實例化fun時,T_RET和T類型都沒有顯式的傳入類型,但是編譯器可以根據參數1、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是根據實參1、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; 則i1、i2、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;
}  

  輸出:

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM