多態(PolyMorphism)
如果有幾個相似而不完全相同的對象,有時人們要求在向它們發出同一個訊息時,它們的反應各不相同,分別執行不同的操作。這種情況就是多態現象。
例如,甲乙丙3個班都是高二年級,他們有基本相同的屬性和行為,在同時聽到上課鈴聲的時候,他們會分別走向三個不同的教室,而不會走向同一個教室。
又如在windows環境下,用滑鼠雙擊一個對象(這就是向對象傳遞一個訊息),如果對象是一個可執行檔,則會執行此程式,如果對象是一個文字檔,則會啟動文字編輯器並開啟該檔案。
C++中所謂的多態(polymorphism)是指,由繼承而產生相關的不同的類,其對象對同一訊息會作出不同的相應。
多態性是物件導向程式設計的一個重要特徵,能增強程式的靈活性。可以減輕系統升級,維護,調試的工作量和複雜度。 多態實現的前提:賦值相容
賦值相容的規則是指在需要基類對象的任何地方都可以使用公有衍生類別的對象來替代。賦值相容是一種預設行為,不需要任何的顯示的轉化步驟。
賦值相容規則中所指的替代包括以下的情況:
* 衍生類別的對象可以複製給基類對象
* 衍生類別的對象可以初始化基類的引用
* 衍生類別對象的地址可以賦值給指向基類的指標
在替代之後,衍生類別對象就可以作為基類的對象使用,但只能使用從基類繼承的成員。
class Shape{public: Shape(int x, int y) :_x(x), _y(y){} void draw() { cout<<"draw from "<<"("<<_x<<", "<<_y<<")"<<endl; }protected: int _x; int _y;};Class Circle: public Shape{public: Circle(int x, int y, int r) :Shape(x, y), _radius(r){} void draw() { cout<<"draw from "<<"("<<_x<<", "<<_y<<")"<<endl; cout<<"radius = "<<_radius<<endl; }private: int _radius;};// 賦值相容只發生在公有派生的父子關係中// 子類對象賦值給父類對象// 子類對象賦值給父類的引用// 子類對象的地址賦值給父類的指標int main(){ Shape s(1,2); s.draw(); Circle c(3, 4, 5); c.draw() return 0; s = c; c.draw(); // 調用Shape中的draw函數 Shape & rc = c; rc.draw();// 調用Shape中的draw函數 Shape * ps = &c; ps->draw(); return 0;}
多態形成的條件
靜多態
前面學習的函數重載,也是一種多態現象,通過命名傾軋在編譯階段決定,故稱為靜多態。 動多態
動多態,不是在編譯器階段決定,而是在運行階段決定,故稱為動多態。動多態形成的條件如下:
1. 父類中有虛函數,聲名虛函數的方法;
2. 子類override(覆寫)父類中的虛函數
3. 通過已被子類對象賦值的父類指標或引用,調用共用介面
class Shape{public: Shape(int x, int y) :_x(x), _y(y){} virtual void draw() { cout<<"draw from "<<"("<<_x<<", "<<_y<<")"<<endl; }protected: int _x; int _y;};class Rect: public Shape{public: Rect(int x, int y, int len, int wid) :Shape(x, y), _len(len), _wid(wid){} virtual void draw() { cout<<"start from"<<"("<<_x<<", "<<_y<<")"<<endl; cout<<"len = "<<_len<<"wid = "<<_wid<<endl; }private: int _len; int _wid;};Class Circle: public Shape{public: Circle(int x, int y, int r) :Shape(x, y), _radius(r){} void draw() { cout<<"draw from "<<"("<<_x<<", "<<_y<<")"<<endl; cout<<"radius = "<<_radius<<endl; }private: int _radius;};int main(){ Circle c(1, 2, 3); Shape *ps = &c'; ps->draw(); // 表現的是Circle的特性 ps = new Rect(3, 4, 5, 6); ps->draw(); // 表現的是Rect的特性}
聲明虛函數的方法,virtual void func();聲明型關鍵字
overload:同一範圍中,函數名相同,參數列表不同
shadow: 發生在父子類中的同名成員
override: 發生在父子類中,父類中函數有virtual聲明的函數,子類中同參同名同返回,函數之間構成覆寫
覆寫的函數,是不是一定同父類的訪問方式一致,看子類的需求。 虛函數小結 在基類中用virtual聲明成員函數為虛函數,類外實現虛函數時,不必再加virtual。 在衍生類別中重新定義此函數稱為覆寫,要求函數名、返實值型別、函數參數個數及類型全部匹配,並根據衍生類別的需要重新定義函數體。 當一個成員函數被聲明為虛函數後,其衍生類別中完全相同的函數(顯示地寫出)也為虛函數,可以在其前加virtual以示清晰。 定義一個指基類對象地指標,並使其指向其子類的對象,通過該指標調用虛函數,此時調用的就是指標變數指向對象的同名函數。 子類中的覆寫的函數,可以為任意訪問類型,依據子類需求決定。 純虛函數
格式
class 類名{ virtual 函式宣告 = 0;}
純虛函數沒有實現體,含有純虛函數的類,稱為抽象基類,不可以執行個體化。
Java interface 介面,作用就是給族類提供介面用的。
如果一個類中聲明了純虛函數,而在衍生類別中沒有對該函數的定義,則該虛函數在衍生類別中仍然為純虛函數,衍生類別仍然為純虛基類。 虛析構
為了析構完全,在delete父類指標的時候,將子類對象析構完全。
class Animal{public: Animal(); virtual ~Animal(); virtual void voice() = 0;};
當一個類中有虛函數的時候,需要將其解構函式一併virtual。
含有虛函數的類,解構函式也應該聲明為虛函數。在delete父類指標的時候,會調用子類的解構函式,實現完整析構。
(1) 只有類的成員函數才能聲明為虛函數
虛函數僅適用於有繼承關係的類對象,所以普通函數不能聲明為虛函數
(2) 靜態成員函數不能是虛函數
靜態成員函數不受對象的捆綁,只有類的資訊;實現多態必須要有對象
(3) 內嵌函式不能是虛函數
(4) 建構函式不能是虛函數
構造時,對象的建立尚未完成。構造完成後,才能算一個名副其實的對象
(5) 解構函式可以是虛函數且通常聲明為虛函數