C 中的多型

2021-08-26 05:15:19 字數 3806 閱讀 2411

什麼是多型?

多型性(polymorphism)據說最早源自希臘語,從字面上理解就是多種形態,多種形式。具體到c++這種物件導向(oop)的語言中,其實就是「一種介面,多種實現(方法)」。

多型可分為靜態多型和動態多型,具體的分類情況如下:

靜態多型和動態多型的區別其實只是在什麼時候將函式實現和函式呼叫關聯起來,是在編譯時期還是執行時期,即函式位址是早繫結還是晚繫結的? 

***靜態多型***是指在編譯期間就可以確定函式的呼叫位址,並生產**,這就是靜態的,也就是說位址是早早繫結的,靜態多型也往往被叫做靜態聯編。

***動態多型***則是指函式呼叫的位址不能在編譯器期間確定,必須需要在執行時才確定,這就屬於晚繫結,動態多型也往往被叫做動態聯編。

靜態多型往往通過函式過載和模版(泛型程式設計)來實現,具體可見下面**:

//兩個函式構成過載

int add(int a, int b)

double add(double a, double b)

//函式模板(泛型程式設計)

template t>

//template t>

t add(t a, t b)

我們知道c++有封裝,繼承和多型等幾大特性,封裝可以使得**模組化,繼承可以在原有的**基礎上擴充套件,他們的目的都是為了**重用。而多型則是為了介面重用。也就是說,不論傳遞過來的究竟是哪個類的物件,函式都能夠通過同乙個介面呼叫到適應各自物件的實現方法。
在具體講多型之前,我們來看看下面的**:

class base 

};class derived : public base

};void funtest()

int main()

此時我們放開了子類中fun函式的注釋:

class

base

};class

derived : public base

};void funtest

()int main()

依舊保持上面的類定義不變,我們試試用指標來呼叫:

void funtest()

int main()

我們知道,c++繼承中有賦值相容,即基類指標可以指向子類,那麼為什麼還會出現基類指標指向子類或者基類物件引用子類物件,卻呼叫基類自己的fun函式列印base::fun()呢?這就是我們上面講的靜態聯編,在編譯時期就將函式實現和函式呼叫關聯起來,不管是引用還是指標在編譯時期都是base類的自然呼叫base類的fun()。為了避免這種情況,我們引入了動態多型。 

所謂的動態多型是通過繼承+虛函式來實現的,只有在程式執行期間(非編譯期)才能判斷所引用物件的實際型別,根據其實際型別呼叫相應的方法。具體格式就是使用virtual關鍵字修飾類的成員函式時,指明該函式為虛函式,並且派生類需要重新實現該成員函式,編譯器將實現動態繫結。

在上面的**如果我們在基類的fun函式前加virtual即可實現動態繫結:

class base 

};

其他不變,在funtest函式中就達到我們想要的效果:

void funtest()

int main()

需要注意的是:【動態繫結條件】

**1、必須是虛函式

2、通過基類型別的引用或者指標呼叫虛函式**

缺一不可!!!

另外補充一點:

我們知道c++中虛函式允許子類重新定義成員函式,而子類重新定義父類的做法稱為覆蓋(override),或者稱為重寫。在這裡我覺得有必要要明白幾個概念的區別:即過載,重寫(覆蓋),以及重定義(同名隱藏)。 

所謂過載是指在同一作用域中允許有多個同名函式,而這些函式的引數列表不同,包括引數個數不同,型別不同,次序不同,需要注意的是返回值相同與否並不影響是否過載。比如int fun()和void fun()不構成過載,連編譯都不過去,給出的提示是無法過載僅按返回型別區分的函式。

而重寫(覆蓋)和重定義(同名隱藏)則有點像,區別就是在寫重寫的函式是否是虛函式,只有重寫了虛函式的才能算作是體現了c++多型性,否則即為重定義,在之前的**中,我們看到子類繼承了基類的fun()函式,若是子類沒有fun函式,依舊會呼叫基類的fun函式,若是子類已重定義,則呼叫自己的fun函式,這就叫做同名隱藏,當然此時如果還想呼叫基類的fun函式,只需在呼叫fun函式前加基類和作用域限定符即可。綜上他們的關係和區別如下圖表明:

純虛函式在成員函式的形參後面寫上=0,則成員函式為純虛函式。包含純虛函式的類叫做抽象類(也叫介面類),抽象類不能例項化出物件。純虛函式必須在派生類中重新定義以後,派生類才能例項化出物件。

class person

;class student : public person

};int main()

抽象類往往用於這樣的情況,它可以方便我們使用多型特性,且在很多情況下,基類本身生成物件是不合情理的,我們知道所有的物件都是通過類來描繪的,但是反過來卻不是這樣。並不是所有的類都是用來描繪物件的,如果乙個類中沒有包含足夠的資訊來描繪乙個具體的物件,這樣的類就要使用抽象類,就像乙個水果類可以派生出橘子香蕉蘋果等等,但是水果類本身定義物件並不合理也沒有必要。
要點1、派生類重寫基類的虛函式實現多型,要求函式名、引數列表、返回值完全相同。(協變除外)

2、基類中定義了虛函式,在派生類中該函式始終保持虛函式的特性。

3、只有類的非靜態成員函式才能定義為虛函式,靜態成員函式和友元函式不能定義為虛函式。

4、如果在類外定義虛函式,只能在宣告函式時加virtual關鍵字,定義時不用加。

5、建構函式不能定義為虛函式,雖然可以將operator=定義為虛函式,但最好不要這麼做,使用時容

易混淆。

6、不要在建構函式和析構函式中呼叫虛函式,在建構函式和析構函式中,物件是不完整的,可能會

出現未定義的行為。

7、最好將基類的析構函式宣告為虛函式。(析構函式比較特殊,因為派生類的析構函式跟基類的析構

函式名稱不一樣,但是構成覆蓋,這裡編譯器做了特殊處理)。

8、虛表是所有類物件例項共用的。

最後看一組**:

class a

};class b

};int main()

我們知道乙個空類佔乙個位元組,那為什麼加了virtual關鍵字就變成了4?

這是因為每個有虛函式的類或者虛繼承的子類,編譯器都會為它生成乙個虛函式表(簡稱:虛表),表中的每乙個元素都指向乙個虛函式的位址。(注意:虛表是從屬於類的)

此外,編譯器會為包含虛函式的類加上乙個成員變數,是乙個指向該虛函式表的指標(常被稱為vptr),每乙個由此類別派生出來的類,都有這麼乙個vptr。虛表指標是從屬於物件的。也就是說,如果乙個類含有虛表,則該類的所有物件都會含有乙個虛表指標,並且該虛表指標指向同乙個虛表。因此這裡的4是指標的大小。

C 中的多型

封裝 繼承 多型,物件導向的三大特性,前兩項理解相對容易,但要理解多型,特別是深入的了解,對於初學者而言可能就會有一定困難了。我一直認為學習oo的最好方法就是結合實踐,封裝 繼承在實際工作中的應用隨處可見,但多型呢?也許未必,可能不經意間用到也不會把它跟 多型 這個詞對應起來。在此拋磚引玉,大家討論...

C 中的多型

c 中的多型分為靜多型和動多型 也就是靜態繫結和動態繫結兩種現象 靜動的區別主要在於這種繫結發生在編譯期還是執行期,發生在編譯期的是靜態繫結,也就是靜多型 發生在執行期的則是動態繫結,也就是動多型。一 靜多型可以通過模板和函式過載來實現,下面舉兩個例子 1 函式模板 template t max c...

C 中的多型

定義 同樣的訊息被不同型別的物件接收時產生不同的行為。原理 1.編譯時多型 靜態繫結 2.執行時多型 動態繫結 分類 1.過載 包括函式過載,運算子過載 靜態繫結 2.覆蓋 包括子類和父類間虛函式 虛析構函式和純虛函式 動態繫結 要求 1.函式名相同 2.引數不同 栗子 include using ...