C++模版深度解析


在C++發明階段,C++之父Stroustrup和貝爾實驗室的C++小組對原先的宏方法進行了修訂,對其進行了簡化並將它從預處理范圍移入了編譯器。這種新的代碼替換裝置被稱為模板,而且它變現了完全不同的代碼重用方法:模板對源代碼重用,而不是通過繼承和組合重用對象代碼。當用戶使用模板時,參數由編譯器來替換,這非常像原來的宏方法,卻更清晰,更容易使用。

模板使類和函數可在編譯時定義所需處理和返回的數據類型,一個模板並非一個實實在在的類或函數,僅僅是一個類和函數的描述。由於模板可以實現邏輯相同、數據類型不同的程序代碼復制,所以使用模板機制可以減輕編程和維護的工作量和難度。模板一般分為模板函數和類模板。以所處理的數據類型的說明作為參數的類就叫類模板,或者模板類,而以所處理的數據類型的說明作為參數的函數,則稱為函數模板。

本文包含函數目標和類模板,有些可能會交錯設計兩個模塊的細節。

1.函數模板

函數模板定義了參數化的非成員函數,這使得程序員能夠用不同類型的參數調用相同的函數,由編譯器決定調用哪一種類型,並且從模板中生成相應的代碼。

定義:

Template﹤類型參數表﹥返回類型 函數名 (形參表){函數體}

簡單實例,調用函數打印字符串或數字等。

普通函數形式:

#include <string>
#include <iostream>
void printstring(const std::string& str) 
{    
    std::cout << str << std::endl;
}
int main()
{    
    std::string str("Hello World");    
    printstring(str);
    return 0;
}//輸出:Hello World

模板函數形式:

#include <string>
#include <iostream>
using namespace std;
template<typename T> void print(const T& var)
{    
    cout << var << endl;
}
int main()
{    
    string str("Hello World");    
    const int num=1234;
    print(str);
    print(num);
    return 0;
}
//輸出:Hello World 
//       1234

可以看出使用模板后的函數不僅可以輸出字符串形式還可以輸出數字形式的內容。

上面兩個例子介紹了函數模板的簡單使用方法,但只有一個參數,如果需要多個參數,相應的函數模板應采用以下形式定義:

Template﹤類型1 變量1,類型2 變量2 ,…﹥返回類型 函數名 (形參表){函數體}

現在,為了看到模板時如何稱為函數的,我們假定min()函數接受各種類型的參數,並找出其中的最小者,如果不采用模板技術,則只能接受一個特定類型的參數,如果希望也能接受其他類型的參數,就需要對每一種類型的參數都定義一個同功能的函數,其實為函數的重載,這里不在討論,但這將是一件非常讓人麻煩的事情。如:

普通定義:

#include <iostream.h>
// 定義多態函數,找出三個整數中最小的數
int min0(int ii, int jj, int kk)
{
    int temp;
    if((ii<jj)&&(ii<kk)){temp=ii;}
    else if((jj<ii)&&(jj<kk)){temp=jj;    }
    else{    temp=kk;    }
    return temp;
}
// 定義多態函數,找出三個小數中最小的數
float min1(float ii, float jj, float kk)
{
    float temp;
    if((ii<jj)&&(ii<kk)){temp=ii;}
    else if((jj<ii)&&(jj<kk)){temp=jj;    }
    else{    temp=kk;    }
    return temp;
}

// 定義多態函數,找出三個子符中最小的字符
char min2(char ii, char jj, char kk)
{
    char temp;
    if((ii<jj)&&(ii<kk))    {temp=ii;    }
    else if((jj<ii)&&(jj<kk)){temp=jj;}    
    else{temp=kk;}
    return temp;
}

void main()
{
    int temp1=min0(100,20,30);
    cout<<temp1<<endl;
    float temp2=min1(10.60,10.64,53.21);
    cout<<temp2<<endl;
    char temp3=min2('c','a','C');
    cout<<temp3<<endl;
}
//以換行形式輸出20  10.6  C

使用模板:

#include <iostream.h>
// 定義函數模板,找出三個值中最小的值,與數據類型無關
template <class T>
T min(T ii, T jj, T kk)
{
    T temp;
    if((ii<jj)&&(ii<kk)){temp=ii;}
    else if((jj<ii)&&(jj<kk)){temp=jj;}
    else{    temp=kk; }
    return temp;
}
// 下面是主函數
void main()
{
    cout<<min(100,20,30)<<endl;
    cout<<min(10.60,10.64,53.21)<<endl;
    cout<<min('c','a','C')<<endl;
}

輸出結果同上,但可以清楚的看到二者之間的工作量大小之差距。

函數模板功能非常強大,但是有時候可能會陷入困境,加入待比較的函數模板沒有提供正確的操作符,則程序不會對此進行編譯。為了避免這種錯誤,可以使用函數模板和同名的非模板函數重載,這就是函數定制。函數模板與同名的非模板函數重載必須遵守以下規定:

n  尋找一個參數完全匹配的函數,如有,則調用它

n  如果失敗,尋找一個函數模板,使其實例化,產生一個匹配的模板函數,若有,則調用它

n  如果失敗,再試低一級的對函數重載的方法,例如通過類型轉換可產生的參數匹配等,若找到匹配的函數,調用它

n  如果失敗,則證明這是一個錯誤的調用

現在用上例的模板函數比較兩個字符串,但會出現問題:

#include <iostream.h>
// 定義函數模板,找出三個值中最小的值,與數據類型無關
template <class T>
T min(T ii, T jj, T kk)
{
    T temp;
    if((ii<jj)&&(ii<kk)){     temp=ii; }
    else if((jj<ii)&&(jj<kk)){ temp=jj; }
    else{    temp=kk;}
    return temp;
}
void main() 
{
    cout<<min("anderson","Washington","Smith")<<endl;
}

輸出anderson 與實際結果不否,原因在於編譯器會生成對字符串指針做比較的函數,但比較字符串和比較字符串指針是不一樣的,為了解決此問題,我們可以定制函數模板,如:

#include <iostream>
#include <string>
using namespace std;
// 定義函數模板,找出三個值中最小的值,與數據類型無關
template <class T>
T min(T ii, T jj, T kk)
{
    T temp;
    if((ii<jj)&&(ii<kk)){        temp=ii;    }
    else if((jj<ii)&&(jj<kk)){        temp=jj;    }
    else    {        temp=kk;    }
    return temp;
}
//非模板函數重載
const char* min(const char* ch1, const char* ch2,const char* ch3)
{
    const char* temp;
    int result1 = strcmp(ch1,ch2);
    int result2 = strcmp(ch1,ch3);
    int result3 = strcmp(ch2,ch1);
    int result4 = strcmp(ch2,ch3);
    if((result1<0)&&(result2<0))    {        temp = ch1;    }
    else if((result3<0)&&(result4<0))    {        temp=ch2;    }
    else    {        temp=ch3;    }
    return temp;
}
void main()
{
    cout<<min(100,20,30)<<endl;
    cout<<min(10.60,10.64,53.21)<<endl;
    cout<<min('c','a','C')<<endl;    
    cout<<min("anderson","Washington","Smith")<<endl;
}

在VS 2010中,最后一行會輸出Smith, 與結果先符。

注意:若上例在VC++ 6.0 中運行,其結果最后一行仍會輸出anderson,讀者可自己上機查看情況並分析原因。

下面給出一些實例:

#ifndef HEADER_MY
#define HEADER_MY
#include <string>
#include <sstream>
template<class T>
T fromString(const std::string &s)
{
    std::istringstream is(s);
    T t;
    is>>t;
    return t;
}
template<class T>
std::string toString(const T &s)
{
    std::ostringstream t;
    t<<s;
    return t.str();
}

#endif
#include "HEADER.h" 
#include <iostream> 
#include <complex> 
using namespace std; 
int main() 
{ 
    int i = 1234; 
    cout << "i == \"" << toString(i) << "\"\n"; 
    float x = 567.89; 
    cout << "x == \"" << toString(x) << "\"\n"; 
    complex<float> c(1.0, 2.0); 
    cout << "c == \"" << toString(c) << "\"\n"; 
    cout << endl; 
    i = fromString<int>(string("1234")); 
    cout << "i == " << i << endl; 
    x = fromString<float>(string("567.89")); 
    cout << "x == " << x << endl; 
    c = fromString< complex<float> >(string("(1.0,2.0)")); 
    cout << "c == " << c << endl; 
    return 0;
}

模板實參推演

當函數模板被調用時,對函數實參類型的檢查決定了模板實參的類型和值的這個過程叫做模板實參推演。如template <class T> void h(T a){}; h(1); h(0.2);第一個調用因為實參是int型的,所以模板形參T被推演為int型,第二個T的類型則為double。

在使用函數模板時,請注意以下幾點:

n  在模板被實例化后,就會生成一個新的實例,這個新生成的實例不存在類型轉換。比如有函數模板template <class T>void H(T a){};int a=2; short b=3;第一個調用H(a)生成一個int型的實例版本,但是當調用h(b)的時候不會使用上次生成的int實例把short轉換為int,而是會另外生成一個新的short型的實例。

n  在模板實參推演的過程中有時類型並不會完全匹配,這時編譯器允許以下幾種實參到模板形參的轉換,這些轉換不會生成新的實例。

u  數組到指針的轉換或函數到指針的轉換:比如template<class T> void h(T * a){},int b[3]={1,2,3};h(b);這時數組b和類型T *不是完全匹配,但允許從數組到指針的轉換因此數組b被轉換成int *,而類型形參T被轉換成int,也就是說函數體中的T被替換成int。

u  限制修飾符轉換:即把const或volatile限定符加到指針上。比如template<class T> void h(const T* a){},int b=3; h(&b);雖然實參&b與形參const T*不完全匹配,但因為允許限制修飾符的轉換,結果就把&b轉換成const int *。而類形型參T被轉換成int。如果模板形參是非const類型,則無論實參是const類型還是非const類型調用都不會產生新的實例。

u  到一個基類的轉換(該基類根據一個類模板實例化而來):比如tessmplate<class T1>class A{}; template<class T1> class B:public A<T1>{}; template<class T2> void h(A<T2>& m){},在main函數中有B<int> n; h(n);函數調用的子類對象n與函數的形參A<T2>不完全匹配,但允許到一個基類的轉換。在這里轉換的順序為,首先把子類對象n轉換為基類對象A<int>,然后再用A<int>去匹配函數的形參A<T2>&,所以最后T2被轉換為int,也就是說函數體中的T將被替換為int。

n  對於函數模板而言不存在h(int,int)這樣的調用,不能在函數調用的參數中指定模板形參的類型,對函數模板的調用應使用實參推演來進行,即只能進行h(2,3)這樣的調用,或者int a, b; h(a,b)。

模板實參推演實例,說明內容較長,采用注釋形式,但代碼較亂:

#include <iostream>
using namespace std;
template<class T>void h(T a){cout<<" h()"<<typeid(T).name()<<endl;}  //帶有一個類型形參T的模板函數的定義方法,typeid(變量名).name()為測試變量類型的語句。
template<class T>void k(T a,T b){T c;cout<<" k()"<<typeid(T).name()<<endl;} //注意語句T c。模板類型形參T可以用來聲明變量,作為函數的反回類型,函數形參等凡是類類型能使用的地方。
template<class T1,class T2> void f(T1 a, T2 b){cout<<" f()"<<typeid(T1).name()<<","<<typeid(T2).name()<<endl;}   //定義帶有兩個類型形參T1,T2的模板函數的方法template<class T> void g(const T* a){T b;cout<<" g()"<<typeid(b).name()<<endl;} 
//template<class T1,class T2=int> void g(){}  //錯誤,默認模板類型形參不能用於函數模板,只能用於類模板上。
//main函數開始
int main()
{ // template<class T>void h(){} //錯誤,模板的聲明或定義只能在全局,命名空間或類范圍內進行。即不能在局部范圍,函數內進行。
//函數模板實參推演示例。
// h(int); //錯誤,對於函數模板而言不存在h(int,int)這樣的調用,不能在函數調用的參數中指定模板形參的類型,對函數模板的調用應使用實參推演來進行,即只能進行h(2,3)這樣的調用,或者int a, b; h(a,b)。
//h函數形式為:template<class T>void h(T a)
h(2);//輸出" h() int"使用函數模板推演,在這里數值2為int型,所以把類型形參T推演為int型。
h(2.0);//輸出" h() double",因為2.0為double型,所以將函數模板的類型形參推演為double型
//k函數形式為:template<class T>void k(T a,T b)
k(2,3);//輸出" k() int"
//k(2,3.0);錯誤,模板形參T的類型不明確,因為k()函數第一個參數類型為int,第二個為double型,兩個形參類型不一致。
//f函數的形式為:template<class T1,class T2> void f(T1 a, T2 b)
f(3,4.0);//輸出" f() int,double",這里不存在模板形參推演錯誤的問題,因為模板函數有兩個類型形參T1和T2。在這里將T1推演為int,將T2推演為double。
int a=3;double b=4;
f(a,b); //輸出同上,這里用變量名實現推板實參的推演。
//模板函數推演允許的轉換示例,g函數的形式為template<class T> void g(const T* a)
int a1[2]={1,2};g(a1); //輸出" g() int",數組的地址和形參const T*不完全匹配,所以將a1的地址T &轉換為const T*,而a1是int型的,所以最后T推演為int。
g(&b); //輸出" g() double",這里和上面的一樣,只是把類型T轉換為double型。
h(&b); //輸出" h() double *"這里把模參類型T推演為double *類型。
return 0;
}

函數模板的顯示實例化

u  隱式實例化:比如有模板函數template<class T> void h(T a){}。h(2)這時h函數的調用就是隱式實例化,既參數T的類型是隱式確定的。

u  函數模板顯示實例化聲明:其語法是:template  函數反回類型 函數名<實例化的類型> (函數形參表); 注意這是聲明語句,要以分號結束。例如:template  void h<int> (int a);這樣就創建了一個h函數的int 實例。再如有模板函數template<class T> T h( T a){},注意這里h函數的反回類型為T,顯示實例化的方法為template int h<int>(int a); 把h模板函數實例化為int 型。

u  對於給定的函數模板實例,顯示實例化聲明在一個文件中只能出現一次。

u  在顯示實例化聲明所在的文件中,函數模板的定義必須給出,如果定義不可見,就會發生錯誤。

注意:不能在局部范圍類顯示實例化模板,實例化模板應放在全局范圍內,即不能在main函數等局部范圍中實例化模板。因為模板的聲明或定義不能在局部范圍或函數內進行。

顯示模板實參:

1、顯示模板實參:適用於函數模板,即在調用函數時顯示指定要調用的時參的類型。

2、格式:顯示模板實參的格式為在調用模板函數的時候在函數名后用<>尖括號括住要顯示表示的類型,比如有模板函數template<class T> void h(T a, T b){}。則h<double>(2, 3.2)就把模板形參T顯示實例化為double類型。

3、顯示模板實參用於同一個模板形參的類型不一致的情況。比如template<class T> void h(T a, T b){},則h(2, 3.2)的調用會出錯,因為兩個實參類型不一致,第一個為int 型,第二個為double型。而用h<double>(2, 3.2)就是正確的,雖然兩個模板形參的類型不一致但這里把模板形參顯示實例化為double類型,這樣的話就允許進行標准的隱式類型轉換,即這里把第一個int 參數轉換為double類型的參數。

4、顯示模板實參用法二:用於函數模板的反回類型中。例如有模板函數template<class T1, class T2, class T3> T1 h(T2 a, T3 b){},則語句int a=h(2,3)或h(2,4)就會出現模板形參T1無法推導的情況。而語句int h(2,3)也會出錯。用顯示模板實參就參輕松解決這個問題,比如h<int, int, int>(2,3)即把模板形參T1實例化為int 型,T2和T3也實例化為int 型。

5、顯示模板實參用法三:應用於模板函數的參數中沒有出現模板形參的情況。比如template<class  T>void h(){}如果在main函數中直接調用h函數如h()就會出現無法推演類型形參T的類型的錯誤,這時用顯示模板實參就不會出現這種錯誤,調用方法為h<int>(),把h函數的模板形參實例化為int 型,從而避免這種錯誤。

6、顯示模板實參用法四:用於函數模板的非類型形參。比如template<class T,int a> void h(T b){},而調用h(3)將出錯,因為這個調用無法為非類型形參推演出正確的參數。這時正確調用這個函數模板的方法為h<int, 3>(4),首先把函數模板的類型形參T推演為int 型,然后把函數模板的非類型形參int a用數值3來推演,把變量a設置為3,然后再把4傳遞給函數的形參b,把b設置為4。注意,因為int a是非類型形參,所以調用非類型形參的實參應是編譯時常量表達式,不然就會出錯。

6、在使用顯示模板實參時,我們只能省略掉尾部的實參。比如template<class T1, class T2, class T3> T1 h(T2 a, T3 b){}在顯示實例化時h<int>(3, 3.4)省略了最后兩個模板實參T2和T3,T2和T3由調用時的實參3和3.4隱式確定為int 型和double型,而T1被顯示確定為int 型。h<int, , double><2,3.4>是錯誤的,只能省略尾部的實參。

7、顯示模板實參最好用在存在二義性或模板實參推演不能進行的情況下。

下面來看看實例:

#include <iostream>
using namespace std;
template<class T>void g1(T a, T b){cout<<"hansu g1()"<<typeid(T).name()<<endl;}
template<class T1,class T2,class T3>T1 g2(T2 a,T3 b)
{T1 c=a;cout<<"hansug2()"<<typeid(T1).name()<<typeid(T2).name()<<typeid(T3).name()<<endl; return c;}
template<class T1,class T2> void g3 ( T1 a ) {cout<<"hansu g3()"<<typeid(T1).name()<<typeid(T2).name()<<endl;}
template<class T1,int a> void g4(T1 b, double c){cout<<"hansu g4()"<<typeid(T1).name()<<typeid(a).name()<<endl;}
template<class T1,class T2> class A{public:void g();};
//模板顯示實例化示例。
//因為模板的聲明或定義不能在局部范圍或函數內進行。所以模板實例化都應在全局范圍內進行。
template void g1<double>(double a,double b); //把函數模板顯示實例化為int型。
template class A<double,double>; //顯示實例化類模板,注意后面沒有對象名,也沒有{}大括號。
//template class A<int,int>{};  //錯誤,顯示實例化類模板后面不能有大括號{}。
//template class A<int,int> m;  //錯誤,顯示實例化類模板后面不能有對象名。
//main函數開始
int main()
{//顯示模板實參示例。顯示模板實參適合於函數模板
//1、顯示模板實參用於同一個模板形參的類型不一致的情況。函數g1形式為template<class T>void g1(T a, T b)
g1<double>(2,3.2);//輸出"hansu g1() int"兩個實參類型不一致,第一個為int第二個為double。但這里用顯示模板實參把類型形參T指定為double,所以第一個int型的實參數值2被轉換為double類型。
//g1(2,3.2);錯誤,這里沒有用顯式模板實參。所以兩個實參類型不一致。
//2、用於函數模板的反回類型中。函數g2形式為template<class T1,class T2,class T3> T1 g2(T2 a,T3 b)
//g2(2,3);錯誤,無法推演類型形參T1。
//int g2(2,3);錯誤,不能以這種方法試圖推導類型形參T1為int型。
//int a=g2(2,3);錯誤,以這種方式試圖推演出T1的類型為int也是錯誤的。
g2<int,int,int>(2,3);//正確,將T1,T2,T3 顯示指定為int型。輸出"hansu g2() intintint"
//3、應用於模板函數的參數中沒有出現模板形參的情況其中包括省略的用法,函數g3的形式為template<class T1,class T2> void g3(T1 a)
//g3(2);錯誤,無法為函數模板的類型形參T2推演出正確的類型
//g3(2,3);錯誤,豈圖以這種方式為T2指定int型是錯誤的,因為函數只有一個參數。
//g3<,int>(2);錯誤,這里起圖用數值2來推演出T1為int型,而省略掉第一個的顯示模板實參,這種方法是錯誤的。在用顯示模板實參時,只能省略掉尾部的實參。
//g3<int>(2);錯誤,雖然用了顯示模板實參方法,省略掉了尾部的實參,但該方法只是把T1指定為int型,仍然無法為T2推演正確的類型。
g3<int,int>(2);//正確,顯示指定T1和T2的類型都為int型。
//4、用於函數模板的非類型形參。g4函數的形式為template<class T1,int a> void g4(T1 b,double c)
//g4(3,3.2);錯誤,雖然指定了兩個參數,但是這里仍然無法為函數模板的非類型形參int a推演出正確的實參。因為第二個函數參數x.2是傳遞給函數的參數double c的,而不是函數模板的非類型形參int a。
//g4(3,2);錯誤,起圖以整型值把實參傳遞給函數模板的非類型形參是不行的,這里數值2會傳遞給函數形參double c並把int型轉換為double型。所以非類型形參int a仍然無實參。
//int d=1; g4<int ,d >(3,3.2); //錯誤,調用方法正確,但對於非類型形參要求實參是一個常量表達式,而局部變量c是非常量表達式,不能做為非類型形參的實參,所以錯誤。
g4<int,1>(2,3.2);//正確,用顯示模板實參,把函數模板的類型形參T1設為int型,把數值1傳給非類型形參int a,並把a設為1,把數值2 傳給函數的第一個形參T1 b並把b設為2,數值?.2傳給函數的第二個形參double c並把c設為?.2。
const int d=1; g4<int,d>(2,3.2);//正確,這里變量d是const常量,能作為非類型形參的實參,這里參數的傳遞方法同上面的語句。
return 0;
}

顯示具體化(模板特化,模板說明) 和函數模板的重載

1、具體化或特化或模板說明指的是一個意思,就是把模板特殊化,比如有模板template<class T>void h(T a){},這個模板適用於所有類型,但是有些特殊類型不需要與這個模板相同的操作或者定義,比如int 型的h實現的功能和這個模板的功能不一樣,這樣的話我們就要重定義一個h模板函數的int 版本,即特化版本。

特化函數模板:

2、顯示特化格式為:template<>  反回類型函數名<要特化的類型>(參數列表) {函數體},顯示特化以template<>開頭,表明要顯示特化一個模板,在函數名后<>用尖括號括住要特化的類型版本。比如template <class T> void h(T a){},其int 類型的特化版本為template<> void h<int>(int a){},當出現int 類型的調用時就會調用這個特化版本,而不會調用通用的模板,比如h(2),就會調用int 類型的特化版本。

3、如果可以從實參中推演出模板的形參,則可以省略掉顯示模板實參的部分。比如:template<> void h(int a){}。注意函數h后面沒有<>符號,即顯示模板實參部分。

4、對於反回類型為模板形參時,調用該函數的特化版本必須要用顯示模板實參調用,如果不這樣的話就會出現其中一個形參無法推演的情況。如template<class T1,class T2,class T3> T1 h(T2 a,T3 b){},有幾種特化情況:

情況一:template<> int h<int,int>(int a, in b){}該情況下把T1,T2,T3的類型推演為int 型。在主函數中的調用方式應為h<int>(2,3)。

情況二:template<> int h(int a, int b){},這里把T2,T2推演為int 型,而T1為int 型,但在調用時必須用顯示模板實參調用,且在<>尖括號內必須指定為int 型,不然就會調用到通用函數模板,如h<int>(2,3)就會調用函數模板的特化版本,而h(2,3)調用會出錯。h<double>(2,3)調用則會調用到通用的函數模板版本。

這幾種情況的特化版本是錯誤的,如template<> T1 h(int a,int b){},這種情況下T1會成為不能識別的名字,因而出現錯誤,template<> int h<double>(int a,int b){}在這種情況下反回類型為int 型,把T1確定為int 而尖括號內又把T1確定為double型,這樣就出現了沖突。

5、具有相同名字和相同數量反回類型的非模板函數(即普通函數),也是函數模板特化的一種情況,這種情況將在后面參數匹配問題時講解。

函數模板重載(函數定制):

1、函數模板可以重載,注意類模板不存在重載問題,也就是說出現這兩條語句時template<class  T>class A{};

template<classT1,class T2>class A{};將出錯。

2、模板函數重載的形式為:template<class T> void h(T a, int b){}。Template<class T>void h(T a, double b){}等。

3、重載模板函數要注意二義性問題,比如template<class T> void h(T a, int b){}和template<class T>void h(T a, T b){}這兩

個版本就存在二義性問題,當出現語句h(2,3)時就不知道調用哪個才正確,在程序中應避免這種情況出現。

4、重載函數模板的第二個二義性問題是template<class T>void h(T a, T b){}與template<class T1, class T2>void h(T1 a,T2 b){},當出現h(2,4)這樣的調用時就會出現二義性。解決這個問題的方法是使用顯示模板實參,比如要調用第一個h函數,可以使用語法h<int>(2,3),調用第二個h函數的方法為h<int, int>(2,3)。

5、函數模板的特化也可以理解為函數模板重載的一種形式。只是特化以template<>開始。

6、重載的特殊情況:比如template<class T1,class T2> void h(T1 a, T2 b){},還有個版本如template<class T1>void h(T1 a, int b){}這里兩個函數具有兩同的名字和相同的形參數量,但形參的類型不同,可以認為第二個版本是第一個版本的重載版本。

7、函數模板的重載和特化很容易混曉,因為特化很像是一個函數的重載版本,只是開頭以template<>開始而已。

特化類模板:

6、特化整個類模板:比如有template<class T1,class T2> class A{};其特化形式為template<> class A<int, int>{};特化形式以template<>開始,這和模板函數的形式相同,在類名A后跟上要特化的類型。

7、在類特化的外部定義成員的方法:比如template<class T> class A{public: void h();};類A特化為template<>  class A<int>{public: void h();};在類外定義特化的類的成員函數h的方法為:void A<int>::h(){}。在外部定義類特化的成員時應省略掉template<>。

8、類的特化版本應與類模板版本有相同的成員定義,如果不相同的話那么當類特化的對象訪問到類模板的成員時就會出錯。因為當調用類的特化版本創建實例時創建的是特化版本的實例,不會創建類模板的實例,特化版本如果和類的模板版本的成員不一樣就有可能出現這種錯誤。比如:模板類A中有成員函數h()和f(),而特化的類A中沒有定義成員函數f(),這時如果有一個特化的類的對象訪問到模板類中的函數f()時就會出錯,因為在特化類的實例中找不到這個成員。

9、類模板的部分特化:比如有類模板template<class T1, class T2> class A{};則部分特化的格式為template<class T1> class A<T1, int>{};將模板形參T2特化為int 型,T1保持不變。部分特化以template開始,在<>中的模板形參是不用特化的模板形參,在類名A后面跟上要特化的類型。如果要特化第一個模板形參T1,則格式為template<class T2> class A<int, T2>{};部分特化的另一用法是template<class T1> class A<T1,T1>{};將模板形參T2也特化為模板形參T1的類型。

10、在類部分特化的外面定義類成員的方法:比如有部分特化類template<class T1> class A<T1,int>{public: void h();};則在類外定義的形式為template<class T1> void A<T1,int>::h(){}。注意當在類外面定義類的成員時template 后面的模板形參應與要定義的類的模板形參一樣,這里就與部分特化的類A的一樣template<class T1>。

其他說明:

11、可以對模板的特化版本只進行聲明,而不定義。比如template<> void h<int>(int a);注意,聲明時后面有個分號。

12、在調用模板實例之前必須要先對特化的模板進行聲明或定義。一個程序不允許同一模板實參集的同一模板既有顯示特化又有實例化。比如有模板template<class T> void h(T a){}在h(2)之前沒有聲明該模板的int 型特化版本,而是在調用該模板后定義該模板的int 型特化版本,這時程序不會調用該模板的特化版本,而是調用該模板產生一個新的實例。這里就有一個問題,到底是調用由h(2)產生的實例版本呢還是調用程序中的特化版本。

13、注意:因為模板的聲明或定義不能在局部范圍或函數內進行。所以特化類模板或函數模板都應在全局范圍內進行。

14、在特化版本中模板的類型形參是不可見的。比如template<> void h<int,int>(int a,int b){T1 a;}就會出現錯誤,在這里模板的類型形參T1在函數模板的特化版本中是不可見的,所以在這里T1是未知的標識符,是錯誤的。

#include <iostream>
using namespace std;
//函數模板特化和類模板特化示例
//定義函數g1,g2和類A
template<class T1,class T2> void g1(T1 a,T2 b){cout<<"g1"<<endl;}
template<class T1,class T2,class T3>T1 g2(T2 a,T3 b){  int c=1;cout<<"g2"<<endl;return c;}
template<class T1,class T2,class T3>class A{public:void h();}
//函數模板的特化定義。函數模板的特化可以理解為函數模板重載的另一種形式。
//下式為g1的類型形參顯示指定其類型,把T1,T2在模板實參的尖括號中設為int型。
template<> void g1<int,int>(int a,int b){cout<<"g1一"<<endl;}
//下式顯示設定g1的類型形參T1,並設為int型,T2由函數參數double推演為double型。
template<> void g1<int>(int a,double b){cout<<"g1二"<<endl;}  
template<> void g1(double a,double b){cout<<"g1三"<<endl;} //g1的類型形參都由g1的形參推演出來。
//template<> void g1<int>(double a,int b){cout<<"g•一"<<endl;}  //錯誤,在顯示模板實參的尖括號中顯示把類型形參T1的類型設為int型,而又在函數的形參中把類型形參T1的類型推演為double型,這樣就發生了沖突,出現錯誤。
template<> int g2<int>(int a,int b){int c=1;cout<<"g2一"<<endl;return c;}
 template<>double g2(int a,int b){int c=1;cout<<"g2二"<<endl;return c;}
//注意,下式正確,該式並不是對函數模板g2的部分特化,而是g2的重載。
//template<class T2> int g2(int a, T2 b){int c=1;cout<<"g2三"<<endl;return c;}
//下式錯誤,函數反回類型和<double>尖括號中的double類型不同,發生沖突。
//template<> int g2<double>(int a,int b){int c=1;cout<<"two"<<endl;return c;} 
//下式錯誤,函數模板的類型形參在特化版本中是不可見的,也就是說這里的會把類型形參T1理解為未聲明的標識符
//template<> T1 g2<int>(int a,int b){int c=1;cout<<"two"<<endl;return c;} 
//類模板的特化和部分特化
 template<>class A<int,int,int>{public:void h();}//特化整個類模板的格式,注意類名后的尖括號中必須指定所有的類模板的類型形參。
//template<> class A<int>{}; //錯誤,在特化的類名后的尖括號中指定的類模板類型形參的數量不夠。要想只特化其中一個類模板的類型形參,就要使用類模板的部分特化。
template<class T1,class T3>class A<T1,double,T3>{public:void h();}//特化T2,而T1和T?不特化,注意尖括號中的類型形參是不特化的形參。
//在類模板的特化或部分特化版本的外部定義成員函數的方法。
void A<int,int,int>::h(){cout<<"class A tehua"<<endl;} /*  T1 c; 錯誤,在特化版本中模板的類型形參是不可見的,也就是說在這里
T1是未聲明的標識符。*/
//template<> void A<int,int,int>::h(){} //錯誤,在類模板的特化版本外面定義類模板的成員時應省略掉template<>
template<class T1,class T3>void A<T1,double,T3)::h(){cout<<"class A bute"<<endl;}
template<class T1,class T2,class T3>void A<T1,T2,T3>::h(){cout<<"class A putong"<<endl;} //定義普通類模板中的成員函數。
//main函數開始
int main()
{   //特化的函數模板的調用方式。
    g1(2,2); //輸出"g1一",調用函數模板g1的第一個特化版本template<> void g1<int,int>(int a,int b){cout<<"g1一"<<endl;}
    g1(2,3.2); //輸出"g1二",調用函數模板g1的第二個特化版本template<> void g1<int>(int a,double b){cout<<"g1二"<<endl;}
    g1(3.3,4.4); //輸出"g1三",調用函數模板g1的第三個特化版本template<> void g1(double a,double b){cout<<"g1三"<<endl;}
    g1<double>(3,2.3);//輸出"g1三",這里用顯示模板實參把第一個實參指定為double型,這樣g1的兩個實參都是double型,所以將調用g1的第三個特化版本。
    //g2(3,3); 錯誤,在調用反回類型為類型形參的時候必須用顯示模板實參的形式為反回類型的形參顯示指定類型。在這里就會出現無法為T1確定類型的情況。
    g2<int>(2,3);//正確,把g2的類型形參T1設顯示指定為int,調用g2的第一個特化版本。template<> int g2<int>(int a,int b){int c=1;cout<<"g2一"<<endl;return c;}
    g2<double>(2,3);//正確,把g2的類型形參T1設顯示指定為double,調用g2的第二個特化版本。template<> double g2(int a,int b){int c=1;cout<<"g2二"<<endl;return c;}
    g2<char>(2,3);//正確,把g2的類型形參T1設顯示指定為char,對於char版本的g2函數沒有特化版本,因此調用g2的通用版本。
    //    template<class T1,class T2,class T3>T1 g2(T2 a,T3 b) {int c=1;cout<<"g2"<<endl;return c;}
   // 類模板特化和部分特化的調用。
     A<int,int,int> m1; m1.h();//正確,調用類模板的特化版本。
     A<int,double,int> m; m.h(); //正確,調用類模板的部分特化版本。
           //A<int,int> m2; //錯誤,類模板有三個類型形參,這里只提供了兩個,數量不夠,錯誤。
     A<double,double,int> m3; m3.h();//調用類A的部分特化版本。
      A<double,int,int> m4; m4.h();//調用類A的普通版本,在這里沒有A<double,int,int>型的特化或者部分特化版本可用。
      return 0;
}

1. 類模板

在此之前我們來看看模板的形參。因為函數模板的參數相對比較簡單,故將此內容放置於類模板中。模板形參有三種類型:類型形參、非類型形參和模板形參。先分別解釋如下:

n  類型形參。即由關鍵字class 或 typename后接的說明符構成,如template <class T>void function(T a);其中T就是類型形參。類型形參的名字由用戶自定義,只要是合法的標識符即可。

n  非類型形參。模板的非類型形參也就是內置類型形參,如template<class T,int a>class B{};其中int a就是非類型形參。非類型形參在模板定義的內部是常量值,也就是說非類型形參在模板內部是常量。使用非類型形參應注意以下幾點:

  1. 非類型形參只能是整型、指針和應用。如:double,string,string **等都是不允許的,但是double & ,double *是正確的。
  2. 調用非類型模板形參的實參必須是一個常量表達式,即在編譯時就能確定其結果。任何局部對象、局部變量、局部變量地址、局部對象地址等都不是一個常量表達式,都不能用作非類型模板形參的實參。全局指針類型、全局變量、全局對象也不是一個常量表達式,不能用作非類型形參的實參。但全局變量的地址、全局對象的地址或應用const類型的變量時常量表達式,可用作非類型模板形參的實參。Sizeof表達式的結果也是一個常量表達式,同樣也可以用作非類型模板形參的實參。如:

Template <class T,int a>class A{};如果有int b,這時A<int,b> m;就會出錯,因為b不是常量,如果有const int b;這時A<int ,b>就是正確的。

  1. 非類型形參一般不用於函數模板中。比如有函數模板template <class T,int a>void h(T,b){};若使用h(2)調用就會出錯,無法為非類型形參a推演出參數的錯誤。對這種函數模板可以采用顯示模板實參來解決,如h<int ,3>(2),這樣就把非類型形參a設置為整數3。顯示模板參數將在后面介紹。
  2. 非類型模板形參和實參間允許轉換。具體如下;

u  允許從數組到指針,從函數到指針的轉換。如template <int *a>class A{};int b[1];A<b>m。

u  Const修飾符的轉換。如template <const int *a>class A{};int b;A<&b>m;即從int * 到const int *的轉換。

u  提升轉換。如template <int a>class A{};const shor b;A<b>m;即從short到int的提升轉換。

u  整值轉換。如template <unsigned int a> class A{};A<3> m;即從int到unsigned int的轉換。

n  可以為類模板的類型形參提供默認值,但不能為函數模板的類型形參提供默認值。函數模板和類模板都可以為模板的非類型形參提供默認值。如template <class T1,class T2=int>class A{};為第二個模板類型形參提供int型的默認值。

n  類模板的類型形參默認值和函數的默認參數一樣,如果有多個類型形參則從第一個設定了默認值之后所以的模板形參都應設定默認值。如template <class T1=int,class T2>class D{};就是錯誤的,因為沒有給T2設定默認值。但在外部定義類中的成員時,應省去默認的形參類型。如template <class T1,class T2=int>class A{public:void H();};定義方法是template <class T1,class T2>void A<T1,T2>::H(){};

 

現將以上小節總結於以下一例,並通過vs2010調試,請讀者仔細相關知識點的應用。

#include <iostream>
using namespace std;
//模板的聲明或定義只能在全局,命名空間或類范圍內進行。即不能在局部范圍,函數內進行,比如不能在main函數中聲明或定義一個模板。
//類模板的定義
template<class T>class A{public:T g(T a, T b); A();};  //定義帶有一個類模板類型形參T的類A
template<class T1,class T2>class B{public:void g();}; //定義帶有兩個類模板類型形參T1,T2的類B
//定義類模板的默認類型形參,默認類型形參不適合於函數模板。
template<class T1,class T2=int> class D{public: void g();}; //定義帶默認類型形參的類模板。這里把T2默認設置為int型。
//template<class T1=int, class T2>class E{}; //錯誤,為T1設了默認類型形參則T1后面的所有形參都必須設置認默值。
//以下為非類型形參的定義
//非類型形參只能是整型,指針和引用,像double,String, String **這樣的類型是不允許的。但是double &,double *對象的引用或指針是正確的。
template<class T1,int a> class Ci{public:void g();}; //定義模板的非類型形參,形參為整型
template<class T1,int &a>class Cip{public:void g();}; 
template<class T1,A<int>* m> class Cc{public:void g();}; //定義模板的模板類型形參,形參為int型的類A的對象的指針。
template<class T1,double*a>class Cd{public:void g();};  //定義模板的非類型形參,形參為double類型的引用。
class E{}; template<class T1,E &m> class Ce{}; //非類型模板形參為對象的引用。
//以下非類型形參的聲明是錯誤的。
//template<class T1,A m>class Cc{}; //錯誤,對象不能做為非類型形參,非類型模板形參的類型只能是對象的引用或指針。
//template<class T1,double a>class Cc{}; //錯誤,非類型模板的形參不能是double類型,可以是double的引用。
//template<class T1,A<int> m>class Cc{}; //錯誤,非類型模板的形參不能是對象,必須是對象的引用或指針。這條規則對於模板型參也不例外。
//在類模板外部定義各種類成員的方法,
//typeid(變量名).name()的作用是提取變量名的類型,如int a,則cout<<typeid(a).name()將輸出int
template<class T>   A<T>::A(){cout<<"class A goucao"<<typeid(T).name()<<endl;} //在類模板外部定義類的構造函數的方法
template<class T> T A<T>::g(T a,T b){cout<<"class A g(T a,T b)"<<endl;} //在類模板外部定義類模板的成員
template<class T1,class T2>  void B<T1,T2>::g(){cout<<"class g f()"<<typeid(T1).name()<<typeid(T2).name()<<endl;}
//在類外面定義類的成員時template后面的模板形參應與要定義的類的模板形參一致
template<class T1,int a>     void Ci<T1,a>::g(){cout<<"class Ci g()"<<typeid(T1).name()<<endl;}
template<class T1,int &a>    void Cip<T1,a>::g(){cout<<"class Cip g()"<<typeid(T1).name()<<endl;} 
//在類外部定義類的成員時,template后的模板形參應與要定義的類的模板形參一致
template<class T1,A<int> *m> void Cc<T1,m>::g(){cout<<"class Cc g()"<<typeid(T1).name()<<endl;}
template<class T1,double* a> void Cd<T1,a>::g(){cout<<"class Cd g()"<<typeid(T1).name()<<endl;}
//帶有默認類型形參的模板類,在類的外部定義成員的方法。
//在類外部定義類的成員時,template的形參表中默認值應省略
template<class T1,class T2>  void D<T1,T2>::g(){cout<<"class D g()"<<endl;}
//template<class T1,class T2=int> void D<T1,T2>::g(){cout<<"class D k()"<<endl;} //錯誤,在類模板外部定義帶有默認類型的形參時,在template的形參表中默認值應省略。
//定義一些全局變量。
int e=2;  double ed=2.2; double*pe=&ed;
A<int> mw; A<int> *pec=&mw; E me;
//main函數開始
int main()
{ // template<class T>void h(){} //錯誤,模板的聲明或定義只能在全局,命名空間或類范圍內進行。即不能在局部范圍,函數內進行。
    //A<2> m; //錯誤,對類模板不存在實參推演問題,類模板必須在尖括號中明確指出其類型。
    //類模板調用實例
    A<int> ma; //輸出"class A goucao int"創建int型的類模板A的對象ma。
    B<int,int> mb; mb.g(); //輸出"class B g() int int"創建類模板B的對象mb,並把類型形參T1和T2設計為int
    //非類型形參的調用
    //調用非類型模板形參的實參必須是一個常量表達式,即他必須能在編譯時計算出結果。任何局部對象,局部變量,局部對象的地址,局部
//    變量的地址都不是一個常量表達式,都不能用作非類型模板形參的實參。全局指針類型,全局變量,全局對象也不是一個常量表達式,不能
//        用作非類型模板形參的實參。
        //全局變量的地址或引用,全局對象的地址或引用const類型變量是常量表達式,可以用作非類型模板形參的實參。
        //調用整型int型非類型形參的方法為名為Ci,聲明形式為template<class T1,int a> class Ci        Ci<int,GHIJKLMJKLNOPQMII//正確,數值R是一個int型常量,輸出"class Ci g() int"
        const int a2=3;Ci<int,a2> mci1; mci1.g(); //正確,因為a2在這里是const型的常量。輸出"class Ci g() int"
    //Ci<int,a> mci; //錯誤,int型變量a是局部變量,不是一個常量表達式。
    //Ci<int,e> mci; //錯誤,全局int型變量e也不是一個常量表達式。
    //調用int&型非類型形參的方法類名為Cip,聲明形式為template<class T1,int &a>class Cip
    Cip<int,e> mcip;  //正確,對全局變量的引用或地址是常量表達式。
    //Cip<int,a> mcip1; //錯誤,局部變量的引用或地址不是常量表達式。
    //調用double*類型的非類形形參類名為Cd,聲明形式為template<class T1,double *a>class Cd
    Cd<int,&ed> mcd; //正確,全局變量的引用或地址是常量表達式。
    //Cd<int,pe> mcd1; //錯誤,全局變量指針不是常量表達式。
    //double dd=aNGMIITbULcdefbbHIJKbgMIhh錯誤,局部變量的地址不是常量表達式,不能用作非類型形參的實參
    //Cd<int,&e> mcd;  //錯誤,非類型形參雖允許一些轉換,但這個轉換不能實現。
    //調用模板類型形參對象A<int> *的方法類名為Cc,聲名形式為template<class T1,A<int>* m> class Cc
    Cc<int,&mw> mcc; mcc.g(); //正確,全局對象的地址或者引用是常量表達式
    //Cc<int,&ma> mcc;  //錯誤,局部變量的地址或引用不是常量表達式。
    //Cc<int,pec> mcc2;  //錯誤,全局對象的指針不是常量表達式。
    //調用非類型形參E&對象的引用的方法類名為Ce。聲明形式為template<class T1,E &m> class Ce
    E me1; //Ce<int,me1> mce1; //錯誤,局部對象不是常量表達式
    Ce<int,me> mce;  //正確,全局對象的指針或引用是常量表達式。
    //非類型形參的轉換示例,類名為Ci
    //非類型形參允許從數組到指針,從函數到指針的轉換,const修飾符的轉換,提升轉換,整值轉換,常規轉換。
    const short s=3 ;Ci<int,s> mci ;//正確,雖然short型和int不完全匹配,但這里可以將short型轉換為int型
    return 0;
}

與函數模板相同,類模板的聲明語句也必須至於類聲明的前面。有兩個以上模板參數時,應使用逗號分開。使用含類模板的類定義對象時也必須在類名的后面帶上“﹤實際類型﹥”的參數列表。類模板最常用於各種類包容關系的設計模型中。定義:

Template ﹤類型參數表﹥ class 類名 {類聲明體}

在使用類模板時,應注意以下幾點:

n  在所有出現類模板的地方不能直接用類名表示,都應加上﹤…﹥

n  在類模板定義體中,可以省略﹤…﹥

n  一個類模板的各個實例之間沒有特殊的聯系(形成一個獨立的類)如:Queue<int> qi 和Queue<string> qs,分別表示整數隊列和字符隊列

n  實例化時機:在需要時實例化,比如定義指針或引用是不需要實例化,定義具體的變量或常量是會實例化,而訪問對象的成員時會實例化。如 Queue<int> *q //不實例化Queue<> ,Queue<int> iq //實例化Queue<>,iq->add(2) //實例化Queue<>

類模板中的友元:

n  非模板函數、類成為所有實例類的友元。如:

Class Foo { void bar();};

Template <class Type>

Class QueueItem

{

         Friend class Foobar;  //類Foobar不需要先定義或聲明,並沒有<>

         Frined void foo();    //函數foo()

         Frined void Foo::bar();//類Foo必須先定義

}

n  模板函數、模板類成為同類型實例類的友元。如:

Template <class Type> class Foo {…};

Template <class Type> void foo(QueueItem<Type>);

Template <class Type> class Queue{ void bar();};

Template <class Type> class QueueItem

{

         Friend class Foo<Type>;  //模板類Foo需要先定義或聲明,並帶有<>

         Friend void foo(QueueItem<Type>); //模板函數foo()需要先定義或聲明

         Friend void Queue<Type>::bar();   //模板類Queue必須先定義

}

n  模板函數、模板類成為不同類型實例類的友元。如:

Template <class T> class QueueItem

{

         Template <class Type> friend class Foo;

         Template <class Type> friend void foo(QueueItem<Type>);

         Template <class Type> friend void Queue::bar();

}

n  類模板的顯示實例化:和函數模板的顯示實例化一樣都是以template 開始。比如template class A<int,int>;將類A顯示實例化為兩個int 型的類模板。這里要注意顯示實例化后面不能有對象名,且以分號結束。顯示實例化可以讓程序員控制模板實例化發生的時間。

1、類模板中有普通友元函數,友元類,模板友元函數和友元類。

2、可以建立兩種類模板的友元模板,即約束型的友元模板和非約束型的友元模板。

3、非約束型友元模板:即類模板的友元模板類或者友元模板函數的任一實例都是外圍類的任一實例的友元,也就是外圍類和友元模板類或友元模板函數之間是多對多的關系

4、約束型友元模板:即類模板的友元模板類或友元模板函數的一個特定實例只是外圍類的相關的一個實例的友元。即外圍類和友元模板類或友元模板函數之間是一對一的關系。

5、約束型友元模板函數或友元類的建立:比如有前向聲明:template<class T1> void g(T1 a); template<class T2> void g1(); template<class T3>class B;則template<class T>class A{friend void g<>(T a); friend void g1<T>(); friend class B<T>;};就建立了三個約束型友元模板,其中g和g1是函數,而B是類。注意其中的語法。這里g<int>型和類A<int>型是一對一的友元關系,g<double>和A<double>是一個一對一的友元關系。

6、非約束型友元模板函數或友元類的建立:非約束型友元模板和外圍類具有不同的模板形參,比如template<class T>class A{template<class T1> friend void g(T1 a); template<class T2> friend class B;}注意其中的語法,非約束型友元模板都要以template開頭。要注意友元模板類,在類名B的后面沒有尖括號。

7、不存在部分約束型的友元模板或者友元類:比如template<class T> class A{template<class T1>friend void g(T1 a, T b);

template<class T3>friend class B<T3,T>;}其中函數g具有template<class T1,class T2>void g(T1 a,T2 b)的形式。其中的函數g試圖把第二個模板形參部分約束為類A的模板形參類型,但是這是無笑的,這種語法的結果是g函數的非約束型類友元函數,而對類B的友元聲明則是一種語法錯誤。

 

類模板中的模板成員(模板函數,模板類)和靜態成員

1、類模板中的模板函數和模板類的聲明:與普通模板的聲明方式相同,即都是以template 開始

2、在類模板外定義類模板中的模板成員的方法:比如template<class  T1> class A {public:template<class T2> class B;

template<class T3> void g(T3 a);};則在類模板外定義模板成員的方法為,template<class  T1> template<class T2> class A<T1>::B{};定義模板函數的方法為:template<class T1> template<class T3> void A<T1>::g(T3 a){}其中第一個template指明外圍類的模板形參,第二個template指定模板成員的模板形參,而作用域解析運算符指明是來自哪個類的成員。

3、實例化類模板的模板成員函數:比如上例中要實例化函數g()則方法為, A<int> m; m.g(2);這里外圍類A的模板形參由尖括號中指出,而類中的模板函數的參數由整型值2推演出為int 型。

4、創建類模板中的模板成員類的對象的方法:比如上例中要創建模板成員類B的方法為,A<int>::B<int> m1;A<int>::B<doble>m2;  A<double>::B<int> m3;在類模板成員B的前面要使用作用域解析運算符以指定來自哪個外圍類,並且在尖括號中要指定創建哪個外圍類的實例的對象。這里說明在類模板中定義模板類成員時就意味意該外圍模板類的一個實例比如int 實例將包含有多個模板成員類的實例。比如這里類A的int 實例就有兩個模板成員類B的int 和double兩個實例版本。

5、要訪問類模板中的模板成員類的成員遵守嵌套類的規則,因為類模板中的模板成員類就是一個嵌套類。即外圍類和嵌套類中的成員是相互獨立的,要訪問其中的成員只能通過嵌套類的指針,引用或對象的方式來訪問。具體情況見嵌套類部分。

6、類模板中的靜態成員是類模板的所有實例所共享的。

#include <iostream>
using namespace std;
template<class T1,class T2> class A{public:int a,b; static int e;};
template<class T3,class T4> class B;
template<class T5,class T6> void g(T5 a,T6 b);
class C{public:void gc(){cout<<"class C gc()"<<endl;}};
void g1(){cout<<"putong g1()"<<endl;} }
template<class T1,class T2>template<class T3,class T4}class A<T1,T2>::B{public:void gb(){cout<<"moban class B gb()"<<endl;}}
//在類模板外面定義類模板的模板成員類的方法
template<class T1,class T2>template<class T5,class T6}void A<T1,T2>::g(T5 a,T6 b){cout<<"moban g()"<<endl;}//在類模板外面定義類模板的模板成員函數的方法
template<class T1,class T2> int A<T1,T2>::e=0;//在類模板外面定義靜態成員的方法。
int main()
{
    A<int,int> ma;
    ma.g(2,3);//創建模板類中模板成員函數的方法,在這里模板類A的模板形參被設為int,而模板成員函數的模板形參則由兩個int型的整    數推演為int型。
    ma.e=1; A<int,int>::e=2;  //把類模板A的int,int型實例的靜態成員設為。
    cout<<"ma.e="<<ma.e<<endl; 
    A<int,double> ma1;
    cout<<"ma.e="<<ma1.e<<A<int,int>::e<<A<int,double>::e<<endl; //因為類模板A的int,int型實例和int,double實例是兩個實例,所以這里的靜態常量e的值不是三個二。
    A<int,int>::B<int,int> mb;  //聲明模板類中模板成員類的方法。
    mb.gb();//調用嵌套類B的成員函數
    //mb.g(); //錯誤,函數g()是外圍類的成員,嵌套類不能訪問外圍類的成員
    return 0;
}
簡單模板實例,參數列表為基本類型:

#include<iostream.h>
template<class T>
class Array
{    T *ar;
public:
        Array(int c){ar=new T[c];}
void init(int n,T x){ar[n]=x;    }
    T& operator[](int n){return ar[n];}
};
void main()
{ 
    Array<int> array(5);
    cout<<"Please input every element's value:"<<endl;
    for(int i=0;i<5;i++)
    { 
        cout<<"No."<<i+1<<':';  
        cin>>array[i];  
    }
}
類模板參數是類:

#include<iostream.h>
class A
{   
    int j;
public:
    A(){}
    A(int x):j(x){}
    A(A *x){j=x->j;}
    void operator!(){cout<<"J="<<j<<endl;}
}; 
template<class T>
class B
{  
    int i;
     T *x;
public:
    B(int xa,T *p):i(xa){x=new T(p);}
    void operator!(){cout<<"I="<<i<<endl;!*x;}
};

void main()
{
    A a(1);        //最后的顯示結果為:
    B<A> b(2,&a);  //I=2
    !b;           //J=1
}

Typename的使用:
#include <iostream>
template<class T> class X 
{ 
    typename T::id i; //如無typename看看情況如何
public: void f()
        {
            i.g();
        }
}; 

class Y 
{ 
public: class id 
        { 
            public: void g() 
                    {
                        std::cout<<"Hello World!"<<std::endl;
                    } 
        }; 
}; 
 
int main() 
{ 
    //Y y;
    X<Y> xy; 
    xy.f(); 
    return 0;
}

Typename關鍵字告訴編譯器把一個特殊的名字解釋成一個類型,在下列情況下必須對一個name使用typename關鍵字:

n  一個唯一的name(可以作為類型理解),嵌套在另一個類型中。

n  依賴於一個模板參數,就是說,模板參數在某種程度上包含這個name。當模板參數使編譯器在指認一個類型時便會產生誤解。

在定義模板時,typename和class作用基本相同,至於二者的其他關系沒有什么區別,僅是歷史原因,typename僅是一個新生代。

復雜的模板類實例:

#include<iostream.h>
#include<string.h>
class Student
{
    int number;
    static Student *ip;
    Student *p;
public:
    Student(){p=NULL;}
    Student(int n);
    static Student* get_first(){return ip;}
    int get_number(){return this->number;}
    Student* get_next(){return this->p;}
};

Student::Student(int n):number(n)  //依據學號的大小順序將學生對象插入鏈表
{
    p=NULL;
    if(ip==NULL)ip=this;  //如果是第一個則使頭指針指向該對象
    else{Student *temp=ip;      
    if(n<ip->number){ip=this;p=temp;}//如學號小於第一個學生對象的學號則使頭指針指向該對象
    else {
        while(temp)
        {
            
            if(n<temp->p->number)
            {
                p=temp->p;  //鏈中間對象的插入
                temp->p=this;
                break;
            }else 
            {    
                if(temp->p->p==NULL)  //最后一個鏈的插入
                {
                    temp->p->p=this;break;
                }                    }
            temp=temp->p;
        }
    }
    }
}
Student* Student::ip=NULL;
template<class T>
class Class
{
    int num;
    T *p;
public:
       Class(){}
       Class(int n):num(n){p=NULL;}
       T* insert(int n){p=new T(n);return p;}
       void list_all_member(T* x)
       {   T *temp=x;
       while(temp) { cout<<temp->get_number()<<",";temp=temp->get_next();}
       }
};
void main()
{ 
    Class<Student> x97x(9707);
    x97x.insert(23);
    x97x.insert(12);
    x97x.insert(38);
    x97x.insert(22);
    x97x.insert(32);
    x97x.list_all_member(Student::get_first());
}

現在來討論模板安全:模板根據參數的類型進行實例化。因為通常事先不知道其具體類型,所以也無法確切知道將在哪兒產生異常。程序員需要知道程序在什么地方發生了異常。下面看一個簡單的模板類:

Template <typename T>

Class Wrapper

{

Public:

         Wrapper(){}

         T get(){return value_;}

         T set(T const &value){value_=value;}

Private:

         T value_;

         Wrapper(Wrapper const &);

        Wrapper &operator=(Wrapper const &);

};

實例化過程很簡單,如Wrapper <int> i;因為Wrapper <int>只接受int或其引用,所以不會觸及異常,Wrapper <int>不拋異常,也沒有直接或者間接調用任何可能拋異常的函數,因此Wrapper <int>是異常安全的。

現在再來看Wrapper<X>x,這里X是一個類。在這個定義里,編譯器實例化了:

Template <> Class Wrapper<X>

{

Public:

         Wrapper(){}

         X get(){return value_;}

         X set(X const &value){value_=value;}

Private:

         T value_;

         Wrapper(Wrapper const &);

        Wrapper &operator=(Wrapper const &);

};

現在就有問題出現了:

n  Wrapper<X> 包含了一個X的子對象。這個子對象需要構造,意味着調用X的構造函數,這個構造函數可能拋出異常。

n  Wrapper<X>::get()產生並返回了一個X的臨時對象。為了這個臨時對象,get()調用了X的拷貝構造函數,這個函數可能拋出異常。

n  Wrapper<X>::set()執行了表達式value_=value,他實際上調用了X的賦值運算。這個運算可能拋出異常。

可以看到,同樣的模板和同樣的語句,但其含義不同。由於這樣的不確定性,我們需要采用保守策略:假設Wrapper會根據類來實例化,而這些類在其成員上沒有進行異常規格申明,則他們可能拋出異常。

再假設Wrapper的異常規格申明承諾其成員不產生異常。至少必須在其成員上加上異常規格申明throw(),所以需要修補掉這些可能導致異常的地方:

n  在Wrapper::Wrapper()中構造value_的過程。

n  在Wrapper::get()中返回value_的過程。

n  在Wrapper::set()中隊value_的賦值過程。

另外,在違背throw()的異常規格申明是,還要處理std::unexpected.

 

再來看默認構造函數:

Wrapper() throw()

Try:T() {}

Catch (…){}

雖然看上去不錯,但它不能工作,根據C++標准:對構造函數或析構函數上的function-try-block,當控制權到達了異常處理函數的結束點是,被捕獲的異常被再次拋出。對於一般的函數,此時是函數返回,等同於沒有返回值的return 語句,對於定了返回類型的函數此時的行為未定義。換句話說,上面的程序相當於:

X::X() throw()

Try: T (){}

Catch (…){ throw;}

這不是程序本來想要的結果,換成以下代碼:

X::X() throw()

Try: T (){}

Catch (…){ return;}

 

但是它卻違背了標准:如果在構造函數上的function-try-block的異常處理函數體中出現了return語句,則程序是病態的。最終:無法用function-try-block快來實現構造函數的接口安全。

n  引申原則1:盡可能使用構造函數不拋異常的基類或成員子對象。

n  引申原則2:為了幫助別人實現原則1,不要從構造函數中拋出任何異常。

其他方面的不再討論,比如析構與關鍵字new等。總之,良好的設計必須滿足以下兩個原則:

n  通過異常對象的存在來注視異常狀態,並適當的做出反應。

n  確保創造和傳播異常對象不會造成更大的破壞。

 

最終代碼的參考將如下:

template <typename T>
class wrapper
{
public:
    wrapper()     throw() : value_(NULL)
    {      
        try 
        {    
            value_ = new T;
        }
        catch (...) {      }
    }
    ~wrapper() throw()
    {    
        try 
        { 
            delete value_;
        }
        catch (...){operator delete(value_);}
    }
    bool get(T &value) const throw(){return assign(value, *value_);}
    bool set(T const &value) throw(){return assign(*value_, value);}
private:
    bool assign(T &to, T const &from) throw()
    {
        bool error(false);
        try{to = from; }
        catch (...) { error = true;}
        return error;
    }
    T *value_;
    wrapper(wrapper const &);
    wrapper &operator=(wrapper const &);
};
void main()
{
    wrapper<int> mywrapper();
}

可以像使用普通類的方法來使用模板類,這一點毫無疑問,例如:可以繼承、創建一個從現有模板繼承過來的並已經初始化的模板。現在,我們來看看模板的繼承,如果vector已經為你做了很多事,但你還想加入sort()的功能,則可用下面代碼來擴充。

#ifndef SORTED
#define SORTED 
#include<vector>

template <class T>
class Sorted:public std::vector<T>
{
public:
        void sort();
};

template <class T>
void Sorted<T>::sort()
{
    for (int i=size();i>0;i--)
    {
        for (int j=1;j<i;j++)
        {
            if (at(j-1)>at(j))
            {
                T t=at(j-1);
                at(j-1)=at(j);
                at(j)=t;
            }
        }
    }
}

#endif
實現文件

#include "123.h" 
#include <iostream> 
#include <string> 
using namespace std; 
char* words[] = {"is", "running", "big", "dog", "a"}; 
char* words2[] = { "this", "that", "theother" }; 
int main() 
{ 
    Sorted<int> is; 
    for(int i = 15; i >0; i--)    is.push_back(i); 
    for(int l = 0; l < is.size(); l++)  cout << is[l] << ' '; 
    cout << endl; 
    is.sort(); 
    for(l = 0; l < is.size(); l++) cout << is[l] << ' '; 
    cout << endl; 
    Sorted<string*> ss; 
    for(i = 0; i < 5; i++) ss.push_back(new string(words[i])); 
    for(i = 0; i < ss.size(); i++) cout << *ss[i] << ' '; 
    cout << endl; 
    ss.sort(); 
    for(i = 0; i < ss.size(); i++) cout << *ss[i] << ' '; 
    cout << endl; 
    Sorted<char*> scp; 
    for(i = 0; i < 3; i++) scp.push_back(words2[i]); 
    for(i = 0; i < scp.size(); i++) cout << scp[i] << ' '; 
    cout << endl; 
    scp.sort(); 
    for(i = 0; i < scp.size(); i++) cout << scp[i] << ' '; 
    cout << endl; 
    return  0;
}

以上簡單實現了模板的繼承,讀者可自行編寫相關代碼進行測試,並分析模板繼承情況下,析構函數和構造函數等的消壞和初始化情況,這里不這討論。

注意:子類並不會從通用的模板基類繼承而來,只能是從基類的某一個實例繼承而來。

現將模板的繼承方式總結以下幾點:

n  基類是模板類的一個特定實例化的版本。比如:template <class T1> class B:public A<int>{}.

n  基類是一個和子類相關的一個實例。比如:template <class T1>class B:public A<T1>{}。這時實例化基類就相應的被實例化一個和基類相同的實例版本,比如:B<int> b;模板B被實例化為int 版本,這時基類A也相應的被實例化為Int版本。

n  如果基類是一個特定的實例化版本,這時子類可以不是一個模板,比如:class B:public A<int>{};。

 

每次實例化一個模板,模板的代碼都會被重新生成(除了inline標記的函數),如果一個模板某些函數不依賴於特定的類型參數而存在,那它們就可以放置在一個通用的基礎類中,來阻止無意義的代碼重生。

Inline函數因不產生新的代碼所以它們是自由的,在整個過程中,功能性的代碼只是在我們創建基礎類代碼時產生了一次,而且,所屬權的問題也因為增加了新的析構函數而解決。通常模板只有在需要的時候才實例化,對函數模板來說,這就意味着調用它時才被實例化,但對類模板來說,它更加明細化,只有在使用到模板中的某個函數式,函數才會被實例化,換句話說:只有用到的成員函數被實例化了。例如:

#include <iostream.h>
class X 
{
public: 
    void x() 
    {  
        cout<<"This is fuction x()"<<endl;
    } 
}; 
class Y 
{
public: 
    void y() 
    {   
        cout<<"This is fuction y()"<<endl; 
    } 
}; 

template <typename T> class Z 
{
    T t; 
public: 
    void a() { t.x(); } 
    void b() { t.y(); } 
}; 

int main() 
{
    Z<X> zx; 
    zx.a(); // Doesn't create Z<X>::b() 
    Z<Y> zy; 
    zy.b(); // Doesn't create Z<Y>::a()
    return 0;
}

最后用模板技術演示list的的使用。

// Template class for storing list elements
#include <iostream>
#include <string>
using namespace std;
template <class T>                          // Use template keyword
class ListElement                            //定義類ListElement,用於表示list對象
{
public:
    T data;
    ListElement<T> * next ;
    ListElement(T& i_d, ListElement <T>* i_n)
    : data(i_d),next(i_n) { }
    ListElement<T>* copy()                    // copy includes all next elements
    {    
        return new ListElement(data,(next?next->copy():0));    
    }
};
template <class T> 
class ListIterator        //定義類ListIterator,用於訪問和操作list對象
{ 
public :
                                            //ListIterator(List<T>& l) ;
   T operator()() ;
   int operator++() ;
   int operator!() ;
public:
     ListElement<T>* rep ;
};
template <class T>
T ListIterator<T>::operator() ()
{ 
    if (rep) return rep->data;
    else 
    {
        T tmp ;    return tmp ;                // Default value  
    }
}
template <class T>
int ListIterator<T>::operator++()
{   
    if (rep)
        rep = rep->next ;   
    return (rep != 0) ;
}
template <class T>
int ListIterator<T>::operator!()
{   
    return (rep != 0) ;
}
template <class T>
class List                //定義類List 
{
public:
    friend class ListIterator<T> ;
    List();
    List(const List&);
    ~List();
    List<T>& operator=(const List<T>&);
                                            // typical list ops
    T head();
    List<T> tail();
    void add(T&);
    friend ostream& operator<<(ostream&, const List<T>&);
public:
    void clear() ;                            // Delete all list elements
    ListElement<T>* rep ;
};
                                            // Default Constructor
template <class T>
List<T>::List(){ rep = 0 ; }
                                            // Copy Constructor
template <class T>List<T>::List(const List<T>& l)
{     
    rep = l.rep ? l.rep->copy() : 0 ;
}
                                            // Overloaded assignment operator
template <class T>
List<T>& List<T>::operator=(const List<T>& l)
{
    if (rep != l.rep)   
    {        
        clear() ;        
        rep = l.rep ? l.rep->copy() : 0 ;    
    }
    return *this ;
}
                                            // Destructor
template <class T>
List<T>::~List(){  clear() ;}
                                            // Delete representation
template <class T>
void List<T>::clear()
{   while (rep)
    {   ListElement<T>* tmp = rep ;
        rep = rep->next ;
        delete tmp ;
    }
    rep = 0 ;
}
                                            // Add element to front of list
template <class T>
void List<T>::add(T& i)
{   
    rep = new ListElement<T>(i,rep) ;
}
                                            // Return head of list or default value of type T
template <class T>
T List<T>::head()
{ 
    if (rep) return rep->data ; 
    else 
    {
        T tmp ; 
        return tmp ;
    }
}
                                            // Return tail of list or empty list
template <class T>
List<T> List<T>::tail()
{    List<T> tmp ;
     if (rep)
       if (rep->next) tmp.rep = rep->next->copy() ;
     return tmp ;
}
                                            // Output operator
template <class T>
ostream& operator<<(ostream& os, const List<T>& l)
{
    if (l.rep)
    {
        ListElement<T>* p = l.rep ;
        os << "( " ;
        while (p){ os << p->data << " " ;   p = p->next ; }
        os << ")\n" ;
    }
    else
    os << "Empty list\n" ;
    return os ;
}
int main()
{
   List<int> l ;                            // Integer list
   cout << l ;
   int i=1;
   l.add(i) ;
   i=2;
   l.add(i) ;
   i=3;
   l.add(i) ;
   cout << "l is " << l << endl ;
   cout << "head of l is " << l.head() << endl ;
   List<int> m = l.tail() ;
   List<int> o ;
   o = m;
   i=4;
   m.add(i);
   cout << "m is " << m << endl ;
   cout << "o is " << o << endl ;
   List<char> clist ;                        // Character list
   char ch;
   ch='a';
   clist.add(ch);
   ch='b';
   clist.add(ch);
   cout << clist << endl ;
   List<string> ls ;                        // string List
   ls.add(string("hello")) ;
   ls.add(string("world")) ;
   cout << "List of strings" << endl ;
   cout << ls << endl ;
   List<List<int> > listlist ;                // List of lists of integer. Notice that lists of lists are possible
   listlist.add(o) ;
   listlist.add(m) ;
   cout << "List of lists of ints\n" ;
   cout << listlist << endl ;
   List<List<List<int> > > lllist ;            // List of lists of lists of integer
   lllist.add(listlist) ;
   lllist.add(listlist) ;
   cout << "List of lists of lists of ints\n" ;
   cout << lllist << "\n" ;
   List<List<string> > slist ;               // List of list of strings
   slist.add(ls) ;
   slist.add(ls) ;
   cout << "List of lists of strings\n" ;
   cout << slist << "\n" ;
   return 0 ;
}

注意:以上某一個實例好像未通過調試,基於時間本人已忘記,讀者發現后可查看相關情況自行更正。

 


免責聲明!

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



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