文章目錄
- 1. 認識多態
- 2. 多態的定義和實現
- 2.1 構成多態的必要條件
- 2.2 虛函數
- 2.3 虛函數的重寫或覆蓋
- 2.4 協變(了解)
- 2.5 析構函數的重寫
- 2.6 override和final關鍵字
- 2.7 重載、重寫、隱藏對比
- 3. 純虛函數和抽象類
- 4. 多態原理
- 4.1 虛函數表指針
- 4.2 多態的實現
- 4.3 靜態綁定和動態綁定
- 4.4 虛函數表
1. 認識多態
- 通俗點講,多態其實就是多種形態,它分為編譯時多態(靜態多態)和運行時多態(動態多態)
- 編譯時多態(靜態多態):主要就是之前講過的函數模版和函數重載,它們在編譯階段,通過傳不同類型的參數來調用對應的函數,通過參數不同就可以達到多種形態
- 運行時多態(動態多態):具體一點就是傳不同對象就可以完成不同的行為(函數),從而達到多種形態。例如:對于買票來說,學生買票半價,普通人全價,軍人優先買票。
2. 多態的定義和實現
- 多態是一個繼承關系下的類的對象,去調用同一個函數,產生了不同的行為。
2.1 構成多態的必要條件
- 必須是基類的指針或引用調用虛函數
- 被調用的函數必須是虛函數,并且完成了虛函數的重寫/覆蓋
2.2 虛函數
- 簡單來說,虛函數就是在類成員函數前面+關鍵字virtual修飾,該成員函數也被稱為虛函數,注意:非成員函數不能+virtual修飾
2.3 虛函數的重寫或覆蓋
- 虛函數的重寫或覆蓋:派生類中有一個跟基類完全相同的虛函數(即返回值、函數名、參數列表類型完全相同),稱為派生類的虛函數重寫了基類的虛函數
- 關于多態,可以看下面三組對比,就明白為啥只有使用基類指針/引用調用虛函數才會實現多態的效果了
- 在重寫虛函數時,派生類的虛函數可以不+virtual,也可以和基類的虛函數構成重寫/覆蓋(基類被繼承后,其虛函數的屬性也被派生類繼承了下來)。但是實際項目中,最好還是加上
- 接下來我們看一道關于多態的選擇題
2.4 協變(了解)
- 派生類重寫虛函數時,與基類虛函數返回值類型不同,即基類虛函數返回基類對象的指針/引用,派生類虛函數返回派生類對象的指針/引用時,稱為協變。實際項目中意義不大,了解即可
2.5 析構函數的重寫
- 基類的析構函數為虛函數,那么派生類的析構函數無論加不加virtual修飾,都和基類的析構函數構成重寫(主要是因為編譯器將類的析構函數名字都特殊處理為destructor)
2.6 override和final關鍵字
- 可以看出,C++對于虛函數重寫要求比較高,但也有可能有些情況下就疏忽忘寫,例如函數名寫錯,寫錯參數等,因此C++就提出關鍵字override來檢查是否達到虛函數重寫
- 如果不想讓派生類去重寫虛函數,可用final修飾
2.7 重載、重寫、隱藏對比
3. 純虛函數和抽象類
- 在虛函數的后面寫上=0,則這個函數為純虛函數,純虛函數不需要定義實現(實現也沒啥意義,因為要被派生類重寫,但是語法上可以實現),只要聲明即可
- 包含純虛函數的類叫做抽象類,抽象類不能實例化出對象
- 如果派生類繼承后不重寫純虛函數,那么派生類也是抽象類
- 純虛函數某種程度上強制了派生類重寫虛函數,因為不重寫實例化不出對象
4. 多態原理
4.1 虛函數表指針
- 可以看出,對于含有虛函數的類,其內都有一個虛函數表指針vfptr(全稱virtual function ptr),該指針指向虛函數的地址(虛函數表指針可能放在對象前面,也可能放在對象后面,具體看平臺)
4.2 多態的實現
- 由此可以看出,滿足多態條件后,底層不再是編譯時通過調用對象確定函數的地址,而是運行時到指向的對象的虛表中確定對應的虛函數的地址,這樣就實現了指針或引用指向基類就調用基類的虛函數,指向派生類就調用派生類對應的虛函數
- 第一張圖,ptr指向的Person對象,調用的是Person的虛函數;第二張圖,ptr指向的Student對
象,調用的是Student的虛函數
- 多態也可以發生在多個派生類
4.3 靜態綁定和動態綁定
- 對不滿足多態條件的函數,調用時是在編譯階段綁定的,也就是編譯時確定調用該類函數的地址,叫做靜態綁定
- 滿足多態條件的函數,在運行時,到指向對象的虛函數表中找到調用該類函數的地址,也叫做動態綁定
4.4 虛函數表
- 基類對象的虛函數表中存放基類所有虛函數的地址。同類型的對象共用同一張虛表,不同類型的對象各自有獨立的虛表,所以基類和派生類有各自獨立的虛表
- 派生類由兩部分構成,繼承下來的基類和自己的成員,一般情況下,繼承下來的基類中有虛函數表指針,自己就不會再生成虛函數表指針。但是要注意的這里繼承下來的基類部分虛函數表指針和基類對象的虛函數表指針不是同一個,就像基類對象的成員和派生類對象中的基類對象成員也獨立的
- 派生類中重寫的基類的虛函數,派生類的虛函數表中對應的虛函數就會被覆蓋成派生類重寫的虛函數地址
- 派生類的虛函數表中包含,(1)基類的虛函數地址,(2)派生類重寫的虛函數地址完成覆蓋,派?類自己的虛函數地址三個部分
- 虛函數表本質是?個存虛函數指針的指針數組,?般情況這個數組最后面放了?個0x00000000標記。(這個C++并沒有進行規定,各個編譯器自行定義的,vs系列編譯器會再后面放個0x00000000標記,g++系列編譯不會放)
- 虛函數存在哪的?虛函數和普通函數?樣的,編譯好后是?段指令,都是存在代碼段的,只是虛函數的地址又存到了虛表中
- 虛函數表存在哪的?這個問題嚴格說并沒有標準答案C++標準并沒有規定,我們寫下面的代碼可以對比驗證?下。vs下是存在代碼段(常量區)