1. 程式人生 > >C++模板詳解

C++模板詳解

一.C++模板的定義:

       模板是C++支援引數化多型的工具,使用模板可以使使用者為類或者函式宣告一種一般模式,使得類中的某些資料成員或者成員函式的引數、返回值取得任意型別。

       模板是一種對型別進行引數化的工具。

二.使用模板的目的:

   能夠使得程式設計師編寫與型別無關的程式碼。比如編寫了一個交換兩個整型int 型別的swap函式,這個函式就只能實現int 型,對double,字元這些型別無法實現,要實現這些型別的交換就要重新編寫另一個swap函式。使用模板的目的就是要讓這程式的實現與型別無關,比如一個swap模板函式,即可以實現int 型,又可以實現double型的交換。模板可以應用於函式和類(注意:模板的宣告或定義只能在全域性,名稱空間或類範圍內進行。即不能在區域性範圍,函式內進行,比如不能在main函式中宣告或定義一個模板)。

三.C++模板通常有兩種形式:

1.函式模板

(1).函式模板針對僅引數型別不同的函式;

(2).函式模板通式:

    template <class 形參名,class 形參名,......> 返回型別函式名(引數列表)
{
      函式體
}

其中template和class是關鍵字,class可以用typename 關鍵字代替,在這裡typename和class沒區別,<>括號中的引數叫模板形參,模板形參和函式形參很相像,模板形參不能為空。一但聲明瞭模板函式就可以用模板函式的形參名宣告類中的成員變數和成員函式,即可以在該函式中使用內建型別的地方都可以使用模板形參名。模板形參需要呼叫該模板函式時提供的模板實參來初始化模板形參,一旦編譯器確定了實際的模板實參型別就稱他例項化了函式模板的一個例項。比如swap的模板函式形式為:

 template <class T> void swap(T& a,T& b){}, 

當呼叫這樣的模板函式時型別T就會被被呼叫時的型別所代替,比如swap(a,b)其中a和b是int 型,這時模板函式swap中的形參T就會被int 所代替,模板函式就變為swap(int &a, int &b)。而當swap(c,d)其中c和d是double型別時,模板函式會被替換為swap(double&a, double &b),這樣就實現了函式的實現與型別無關的程式碼(注意:對於函式模板而言不存在 h(int,int) 這樣的呼叫,不能在函式呼叫的引數中指定模板形參的型別,對函式模板的呼叫應使用實參推演來進行,即只能進行 h(2,3) 這樣的呼叫,或者int a, b; h(a,b))。

2.類模板

(1).類模板針對僅資料成員和成員函式型別不同的類。

(2).類模板通式

     template<class  形參名,class 形參名,…>   class 類名
{
        …..;
  };

  類模板和函式模板都是以template開始後接模板形參列表組成,模板形參不能為空,一但聲明瞭類模板就可以用類模板的形參名宣告類中的成員變數和成員函式,即可以在類中使用內建型別的地方都可以使用模板形參名來宣告。比如:

template<classT> class A{public: T a; T b; T hy(Tc, T &d);};

在類A中聲明瞭兩個型別為T的成員變數a和b,還聲明瞭一個返回型別為T帶兩個引數型別為T的函式hy。

  類模板物件的建立:比如一個模板類A,則使用類模板建立物件的方法為A<int> m;在類A後面跟上一個<>尖括號並在裡面填上相應的型別,這樣的話類A中凡是用到模板形參的地方都會被int 所代替。當類模板有兩個模板形參時建立物件的方法為A<int, double> m;型別之間用逗號隔開。

  對於類模板,模板形參的型別必須在類名後的尖括號中明確指定。比如A<2> m;用這種方法把模板形參設定為int是錯誤的(編譯錯誤:error C2079: 'a' uses undefined class'A<int>'),類模板形參不存在實參推演的問題。也就是說不能把整型值2推演為int 型傳遞給模板形參。要把類模板形參調置為int 型必須這樣指定A<int> m。

  在類模板外部定義成員函式的方法為:

  template<模板形參列表> 函式返回型別類名<模板形參名>::函式名(引數列表){函式體},

比如有兩個模板形參T1,T2的類A中含有一個void h()函式,則定義該函式的語法為:

 template<classT1,class T2> voidA<T1,T2>::h(){}。

注意:當在類外面定義類的成員時template後面的模板形參應與要定義的類的模板形參一致。

  備註:再次提醒注意:模板的宣告或定義只能在全域性,名稱空間或類範圍內進行。即不能在區域性範圍,函式內進行,比如不能在main函式中宣告或定義一個模板。

四.模板的形參

1.型別模板形參:

型別形參由關鍵字class或typename後接說明符構成,如template<class T> void h(T a){};其中T就是一個型別形參,型別形參的名字由使用者自已確定。模板形參表示的是一個未知的型別。模板型別形參可作為型別說明符用在模板中的任何地方,與內建型別說明符或類型別說明符的使用方式完全相同,即可以用於指定返回型別,變數宣告等。

在類模板中,當我們宣告類物件為:A<int> a,比如template<class T>T g(T a, T b){},語句呼叫a.g(2, 3.2)在編譯時不會出錯,但會有警告,因為在宣告類物件的時候已經將T轉換為int型別,而第二個實參3.2把模板形參指定為double,在執行時,會對3.2進行強制型別轉換為3。當我們宣告類的物件為:A<double> a,此時就不會有上述的警告,因為從int到double是自動型別轉換。

在函式模板中,不能為同一個模板型別形參指定兩種不同的型別,比如template<class T>void h(T a, T b){},語句呼叫h(2,3.2)將出錯,因為該語句給同一模板形參T指定了兩種型別,第一個實參2把模板形參T指定為int,而第二個實參3.2把模板形參指定為double,兩種型別的形參不一致,會出錯。

型別實參例項:

TemplateDemo.h

#ifndef TEMPLATE_DEMO_HXX
#define TEMPLATE_DEMO_HXX
template<class T> class A{
   public:
       T g(T a,T b);
       A();
};
#endif

TemplateDemo.cpp

#include<iostream.h>
#include "TemplateDemo.h"
 
template<class T> A<T>::A(){}
 
template<class T> T A<T>::g(Ta,T b){
   return a+b;
}
void main(){
   A<int> a;
   cout<<a.g(2,3.2)<<endl;//注意,如果兩個變數型別不一致,會報警告
}

2.非型別形參:

非型別模板形參概念(一般不應用於函式模板中):

模板的非型別形參也就是內建型別形參,如template<classT, int a> class B{};其中inta就是非型別的模板形參。

非型別模板形參的特點:

(1).非型別形參在模板定義的內部是常量值,也就是說非型別形參在模板的內部是常量。

(2). 非型別模板的形參只能是整型,指標和引用,像double,String, String **這樣的型別是不允許的。但是double &,double *,物件的引用或指標是正確的。

(3). 呼叫非型別模板形參的實參必須是一個常量表達式,即他必須能在編譯時計算出結果。

(4).注意:任何區域性物件,區域性變數,區域性物件的地址,區域性變數的地址都不是一個常量表達式,都不能用作非型別模板形參的實參。全域性指標型別,全域性變數,全域性物件也不是一個常量表達式,不能用作非型別模板形參的實參。

(5). 全域性變數的地址或引用,全域性物件的地址或引用const型別變數是常量表達式,可以用作非型別模板形參的實參。

(6).sizeof表示式的結果是一個常量表達式,也能用作非型別模板形參的實參。

(7).當模板的形參是整型時呼叫該模板時的實參必須是整型的,且在編譯期間是常量,比如template <class T, int a> class A{};如果有int b,這時A<int, b> m;將出錯,因為b不是常量,如果const int b,這時A<int, b> m;就是正確的,因為這時b是常量。

(8).非型別形參一般不應用於函式模板中,比如有函式模板template<class T, int a> void h(Tb){},若使用h(2)呼叫會出現無法為非型別形參a推演出引數的錯誤,對這種模板函式可以用顯示模板實參來解決,如用h<int, 3>(2)這樣就把非型別形參a設定為整數3。顯示模板實參在後面介紹。

(9). 非型別模板形參的形參和實參間所允許的轉換:

1).允許從陣列到指標,從函式到指標的轉換。如:template <int *a> classA{}; int b[1]; A<b> m;即陣列到指標的轉換

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

3).提升轉換。如:template<int a> class A{}; const short b=2; A<b> m; 即從short到int 的提升轉換

4).整值轉換。如:template<unsigned int a> class A{};   A<3> m; 即從int 到unsigned int的轉換。5).常規轉換。

非型別形實參例項1:由使用者自己親自指定棧的大小,並實現棧的相關操作。

TemplateDemo.h

#ifndef TEMPLATE_DEMO_HXX
#define TEMPLATE_DEMO_HXX
template<class T,int MAXSIZE> classStack{//MAXSIZE由使用者建立物件時自行設定
   private:
       T elems[MAXSIZE];    // 包含元素的陣列
       int numElems;    // 元素的當前總個數
   public:
       Stack();    //建構函式
       void push(T const&);    //壓入元素
       void pop();        //彈出元素
       T top() const;    //返回棧頂元素
       bool empty() const{     // 返回棧是否為空
           return numElems == 0;
       }
       bool full() const{    // 返回棧是否已滿
           return numElems == MAXSIZE;
       }
};
template <class T,int MAXSIZE>
Stack<T,MAXSIZE>::Stack():numElems(0){     // 初始時棧不含元素
   // 不做任何事情
}
template <class T,int MAXSIZE>
void Stack<T, MAXSIZE>::push(Tconst& elem){
   if(numElems == MAXSIZE){
       throw std::out_of_range("Stack<>::push(): stack isfull");
   }
   elems[numElems] = elem;   // 附加元素
   ++numElems;               // 增加元素的個數
}
template<class T,int MAXSIZE>
void Stack<T,MAXSIZE>::pop(){
   if (numElems <= 0) {
       throw std::out_of_range("Stack<>::pop(): empty stack");
   }
   --numElems;               // 減少元素的個數
}
template <class T,int MAXSIZE>
T Stack<T,MAXSIZE>::top()const{
   if (numElems <= 0) {
       throw std::out_of_range("Stack<>::top(): empty stack");
   }
   return elems[numElems-1];  // 返回最後一個元素
}
#endif

TemplateDemo.cpp

#include<iostream.h>
#include <iostream>
#include <string>
#include <cstdlib>
#include "TemplateDemo.h"
int main(){
   try {
       Stack<int,20> int20Stack;  // 可以儲存20個int元素的棧
       Stack<int,40> int40Stack;  // 可以儲存40個int元素的棧
       Stack<std::string,40> stringStack; // 可儲存40個string元素的棧
       // 使用可儲存20個int元素的棧
       int20Stack.push(7);
       std::cout << int20Stack.top() << std::endl;    //7
       int20Stack.pop();
       // 使用可儲存40個string的棧
       stringStack.push("hello");
       std::cout << stringStack.top() << std::endl;    //hello
       stringStack.pop();   
       stringStack.pop();    //Exception:Stack<>::pop<>: empty stack
       return 0;
   }
   catch (std::exception const& ex) {
       std::cerr << "Exception: " << ex.what() <<std::endl;
       return EXIT_FAILURE;  // 退出程式且有ERROR標記
   }
}

執行結果:

 非型別形參演示示例2:

TemplateDemo01.h

#ifndef TEMPLATE_DEMO_O1
#define TEMPLATE_DEMO_01
template<typename T> classCompareDemo{
   public:
       int compare(const T&, const T&);
};
template<typename T>
int CompareDemo<T>::compare(constT& a,const T& b){
   if((a-b)>0)
       return 1;
   else if((a-b)<0)
       return -1;
   else
       return 0;
}
#endif

測試1:

#include<iostream.h>
#include "TemplateDemo01.h"
void main(){
   CompareDemo<int> cd;
   cout<<cd.compare(2,3)<<endl;
}

結果:-1

測試2:

#include<iostream.h>
#include "TemplateDemo01.h"
void main(){
   CompareDemo<double> cd;
   cout<<cd.compare(3.2,3.1)<<endl;
}

結果:1

TemplateDemo01.h 改動如下:

#ifndef TEMPLATE_DEMO_O1
#define TEMPLATE_DEMO_01
template<typename T> classCompareDemo{
   public:
       int compare(T&, T&);
};
template<typename T>
int CompareDemo<T>::compare(T&a,T& b){
   if((a-b)>0)
       return 1;
   else if((a-b)<0)
       return -1;
   else
      return 0;
}
#endif

TempalteDemo01.cpp

#include<iostream.h>
#include "TemplateDemo01.h"
 
void main(){
   CompareDemo<int> cd;
   int a=2,b=3;
   cout<<cd.compare(a,b)<<endl;
}
TemplateDemo02.cpp
#include<iostream.h>
template<typename T>
const T& max(const T& a,constT& b){
   return a>b ? a:b;
}
void main(){
   cout<<max(2.1,2.2)<<endl;//模板實參被隱式推演成double
   cout<<max<double>(2.1,2.2)<<endl;//顯示指定模板引數。
   cout<<max<int>(2.1,2.2)<<endl;//顯示指定的模板引數,會將函式函式直接轉換為int。(備註:會產生警告)
}

3.類模板的預設模板型別形參

(1).可以為類模板的型別形參提供預設值,但不能為函式模板的型別形參提供預設值。函式模板和類模板都可以為模板的非型別形參提供預設值。

(2).類模板的型別形參預設值形式為:template<classT1, class T2=int> class A{};為第二個模板型別形參T2提供int型的預設值。

(3).類模板型別形參預設值和函式的預設引數一樣,如果有多個型別形參則從第一個形參設定了預設值之後的所有模板形參都要設定預設值,比如template<classT1=int, class T2>class A{};就是錯誤的,因為T1給出了預設值,而T2沒有設定。

(4).在類模板的外部定義類中的成員時template 後的形參表應省略預設的形參型別。比如template<class T1, class T2=int> class A{public: void h();}; 定義方法為template<class T1,class T2> void A<T1,T2>::h(){}。

定義類模板型別形參:

演示例項1:

TemplateDemo.h

#ifndefTEMPLATE_DEMO_HXX
#defineTEMPLATE_DEMO_HXX
template<classT> class A{
    public:
        T g(T a,T b);
        A();
};
#endif

TemplateDemo.cpp

#include<iostream.h>
#include"TemplateDemo.h"
template<classT> A<T>::A(){}
template<classT> T A<T>::g(T a,T b){
    return a+b;
}
void main(){
    A<int> a;
    cout<<a.g(2,3)<<endl;
}

結果:5

  類模板的預設模板型別形參示例1:

TemplateDemo3.h

#ifndefTEMPLATE_DEMO_03
#defineTEMPLATE_DEMO_03
//定義帶預設型別形參的類模板。這裡把T2預設設定為int型。
template<classT1,class T2=int> class CeilDemo{
    public:
        int ceil(T1,T2);
};
//在類模板的外部定義類中的成員時template 後的形參表應省略預設的形參型別。
template<classT1,class T2>
intCeilDemo<T1,T2>::ceil(T1 a,T2 b){
    return a>>b;
}
#endif

TemplateDemo3.cpp

#include<iostream.h>
#include "TemplateDemo03.h"
void main(){
   CeilDemo<int> cd;
   cout<<cd.ceil(8,2)<<endl;
}

執行結果:2

在類模板的外部定義類中的成員時template 後的形參表應省略預設的形參型別,如果沒有省略,不會出現編譯錯誤而是提出警告:

原作者:類模板型別形參預設值和函式的預設引數一樣,如果有多個型別形參則從第一個形參設定了預設值之後的所有模板形參都要設定預設值,比如template<class T1=int, classT2>class A{};就是錯誤的,因為T1給出了預設值,而T2沒有設定。

例項測試如下:

類模板的預設模板型別形參示例2:

TemplateDemo03.h

#ifndef TEMPLATE_DEMO_03
#define TEMPLATE_DEMO_03
template<class T1=int,class T2,classT3> class CeilDemo{
   public:
       int ceil(T1,T2,T3);
};
template<class T1,class T2,class T3>
int CeilDemo<T1,T2,T3>::ceil(T1 a,T2b,T3 c){
   return a+b+c;
}
#endif

TemplateDemo03.cpp

#include<iostream.h>
#include "TemplateDemo03.h"
void main(){
   CeilDemo<int,int> cd;
   cout<<cd.ceil(2,3,4)<<endl;
}

類模板的預設模板型別形參示例4:

TemplateDemo03.h

#ifndef TEMPLATE_DEMO_03
#define TEMPLATE_DEMO_03
template<class T1=int,classT2=double,class T3=double> class CeilDemo{
   public:
       double ceil(T1,T2,T3);
};
template<class T1,class T2,class T3>
double CeilDemo<T1,T2,T3>::ceil(T1a,T2 b,T3 c){
   return a+b+c;
}
#endif

TemplateDemo03.cpp

#include<iostream.h>
#include "TemplateDemo03.h"
void main(){
   CeilDemo<int,double,double> cd;
   cout<<cd.ceil(2,3.1,4.1)<<endl;//會報錯
}