1. 程式人生 > >C++ 多型淺析

C++ 多型淺析

本文將圍繞下面四個問題,進行逐一闡述:

1、什麼是多型;2、多型有什麼用;3、多型的原理是什麼;4、如何實現多型。

 

1、什麼是多型?

(1)通過繼承同一個基類,產生了相關的不同的派生類,與基類中同名的成員函式在不同的派生類中會有不同的實現,也就是說:一個介面、多種方法。

(2)多型是面向物件的重要技術之一,它是一種行為的封裝,是同一個事物所表現出來的多種形態。簡單地說就是:一個介面、多種形態。

        C++ 中的虛擬函式的作用主要是實現了多型的機制。關於多型,說白了就是用父類型別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式(當然引用也可以達到該目的)。這種技術可以讓父類的指標有“多種形態”,這是一種泛型技術。所謂泛型技術,說白了就是試圖使用不變的程式碼來實現可變的演算法。比如:模板技術,RTTI技術,虛擬函式技術。要麼是試圖做到在編譯時決議,要麼試圖做到執行時決議。

 

2、多型有什麼用?
        封裝可以使得程式碼模組化,繼承可以擴充套件已存在的程式碼,他們的目的都是為了程式碼重用。而多型的目的則是為了介面重用。也就是說,不論傳遞過來的究竟是那個類的物件,函式都能夠通過同一個介面呼叫到適應各自物件的實現方法。

(1)多型技術允許將基類指標或基類引用指向派生類物件。

(2)把不同派生類的物件都當作基類物件來看待,可以遮蔽不同派生類之間的差異,從而寫出通用的程式碼以適應需求的不斷變化。

 

3、多型的原理是什麼?

這裡主要需要弄清楚幾個概念:虛擬函式、純虛擬函式、抽象類、抽象基類

虛擬函式與純虛擬函式的區別與聯絡?
(1)對成員函式的呼叫是通過物件名還是通過基類指標或是基類引用去訪問。如果是後兩者,則應當宣告為虛擬函式。

(2)虛擬函式:如果一個類中定義了虛擬函式virtual,那麼這個虛擬函式是被實現的,其作用就是為了讓該虛擬函式在這個類的的派生類中被覆蓋,被實現為不同的功能,從而結合基類指標以實現動態多型性。

(3)純虛擬函式:有時,在定義一個虛擬函式時,並不定義其函式體,即它的函式體是空的,它的作用只是保留一個虛擬函式名,它關注的是介面的統一性,其具體的功能實現由它的派生類完成。比如:virtual float area(float a,float b ) = 0;

  • 最後面的“=0”的作用僅僅只是告訴編譯器這是一個純虛擬函式。
  • 純虛擬函式只具有函式的名稱,沒有函式體,不具備函式的功能,因此不能被呼叫。只有在其派生類中被重新定義過之後才具備函式的功能,才能被呼叫。
  • 如果在一個類中聲明瞭純虛擬函式,但是在其派生類中該純虛擬函式並沒有被定義,那麼該虛擬函式在這個派生類中仍然為純虛擬函式,仍然不具備函式的功能。

(4)抽象類與抽象基類:不用來定義物件而只作為一種基本型別用作被繼承的類,稱為抽象類;由於它經常用來作基類,故又被稱之為抽象基類。凡是包含純虛擬函式的類,都是抽象類,這種類不能直接生成物件(例項),它的作用就是作為一個類族的共同基類,或者說是為一個類族提供一個公共介面。

(5)需要說明的是,使用虛擬函式,系統要有一定的空間開銷。當一個類中含有虛擬函式時,編譯系統會為它構造一個虛擬函式指標vptr(4位元組),同時這個虛擬函式指標指向一個虛擬函式表vtable,虛擬函式表是一個指標陣列,存放的是該類中的每個虛擬函式的入口地址。(查表是高效的,因此多型性是高效的。

 

4、如何實現多型?

C++多型性是通過虛擬函式來實現的,虛擬函式允許子類重新定義成員函式,而子類重新定義父類的做法稱為覆蓋(override),或者稱為重寫。(這裡我覺得要補充,重寫的話可以有兩種,直接重寫成員函式和重寫虛擬函式,只有重寫了虛擬函式的才能算作是體現了C++多型性)而過載則是允許有多個同名的函式,而這些函式的引數列表不同,允許引數個數不同,引數型別不同,或者兩者都不同。編譯器會根據這些函式的不同列表,將同名的函式的名稱做修飾,從而生成一些不同名稱的預處理函式,來實現同名函式呼叫時的過載問題。但這並沒有體現多型性。

C++支援兩種多型性:編譯時多型性,執行時多型性。

多型與非多型的實質區別就是:函式地址是早繫結(early binding)還是晚繫結(late binding)。如果函式的呼叫,在編譯器編譯期間就可以確定函式的呼叫地址,並生產程式碼,是靜態的,就是說地址是早繫結的。而如果函式呼叫的地址不能在編譯器期間確定,需要在執行時才確定,這就屬於晚繫結。

(1)在C++中,基類指標是用來指向基類物件的,如果用它來指向派生類物件,則進行指標型別轉換(上行轉換),將派生類指標轉換為基類指標,所以該指標將會指向派生類物件中的基類部分,通過該指標是無法呼叫派生類物件中的成員函式的。

但是,虛擬函式突破了這一限制。在派生類的基類部分中,派生類的虛擬函式取代了基類原來的同名虛擬函式,因此在使基類指標指向派生類物件後,使用該基類指標呼叫這個同名虛擬函式成員時就呼叫了派生類的虛擬函式。

(2)當把基類的某個成員函式宣告為虛擬函式時,C++允許在其派生類中對該虛擬函式進行重新定義,賦予它新的功能,並且可以通過基類指標指向同一類族的不同派生類的物件,來呼叫相應派生類中的該同名虛擬函式。

由虛擬函式實現的動態多型性就是:同一類族中不同的派生類物件,對同一函式呼叫作出不同的響應。

(3)虛擬函式的使用方法如下:

  • 在基類中使用virtual關鍵字宣告成員函式為虛擬函式(這樣就可以在派生類中對該虛擬函式進行重新定義,賦予它新的功能)。
  • 在派生類中重新定義此虛擬函式,要求函式名、形參列表、返回值型別均要與基類中的虛擬函式相同,並根據具體需要重新定義它的函式體。

(4)C++規定,當一個成員函式被定義為虛擬函式後,其派生類中的同名函式都自動成為虛擬函式(而不一定要有關鍵字virtual顯示聲明瞭),但是為了清晰,習慣上每一層都加上virtual關鍵字。

 

5、虛解構函式的作用(補充一點,正確釋放派生類的堆記憶體)

(1)當派生類的物件從記憶體中撤銷時,一般先呼叫派生類的解構函式釋放該物件中的派生類部分,再呼叫基類的解構函式釋放該物件中的基類部分,從而能夠完整的釋放該物件記憶體。

(2)但是,當用基類指標指向了一個派生類物件,即 base *ptrB = new child;此時用delete ptrB;來撤銷 ptrB 指向的動態儲存空間時,只會執行基類的解構函式來釋放該堆記憶體中的基類部分,但是並不會執行派生類的解構函式來釋放該堆記憶體中的派生類部分。此時,就會造成記憶體洩漏現象。

(3)為了避免此類現象發生,我們將基類的解構函式宣告為虛解構函式,這樣就解決了上述問題(即先呼叫派生類的解構函式釋放該動態空間中的派生類部分,再呼叫基類的解構函式釋放該動態空間中的基類部分,從而能夠完整的釋放該堆記憶體)。

(4)如果將基類的解構函式宣告為虛解構函式,那麼該基類的所有派生類的解構函式都自動成為虛解構函式。

--------------------------------------------------------------------------------------

以上內容,根據網上資料整理。