C++ 多態的原理

什麼是多態?
所謂的多態說簡單來講就是不同的對象去完成相同的工作時,會產生不同的狀態。
多態分爲兩種:一種是編譯時的多態(靜態多態),另一種是運行時的多態(動態多態)。

編譯時的多態

編譯時的多態的實現與靜態連編有關。
(1)那麼什麼是靜態連編呢?
所謂的連編就是將函數名和函數體的代碼聯繫在一起的過程。而所謂的靜態連編就是在編譯時進行的連編。程序在編譯期間,編譯器通過對實參與形參的比較,對於同名的重載函數便根據參數上的差異進行區別,然後進行連編,如此就實現了編譯時的多態。
(2)編譯時的多態可以用兩種方式來實現:

  1. 函數重載
  2. 泛型編程

運行時的多態

運行時的多態則是通過動態連編實現的,所謂的動態連編就是在運行階段完成的連編。即當程序調用到某一個函數時,纔去尋找和連接到與之對應的程序代碼。

(1)構成多態的條件

  1. 必須通過基類的指針或者引用去調用函數。
  2. 基類中必須包含虛函數,且在派生類中必須完成了虛函數的重寫。

問題:什麼是虛函數?
成員函數前面加上關鍵字virtual就是虛函數。

問題:什麼是虛函數的重寫?
派生類中存在的虛函數與其基類中的虛函數函數名、參數、返回值都相同(協變例外),那麼我們就說派生類中的虛函數重寫了基類中的虛函數。而且虛函數重寫也叫做虛函數的覆蓋。

多態的原理

結合下面這段代碼進行分析:

class Person
{
	public:
		......
		virtual void show_name();
		virtual void show_age();
		......
};
class Student:public Person
{
	public:
		......
		virtual void show_age();        //重新定義
		virtual void show_all();             //新增的虛函數
		......
}:

虛函數表(虛表):
所謂的虛函數表本質上是一個指針數組,這個數組中的每一個元素都是一個指針,而這個指針指向的是就是虛函數的地址。換句話說,虛函數表中存放的就是虛函數的地址。

虛表指針:
編譯器如果發現這個類中有虛函數,那麼它就會給在個類的對象中添加一個隱藏的成員,而這個隱藏成員其實就是一個指針(vfptr),我們把這個指針叫做虛表指針。虛表指針指向的就是虛表的首地址。

問題:派生類是如何繼承基類的虛函數?
對於每一個類,編譯器都會爲其創建一個虛函數表。也就是說,基類對象有一個虛表指針,該指針指向了基類中的虛函數表。派生類中同樣也有一個虛表指針,該指針指向了派生類的虛函數表。如果派生類中提供了虛函數的新定義,那麼就會在派生類中的虛函數表中添加該新虛函數的地址;如果派生類中沒有新定義某個虛函數,那麼就會直接在虛函數表中保存原始虛函數的地址;如果在派生類中新定義了一個虛函數,那麼就會在虛函數表中直接添加該新虛函數的地址。如下圖所示:
在這裏插入圖片描述
因爲在Student類的虛函數表中沒有重新定義show_name函數,所以該函數的地址和基類中的地址一致;而show_age函數在Student類中重新定義,所以地址與原來不一樣;在Student類中新增加了一個虛函數show_all,所以就直接在虛函數表的後面增加了該函數的地址。

原理:
調用虛函數時,程序將首先找到存儲在對象中的虛表指針,然後根據虛表指針再找到虛函數表的首地址。如果要使用類的聲明中的第一個虛函數,那麼就使用這個數組中的第一個函數地址,然後到該地址去執行這個函數。如果要使用類的聲明中的第二個虛函數,那麼就使用這個數組中的第二個函數地址,然後到該地址去執行這個函數。

一、虛函數相比於非虛函數的缺點:

1. 內存會增加
(1)每個對象都會增大,因爲會多一個虛表指針
(2)編譯器會爲每一個類都創建一個虛表
2. 執行效率減低
(1)每一次虛函數調用時,都需要執行一個額外的操作:到虛表中查找地址

二、不能作爲虛函數的函數類型:

1. 構造函數
因爲派生類不繼承基類的構造函數,所以沒意義。
2. 友元
因爲友元不是類的成員,而虛函數必須是成員函數。
3. 全局函數
4. 靜態成員函數,它沒有this指針
5. 拷貝構造函數,以及賦值運算符重載(可以但是不建議作爲虛函數)

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