多型按字面的意思就是多種形態。當類之間存在層次結構,並且類之間是通過繼承關聯時,就會用到多型。
c++ 多型意味著呼叫成員函式時,會根據呼叫函式的物件的型別來執行不同的函式。
1、什麼是多型;
2、多型有什麼用;
3、多型的原理是什麼;
4、如何實現多型;
5、c++的純虛函式
(2)多型是物件導向的重要技術之一,它是一種行為的封裝,是同乙個事物所表現出來的多種形態。簡單地說就是:乙個介面、多種形態。
c++ 中的虛函式的作用主要是實現了多型的機制。關於多型,說白了就是用父型別別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式(當然引用也可以達到該目的)。這種技術可以讓父類的指標有「多種形態」,這是一種泛型技術。所謂泛型技術,說白了就是試圖使用不變的**來實現可變的演算法。比如:模板技術,rtti技術,虛函式技術。要麼是試圖做到在編譯時決議,要麼試圖做到執行時決議。(rtti:run time type identification,執行時型別識別:指程式能夠使用基類的指標或引用來檢索其所指物件的實際派生型別)
(3)多型與非多型的實質區別:就是函式位址是早繫結還是晚繫結。如果函式的呼叫,在編譯器編譯期間就可以確定函式的呼叫位址,並生產**,是靜態的,就是說位址是早繫結的。而如果函式呼叫的位址不能在編譯器期間確定,需要在執行時才確定,這就屬於晚繫結。
封裝可以使得**模組化,繼承可以擴充套件已存在的**,他們的目的都是為了**重用。而多型的目的則是為了介面重用。也就是說,不論傳遞過來的究竟是那個類的物件,函式都能夠通過同乙個介面呼叫到適應各自物件的實現方法。
(1)多型技術允許將基類指標或基類引用指向派生類物件。
(2)把不同派生類的物件都當作基類物件來看待,可以遮蔽不同派生類之間的差異,從而寫出通用的**以適應需求的不斷變化。
這裡主要需要弄清楚幾個概念:虛函式、純虛函式、抽象類、抽象基類
虛函式與純虛函式的區別與聯絡?
(1)對成員函式的呼叫是通過物件名還是通過基類指標或是基類引用去訪問。如果是後兩者,則應當宣告為虛函式。
(2)虛函式:如果乙個類中定義了虛函式virtual,那麼這個虛函式是被實現的,其作用就是為了讓該虛函式在這個類的的派生類中被覆蓋,被實現為不同的功能,從而結合基類指標以實現動態多型性。
(3)純虛函式:有時在定義乙個虛函式時,並不定義其函式體,即它的函式體是空的,它的作用只是保留乙個虛函式名,它關注的是介面的統一性,其具體的功能實現由它的派生類完成。比如:virtual float area(float a,float b ) = 0;
(4)抽象類與抽象基類:不用來定義物件而只作為一種基本型別用作被繼承的類,稱為抽象類;由於它經常用來作基類,故又被稱之為抽象基類。凡是包含純虛函式的類,都是抽象類,這種類不能直接生成物件(例項),它的作用就是作為乙個類族的共同基類,或者說是為乙個類族提供乙個公共介面。
(5)需要說明的是,使用虛函式,系統要有一定的空間開銷。當乙個類中含有虛函式時,編譯系統會為它構造乙個虛函式指標vptr(4位元組),同時這個虛函式指標指向乙個虛函式表vtable,虛函式表是乙個指標陣列,存放的是該類中的每個虛函式的入口位址。(查表是高效的,因此多型性是高效的。)
c++多型性是通過虛函式來實現的,虛函式允許子類重新定義成員函式,而子類重新定義父類的做法稱為覆蓋(override),或者稱為重寫。(這裡我覺得要補充,重寫的話可以有兩種,直接重寫成員函式和重寫虛函式,只有重寫了虛函式的才能算作是體現了c++多型性)
而過載則是允許有多個同名的函式,而這些函式的引數列表不同,允許引數個數不同,引數型別不同,或者兩者都不同。編譯器會根據這些函式的不同列表,將同名的函式的名稱做修飾,從而生成一些不同名稱的預處理函式,來實現同名函式呼叫時的過載問題。但這並沒有體現多型性。
c++支援兩種多型性:編譯時多型性,執行時多型性。
多型與非多型的實質區別就是:函式位址是早繫結(early binding)還是晚繫結(late binding)。如果函式的呼叫,在編譯器編譯期間就可以確定函式的呼叫位址,並生產**,是靜態的,就是說位址是早繫結的。而如果函式呼叫的位址不能在編譯器期間確定,需要在執行時才確定,這就屬於晚繫結。
(1)在c++中,基類指標是用來指向基類物件的,如果用它來指向派生類物件,則進行指標型別轉換(上行轉換),將派生類指標轉換為基類指標,所以該指標將會指向派生類物件中的基類部分,通過該指標是無法呼叫派生類物件中的成員函式的。但是,虛函式突破了這一限制。在派生類的基類部分中,派生類的虛函式取代了基類原來的同名虛函式,因此在使基類指標指向派生類物件後,使用該基類指標呼叫這個同名虛函式成員時就呼叫了派生類的虛函式。
(2)當把基類的某個成員函式宣告為虛函式時,c++允許在其派生類中對該虛函式進行重新定義,賦予它新的功能,並且可以通過基類指標指向同一類族的不同派生類的物件,來呼叫相應派生類中的該同名虛函式。由虛函式實現的動態多型性就是:同一類族中不同的派生類物件,對同一函式呼叫作出不同的響應。
(3)虛函式的使用方法如下:
(4)c++規定,當乙個成員函式被定義為虛函式後,其派生類中的同名函式都自動成為虛函式(而不一定要有關鍵字virtual顯示宣告了),但是為了清晰,習慣上每一層都加上virtual關鍵字。
(1)當派生類的物件從記憶體中撤銷時,一般先呼叫派生類的析構函式釋放該物件中的派生類部分,再呼叫基類的析構函式釋放該物件中的基類部分,從而能夠完整的釋放該物件記憶體。
(2)但是,當用基類指標指向了乙個派生類物件,即 base *ptrb = new child;此時用delete ptrb;來撤銷 ptrb 指向的動態儲存空間時,只會執行基類的析構函式來釋放該堆記憶體中的基類部分,但是並不會執行派生類的析構函式來釋放該堆記憶體中的派生類部分。此時,就會造成記憶體洩漏現象。
(3)為了避免此類現象發生,我們將基類的析構函式宣告為虛析構函式,這樣就解決了上述問題(即先呼叫派生類的析構函式釋放該動態空間中的派生類部分,再呼叫基類的析構函式釋放該動態空間中的基類部分,從而能夠完整的釋放該堆記憶體)。
(4)如果將基類的析構函式宣告為虛析構函式,那麼該基類的所有派生類的析構函式都自動成為虛析構函式。
注意,當乙個類作為基類時,其析構函式一定要宣告為虛函式,避免派生類申請的記憶體不能被釋放的問題出現!!
附:關於多型的筆試題目:
#includeusing namespace std;
class a
virtual void fun() };
class b : public a
void fun() };
int main(void)
第乙個p->foo()和p->fuu()都很好理解,本身是基類指標,指向的又是基類物件,呼叫的都是基類本身的函式,因此輸出結果就是1、2。
第二個輸出結果就是1、4。p->foo()和p->fuu()則是基類指標指向子類物件,正式體現多型的用法,p->foo()由於指標是個基類指標,指向是乙個固定偏移量的函式,因此此時指向的就只能是基類的foo()函式的**了,因此輸出的結果還是1。而p->fun()指標是基類指標,指向的fun是乙個虛函式,由於每個虛函式都有乙個虛函式列表,此時p呼叫fun()並不是直接呼叫函式,而是通過虛函式列表找到相應的函式的位址,因此根據指向的物件不同,函式位址也將不同,這裡將找到對應的子類的fun()函式的位址,因此輸出的結果也會是子類的結果4。
筆試的題目中還有乙個另類測試方法。即
b *ptr = (b *)&a; ptr->foo(); ptr->fun();
問這兩呼叫的輸出結果。這是乙個用子類的指標去指向乙個強制轉換為子類位址的基類物件。結果,這兩句呼叫的輸出結果是3,2。
並不是很理解這種用法,從原理上來解釋,由於b是子類指標,雖然被賦予了基類物件位址,但是ptr->foo()在呼叫的時候,由於位址偏移量固定,偏移量是子類物件的偏移量,於是即使在指向了乙個基類物件的情況下,還是呼叫到了子類的函式,雖然可能從始到終都沒有子類物件的例項化出現。
而ptr->fun()的呼叫,可能還是因為c++多型性的原因,由於指向的是乙個基類物件,通過虛函式列表的引用,找到了基類中fun()函式的位址,因此呼叫了基類的函式。由此可見多型性的強大,可以適應各種變化,不論指標是基類的還是子類的,都能找到正確的實現方法。
c 各種多型機制的一些總結
1.對類層次中的同名成員函式來說有三種關係 overload,override,hide 過載overload 同一類定義的成員函式才有的關係,返回型別,形參型別或個數不同,但是不能只返回值不同。屬於靜態多型。重寫override 通過虛函式實現,子類覆蓋基類的虛函式。隱藏hide 不通過虛函式實現...
一些關於C語言的總結
看到一哥們寫的筆記,感覺不錯就拿過來了。本想加到網摘裡邊,可搗騰了半天就沒有發現加網摘的鏈結,最後還是寫到自己的部落格中吧!總結1 char ch 12345 char ch 12345 char ch puts printf s char ch 必須有單引號 scanf n a gets a 空格...
關於C語言的一些總結
最近看了一些書籍,總結一下程式設計師容易忽略的程式設計細節吧,對面試還是考試有幫助的,不斷更新中。1.sizeof 想必大家都知道這個關鍵字吧,不是函式哦,凡是在c c 編輯器了有和其它關鍵字有相同顏色的單詞都是關鍵字,這是乙個計算型別或者變數在記憶體中的佔位大小,當是型別時如sizeof int ...