1、虛函式表
虛函式表是C++實作多型的基礎,多型是面向物件的三大特性之一,多型有利于提高代碼的可讀性,便于后期代碼的擴展和維護,我們都知道多型的實作是基于虛函式表,那么虛函式表是什么時候創建的呢?虛函式表是怎么實作多型的功能的呢?
首先應該明確多型也稱為動態多型,他是在程式運行時候確定函式地址的,也就是程式在運行時,如果類成員函式加了virtual關鍵字,就會建立一個虛函式指標(vfptr)指標指向一個虛函式表,這個虛函式表就保存了虛函式的地址,子類繼承父類也自然繼承了虛函式指標,當子類重寫父類的虛函式時,虛函式指標所指向的虛函式表中的虛函式地址就會被覆寫,替換成子類的虛函式地址,也就是通過父類的虛函式指標找到了子類的虛函式地址,進而執行這個函式,下面我們通過代碼進行詳細說明:
#include <iostream> using namespace std; class Base{ public: void func(){ cout << "Base func" << endl; } }; class Son: public Base{ void func(){ cout << "Son func" << endl; } }; void test(Base& base) { base.func(); } int main () { Son son; cout << "sizeof(Base) = " << sizeof(Base) << endl; cout << "sizeof(Son) = " << sizeof(Son) << endl; test(son); system("pause"); return 0; }
代碼運行結果為:

因為函式成員不占用類的大小,所以對Base類和Son類輸出大小,都是一個位元組,這一個位元組是為了可以實體化類,通過參考基類參考派生類,呼叫func函式,函式呼叫了基類的func,那么如果我們加上virtual關鍵字后,就不是這種情況了,
#include <iostream> using namespace std; class Base{ public: virtual void func(){ cout << "Base func" << endl; } }; class Son: public Base{ void func(){ cout << "Son func" << endl; } }; void test(Base& base) { base.func(); } int main () { Son son; cout << "sizeof(Base) = " << sizeof(Base) << endl; cout << "sizeof(Son) = " << sizeof(Son) << endl; test(son); system("pause"); return 0; }
代碼運行結果為:

可以看到加了virtual關鍵字后,父類和子類的大小都變成了四位元組,這是因為生成了虛函式指標,指標指向虛函式表,虛函式表存盤了虛函式地址,繼承了父類的子類重寫了虛函式,虛函式表中的函式地址被替換,再次呼叫虛函式就是呼叫了子類的函式func,
2、虛析構
虛析構主要是為了解決子類中有屬性開辟到堆區,父類指標呼叫函式時,無法呼叫到子類的析構代碼,導致子類堆區記憶體無法釋放,
首先我們看一下子類堆區記憶體開辟,通過父類指標來呼叫函式,捕捉他們的建構式和解構式看下運行結果:
#include <iostream> using namespace std; class Base{ public: Base(){ cout << "Base 的建構式呼叫" << endl; } ~Base(){ cout << "Base 的解構式呼叫" << endl; } virtual void func(){ cout << "Base func" << endl; } }; class Son: public Base{ public: Son(int val):m_val(new int (val)) { cout << "Son 的建構式呼叫" << endl; } ~Son(){ cout << "Son 的解構式呼叫" << endl; if (m_val != NULL) { delete m_val; cout << "Son 解構式的堆記憶體釋放" << endl; m_val = NULL; } } void func(){ cout << "Son func" << endl; } void funcTest(){ cout << "funcTest 函式呼叫" << endl; } int* m_val = NULL; }; void test() { Base *base = new Son(10); base->func(); //base->funcTest(); //無法呼叫,因為虛函式表中不能找到這個函式的地址 delete base; base = NULL; } int main () { test(); system("pause"); return 0; }
代碼運行結果為:

可以明確,通過父類指標來呼叫函式的時候,無法呼叫Son類的解構式,在Son類在堆區上申請的記憶體就無法釋放,造成記憶體泄漏,Son類的解構式不能呼叫的主要原因就是在虛函式表中找不到Son的解構式地址,解決辦法就是把Base類的寫成虛解構式或者純虛解構式,下面給出Base類為純虛解構式的代碼和運行結果:
#include <iostream> using namespace std; class Base{ public: Base(){ cout << "Base 的建構式呼叫" << endl; } virtual ~Base() = 0; virtual void func(){ cout << "Base func" << endl; } }; Base :: ~Base(){ cout << "Base 的解構式呼叫" << endl; } class Son: public Base{ public: Son(int val):m_val(new int (val)) { cout << "Son 的建構式呼叫" << endl; } ~Son(){ cout << "Son 的解構式呼叫" << endl; if (m_val != NULL) { delete m_val; cout << "Son 解構式的堆內存釋放" << endl; m_val = NULL; } } void func(){ cout << "Son func" << endl; } void funcTest(){ cout << "funcTest 函式呼叫" << endl; } int* m_val = NULL; }; void test() { Base *base = new Son(10); base->func(); //base->funcTest(); //無法呼叫,因為虛函式表中不能找到這個函式的地址 delete base; base = NULL; } int main () { test(); system("pause"); return 0; }
代碼運行結果為:

可以看到只要把Base類的解構式寫成虛解構式或純虛解構式,通過父類指標呼叫函式,子類的析構代碼會被呼叫,子類堆區記憶體得到釋放,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/346841.html
標籤:C++
上一篇:C++面向物件高級開發1
