C++封裝繼承多态總結
面向對象的三個基本特征
面向對象的三個基本特征是:封裝、繼承、多态。其中,封裝可以隐藏實作細節,使得代碼子產品化;繼承可以擴充已存在的代碼子產品(類);它們的目的都是為了——代碼重用。而多态則是為了實作另一個目的——接口重用!
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL1czM28FNyUDN2UzM1MTMvwlMy8CXxEjMxAjMvw1ckF2bsBXdvwFdl5mLuR2cj5Set1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
封裝
什麼是封裝?
封裝可以隐藏實作細節,使得代碼子產品化;封裝是把過程和資料包圍起來,對資料的通路隻能通過已定義的界面。面向對象計算始于這個基本概念,即現實世界可以被描繪成一系列完全自治、封裝的對象,這些對象通過一個受保護的接口通路其他對象。在面向對象程式設計上可了解為:把客觀事物封裝成抽象的類,并且類可以把自己的資料和方法隻讓可信的類或者對象操作,對不可信的進行資訊隐藏。
繼承
什麼是繼承?
繼承是指這樣一種能力:它可以使用現有類的所有功能,并在無需重新編寫原來的類的情況下對這些功能進行擴充。其繼承的過程,就是從一般到特殊的過程。
通過繼承建立的新類稱為“子類”或“派生類”。被繼承的類稱為“基類”、“父類”或“超類”。要實作繼承,可以通過“繼承”(Inheritance)和“組合”(Composition)來實作。在某些 OOP 語言中,一個子類可以繼承多個基類。但是一般情況下,一個子類隻能有一個基類,要實作多重繼承,可以通過多級繼承來實作。
繼承的實作方式?
繼承概念的實作方式有三類:實作繼承、接口繼承和可視繼承。
1. 實作繼承是指使用基類的屬性和方法而無需額外編碼的能力;
2. 接口繼承是指僅使用屬性和方法的名稱、但是子類必須提供實作的能力;
3. 可視繼承是指子窗體(類)使用基窗體(類)的外觀和實作代碼的能力。
多态
什麼是多态?
多态性(polymorphisn)是允許你将父對象設定成為和一個或更多的他的子對象相等的技術,指派之後,父對象就可以根據目前指派給它的子對象的特性以不同的方式運作。簡單的說,就是一句話:允許将子類類型的指針指派給父類類型的指針。
例子:(2012某**軟體公司筆試題)
請按順序寫出下面代碼的輸出結果:
答案:call child func
call ~child
call ~base
多态的實作方式分析?
實作多态,有二種方式,覆寫,重載。覆寫:是指子類重新定義父類的虛函數的做法。重載:是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。
分析:
“重載”是指在同一個類中相同的傳回類型和方法名,但是參數的個數和類型可以不同
“覆寫\重寫”是在不同的類中。
其實,重載的概念并不屬于“面向對象程式設計”,重載的實作是:編譯器根據函數不同的參數表,對同名函數的名稱做修飾,然後這些同名函數就成了不同的函數(至少對于編譯器來說是這樣的)。如,有兩個同名函數:function func(p:integer):integer;和function func(p:string):integer;。那麼編譯器做過修飾後的函數名稱可能是這樣的:int_func、str_func。對于這兩個函數的調用,在編譯器間就已經确定了,是靜态的(記住:是靜态)。也就是說,它們的位址在編譯期就綁定了(早綁定),是以,重載和多态無關!真正和多态相關的是“覆寫”。當子類重新定義了父類的虛函數後,父類指針根據賦給它的不同的子類指針,動态(記住:是動态!)的調用屬于子類的該函數,這樣的函數調用在編譯期間是無法确定的(調用的子類的虛函數的位址無法給出)。是以,這樣的函數位址是在運作期綁定的(晚邦定)。結論就是:重載隻是一種語言特性,與多态無關,與面向對象也無關!引用一句Bruce Eckel的話:“不要犯傻,如果它不是晚邦定,它就不是多态。”
C++多态機制的實作:
該部分轉自:http://blog.chinaunix.net/uid-7396260-id-2056657.html
1、c++實作多态的方法
面向對象有了一個重要的概念就是對象的執行個體,對象的執行個體代表一個具體的對象,故其肯定有一個資料結構儲存這執行個體的資料,這一資料包括對象成員變量,如果對象有虛函數方法或存在虛繼承的話,則還有相應的虛函數或虛表指針,其他函數指針不包括。
虛函數在c++中的實作機制就是用虛表和虛指針,但是具體是怎樣的呢?從more effecive c++其中一篇文章裡面可以知道:是每個類用了一個虛表,每個類的對象用了一個虛指針。要講虛函數機制,必須講繼承,因為隻有繼承才有虛函數的動态綁定功能,先講下c++繼承對象執行個體記憶體配置設定基礎知識:
從more effecive c++其中一篇文章裡面可以知道:是每個類用了一個虛表,每個類的對象用了一個虛指針。具體的用法如下:
class A
{public:
virtual void f();
virtual void g();
private:
int a
};
class B : public A
{
public:
void g();
private:
int b;
};
//A,B的實作省略
因為A有virtual void f(),和g(),是以編譯器為A類準備了一個虛表vtableA,内容如下:
A::f 的位址 |
A::g 的位址 |
B因為繼承了A,是以編譯器也為B準備了一個虛表vtableB,内容如下:
A::f 的位址 |
B::g 的位址 |
注意:因為B::g是重寫了的,是以B的虛表的g放的是B::g的入口位址,但是f是從上面的A繼承下來的,是以f的位址是A::f的入口位址。然後某處有語句 B bB;的時候,編譯器配置設定空間時,除了A的int a,B的成員int b;以外,還配置設定了一個虛指針vptr,指向B的虛表vtableB,bB的布局如下:
vptr : 指向B的虛表vtableB |
int a: 繼承A的成員 |
int b: B成員 |
當如下語句的時候:
A *pa = &bB;
pa的結構就是A的布局(就是說用pa隻能通路的到bB對象的前兩項,通路不到第三項int b)
那麼pa->g()中,編譯器知道的是,g是一個聲明為virtual的成員函數,而且其入口位址放在表格(無論是vtalbeA表還是vtalbeB表)的第2項,那麼編譯器編譯這條語句的時候就如是轉換:call *(pa->vptr)[1](C語言的數組索引從0開始哈~)。
這一項放的是B::g()的入口位址,則就實作了多态。(注意bB的vptr指向的是B的虛表vtableB)
另外要注意的是,如上的實作并不是唯一的,C++标準隻要求用這種機制實作多态,至于虛指針vptr到底放在一個對象布局的哪裡,标準沒有要求,每個編譯器自己決定。我以上的結果是根據g++ 4.3.4經過反彙編分析出來的。
2、兩種多态實作機制及其優缺點
除了c++的這種多态的實作機制之外,還有另外一種實作機制,也是查表,不過是按名稱查表,是smalltalk等語言的實作機制。這兩種方法的優缺點如下:
(1)、按照絕對位置查表,這種方法由于編譯階段已經做好了索引和表項(如上面的call *(pa->vptr[1]) ),是以運作速度比較快;缺點是:當A的virtual成員比較多(比如1000個),而B重寫的成員比較少(比如2個),這種時候,B的vtableB的剩下的998個表項都是放A中的virtual成員函數的指針,如果這個派生體系比較大的時候,就浪費了很多的空間。
比如:GUI庫,以MFC庫為例,MFC有很多類,都是一個繼承體系;而且很多時候每個類隻是1、2個成員函數需要在派生類重寫,如果用C++的虛函數機制,每個類有一個虛表,每個表裡面有大量的重複,就會造成空間使用率不高。于是MFC的消息映射機制不用虛函數,而用第二種方法來實作多态,那就是:
(2)、按照函數名稱查表,這種方案可以避免如上的問題;但是由于要比較名稱,有時候要周遊所有的繼承結構,時間效率性能不是很高。(關于MFC的消息映射的實作,看下一篇文章)
3、總結:
如果繼承體系的基類的virtual成員不多,而且在派生類要重寫的部分占了其中的大多數時候,用C++的虛函數機制是比較好的;但是如果繼承體系的基類的virtual成員很多,或者是繼承體系比較龐大的時候,而且派生類中需要重寫的部分比較少,那就用名稱查找表,這樣效率會高一些,很多的GUI庫都是這樣的,比如MFC,QT
PS 其實,自從計算機出現之後,時間和空間就成了永恒的主題,因為兩者在98%的情況下都無法協調,此長彼消;這個就是計算機科學中的根本瓶頸之所在。軟體科學和算法的發展,就看能不能突破這對時空權衡了。呵呵
何止計算機科學如此,整個宇宙又何嘗不是如此呢?最基本的宇宙之謎,還是時間和空間~