C++模板詳解

類模板定義及實例化

1. 定義一個類模板:


 template<class 模板參數表>
 
 class 類名{

 // 類定義......
 
 };


 

其中,template 是聲明類模板的關鍵字,表示聲明一個模板,模板參數可以是一個,也可以是多個,可以是類型參數 ,也可以是非類型參數。類型參數由關鍵字class或typename及其後面的標識符構成。非類型參數由一個普通參數構成,代表模板定義中的一個常量。

例:

 template<class type,int width>
 
//type爲類型參數,width爲非類型參數
 
 class Graphics;

注意:

(1)如果在全局域中聲明瞭與模板參數同名的變量,則該變量被隱藏掉。

(2)模板參數名不能被當作類模板定義中類成員的名字。

(3)同一個模板參數名在模板參數表中只能出現一次。

(4)在不同的類模板或聲明中,模板參數名可以被重複使用。


 typedef string type;
<pre name="code" class="cpp"> 2 
  template<class type,int width>
  
  class Graphics
  
  {
  
  type node;//node不是string類型
 
 typedef double type;//錯誤:成員名不能與模板參數type同名
 
 };
 
 template<class type,class type>//錯誤:重複使用名爲type的參數
 
 class Rect;
 
 template<class type> //參數名”type”在不同模板間可以重複使用
 
 class Round;


(5)在類模板的前向聲明和定義中,模板參數的名字可以不同。

 
 // 所有三個 Image 聲明都引用同一個類模板的聲明
 
  template <class T> class Image;
  
  template <class U> class Image;
  
  // 模板的真正定義
  
  template <class Type>
 
 class Image { //模板定義中只能引用名字”Type”,不能引用名字”T”和”U” };



(6)
類模板參數可以有缺省實參,給參數提供缺省實參的順序是先右後左。


 
template <class type, int size = 1024>
 
 class Image;
 
 template <class type=double, int size >
 
 class Image;



(7)
類模板名可以被用作一個類型指示符。當一個類模板名被用作另一個模板定義中的類型指示符時,必須指定完整的實參表

複製代碼
 template<class type>
  
  class Graphics
  
  {
  
  Graphics *next;//在類模板自己的定義中不需指定完整模板參數表
  
  };
 
 template <calss type>
 
 void show(Graphics<type> &g)
 
 {
 
 Graphics<type> *pg=&g;//必須指定完整的模板參數表
 
 }



2.類模板實例化

定義:從通用的類模板定義中生成類的過程稱爲模板實例化。

例:Graphics<int> gi;

類模板什麼時候會被實例化呢?

當使用了類模板實例的名字,並且上下文環境要求存在類的定義時。

對象類型是一個類模板實例,當對象被定義時。此點被稱作類的實例化點

一個指針或引用指向一個類模板實例,當檢查這個指針或引用所指的對象時。

例:


  template<class Type>
  
  class Graphics{};
  
  void f1(Graphics<char>);// 僅是一個函數聲明,不需實例化
  
  class Rect 
  
  {
 
   Graphics<double>& rsd;// 聲明一個類模板引用,不需實例化
 
   Graphics<int> si;// si是一個Graphics類型的對象,需要實例化類模板
 
 }
 
 int main(){
 
   Graphcis<char>* sc;// 僅聲明一個類模板指針,不需實例化
 
   f1(*sc);//需要實例化,因爲傳遞給函數f1的是一個Graphics<int>對象。
 
   int iobj=sizeof(Graphics<string>);//需要實例化,因爲sizeof會計算Graphics<string>對象的大小,爲了計算大小,編譯器必須根據類模板定義產生該類型。
 
 }



3.非類型參數的模板實參

要點

綁定給非類型參數的表達式必須是一個常量表達式。

從模板實參到非類型模板參數的類型之間允許進行一些轉換。包括左值轉換、限定修飾轉換、提升、整值轉換。

可以被用於非類型模板參數的模板實參的種類有一些限制。

例:


  Template<int* ptr> class Graphics{…….};
  
  Template<class Type,int size> class Rect{……..};
  
  const int size=1024;
  
  Graphics<&size> bp1;//錯誤:從const int*->int*是錯誤的。
  
  Graphics<0> bp2;//錯誤不能通過隱式轉換把0轉換成指針值
 
 const double db=3.1415;
 
 Rect<double,db> fa1;//錯誤:不能將const double轉換成int.
 
 unsigned int fasize=255;
 
 Rect<String, fasize> fa2;//錯誤:非類型參數的實參必須是常量表達式,將unsigned改爲const就正確。
 
 Int arr[10];
 
 Graphics<arr> gp;//正確


 

類模板的成員函數

要點:

類模板的成員函數可以在類模板的定義中定義(inline函數),也可以在類模板定義之外定義(此時成員函數定義前面必須加上template及模板參數)。

類模板成員函數本身也是一個模板,類模板被實例化時它並不自動被實例化,只有當它被調用或取地址,才被實例化。


 
 template<class type>
  
  Class Graphics{
  
  Graphics(){…}//成員函數定義在類模板的定義中
  
  void out();
  
  };
 
 template<class type>//成員函數定義在類模板定義之外
 
 void Graphics<type>::out(){…}


類模板的友元聲明

類模板中可以有三種友元聲明:

1.非模板友元類或友元函數


 
 class Graphics{void out();};
  
  Template<class T>
  
  Class Rect{
  
  friend class Graphics;//類Graphics、函數
  
  friend void create();// create、 out是類模板
 
 friend void Graphics::out();// Rect所有實例的友元
 
 };


2、綁定的友元類模板或函數模板。

3、非綁定的友元類模板或函數模板。

第二種聲明表示類模板的實例和它的友元之間是一種一對一的映射關係。

如圖:

第三種聲明表示類模板的實例和它的友元之間是一種一對多的映射關係。

如圖:

例:綁定的友元模板


 
 template<class type>
  
  void create(Graphics<type>);
  
  template<class type>
  
  class Graphics{
  
  friend void create<type>(Graphics<type>);
 
 };



例:非綁定的友元模板


 
template<class type>
 
 class Graphics{
 
 template<class T>
 
 friend void create(Graphics<T>);
 
 };



注意:
當把非模板類或函數聲明爲類模板友元時,它們不必在全局域中被聲明或定義,但將一個類的成員聲明爲類模板友元,該類必須已經被定義,另外在聲明綁定的友元類模板或函數模板時,該模板也必須先聲明。

例:


 
</pre><pre name="code" class="cpp">template <class T>

class A {

private:

friend class B<T>; //錯誤:類B必須先聲明

};

template <class T>

class B{};



類模板的靜態數據成員、嵌套類型

1.類模板的靜態數據成員

要點:

靜態數據成員的模板定義必須出現在類模板定義之外。

類模板靜態數據成員本身就是一個模板,它的定義不會引起內存被分配,只有對其實例化纔會分配內存。

當程序使用靜態數據成員時,它被實例化,每個靜態成員實例都與一個類模板實例相對應,靜態成員的實例引用要通過一個類模板實例。

例:


 
<pre name="code" class="cpp">template<class type>

class Graphics{

static Graphics *next;

static const type item;

};

template<class type>

Graphics<type> * Graphics<type>::next=0;

template<class type>

type Graphics<type>::item=NULL;

//靜態成員定義分爲兩部分:前一部分是類型,比如Graphics<type>*,後一部分是名稱和值,比如Graphics<type>::next=0;



2.類模板的嵌套類型

要點:

在類模板中允許再嵌入模板,因此類模板的嵌套類也是一個模板,它可以使用外圍類模板的模板參數。

當外圍類模板被實例化時,它不會自動被實例化,只有當上下文需要它的完整類類型時,它纔會被實例化。

公有嵌套類型可以被用在類定義之外,這時它的名字前必須加上類模板實例的名字。

例:

 
</pre><pre name="code" class="cpp">template<class type>

class Graphics{

public:

template<class T>

class Rect{void out(type a,T b);};

};

Graphics<int>::Rect<double> node;

//引用公有嵌套類型必須加上類模板實例名字





成員模板

定義:成員定義前加上template及模板參數表。

要點:

在一個類模板中定義一個成員模板,意味着該類模板的一個實例包含了可能無限多個嵌套類和無限多個成員函數.

只有當成員模板被使用時,它才被實例化.

成員模板可以定義在其外圍類或類模板定義之外.

例:



 <pre name="code" class="cpp">template<class type>

class Graphics<type>{

public:template<class T>

class Rect{void out(type a,T b);};};

template<class Gtype> template<class TT>

void Graphics<Gtype>::Rect<TT>::out(Gtype a,TT b){}//成員模板被定義在類模板定義之外(要根上完整模板實參)

Graphics<int>的實例可能包括下列嵌套類型:

Graphics<int>::Rect<double>

Graphics<int>::Rect<string>



注意:類模板參數不一定與類模板定義中指定的名字相同。

 

類模板的編譯模式

1.包含編譯模式

這種編譯模式下,類模板的成員函數和靜態成員的定義必須被包含在“要將它們實例化”的所有文件中,如果一個成員函數被定義在類模板定義之外,那麼這些定義應該被放在含有該類模板定義的頭文件中。

2.分離編譯模式

這種模式下,類模板定義和其inline成員函數定義被放在頭文件中,而非inline成員函數和靜態數據成員被放在程序文本文件中。

例:

 
//------Graphics.h---------

export template<class type>

Class Graphics

{void Setup(const type &);};

//-------Graphics.c------------

#include “Graphics.h”

Template <class type>

Void Graphics<type>::Setup(const type &){…}

//------user.c-----

#include “Graphics.h”

Void main()

{Graphics<int> *pg=new Graphics<int>;

Int ival=1;

//Graphics<int>::Setup(const int &)的實例(下有註解)

Pg->Setup(ival);

}




Setup的成員定義在User.c中不可見,但在這個文件中仍可調用模板實例Graphics<int>::Setup(const int &)。爲實現這一點,須將類模聲明爲可導出的:當它的成員函數實例或靜態數據成員實例被使用時,編譯器只要求模板的定義,它的聲明方式是在關鍵字template前加關鍵字export

3.顯式實例聲明

當使用包含編譯模式時,類模板成員的定義被包含在使用其實例的所有程序文本文件中,何時何地編譯器實例化類模板成員的定義,我們並不能精確地知曉,爲解決這個問題,標準C++提供了顯式實例聲明:關鍵字template後面跟着關鍵字class以及類模板實例的名字。

例:


 
#include “Graphics.h”
 
 Template class Graphics<int>;//顯式實例聲明


顯式實例化類模板時,它的所有成員也被顯式實例化。

 

類模板的特化及部分特化

1.類模板的特化

先看下面的例子:


 Template<class type>
 
 Class Graphics{
 
 Public:void out(type figure){…}};
 
 Class Rect{…};



如果模板實參是Rect類型,我們不希望使用類模板Graphics的通用成員函數定義,來實例化成員函數out(),我們希望專門定義Graphics<Rect>::out()實例,讓它使用Rect裏面的成員函數。

爲此,我們可以通過一個顯示特化定義,爲類模板實例的一個成員提供一個特化定義。

格式:template<> 成員函數特化定義

下面爲類模板實例Graphics<Rect>的成員函數out()定義了顯式特化:

Template<> void Graphics<Rect>::out(Rect figure){…}

注意:

只有當通用類模板被聲明後,它的顯式特化纔可以被定義。

若定義了一個類模板特化,則必須定義與這個特化相關的所有成員函數或靜態數據成員,此時類模板特化的成員定義不能以符號template<>作爲打頭。(template<>被省略)

類模板不能夠在某些文件中根據通用模板定義被實例化,而在其他文件中卻針對同一組模板實參被特化。

2.類模板部分特化

如果模板有一個以上的模板參數,則有些人就可能希望爲一個特定的模板實參或者一組模板實參特化類模板,而不是爲所有的模板參數特化該類模板。即,希望提供這樣一個模板:它仍然是一個通用的模板,只不過某些模板參數已經被實際的類型或值取代。通過使用類模板部分特化,可以實現這一點。

例:


 template<int hi,int wid>
 
 Class Graphics{…};
 
 Template<int hi>//類模板的部分特化
 
 Class Graphics<hi,90>{…};



格式:template<模板參數表>

注意:

部分特化的模板參數表只列出模板實參仍然未知的那些參數。

類模板部分特化是被隱式實例化的。編譯器選擇“針對該實例而言最爲特化的模板定義”進行實例化,當沒有特化可被使用時,才使用通用模板定義。

例:Graphics<24,90> figure;

它即能從通用類模板定義被實例化,也能從部分特化的定義被實例化,但編譯器選擇的是部分特化來實例化模板。

類模板部分特化必須有它自己對成員函數、靜態數據成員和嵌套類的定義。

 

名字空間和類模板

類模板定義也可以被放在名字空間中。例如:


  Namespace cplusplus_primer{
  
  Template<class type>
  
  Class Graphics{…};
  
  Template<class type>
  
  Type create()
 
 {…}
 
 }



當類模板名字Graphics被用在名字空間之外時,它必須被名字空間名cplusplus_primer限定修,或者通過一個using聲明或指示符被引入。例如:


 Void main()
 
 {
 
 using cplusplus_primer::Graphics;
 
 Graphics<int> *pg=new Graphics<int>;
 }


注意:在名字空間中聲明類模板也會影響該類模板及其成員的特化和部分特化聲明的方式,類模板或類模板成員的特化聲明必須被聲明在定義通用模板的名字空間中(可以在名字空間之外定義模板特化)。

一個關於棧的例子,下面將其代碼整理如下:


 
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>

using namespace std;

template <class T>
class Stack { 
  private: 
    vector<T> elems;     // 元素 

  public: 
    void push(T const&);  // 入棧
    void pop();               // 出棧
    T top() const;            // 返回棧頂元素
    bool empty() const{       // 如果爲空則返回真。
        return elems.empty(); 
    } 
}; 

template <class T>
void Stack<T>::push (T const& elem) 
{ 
    // 追加傳入元素的副本
    elems.push_back(elem);    
} 

template <class T>
void Stack<T>::pop () 
{ 
    if (elems.empty()) { 
        throw out_of_range("Stack<>::pop(): empty stack"); 
    }
	// 刪除最後一個元素
    elems.pop_back();         
} 

template <class T>
T Stack<T>::top () const 
{ 
    if (elems.empty()) { 
        throw out_of_range("Stack<>::top(): empty stack"); 
    }
	// 返回最後一個元素的副本 
    return elems.back();      
} 

int main() 
{ 
    try { 
        Stack<int>         intStack;  // int 類型的棧 
        Stack<string> stringStack;    // string 類型的棧 

        // 操作 int 類型的棧 
        intStack.push(7); 
        cout << intStack.top() <<endl; 

        // 操作 string 類型的棧 
        stringStack.push("hello"); 
        cout << stringStack.top() << std::endl; 
        stringStack.pop(); 
        stringStack.pop(); 
    } 
    catch (exception const& ex) { 
        cerr << "Exception: " << ex.what() <<endl; 
        return -1;
    } 
}  


函數模板例子:
/*函數模板*/
#include<iostream>
using namespace std;

template <class T> T getmax(T a,T b)
{
	return (a>b)?a:b;
} 

int main()
{
	int i=5,j=7,k;
	double a=2.5,b=1.5,c;
	k=getmax(i,j);
	c=getmax(a,b);

	cout<<k<<endl;
	cout<<c<<endl;

	return 0;
}


類模板例子:
#include<iostream>
using namespace std;

template <class T>
class val
{
	T value1,value2;
	public:
		val(T first,T second)
		{
			value1=first;
			value2=second;
		}
		T getmax()
		{
			
			T result;
			result=(value1>value2)?value1:value2;
			return (result);
		}
};


int main()
{
	val<int> obj(50,70);
	cout<<"max="<<obj.getmax()<<endl;
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章