本章將闡述一些具體的 STL 模板思想,並簡單介紹操作符重載與模板的聯系。
模板是 C++ 語言中重要的概念。它提供了一種通用的方法來開發重用的代碼,即以創建參數化的 C++ 類型。模板分為兩種類型:函數模板和類模板。函數模板的用法同 C++ 預處理器的用法有一定的類似之處,它們都提供編譯代碼過程中的文本替換功能,但函數模板還能對類型進行一定的保護。使用類模板可以編寫通用的、類型安全的類。
#includeusing namespace std; /* int sum(int data[],int nsize) { int sum=0; for(int i=0;i T sum(T data[],int nsize) { T sum=0; for(int i=0;i
注釋中的代碼為int型數組相加的函數,將其改為下方模板函數時,只需要將數據類型抽象出來,使用 template
代替原先的變量,即可實現模板功能,函數中的類型相應地也要改為 name 。 該函數已經能夠實現對不同的基本數據類型進行求和,若進一步思考,希望用該函數能夠實現對鏈表、集合等元素的求和,這即是 STL 的思維方式。 另外,用於模板時,class 關鍵詞可以用 typename 代替,即 template 。 2.編寫動態數組的模板類,體會 STL vector 的編寫思想。
#includeusing namespace std; template //一個簡單的模板類,只有構造函數、析構函數、添加元素函數,但已經能夠說明模板類的特征了。 class MyArray { private: int m_nTotalSize; //數組總長度 int m_nValidSize; //數組有效長度,即當前元素數 T * m_pData; //數據指針 public: //構造函數 MyArray(int nSize=2) //假設默認數組長度為2 { m_pData=new T[nSize]; m_nTotalSize=nSize; m_nValidSize=0; } //獲取數組有效長度 int GetValidSize() { return m_nValidSize; } //獲取數組總長度 int GetTotalSize() { return m_nTotalSize; } //返回某一位置元素 T Get(int pos) { return m_pData[pos]; } //添加一個元素 void Add(T value) { if(m_nValidSize array1; cout<<"數組總長度:"< array2; cout<<"數組總長度:"<該模板類模擬了一個簡化的類似 vector 的動態數組,並實現了添加元素功能。可以看到該數組類默認數組大小是2,當插入3個元素之後,數組大小動態增加為原來的兩倍,這也是 vector 中的實現方法。雖然實現了數組動態大小,但是需要將數組的原先數據做一份拷貝,因此此時會有效率上的損失。 程序中演示了對於 int 型和 double 型類的使用,說明了模板函數抽象的意義所在,即能實現代碼的重用。 本類能體現出 STL 容器關於內存"內存分配、銷毀、再分配"的思想,也就是把內存管理的部分進一步抽象,編程系統代碼,應用方不必明了過程中的內存變化,用專家級編寫的代碼,而不是自己編寫的代碼來管理內存。STL 是編寫普通模板類發展的必然結果,不是一種新技術。
3.traits技術
STL標准模板庫非常強調軟件的復用,traits 技術是重要的手段。traits 的中文意思就是特性,traits 就像特性萃取機,提取不同類的特性,以便能夠統一處理,traits 依靠顯式模板特殊化來把代碼中因類型不同而發生變化的片段拖出來,用統一的接口來包裝。這個接口可以包含 C++ 類所能包含的任何東西,如內嵌類型、成員函數、成員變量。作為客戶的模板代碼,可以通過 traits 模板類所公開的接口來間接訪問。下面通過一個簡單的例子加以理解。 已知整形數組類 IntArray 與浮點型數組 FloatArray ,求數組和與一個數的乘積。 按普通方式編寫的代碼如下:(紅色為兩個數組類的區別)
#includeusing namespace std; class IntArray { private: int a[10]; public: IntArray() { for(int i=0;i<10;i++) { a[i]=i+1; } } //該函數輸出所有元素的和與times的乘積 int Calculate(int times) { int sum=0; for(int i=0;i<10;i++) { sum+=a[i]; } return sum * times; } }; class FloatArray { private: float a[10]; public: FloatArray() { for(int i=0;i<10;i++) { a[i]=i+1; } } //該函數輸出所有元素的和與times的乘積 float Calculate(float times) { int sum=0; for(int i=0;i<10;i++) { sum+=a[i]; } return sum * times; } }; int main() { IntArray a; FloatArray f; cout<<"整形數組和的 2 倍是:"<可以發現,兩個數組類的 Calculate 函數出了參數類型、返回值類型不同外,其余都一樣,那麼能否通過一個類的接口函數來完成上述功能呢?可以,當然要用到模板。這裡要增加一個類 Array 。
template並且主函數需要改變:class Array { public: float Calculate(T &t,float times) { return t.Calculate(times); } };
int main() { IntArray a; FloatArray f; ArrayA1; Array A2; cout<<"整形數組和的 2 倍是:"<這裡使用 Array 接口函數實現對整形數組和浮點型數組類的操作。但是仔細分析一下就能發現,細節上還是有問題的。比如在 IntArray 中的 Calculate() 函數的參數和返回值都是 int 類型,FloatArray 中的 Calculate() 函數的參數和返回值都是 float 類型,而模板類 Array 中將 Calculate() 函數的參數和返回值都固定為了 float 類型,雖然從程序的結果來看似乎是正確的,但是並不夠嚴密,當程序復雜點時極有可能出錯,那麼如何解決輸出、輸出參數類型的不同呢?traits 技術就是很好的解決方法,步驟與如下所示。
步驟一:定義基本模板類
templateclass NumTraits { }; NumTraits 類可以什麼都不寫,只是說明它是一個模板類。 步驟二:模板特化。
template<> class NumTraits{ public: typedef int inputType; typedef int resultType; };
template<> class NumTraits
{ public: typedef int inputType; typedef int resultType; }; 可以看出相應模板特化類中只是用了 typedef 重定義函數,將 IntArray 和 FloatArray 兩個數組類中 Calculate() 函數的參數類型、返回值類型重新定義成 inputType 和 resultType,為編寫模板類共同的調用接口做准備。 步驟三:統一模板調用類編寫
templateclass Array { public: typename NumTraits ::resultType Calculate(T &t,typename NumTraits ::inputType times) { return t.Calculate(times); } }; 這裡 typename 關鍵字的作用是告訴編譯器 NumTraits ::resultType 和 NumTraits ::inputType 是一個類型而不是變量。 乍一看 Array 類的 Calculate() 函數有些難懂。當模板參數代表 IntArray 時,該定義變為如下代碼: typename NumTraits ::resultType Calculate(T &t,typename NumTraits ::inputType times) 根據(2)中模板特化定義,NumTraits ::resultType 代表 int,NumTraits ::inputType 也代表 int ,於是上述定義就變為 int Calculate(T &t,int times)。當模板參數代表 FloatType 時情況類似這裡就不做贅述了。 因此 Array 類中的 Calculate() 函數的參數類型和返回值類型是可變的,隨著模板參數的不同而不同。因此在模板特化類中給輸入、輸出參數進行 typedef 重定義非常重要,而且起的對應名稱還要相同。 最後,Array 類中的 Calculate() 函數定義看起來很繁瑣,這裡再次采用 typedef 定義使其清晰: template class Array { public: typedef typename NumTraits ::resultType result; typedef typename NumTraits ::inputType input; result Calculate(T &t,input times) { return t.Calculate(times); } }; 最後給上完整代碼: #includeusing namespace std; class IntArray { private: int a[10]; public: IntArray() { for(int i=0;i<10;i++) { a[i]=i+1; } } //該函數輸出所有元素的和與times的乘積 int Calculate(int times) { int sum=0; for(int i=0;i<10;i++) { sum+=a[i]; } return sum * times; } }; class FloatArray { private: float a[10]; public: FloatArray() { for(int i=0;i<10;i++) { a[i]=i+1; } } //該函數輸出所有元素的和與times的乘積 float Calculate(float times) { int sum=0; for(int i=0;i<10;i++) { sum+=a[i]; } return sum * times; } }; //基本模板類 template class NumTraits { }; //模板特化 template<> class NumTraits { public: typedef int inputType; typedef int resultType; }; //模板特化 template<> class NumTraits { public: typedef float inputType; typedef float resultType; }; //統一模板調用類編寫 template class Array { public: typedef typename NumTraits ::resultType result; typedef typename NumTraits ::inputType input; result Calculate(T &t,input times) { return t.Calculate(times); } }; int main() { IntArray a; FloatArray f; Array A1; Array A2; cout<<"整形數組和的 2 倍是:"< 4.模板與操作符重載
例如有關於大小比較的模板函數時,有下列代碼:
#includeusing namespace std; template bool Grater(U const &u,V const &v) { return u>v; } int main() { cout< 該模板函數在比較基本類型時完全正確,但是當 U 或者 V 中有一個表示類時,無法比較,此時就需要重載操作符。 假設有一個學生類:
class Student { private: char name[20]; int grade; public: Student(char name[],int grade) { strcpy(this->name,name); this->grade=grade; } bool operator>(const int &value)const { return this->grade>value; } };需要比較他的成績是否大於99分,使用 Grater(s,99) 時,需要重載 Student 類的 ">" 操作符,代碼如下所示:
bool operator>(const int &value)const
{
return this->grade>value;
}
現在已經可以將 Student 類與 int 型進行比較了。
完整的模板與重載操作符函數如下:
#include#include using namespace std; class Student { private: char name[20]; int grade; public: Student(char name[],int grade) { strcpy(this->name,name); this->grade=grade; } bool operator>(const int &value)const { return this->grade>value; } }; template bool Grater(U const &u,V const &v) { return u>v; } int main() { Student s("Raito",100); cout< 由於 STL 中有大量的模板函數,因此很多時候都要重載與之對應的操作符。模板函數相當於已經編寫好的應用框架,操作符重載相當於調用的接口。