跟狄泰軟件學院的唐老師學習C++已經有一段時間了,發現虛函數的概念一直不是很清楚,今天把唐老師的課程重新看了一下,先將相關知識點總結一下。
一、多態的概念
多態值通過類的指針(引用)調用類的方法時,根據實際的對象決定調用函數的具體目標。也就是說同樣的調用語句在實際運行時有多種不同的表現形態。
二、多態的實現方式
C++ 直接支持多態的概念,通過使用virtual關鍵字對多態進行支持。被virtual聲明的函數被子類重寫後具有多態特性。被virtual聲明的函數叫做虛函數。
三、示例一
#include <iostream>#include <string>using namespace std;class Parent {public: virtual void print() { cout << "I'm Parent." << endl; } };class Child : public Parent {public: void print() { cout << "I'm Child." << endl; } };void how_to_print(Parent* p) { p->print(); // 展現多態的行爲}int main() { Parent p; Child c; how_to_print(&p); // Expected to print: I'm Parent. how_to_print(&c); // Expected to print: I'm Child. return 0; }12345678910111213141516171819202122232425262728293031323334353637381234567891011121314151617181920212223242526272829303132333435363738
程序運行結果如下:
上面代碼中通過調用函數void how_to_print(Parent* p)
來體現出多態性,雖然函數的參數都爲Parent* p
,但調用後的結果確不相同。下圖爲void how_to_print(Parent* p)
函數執行流程:
四、C++多態的實現原理
1.當類中聲明虛函數時,編譯器會在類中生成一個虛函數表;
2.虛函數表是一個存儲成員函數地址的數據結構;
3.虛函數表是由編譯器自動生成與維護的;
4.virtual成員函數地址會被編譯器放入虛函數表;
5.存在虛函數表時,每個對象中都有一個指向虛函數表的指針,且放在類的最前面。
下圖爲虛函數調用流程:
下面通過一個示例來說明:
#include <iostream>#include <string>using namespace std;class Parent {private: int m_data;public: Parent() : m_data(10) { } virtual void print() { cout << "I'm Parent." << endl; } };class Child : public Parent {public: void print() { cout << "I'm Child." << endl; } };struct Data { // 模擬Child在內存中的分佈 int *p; int data; };typedef void (*Fun)();int main() { Parent* c = new Child(); Data* data = (Data*)c; cout << "sizeof(Child): " << sizeof(Child) << endl; cout << endl; cout << "data->p: "<< data->p << endl; cout << "*(data->p): " << *(data->p) << endl; cout << "data->data:" << data->data << endl; cout << endl; Fun fun = (Fun)(*(data->p)); fun(); return 0; }12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758591234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
程序運行結果如下圖所示:
1.sizeof(Child): 8
:表明Child除了一個繼承自父類Parent的int m_data;
成員變量所佔用4字節之外,還多出了4字節。這4字節便用來存放虛函數表;
2.data->p: 0x404318
:虛函數表地址;
3.data->data:10
:打印出的是m_data
對應的值,更進一步證明虛函數表是存儲在類的最前面,佔用4字節;
4.*(data->p): 4204792
:虛函數表中存儲的內容,是對應虛函數的入口地址,即void print()
的入口地址。通過下一句的打印I'm Child.
即可證明。
五、構造函數和析構函數能否聲明爲虛函數
1.構造函數不可能成爲虛函數,因爲在構造函數執行結束後,虛函數表指針纔會被正確的初始化,所以在構造函數中不可能發生多態。
2.析構函數可以設計成虛函數。若發生繼承關係時,父類的析構函數一定要實現爲虛函數,否則若通過父類指針釋放子類對象時無法調用子類的析構函數,會造成資源釋放不完全。
3.析構函數中也不可能發生多態行爲。因爲在析構函數執行時,虛函數表指針已經被銷燬,只能調用當前類中定義的版本。
頂
1
踩