1.靜態聯編和動態聯編
聯編:將源代碼中的函數調用解釋為要執行函數代碼。
靜態聯編:編譯時能確定唯一函數。
在C中,每個函數名都能確定唯一的函數代碼。
在C++中,因為有函數重載,編譯器須根據函數名,參數才能確定唯一的函數代碼。
動態聯編:編譯時不能確定調用的函數代碼,運行時才能。
C++中因為多態的存在,有時編譯器不知道用戶將選擇哪種類型的對象,因此無法確定調用的唯一性,只有在運行時才能確定。
2.虛成員函數,指針或引用,動態聯編
指針或引用才能展現類的多態性。
當類中的方法聲明為virtual時,使用指針或引用調用該方法,就是動態聯編。
若是普通方法,則為靜態聯編。
示例如下:
class Test { public: virtual show() { std::cout<<"Test::show()"<<std::endl; } }; class SubTest:public Test { public: virtual show() { std::cout<<"SubTest::show()"<<std::endl; } }; int main() { SubTest subTest; Test * p = &subTest;//指向子類的指針 Test & a = subTest;//子類的引用 Test * p2 = new Test;//指向父類的指針 p->show(); a.show(); p2->show(); return 0; }
程序沒有釋放內存,我們將在后面析構函數的時候,完善該程序。
3.動態聯編使用原則
動態聯編,需要跟蹤基類指針或引用指向的實際對象類型,因此效率低於靜態聯編。
1)當類不會用作基類時,成員函數不要聲明為virtual
2)當成員函數不重新定義基類的方法,成員函數不要聲明為virtual
4.關於虛函數
1)父類成員函數若聲明為virtual,則子類中也是虛的,若要重新定義該方法,可顯式加上virtual關鍵字,也可不加,編譯器編譯時會自動加上。
2)使用指向對象的引用或指針來調用虛方法,將使用具體對象類型定義的方法,而不一定是引用或指針類型定義的方法。
SubTest subTest;
Test * p = &subTest;//指向子類的指針
p->show();//將調用SubTest對象定義的show()方法
5.虛函數的工作原理
當類中存在虛函數時,編譯器默認會給對象添加一個隱藏成員。該成員為一個指向虛函數表(virtual function table,vtbl)的指針。
虛函數表是一個保存了虛函數地址的數組。編譯器會檢查類中所有的虛函數,依次將每個虛函數的地址,存入虛函數表。
class Test { public: virtual show() { std::cout<<"Test::show()"<<std::endl; } private: int a; }; class SubTest:public Test { public: virtual show() { std::cout<<"SubTest::show()"<<std::endl; } };
內存結構圖如下所示:
可以看出,父類和子類有獨立的虛函數表,且虛函數表中虛函數指針也指向各自的虛函數地址,
若子類沒有覆蓋父類中的show方法,則虛函數指針show_ptr指向的虛函數show()的地址是一樣的,均指向父類show()函數地址。虛函數表的存在和動態聯編,就是多態的原理。
6.虛析構函數
1)構造函數是特殊的,是沒有虛函數的概念的。
構造函數是不繼承的,創建子類對象時,將調用子類的構造函數,子類的構造函數將自動調用父類的構造函數。
2)析構函數應是虛函數,除非類不用做基類。
我們看下面的代碼:
Test *p = new SubTest;
delete p;
p=NULL;
由虛函數表,我們知道,若析構函數不聲明為virtual,則調用的將是Test類的析構函數,而沒有調用SubTest類的析構函數,此時造成了內存泄露。
所以析構函數必須聲明為虛函數,調用的將是子類SubTest的析構函數,
我們還需要知道的一點是,子類析構函數,一定會調用父類析構函數,釋放父類對象,則內存安全釋放。
我們第一個例子的完整的示例代碼如下:
class Test { public: virtual show() { std::cout<<"Test::show()"<<std::endl; } virtual ~Test(){} }; class SubTest:public Test { public: virtual show() { std::cout<<"SubTest::show()"<<std::endl; } }; int main() { SubTest subTest; Test * p = &subTest;//指向子類的指針 Test & a = subTest;//子類的引用 Test * p2 = new Test;//指向父類的指針 p->show(); a.show(); p2->show(); delete p; p=NULL; delete p2; p2=NULL; return 0; }
參考資料:《C++ Primer.Plus》 pp.490-507
http://www.imooc.com/video/9199