模板、函式模板、類模板
一、模板
泛型(Generic Programming)即是指具有在多種資料型別上皆可操作的含意。泛型程式設計的代表作品 STL 是一種高效、泛型、可互動操作的軟體元件。
泛型程式設計最初誕生於 C++中,目的是為了實現 C++的 STL(標準模板庫)。其語言支援機制就是模板(Templates)。模板的精神其實很簡單:引數化型別。換句話說, 把一個原本特定於某個型別的演算法或類當中的型別資訊抽掉,抽出來做成模板引數 T。
所謂函式模板,實際上是建立一個通用函式,其函式型別和形參型別不具體指定,用一個虛擬的型別來代表。這個通用函式就稱為函式模板。
二、函式模板
1.語法格式
template 是語義是模板的意思,尖括號中先寫關鍵字 typename 或是class,後面跟一個型別 T,此類即是虛擬的型別。至於為什麼用 T,用的人多了,也就是 T 了。
2.函式模板的例項
呼叫過程是這樣的,先將函式模板實在化為函式,然後再發生函式呼叫。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; template<class T> void MySwap(T& a, T& b) { T temp= a; a = b; b = temp; } int main(void) { int a = 10; int b = 20; //1.自動型別推導 cout << "a:" << a << ",b:" << b << endl;//a:10,b:20 MySwap(a, b);//編譯器根據你傳的值,進行型別自動推導 cout << "a:" << a << ",b:" << b << endl;//a:20,b:10 //2.顯式的指定型別MySwap<int>(a, b); cout << "a:" << a << ",b:" << b << endl;//a:10, b:20 double da = 10.01; double db = 20.02; cout << "da:" << da << ",db:" << db << endl;//da:10.01,db:20.02 MySwap(da, db);//編譯器根據你傳的值,進行型別自動推導 cout << "da:" << da << ",db:" << db << endl;//da:20.02,db:10.01 MySwap<double>(da, db); cout << "da:" << da << ",db:" << db << endl;//da:10.01,db:20.02 return 0; }
函式模板,只適用於函式的引數個數相同而型別不同,且函式體相同的情況。如果個數不同,則不能用函式模板。
3.函式模板與普通函式的區別
- 函式模板不允許自動型別轉化
- 普通函式能夠自動進行型別轉化
4.函式模板和普通函式在一起呼叫規則
- 函式模板可以像普通函式那樣被過載;
- C++編譯器優先考慮普通函式;
- 如果函式模板可以產生一個更好的匹配,那麼選擇匹配;
- 可以通過空模板實參列表的語法限定編譯器只能通過模板匹配。
5.編譯器對模板機制剖析
編譯器編譯原理
總結:
(1)編譯器並不是把函式模板處理成能夠處理任意類的函式;
(2)編譯器從函式模板通過具體型別產生不同的函式;
(3)編譯器會對函式模板進行兩次編譯,在宣告的地方對模板程式碼本身進行編譯,在呼叫的地方對引數替換後的程式碼進行編譯。
函式模板案例——char和int型別陣列排序
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; //對char型別和int型別陣列進行排序 //列印函式 template<class T> void PrintArray(T* arr, int len) { for (int i = 0;i < len;i++) { cout << arr[i] << " "; } cout << endl; } //排序 template<class T> void MySort(T* arr, int len) { for (int i = 0;i < len;i++) { for (int j = i + 1;j < len;j++) { //從大到小排序 if (arr[i] < arr[j]) { T temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } } int main(void) { int arr[] = { 2,6,1,8,9,2 }; int len = sizeof(arr) / sizeof(int); cout << "排序前:"; PrintArray(arr,len);//排序前:2 6 1 8 9 2 MySort(arr, len); cout << "排序後:"; PrintArray(arr, len);//排序後:9 8 6 2 2 1 char chArr[] = { 'a','c','q','d','t', }; len = sizeof(chArr) / sizeof(char); cout << "排序前:"; PrintArray(chArr, len);//排序前:a c q d t MySort(chArr, len); cout << "排序後:"; PrintArray(chArr, len);//排序後:t q d c a return 0; }
三、類模板
1.類模板定義
類模板與函式模板的定義和使用類似,有時,有兩個或多個類,其功能是相同的,僅僅是資料型別不同,所以將類中的型別進行泛化。
類模板示例:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; template<class T> class Person { public: Person(T id, T age) { this->m_id = id; this->m_age = age; } void Show() { cout << "id:" << this->m_id << ",age:" << this->m_age << endl; } public: T m_id; T m_age; }; void test() { //函式模板在呼叫的時候,可以自動型別推導 //類模板必須顯式指定型別 Person<int> p(10, 20); p.Show(); } int main(void) { test();//id:10,age:20 return 0; }
2.類模板派生普通類、類模板派生類模板
#include <iostream> using namespace std; template<class T> class Person { public: Person(T age) { this->age = age; } private: T age; }; //模板類派生普通類 //為什麼繼承的型別需顯式,而不是T? //原因:類去定義物件,這個物件需要編譯分配記憶體,所以要在 //public Person<int>這裡顯式的指定型別,可以知道給父類分配多少記憶體 class SubPerson1 :public Person<int> { public: SubPerson1(int age, int id) :Person<int>(age) { this->id = id; } private: int id; }; //模板類派生模板類 template<class T> class SubPerson2 :public Person<T> { public: SubPerson2(T age, T id) :Person<T>(age) { this->id = id; } private: int id; };
3、類模板實現
(1)類模板類內實現
(2)類模板類外實現(在一個.cpp中)
模板類不要輕易使用友元函式
(3)類模板類外實現(在.h和.cpp中)
由於二次編譯,模板類在.h在第一次編譯之後,並沒有最終確定類的具體實現,只是編譯器的詞法校驗和分析。在第二次確定類的具體實現後,是在.hpp檔案生成的最後的具體類,所以main函式需要引入.hpp檔案。
引入hpp檔案一說也是曲線救國之計,所以實現模板方法建議在同一個檔案.h中完成。
(4)類模板中的static
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; template<class T> class Person { public: static T a; }; //類外初始化 template<class T> T Person<T>::a = 0; int main(void) { Person<int> p1, p2, p3; Person<char> pp1, pp2, pp3; p1.a = 10; pp1.a = 'c'; cout << p1.a << " " << p2.a << " " << p3.a << endl;//10 10 10 cout << pp1.a << " " << pp2.a << " " << pp3.a << endl;//c c c
//通過以上結果,說明p1,p2,p3是屬於Person<int>家族的,他們共享Person<int>::a; //pp1,pp2,pp3是屬於Person<char>家族的,他們共享Person<char>::a; return 0; }
練習:
設計一個數組模板類(MyArray),完成對int、char型別元素的管理。
需要實現 建構函式 拷貝建構函式 [] 過載=操作符。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; template<class T> class MyArray { public: MyArray(int capacity) { this->mCapacity = capacity; this->mSize = 0; //申請記憶體 this->pAddr = new T[this->mCapacity]; } MyArray(const MyArray<T>& arr) { this->mCapacity = arr.mCapacity; this->mSize = arr.mSize; //申請記憶體空間 this->pAddr = new T[this->mCapacity]; //資料拷貝 for (int i = 0; i < this->mSize;i++) { this->pAddr[i] = arr.pAddr[i]; } } T& operator[](int index) { return this->pAddr[index]; } MyArray<T> operator=(const MyArray<T>& arr) { if (this->pAddr != NULL) { delete[] this->pAddr; } this->mCapacity = arr.mCapacity; this->mSize = arr.mSize; //申請記憶體空間 this->pAddr = new T[this->mCapacity]; //資料拷貝 for (int i = 0; i < this->mSize;i++) { this->pAddr[i] = arr.pAddr[i]; } return *this; } void PushBack(T& data) { //判斷容器中是否有位置 if (this->mSize >= this->mCapacity) { return; } //呼叫拷貝構造 =操作符 //1.物件元素必須能夠被拷貝 //2.容器都是值寓意,而非引用寓意 向容器中放入元素,都是放入的元素的拷貝 //3.如果元素的成員有指標,注意深拷貝和淺拷貝問題 this->pAddr[this->mSize] = data; this->mSize ++ ; } //T&& 對右值取引用 void PushBack(T&& data) { //判斷容器中是否有位置 if (this->mSize >= this->mCapacity) { return; } this->pAddr[this->mSize] = data; this->mSize++; } ~MyArray() { if (this->pAddr != NULL) { delete[] this->pAddr; } } public: //一共可以容下多少元素 int mCapacity; //當前陣列有多少元素 int mSize; //儲存資料的首地址 T* pAddr; }; class Person { }; void test2() { Person p1, p2; MyArray<Person> arr(10); arr.PushBack(p1); arr.PushBack(p2); } void test1() { MyArray<int> marray(20); int a = 10, b = 20, c = 30, d = 40; marray.PushBack(a); marray.PushBack(b); marray.PushBack(c); marray.PushBack(d); //不能對右值取引用 //左值 可以在多行使用 //臨時變數 只能當前行使用 marray.PushBack(100); marray.PushBack(200); marray.PushBack(300); for (int i = 0;i < marray.mSize;i++) { cout << marray[i] << " "; } cout << endl; } int main(void) { test1(); return 0; }