多態是C++中很關鍵的一部分,在面向對象程序設計中的做用尤其突出,其含義是具備多種形式或形態的情形,簡單來講,多態:向不一樣對象發送同一個消息,不一樣的對象在接收時會產生不一樣的行爲。即用一個函數名能夠調用不一樣內容的函數。ios
多態可分爲靜態多態與動態多態,靜態多態的實如今於靜態聯編,關聯出如今編譯階段而非運行期,用對象名或者類名來限定要調用的函數,稱爲靜態關聯或靜態聯編。常見有三種方法函數
(1)函數多態(函數與運算符的重載);this
(2)宏多態;spa
(3)模板多態。設計
而對於動態多態的實現是運行階段把虛函數和類對象綁定在一塊兒的,即動態聯編,動態綁定。具體的說,經過一個指向基類的指針調用虛成員函數的時候,運行時系統將可以根據指針所指向的實際對象調用恰當的成員函數實現。指針
當編譯器使用動態綁定時,就會在運行時再去肯定對象的類型以及正確的調用函數。而要讓編譯器採用遲綁定,就要在基類中聲明函數時使用virtual關鍵字,這樣的函數稱之爲虛函數(virtual functions)。根據賦值兼容,用基類類型的指針指向派生類,就能夠經過這個指針來使用派生類的成員函數。若是這個函數是普通的成員函數,經過基類類型的指針訪問到的只能是基類的同名成員。 而若是將它設置爲虛函數,則可使用基類類型的指針訪問到指針正在指向的派生類的同名函數。這樣,經過基類類型的指針,就可使屬於不一樣派生類的不一樣對象產生不一樣的行爲,從而實現運行過程的多態。可看這個例子:對象
#include <iostream> using namespace std; class A { public : void print( ) { cout << 「A::print」<<endl ; } }; class B:public A { public : void print( ) { cout << 「B::print」 <<endl; } }; int main( ) { A a; B b; A *pA = &b; pA->print( ); return 0; }
此時輸出A::print ,若將A類中的print( )函數聲明爲virtual,則此時就爲動態聯編 程序執行結果爲: B::print。blog
注意點1:構造函數和靜態成員函數不能是虛函數:靜態成員函數不能爲虛函數,是由於virtual函數由編譯器提供了this指針,而靜態成員函數沒有this指針,是不受限制於某個對象;構造函數不能爲虛函數,是由於構造的時候,對象仍是一片未定型的空間,只有構造完成後,對象纔是具體類的實例。繼承
class A { public: virtual A( ) {}; //error }; class B { public: virtual static void func( ) {}; //error 「virtual」不能和「static」一塊兒使用 }; int main( ) {
B b; //報錯
A *a=&b; return 0; }
注意點2:派生類對象的指針能夠直接賦值給基類指針,如上面中的A *a=&b;*a能夠看做一個類A的對象,訪問它的public成員。經過強制指針類型轉換,能夠把a轉換成B類的指針: a = &b; aa = static_cast< B * > a。此外指向基類的指針,能夠指向它的公有派生的對象,但不能指向私有派生的對象,對於引用也是同樣的。接口
class B { public: virtual void print() { cout<<"Hello B"<<endl; } }; class D : private B { public: virtual void print() { cout<<"Hello D"<<endl; } }; int main() { D d; B* pb = &d; //轉換存在,沒法訪問 pb->print(); B& rb = d; //轉換存在,沒法訪問 rb.print(); return 0; }
注意點3:構造函數中調用virtual函數 ,在構造函數和析構函數中調用虛函數時:他們調用的函數是本身的類或基類中定義的函數,不會等到運行時才決定調用本身的仍是派生類的函數。
class Transaction { public: Transaction( ){ logTransaction( ); } virtual void logTransaction( ) = 0; }; class BuyTransaction: public Transaction { public: int buyNum; virtual void logTransaction( ) { cout<< "This is a BuyTransaction"; } }; class SellTransaction: public Transaction { public: int sellNum; virtual void logTransaction( ) { cout<< "This is a SellTransaction"; } }; int main( ) { BuyTransaction b; SellTransaction s; }
以上代碼應該會有報錯提示,
若將基類的Transaction中虛函數logTransaction改成:
virtual void logTransaction( ) { cout<< "This is a Transaction"<<endl; };
程序執行結果爲: This is a Transaction
This is a Transaction
注意點4:普通成員函數中調用虛函數,在普通成員函數中調用虛函數,則是動態聯編,是多態。
#include <iostream> using namespace std; class Base { public: void func1( ) { func2( ); } void virtual func2( ) { cout << "Base::func2( )" << endl; } }; class Derived:public Base { public: virtual void func2( ) { cout << "Derived:func2( )" << endl; } }; int main( ) { Derived d; Base * pBase = & d; pBase->func1( ); return 0; }
由於,Base類的func1( )爲非靜態成員函數,編譯器會給加一個this指針: 至關於 void func1( ) { this->func2( ); } 編譯這個函數的代碼的時候,因爲func2( )是虛函數,this是基類指針,因此是動態聯編。上面這個程序運行到func1函數中時, this指針指向的是d,因此通過動態聯編,調用的是Derived::func2( )。
注意點5:虛函數的訪問權限,若是基類定義的成員虛函數是私有的,咱們來看看會怎麼樣
class Base{ private: virtual void func( ) { cout << "Base::func( )" << endl; } }; class Derived : public Base { public: virtual void func( ) { cout << "Derived:func( )" << endl; } }; int main( ) { Derived d; Base *pBase = & d; pBase->func( ); // 沒法訪問, private 成員(在「Base」類中聲明) return 0; }
對於類的private成員 ,只能由該類中的函數、其友元函數訪問,不能被任何其餘訪問,該類的對象也不能訪問.因此即便是虛函數,也沒辦法訪問。可是!派生類虛函數的可訪問性與繼承的方式和虛函數在基類的聲明方式有關(public,或private)與派生類聲明的方式無關(如public繼承,但聲明爲private,但仍可訪問),把上面的public與private互換位置,程序能夠正常運行,並輸出Derived:func( )。
注意點6:虛函數與友元,先看代碼
class A; class B { private: int x; void print() { cout<<x<<endl; } public: B(int i = 0) { x = i; } friend class A; }; class A { public: void func(B b){ b.print(); } }; class C : public A { }; class D: public B { public: D(int i):B(i){} }; int main() { D d(99); A a; C c; a.func(d); c.func(d); return 0; }
程序執行結果爲:99 99
由第一個99可知,A是B的友元類,A中的全部成員函數都爲B的友元函數,可訪問B的私有成員函數。友元類A不是基類B的一部分,更不是派生類D的一部分。從上例看,友元視乎可以被繼承,基類的友元函數或友元類可以訪問派生類的私有成員。但public繼承是一種「is a」的關係,即一個派生類對象可當作一個基類對象。因此,上例中不是基類的友元被繼承了,而是派生類被識別爲基類了。而第二個99說明一個友元類的派生類,能夠經過其基類接口去訪問設置其基類爲友元類的類的私有成員,也就是說一個類的友元類的派生類,某種意義上仍是其友元類。
注意點7:析構函數一般是虛函數。虛析構函數保證了在析構時,避免只調用基類析構函數而不調用派生類析構函數的狀況,保證資源正常釋放,避免了內存釋放。只有當一個類被用來做爲基類的時候,纔會把析構函數寫成虛函數。
以上爲我的總結,有不妥的地方歡迎指出。