c 的多型和虛函式(一)

2021-05-26 14:07:35 字數 2564 閱讀 4667

前言:   

提到c++的多型,很容易就會想到通過繼承和虛函式,讓同一函式的呼叫表現出不同的結果。其實c++支援多種形式的多型,從繫結時間來看可以分成靜態多型和動態多型,也稱為編譯期多型和執行期多型。

其中,動態多型,通過繼承和虛函式機制實現;靜態多型,在c++中則主要體現為模板(類模板和函式模板)和過載(函式過載和運算子過載)。其實說到靜態多型,突然想到,在c裡面的巨集也可以看做是一種多型(個人觀點),還有我們熟悉的printf函式,雖然不是物件導向的多型。說到巨集,扯一句,突然知道的getchar和putchar以及的tolower等「函式」,居然一般都是巨集,很是震驚,居然為了避免函式呼叫的開銷,用巨集提高效率,不過不得不服啊,這些庫函式設計者的智慧型。另外,c++的內聯函式,感覺也是巨集的思想(會不會內聯是因為類內部不支援巨集而產生的呢?貌似不是)

一、向上轉換和位址

向上對映,官方定義是,取乙個物件的位址(或指標或引用),並看作基類的位址。由於繼承樹是以基類為頂點的,物件可以作為它自己的型別或它的基類的物件使用。另外,還能通過基類的位址被操作。

需要注意的就是向上對映,僅處理位址。純虛函式還能防止對純抽象類的函式以傳值方式呼叫。這樣,它也是防止物件意外使用值向上對映的一種方法。這樣就能保證在向上對映期間總是使用指標或引用。

為什麼傳位址而一般不是傳值呢,因為位址大小相同,哈哈,這樣傳基類和派生類位址就一樣了。說到底,多型的目的,還是是讓對基類物件操作的**也能操作派生類物件。值向上對映引發物件切片什麼的,就不說了,悲劇啊。

二、晚**(感覺這翻譯忒彆扭,late binding)

繫結,官方定義,是將函式體與函式呼叫相關聯。當繫結發生在程式執行前時,叫早繫結, 由編譯器和聯結器完成(c編譯只有一種函式呼叫)。晚繫結,高階貨,通過關鍵字v i r t u a l告訴編譯器,先別繫結。當然,編譯器得支援晚繫結機制了。

為了晚繫結呢,編譯器就對每個包含虛函式的類建立乙個表(稱為v ta b l e)。在v ta b l e中,編譯器放置特定類的虛函式位址。在每個帶有虛函式的類中,編譯器秘密地置一指標,稱為v p o i n t e r(縮寫為v p t r),指向這個物件的v ta b l e。通過基類指標做虛函式呼叫時(也就是做多型呼叫時),編譯器靜態地插入取得這個v p t r,並在v ta b l e表中查詢函式位址的**,這樣就能呼叫正確的函式使晚**發生。為每個類設定v ta b l e、初始化v p t r、為虛函式呼叫插入**,所有這些都是自動發生的,所以我們不必擔心這些。利用虛函式,這個物件的合適的函式就能被呼叫,哪怕在編譯器還不知道這個物件的特定型別的情況下。

當然,測試vptr的大小可以發現,為4(32位win7,vc6和mingw測試),這是因為v p t r指向乙個存放位址的表,只需要乙個指標。(其實我很好奇的是這個存位址的表存在**)

虛表什麼的,太長,下回再搞

三、虛函式與建構函式

首先,我們提出個問題,建構函式可以為虛函式嗎?

當建立乙個包含有虛函式的物件時,必須初始化它的v p t r以指向相應的v ta b l e。這必須在有關虛函式的任何呼叫之前完成。因為建構函式有使物件存在的工作,所以它也必須設定v p t r。編譯器在建構函式的開頭部分秘密地插入能初始化v p t r的**。事實上,即使我們沒有對乙個類建立建構函式,在有虛函式的情況下,編譯器也會為我們建立乙個帶有相應v p t r初始化**的建構函式。

如果我們正在建構函式中並且呼叫虛函式,那麼會發生什麼現象呢?

對於普通的成員函式,虛函式的呼叫是在執行時決定的,這是因為編譯時並不能知道這個物件是屬於這個成員函式所在的那個類,還是屬於由它派生出來的類。但是,對於在建構函式中呼叫乙個虛函式的情況,被呼叫的只是這個函式的本地版本

why?乙個原因是虛函式能呼叫在派生類中的函式,如果我們在建構函式中也這樣做,那麼我們所呼叫的函式可能操作還沒有被初始化的成員,這將導致災難的發生。另乙個原因是,當乙個建構函式被呼叫時,它做的首要的事情之一是初始化它的v p t r。

當編譯器為這個建構函式產生**時,它是為這個類的建構函式產生**- -既不是為基類,也不是為它的派生類(因為類不知道誰繼承它)。所以它使用的v p t r必須是對於這個類的v ta b l e。而且,只要它是最後的構造函式呼叫,那麼在這個物件的生命期內, v p t r將保持被初始化為指向這個v ta b l e。但如果接著還有乙個更晚派生的建構函式被呼叫,這個建構函式又將設定v p t r指向它的v ta b l e,等等,直到最後的建構函式結束(其實就是繼承過程中,構造函式呼叫順序的問題)。v p t r的狀態是由被最後呼叫的建構函式確定的,這也是為什麼構造函式呼叫是從基類到更加派生類順序的另乙個理由。

乙個問題就是,如果子類沒有重寫建構函式,那麼這個vptr指向的還是基類的vtable,這不是bug嗎

那,析構函式能為虛函式嗎? 

雖然析構函式象建構函式一樣,是「例外」函式,但析構函式可以是虛的,這是因為這個物件已經知道它是什麼型別(而在構造期間則不然)。一旦物件已被構造,它的v p t r就已被初始化了,所以虛函式呼叫能發生。雖然書上有云:但析構函式能夠且常常必須是虛的,不過目前還遇不到,暫且就不記了吧

目前先總結這些吧,大多是參考 c++程式設計思想,記錄以備不忘,希望有用。

c 多型和虛函式

c 有三大特性 封裝,繼承,多型 多型是物件導向程式設計的乙個重要特徵,多型就是乙個東西有多重狀態,具有不同功能的函式可以用乙個函式名,這樣就可以用乙個函式名實現不同的功能 靜態多型和動態多型靜態多型是利用過載實現的,在程式編譯時確定要呼叫的是哪個函式,也稱為編譯時多型。動態多型是利用虛函式實現的,...

c 虛函式和多型 虛函式表

參考 1 c primer plus 第六版 2 中國大學慕課 程式設計與演算法 三 在類的定義中,前面有virtual關鍵字的成員函式就是虛函式 class a 類外函式定義 void a function 說明 1 virtual關鍵字只需要在類定義裡的函式宣告時加上,函式定義時不用加 2 建構...

C 多型 虛函式和純虛函式的關係

c 多型意味著呼叫成員函式時,會根據呼叫函式的物件的型別來執行不同的函式 形成多型必須具備三個條件 1 必須存在繼承關係 2 繼承關係必須有同名虛函式 其中虛函式是在基類中使用關鍵字virtual宣告的函式,在派生類中重新定義基類中定義的虛函式時,會告訴編譯器不要靜態鏈結到該函式 3 存在基類型別的...