標籤:虛函數 純虛函數 多態 c++
在行文之前查閱了相關書籍,參考了一些別人的部落格,在這裡謝謝大家的分享!希望自己和大家在學習語言的道路上漸行漸遠,一直走下去~~~
上一篇文章中說道,C++ 的三個基本特質是 封裝、繼承、多態。
多態性是將介面與實現進行分離。用形象的語言解釋就是實現已共同的方法,但因個體差異而採用不同的策略。
多態包括靜多態和動多態,分別在編譯和運行過程中實現。而動多態是由虛函數來實現的,其實現機制體現了C++的神秘性。
1.虛函數的實現機制
虛函數是那些以 virtual 關鍵字修飾的成員函數,是用來實現多態的。
兩個重要的關鍵詞揭示了它的神秘面紗 : 虛表、虛指標。每個類用了一個虛表,每個類對象用了一個虛指標。
具體示範如下:
class A{public: virtual void f(); virtual void g();private: int a;};class B : public A{public: void f(); // 覆蓋父類A中的虛函數f()private: int b; };
因為 A 有 virtual void f( ) ,和 g( ),所以編譯器為 A 類準備了一個虛表 vtableA,內容如下:
B 繼承了A,所以裡面也有繼承於A的虛函數,故B也有一個虛表vtableB:
PS:因為B:: g是重寫了的,所以B的虛表的g放的是B::g的入口地址,而f 還是A:: g 的入口地址。
當執行B bt; 定義類對象的時候,編譯器分配空間時,除了 A 的int a 成員 , B的 int b 成員,還分配了指向B的虛表vtableB 的指標vptr , 對象bt 的布局如下:
其中,虛表指標總是在最前面的。
當執行下面的語句時:
A *pa = &bt; //一個A 類型的指標,指向B類型的對象bt
pa ->g( ) 實際上是由 指標vptr指向B的虛表vtableB,然後去裡面尋找g()函數。可以看到,vtableB裡面是 B:: g( ),即B 自己的g( ),而不是A 的g( )。
這就是多態。
總結:
要知道 虛函數實現多態的機制,謹記虛表、虛指標。能夠列出父類和子類的虛表,以及子類對象的虛指標,就不難理解這個機制了。
2. 純虛函數(pure virtual function)
很多時候,定義一個類的對象是沒有意義的,例如,動物這個類,由它可以派生出大象、獅子、猴子等子類,但動物本身產生對象是沒有實際意義的。為瞭解決這個問題,提出了純虛函數的概念。
假如定義的虛函數為:
virtual void animals(string& name, int age){}
那麼如下就是純虛函數:
virtual void animals(string& name, int age) = 0; // 函數體直接為0 的虛函數
存在純虛函數的類稱為 抽象類別,是一個單純地介面,抽象類別本身執行個體化,即不能產生對象,只能也必須在衍生類別中去執行個體化。
什麼情況下使用純虛函數呢?
針對上述兩點分別舉例說明:
1) 抽象類別不能執行個體化
例如:定義一個形狀的類(Cshape),但凡是形狀我們都要求能顯示自己,所以定義一個類如下:
class Cshape{ virtual show(){}};
我們不想將這個類執行個體化,首先會想到將show( )這個函數的函數體 { } 刪除,改為 virtual show( );
這時如果嘗試執行個體化 Cshape shape; 這樣其實是能夠通過編譯的,只能在串連的時候報錯。
那麼如何能夠在編譯的時候就報錯呢?
~~用純虛函數:
class Cshape{ virtual show() = 0; // 純虛函數不能執行個體化};
2) 該方法必須在衍生類別中實現
如上面的例子,show( ) 必須在每一個子類中實現,即在子類中重新定義自己的 show( ) 函數,若沒有定義,編譯時間就會報錯:
// 定義Cshape 的衍生類別,忘了定義show()class CPoint : public Cshape{ public: void msg() { cout << " 這是一個點。" << endl; }private: float x; float y;};// 下面執行個體化 CPoint 類的時候就會報錯!!!CPoint point1;
在編譯的時候就會報錯!!!
~~~~
這是不是一個預防在衍生類別中實現基類的方法? (^__^)
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
C++ Primer學習筆記(14)——虛函數的實現機制、純虛函數