C++之泛型编程(模板)
1.模板綜述
背景
- 有時候許多函數或子程序的邏輯結構是一樣的,只是要處理的數據類型不一樣
- 有時候多個類具有相同邏輯的成員函數和成員變量,只是成員變量的數據類型以及成員函數的參數類型不一樣
- 模板就是解決數據類型不一致造成代碼冗余的一種機制,本質上就是數據類型參數化,用一種邏輯結構抽象出多種數據類型對應的函數或者類
2.函數模板
2.1基礎語法
示例代碼
#include <iostream> using namespace std;template <typename T>//模板說明 T myFunc(T size)//函數實現 {cout << "size:" <<size<< endl;return size; }int main(void) {myFunc(8);//自動推導調用myFunc<float>((float)12.9);//顯示調用cout << "Hello!" << endl;//system("pause");return 0; }- 模板說明里面的類屬參數在函數定義里面一定要使用,普通類型可以不使用
- 可以使用多個類型參數進行模板說明
2.2函數模板遇上函數重載
結論: 
 函數模板不允許自動類型轉化 
 普通函數能夠進行自動類型轉換
調用規則 
 1 函數模板可以像普通函數一樣被重載 
 2 C++編譯器優先考慮普通函數 
 3 如果函數模板可以產生一個更好的匹配,那么選擇模板 
 4 可以通過空模板實參列表的語法限定編譯器只通過模板匹配
2.3函數模板實現機制
編譯器介紹
- gcc(GNU C Compiler)編譯器的作者是RichardStallman,也是GNU項目的奠基者。
- 什么是gcc:gcc是GNU Compiler Collection的縮寫。最初是作為C語言的編譯器(GNU C Compiler),現在已經支持多種語言了,如C、C++、Java、Pascal、Ada、COBOL語言等。
- gcc支持多種硬件平臺,甚至對Don Knuth 設計的 MMIX 這類不常見的計算機都提供了完善的支持
gcc主要特征
1)gcc是一個可移植的編譯器,支持多種硬件平臺 
 2)gcc不僅僅是個本地編譯器,它還能跨平臺交叉編譯。 
 3)gcc有多種語言前端,用于解析不同的語言。 
 4)gcc是按模塊化設計的,可以加入新語言和新CPU架構的支持 
 5)gcc是自由軟件
gcc編譯過程
- 預處理(Pre-Processing)
- 編譯(Compiling)
- 匯編(Assembling)
- 鏈接(Linking)
結論:gcc編譯工具是一個工具鏈。。。。
GCC常用編譯選項
| -o | 產生目標(.i、.s、.o、可執行文件等) | 
| -c | 通知gcc取消鏈接步驟,即編譯源碼并在最后生成目標文件 | 
| -E | 只運行C預編譯器 | 
| -S | 告訴編譯器產生匯編語言文件后停止編譯,產生的匯編語言文件擴展名為.s | 
| -Wall | 使gcc對源文件的代碼有問題的地方發出警告 | 
| -Idir | 將dir目錄加入搜索頭文件的目錄路徑 | 
| -Ldir | 將dir目錄加入搜索庫的目錄路徑 | 
| -llib | 鏈接lib庫 | 
| -g | 在目標文件中嵌入調試信息,以便gdb之類的調試程序調試 | 
GCC常用編譯步驟
1.gcc -E hello.c -o hello.i(預處理) 
 2.gcc -S hello.i -o hello.s(編譯) 
 3.gcc -c hello.s -o hello.o(匯編) 
 4.gcc hello.o -o hello(鏈接) 
 以上四個步驟,可合成一個步驟 
 gcc hello.c -o hello(直接編譯鏈接成可執行目標文件) 
 gcc -c hello.c或gcc -c hello.c -o hello.o(編譯生成可重定位目標文件)
Gcc編譯多個文件
hello_1.h hello_1.c main.c一次性編譯
gcc hello_1.c main.c –o newhello獨立編譯
gcc -Wall -c main.c -o main.o gcc -Wall -c hello_1.c -o hello_fn.o gcc -Wall main.o hello_1.o -o newhello反匯編觀察
- 編譯器并不是把函數模板處理成能夠處理任意類的函數
- 編譯器從函數模板通過具體類型產生不同的函數
- 編譯器會對函數模板進行兩次編譯 
 - 在聲明的地方對模板代碼本身進行編譯;
- 在調用的地方對參數替換后的代碼進行編譯。
 
3.類模板
類模板與函數模板的定義和使用類似,我們已經進行了介紹。 有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同,如下面語句聲明了一個類:
類模板用于實現類所需數據的類型參數化
類模板在表示如數組、表、圖等數據結構顯得特別重要, 
 這些數據結構的表示和算法不受所包含的元素類型的影響
使用類模板聲明對象的時候要顯示指定形式參數的具體類型,以便C++編譯器給對象分配具體的內存
3.1普通類模板的語法
單個類的語法
#include <iostream> using namespace std;template <typename T> class A { public:A(int a = 0){this->a = a;}void printA(){cout << a << endl;} protected: private:T a; };int main(void) {A<int> a1;a1.printA();A<int> a2(19);a2.printA();cout<<"Hello!"<<endl;return 0; }模板類作函數參數
#include <iostream> using namespace std;template <typename T> class A { public:A(int a = 0){this->a = a;}void printA(){cout << a << endl;} protected: private:T a; };//類模板 做函數參數//參數 ,C++編譯器 要求具體的類 所以所 要 A<int> &a void UseA(A<int> &a) {a.printA(); }int main(void) {A<int> a1;UseA(a1);A<int> a2(19);UseA(a2);cout<<"Hello!"<<endl;return 0; }3.2繼承中的類模板語法
模板類派生時, 需要具體化模板類. C++編譯器需要知道 父類的數據類型具體是什么樣子的。要知道父類所占的內存大小是多少。只有數據類型固定下來,才知道如何分配內存。
模板類派生時, 需要具體化模板類. C++編譯器需要知道 父類的數據類型具體是什么樣子的。要知道父類所占的內存大小是多少。只有數據類型固定下來,才知道如何分配內存。
從模板類派生普通類
#include <iostream> using namespace std;template <typename T> class A { public:A(int a = 0){this->a = a;}void printA(){cout << a << endl;} protected:T a; private:};class B:public A<int> { public:B(int a = 10, int b = 20) : A<int>(a){this->b = b;}void printB(){cout << "a:" << a << " b: " << b << endl;} protected:private:int b; };int main(void) {B b1(1, 2);b1.printB();cout<<"Hello!"<<endl;return 0; }從模板類派生模板類
#include <iostream> using namespace std;template <typename T> class A { public:A(int a = 0){this->a = a;}void printA(){cout << a << endl;} protected:T a; private:};template <typename T>class C :public A<T> { public:C(T a, T c) :A<T>(a){this->c = c;}void printC(){cout << "a:" << a << "c:" << c << endl;} private:T c; protected: };int main(void) {C<int> c1(1, 2);c1.printC();cout<<"Hello!"<<endl;return 0; }3.2類模板知識體系梳理
類模板函數全部寫在類的內部
#include <iostream> using namespace std;template <typename T> class Complex {friend Complex MySub(Complex c1, Complex c2){Complex tmp(c1.a-c2.a,c1.b-c2.b);return tmp;}friend ostream & operator<<(ostream & out, Complex & c){out << c.a << " + " << c.b << "i" << endl;return out;} public:Complex(T a, T b){this->a = a;this->b = b;}Complex operator+(Complex & c2){Complex tmp(a + c2.a,b + c2.b);return tmp;}void printCom(){cout << a << " + " << b << "i" << endl;} protected: private:T a;T b; };int main(void) {//需要把模板類 進行具體化以后 才能定義對象 C++編譯器要分配內存Complex<int> c1(1, 2);Complex<int> c2(3, 4);Complex<int> c3 = c1 + c2;//c3.printCom();cout << c3 << endl;//濫用友元函數{Complex<int> c4 = MySub(c1, c2);cout << c4 << endl;}cout<<"Hello!"<<endl;return 0; }類模板函數全寫在類的外部,但在同一個cpp中
如果一個模板類具有友元函數,且該友元函數的形參包含模板類對象,則需要進行類模板和友元函數的前置聲明。 
 需要注意的是:
結論:不要濫用友元函數,一般友元函數只適用于重載<<或者>>操作符。
類模板函數全寫在類的外部,但在不同的.h和cpp中
由于模板的實現機制在本質上是兩次編譯,所以如果只在主程序里面包含頭文件(類模板的聲明),編譯器不會自動尋找cpp文件里面的成員函數和友元函數的函數體。所以會出現找不到某個函數體的錯誤。只能是包含實現函數體的cpp文件,而cpp文件又包含了h文件,所以實質上是包含了類模板的聲明以及類模板函數的實現,故業界都是將這兩部分(.h和.cpp)寫在同一個文件中,叫做hpp文件,只需要在提供的開源庫里面包含該hpp文件,即可使用類模板。
- 傳統類的頭文件(類模板聲明部分)
- 傳統類的實現部分(具體類模板函數的實現部分)
- 測試程序(包含hpp文件)
3.3類模板中的static關鍵字
- 類模板—>實例化—>模板類 
 每一個模板類有自己的類模板數據成員,該模板類的所有對象共享一個static數據成員
- 和非模板類的static數據成員一樣,模板類的static數據成員也應該在文件范圍定義和初始化
- 每個模板類有自己的類模板的static數據成員副本
3.4類模板小結
類模板的聲明
1.先寫出一個實際的類。由于其語義明確,含義清楚,一般不會出錯。 
 2.將此類中準備改變的類型名(如int要改變為float或char)改用一個自己指定的虛擬類型名(如上例中的numtype)。 
 3.在類聲明前面加入一行,格式為(class和typename作用一樣): 
 template <class虛擬類型參數> 
 如:
4.用類模板定義對象時用以下形式: 
 類模板名<實際類型名> 對象名; 
 類模板名<實際類型名> 對象名(實參表列); 
 如:
5.如果在類模板外定義成員函數,應寫成類模板形式: 
 template <class 虛擬類型參數> 
 函數類型 類模板名<虛擬類型參數>::成員函數名(函數形參表列) {…} 
 6.類模板的類型參數可以有一個或多個,每個類型前面都必須加class或者typename,如:
在定義對象時分別代入實際的類型名,如: 
 someclass<int,double> obj; 
 7.和使用類一樣,使用類模板時要注意其作用域,只能在其有效作用域內用它定義對象。 
 8.模板可以有層次,一個類模板可以作為基類,派生出派生模板類。
4.模板在工程中的應用
綜述
1.模板是C++中類型參數化的多態工具,提供函數模板和類模板 
 2.模板定義從模板說明開始,類屬參數必須在模板實現中至少使用一次 
 3.同一個類屬參數可以用于多類模板 
 4.類屬參數可用于函數形參,返回類型以及聲明函數中的變量 
 5.模板由編譯器根據實際的數據類型實例化,生成實際的可執行代碼,從而得到模板函數和模板類 
 6.函數模板可以進行重載 
 7.類模板可以進行派生繼承
工程中用到的容器  
 所有容器提供的都是值(value)語意,而非引用(reference)語意。容器執行插入元素的操作時,內部實施拷貝動作。所以STL容器內存儲的元素必須能夠被拷貝(必須提供拷貝構造函數)。
案例 
 設計一個數組模板類( MyVector ),完成對int、char、Teacher類型元素的管理。
- 類模板定義
- 構造函數
- 拷貝構造函數
- 重載操作符<< [] =操作符
- 從數組模板中進行派生
使用基礎數據類型以及一般的自定義類對象作為容器元素
- 容器/數組類模板頭文件
- 容器/數組類模板實現文件
- 容器/數組類測試文件
優化后的Teacher類對象作為容器存儲元素(類模板頭文件和實現文件不變)
#define _CRT_SECURE_NO_WARNINGS#include <iostream> #include "myvector.cpp"//注意包含的是cpp文件 using namespace std;//1 優化Teacher類, 屬性變成 char *panme, 購置函數里面 分配內存 //2 優化Teacher類,析構函數 釋放panme指向的內存空間 //3 優化Teacher類,避免淺拷貝 重載= 重寫拷貝構造函數 //4 優化Teacher類,在Teacher增加 << //5 在模板數組類中,存int char Teacher Teacher*(指針類型)//=====>stl 容器的概念 class Teacher { public:Teacher(){age = 22;name = new char[1];strcpy(name, "");}Teacher(int age, char *name){this->age = age;this->name = new char[strlen(name) + 1];strcpy(this->name,name);}Teacher(const Teacher &obj){age = obj.age;name = new char[strlen(obj.name) + 1];strcpy(name,obj.name);}~Teacher(){if (name != NULL){delete[]name;name = NULL;age = 22;}}void printTeacher(){cout << name << "," << age << endl;}friend ostream & operator<<(ostream & out, Teacher &obj);Teacher & operator=(const Teacher & obj){if (name != NULL){delete[]name;name = NULL;age = 22;}name = new char[strlen(obj.name) + 1];strcpy(name, obj.name);age = obj.age;return *this;} protected: private:int age;char *name; };ostream & operator<<(ostream & out, Teacher &obj) {out << obj.name << "," << obj.age << endl;return out; } int main(void) { /*使用類對象設置容器并打印*/Teacher t1(31, "t1"), t2(32, "t2"), t3(33, "t3"), t4(34, "t4");MyVector<Teacher> v3(4);v3[0] = t1;v3[1] = t2;v3[2] = t3;v3[3] = t4;for (int i = 0; i < v3.getLen(); i++){Teacher tmp = v3[i];tmp.printTeacher();}cout<<"Hello!"<<endl;system("pause");return 0; }指針作為容器元素存儲
#define _CRT_SECURE_NO_WARNINGS#include <iostream> #include "myvector.cpp"//注意包含的是cpp文件 using namespace std;//1 優化Teacher類, 屬性變成 char *panme, 購置函數里面 分配內存 //2 優化Teacher類,析構函數 釋放panme指向的內存空間 //3 優化Teacher類,避免淺拷貝 重載= 重寫拷貝構造函數 //4 優化Teacher類,在Teacher增加 << //5 在模板數組類中,存int char Teacher Teacher*(指針類型)//=====>stl 容器的概念 class Teacher { public:Teacher(){age = 22;name = new char[1];strcpy(name, "");}Teacher(int age, char *name){this->age = age;this->name = new char[strlen(name) + 1];strcpy(this->name,name);}Teacher(const Teacher &obj){age = obj.age;name = new char[strlen(obj.name) + 1];strcpy(name,obj.name);}~Teacher(){if (name != NULL){delete[]name;name = NULL;age = 22;}}void printTeacher(){cout << name << "," << age << endl;}friend ostream & operator<<(ostream & out, Teacher &obj);Teacher & operator=(const Teacher & obj){if (name != NULL){delete[]name;name = NULL;age = 22;}name = new char[strlen(obj.name) + 1];strcpy(name, obj.name);age = obj.age;return *this;} protected: private:int age;char *name; };ostream & operator<<(ostream & out, Teacher &obj) {out << obj.name << "," << obj.age << endl;return out; } int main(void) { /*使用類對象設置容器并打印*/Teacher t1(31, "t1"), t2(32, "t2"), t3(33, "t3"), t4(34, "t4");MyVector<Teacher*> v3(4);v3[0] = &t1;v3[1] = &t2;v3[2] = &t3;v3[3] = &t4;for (int i = 0; i < v3.getLen(); i++){Teacher *tmp = v3[i];tmp->printTeacher();}cout << t1;cout<<"Hello!"<<endl;system("pause");return 0; }總結:類模板實現了數據結構(具體數據類型)和算法的分離,真正的實現了泛型編程。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的C++之泛型编程(模板)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: php插不入数据原因,php程序插数据入
- 下一篇: 设计模式之开放封闭原则
