日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++虚函数的实现原理

發布時間:2024/3/12 c/c++ 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++虚函数的实现原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一. 虛函數介紹

C++中的虛函數主要是用來實現多態(面向對象的三大特性之一)的。
下面是一個實現多態的錯誤例子:

// 基類 // class Base { public:Base() {printf("Call Base::Base()\n");}~Base() {}void Name() {printf("Call Base::Name()\n");} };// 派生類 // class Derive : public Base { public:Derive() {printf("Call Derive::Derive()\n");}~Derive() {}void Name() {printf("Call Derive::Name()\n");} };int main() {Base* pBase = new Derive();pBase->Name();delete pBase;return 0; }

程序輸出:

Call Base::Base() Call Derive::Derive() Call Base::Name()

輸出內容的第3行為:Call Base::Name(),并不是期望的Call Derive::Name()。
因為void Name()函數不是虛函數,所以pBase->Name()調用的是基類的Name()函數,并不是我們所期望的派生類Derive的Name()函數。 如果將基類中void Name()改成虛函數virtual void Name(),程序輸出就會和我們期望的一樣:

// 基類 // class Base { public:Base() {printf("Call Base::Base()\n");}~Base() {printf("Call Base::~Base()\n");}virtual void Name() {printf("Call Base::Name()\n");} };

二、 虛析構函數

virtual不僅可以修飾成員函數,也可以用來修飾析構函數,也就是我們常說的虛析構函數。 下面的例子中的基類的析構函數沒有使用virtual修飾,我們先執行程序,觀察運行結果(類似上面的程序,只是在析構函數中多加入了輸出打印語句)。

// 基類 // class Base { public:Base() {printf("Call Base::Base()\n");}~Base() {printf("Call Base::~Base()\n");}virtual void Name() {printf("Call Base::Name()\n");} };// 派生類 // class Derive : public Base { public:Derive() {printf("Call Derive::Derive()\n");}~Derive() {printf("Call Derive::~Derive()\n");}void Name() {printf("Call Derive::Name()\n");} };int main() {Base* pBase = new Derive();pBase->Name();delete pBase;return 0; }

程序輸出:

Call Base::Base() Call Derive::Derive() Call Derive::Name() Call Base::~Base()

從輸出內容的第4行可以看到,執行delete pBase語句只有基類Base類的析構函數被調用了,而派生類Derive的析構函數卻沒有被調用。如果此時派生類Derive中有需要在析構函數執行的代碼(如內存釋放,句柄關閉等),這些代碼將不會執行,有可能就會造成內存泄漏、句柄泄漏、邏輯錯誤等問題。

正確的做法是:使用virtual修飾基類的析構函數,即虛析構函數。

// 基類 // class Base { public:Base() {printf("Call Base::Base()\n");}virtual ~Base() {printf("Call Base::~Base()\n");}virtual void Name() {printf("Call Base::Name()\n");} };

此時程序輸出為:

Call Base::Base() Call Derive::Derive() Call Derive::Name() Call Derive::~Derive() Call Base::~Base()

調用delete pBase之后,先執行了派生類Derive的析構函數,然后執行基類Base的析構函數。

在《C++API設計》一書中有明確的說到:“如果希望一個類可以被繼承,那么就應該將它的析構函數使用virtual修飾;反過來可以理解為,如果一個類的析構函數不是虛的,那么這個類是被設計為不可繼承的。”

二. 虛函數的實現原理

2.1 實現原理

C++的虛函數是使用虛函數表(即指針數組,也就是指針的指針)來實現的。 只要在類中聲明了虛函數,編譯器就會在類的對象中自動生成一個虛函數表,但一個對象最多只有一個虛函數表,不管這個類聲明了多少個虛函數。虛函數表是針對于類的對象的。

2.2 虛函數表(指針)存儲位置

不同的編譯器將自動生成的虛函數表指針存放的位置不同,有的存放在類對象所占內存的起始位置,有的存放在類對象所占內存的末尾。 可以通過如下代碼來判斷:

// 返回值: ture - 虛函數指針存放在對象內存起始位置 // false - 虛函數指針存放在對象內存末尾位置 // bool VirtualTableAtFirst() {class _C {public:char _i;virtual void _f() {}};_C c;char * p1 = reinterpret_cast<char*>(&c);char * p2 = reinterpret_cast<char*>(&c._i);return p1 != p2; }

通過MSVC2015編譯運行,返回true, 說明MSVC編譯器是將虛函數表指針放置在類對象內存的起始位置處。

2.3 虛函數表存儲方式

既然知道了C++是使用虛函數表的形式來實現虛函數的,那個虛函數表中的數據是以何種形式來存儲的了? 現在我們根據類的繼承方式的不同來分別說明。

1. 單繼承無重載

類結構如圖,Derive繼承于Base,但Derive沒有重載Base類中的任何函數。

需要說明的是,函數f(), g(), h(), f1(), g1(), h1() 均為虛函數,這個在圖上沒有明確的寫出來,后面的圖也是一樣。

這時Base b; 對象b的虛函數表為:

Derive d; 對象d的虛函數表為:

2. 單繼承有重載

Derive重載Base類中的f()函數:

這時Base b; 對象b的虛函數表不變,無論繼承于它派生類如何重載,都不會影響基類的虛函數:

Derive d; 對象d的虛函數表為:

派生類中重載基類的f()函數指針替換了原來基類中虛函數Base::f()的指針; 派生類中其他的虛函數存放在基類虛函數之后。

3. 多繼承無重載

此時,Derive d; 對象d的虛函數表為:

派生類自己的虛函數存放在第一個基類的虛函數表的最后面。

4. 多繼承有重載

此時,Derive d; 對象d的虛函數表為:

圖片引用自 http://blog.csdn.net/haoel/article/details/1948051

總結

以上是生活随笔為你收集整理的C++虚函数的实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。