c++是一種物件導向的語言,最重要的乙個目的就是——提供可重用的**,而類繼承就是c++提供來擴充套件和修改類的方法。
類繼承就是從已有的類中派生出新的類,派生類繼承了基類的特性,同時可以新增自己的特性。實際上,類與類之間的關係分為三種:**、組合和繼承。以下是三種關係的**:(為了更好的理解)
基類可以派生出派生類,基類也叫做「
父類」,派生類也稱為「
子類」。
那麼,派生類從基類中繼承了哪些東西呢?
分為兩個方面:1. 變數——派生類繼承了基類中所有的成員變數,並從基類中繼承了基類作用域
,即使子類中的變數和父類中的同名,有了作用域,兩者也不衝突。2.方法——派生類繼承了基類中除去建構函式、析構函式以外的所有方法。
繼承方式有三種——public、protected和private,不同的繼承方式對繼承到派生類中的基類成員有什麼影響?見下圖:
總的來說,父類成員的訪問限定符通過繼承派生到子類中之後,訪問限定符的許可權小於、等於原許可權。其中,父類中的private成員只有父類本身及其友元可以訪問,通過其他方式都不能進行訪問,當然就包括繼承。protected多用於繼承當中,如果對父類成員的要求是——子類可訪問而外部不可訪問,則可以選擇protected繼承方式。
前面也提到,派生類將基類中除去建構函式和析構函式的其他方法繼承了過來,那麼對於派生類物件中自己的成員變數和來自基類的成員變數,它們的構造方式是怎樣的呢?
答案是:1.
先呼叫基類建構函式,構造基類部分成員變數,再呼叫派生類建構函式構造派生類部分的成員變數。2.基類部分成員的初始化方式在派生類建構函式的初始化列表中指定。3.若基類中還有成員物件,則先呼叫成員物件的建構函式,再呼叫基類建構函式,最後是派生類建構函式。析構順序和構造順序相反。見下:
#include using namespace std;
class test
void show()
void show()
void show()
virtual void show()
virtual void show()
void show()
virtual void show()
結果為:
6.抽象類和純虛函式
純虛函式沒有具體的實現,含有純虛函式的類稱為「抽象類」。抽象類不能例項化物件,只能作為基類,派生類可以繼承抽象類,對抽象類中的純虛函式實現 函式重寫覆蓋。
7.析構函式之虛函式
前面我們**了那些不能實現虛函式的情況,析構函式是可以的。那麼什麼時候應該將析構函式實現為虛函式呢?答案是:當基類指標指向堆上開闢的派生類物件時。
class base
;class derive : public base
virtual ~base(){}
void clear()
virtual void show()
執行結果如下:
執行p->show()時出錯了?因為發生執行時多型時,clear()將基類物件整個記憶體都置為0,此時基類的虛函式指標儲存的是0x00000000,虛表找不到,則show()函式的位址也找不到,呼叫出錯!
那麼,下面的**可以執行成功嗎?
class base
virtual ~base(){}
void clear()
virtual void show()
不能?答案見下:
為什麼這下就執行成功了呢?不是已經把虛函式指標置零了嗎?
派生類物件的構造過程是怎麼的?先構造基類部分在構造派生類部分。在構造基類部分時,會將虛函式指標和虛表都初始化好,在這裡構造完之後就緊接著clear()置零了,但是後續還要構造派生類部分!這個過程會重寫虛函式指標,指標指向了derive類對應的虛函式表,虛函式指標值由「0x00000000」改變成乙個有效值。既然派生類物件中有乙個有效的虛函式指標,那麼p->show()當然就能成功。
10.靜態繫結和動態繫結的時間
我們已經明確地知道:靜態繫結發生在編譯階段,動態繫結發生在執行階段。為了對此有更深刻的理解,這裡分以下幾點來延伸內容。
10.1 建構函式和析構函式中能不能實現動多型?
在之前的討論中,我們確定了建構函式本身是不能寫成虛函式的,而析構函式必要時需要實現為虛函式。那麼在他們的函式體中能否實現多型,呼叫虛函式呢?
class base
virtual ~base()
{} virtual void show()
virtual ~base(){}
virtual void show()
;int main()
結果如下:
編譯、執行都通過了,為什麼呢?
編譯階段,訪問限定符public、protected和private發揮著作用,控制著外界對類成員的訪問。p是乙個base*型別,因此p只能看到base類中的成員,即base::show(),而它的訪問限定符為public——外界可訪問,因此編譯階段可以通過。到了執行階段後,因為基類中的show()是乙個虛函式,所以發生動多型,最後執行的show()方法就是派生類的show方法。
注意:1. 在派生類中,只要實現了和基類虛方法同函式頭的函式,不論它之前的限定符是何種,它和基類同名虛函式的關係都叫做「覆蓋」。
2. 執行期期間,限定符不發揮作用,因為找到虛函式表中對應的虛函式位址後,直接「call 函式位址」。
10.3 將基類虛函式放入protected或private中,利用基類指標呼叫該方法,編譯能通過嗎?
編譯不能通過。就如10.3中所說,編譯階段訪問限定符發揮著作用。base類中的show()的限定符為protected,這意味著對外界不可見,只有本身和子類可以訪問,base::show()對base* p不可見,因此編譯錯誤。
10.4 當基類虛函式帶有預設值,派生類同名虛函式也帶有不同預設值時,通過基類指標呼叫派生類該方法時,預設值究竟是哪個?
class base
virtual ~base(){}
virtual void show(int a=30)
結果如下:
最後得知,derive::show()中的預設值變為了基類同名方法的預設值,為什麼呢?
因為,在編譯階段,呼叫函式之前需要壓引數,引數有預設值的話,壓入的就是確切的值。,
main
函式中base*-->
derive::show()
,此時在產生的彙編**中
,壓入的引數就是base::show()中的預設值30,在執行階段發生多型呼叫
derive
中的方法,執行的還是這段彙編**,即得到的預設值還是30。
總的來說,
在編譯期間,函式預設值、是否可呼叫該函式、虛函式指標和虛表的內容都可以確定下來。
C 繼承與多型
派生類繼承基類 又稱父類 超類 的屬性和方法,在此基礎上可以進行修改或新增新的屬性和方法。class 派生類名 繼承方式 基類名 為了保護基類的資料封裝性,無論哪種繼承方式,積累的私有成員在派生類中都是不可見的。1.public繼承 基類的訪問許可權在派生類中不變。公有還是公有,保護還是保護,派生類...
C 繼承與多型
1 分析菱形繼承的問題。2 剖析虛繼承是怎麼解決二義性和資料冗餘的 首先,我們先來看乙個菱形繼承的程式。為了解決以上問題,我們引入虛繼承的語法。可以有效的解決菱形繼承的資料冗餘和二義性問題。1.虛繼承解決了菱形繼承體系裡子類物件包含多份父類物件的資料冗餘問題和浪費空間的問題。2.虛繼承看起來複雜,但...
C 繼承與多型
public繼承 父類的 public protected保持不變,private 不可見 protected繼承 父類的 public 變為protected protected保持不變 private 不可見 private繼承 父類的 public protected 變為private,pr...