多態是OOP中一個十分重要的特性,至於如何使用可以參考這篇C++與JAVA多態相同與不同,相信對於學習C++和java的人都有幫助。
多態實現的關鍵技術是動態綁定。
動態綁定:程序在運行期間尋找函數地址
靜態綁定:代碼在編譯時已經確定了函數地址
多態的實現表象是指針+虛函數,本質是虛表+虛指針。
這里有一篇博客寫的很全面,可以參考一下c++多態實現的機制
1.虛表
聲明了虛函數的類會隱式創建一個虛指針指向虛表,而虛表保存了虛函數的地址(虛表中只有虛函數,非虛函數在另一個表中),當我們調用虛函數時,實際上是通過虛指針去查找虛表中對應虛函數的地址
2.虛指針
虛指針指向類的虛表,屬於類的成員變量,可以繼承,當子類繼承父類的虛指針時,子類的虛指針指向子類的虛表。虛表也可以繼承,也可以理解為繼承了虛函數,然后創建了自己的虛表,所以如果沒有重定義父類虛函數,那么子類虛表和父類虛表完全相同;重定義的虛函數在子類虛表中的地址會改變。
3.虛指針的初始化
虛指針在構造函數中進行初始化,指向類的虛表。如果類B繼承了類A,那么我們知道生成B的實例對象時,會先調用A的構造函數,此時虛指針指向A的虛表,然后調用B的構造函數,此時虛指針指向B的虛表,所以當B的實例構造完成后,虛指針指向B的虛表。
4.多態
用下面的例子講解,當我們聲明一個父類A的指針p指向子類B的對象時,在編譯階段是不分配內存的(不構造對象的),如果想了解編譯過程做了什么,可以參考一下這篇C程序編譯過程淺析。也就是說編譯器並不知道指針p指向的是B類的對象,只知道這是一個A類的指針,那么在編譯p->func()時,會直接查找A中func的地址並替換(可以簡單理解為這樣),也就是靜態綁定,那么在運行時自然調用的就是A的func了
1 class A 2 { 3 public: 4 A(){}; 5 void func() 6 { 7 cout << "A func called!"; 8 } 9 }; 10 11 class B:public A 12 { 13 public: 14 B(){}; 15 void func() 16 { 17 cout << "B func called!"; 18 } 19 20 }; 21 22 int main() 23 { 24 A*p = new B(); 25 p->func(); 26 return 0; 27 }
但是如果將func聲明為虛函數,如下,那么在編譯時編譯器一看func是虛函數,會直接跳過,那么在運行時,B的對象已經被構造出來了,那么p所指向的B對象的虛指針已經指向了B的虛表,此時調用p->func()時,操作系統會在p的虛表中去尋找func這個函數,也就是動態綁定,然后調用,這時候調用的自然就是B的func了,可以說虛指針和虛表就是為了多態而生的。
1 class A 2 { 3 public: 4 A(){}; 5 virtual void func() 6 { 7 cout << "A func called!"; 8 } 9 }; 10 11 class B:public A 12 { 13 public: 14 B(){}; 15 void func() 16 { 17 cout << "B func called!"; 18 } 19 20 }; 21 22 int main() 23 { 24 A*p = new B(); 25 p->func(); 26 return 0; 27 }
看到這里,大家再去看我寫的C++與JAVA多態相同與不同,也就明白為什么我說重寫和重定義不同了。
