1. 程式人生 > >C++面向物件多型的理解

C++面向物件多型的理解

多型與遞迴類似,不管是書中還是老師授課,都把其講得神乎其神,讓讀者一頭霧水,莫名其妙。多型實際上非常簡單,學習的難點在於在接觸多型之前,缺乏一個感性的認識。
多型允許將子類的物件當作基類的物件使用,某基型別的引用指向其子型別的物件,呼叫的方法是該子型別的方法。這裡引用和呼叫方法在程式碼編譯前就已經決定了,而引用所指向的物件可以在執行期間動態繫結。再舉個比較形象的例子:
比如有一個函式是叫某個人來吃飯,函式要求傳遞的引數是人的物件,可是來了一個美國人,你看到的可能是用刀和叉子在吃飯,而來了一箇中國人你看到的可能是用筷子在吃飯,這就體現出了同樣是一個方法,可以卻產生了不同的形態,這就是多型!

多型的意義:
增加了程式的可拓展性,實現了模組之間的解耦;
1. 應用程式不必為每一個派生類編寫功能呼叫,只需要對抽象基類進行處理即可。大大提高程式的可複用性。//繼承
2. 派生類的功能可以被基類的方法或引用變數所呼叫,這叫向後相容,可以提高可擴充性和可維護性。 //多型的真正作用,以前需要用switch實現

多型的表現(效果):
一個呼叫語句可以出現/實現多種形態.
比如,在發生多型時,如果傳入引數是一個子物件,那麼會呼叫子物件的成員方法,如果傳入引數是一個父物件,那麼會呼叫父物件的成員方法.

C++中多型的實現:
C++中多型的實現需要三個條件.
1. 存在繼承關係
2. 虛擬函式;虛擬函式對多型的實現是必要的,遇到這個處理過程要等到執行時再確定到底呼叫哪個類的處理過程;
3. 子類重寫基類虛擬函式,基類指標或引用指向子類物件.

C++面向物件三大特性分別是封裝,繼承,多型. 
封裝 站在類的物件作函式引數的角度思考.當類的物件作為函式的引數進行傳遞時,引數的顆粒度變大,一次性可以傳遞多個屬性和多個方法,突破了C語言的函式的概念. 
繼承 使子類可以使用基類的東西,增加了程式碼的可複用性. 
多型 使基類物件可以使用子類物件寫的程式碼,實現瞭解耦和;使之前的程式碼可以執行後來人寫的程式碼,增加了程式的可拓展性和可維護性.

C++實現多型原理:
C++編譯器為含有虛擬函式的類物件提前佈局vptr指標,生成虛擬函式表;當發生多型時(虛擬函式呼叫時),去虛擬函式表中查詢呼叫地址(函式的入口地址).
如果用面向過程的實現就是在介面中使用typeof判斷入參並使用switch語句處理不同的型別;而C++直接使用虛擬函式就可以實現動態繫結;

多型的分類(Java)
1、編譯時多型,即方法的過載,從JVM的角度來講,這是一種靜態分派(static dispatch)
2、執行時多型,即方法的重寫,從JVM的角度來講,這是一種動態分派(dynamic dispatch)

從使用角度看多型無非就是三句話:
比如我們有一個基類Basic並定義了虛方法,有一個子類SubClass也實現了自己的方法;
1、向上轉型是自動的。即Basic b = new Children()是自動的,不需要強轉;
2、向下轉型需要強轉。即SubClass s = new Basic()是無法編譯通過的,必須要SubClass s = (SubClass)new Basic(),讓基類知道它要轉成具體哪個子類
3、基類引用指向子類物件,子類重寫了基類的方法,在呼叫基類的方法時,實際呼叫的是子類重寫的基類的該方法。即Basic b = new SubClass(),b.toString()實際上呼叫的是SubClass中的toString()方法;

多型性往往用於面向物件中抽象和具體類的層次設計中,好處就在於提供系統的彈性,避免程式碼的僵化。
例如,可以增加一個新的子類而不需要修改原始碼,避免程式中的複雜的條件分析語句。也就是面向物件中的“對擴充套件開放、對修改封閉”的原則。


案例
// 多型
// 多型現象:同一個呼叫語句 可以有多種形態 扔過來一個子類物件,執行子類的API函式 扔過來一個基類物件,執行基類API函式
// 多型成立的三個條件:繼承、基類中定義了虛擬函式、子類重寫基類的虛擬函式 使用時:基類指標(引用)指向子類物件
// 效果:同樣一個呼叫語句可以有多種形態(多種呼叫方法)

 

#include <iostream>
using namespace std;

class BasicClass {
    public:
        BasicClass() { cout << "Basic Class init" <<endl; }
        virtual void work() { cout << "Basic Class working" <<endl; }
        virtual ~BasicClass() { cout << "Basic Class deinit" <<endl; }
};

class SubClass: public BasicClass {
    public:
        SubClass() { cout << "\tSub Class init" <<endl; }
        virtual void work() { cout << "\tSub Class working" <<endl; }
        virtual ~SubClass() { cout << "\tSub Class deinit" <<endl; }
};

class SubSubClass: public SubClass {
    public:
        SubSubClass() { cout << "\t\tSub-Sub Class init" <<endl; }
        virtual void work() { cout << "\t\tSub-Sub Class working" <<endl; }
        ~SubSubClass() { cout << "\t\tSub-Sub Class deinit" <<endl; }
};

/* 當呼叫how2Work()函式時表現多型性,根據入參不同調用不同的方法 */
/* 如果父類沒有宣告為virtual即沒有實現多型,傳入子類物件時就不會呼叫子類的方法 */
void how2Work(BasicClass *base)
{
    base->work();
}

int main(void)
{
#ifdef CASE1
    BasicClass *person = NULL;
    person = new BasicClass();
    person->work();
    delete person;
    person = NULL;

    person = new SubClass();
    person->work();
    delete person;
    person = NULL;

    person = new SubSubClass();
    person->work();
    delete person;
    person = NULL;
    /* 雖然都是person->work()結果卻有不同的表現 */
#else
    /* 多型的使用常見的是下面這種方式 */
    BasicClass p1;
    SubClass c1;
    SubSubClass cc1;

    how2Work(&p1);
    how2Work(&c1);
    how2Work(&cc1);
#endif

    return 0;
}