C++中多態由淺到深理解


  簡單一句話就是,操作接口表現出多種形態。嚴格的多態分爲編譯時多態(靜態多態)和運行時多態(多態多態)。我們一般講的多態都是運行時多態。本文講的多態就是運行時多態。
  言歸正傳,講我們的多態。


多態

多態通過繼承和虛函數實現。運行時才綁定函數地址(晚綁定)。最常見的用法就是什麼一個父類指針,讓父類型的指針指向任意一個子類對象,然後通過父類的指針動態地調用實際子類的成員函數。根據指向的子類的不同而實現不同的功能。(或者讓父類型的引用綁定子類對象(不用指針用引用的話))

怎麼寫多態

通過虛函數
目的:**接口重用。**一個接口,多種形態。
比方,我們有個動物類,吃飯函數。一個接口:吃飯。繼承的類有狗、羊、貓,它們也要吃飯,但是狗要吃骨頭、羊要吃草、貓要吃魚。我們就可以把父類動物類的吃飯函數加個virtual定義爲虛函數。然後子類去重寫(即重新定義)吃飯函數。用的時候,用父類的指針,指向任意一個子類對象,調用的時候去調用實際子類的成員函數,動物類指向貓則調用吃魚,指向羊則調用吃草,指向狗則調用吃骨頭。一個吃飯的接口,表現出了多種形態,吃魚吃骨頭吃草。

Base 動物{virtual 吃函數 };
Derived 貓{重寫吃飯爲吃魚};
Derived 狗{重寫吃飯爲吃骨頭};
Derived 羊{重寫吃飯爲吃草};
貓類 cat;
狗類 dog;
羊類 hseep; 

動物類 *a= &dog;
a.吃();//調用的是吃骨頭

動物類 *a1= &cat;
a1.吃();//調用的是吃魚

注意:所有類的吃飯函數都是虛函數了,不僅僅是父類。

多態的實現:by虛函數

c++成員函數有兩種:一種是子類直接繼承而不需要改變的函數,一種是允許子類重寫/覆蓋(override)的函數。把後者定義爲了虛函數。加virtual關鍵字。

什麼時候要聲明爲虛函數?

即,如果子類需要修改父類的行爲,即要重寫,就應該在父類中將相應的函數聲明爲虛函數。

虛函數的目的?

加關鍵字後,virtual告訴編譯器不應當完成早綁定(綁定現在這個函數(虛函數)的地址),應當晚綁定,運行時動態綁定。

虛函數virtual function的實現機制——虛函數表Virtual table

V-table好比一張地圖,指明瞭實際所應該調用的函數。
V-table也是虛函數的一個代價:產生系統開銷。(弊)
編譯器對每一個包含虛函數的類創建一個表。
當一個包含虛函數的類的對象被創建,會有一個虛表指針vptr(vpointer),指向該類的虛表,虛表裏放的是一個類的虛函數的地址。是順序存放虛函數地址的。

eg.

  1. class Base有: virtual void f(), virtual void g(), virtual void h()
    可以通過Base的實例b來得到虛函數表:
    在這裏插入圖片描述
    地址:虛函數表地址、虛函數表中第一個虛函數地址、表中第二個虛函數地址…
  2. 沒有虛函數重寫/覆蓋(實際上毫無意義,爲了便於理解)
    class Derive:public Base{
    vir void f1(), vir void g1(), vir void h1()}
    對於實例Derive1 d虛函數表如下:
    在這裏插入圖片描述
    看到:虛函數按照其聲明順序放於表中。父類的虛函數在子類的虛函數前面。
  3. 子類覆蓋/重寫了父類的虛函數f
    class Derive:public Base{
    vir void f(), vir void g1(), vir void h1()}
    對於子類的實例,其虛函數表如下:
    在這裏插入圖片描述
    看到:覆蓋的f()函數被放到了虛表中原來父類虛函數f()的位置。沒有被覆蓋的虛函數依舊。

於是,就有了,我們一般寫的程序父類指針指向子類對象Base *b= new Derive(); b->f(); delete b;,實際調用的時候調用的是子類重寫的函數Derive::f(),實現了多態。因爲b所指的內存中的虛函數表的f()的位置已經被Derive::f()函數地址所取代。
  Derive d;(棧上面開闢空間) *Base b= &d;
  new是在堆上面。

爲什麼虛函數效率低?

因爲虛函數需要一次間接尋址,而一般的函數可以在編譯時定爲到函數的地址,虛函數(動態類型調用)要根據某個指針定位到函數的地址。多了一個過程,效率低一些,但帶來了運行時的多態。

虛函數的入口地址和普通函數有什麼區別?

虛函數表:
在這裏插入圖片描述
當一個包含虛函數的對象被創建的時候,
它在頭部附加一個指針vptr,執行vtable
中相應位置。調用虛函數的時候,不管
你是用什麼指針調用的,它先要去vtable
裏找到入口地址在再執行
,從而實現了
“動態聯編”。不像普通函數那樣簡單地
跳轉到一個固定地址。

發佈了137 篇原創文章 · 獲贊 74 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章