多態(Polymorphism)按字面的意思就是「多種狀態」。在面嚮對象語言中,接口的多種不一樣的實現方式即爲多態。引用Charlie Calverts對多態的描述——多態性是容許你將父對象設置成爲一個或更多的他的子對象相等的技術,賦值以後,父對象就能夠根據當前賦值給它的子對象的特性以不一樣的方式運做(摘自「Delphi4 編程技術內幕」)。ios
簡單的說,就是一句話:容許將子類類型的指針賦值給父類類型的指針。c++
多態按字面的意思就是多種形態。當類之間存在層次結構,而且類之間是經過繼承關聯時,就會用到多態。segmentfault
C++ 多態意味着調用成員函數時,會根據調用函數的對象的類型來執行不一樣的函數。 函數
虛基類使得其派生類在間接地屢次繼承本類時,只繼承本類的一份成員,避免出現屢次繼承產生多份拷貝的二義性。this
/* VirtualBaseClass.cpp 虛基類實例*/ #include <iostream> class Animal // 定義動物類 { protected: int age; // 存儲年齡 public: Animal(int age_t):age(age_t){}; // 參數列表初始化年齡 ~Animal(){}; // 析構函數,這裏用不上 }; class Bird:virtual public Animal // 鳥虛繼承動物類 { private: int flyHight; // 定義鳥飛的高度 protected: int getFlyHight(); // 獲取鳥飛的高度 public: Bird(int flyHight_t, int age_t):flyHight(flyHight_t), Animal(age_t){}; // 參數列表初始化高度和年齡 ~Bird(){}; // 析構函數 }; int Bird::getFlyHight() { return flyHight; // 返回鳥飛的高度 } class Fish:virtual public Animal // 魚虛繼承動物類 { private: int divingDepth; // 定義魚潛水深度 protected: int getDivingDepth(); // 獲取魚潛水深度 public: Fish(int divingDepth_t, int age_t):divingDepth(divingDepth_t), Animal(age_t){}; // 參數列表初始化深度年齡 ~Fish(){}; // 析構函數 }; int Fish::getDivingDepth() { return divingDepth; // 返回魚潛的深度 } class WaterBird:virtual public Bird, Fish // 多重繼承鳥和魚,此時間接地兩次繼承了動物類,可是以前鳥和魚繼承時使用了虛繼承,所以此處默認虛繼承,可是爲了可讀性,這裏最好寫上virtual { public: WaterBird(int flyHight_t, int divingDepth_t, int age_t):Bird(flyHight_t, 5), Fish(divingDepth_t, 6), Animal(age_t){}; ~WaterBird(){}; void displayInfos(); }; void WaterBird::displayInfos() { std::cout << "種族:飛魚" << std::endl; std::cout << "年齡:" << age << std::endl; std::cout << "最高飛行高度:" << getFlyHight() << std::endl; std::cout << "最大潛水深度:" << getDivingDepth() << std::endl; } int main() { WaterBird wb(200, 50, 4); wb.displayInfos(); return 0; }
種族:飛魚 年齡:4 最高飛行高度:200 最大潛水深度:50
咱們發現,在水鳥內繼承鳥和魚的age時,初始化的值分別爲5和6,而後虛繼承動物類的age時,賦值爲4,最終的結果爲4。設計
這意味着咱們經過建立虛基類繼承的成員默認爲最高基類的成員。指針
若是想要用鳥或者魚的age,須要使用域做用符。code
多態的本質是同一個函數的多種形態
通常而言,C++支持的多態有兩種:
靜態聯編在編譯時就已經肯定了多態性,通常經過重載進行實現;
動態聯編則在運行時才能肯定多態性 ,通常經過繼承和虛函數來實現。
若某個基類函數 聲明爲虛函數,當派生類使用 基類指針或 基類引用操做派生類對象時,系統會自動用 派生類的 同名函數代替基類虛函數。
若是基類函數沒有聲明爲虛函數,那麼使用基類指針調用時,調用到的是基類的函數。
/* VirtualFunction.cpp 虛函數實例*/ #include <iostream> class BaseClass // 基類 { private: public: BaseClass(){}; ~BaseClass(){}; void speakNormal() // 提示信息的函數,這裏是普通函數 { std::cout << "這裏是基類!" << std::endl; } virtual void speakVirtual() // 提示信息的函數,這裏是虛函數 { std::cout << "這裏是基類!" << std::endl; } }; class DerivedClass:public BaseClass // 公有繼承 { private: public: DerivedClass(){}; ~DerivedClass(){}; void speakNormal() // 提示信息的函數,這裏是普通函數 { std::cout << "這裏是派生類!" << std::endl; } virtual void speakVirtual() // 提示信息的函數,這裏是虛函數,虛函數默認繼承時虛函數,可是爲了可讀性,建議在寫的時候加上virtual關鍵字 { std::cout << "這裏是派生類!" << std::endl; } }; int main() { DerivedClass dercs; // 建立一個普通的派生類對象 BaseClass* bascs = &dercs; // 建立一個基類指針指向剛剛的派生類對象 bascs->speakNormal(); // 基類指針調用普通函數 bascs->speakVirtual(); //基類指針調用虛函數 return 0; }
這裏是基類! 這裏是派生類!
咱們能夠看到:
使用普通函數,基類指針調用的是基類同名函數;
使用虛函數,基類指針調用的是基類指針指向對象的同名函數。
注意:派生類有時候須要銷燬資源,若是使用基類指針,那麼必需要將基類析構函數設爲虛函數,不然沒法銷燬派生類資源。
另外,構造函數不能做爲虛函數。
純虛函數是指以下模式的函數:
virtual 返回值 函數名( 參數列表 ) = 0;
抽象類是指包含至少一個純虛函數的類:
class 類名 { public: virtual 返回值 函數名( 參數列表 ) = 0; 其它函數聲明 }
有時候咱們不知道如何實現某個功能,好比要給一個形狀求面積,可是三角形和矩形的求面積方法並不相同,可是求面積又是必需要作的。
這種狀況咱們就可使用純虛函數。
純虛函數只需聲明,無需實現,具體的實如今其派生出子類之後再實現,若是子類聲明瞭這個純虛函數可是沒有實現,那麼這個函數依然被視做純虛函數。
例如定義一個形狀類,裏面有一個求面積的純虛函數做爲接口,在三角形和矩形、圓形等不一樣的形狀裏具體實現便可。
包含至少一個純虛函數的類是抽象類,抽象類不能實例化對象,必需要全部純虛函數實現的子類才能實例化對象,若是子類依然有純虛函數,那麼這個類依然是一個抽象類。
/* AbstractClass.cpp 抽象類實例*/ #include <iostream> class Shapes // 定義形狀類 { private: public: Shapes(){}; ~Shapes(){}; virtual void disp() = 0; // 定義求面積的純虛函數,此時形狀類做爲抽象類,不能實例化對象,純虛函數要在派生類中實現 }; class Squera:public Shapes { public: Squera(int length, int width); // (矩形長度, 矩形寬度) ~Squera(){}; class Squera_data // 定義一個內部類 { public: int length_t; // 長 int width_t; // 寬 }; void disp(); // 實現基類的純虛函數 private: Squera_data data_t; // 存儲矩形的數據 }; Squera::Squera(int length, int width) { data_t.length_t = length; data_t.width_t = width; } void Squera::disp() { std::cout << "矩形面積爲:" << data_t.length_t * data_t.width_t << std::endl; } class Triangle { private: double bottomL_l; // 底邊 double heightL_l; // 高 class Triangle_run // 內部類來運算面積 { private: public: Triangle_run(Triangle& tri_l){ std::cout << "三角形面積爲:" << tri_l.bottomL_l * tri_l.heightL_l / 2 << std::endl; } }; public: Triangle(double bottomL, double heightL):bottomL_l(bottomL), heightL_l(heightL){}; // (底, 高),三角形的屬性 ~Triangle(){}; void disp(); // 實現純虛函數 }; void Triangle::disp() { Triangle_run tri_r_l(*this); // 把剛剛實例化的本類做爲參數傳入 } int main() { Squera squera_l(3, 4); // 矩形求面積實現 squera_l.disp(); Triangle tri_t(3, 4); // 三角形求面積實現 tri_t.disp(); return 0; }
矩形面積爲:12 三角形面積爲:6
咱們發現,咱們實現了子類不一樣求面積的函數,可是都以disp爲名字,這樣,咱們直接調用disp就能夠在不一樣的派生類中作到同一件事情——求面積,儘管它們的具體實現方式不一樣。
值得注意的是,這裏用到了內部類,又稱嵌套類,在類中定義類,內部類和外部類能夠互相調用對方私有成員,可是內部類調用外部類時須要傳入參數。
參考資料: