c 虛函式 一

2021-08-25 07:49:42 字數 2329 閱讀 9224

虛函式是c++中用於實現多型(polymorphism)的機制。核心理念就是通過基類訪問派生類定義的函式。假設我們有下面的類層次:

class a

};class b: public a

};那麼,在使用的時候,我們可以:

a * a = new b();

a->foo(); // 在這裡,a雖然是指向a的指標,但是被呼叫的函式(foo)卻是b的!

這個例子是虛函式的乙個典型應用,通過這個例子,也許你就對虛函式有了一些概念。它虛就虛在所謂「推遲聯編」或者「動態聯編」上,乙個類函式的呼叫並不是在編譯時刻被確定的,而是在執行時刻被確定的。由於編寫**的時候並不能確定被呼叫的是基類的函式還是哪個派生類的函式,所以被成為「虛」函式。

虛函式只能借助於指標或者引用來達到多型的效果,如果是下面這樣的**,則雖然是虛函式,但它不是多型的:

class a

;class b: public a

;void bar()

1.1 多型

在了解了虛函式的意思之後,再考慮什麼是多型就很容易了。仍然針對上面的類層次,但是使用的方法變的複雜了一些:

void bar(a * a)

因為foo()是個虛函式,所以在bar這個函式中,只根據這段**,無從確定這裡被呼叫的是a::foo()還是b::foo(),但是可以肯定的說:如果a指向的是a類的例項,則a::foo()被呼叫,如果a指向的是b類的例項,則b::foo()被呼叫。

這種同一**可以產生不同效果的特點,被稱為「多型」。

1.2 多型有什麼用?

多型這麼神奇,但是能用來做什麼呢?這個命題我難以用一兩句話概括,一般的c++教程(或者其它物件導向語言的教程)都用乙個畫圖的例子來展示多型的用途,我就不再重複這個例子了,如果你不知道這個例子,隨便找本書應該都有介紹。我試圖從乙個抽象的角度描述一下,回頭再結合那個畫圖的例子,也許你就更容易理解。

在物件導向的程式設計中,首先會針對資料進行抽象(確定基類)和繼承(確定派生類),構成類層次。這個類層次的使用者在使用它們的時候,如果仍然在需要基類的時候寫針對基類的**,在需要派生類的時候寫針對派生類的**,就等於類層次完全暴露在使用者面前。如果這個類層次有任何的改變(增加了新類),都需要使用者「知道」(針對新類寫**)。這樣就增加了類層次與其使用者之間的耦合,有人把這種情況列為程式中的「bad smell」之一。

多型可以使程式設計師脫離這種窘境。再回頭看看1.1中的例子,bar()作為a-b這個類層次的使用者,它並不知道這個類層次中有多少個類,每個類都叫什麼,但是一樣可以很好的工作,當有乙個c類從a類派生出來後,bar()也不需要「知道」(修改)。這完全歸功於多型--編譯器針對虛函式產生了可以在執行時刻確定被呼叫函式的**。

1.3 如何「動態聯編」

編譯器是如何針對虛函式產生可以再執行時刻確定被呼叫函式的**呢?也就是說,虛函式實際上是如何被編譯器處理的呢?lippman在深度探索c++物件模型[1]中的不同章節講到了幾種方式,這裡把「標準的」方式簡單介紹一下。

我所說的「標準」方式,也就是所謂的「vtable」機制。編譯器發現乙個類中有被宣告為virtual的函式,就會為其搞乙個虛函式表,也就是vtable。vtable實際上是乙個函式指標的陣列,每個虛函式占用這個陣列的乙個slot。乙個類只有乙個vtable,不管它有多少個例項。派生類有自己的vtable,但是派生類的vtable與基類的vtable有相同的函式排列順序,同名的虛函式被放在兩個陣列的相同位置上。在建立類例項的時候,編譯器還會在每個例項的記憶體布局中增加乙個vptr欄位,該欄位指向本類的vtable。通過這些手段,編譯器在看到乙個虛函式呼叫的時候,就會將這個呼叫改寫,針對1.1中的例子:

void bar(a * a)

會被改寫為:

void bar(a * a)

因為派生類和基類的foo()函式具有相同的vtable索引,而他們的vptr又指向不同的vtable,因此通過這樣的方法可以在執行時刻決定呼叫哪個foo()函式。

雖然實際情況遠非這麼簡單,但是基本原理大致如此。

1.4 overload和override

虛函式總是在派生類中被改寫,這種改寫被稱為「override」。我經常混淆「overload」和「override」這兩個單詞。但是隨著各類c++的書越來越多,後來的程式設計師也許不會再犯我犯過的錯誤了。但是我打算澄清一下:

override是指派生類重寫基類的虛函式,就象我們前面b類中重寫了a類中的foo()函式。重寫的函式必須有一致的參數列和返回值(c++標準允許返回值不同的情況,這個我會在「語法」部分簡單介紹,但是很少編譯器支援這個feature)。這個單詞好象一直沒有什麼合適的中文詞彙來對應,有人譯為「覆蓋」,還貼切一些。

overload約定成俗的被翻譯為「過載」。是指編寫乙個與已有函式同名但是參數列不同的函式。例如乙個函式即可以接受整型數作為引數,也可以接受浮點數作為引數。

C 之虛函式(一)純虛函式詳解

有時在基類中將某一成員函式定為虛函式,並不是基類本身的要求,而是考慮到派生類的需要,在基類中預留了乙個函式名,具體功能留給派生類根據需要去定義。例如在前邊的例12.1 詳情請檢視 什麼是c 虛函式 程式中,基類point中沒有求面積的area函式,因為 點 是沒有面積的,也就是說,基類本身不需要這個...

C 虛函式 純虛函式

1 基本概念 虛函式是在基類中使用關鍵字virtual宣告的函式。在派生類中重新定義基類中定義的虛函式時,會告訴編譯器不要靜態鏈結到該函式。我們想要的是在程式中任意點可以根據所呼叫的物件型別來選擇呼叫的函式,這種操作被稱為動態鏈結,或後期繫結。您可能想要在基類中定義虛函式,以便在派生類中重新定義該函...

C 虛函式 純虛函式

1.析構函式是否應為虛函式問題?2.成員函式的虛函式問題?3.析構函式是否可以為純虛函式問題?說明 僅在使用父類指標指向子類物件時有區別 當析構函式非虛函式時,使用父類指標指向子類物件,在析構時將不會呼叫子類析構函式 當析構函式是虛函式時,使用分類指標指向子類物件,在析構時會呼叫子類析構函式,且呼叫...