1. 程式人生 > >c++之多型性(動態繫結)

c++之多型性(動態繫結)

多型性 
指相同物件收到不同訊息或不同物件收到相同訊息時產生不同的實現動作。C++支援兩種多型性:編譯時多型性,執行時多型性。 
  a、編譯時多型性:通過過載函式實現 
  b、執行時多型性:通過虛擬函式實現
 

多型性可以簡單地概括為“一個介面,多種方法”,程式在執行時才決定呼叫的函式,它是面向物件程式設計領域的核心概念。多型(polymorphism),字面意思多種形狀。多型亦可以解釋為c++中的動態繫結.

C++多型是怎麼實現的?

C++多型性是通過虛擬函式來實現的,虛擬函式允許子類重新定義成員函式,而子類重新定義父類的做法稱為覆蓋(override),或者稱為重寫。(這裡我覺得要補充,重寫的話可以有兩種,直接重寫成員函式和重寫虛擬函式,只有重寫了虛擬函式的才能算作是體現了C++多型性)而過載則是允許有多個同名的函式,而這些函式的引數列表不同,允許引數個數不同,引數型別不同,或者兩者都不同。編譯器會根據這些函式的不同列表,將同名的函式的名稱做修飾,從而生成一些不同名稱的預處理函式,來實現同名函式呼叫時的過載問題。但這並沒有體現多型性。


這裡我們拿C++primer中的例子來說明:

分別定義Quote類(按原價出售的書籍)和派生類Bulk_quote(按打折價格出售的書籍)

class Quote {
public:
    virtual double net_price(std::size_t n) const;
};

class Bulk_quote : public Quote {
public:
    double net_price(std::size_t) const override;
};

這裡Quote定義了虛擬函式net_price,派生類對其進行了override.因為Bulk_quote是以public方式繼承Quote類,public繼承是一種"is a"的方式,即Bulk_quote是一種Quote類,可以把Bulk_quote的物件當作Quote的物件來使用.

繼續看下面的程式碼,用於計算所買書籍總價格

double print_total(const Quote &item,size_t n)
{
    double ret = item.net_price(n);
    return ret;
}
因為函式printe_total中的item行參是基類的一個引用,由於上述的"is a"關係,所以我們既可以使用基類Quote的物件來呼叫它,也可以用派生類Bulk_quote來呼叫它.那麼問題來了? 當我們用Bulk_quote物件呼叫該函式時,究竟執行基類的net_price還是Bulk_quote中的net_price?

這裡就是C++動態繫結也是多型起作用的時候了,根據實際傳入的物件型別來決定執行net_price的哪個版本.

//basic的型別是Quote;Bulk的型別是Bulk_quote
print_total(basic, 20); //呼叫Quote的net_price
print_total(bulk, 20); //呼叫Bulk_quote的net_price版本

多型與非多型的實質區別就是函式地址是早繫結還是晚繫結。如果函式的呼叫,在編譯器編譯期間就可以確定函式的呼叫地址,並生產程式碼,是靜態的,就是說地址是早繫結的。而如果函式呼叫的地址不能在編譯器期間確定,需要在執行時才確定,這就屬於晚繫結。
  那麼多型的作用是什麼呢,封裝可以使得程式碼模組化,繼承可以擴充套件已存在的程式碼,他們的目的都是為了程式碼重用。而多型的目的則是為了介面重用。也就是說,不論傳遞過來的究竟是那個類的物件,函式都能夠通過同一個介面呼叫到適應各自物件的實現方法。

最常見的用法就是宣告基類的指標,利用該指標指向任意一個子類物件,呼叫相應的虛擬函式,可以根據指向的子類的不同而實現不同的方法。如果沒有使用虛擬函式的話,即沒有利用C++多型性,則利用基類指標呼叫相應的函式的時候,將總被限制在基類函式本身,而無法呼叫到子類中被重寫過的函式因為沒有多型性,函式呼叫的地址將是一定的,而固定的地址將始終呼叫到同一個函式,這就無法實現一個介面,多種方法的目的了。


C++純虛擬函式

一、定義
  純虛擬函式是在基類中宣告的虛擬函式,它在基類中沒有定義,但要求任何派生類都要定義自己的實現方法。在基類中實現純虛擬函式的方法是在函式原型後加“=0” 
  virtual void funtion()=0 
二、引入原因
   1、為了方便使用多型特性,我們常常需要在基類中定義虛擬函式。 
   2、在很多情況下,基類本身生成物件是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成物件明顯不合常理。 

又例如:

class Shape {
public:
    virtual void draw() const = 0;
    ....
};

class Rectangle : public Shape {...};  //矩形
class Ellipse : public Shape {...};  //橢圓
這裡Shape類只為Rectangle和Ellipse提供draw介面. 所有的Shape物件都應該是可以繪出的,但是Shape基類無法為此函式提供合理的預設實現,畢竟橢圓繪法和矩形繪法不一樣!Shape::draw的宣告式是對派生類設計人員說:"你必須提供一個draw函式,但是我不管你怎麼實現它".定義純虛擬函式同時 起到一個規範的作用,規範繼承這個類的程式設計師必須實現這個函式
為了解決上述問題,引入了純虛擬函式的概念,將函式定義為純虛擬函式(方法:virtual ReturnType Function()= 0;),則編譯器要求在派生類中必須予以重寫以實現多型性。同時含有純虛擬函式的類稱為抽象類,它不能生成物件。這樣就很好地解決了上述兩個問題

參考資料:

http://blog.csdn.net/hackbuteer1/article/details/7475622

<<Effective c++>>