多態是c++中很重要的一環。多態能夠分爲如下幾個層面來剖析:c++
1.對象的類型編程
2.多態ide
3.虛表函數
先說第一點對象的類型,這個很是簡單。好比說、this
int a;
那麼我就定義了一個int類型的變量a。再來看下面的代碼3d
class Base { }; class Derive:public Base { };
這裏我寫了一個Base類和一個Derive類,而且Derive類是派生於Base類指針
Base b; Derive d; Base* pb=&b; pb=&d;
上面的代碼實例化了一個Base類類型的對象b,Derive類類型的對象d,Base*類型的指針pb。
對象
pb的靜態類型就是Base*類型,咱們也可讓pb指向d,Derive*就是pb的動態類型。blog
下面來講說第二點,多態。繼承
int Add(int left,int right) { return left+right; } double Add(double left,double right) { return left+right; }
上面這兩個函數構成了函數的重載,傳進去int類型的參數就調用上面的,double類型的參數就調用下面的。這也是一種多態,稱爲靜態的多態。還有一種泛型編程也是靜態的多態。靜態多態就是在編譯器編譯期間完成的,編譯器根據函數的實參的類型(可能進行隱式的類型轉換),可推斷出到底要調用哪一個函數,若是有對應的函數就調用該函數,不然就會出現編譯錯誤。
那麼動態多態(也叫動態綁定)就是指在程序執行期間判斷所引用對象的實際類型,根據其實際類型調用相應的方法。
使用virtual關鍵字來修飾函數,指明該函數爲虛函數,派生類須要從新實現,編譯器將實現動態綁定。
所謂虛函數就是指在類中被聲明爲virtual的成員,基類但願這種成員在派生類中重定義。除了構造函數外,任意非static成員均可覺得虛成員。保留字 virtual 只在類內部的成員函數聲明中出現,不能用在類定義體外部出如今函數定義上。
看一段代碼:
class Base { public: virtual void FunTest() { cout<<"Base::FunTest()"<<endl; } }; class Derive :public Base { public: void FunTest() { cout<<"Derive::FunTest()"<<endl; } }; int main() { Derive d; d.FunTest(); Base b; b.FunTest(); Base *pb=&b; pb->FunTest(); pb=&d; pb->FunTest(); return 0; }
在這一段代碼裏面,我定義了兩個類一個是Base,另外一個是他的派生類Derive。Base類裏面有一個虛函數FunTest(),Derive類裏面也有一個FunTest()。而且在主函數裏面實例化了兩個類的對象,而且調用了FunTest函數,下面也定義了Base*類型的指針,先指向b,而後調用了FunTest函數,以後指向d,而後調用FunTest函數。這段代碼運行結果會是什麼樣呢?
正如咱們所看到的調用派生類裏面的函數他有他就調用他本身的,他沒有再去基類裏面找。
那麼動態綁定實現的條件是什麼呢?第一,必需要是虛函數。第二,要經過基類類型的引用或者指針調用。
class CBase { public: virtual void FunTest1(int _iTest) { cout << "CBase::FunTest1()" << endl; } void FunTest2(int _iTest) { cout << "CBase::FunTest2()" << endl; } virtual void FunTest3(int _iTest1) { cout << "CBase::FunTest3()" << endl; } virtual void FunTest4(int _iTest) { cout << "CBase::FunTest4()" << endl; } }; class CDerive:public CBase { public: virtual void FunTest1(int _iTest) { cout << "CDerive::FunTest1()" << endl; } virtual void FunTest2(int _iTest) { cout << "CDerive::FunTest2()" << endl; } void FunTest3(int _iTest1) { cout << "CDerive::FunTest3()" << endl; } virtual void FunTest4(int _iTest1,int _iTest2) { cout << "CDerive::FunTest4()" << endl; } }; int main() { CBase* pBase = new CDerive; pBase->FunTest1(0); pBase->FunTest2(0); pBase->FunTest3(0); pBase->FunTest4(0); return 0; }
上面是一個例子,CBase類是CDerive類的基類。以後FunTest1()是一個虛函數,FunTest2()不是一個虛函數,FunTest3()也是虛函數,FunTest4()雖然是虛函數可是在子類裏面從新實現給了兩個參數。因此運行結果是這樣的:
假如咱們想調用CDerive裏面的FunTest4(),咱們就要用CDerive類的對象了。就像下面這樣:
CDerive d; d.FunTest4(0, 0);
這裏還須要注意:構造函數是不能夠定義爲虛函數的,由於構造函數是用來構建咱們的對象 的,構造函數沒有執行完咱們的對象就是不完整的。假如咱們要調用構造函數,是須要經過咱們的基類對象來調用的,可是咱們的對象都沒有構造完,因此是不能這樣的。
靜態函數和友元函數也一樣不能夠用virtual來修飾。由於這兩種函數都沒有this指針。
這裏還有一個東西:
class test { virtual void Test() = 0; };
這段代碼定義的類叫抽象類。它不可以實例化產生對象,它只是提供一些接口。它裏面的那個函數後面跟了一個=0,表示它是純虛函數,它表示它的派生類要對它這個函數進行重寫。
最後來講虛表和虛指針。
當咱們求sizeof(test)時,咱們得出的結果是4。爲何呢?對類求大小的時候不該該是它的成員的大小嗎?這裏就是有一個虛指針。那這個虛指針指向那裏呢?是指向的虛表。虛表裏面存的就是虛函數的地址。
舉一個例子:
class test { public: virtual void FunTest1() {} virtual void FunTest2() {} virtual void FunTest3() {} virtual void FunTest4() {} };
這個類裏面只有四個虛函數,那麼sizeof(test)等於多少呢?
再來看看t中到底有什麼:
因此當咱們要調用虛函數的時候,編譯器是先找到咱們的虛表地址,以後找到對應的虛函數。