C++中的多態(polymorphism)是指由繼承而產生的相關的不一樣的類,其對象對同一消息會做出不一樣的響應。
多態性是面向對象程序設計的一個重要特徵,能增長程序的靈活性。能夠減輕系統升級,維護,調試的工做量和複雜度。
多態是一種不一樣層次分類下的重要聯繫,是一種跨層操做。ios
賦值兼容規則是指在須要基類對象的任何地方均可以使用公有派生類的對象來替代。
賦值兼容是一種默認行爲,不須要任何的顯式的轉化步驟,只能發生在public繼承方式中,是多態實現的前提條件。
賦值兼容規則中所指的替代包括如下的狀況:
A、子類對象能夠直接賦值給父類對象
B、子類對象能夠直接初始化父類對象
C、父類引用能夠直接引用子類對象
D、父類指針能夠直接指向子類對象
當使用父類指針(引用)指向子對象時,子類對象退化爲父類對象,只能訪問父類中定義的成員,能夠直接訪問被子類覆蓋的同名成員。編程
#include <iostream> using namespace std; class Parent { public: int m; Parent(int a) { m = a; } void print() { cout << "Parent m = " << m << endl; } }; class Child : public Parent { public: int m; Child(int a):Parent(a) { m = a; } void print() { cout << "Child m = " << m << endl; } }; int main() { Parent p(0); Child c(0); Parent p1(c); Parent* pp = &c;//指向子類對象的父類指針退化爲父類對象 Parent& rp = c;//指向子類對象的父類引用退化爲父類對象 pp->print(); //Parent m = 0 rp.print(); //Parent m = 0 //Child cc = static_cast<Child>(p);//須要轉換構造函數支持 Child* pc = static_cast<Child*>(&p); pc->print();//Child m = xxxx return 0; }
在替代後,派生類對象就能夠做爲基類的對象使用,但只能使用從基類繼承的成員。
父類也能夠經過強轉的方式轉化爲子類,但存在訪問越界的風險。
子類中能夠重定義父類中已經存在的成員函數,即函數重寫。
當函數重寫遇到賦值兼容時,編譯器只能根據指針的類型判斷所指向的對象,根據賦值兼容原則,編譯器認爲父類指針指向的是父類對象,只能調用父類中定義的同名函數。
面向對象編程中,一般須要根據實際的對象類型判斷如何調用重寫函數。當父類指針指向父類對象,則調用父類定義的函數;當父類指針指向的是子類對象時,須要調用子類定義的函數;當父類引用對父類對象進行引用時,調用父類對象定義的函數;當父類引用對子類對象進行引用時,調用子類定義的函數。ide
根據父類指針指向的實際對象類型決定調用的函數,即多態。多態中,父類指針(引用)指向父類對象則調用父類中定義的函數,父類指針(引用)指向子類對象時調用子類對象的函數。
C++經過virtual關鍵字對多態進行支持,被virtual聲明的函數被重寫後具備多態屬性。
多態造成的條件:
A、父類中有虛函數。
B、子類override(覆寫)父類中的虛函數。
C、經過己被子類對象賦值的父類指針,調用共用接口。函數
虛函數的聲明語法以下:組件化
virtual 函數聲明;
虛函數的使用規則以下:
A、在父類中用 virtual 聲明成員函數爲虛函數。類外實現虛函數時,沒必要再加virtual。
B、在派生類中重定義父類中已經存在的成員函數稱爲函數覆寫,要求函數名,返值類型,函數參數個數及類型所有匹配,並根據派生類的須要從新定義函數體。
C、當一個成員函數被聲明爲虛函數後,其派生類中徹底相同的函數也爲虛函數,派生類中的虛函數能夠加virtual關鍵字,也能夠不加。
D、定義一個父類對象指針,使其指向其子類的對象,經過父類指針調用虛函數,此時調用的是子類的同名函數。
E、構造函數不能爲虛函數,在構造函數執行完畢後虛函數表指針才能被正確初始化。析構函數能夠爲虛函數,定義一個父類指針並使用new建立的子類對象初始化,使用delete釋放父類指針的堆空間時,只會調用父類的析構函數,不會調用子類的析構函數,會形成內存泄漏,父類析構函數聲明爲虛函數能夠避免該問題。通常來講須要將析構函數聲明爲虛函數。構造函數執行時,虛函數表指針未被正確初始化,所以構造函數不可能發生多態;析構函數函數執行時,虛函數表指針已經被銷燬,所以析構函數也不可能發生多態。構造函數和析構函數中只能調用當前類中定義的函數版本。學習
#include <iostream> using namespace std; class Parent { public: int mi; void add(int i) { mi += i; } void add(int a, int b) { mi += (a + b); } virtual void print() { cout << "Parent" << endl; } }; class Child : public Parent { public: int mi; //函數重寫 void add(int x, int y) { mi += (x + y); } //函數重寫 void print() { cout << "Child" << endl; } }; int main(int argc, char *argv[]) { Child child; child.add(1,2);//調用子類函數 child.Parent::add(1);//調用父類函數 child.Parent::add(1,2);//調用父類函數 Parent p = child; p.add(1); p.add(1,2); Parent& rp = child; rp.add(1); rp.add(1,2); rp.print();//Child Parent* pp = &child; pp->add(1); pp->add(1,2); pp->print();//Child return 0; }
純虛函數的聲明語法以下:spa
virtual 函數聲明 = 0;
純虛函數的使用規則以下:
A、含有純虛函數的類,稱爲抽象基類,不可實例化,即不能建立對象,存在的意義就是被繼承,提供類族的公共接口,Java中稱爲 interface。
B、純虛函數只有聲明,沒有實現。
C、若是一個類中聲明瞭純虛函數,而在派生類中沒有對純虛函數進行實現,則該虛函數在派生類中仍然爲純虛函數,派生類仍然爲抽象基類。設計
虛函數的使用規則以下:
A、只有類的成員函數才能聲明爲虛函數。虛函數僅適用於有繼承關係的類對象,因此普通函數不能聲明爲虛函數。
B、靜態成員函數不能是虛函數。靜態成員函數不受對象的捆綁,只有類的信息。
C、內聯函數不能是虛函數。
D、構造函數不能是虛函數。構造時,對象的建立還沒有完成。構造完成後,才能算一個名符其實的對象。
E、析構函數能夠是虛函數且一般聲明爲虛函數
F、含有虛函數的類,析構函數也必須聲明爲虛函數。在delete父類指針的時候,會調用子類的析構函數。指針
多態的意義以下:
A、在程序運行過程當中展示出的動態特性
B、函數重寫必須多態實現,不然沒有意義
C、多態是面向對象組件化程序設計的基礎特性
D、多態是一種跨層操做
靜態聯編是在程序的編譯期間就能肯定具體的函數調用,如函數重載。
動態聯編是在程序實際運行時才能肯定具體的函數調用,如函數重寫。調試
#include <iostream> using namespace std; class Parent { public: int mi; virtual void add(int i) { mi += i; } virtual void add(int a, int b) { mi += (a + b); } virtual void print() { cout << "Parent" << endl; } }; class Child : public Parent { public: int mi; //函數重寫 void add(int x, int y) { mi += (x + y); } //函數重寫 void print() { cout << "Child" << endl; } }; int main(int argc, char *argv[]) { Child child; child.add(1,2);//靜態聯編 child.Parent::add(1);//靜態聯編 child.Parent::add(1,2);//靜態聯編 Parent p = child; p.add(1);//靜態聯編 p.add(1,2);//靜態聯編 p.print();//靜態聯編 cout << endl; Parent& rp = child; rp.add(1);//靜態聯編 rp.add(1,2);//動態聯編 rp.print();//動態聯編 Parent* pp = &child; pp->add(1);//靜態聯編 pp->add(1,2);//動態聯編 pp->print();//動態聯編 return 0; }