1. 程式人生 > >C++筆記(十一)——多型的概念和作用(深入理解)

C++筆記(十一)——多型的概念和作用(深入理解)

        多型是面向物件的重要特性,簡單點說:“一個介面,多種實現”,就是同一種事物表現出的多種形態。         程式設計其實就是一個將具體世界進行抽象化的過程,多型就是抽象化的一種體現,把一系列具體事物的共同點抽象出來, 再通過這個抽象的事物, 與不同的具體事物進行對話。         對不同類的物件發出相同的訊息將會有不同的行為。比如,你的老闆讓所有員工在九點鐘開始工作, 他只要在九點鐘的時候說:“開始工作”即可,而不需要對銷售人員說:“開始銷售工作”,對技術人員說:“開始技術工作”, 因為“員工”是一個抽象的事物, 只要是員工就可以開始工作,他知道這一點就行了。至於每個員工,當然會各司其職,做各自的工作。         多型允許將子類的物件當作父類的物件使用,某父型別的引用指向其子型別的物件,呼叫的方法是該子型別的方法。這裡引用和呼叫方法的程式碼編譯前就已經決定了,而引用所指向的物件可以在執行期間動態繫結。再舉個比較形象的例子:         比如有一個函式是叫某個人來吃飯,函式要求傳遞的引數是人的物件,可是來了一個美國人,你看到的可能是用刀和叉子在吃飯,而來了一箇中國人你看到的可能是用筷子在吃飯,這就體現出了同樣是一個方法,可以卻產生了不同的形態,這就是多型! 多型的作用:         1. 應用程式不必為每一個派生類編寫功能呼叫,只需要對抽象基類進行處理即可。大大提高程式的可複用性。//繼承         2. 派生類的功能可以被基類的方法或引用變數所呼叫,這叫向後相容,可以提高可擴充性和可維護性。 //多型的真正作用,以前需要用switch實現

        多型是面向物件程式設計和麵向過程程式設計的主要區別之一,何謂多型?記得在CSDN裡一篇論C++多型的文章裡有一名話:“龍生九子,子子不同”多型就是同一個處理手段可以用來處理多種不同的情況,在錢能老師的《C++程式設計教程》書中有這樣一個例子: 定義了一個小學生類

//[本文全部程式碼均用偽碼]
class Student
{
public:
    Student(){}
    ~Student(){}
    void 交學費(){}
    //......
};

裡面有一個 “交學費”的處理函式,因為大學生和小學生一些情況類似,我們從小學生類中派生出大學生類:

class AcadStudent:public Student
{
public:
    AcadStudent(){}
    ~ AcadStudent(){}
    void 交學費(){}
    //.......
};

我們知道,中學生交費和大學生交費情況是不同的,所以雖然我們在大學生中繼承了中學生的"交學費"操作,但我們不用,把它過載,定義大學生自己的交學費操作,這樣當我們定義了一個小學生,一個大學生後:

Student A;
AcadStudent B;

A.交學費(); 即呼叫小學生的,B.交學費();是呼叫大學生的,功能是實現了,但是你要意識到,可能情況不僅這兩種,可能N種如:小學生、初中生、高中生、研究生..... 它們都可以以Student[小學生類]為基類。

如果系統要求你在一群這樣的學生中,隨便抽出一位交納學費,你怎麼做?

:
//A為抽出來的要交學費的同學
{
    switch(typeof(A))
    {
        case 小學生:A.小學生:: 交學費 ();break;
        case 初中生:A.初學生:: 交學費 ();break;
        case 高中生:A.高學生:: 交學費 ();break;

        default:
        .............
    }
}

        首先,我們要在每個類中定義一個 typeof()用來識別它的型別,然後還要在程式中進行區別,這樣一來,雖然也行,但是,如果再增加型別則要改動switch,又走了面向過程的老路,而且想通過一個模組進行操作實現起來也有難度。所以C++中提供了多型,即能通過遲後聯編的技術實現動態的區分。         在基類的"交學費"前加個Virtual 用來告訴系統,遇到這個處理過程要等到執行時再確定到底呼叫哪個類的處理過程。這樣一來就可以:

void 通用的交學費操作 (Student &A)
{
    A.交學費();
}

        一下全部搞定,你再加新的型別我也不怕!!![具體的實現原理參考:《Inside The C++ Object Model》]。如果沒有 virtual這一宣告,那麼,系統在執行前就確定了操作,比如把“大學生”傳給

void 通用的交學費操作 (Student &A)
{
    A.交學費();
}

,則A.交學費();呼叫的是大學生類中繼承於Student類中的“交學費操作” 所以虛擬函式對多型的實現是必要的。

為什麼會出現純虛擬函式呢?

        如果按上面的例子進行程式設計,所有型別都繼承小學生類,我們會發現一此小學生自己特定的東東[比如 void 上美術課();],也都被大學生繼承來了,雖然不影響大學生的操作,但是隨時間的加長,小學生類中自已所特定的東東越來越多,這樣下去,大學生中冗餘的資料就多了,有什麼辦法可以解決????

        就是定義基類時定義一個抽象類,如學生類,在學生類中實現一此大家都有的操作。這個過程就叫分解。這個類子對純虛擬函式的說明還不夠明顯,換個例子比如:

class 人()
{
public :
    //......
    void 吃()
    {
        人吃飯;
    }
    //......
    char *Name;
    int age;
};

class 狗()
{
public :
    //......
    void 吃()
    {
        狗吃屎;
    }
    //......
    char *Name;
    int age;
};

        人類、狗類有一些相同的東東,如名字,年紀,吃的動作等,有人想到了為了程式碼的重用,讓人類繼承狗類,可是資料的冗餘讓這個想法完蛋了,所以有人又想出可不可以定義一個這樣的類:         這個類界於人類狗類之間,有人類和狗類共有的一些東東,比如年紀,名字,體重什麼的,但它是不存在例項的,它的存在意義也是隻是為了派生其它類,如人類、狗類,這樣可以使系統清淅、。。。。。、、反正好處多多。

        在這個世界上根本不存在界於人狗之間的東東,所以這個“人狗之間類”中的“吃”也是沒什麼意義,你也很難為它的內容下個定義,況且也沒必要,所以定義它為純虛擬函式,形式為:virtual void 吃()=0; 用來告訴系統: 1、這個函式可以沒有函式定義; 2、擁有本函式的類是抽象類; 你可能會說,即然純虛擬函式沒什麼意義,為什麼還要它,它的作用是什麼呢? 為實現多型作準備!!!

        由於抽象類的例項沒意義,所以C++中不允許定義它的例項。(如果定義了這樣的例項A,那麼你呼叫A.吃()怎麼辦?)當然了,你也可以在基類中,virtual 吃(){};這樣一來,基類就不是抽象類了,可以有例項,而且對於其它方面都不影響。

        但你也要承認這樣的物件是沒有什麼意識的,它的存在只能使你思考上增加負擔,除錯時還要考慮到是不是有這樣類的物件在作怪,所以C++乾脆提供了“虛擬函式”、抽象類,的機制,給我們操作時加了限制也可以認為是保險[不可以有抽象類的物件],試想當代碼變得非常之龐大時,它的存在是多麼有必要啊!!!