C++重載、多態、虛函數

另外一篇關於C++重載、多態、虛函數的文章:

[C++基礎]重載、覆蓋、多態與函數隱藏(1)

[C++基礎]重載、覆蓋、多態與函數隱藏(2)

[C++基礎]重載、覆蓋、多態與函數隱藏(3)

[C++基礎]重載、覆蓋、多態與函數隱藏(4)


初學C++,虛函數這部分感覺博大精深啊。C++正是通過虛函數實現了多態。在C++中,以virtual關鍵字開始的函數是虛函數,虛函數是基類希望派生類進行重新定義的函數,不希望派生類重新定義而完全繼承的不要定義爲虛函數。一旦函數在基類中聲明爲虛函數,派生類就無法改變這個事實,派生類中重新定義虛函數時,關鍵字virtual可有可無。

      虛函數發生動態綁定一定要同時滿足兩個條件:1)這個函數是虛函數2)必須通過基類類型的引用或指針進行函數的調用。首先要清楚一個事實,那就是每個派生類的對象都包含基類部分,存在從派生類到基類的轉換,即可以使用基類類型的指針和引用來引用派生類的對象,因此當使用一個基類類型的指針或引用時,實際上我們是無法立刻確認到底綁定的對象類型是基類的還是派生類的,只能在運行時確定。當使用基類對象調用虛函數時,調用基類中的虛函數版本,當使用派生類調用時,調用的是派生類中重定義的版本。當想克服虛函數機制時,可以使用作用域操作符進行顯示約束。

從學習到面試,個人總結了關於虛函數的幾個問題,如下:

1.虛函數表;

2.構造函數可不可以是虛函數;

3.static成員函數可不可以是虛函數;

4.C++中虛函數與重載,爲什麼要用虛函數;

5.純虛函數

要搞懂2.3問題,首先要明白虛函數表的概念和工作原理,之前也讀過一些資料,但是不是特清晰,找了一個大牛寫的blog,受益匪淺:

http://blog.csdn.net/haoel/article/details/1948051
之後我們來看看後面問題的回答

2.構造函數可不可以是虛函數:

answer:

1)從上面多提到的知識,我們可以看出虛函數的執行是通過虛函數表來實現的,因此這個虛函數表一定要在虛函數調用之前初始化完成。生成一個類的對象要執行構造函數,而設置VPTR也是在構造函數中完成的。其實在虛函數運行之前,對象的類型應該是完全的。而構造函數執行之前,對象的類型是不完全的。如果構造函數也是虛函數的話,那麼VPTR就沒人來初始化,不完全的類型的對象也無法執行這個虛的構造函數。2)試想一下,虛函數是一個基類中的函數在子類中的重寫,如果構造函數成了虛函數,那豈不是派生類中的構造函數名是基類類型的名字?這顯然是不合理的。

3.static成員函數可不可以是虛函數:

answer:

在虛函數表的原理中,編譯器在每個類型對象裏偷偷的插入了一個VPTR,這個VPTR指向虛函數表。虛成員函數在執行時可以通過this這個隱藏形參所指對象判斷是什麼類型的,再通過虛函數表調用正確版本的函數。而static成員函數無this指針,而且他是靜態的,子類繼承後仍然是原來那個靜態的函數,不會出現多態的應用,所以static函數不能是虛函數。

4.
      重載overload是根據函數的參數列表來選擇要調用的函數版本,而多態是根據運行時對象的實際類型來選擇要調用的虛virtual函數版本,多態的實現是通過派生類對基類的虛virtual函數進行覆蓋override來實現的,若派生類沒有對基類的虛virtual函數進行覆蓋override的話,則派生類會自動繼承基類的虛virtual函數版本,此時無論基類指針指向的對象是基類型還是派生類型,都會調用基類版本的虛virtual函數;如果派生類對基類的虛virtual函數進行覆蓋override的話,則會在運行時根據對象的實際類型來選擇要調用的虛virtual函數版本,例如基類指針指向的對象類型爲派生類型,則會調用派生類的虛virtual函數版本,從而實現多態。

      使用多態的本意是要我們在基類中聲明函數爲virtual,並且是要在派生類中覆蓋override基類的虛virtual函數版本,注意,此時的函數原型與基類保持一致,即同名同參數類型;如果你在派生類中新添加函數版本,你不能通過基類指針動態調用派生類的新的函數版本,這個新的函數版本只作爲派生類的一個重載版本。還是同一句話,重載只有在當前類中有效,不管你是在基類重載的,還是在派生類中重載的,兩者互不牽連。

      重載是靜態聯編的,多態是動態聯編的。進一步解釋,重載與指針實際指向的對象類型無關,多態與指針實際指向的對象類型相關。若基類的指針調用派生類的重載版本,C++編繹認爲是非法的,C++編繹器只認爲基類指針只能調用基類的重載版本,重載只在當前類的名字空間作用域內有效,繼承會失去重載的特性,當然,若此時的基類指針調用的是一個虛virtual函數,那麼它還會進行動態選擇基類的虛virtual函數版本還是派生類的虛virtual函數版本來進行具體的操作,這是通過基類指針實際指向的對象類型來做決定的,所以說重載與指針實際指向的對象類型無關,多態與指針實際指向的對象類型相關。

這裏“隱藏”是指派生類的函數屏蔽了與其同名的基類函數,具體規則我們也來做一小結:

       如果派生類的函數與基類的函數同名,但是參數不同。此時,若基類無virtual關鍵字,基類的函數將被隱藏。(注意別與重載混淆,雖然函數名相同參數不同應稱之爲重載,但這裏不能理解爲重載,因爲派生類和基類不在同一名字空間作用域內。這裏理解爲隱藏)

       如果派生類的函數與基類的函數同名,但是參數不同。此時,若基類有virtual關鍵字,基類的函數將被隱式繼承到派生類的vtable中。此時派生類vtable中的函數指向基類版本的函數地址。同時這個新的函數版本添加到派生類中,作爲派生類的重載版本。但在基類指針實現多態調用函數方法時,這個新的派生類函數版本將會被隱藏。

       如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏。(注意別與覆蓋混淆,這裏理解爲隱藏)。

       如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數有virtual關鍵字。此時,基類的函數不會被“隱藏”。(在這裏,你要理解爲覆蓋哦)。

5. 純虛函數

有時候,基類僅僅作爲其派生類的一個接口,而不希望用戶實際創建一個基類的對象。此時可以在基類中加入至少一個純虛函數,使得基類變爲抽象基類。純虛函數在普通的虛函數後面加上=0.當派生類繼承一個抽象基類時,必須實現所有的純虛函數,否則該派生類仍然是一個抽象類,不能創建對象。


 作者“志在千里”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章