第二十二篇:C++中的多型機制

穆晨發表於2017-01-26

前言

       封裝性,繼承性,多型性是面嚮物件語言的三大特性。其中封裝,繼承好理解,而多型的概念讓許多初學者感到困惑。本文將講述C++中多型的概念以及多型的實現機制。

什麼是多型?

       多型就是多種形態,就是許多情況下可以互換地使用基型別和派生型別的多種形態。

多型的實現

       依賴於動態繫結機制。

動態繫結機制相關

       動態繫結是函式實際引數和形式引數繫結的一種方式,它是指我們能夠在函式介面中使用繼承層次中任意型別的物件,無需關心物件的具體型別。

       動態執行介面函式的物件引數的哪個函式得在程式實際執行的時候才能確定

       C++中預設不使用動態繫結,要觸發動態繫結必須滿足兩個條件:

       1. 介面函式的形式引數必須是引用型別或者指標型別。

       2. 動態執行函式(物件引數的成員函式而非介面函式)必須是宣告為虛成員函式。

程式碼例項一

       下面程式碼建立基類物件a,然後建立其派生物件b,當將a,b作為引數傳入函式printNum()後,函式讓它們分別呼叫自己的函式getNum():

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 // 基類
 6 class A {
 7 public:
 8     A() {a=0;}
 9     // **需要動態執行的函式必須宣告為虛擬函式,滿足了上條件2。
10     virtual int getNum() {
11         return a;
12     }
13 private:
14     int a;
15 };
16 
17 // 派生類
18 class B : public A {
19 public:
20     B() {b=1;}
21     // 基類中已經宣告過為虛擬函式了不需要再次宣告
22     int getNum() {
23         return b;
24     }
25 private:
26     int b;
27 };
28 
29 // **介面函式形參宣告為引用型別,滿足了上條件1。
30 void printNum(A & a) {
31     // 當實參為基類物件則呼叫基類物件的getNum,實參為派生類物件則呼叫派生類物件的getNum。
32     cout << a.getNum() << endl;
33 }
34 
35 int main()
36 {
37     A a;
38     B b;
39 
40     printNum(a);
41     printNum(b);
42 
43     return 0;
44 }

       執行結果:

       

       可以觀察到順利實現了動態繫結,a b分別執行自己的getNum() 函式。

程式碼例項二

  下面程式碼同樣建立基類物件a,然後建立其派生物件b,當將a,b作為引數傳入函式printNum()後,函式讓它們分別呼叫自己的函式getNum()(但本例介面函式的形式引數改成了值型別 ):

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 // 基類
 6 class A {
 7 public:
 8     A() {a=0;}
 9     // **需要動態執行的函式必須宣告為虛擬函式,滿足了上條件2。
10     virtual int getNum() {
11         return a;
12     }
13 private:
14     int a;
15 };
16 
17 // 派生類
18 class B : public A {
19 public:
20     B() {b=1;}
21     // 基類中已經宣告過為虛擬函式了不需要再次宣告
22     int getNum() {
23         return b;
24     }
25 private:
26     int b;
27 };
28 
29 // **介面函式形參宣告為值型別,不滿足上條件1。
30 void printNum(A a) {
31     // 未有實現動態繫結,因此不論實參是何種型別,均執行基類的getNum()函式。 
32     cout << a.getNum() << endl;
33 }
34 
35 int main()
36 {
37     A a;
38     B b;
39 
40     printNum(a);
41     printNum(b);
42 
43     return 0;
44 }

       執行結果:

       

       可以觀察到沒有實現動態繫結,a b都執行a的getNum() 函式。這意味著,物件本身並不支援多型,它剛進入函式就被徹底地轉換成了形參型別。因此,實現多型要靠的是物件的指標或者引用,而不是物件本身。這也是《C++ Primer》一書中不斷強調的東西。

說明

       1. 派生類和基類的虛擬函式型別要一致,只有一種例外 --- 返回對基類型別的引用的虛擬函式。派生類中的虛擬函式可以返回基類函式所返回型別的派生類的引用。

       2. 基類和派生類的虛擬函式的預設實參要相同,不然會引起混淆。

小結

       封裝保證了類的重用( 安全方面 ),繼承實現了類的重用,多型則實現了介面的重用。這三個機制體現了C++程式碼的可重用性,反映了C++在處理大型程式的優勢。

相關文章