C++學習隨筆之十:類的繼承

來源:互聯網
上載者:User

1.基類與衍生類別:

從一個類派生出另一個類時,原始類稱為基類,繼承類稱為衍生類別。基本文法是:class SubClassName:public BaseClassName{};public表示公有派生,當然也有私人派生和受保護派生(private和protected)。衍生類別對象包含基類對象。使用公有派生,基類的公有成員將成為衍生類別的公有成員,基類的私人部分也將稱為衍生類別的一部分,但只能通過基類的公有和保護方法訪問。

衍生類別不能直接存取基類的私人成員,而必須通過基類的的類方法進行訪問。

衍生類別建構函式:

(1)基類對象首先被建立

(2)衍生類別建構函式應通過成員初始化列表基類資訊傳遞給基類建構函式:如:

RatePlayer::RatePlayer(int r,cosnt char *fn,const  char *ln):TableTennisPlayer(fn,ln)

{

rating = r;

}

基類對象必須首先被建立,如果不調用基類建構函式,則程式使用預設的基類建構函式。一般情況,除非要使用預設建構函式,否則應該顯式調用基類的建構函式。

(3)衍生類別構造哈市應初始化衍生類別新增的資料成員

注意:建立衍生類別對象時,程式首先調用基類的建構函式,然後再調用衍生類別建構函式。基類建構函式複製初始化繼承來的資料成員;衍生類別建構函式主要用於初始化新增的資料成員。衍生類別建構函式總是調用一個基類建構函式。衍生類別對象到期時,程式首先調用衍生類別解構函式,然後再調用基類解構函式。

2.衍生類別和基類直接的特殊關係

衍生類別和基類之間存在著一些微妙的關係,主要有:

(1)衍生類別對象可以使用基類的方法,但是方法不是私人的

(2)基類指標可以在不進行顯示類型轉換的情況下指向衍生類別對象;引用也是,基類引用可以在不進行顯示類型轉換的情況下引用衍生類別對象;不過基類指標和引用只能用於調用基類方法,不能調用衍生類別的方法(不考慮虛函數)

(3)不能將基類對象和地址賦給衍生類別引用和指標。

(4)引用相容性也可以將基類對象初始化為衍生類別對象

(5)也可以將衍生類別對象賦給基類對象

3.繼承————is-a關係

雖然在C++中,完全可以使用公有繼承來建立has-a,is-imlimented-as-a或uses-a關聯式模式,但是為了避免編程方面的問題,建議堅持使用is-a關係。

4.多態公有繼承

當一個方法在基類和衍生類別中的行為不同時,重新修改基類中的那個方法,使之具有多態。一般有兩種機制用於實現多態公有繼承:

(1)在衍生類別中重新定義基類的方法

(2)使用虛函數(虛方法)

第一種就不說了,說說第二種吧,虛函數,關鍵字是virtual,在聲明中需要virtual關鍵字,在定義實現中不用。

至於為什麼要使用虛函數呢?

如果方法是通過引用或指標而不是對象調用的,如果沒有採用虛函數方法,則程式根據參考型別或指標類型選擇方法,如果採用虛函數了,則程式根據引用或者指標指向的對象的類型來選擇方法。

例如:有個 Brass類,SubBrass是Brass的衍生類別,都有一個叫Show()的方法,

Brass tom ;

SubBrass jack;

Brass &pt1 = tom;

Brass &pt2 = jack;

pt1.Show();

pt2.Show();

如果show()不是虛函數的話,上面最後兩行分別是

pt1.Show(); //調用Brass::Show();

pt2.Show();//調用Brass::Show();

如果show()是虛函數的話,上面最後兩行分別是

pt1.Show(); //調用Brass::Show();

pt2.Show();//調用SubBrass::Show();

虛擬解構函式:是為了確保釋放對象的時候按照正確的順序調用洗過後函數。

注意:通常如果在衍生類別中重新定義基類的方法,一般都應將基類方法聲明為徐您的,這樣程式將根據引用或指標指向的對象來選擇方法。為基類聲明一個虛擬解構函式也是一種慣例。

靜態聯編和動態聯編:

聯編:將原始碼中函數調用解釋為執行特定的函數代碼成為函數名聯編。C/C++在編譯過程中進行的聯編稱之為靜態聯編,又稱為早期聯編;在程式運行過程中聯編稱之為動態聯編,也稱為晚期聯編。編譯器對非虛擬方法使用靜態聯編,對虛擬方法使用動態聯編。

注意:如果要在衍生類別中重新定義基類的方法,則將它設定為虛方法;否則設定為非虛方法。

虛函數的工作機制:

C++規定了虛函數的行為,但將實現方法留給了編譯器作者。不需要知道實現方法就可以使用虛函數,但是瞭解虛函數的工作原理有助於更好的理解虛函數:

通常,編譯器處理虛函數的方法是:給每個類對象添加一個隱藏成員。隱藏成員中儲存了一個指向函數地址數組的指標。這種數組稱為虛函數表(virtual function table,vtbl)。虛函數表中儲存了為類對象進行聲明的虛函數的地址。例如,基類對象包含一個指標,該指標指向基類中所有虛函數的地址表。衍生類別

將包含一個指向對立地址表的指標。如果衍生類別提供了虛函數的新定義,該虛函數表將儲存新函數的地址,如果衍生類別沒有重新定義虛函數,則該vtbl將儲存

函數原始版本的地址。如果衍生類別定義了 新的虛函數,則該函數的地址也將被添加到vtbl中。但是注意,無論類中包含多少個虛函數,都只需要在對象中添加1個地址成員表,只是表的大小不一樣。

有關虛函數的注意事項:

(1)在基類方法的聲明中使用關鍵字virtual可使該方法在基類以及所有的衍生類別(包括從衍生類別的派生出來的衍生類別)中是虛擬,因為方法在基類中被聲明為虛擬後,它在衍生類別中自動預設為虛擬。

(2)如果使用指向對象的引用或指標來調用虛方法,程式將使用為物件類型定義的方法,而不使用為引用或指標類型定義的方法。這個就是動態聯編(晚期)聯編。這個行為非常重要,因為這樣就可以使得基類指標或引用可以指向衍生類別對象

(3)如果定義的類將被用作基類,則應將那些要在衍生類別中重新定義的方法聲明為虛方法。

(4)建構函式不能是虛函數,因為衍生類別不能繼承建構函式,所以沒有什麼意義

(5)虛構函數應當是虛函數,除非類不做基類。通常應該給基類提供一個虛解構函式,即使它不需要解構函式。

(6)友元函數不能是虛函數,因為友元不是類成員,而只有類成員才能是虛函數。如果由於這個原因而導致了設計問題 ,可以通過友元函數使用虛函數來解決。

(7)如果衍生類別沒有重新定義函數,將使用該函數的基類版本。如果衍生類別位於衍生類別鏈中,則將使用最新的虛函數方法辦法,如果基類是隱藏的除外。

(8)重新定義 隱藏方法:

class BaseClass

{

public:

virtual void Show(int a)const;

...

}

class SubClass : public BaseClass

{

public:

virtual void Show()const;

...

}

有如下代碼

SubClass sc;

sc.Show();//這個是有效,調用的是SubClass的方法

sc.Show(5);//這個是無效的,因為SubClass 類中的方法已經隱藏了基類BassClass的Show()方法。

這就出現了兩條經驗規則:

第一,如果重新定義繼承的方法,應確保與原來的原型完全一致,但如果傳回型別是基類引用或指標,則可以修改為指向衍生類別的引用或指標。這種特性稱為

傳回型別協變(covariance of return type),因為傳回型別隨類類型的變化而變化:

lass BaseClass

{

public:

virtual BaseClass & Show(int a)const;

...

}

class SubClass : public BaseClass

{

public:

virtual SubClass & Show()const;

...

}

第二,如果基類聲明被重載了,則應在衍生類別中重新定義所有的基類版本。

class BaseClass

{

public:

virtual void Show(int a)const;

virtual void Show(double x)const;

virtual void Show()const;

...

}

class SubClass : public BaseClass

{

public:

virtual void Show(int a)const;

virtual void Show(double x)const;

virtual void Show()const;

...

}

這是因為如果只重新定義一個版本,其他另外兩個版本將被隱藏,衍生類別對象無法使用它們。還有就是,如果不需要修改,則新定義可只調用基類版本。

5.Protected存取控制:

protected和private 相似,在類外只能用公有類成員訪問protected部分中的類成員。protected與private的區別只有在衍生類別中能體現出來,衍生類別的成員可以直接存取基類的保護成員,但不能訪問基類的私人成員,因此,對於外部,保護成員的行為與private相似,對於衍生類別來說,保護成員與public相似。

警告:最好對類成員資料採用私人存取控制,不要使用保護存取控制;同時通過基類方法使衍生類別能夠訪問基類資料。但是對於成員函數是,保護存取控制很有用,它能讓衍生類別能夠訪問公眾不能使用的內建函式。

6.單設計模式:

希望有且只有一個類的實列返回給調用程式時,就可以使用單元素模式(singleton pattern):

  class  TheOnlyInstance

{

public:

static TheOnlyInstance * GetTheOnlyInstance(); //Factory 方法,用來獲得執行個體

protected:

TheOnlyInstance(){}

};

通過 把建構函式聲明為protected,並去掉公有構造。防止局部執行個體被建立。

TheOnlyInstance the; // not allowed

只能通過公有靜態方法GetTheOnlyInstance 來訪問類。

通過 把建構函式聲明為protected,並去掉公有構造。防止局部執行個體被建立。

TheOnlyInstance *  TheOnlyInstance : GetTheOnlyInstance()

{

static TheOnlyInstance obj;

return &obj;

}

檢索指向這個類的指標,只需要調用GetTheOnlyInstance()

TheOnlyInstance* pTheOnlyInstance = TheOnlyInstance: GetTheOnlyInstance();

單設計模式C++代碼實現:

#include <iostream>   

using namespace std;   

//單例類的C++實現   

class Singleton   

{   

private:   

Singleton();//注意:構造方法私人   

virtual ~Singleton();   

static Singleton* instance;//惟一執行個體   

int var;//成員變數(用於測試)   

public:   

static Singleton* GetInstance();//Factory 方法(用來獲得執行個體)   

int getVar();//獲得var的值   

void setVar(int);//設定var的值   

};   

//構造方法實現   

Singleton::Singleton()   

{   

this->var = 20;   

cout<<"Singleton Constructor"<<endl;   

}   

Singleton::~Singleton()   

{   

     delete instance;   

}   

//初始化靜態成員   

Singleton* Singleton::instance=new Singleton();   

Singleton* Singleton::GetInstance()   

{   

return instance;   

}   

//seter && getter函數   

int Singleton::getVar()   

{   

return this->var;   

}   

void Singleton::setVar(int var)   

{   

this->var = var;   

}   

//main   

int main(int argc, char* argv[])   

{   

Singleton *ton1 = Singleton::GetInstance();   

Singleton *ton2 = Singleton::GetInstance();   

cout<<"ton1 var = "<<ton1->getVar()<<endl;   

ton1->setVar(150);

cout<<"ton2 var = "<<ton2->getVar()<<endl;

return 0;   

}

輸出如下:

Singleton Constructor

   ton1 var = 20

   ton2 var = 150

在輸出結果中,構造方法只調用了一次,ton1與ton2是指向同一個對象的。

  

7.抽象基類和純虛函數:

★抽象類別:一個類可以抽象出不同的對象來表達一個抽象的概念和通用的介面,這個類不能執行個體化(創造)對象。

★純虛函數(pure virtual):在本類裡不能有實現(描述功能),實現需要在子類中實現。

例:

 virtual typeT function_name(parameter_list)=0;

 

 virtual void draw()=0; //畫,純虛函數;

 virtual void rotate(double)=0; //旋轉,純虛函數;

★抽象類別(abstract class):如果一個類包含純虛函數,那麼這個類就叫抽象類別。

★一個抽象類別只能用作基類,不能能用作派生,不能執行個體化(建立)對象。一個類要是包含至少一個純虛函數,則這個類是抽象類別。一個抽象類別的子類可以添加更多的資料成員和成員函數。

★抽象類別的子類可以還是抽象類別,可以添加更多的成員函數和成員方法,直到可以產生對象為止。

★由於抽象類別不能構造對象,因此它的建構函式不能被單獨調用。它的建構函式只能在子類的成員初始化列表裡面調用。

★抽象類別不一定有解構函式,如果有必須是虛解構函式。

★★★一個函數不能有抽象類別對象的值參數<參數不能傳值>,這個函數不能有抽象類別對象的值返回。然而可以有抽象類別類型的指標和引用可以作為參數,同樣抽象類別的指標和引用可以作為函數的傳回值類型。因為他們可以指向或者引用抽象類別的子類對象。

抽象基類介面規則:抽象基類要求具體衍生類別覆蓋其純虛函數,迫使衍生類別遵循抽象基類所設定的規則。

8.繼承和動態記憶體分配:

(1)派生不使用new

先調用派生解構函式對派生新成員進行處理,再調用基類的解構函式。(通常需要在基類中把解構函式定義為虛擬,因為

在調用基類引用或指標的時候,解構函式就只能處理基類對象的,衍生類別將無法處理,所以定義為虛擬後,會安要求

順序,正確處理)

如衍生類別沒用new則,在衍生類別中,預設解構函式,預設複值建構函式,預設賦值函數都符合要求,也即是說不需要定義顯示解構函式,複製建構函式和賦值操作符

(2)衍生類別使用new

如果衍生類別使用new,則必須為衍生類別定義顯示解構函式,複製建構函式和賦值操作符。

衍生類別的友元函數:通常需要在其中嵌套調用基類的對應友元函數,完成對基類成員的操作

9.靜態成員的繼承:

在一個類中還可以定義靜態成員,但靜態成員是所有對象公有的。靜態成員分為待用資料成員和靜態成員函數。

1.待用資料成員

在類中定義待用資料成員的方法就是在該成員的前面加上關鍵字static.

定義待用資料成員的語句格式如下:

class 類名

{

……

static 類型說明符 成員名;

……

};

待用資料成員是類的所有對象共用的成員。待用資料成員所佔的空間不會隨著對象的產生而分配,也不會隨著對象的消失而回收。對待用資料成員的操作和類中一般資料成員的操作是不一樣的,定義為私人的待用資料成員不能被外界所訪問。待用資料成員可由任意存取權限許可的函數所訪問。

由於待用資料成員是類的所有對象共用的,而不從屬於任何一個具體對象,所以必須對類的待用資料成員進行初始化,但對它的初始化不能在類的建構函式中進行,其初始化語句應當寫在程式的全域地區中,並且必須指明其資料類型與所屬的類名,其初始化格式如下:

類型 類名::變數名=值;

對於在類的public部分說明的待用資料成員,在類的外部可以不使用成員函數而直接存取,但在使用時必須用類名指明所屬的類,其訪問格式為:

類名::待用資料成員名

對於在類的非public部分說明的待用資料成員,則只能由類的成員函數訪問,其存取方法與訪問類中普通資料成員的存取方法完全一樣,但在類的外部不能訪問。

2.靜態成員函數

靜態成員函數的定義與一般成員函數的定義相同,只是在其前面冠以static關鍵字,其定義格式如下:

class 類名

{

static 類型 函數名(形參)

{   函數體   }

};

說明:

(1)類的靜態成員函數只能訪問類的待用資料成員,而不能訪問類中的普通函數成員(非待用資料成員),因為普通資料成員只有類的對象存在時才有意義。

(2)靜態成員函數與類相聯絡,而不與類的對象相聯絡,所以,在類的外部調用類中的公有靜態成員函數,必須在其左面加上“類名::”,而不是通過對象名調用公有靜態成員函數。在類的外部不能調用類中的私人靜態成員函數。

10.類總結:

學到現在了,對類繼承有了比較深的瞭解了,現在就到目前為止作個總結吧。

(1)衍生類別從基類那裡繼承了什嗎?又不能從基類那裡繼承到什麼呢?

基類的公有成員成為衍生類別的公有成員,基類的保護成員成為了衍生類別的保護成員,基類的私人成員被繼承,但是不能直接存取。不能繼承的有建構函式、解構函式、賦值操作符和友元。

(2)建構函式:

建構函式不同於其他函數,因為它要建立對象,要麼沒有參數,要麼所有參數都要有預設值。

(3)解構函式:

一定要定義顯示解構函式來釋放類建構函式使用new來發呢跑的所有記憶體,並完成類對象的所需的任何特殊的清理工作。對於基類,即使它不需要建構函式,也應提供一個虛擬解構函式。

(4)賦值操作符:

是不能被繼承的,如果類建構函式使用new來初始化指標,則需要提供一個顯示賦值操作符,因為對於派生對象的基類部分,C++將使用基類的操作符,所以不需要為衍生類別重新定義賦值操作符,除非它添加了需要特別留意的資料成員。不過,如果衍生類別使用new,則必須提供顯示賦值操作符。必須給類的每個成員提供賦值操作符,不僅僅是新成員。

將衍生類別對象賦給基類對象會如何呢?答案是 可以,但只涉及到基類的成員。那反過來呢,將基類對象賦給衍生類別對象呢?答案是也許。如果衍生類別包含了這樣的建構函式,即對將基類對象轉換為衍生類別對象進行了定義,則可以將基類對象賦給衍生類別對象。如果衍生類別定義了用於將基類對象賦給衍生類別對象的賦值操作符,則也可以這樣做。如果上述連個條件都不滿足,則不能將基類對象賦給衍生類別對象,除非使用顯示強制類型轉換。

(5)友元:

由於友元非類成員,故不能繼承。但是,如果希望衍生類別的友元函數能夠使用基類的友元函數,可以這樣做,即通過強制類型轉換將衍生類別引用或坐或站轉換為基類引用或指標,然後使用轉換後的指標或引用來調用基類的友元函數:

ostream & operator<<(ostream & os ,const SubClassName & sc)

{

os <<(const BaseClassName &)sc;//轉換衍生類別的引用為基類的引用

...

return os;

}

當然也可以用操作符dynamic_cast<>來進行強制類型轉換。

(6)虛方法:

在設計基類時,要注意是否要將類方法聲明為虛擬。如果希望衍生類別能夠重新定義方法,則應在基類中將方法聲明為虛擬。如果不希望的話,則不必。

(7)使用基類方法的有關說明:

 ● 衍生類別對象自動使用類繼承的基類方法,如果衍生類別沒有重新定義

  ● 衍生類別的建構函式自動調用基類的函數

  ● 衍生類別的建構函式自動調用基類的預設建構函式,如果沒有在成員初始化列表中指定其他建構函式。

  ● 衍生類別建構函式顯示地調用成員初始化列表中指定的基類建構函式

  ● 衍生類別方法可以使用範圍解析操作符來調用公有的和受保護的基類方法

  ● 衍生類別的友元函數可以通過強制類型轉換,將衍生類別引用或坐或站轉換為基類引用或指標,然後使用轉換後的指標或引用來調用基類的友元函數

(8)類成員函數屬性總結表:

   成員函數屬性

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.