虛函式 執行時多型的理解

2021-07-10 12:50:34 字數 2594 閱讀 7138

形狀對外公開乙個函式來把自己繪製出來。這是合理的,形狀就應該能繪製出來,對吧?由於繼承的原因,多邊形和圓形也有了繪製自己這個函式。

現在我們來討論在這三個類中的繪製自己的函式都應該怎麼實現。在形狀中嘛,什麼也不做就行了。在多邊形中嘛,只要把它所有的頂點首尾相連起來就行了。在圓形中嘛,依據它的圓心和它的半徑畫乙個360度的圓弧就行了。

可是現在的問題是:多邊形和圓形的繪製自己的函式是從形狀繼承而來的,並不能做連線頂點和畫圓弧的工作。

怎麼辦呢?覆蓋它,覆蓋形狀中的繪製自己這個函式。於是我們在多邊形和圓形中各做乙個繪製自己的函式,覆蓋形狀中的繪製自己的函式。為了實現覆蓋,我們需要把形狀中的繪製自己這個函式用virtual修飾。而且形狀中的繪製自己這個函式什麼也不幹,我們就把它做成乙個純虛函式。純虛函式還有乙個作用,就是讓它所在的類成為抽象類。形狀理應是乙個抽象類,不是嗎?於是我們很快寫出這三個類的**如下:

class shape//形狀

};class polygo:public shape//多邊形

};class circ:public shape//圓

};下面,我們將以上面的這三個類為基礎來說明動態多型。在進行更進一步的說明之前,我們先來說乙個不得不說的兩個概念:「子型別」和「向上轉型」。

7.2.向上轉型

子型別很好理解,比如上面的多邊形和圓形就是形狀的子型別。關於子型別還有乙個確切的定義為:如果型別x擴充或實現了型別y,那麼就說x是y的子型別。

向上轉型的意思是說把乙個子型別轉的物件換為父型別的物件。就好比把乙個多邊形轉為乙個形狀。向上轉型的意思就這麼簡單,但它的意義卻很深遠。向上轉型中有三點需要我們特別注意。第一,向上轉型是安全的。第二,向上轉型可以自動完成。第三,向上轉型的過程中會丟失子型別資訊。這三點在整個動態多型中發揮著重要的作用。

假如我們有如下的乙個函式:

void outputshape( shape arg)//專門負責呼叫形狀的繪製自己的函式

那麼現在我們可以這樣使用outputshape這個函式:

polygon shape1;

circ shape2;

outputshape(shape1);

outputshape(shape2);

我們之所以可以這樣使用outputshape函式,正是由於向上轉型是安全的(不會有任何的編譯警告),是由於向上轉弄是自動的(我們沒有自己把shape1和shape2轉為shape型別再傳給outputshape函式)。可是上面這段程式執行後的輸出結果是這樣的:

我是乙個什麼也繪不出的圖形

我是乙個什麼也繪不出的圖形

明明是乙個多邊形和乙個圓呀,應該是輸出這下面這個樣子才合理呀!

連線各頂點

以圓心和半徑為依據畫弧

造成前面的不合理的輸出的罪魁禍首正是『向上轉型中的子型別資訊丟失』。為了得到乙個合理的輸出,得想個辦法來找回那些丟失的子型別資訊。c++中用一種比較巧妙的辦法來找回那些丟失的子型別資訊。這個辦法就是採用指標或引用。

7.3.為什麼要用指標或引用來實現動態多型

對於乙個物件來說無論有多少個指標指向它,這些個指標所指的都是同乙個物件。(即使你用乙個void的指標指向乙個物件也是這樣的,不是嗎?)同理對於引用也一樣。

這究竟有多少深層次的意義呢?這裡的深層的意義是這樣的:子型別的資訊本來就在它本身中存在,所以我們用乙個基類的指標來指出它,這個子型別的資訊也會被找到,同理引用也是一樣的。c++正是利用了指標的這一特性。來做到動態多型的。注2現在讓我們來改寫outputshape函式為這樣:

void outputshape( shape& arg)//專門負責呼叫形狀的繪製自己的函式

現在我們的程式的輸出為:

連線各頂點

以圓心和半徑為依據畫弧

這樣的輸出才是我們真正的想要的。我們實現的這種真正想要的輸出就是動態多型的實質。

7.4.為什麼動態多型要用public繼承

在我們上面的**中,圓和多邊形都是從形狀公有繼承而來的。要是我們把圓的繼承改為私有或保護會怎麼樣呢?我們來試一試。哇,我們得到乙個編譯錯誤。這個錯誤的大致意思是說:「請不要用乙個私有的方法」。怎麼回事呢?

是這麼回事。它的意思是說下面這樣說不合理。

所有的形狀都可以畫出來,圓這種形狀是不能畫出來的。

這樣合理嗎?不合理。所以請在多型中使用公有繼承吧。

8.總結

多型的思想其實早在物件導向的程式設計出現之前就有了。比如c語言中的+運算子。這個運算子可以對兩個int型的變數求和,也可以對兩個char的變數求和,也可以對乙個int型乙個char型的兩個變數求和。加法運算的這種特性就是典型的多型。所以說多型的本質是同樣的用法在實現上卻是不同的。

9.附錄:

注1:嚴格地講返回值可以不同,但這種不同是有限制的。詳細情況請看有關協變的內容。

注2:c++會悄悄地在含有虛函式的類裡面加乙個指標。用這個指標來指向乙個**。這個**會包含每乙個虛函式的索引。用這個索引來找出相應的虛函式的入口位址。對於我們所舉的形狀的例子來說,c++會悄悄的做三個表,shape乙個,polygon乙個,circ乙個。它們分別記錄乙個 drawself函式的入口位址。在程式執行的過程中,c++會先通過類中的那個指標來找到這個**。再從這個**中查出drawself的入口位址。然後現通過這個入口位址來呼叫正直的drawself。正是由於這個查詢的過程,是在執行時完成的。所以這樣的多型才會被叫做動態多型(執行時多型)

Day 71 虛函式,執行時多型

liunx下如何將結構體中的成員儲存的更緊湊一些 可以有兩種方式 1 將資料型別小的型別放在結構體的前面。2 使用 pragma pack 1 預處理命令將,儲存方式改為乙個位元組的儲存方式,這樣做雖然節省了空間,但效率卻有所降低。過載運算子 時不能定義在類中,要在類外定義 賦值運算子只能寫為類的成...

RTTI 執行時型別檢查 虛函式

在c 層面主要體現在dynamic cast和typeid,vs中虛函式表的 1位置存放了指向type info的指標。對於存在虛函式的型別,typeid和dynamic cast都會去查詢type info rtti即執行時型別識別,用來識別動態物件的型別。即使我們僅僅有基類的指標和引用,可以識別...

執行時 RunTime 的理解

首先是 什麼是執行時 oc是執行時語言,只有在程式執行時,才會去確定物件的型別,並呼叫類與物件相應的方法.利用runtime機制讓我們可以在程式執行時動態修改類.物件中的所有屬性,方法 執行時是oc訊息機制的平台,例如函式的呼叫,在編譯的時候並不能決定真正呼叫哪個函式,只有在真正執行時的時候才會根據...