程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 重載,多態虛函數

重載,多態虛函數

編輯:關於C語言

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

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

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

1.虛函數表;

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

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

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

5.純虛函數

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


之後我們來看看後面問題的回答

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.當派生類繼承一個抽象基類時,必須實現所有的純虛函數,否則該派生類仍然是一個抽象類,不能創建對象。

 作者“志在千裡”

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved