您好,登錄后才能下訂單哦!
這篇文章主要介紹了C++模板怎么實現多態思想的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇C++模板怎么實現多態思想文章都會有所收獲,下面我們一起來看看吧。
再進一步了解如何用模板來實現多態前,我們還是來看一看這兩個概念的基礎理解
首先是模板,其主要用途在于讓我們程序員少寫代碼
比如像下面兩個函數類似的一系列函數:
int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; }
就可以用模板簡寫為:
template<class T> T add(T a, T b) { return a + b; }
使用的方式如下:
add<int>(1,3); add<double>(1.1, 3.4); add<char>('s','a');
但由于C++編譯器可以自動推斷參數類型,所以中間的<int>是可以省略的
這里要注意一個非常重要的問題,雖然我們只寫了一個模板函數,但實際上并不止有一個函數
比如這里我們用了三種類型的add函數,那么編譯器就會為我們分別生成三個函數
也就是說,這是編譯器根據我們寫的模板。幫我們自動生成的函數
進一步來說,模板是完全給編譯器看的,并不會參與到最終的可執行文件中
上面這一點便是模板的精髓!
為了更加直觀的理解,我們來看一下最終生成的三個函數的內存地址:
#include<iostream> template<class T> T add(T a, T b) { return a + b; } int main() { printf("%p\n", add<int>); printf("%p\n", add<double>); printf("%p\n", add<char>); }
這樣我們就能實際的看到最終確實是生成了三個函數,因為三個函數的地址完全不同,分別就代表著三個版本的add函數
總結來說就是,模板并沒有減少最終的代碼量,它僅僅只是減少了我們程序員需要寫的代碼量
并且這個過程是在編譯期間就完成了的,這一點很重要!
之所以要用模板來實現多態,就是看重了它是在編譯期間就完成的,而不會去影響最終的可執行文件的執行時間、大小
然后便是多態了,多態是類中一個很重要的概念,其主要用途就是使得函數接口統一化
比如下面這段代碼:
#include <iostream> using namespace std; class A { public: virtual void area() { cout << "這是基類A" << endl; } }; class B : public A { public: void area() { cout << "這是子類B" << endl; } }; class C : public A { public: void area() { cout << "這是子類C" << endl; } }; // 程序的主函數 int main() { A* a; a = new B(); a->area(); a = new C(); a->area(); return 0; }
邏輯并不復雜,就是B,C兩個類都繼承于A,
并且在基類中我們用到了關鍵字virtual 定義area為虛函數,還在兩個子類里面都分別重寫了這個函數
因為B,C類都繼承于A類,所以我們可以用A類指針來接收B,C對象
從占用內存上考慮,子類是繼承父類的,所以子類所占用的內存量肯定大于或等于父類占用內存,那么子類申請一塊內存,賦值給父類的指針,父類就不可能會內存訪問越界,而反過來,如果用子類指針存儲父類對象,由于子類訪問的內存大于等于父類,就可能造成內存訪問越界,因此一般禁止這樣使用
此時我們發現,我們只用了一個A調用同一個函數area,卻可以完成兩個類的調用!
所以很多時候,當我們使用別人的提供給我們的類時,只要知道了它的父類有哪些函數,那么其子類就必然有對應的函數
這可以極大方便類的管理、升級以及使用
雖然它的好處很多,但同樣也有壞處,那就是它是動態綁定函數的,依靠了一個叫做虛函數表的東西,導致其內存占用更大,運行時間更長
比如上面的代碼我們就可以在調試窗口中看到其虛函數表:
就是這個名為 _vfptr的變量名稱,他就是指向虛函數表的函數指針,而虛函數表中就存有我們的虛函數
父類指針想要正確使用子類重寫的函數,就必須要在這個虛函數表中進行遍歷查詢對應的函數地址
所以一旦你的類中有虛函數,那么你的類就肯定會多出一個指針大小的內存用于存儲虛函數表的地址,并且最終生成的可執行文件也會變大很多字節
這取決于你的虛函數個數,每多一個虛函數,那么虛函數表就需要多一個指針大小的內存來存儲
如果依舊不太懂的,可以自行在瀏覽器中搜索一下,有很多優秀的文章對此有解釋
總結來說就是:使用傳統類的多態特性,會導致程序效率變低,最終生成的可執行文件體積變大
原因就是它生成了虛函數表、虛函數指針,在程序運行過程中執行查詢函數的操作
MFC就是因為大量使用的這種多態,公共控件都繼承于基本窗口類,一般都有數十上百個虛函數,所以這就導致即使你什么都沒干,一個MFC程序都至少有數兆大小,并且運行效率還較低
了解了上面所說的兩個基本概念的優缺點之后,現在我們就可以來到如何使用模板來實現多態了
因為模板就是編譯期間就完成的操作,如果讓模板來實現多態,那么就不存在運行期間去遍歷虛函數表來找對應的函數,也不需要開辟一個虛函數表來存儲虛函數地址
既能節約內存,又能提高程序運行效率,是不是非常的完美!
下面我們就來看一看模板實現多態的基本流程
#include <iostream> using namespace std; template<class T> class A { public: void Show() { T* p=static_cast<T*>(this); p->area(); } void area() { cout << "這是基類A" << endl; } }; class B : public A<B> { public: void area() { cout << "這是子類B" << endl; } }; class C : public A<C> { public: void area() { cout << "這是子類C" << endl; } }; // 程序的主函數 int main() { B b; b.Show(); C c; c.Show(); return 0; }
這里同樣是B,C兩個類都繼承自A類,但不同點就在于A類帶了一個模板變量
所以B,C類在繼承A的時候,就需要將自己這個類型傳遞進去
此時三個類都寫了area函數,但只有基類寫了show方法對吧
但由于B,C類都是繼承自A類,所以它們其實也已經含有了show方法
然后便是最重要的一步,在基類的show方法中,我將this指針轉化為T類型指針
static_cast與強制轉化基本等價,唯一很大一點的區別就是,強制轉換可以任意使用,比如B沒有繼承自A類,強制轉換仍然可以將兩者指針進行轉換,而static_cast無法轉換兩個毫不相干的東西,這樣就保證了傳入的類型是繼承自基類的,否則編譯會直接報錯
此時這里的p指針
T* p=static_cast<T*>(this);
實際就轉化為了調用者的指針,以B舉例子:
B b; b.Show(); //調用Show方法后,完成了指針的轉換,指代的B,那么B調用area函數,也就是調用自己重寫的area函數
如果現在再多出一個子類D繼承于A,但里面什么都沒有:
class D : A<D>{ }
那么當你使用D時:
D d; d.Show(); //將指針轉換為D類型,由于D類型沒有重寫area方法,所以將調用繼承下來的基類area方法
同樣是一個show函數,能夠卻能根據情況選擇出不同的函數調用,而且還是在編譯期間就完成了的
這便是模板實現多態的基本原理
由于上面說的都是實現原理,例子比較奇怪,下面我們來直接看一看ATL中的代碼:
WTL是基于ATL之上開發的,而ATL庫則是vs開發環境中自帶,WTL庫需要自己去下載
可以輸入以下代碼
#include<atlwin.h> class MyWindow :public CWindowImpl<MyWindow> { };
然后右鍵速覽CWindowImpl類,接著在跳出的文件中搜索static_cast:
就能看到很多像上圖這樣的調用
1.先將this指針還原為子類
2.然后再調用對應的函數
3.如果子類重寫了這個函數,那么就調用子類的函數,否則就調用父類的函數
當然這并不完全如此,比如上圖中的那一出,是將其轉化為子類后,傳給某個函數進行處理
不過總體邏輯是一致的:在父類中操作子類,以實現靜態多態的目的
關于“C++模板怎么實現多態思想”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“C++模板怎么實現多態思想”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。