一、多態的概念
在說多態之前,我們來先看一看對象的類型
來看一個例子:
多態:意思既是同一個事物的多種形態,用我們C++的專業詞語來說就是:一個借口、多種實現方式。
二、多態分類
靜態多態:
靜態多態:編譯器在編譯期間完成的,編譯器根據函數實參的類型(可能會進行隱式類型轉換),可推斷出要調用那個函數,如果有對應的函數就調用該函數,否則出現編譯錯誤。
動態多態:
動態多態就是我們常說的多態。動態多態是在程序運行期間才決定調用哪個函數,是根據虛函數表實現的。聲明了虛函數的類,類中都有一張虛函數表,里面存放類的入口地址。通過賦值兼容規則,可以用父類的指針或引用找到子類的虛函數。虛函數是處理類的派生體系中不同層次上不同作用域的同名問題,因此動態多態必須在類的繼承體系中才能實現。
三、多態的實現原理(動態)
說到這里,有幾個非常重要的概念,需要我們加以區分:重載、重寫、重定義
通過上面的多態的介紹,這里來說幾個重要的概念:
(1)虛函數表指針:類中除了定義成員函數之外還有一個成員是虛函數表指針(占四個基本內存單位),這個指針指向一個虛函數表的起始位置,這個表會與類的定義同時出現,這個表會與類的定義同時出現,這個表存放著該類的虛函數指針,調用的時候可以找到該類虛函數表指針,通過虛函數表指針找到虛函數表,通過虛函數表的偏移找到函數的入口地址,從而找到要使用的虛函數。
(2)當實例化一個該類的子類對象的時候,(如果)該類的子類并沒有定義虛函數,但是卻從父類中繼承了虛函數,所以在實例化該類子類對象的時候也會產生一個虛函數表,這個虛函數表是子類的虛函數表,但是記錄的子類的虛函數地址卻是與父類的是一樣的。所以通過子類對象的虛函數表指針找到自己的虛函數表,在自己的虛函數表找到的要執行的函數指針也是父類的相應函數入口的地址。
(3)如果我們在子類中定義了從父類繼承來的虛函數,對于父類來說情況是不變的,對于子類來說它的虛函數表與之前的虛函數表是一樣的,但是此時子類定義了自己的(從父類那繼承來的)相應函數,所以它的虛函數表當中管于這個函數的指針就會覆蓋掉原有的指向父類函數的指針的值,換句話說就是指向了自己定義的相應函數,這樣如果用父類的指針,指向子類的對象,就會通過子類對象當中的虛函數表指針找到子類的虛函數表,從而通過子類的虛函數表找到子類的相應虛函數地址,而此時的地址已經是該函數自己定義的虛函數入口地址,而不是父類的相應虛函數入口地址,所以執行的將會是子類當中的虛函數。這就是多態的原理。
(4)純虛函數
在成員函數的參數列表后面寫上“=0”則該成員函數為純虛函數。
包含純虛函數的類叫做抽象類(也叫接口類),抽象類不能實例化出對象。
純虛函數在抽象類中重新定義以后,派生類才能實現實例化出對象。
總結:
(1)派生類重寫基類的虛函數實現多態,要求函數名、參數列表、返回值完全相同(協變除外)
協變:基類和派生類中的虛函數名字和參數列表相同、返回值類型不同,基類中的虛函數返回Base*,派生類中的虛函數返回Derived*
(2)基類中定義了虛函數,在派生類中該函數始終保持虛函數的特性。
(3)只有類的非靜態成員函數才能定義為虛函數,靜態成員函數不能定義為虛函數。
(4)如果在類外定義虛函數,只能在聲明函數時加virtual關鍵字,定義時不用加。
(5)構造函數不能定義為虛函數,雖然可以將operator=定義為虛函數,但最好不要這么做,使用時容易混淆
(6)不要在構造函數和析構函數中調用虛函數,在構造函數和析構函數中,對象是不完整的,可能會出現未定義的行為。
(7)最好將基類的析構函數聲明為虛函數。(析構函數比較特殊,因為派生類的析構函數跟基類的析構函數名稱不一樣,但是構成覆蓋,這里編譯器做了特殊處理)
(8)虛表是對所有類對象實例共用的
-
C++
+關注
關注
21文章
2084瀏覽量
73297 -
動態多態
+關注
關注
0文章
4瀏覽量
5894 -
靜態多態
+關注
關注
0文章
2瀏覽量
5479
發布評論請先 登錄
相關推薦
評論