繼承&派生

一、什麼是繼承和派生

   封裝、繼承、多態是C++的三個重要的特性。在面嚮對象的技術中強調軟件的可重用性,而繼承機制就是用來解決軟件的重用問題。在C++中,所謂“繼承”就是在一個已經存在的類的基礎上建立一個新的類。已經存在的類成爲基類或父類,新建立的類稱爲派生類或子類。

   一個類從一個已有的類那裏獲得已有的特性,這種現象稱爲類的繼承。通過繼承,一個新建的子類從父類那裏獲得父類的特性。從另一角度說,從已有的類(父類)產生一個新的類(子類),稱爲類的派生。派生類繼承類基的所有數據成員和成員函數,並可以對成員做出必要的調整。一個基類可以派生出多個派生類,每一個派生類又能作爲基類再派生出新的派生類。因此基類和派生類是相對而言的。類的每一次派生都繼承了其基類的基本特徵,同時又根據需要做出新的調整。一個派生類只從一個基類派生,這種稱爲單繼承。一個派生類也有從兩個或多個基類,這種稱爲多繼承。關於基類和派生類的關係可以理解爲:基類是派生類的抽象,派生類是基類的具體化。


二、派生類的聲明方式

聲明一個單繼承的派生類的一般形式:

   class 派生類名:繼承方式 基類名

   {

         派生類新增加的成員

   };

  其中繼承方式有三種:public(公用的),protected(受保護的),private(私有的)。此項如果不寫則默認爲private(私有的)。

例:假設已經聲明瞭一個基類Time,在此基礎上通過單繼承聲明一個派生類Date

classDate:publicTime                              //公用繼承

{

public:

                voiddisplay()

                {

                                cout << _year << _month << _day << endl;

                }

private:

                int_year;

                int_month;

                int_day;

};


三、派生類的構成

   派生類的成員包括從基類繼承過來的成員和自己增加的成員兩大部分。但是並不是說把基類的成員和派生類增加的成員簡單的加在一起就成爲了派生類。構造一個派生類包括以下3個部分:

1、從基類接受成員

   派生類把基類全部成員(不包括構造和析構函數)接受過來。不能選擇接受一部分而捨棄另一部分,這是不可選擇的。因此如果不能合理的選擇基類的話,會造成數據的冗餘。

2、調整從基類接收的成員

   雖然接受基類成員是不可選擇的,但是可與對這些成員做出調整,例如同繼承方式改變基類成員在派生類中的訪問屬性。此外,還可以在派生類中聲明一個與基類成員同名的成員,則派生類中的新成員會覆蓋基類的同名成員。要注意,如果是成員函數的話,不僅要求函數名相同,還要求函數的參數列表也相同。

3、在派生類中增加新的成員

   基類只是提供了最基本的功能,而另有些功能未實現,所以就需要在聲明派生類時加入某些具體的功能,形成適用於某一特定應用的派生類。此外,在聲明派生類時,一般還有定義適用於派生類的構造函數和析構函數。


四、派生類成員的訪問屬性

1、基類成員函數只能訪問基類成員

2、派生類成員函數可以訪問派生類自己增加的成員

3、基類的成員函數不能訪問派生類新增加的成員。

4、派生類成員函數在類內或類外訪問基類的成員,這種情況比較複雜,它不僅取決於基類成員在基類中的訪問屬性,還要考慮派生類所聲明的對基類的繼承方式。



spacer.gif

通過上表可以看出,在派生類中有4中不同的訪問屬性:

1、公用的,在派生類內和派生類外都可以訪問。

2、受保護的,派生類內可以訪問,派生類外不可以訪問。

3、私有的,只能在派生類內進行訪問。

4、不可訪問的,或者說不能直接訪問的,可以通過基類成員函數進行間接訪問。


五、派生類的構造函數

  因爲基類的構造函數和析構不能通過繼承得到,因此,對繼承過來的基類成員的初始化工作也要由派生類的構造函數承擔,所以就要在執行派生類的構造函數時,調用基類的構造函數。其一般形式爲:

    派生類構造函數名(總參數列表):基類構造函數名(參數列表)

   {派生類中新增加數據成員初始化語句}


   基類成員的初始化要通過初始化列表進行初始化。總參數列表中包含了基類構造函數所需要的參數和對派生類自增的數據成員初始化所需要的參數。而基類構造函數名後面的參數列表中只有參數名而不包括參數類型,因爲在這裏是調用基類的構造函數,這些參數是實參。

例:

classTime

{

public:

                Time(inthour= 0,intminute= 0,intsec= 0) :_hour(hour)

                                , _minute(minute)

                                , _sec(sec)

                {

                }

private:

                int_hour;

                int_minute;

                int_sec;

};



classDate:publicTime

{

public:

                Date(inthour= 0,intminute= 0,intsec= 0,intyear= 0,intmonth= 0,intday= 0) :Time(hour,minute,sec)                            //調用基類的構造函數對基類成員進行初始化

                {

                                _year =year;

                                _month =month;

                                _day =day;

                }

private:

                int_year;

                int_month;

                int_day;

};

有子對象的派生類,對子對象初始化也是同樣的方法。


所以派生類構造函數應該包含3個部分:

1、對基類數據成員初始化

2、對子對象初始化

3、對派生類數據成員初始化


   系統時自動調用基類的構造函數的,將派生類的構造函數寫成上面那種方式只是爲了給基類的構造函數傳參而已,假如不寫成上面那種形式,系統還是會調用基類的默認構造函數的(子對象也是相同的道理)。系統先調用基類的構造函數再執行派生類的構造函數。


六、派生類中的析構函數

   我們只需要定義派生類中新增成員的構造函數即可。基類的析構函數和子對象的析構函數是系統自動調用的。調用析構函數的順序與調用構造函數的順序相反,先執行派生類的析構函數再調用基類的析構函數。


七、多重繼承

多重繼承:運行一個派生類同時繼承多個基類。

1、聲明多重繼承的方法

  例:如果已經聲明瞭類A、類B、類C,可以聲明多重繼承的派生類D:

    class D:private A,protected B,public C

    { 類D新增的成員};

  D是多重繼承的派生類,它以私有繼承方式繼承A類,以保護繼承的方式繼承B類,以公用繼承的方式繼承C類。


2、多重繼承派生類的構造函數

   多重繼承派生類的構造函數形式與單繼承的構造函數形式基本相同,只是在初始化表中包含多個基類構造函數。如:

    派生類構造函數名(總參數列表):基類1構造函數(參數列表),基類2構造函數(參數列表)...

       {派生類中新增數據成員初始化語句 }; 


3、多重繼承引發的問題

多重繼承會引發二義性問題,即繼承的成員同名,這時引用的話會產生二義性而發生錯誤。

  例:如果類A和類B中都有數據成員name和成員函數display。如果類C是類A和類B的直接派生類。

  如果在main函數中有:

        C c1;

        c1.name;

        c1.display();

   因爲類A和類B都有數據成員name和成員函數display,系統無法判別要訪問的是哪個基類的成員,因此編譯出錯。要解決      這個問題,可以用基類名來限制作用域:

        c1.A::name;

        c1.A::display();     //這樣就表示引用的是類A中的成員。


八、虛基類

假如一個派生類有多個直接基類,而這些直接基類又有一個共同的基類。如圖:


spacer.gif

   那麼就會出現一個問題,在類E中會保存3份類A的成員,如果人們只需要一份類A的成員,那麼這種情況下就會佔用較多的內存空間,還增加訪問的難度。而在實際中人們往往只需要一份類A的成員。爲了解決這個問題,C++中提供了虛基類的方法,使得在繼承間接共同基類時只保留一份成員。

現在將類A聲明爲虛基類:

class A

{...};

class B:virtual public A    //A是B的虛基類

{...};

class C:virtual public A    //A是C的虛基類

{...};

class D:virtual public A    //A是D的虛基類

{...};

注意:虛基類並不是在聲明基類時聲明的,而是在聲明派生類時,指定繼承方式時聲明的。



聲明虛基類的一般方法如下:

class 派生類名:virtual 繼承方式 基類名


   經過虛基類的聲明後,當基類通過多條派生路徑被一個派生類繼承時,該派生類值繼承該派生類一次,即基類成員派生類只保留一次。


1、虛基類的初始化

class A

{

A(int i=0){}

...};


class B:virtual public A    //A是B的虛基類

{

B(int j=2):A(j){}

...};


class C:virtual public A    //A是C的虛基類

{

 C(int k=3):A(k)()

...};


class D:virtual public A    //A是D的虛基類

{

D(int n=4):A(n){}

...};


class E:public B,public C,public D

{

E(int i=0,int j=0,int k=0,int n=0):A(i),B(j),C(k),D(n){}

...};


注意:

   在定義類E的構造函數時,與以往使用的方法有所不同,由於虛基類在派生類中只有一份數據成員,所以這份數據成員必須由派生類之間給出。規定:在最後的派生類中不僅要負責對其之間基類進行初始化,還有負責對虛基類進行初始化。C++編譯系統只執行最後的派生類對虛基類的構造函數的調用,而忽略虛基類的其他派生類對虛基類構造函數的調用。


九、基類與派生類的轉換

   派生類是繼承了基類的數據成員,那麼基類對象與派生類對象之間是否存在賦值關係,可否進行類型的轉換???

   答案是可以的。基類對象與派生類對象之間 有賦值兼容的關係,由於派生類中包含從基類中繼承的成員,因此可以將派生類的值賦給基類對象,在使用基類對象的時候可以用派生類對象替代。但是注意,這種關係是單向的,不可逆的。


具體有以下4個方面:

1、派生類對象可以向基類對象賦值,這種關係是單向的。

2、派生類對象可以代替基類對象向基類對象的引用進行賦值或初始化。

3、如果函數的參數是基類對象的或基類對象的引用,相應的實參可以是派生類的對象。

4、派生類的地址可以賦給指向基類對象的指針。即指向基類的指針可以指向派生類的對象。不過,指向的是派生類對象中從基類繼承的部分。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章