多型性之編譯期多型和執行期多型 C 版

2021-07-06 10:59:06 字數 3177 閱讀 3490

c++中最為經典的就是多型性,多型性充分體現了物件導向的思想,並且是c++與c的最大區別之一。多型性分為編譯期多型和執行期多型,也稱為靜態多型和動態多型,有些人也稱其為編譯時多型和執行時多型,不管什麼稱呼,萬變不離其宗,乙個是編譯期的靜態的多型,乙個是執行期的動態的多型,那麼它們在c++中分別體現在**呢?又有什麼區別呢?下面將詳細介紹(重點是執行期多型)。

編譯期多型,正如其名,就是在編譯期確定的一種多型性。這個在c++中主要體現在函式模板,這裡需要注意的是函式過載和多型無關,很多地方把函式過載也誤認為是編譯期多型,這是錯誤的。

那麼函式模板是如何體現編譯期多型的呢?下面舉乙個簡單的例子就可以明白。

// 例1:函式模板體現出編譯期多型

#include template t add(t a, t b)

int main()

從例1中可以看到,我們定義了乙個函式模板add,用來求兩個數的和,這兩個數的資料型別在使用時才知道。main函式中使用了兩個int值的求和以及兩個double值的求和,這裡就體現了多型性,即在編譯期,編譯器根據一定的最佳匹配演算法確定函式模板的引數型別到底是什麼,這就體現了編譯期的多種狀態。

當說到多型性的時候一般都預設指執行期多型,所以編譯期多型大家只要知道是如何表現的就可以了,下面重點來討論執行期多型。    

執行期多型主要是指在程式執行的時候,動態繫結所呼叫的函式,動態地找到了呼叫函式的入口位址,從而確定到底呼叫哪個函式。在c++中,執行期多型主要通過虛函式來實現,並且一定要有繼承關係,下面舉乙個簡單的例子來講解。

// 例2:虛函式和繼承關係體現執行期多型

#include class parent

// 父類的虛函式

virtual void eat()

// 注意這個並不是虛函式!!!

void drink() };

class child : public parent

// 子類重寫了父類的虛函式

void eat()

// 子類覆蓋了父類的函式,注意由於父類的這個函式

// 並不是虛函式,所以不存在繼承後重寫的說法

void drink()

// 子類特有的函式

void childlove() };

int main()

執行結果:

child eat.

parent drink.

例2寫得比較完善,說明了很多問題,我們先來看主要的,即多型性的體現。注意,在c++中只能用指標或引用來實現多型,不能通過普通的物件來實現多型,例2中使用的是指標的形式。我們來仔細分析一下。

第一,在我們的父類即parent中定義了乙個虛函式(virtual關鍵字修飾的函式)---eat(),既然是虛函式,那麼我們的子類就可以重寫這個函式(注意這裡強調是重寫,而不是過載,也不是覆蓋,重要的事情說三遍,是「重寫!重寫!重寫!」)。我們的子類child中重寫了eat()函式,至於父類和子類中的其他函式暫且先忽略,後面再講解,這裡只關注多型性相關的點。然後,在main函式中,我們定義了乙個父類的指標parent,但是注意,我們雖然定義的是父類的指標,但是我們指向的是子類物件,即new的是child物件,這裡涉及到向上轉型,即將子類物件向上轉型到了父類的指標所指。總之,一句話來說就是定義乙個父類指標,指向子類物件,然後我們用父類的這個指標去呼叫eat()函式,這裡就是多型發生的地方。從執行結果可以看到,實際上呼叫的是子類的eat()函式,並不是父類的eat()函式,這是因為虛函式,父類定義的是虛函式,而子類重寫了這個函式,雖然我們定義的是父類指標,但是實際上指向的是子類物件,那麼在執行期間,就會找到動態繫結到父類指標上的物件就是子類物件,然後實際上執行期間就是找到了子類物件中eat()函式的入口位址,然後呼叫了子類的eat()函式,這就是執行期多型。說了這麼多,聽起來十分繞口,貌似很難理解,但是大家仔細想想,總結成一句話就是:「定義父類指標並指向子類物件,此時用父類指標去呼叫乙個特殊的函式,即父類中該函式是虛函式,而子類重寫了這個虛函式,此時呼叫的這個函式就在執行期間動態地繫結到了指標實際所指的物件,即子類物件,從而去呼叫子類中的這個函式」。

第二,說完了多型,我們來看看例2中其他需要注意的地方。我們發現在main函式中pa指標還呼叫了drink()函式,最終的執行結果顯然呼叫的是父類的drink()函式,那麼這裡為什麼沒有多型呢?原因很簡單,因為drink()函式不是虛函式,所以根本不存在多型這一特性,雖然父類和子類中都有drink()這個函式,但是子類僅僅是覆蓋或者說隱藏了父類的drink()函式,並不是重寫。而我們的指標是父類指標,所以必定要去呼叫父類的drink()函式。

第三,我們來看看最後一點。細心的朋友會發現,在子類child中,有乙個只有它有而父類沒有的函式,即childlove()函式,該函式是子類特有的,和父類沒有任何關係,所以在main函式中用pa指標去呼叫這個函式會出錯,因為父類指標根本訪問不到子類的這個函式。這也是多型性的乙個缺陷,即父類的指標只能訪問子類中重寫了父類中的那些虛函式,而不能訪問子類新增的特有的函式。

最後我們來看乙個純虛函式實現多型性的例子。

// 例3:純虛函式和繼承關係體現執行期多型

#include // 父類因為包含純虛函式,所以該類是抽象類,即不能定義物件

class parent

// 父類的純虛函式

virtual void eat() = 0;

};class child : public parent

// 子類重寫了父類的純虛函式

void eat() };

int main()

執行結果:

child eat.

例3和例2基本上是一樣的,省略了很多函式,這裡多型性的體現和例2也是完全一樣的,之所以舉這個例子是為了解釋下抽象類用來實現多型的乙個理解誤區。從例3中就可以看到,純虛函式就是乙個函式後面加上"=0",而沒有任何實現,那麼包含純虛函式的類就自然成為了抽象類,而抽象類是不能定義物件的,這也就是為什麼main函式中的第一行會出錯。注意,繼承抽象類的具體類必須實現抽象類中的純虛函式,否則也會出錯。這裡想強調乙個誤區,很多人覺得既然抽象類不能定義物件,那麼main函式中的parent *pa為什麼沒有出錯,這是因為抽象類雖然不能定義物件,但是可以定義指標,用來指向具體類,從而實現多型,一定要分清楚c++中的指標、引用、物件三者之間的關係,不要一概而論。

編譯期多型和執行期多型

所謂的多型是通過乙個單一的識別符號支援不同的特定行為的能力。靜態多型 編譯期多型 動態多型 執行期多型 虛函式過載 模板 轉換 型別別名 今天我們就只討論從繫結時間來分的多型種類,即編譯期多型和執行期多型。執行期多型可以說只要學了c 的人都是知道的。因為執行期多型就是我通俗所說的多型,它的提出可以歸...

編譯期多型和執行期多型

執行期多型 runtime polymorphism 也稱為顯式多型,是指類中成員函式是virtual,類將對這些函式表現出執行期多型,也就是說將於執行期根據基類指標或者引用的動態型別決定究竟呼叫哪乙個函式。編譯期多型 cmpile time polymorphisms 也稱為隱式多型,是指以不同的...

C 的執行期多型和編譯期多型

今日的c 不再是個單純的 帶類的c 語言,它已經發展成為乙個多種次語言所組成的語言集合,其中泛型程式設計與基於它的stl是c 發展中最為出彩的那部分。在物件導向c 程式設計中,多型是oo三大特性之一,這種多型稱為執行期多型,也稱為動態多型 在泛型程式設計中,多型基於template 模板 的具現化與...