類别範本及其成員函式的定義及注意事項

huxxyyy發表於2020-12-18

一、類别範本的定義:

  1. 類似函式模板,類别範本以關鍵字template開始,後跟模板引數列表,即 <typename T, ...>
  2. 在類别範本(及其成員函式)的定義中,我們將模板引數當作替身,代替使用模板時使用者提供的型別或值。
    程式碼示例:
template <typename T> //用T這個模板型別引數,來表示A中儲存的元素的型別。
//當使用者例項化A時,T就會被替換為特定的模板實參型別
class A{
private:
	int size;
	T* list;
public:
//建構函式
	A();
	A(int size);
	A(T[], int size);
//解構函式
	~A();
}

二、例項化類别範本:

例項化:將函式模板中的“T”編譯為具體的資料型別。

在例項化類時,必須提供元素的型別,如:

A<int> a1;
A<double> a2;

例項化之後,編譯器自動例項化出與下面等價的類:

//對於上述的A<int> a1:
template <> 
class A<int>{
private:
	int size;
	T* list;
public:
	A();
	A(int i);
	A(int a[], int size);
	~A();
};//就是把所有的T都變為了目標型別

一個類别範本的每個例項都形成一個獨立的類。型別A<string>與其他的A型別都無關,也不會對其他A型別的成員有特殊的訪問許可權(即一個A<int>A<string>之間不能進行運算或者訪問對方的成員)

三、類别範本的成員函式:

與其他任何類相同,我們既可以在類别範本內部,也可以在類别範本的外部為其定義成員函式,且定義在類别範本內的成員函式被隱式宣告為行內函數

類别範本的成員函式本身是一個普通函式。不同的是,類别範本的每個例項(如A<int>A<string>等等)都有其自己版本的成員函式。因此,類别範本的成員函式具有和模板相同的模板引數<typename T, ...>

因而,定義在類别範本之外的成員函式就必須以關鍵字template開頭,後接類别範本引數列表

四、類外定義函式的方法:

說明成員函式屬於哪個類,而且從一個模板生成的類的名字中必須包含其模板引數T(如 A<T>::
(注意模板實參和模板形參相同)

如,對於類內定義函式:

 ret-type StrA::member-name(parm-list);

對應在類外定義時應為:

template<typename T>
ret-type StrA<T>::member-name(parm-list){...}

五、訪問類內成員的成員函式:

先定義一個check成員函式,它用來檢查一個給定的索引:

//若data[i]無效(即超過原有長度),就丟擲msg
template<typename T>
void Blob<T>::check(size_type i, const std::string &msg){
//size_type是想要訪問的位置,msg是輸入的一句話,它是一個字串常量
	if(i >= data->size())//size()返回本身長度
		throw std::out_of_range(msg);
}

該函式的特點是:除了使用了模板引數列表外(即使用了Blob<T>::),此函式與非模板Blob類check成員函式一毛一樣!!!

類似的只改變少數的還有:

下標運算子和back函式用模板引數指出返回型別,其他未變:

template<typename T>
T& Blob<T>::back(){
	check(0,"back on empty Blob!");
	return data->back();
}
template<typename T>
T& Blob<T>::operator[](size_type i)//i是即將要提取的數的位置
{
	//如果i太大,check會丟擲異常,阻止訪問一個不存在的元素
	check(i,"subscript out of range");
	return (*data)[i];
}

六、建構函式:

再拿Blob函式舉例:

template<typename T>
Blob<T>::Blob(): data( std::make_shared< std::vector<T> >() ){}

這段程式碼在作用域Blob<T>中定義了名為Blob的成員函式,類似非模板類預設建構函式

此建構函式分配一個空的vector,並將指向vector的指標儲存在data中。

如前所示,我們將類别範本自己的型別引數作為vector的模板實參來分配vector。

類似的,接受一個initialize_list 引數的建構函式將其型別引數T作為initialize_list引數的元素型別:

template<typename T>
Blob<T>::Blob(std::initializer_list<T> il)
				: data(std::make_shared<std::vector<T>>(il)){}

類似預設建構函式,此建構函式分配一個新的vector。在本例中,我們用引數 il 來初始化此vector。

為了使用這個建構函式,我們必須傳遞給他一個initializer_list,其中的元素必須和Blob元素型別相容:

Blob<string> bl = {"a","b","c"}

這條語句中,建構函式的引數型別為:initializer_list<string>。list中的每個字串常量隱式轉換一個string

**七、類别範本成員函式的例項化:(在main中的實現)

note:預設情況下,一個類别範本的成員函式只有當程式用到它時才會進行例項化。

例如下列程式碼:

// 例項化Blob<int>和接受initializer_list<int>的建構函式
Blob<int> squares = {0,1,2,3,4,5,6,7,8,9}
// 例項化Blob<int>::size() const
for(size_t i = 0; i != squares.size(); i++)
	squares[i] = i*i; // 例項化Blob<int>::operator[](size_t)

例項化了Blob<int>類和它的三個成員函式: operator[ ], size和接受initializer_list<int>的建構函式

八、在類程式碼內簡化模板類名的使用

一般我們使用類别範本型別時都要提供模板實參,但是也有不用提供實參的特殊情況:

在類别範本自己的作用域中,我們可以直接使用模板名而不提供實參:
// 若試圖訪問一個不存在的元素,BlobPtr丟擲一個異常
template<typename T> class BlobPtr{
public:
	BlobPtr(): curr(0){}
	BlobPtr(Blob<T> &a, size_t sz=0):
			wptr(a.data), curr(sz);
	T& operator*() const{
		auto p = check(curr, "dereference past end");
		return (*p)[curr];//(*p)為本物件指向的vector
	}
	//遞增和遞減:
	BlobPtr& operator++();
	BlobPtr& operator--();
private:
	//若檢查成功,check返回一個vector的shared_ptr
	std::shared_ptr<std::vector<T>>
		check(std::size_t, const std::string&) const;
	std::size_t curr;// 陣列中的當前位置
};

觀察到,

BlobPtr& operator++();
BlobPtr& operator--();

這裡的返回值型別是BlobPtr&,而不是BlobPtr<T>&
所以當我們在一個類别範本的作用域中時,編譯器在處理模板自身引用時就好像我們已經提供了和模板引數匹配的實參一樣。

所以在類别範本作用域中寫的上述兩行就相當於:

BlobPtr<T>& operator++();
BlobPtr<T>& operator--();

而在類别範本外使用定義類别範本的成員函式的時候,就要標出來BlobPtr<T>&了。

note:在類别範本的作用域內,可以直接使用模板名而不用指定模板實參!

相關文章