C 虛函式本質論

2021-08-28 23:10:46 字數 4070 閱讀 6454

13.2.1 多型性原理

1.多型性概念

對多型性最簡單的理解就是一種事物有多種形態。在物件導向設計中,多型性指的是向不同的物件傳送同一訊息時,會產生不同的動作(或行為、功能)。所謂的「向不同的物件傳送同一訊息」,其實就是指呼叫不同物件的某個函式;而「產生不同的動作」,指的是該函式會實現不同的功能。沒有實現多型性的程式語言不能稱為真正的物件導向語言。比如語句pa->f();,若c++實現了多型性時,則會根據pa所指向的類物件的不同而呼叫相同的函式f(在語句形式上是相同的),這就是「向不同的物件傳送同一訊息」,這裡的同一訊息指的是呼叫函式f(),然後程式根據函式f所在的類不同會實現不同的功能,這就是「產生不同的動作」。再比如a、b、c是同一年級而不同班級的三個學生,當聽到上課鈴聲時,他們會進入不同的教室去上課。a、b、c三個物件在收到同一訊息「上課鈴聲」時,執行了不同的動作,這就是多型性。至於多型性怎樣實現,根據程式語言而定。

2.動態型別與靜態型別

(1)靜態型別指的是物件(指標、引用)在宣告時的型別。這種型別在編譯階段就能確定,僅從表示式的字面形式就能夠確定其型別。靜態型別在程式執行階段不會改變。

(2)動態型別指的是當前物件(包括指標和引用)實際指向的型別。這種型別需要在執行階段才能確定,物件的動態型別可以更改。

(3)編譯階段與執行階段的區別:在編譯階段,系統只做靜態的語法檢查,即從表示式的字面形式(或語句形式)就能夠確定資訊。比如a* pa,則對於pa->f();,在編譯階段從表示式的語句形式是無法確定指標pa所指向的型別(動態型別)的,因為pa可指向不同的子類型別物件,這就需要等到執行階段才能確定。但pa自身的型別(即類a型別),在編譯時是可以確定的,就是靜態型別。

示例如下:

a pa=&mb;

這裡pa的靜態型別是a,pa的動態型別是mb的型別。pa的動態型別可以改變(只需重新指向另乙個物件即可),而靜態型別無法更改。pa的動態型別需要在程式執行階段才能確定,在編譯階段是無法確定的。

a* pa=new a();

此時pa的靜態型別和動態型別都是a*。

3.c++中的動態型別與靜態型別

(1)在c++的繼承關係中,指標或引用的動態型別與靜態型別可以不同,這是c++實現多型性的關鍵。比如class a{};class b:public a{};b mb;,則對於a pa=&mb;,此時pa的靜態型別為a,動態型別為b*,可見pa的靜態型別與動態型別並不相同。

(2)在繼承關係中,類物件(類指標和類引用除外)的靜態型別與動態型別總是相同的。因為在把子類物件賦給父類物件時(注意:父類物件不能賦給子類物件),會把子類的父類部分「切除」,然後把「切除」部分賦給父類物件,這時父類物件的型別相當於是子類被「切除」的父類型別(其實就是父類型別)。比如class a{};class b:public a{};b mb; a ma=mb;,這時ma的動態型別與子類mb被「切除」的父類型別ba 相同,即ma的動態型別就是型別ba,這與ma的靜態型別是相同的。

4.動態繫結與靜態繫結

(1)繫結(binding):確定呼叫的是哪個具體物件的行為。在本文中,繫結一般是指把某個函式名與某乙個類物件繫結在一起的行為。

(2)動態繫結:在程式執行時(執行階段)繫結到物件的動態型別的行為,其特性依賴於該物件的動態型別。動態繫結後的結果就是根據所繫結的動態型別,呼叫動態型別中的函式。動態繫結又被稱為後期繫結。

(3)靜態繫結:繫結到物件的靜態型別的行為。靜態繫結後的結果就是根據所繫結的靜態型別,呼叫靜態型別中的函式,該過程在編譯階段進行。靜態繫結又被稱為早期繫結。

示例如下:

class a}; class b:public a};

b mb; a pa=&mb; pa->f();

① 若函式f繫結到指標pa的動態型別(即動態繫結),則呼叫pa的動態型別中的函式f,此時pa的動態型別是b,因此pa->f();呼叫類b中的函式f。可見,動態繫結時呼叫哪乙個函式,是由指標所指向的動態型別決定的。

② 若函式f繫結到指標pa的靜態型別(即靜態繫結),則呼叫pa的靜態型別中的函式f,此時pa的靜態型別是a*,因此pa->f();呼叫類a中的函式f。

5.c++中的動態繫結與多型性、虛函式

(1)c++中的多型表示的是使用乙個公有的父類指標(或引用),定址出乙個子類物件。換句話說,就是「乙個介面,多個方法」。在非多型情形下,當宣告乙個父類指標指向子類物件時,這個父類指標只能訪問父類中的成員函式,不能訪問子類中特有的成員變數或函式(因為父類並不知道子類特有的成員)。c++多型性的實質就是為了實現「使用指向子類物件的父類指標(或引用)訪問子類中的成員函式,而不是父類中的成員函式」。比如,若類b和類c是類a的子類,且都有各自的成員函式f,則對於b mb; c mc; a* pa;,若pa=&mb;,那麼在多型情形下,pa->f()將呼叫類b中的成員函式f();同理若pa=&mc; ,那麼pa->f()將呼叫類c中的成員函式f;若沒有多型,則無論pa指向類a的哪個子物件,pa->f()都將呼叫類a中的函式f。這就是c++中的多型,即根據共有的父類指標pa,通過pa指向不同的子物件,可以呼叫不同子物件中的成員函式。

(2)c++使用動態繫結實現多型性,靜態繫結是不能實現多型性的。比如,若類b是類a的子類,且都有各自的成員函式f,則對於b mb; a* pa;,若pa=&mb;,要實現多型性就必須使pa->f()呼叫子類b中的函式f,而這種呼叫就是動態繫結,因此只有動態繫結才能實現多型性;若pa->f()的呼叫是靜態繫結,則無法呼叫子類b中的函式f。

(3)c++實現動態繫結:在c++的繼承關係中,動態繫結必須使用虛函式和父類型別的指標(或引用)來實現,這兩者缺一不可。

① 為什麼需要虛函式:在c++中,只有虛函式才有可能使用動態繫結,虛函式是實現動態繫結的條件之一,非虛函式全部使用靜態繫結。

② 為什麼需要指標或引用:在c++中,指標或引用的動態型別與靜態型別可以不同,但類物件的靜態型別與動態型別總是相同的,只有在靜態型別與動態型別不相同時,使用動態繫結才能實現多型性,因此使用類物件無法繫結到動態型別,即無法實現動態繫結,動態繫結必須使用指標或引用才能實現。

③ 為什麼必須是父類的指標或引用:在c++中,子類可以轉換為父類,但不存在從父類到子類的轉換,因此要實現動態繫結應使用父類型別的指標或引用。不管是通過動態繫結還是靜態繫結,把子類物件繫結到子類型別、把父類物件繫結到父類型別都沒有實際意義,而把父類物件繫結到子類型別是錯誤的,因此只有把子類型別動態繫結到父類型別才有意義。

示例如下:

class a};class b:public a};

b mb; a ma; a pa; b pb;

① 對於pa=&mb; pa->f(); :

 pa的靜態型別為a*,動態型別為b*,其靜態型別與動態型別是不同的。

 若函式f是虛函式,則通過動態繫結把函式f繫結到指標pa的動態型別,呼叫類b中的函式f。只有此情形才可實現多型性。

 若函式f是非虛函式,則通過靜態繫結把函式f繫結到指標pa的靜態型別,呼叫類a中的函式f。因為使用的是靜態繫結,所以未實現多型性。

 ② 對於pa=&ma; pa->f();:

 pa的靜態型別為a*,動態型別為a*,其靜態型別與動態型別是相同的。

 若函式f是虛函式,則通過動態繫結把函式f繫結到指標pa的動態型別,呼叫類a中的函式f。因為靜態型別與動態型別相同,所以未實現多型性。

 若函式f是非虛函式,則通過靜態繫結把函式f繫結到指標pa的靜態型別,呼叫類a中的函式f。因為使用的是靜態繫結,所以未實現多型性。

③ pb=&mb; pb->f();,原理同② 。

④ pb=&ma; 或mb=ma; ,錯誤,不存在從父類到子類的轉換。

⑤ ma=mb; ma.f(); mb.f(); ,此時不管函式f是虛函式還是非虛函式,對於類物件使用的都是靜態繫結,因此ma.f()中的函式f被繫結到ma的靜態型別,呼叫類a中的f函式。mb.f()中的f函式被繫結到mb的靜態型別,呼叫類b中的函式f。

⑥ 由以上可見,要實現多型性,必須要滿足兩個條件,即虛函式和父類型別的指標(或引用)。只有使用虛函式,才可以進行動態繫結;只有使用引用或指標,才能使靜態型別與動態型別不同。

(4)總結: c++中的多型就是「通過父類的引用(或指標)呼叫虛函式」,這時發生的是動態繫結,指標或引用的動態型別與靜態型別可以不同(因為引用(或指標)既可以指向父類物件,也可以指向子類物件),這是實現動態繫結的關鍵。用引用(或指標)呼叫虛函式時,被呼叫的是引用(或指標)所指物件的實際型別(動態型別)中所定義的虛函式。

以上內容摘自本人所作《c++語法詳解》一書,電子工業出版社出版

繼承本質論

1.引言 關於繼承,你是否駕熟就輕,關於繼承,你是否瞭如指掌。本文不討論繼承的基本概念,我們回歸本質,從編譯器執行的角度來揭示.net繼承中的執行本源,來發現子類物件是如何實現了對父類成員與方法的繼承,以最為簡陋的示例來揭示繼承的實質,闡述繼承機制是如何被執行的,這對於更好的理解繼承,是必要且必然的...

繼承本質論

原創作品,轉貼請註明作者和出處。關於繼承,你是否駕熟就輕,關於繼承,你是否瞭如指掌。本文不討論繼承的基本概念,我們回歸本質,從編譯器執行的角度來揭示.net繼承中的執行本源,來發現子類物件是如何實現了對父類成員與方法的繼承,以最為簡陋的示例來揭示繼承的實質,闡述繼承機制是如何被執行的,這對於更好的理...

指標本質論

指標本質論 1.指標是什麼?和一般變數有什麼區別?指標就是位址,和一般變數沒有本質區別,僅僅是它有自己的規則。int a 100 int p a printf d n a 100 printf p n p 0xbfa47858 a是乙個變數名,型別是int,值是100,a有自己的位址 a p是乙個變...