http://www.cnblogs.com/fly1988happy/archive/2012/09/25/2701237.htmlhtml
1. 多態算法
在面嚮對象語言中,接口的多種不一樣實現方式即爲多態。多態是指,用父類的指針指向子類的實例(對象),而後經過父類的指針調用實際子類的成員函數。app
多態性就是容許將子類類型的指針賦值給父類類型的指針,多態是經過虛函數實現的。函數
多態可讓父類的指針有「多種形態」,這是一種泛型技術。(所謂泛型技術,就是試圖使用不變的代碼來實現可變的算法)。性能
2. 虛函數指針
2.1虛函數定義htm
在基類的類定義中,定義虛函數的通常形式:對象
Virtual 函數返回值類型 虛函數名(形參表)
{函數體}blog
虛函數必須是類的非靜態成員函數(且非構造函數),其訪問權限是public。繼承
2.2 虛函數的做用
虛函數的做用是實現動態聯編,也就是在程序的運行階段動態地選擇合適的成員函數,在定義了虛函數後,能夠在基類的派生類中對虛函數進行從新定義(形式同上)。在派生類中定義的函數應與虛函數具備相同的形參個數和形參類型(覆蓋),以實現統一的接口,不一樣定義過程。若是在派生類中沒有對虛函數從新定義,則它繼承其基類的虛函數。
虛函數可讓成員函數操做通常化,用基類的指針指向不一樣的派生類的對象時,基類虛成員函數調用基類指針,則會調用其真正指向的對象的成員函數,而不是基類中定義的成員函數(只要派生類改寫了該成員函數)。若不是虛函數,則無論基類指針指向哪一個派生類對象,調用時都會調用基類中定義的那個函數。
2.3 實現動態聯編須要三個條件:
1)必須把須要動態聯編的行爲定義爲類的公共屬性的虛函數;
2)類之間存在子類型關係,通常表現爲一個類從另外一個類公有派生而來;
3)必須先使用基類指針指向子類型的對象,而後直接或者間接使用基類指針調用虛函數。
2.4 定義虛函數的限制
1)非類的成員函數不能定義爲虛函數,類的成員函數中靜態成員函數和構造函數也不能定義爲虛函數,但能夠將析構函數定義爲虛函數。
2)只須要在聲明函數的類體中使用關鍵字「virtual」將函數聲明爲虛函數,而定義函數時不須要使用關鍵字「virtual」。
3)若是聲明瞭某個成員函數爲虛函數,則在該類中不能出現和這個成員函數同名而且返回值、參數個數、參數類型都相同的非虛函數。在以該類爲基類的派生類中,也不能出現這種非虛的同名同返回值同參數個數同參數類型函數。
2.5
1)爲何類的靜態成員函數不能爲虛函數:
若是定義爲虛函數,那麼它就是動態綁定的,也就是在派生類中能夠被覆蓋的,這與靜態成員函數的定義(在內存中只有一份拷貝,經過類名或對象引用訪問靜態成員)自己就是相矛盾的。
2)爲何構造函數不能爲虛函數:
由於若是構造函數爲虛函數的話,它將在執行期間被構造,而執行期則須要對象已經創建,構造函數所完成的工做就是爲了創建合適的對象,所以在沒有構建好的對象上不可能執行多態(虛函數的目的就在於實現多態性)的工做。在繼承體系中,構造的順序就是從基類到派生類,其目的就在於確保對象可以成功地構建。構造函數同時承擔着虛函數表的創建,若是它自己都是虛函數的話,如何確保vtbl的構建成功呢?
3)虛析構函數
C++開發的時候,用來作基類的類的析構函數通常都是虛函數。當基類中有虛函數的時候,析構函數也要定義爲虛析構函數。若是不定義虛析構函數,當刪除一個指向派生類對象的指針時,會調用基類的析構函數,派生類的析構函數未被調用,形成內存泄露。
虛析構函數工做的方式是:最底層的派生類的析構函數最早被調用,而後各個基類的析構函數被調用。這樣,當刪除指向派生類的指針時,就會首先調用派生類的析構函數,不會有內存泄露的問題了。
通常狀況下,若是類中沒有虛函數,就不用去聲明虛析構函數。當且僅當類裏包含至少一個虛函數的時候纔去聲明虛析構函數。
只有當一個類被用來做爲基類的時候,才把析構函數寫成虛函數。
2.6虛函數的實現——虛函數表
虛函數是經過一張虛函數表來實現的,簡稱V-Table。類的虛函數表是一塊連續的內存,每一個內存單元中記錄一個JMP指令的地址。編譯器會爲每一個有虛函數的類建立一個虛函數表,該虛函數表將被該類的全部對象共享,類的每一個虛函數成員佔據虛函數表中的一行。
在這個表中,主要是一個類的虛函數的地址表。這張表解決了繼承、覆蓋的問題,保證其真實反應實際的函數。在有虛函數的類的實例中,分配了指向這個表的指針的內存,因此,當用父類的指針來操做一個子類的時候,這張虛函數表就指明瞭實際所應該調用的函數。
3. 純虛函數
許多狀況下,在基類中不能對虛函數給出有意義的實現,則把它聲明爲純虛函數,它的實現留給該基類的派生類去作。
純虛函數的聲明格式:virtual <函數返回類型說明符> <函數名> ( <參數表> )=0;
純虛函數的做用是爲派生類提供一個一致的接口。
4.抽象類(abstract class)
抽象類是指含有純虛函數的類(至少有一個純虛函數),該類不能建立對象(抽象類不能實例化),可是能夠聲明指針和引用,用於基礎類的接口聲明和運行時的多態。
抽象類中,既能夠有抽象方法,也能夠有具體方法或者叫非抽象方法。抽象類中,既能夠全是抽象方法,也能夠全是非抽象方法。一個繼承於抽象類的子類,只有實現了父類全部的抽象方法纔可以是非抽象類。
5.接口
接口是一個概念。它在C++中用抽象類來實現,在C#和Java中用interface來實現。
接口是專門被繼承的。接口存在的意義也是被繼承。和C++裏的抽象類裏的純虛函數是相同的。不能被實例化。
定義接口的關鍵字是interface,例如:
public interface MyInterface{
public void add(int x,int y);
public void volume(int x,int y,int z);
}
繼承接口的關鍵字是implements,至關於繼承類的extends。須要注意的是,當繼承一個接口時,接口裏的全部函數必須所有被覆蓋。
當想繼承多個類時,開發程序不容許,報錯。這樣就要用到接口。由於接口容許多重繼承,而類不容許(C++中能夠多重繼承)。因此就要用到接口。
6.虛基類
在派生類繼承基類時,加上一個virtual關鍵詞則爲虛擬基類繼承,如:
class derive : virtual public base
{
};
虛基類是相對於它的派生類而言的,它自己能夠是一個普通的類。只有它的派生類虛繼承它的時候,它才稱做虛基類,若是沒有虛繼承的話,就稱爲基類。好比類B虛繼承於類A,那類A就稱做類B的虛基類,若是沒有虛繼承,那類B就只是類A的基類。
虛繼承主要用於一個類繼承多個類的狀況,避免重複繼承同一個類兩次或屢次。
例如 由類A派生類B和類C,類D又同時繼承類B和類C,這時候類D就要用虛繼承的方式避免重複繼承類A兩次。
7. 抽象類VS接口
一個類能夠有多個接口,只能繼承一個父類??
抽象類能夠有構造方法,接口中不能有構造方法;
抽象類中能夠有普通成員變量,接口中沒有普通成員變量;
接口裏邊所有方法都必須是abstract的,抽象類的能夠有實現了的方法;
抽象類中的抽象方法的訪問類型能夠是public,protected,但接口中的抽象方法只能是public類型的,而且默認即爲public abstract類型;
抽象類中能夠包含靜態方法,接口中不能包含靜態方法;
抽象類和接口中均可以包含靜態成員變量,抽象類中的靜態成員變量的訪問類型能夠任意,但接口中定義的變量只能是public static final類型,而且默認即爲public static final類型。
8. 虛函數VS純虛函數
虛函數
引入緣由:爲了方便使用多態特性,咱們經常須要在基類中定義虛函數。
純虛函數
引入緣由:
1)同「虛函數」;
2)在不少狀況下,基類自己生成對象是不合情理的。例如,動物做爲一個基類能夠派生出老虎、孔雀等子類,但動物自己生成對象明顯不合常理。
純虛函數就是基類只定義了函數體,沒有實現過程。
純虛函數至關於接口,不能直接實例話,須要派生類來實現函數定義;
有的人可能在想,定義這些有什麼用?
好比你想描述一些事物的屬性給別人,而本身不想去實現,就能夠定義爲純虛函數。說的再透徹一些,好比蓋樓房,你是老闆,你給建築公司描述清楚你的樓房的特性,多少層,樓頂要有個花園什麼的,建築公司就能夠按照你的方法去實現了,若是你不說清楚這些,可能建築公司不太瞭解你須要樓房的特性。用純需函數就能夠很好的分工合做了。
兩者的區別:
1> 類裏聲明爲虛函數的話,這個函數是實現的,哪怕是空實現,它的做用就是爲了能讓這個函數在它的子類裏面能夠被重載,這樣的話,編譯器就可使用後期綁定來達到多態了;
純虛函數只是一個接口,是個函數的聲明而已,它要留到子類裏去實現。
2>虛函數在子類裏面也能夠不重載的;但純虛必須在子類去實現,這就像Java的接口同樣。一般咱們把不少函數加上virtual,是一個好的習慣,雖然犧牲了一些性能,可是增長了面向對象的多態性,由於你很難預料到父類裏面的這個函數不在子類裏面不去修改它的實現;
3>虛函數的類用於「實做繼承」,繼承接口的同時也繼承了父類的實現。固然咱們也能夠完成本身的實現。純虛函數的類用於「介面繼承」,主要用於通訊協議方面。關注的是接口的統一性,實現由子類完成。通常來講,介面類中只有純虛函數的;
4>帶純虛函數的類叫抽象類,這種基類不能直接生成對象,而只有被繼承,並重寫其虛函數後,才能使用。