青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

聚星亭

吾笨笨且懶散兮 急須改之而奮進(jìn)
posts - 74, comments - 166, trackbacks - 0, articles - 0
  C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

       當(dāng)我們進(jìn)一步研究類與對象的時(shí)候,難免的就要考慮到類本身的一些特點(diǎn)以及類與其它類之間的關(guān)系。在本專題開始之前,我們已經(jīng)接觸到像一個(gè)類對象作為另一個(gè)類成員的嵌套關(guān)系了。本專題,我們就專心的研究一下類與類之間的繼承關(guān)系和其類本身的特點(diǎn)。

 

       我們知道,類與對象的概念是來自于對現(xiàn)實(shí)事物的模擬,就像孩子用于其父母的一些特征,不論是木桌還是石桌都有桌子的特點(diǎn)。同樣,類與類之間自然的也應(yīng)該擁有這些特點(diǎn)的。而擁有這些特點(diǎn)就使得我們代碼更加結(jié)構(gòu)化,條理化,最大的好處則是:簡化我們的代碼,提高代碼的重用性。

 

       好,不多廢話,先讓我們看看,這個(gè)專題大概要講些什么:

1、      體驗(yàn)類的靜態(tài)多態(tài)性之重載

2、      構(gòu)建類與類之間的父子關(guān)系及其訪問限制

3、      體驗(yàn)類的動態(tài)多態(tài)性之虛函數(shù)

4、      淺析類的多繼承

5、      學(xué)習(xí)小結(jié)

 

從這個(gè)目錄可以看出這個(gè)專題內(nèi)容非常的關(guān)鍵而且非常的龐雜。本來我是想將它們分成兩個(gè)專題,分別講述的。可是鑒于它們之間好多的知識點(diǎn)相互參雜,沒有辦法很好的分離,為了不給各位讀者遺留困惑,我決定將他們合到一起,希望各位能慢慢體會其中的奧秘,從根本上掌握它們。

 

好廢話不多說,我們進(jìn)入正題。

一、       體驗(yàn)類的靜態(tài)多態(tài)性之重載

重載,當(dāng)時(shí)我理解了半天沒弄明白是什么意思,現(xiàn)在才知道,就是用模樣相同的東西實(shí)現(xiàn)不同的功能,下面我們分別看一下它們的用法。

1、 函數(shù)重載與缺省參數(shù)

A、函數(shù)重載的實(shí)現(xiàn)原理

假設(shè),我們現(xiàn)在想要寫一個(gè)函數(shù)(如Exp01),它即可以計(jì)算整型數(shù)據(jù)又可以計(jì)算浮點(diǎn)數(shù),那樣我們就得寫兩個(gè)求和函數(shù),對于更復(fù)雜的情況,我們可能需要寫更多的函數(shù),但是這個(gè)函數(shù)名該怎么起呢?它們本身實(shí)現(xiàn)的功能都差不多,只是針對不同的參數(shù):

int sum_int(int nNum1, int nNum2)

{

    return nNum1 + nNum2;

}

 

double sum_float(float nNum1, float nNum2)

{

    return nNum1 + nNum2;

}

 

C++中為了簡化,就引入了函數(shù)重載的概念,大致要求如下:

1、    重載的函數(shù)必須有相同的函數(shù)名

2、    重載的函數(shù)不可以擁有相同的參數(shù)

 

這樣,我們的函數(shù)就可以寫成:

int sum (int nNum1, int nNum2)

{

    return nNum1 + nNum2;

}

 

double sum (float nNum1, float nNum2)

{

    return nNum1 + nNum2;

}

到現(xiàn)在,我們可以考慮一下,它們既然擁有相同的函數(shù)名,那他們怎么區(qū)分各個(gè)函數(shù)的呢?相信聰明的你一定根據(jù)上面的要求推測出來了,是的,名稱粉碎。很簡單,我們來驗(yàn)證一下我的說法,繼續(xù)打開Exp01工程,點(diǎn)擊菜單欄的”project”à”settings”:

勾選“Generate mapfile”選項(xiàng),然后重新編譯程序,到Debug目錄下找到Exp01.map文件,用記事本打開它:

Address

Publics by Value

Rva+Base

Lib:Obj

0001:00000050

?sum_int@@YAHHH@Z

00401050

f

Exp01.obj

0001:00000080

?sum_float@@YAMMM@Z

00401080

f

Exp01.obj

0001:000000b0

?TestCFun@@YAXXZ

004010b0

f

Exp01.obj

0001:00000130

?sum@@YAHHH@Z

00401130

f

Exp01.obj

0001:00000160

?sum@@YAMMM@Z

00401160

f

Exp01.obj

0001:00000190

?TestCplusFun@@YAXXZ

00401190

f

Exp01.obj

0001:00000210

_main

00401210

f

Exp01.obj

 

哈哈,將參數(shù)信息與函數(shù)名粉碎了并整合成了一個(gè)新的函數(shù)名,今后,我們在編寫C++程序的時(shí)候,調(diào)試、排錯(cuò)都難免與這些粉碎后的函數(shù)名打交道,好多的朋友為了解決這個(gè)問題想出了各種方法,記得在看雪壇子上有一篇名叫《史上最牛資料助你解惑c++調(diào)試》的文章,大致上把粉碎后的函數(shù)名各部分的含義都解釋出來了,其實(shí)沒有這個(gè)必要的,我們用的VC開發(fā)環(huán)境中已經(jīng)提供了一個(gè)工具(UNDNAME.EXE),它可以解析這些粉碎后的函數(shù),當(dāng)然如果我們逆向分析它,很容易就可以知道,其實(shí)就是調(diào)用一個(gè)API函數(shù):

UnDecorateSymbolName(m_szFuncName, m_szResultInfo.GetBuffer(0), MAX_PATH,\

              UNDNAME_32_BIT_DECODE|UNDNAME_NO_RETURN_UDT_MODEL|\

            UNDNAME_NO_MEMBER_TYPE|UNDNAME_NO_THROW_SIGNATURES|\

           UNDNAME_NO_THISTYPE|UNDNAME_NO_CV_THISTYPE);

                    

OK,我們隨便輸入粉碎的一個(gè)函數(shù)名看看效果:

 

B、缺省參數(shù)

如果用Win32API寫過程序的朋友一定知道,好多的函數(shù)存在許多參數(shù),而且大部分都是NULL,倘若我們有個(gè)函數(shù)大部分的時(shí)候,某個(gè)參數(shù)都是固定值,僅有的時(shí)候需要改變一下,而我們每次調(diào)用它時(shí)都要很費(fèi)勁的輸入?yún)?shù)豈不是很痛苦?C++提供了一個(gè)給參數(shù)加默認(rèn)參數(shù)的功能,例如:

double sum (float nNum1, float nNum2 = 10);

 

我們調(diào)用時(shí),默認(rèn)情況下,我們只需要給它第一個(gè)參數(shù)傳遞參數(shù)即可,但是使用這個(gè)功能時(shí)需要注意一些事項(xiàng),以免出現(xiàn)莫名其妙的錯(cuò)誤,下面我簡單的列舉一下大家了解就好。

A、 默認(rèn)參數(shù)只要寫在函數(shù)聲明中即可。

B、 默認(rèn)參數(shù)應(yīng)盡量靠近函數(shù)參數(shù)列表的最右邊,以防止二義性。比如

double sum (float nNum2 = 10float nNum1);

這樣的函數(shù)聲明,我們調(diào)用時(shí):sum15;程序就有可能無法匹配正確的函數(shù)而出現(xiàn)編譯錯(cuò)誤。

2、 淺析運(yùn)算符重載

運(yùn)算符重載也是C++多態(tài)性的基本體現(xiàn),在我們?nèi)粘5木幋a過程中,我們經(jīng)常進(jìn)行+、—、*/等操作。在C++中,要想讓我們定義的類對象也支持這些操作,以簡化我們的代碼。這就用到了運(yùn)算符重載。

 

比如,我們要讓一個(gè)日期對象減去另一個(gè)日期對象以便得到他們之間的時(shí)間差。再如:我們要讓一個(gè)字符串通過“+”來連接另一個(gè)字符串……

 

要想實(shí)現(xiàn)運(yùn)算符重載,我們一般用到operator關(guān)鍵字,具體用法如下:

返回值  operator 運(yùn)算符(參數(shù)列表)

{

         // code

}

例如:

CMyString Operator +(CMyString & csStr)

{

int nTmpLen = strlen(msString.GetData());

if (m_nSpace <= m_nLen+nTmpLen)

{

char *tmpp = new char[m_nLen+nTmpLen+sizeof(char)*2];

strcpy(tmpp, m_szBuffer);

strcat(tmpp, msString.GetData());

delete[] m_szBuffer;

m_szBuffer = tmpp;

}

}

 

當(dāng)然,運(yùn)算符重載也存在一些限制,在我們編碼的過程中需要注意一下:

A、 不能使用不存在的運(yùn)算符(如:@**等等)

B、 “::. .*”運(yùn)算符不可以被重載。

C、 不能改變運(yùn)算符原有的優(yōu)先級和結(jié)核性。

D、不能改變操作數(shù)的數(shù)量。

E、 只能針對自定義的類型做重載。

F、 保留運(yùn)算符原本的含義

二、       構(gòu)建類與類之間的父子關(guān)系及其訪問限制

1、 繼承代碼的定義方法及其內(nèi)存布局

看代碼:

// 基類

class BaseCls 

{

private:

    int  m_na;

    int  m_nb;

public:

    int GetAValue() const

    {

        return m_na;

    }

    int GetBValue() const

    {

        return m_nb;

    }

 

    void SetAValue(int nA);

    void SetBValue(int nB);

 

    BaseCls();

    ~BaseCls();

};

                     相信大家看明白上面的代碼應(yīng)該是非常容易的。OK,我們繼續(xù):

class SubCls : public BaseCls  // 以共有的方式繼承BaseCls

{

private:

    int m_nc;

 

public:

    int GetCValue() const

    {

        return m_nc;

    }

    void SetCValue(int nC);

 

    SubCls();

    ~SubCls();

 

};

OK,倘若我們要給SubCls增加一個(gè)成員函數(shù),來計(jì)算m_nA+m_nB+m_nC的和:

int  SubCls::GetSum()

{

    //return m_na+m_nb+m_nc; //  沒有權(quán)限訪問m_nam_nb

    return  GetAValue()+ GetBValue() + m_nc;

}

現(xiàn)在我們來看下它的內(nèi)存結(jié)構(gòu):

由此可見,對于簡單的繼承關(guān)系,其子類內(nèi)存布局,是先有基類數(shù)據(jù)成員,然后再是子類的數(shù)據(jù)成員,當(dāng)然后面講的復(fù)雜情況,本規(guī)律不一定成立。

2、 關(guān)于繼承權(quán)限的說明及其改變

什么時(shí)候SubCls類才能直接使用父類中的變量呢,前人已經(jīng)為我們總結(jié)了一個(gè)張表:

       基類訪問屬性

繼承權(quán)限

public

protected

private

public

public

protected

不可訪問

protected

protected

protected

不可訪問

private

private

private

不可訪問

對于基類的私有成員,它的子類都無法直接訪問,只有通過相應(yīng)的Get/Set方法來操作它們(get/set方法必須是public/protected權(quán)限)。換句話說:基類的private成員雖然不能直接被訪問,但是他們在子類對象的內(nèi)存中仍然存在,可以通過指針來讀取它們。

 

當(dāng)一個(gè)類中的成員變量為publi權(quán)限,但它子類繼承它時(shí)采用了priva方式繼承,這樣它就變成了private權(quán)限,我們要讓它繼續(xù)變?yōu)?/span>public權(quán)限,可以對它重新調(diào)整。比如:

class BaseCls 

{

public:

    int  m_nPub;

};

 

class SubCls : private BaseCls 

{

public:

     using BaseCls::b3;    // 調(diào)整變量權(quán)限為publi

}

                注意,調(diào)整成員訪問權(quán)限的前提是:基類成員在子類中是可見的,沒有被隔離。

三、       體驗(yàn)類的動態(tài)多態(tài)性之虛函數(shù)

之前我們講述的多態(tài)性,像函數(shù)重載,運(yùn)算符重載,拷貝構(gòu)造等都是在編譯器完成的多態(tài)性,我們稱之為靜態(tài)多太性,現(xiàn)在我們討論下運(yùn)行期間才體現(xiàn)出來的多態(tài)性——動態(tài)多態(tài)性。我將分成幾個(gè)不同的小結(jié),逐步深入的描述我對虛函數(shù)的理解。

1、 什么是虛函數(shù),用在什么場合

之所以稱為虛函數(shù),是因?yàn)榇祟惡瘮?shù)在被調(diào)用之前誰都不確定它會被誰調(diào)用。換句話說就是:調(diào)用虛函數(shù)的方式不同于以往的立即數(shù)直接尋址,而是采用了寄存器間接尋址的方式,因此它調(diào)用的地址并不固定。所以,虛函數(shù)可以通過相同的函數(shù)實(shí)現(xiàn)不同的功能。這便是虛函數(shù)的特點(diǎn)。

 

我可以舉個(gè)例子來說明虛函數(shù)的用途:

假如,我們有一個(gè)家具類,他有一個(gè)成員函數(shù)來獲取家具的價(jià)格,如果家具類派生出了桌子、椅子、床、沙發(fā)……各種對象不計(jì)其數(shù)。這時(shí),如果我們要實(shí)現(xiàn)一個(gè)函數(shù)來輸出用戶指定“家具”的價(jià)格,我想最常規(guī)的做法應(yīng)該是用一個(gè)很深的if……else if ……else結(jié)構(gòu),當(dāng)然,如果你看過我寫的switch的學(xué)習(xí)筆記的話,你或許會用一個(gè)很大的switch結(jié)構(gòu)來判斷用戶選擇了那個(gè)家具,然后創(chuàng)建相應(yīng)的對象,調(diào)用其獲取價(jià)格的方法,打印輸出……

 

當(dāng)然,如果有虛函數(shù),我們就不需要這樣費(fèi)事的寫程序了,我們大可以創(chuàng)建一個(gè)家具類的對象指針,然后讓它直接指向用戶選擇的家具對象,當(dāng)用戶選擇“家具”時(shí),我們不需要確定用戶選擇的是哪個(gè)“家具”,只需要簡單的調(diào)用基類的虛函數(shù)即可。程序在運(yùn)行時(shí),會自動的根據(jù)用戶的不同選擇調(diào)用不同子類的虛函數(shù)……

2、 怎樣使用虛函數(shù)

說了一堆的廢話,或許你真的知道虛函數(shù)是怎么回事了,或許你一定很好奇虛函數(shù)是怎么實(shí)現(xiàn)的,也或許,你可能更加疑惑:到底什么是虛函數(shù)了。

 

都沒關(guān)系,我們先帶著這些問題,一步步的來,先看看如何定義和使用一個(gè)虛函數(shù)以簡化我們的程序。

 

要使用虛函數(shù),異常的簡單,你只要在要制定為虛函數(shù)的聲明前加上Virtual關(guān)鍵字,當(dāng)然,在使用的過程中需要遵循如下規(guī)則:

a)         虛函數(shù)必須在繼承的情況下使用。

b)        若基類中有一個(gè)虛函數(shù),那它所派生的所有子類中所有函數(shù)名、參數(shù)、返回值都相同的成員方法都是虛函數(shù),不論它們的聲明前是否有Virtual關(guān)鍵字。

c)        只有類的成員方法才能聲明為虛函數(shù),但是:靜態(tài)、內(nèi)聯(lián)、構(gòu)造除外。

d)        析構(gòu)函數(shù)建議聲明為虛函數(shù)。

e)         調(diào)用虛函數(shù)時(shí),必須要通過基類對象的指針或者引用來完成調(diào)用,否則無法發(fā)揮虛函數(shù)的特性。

如:下來程序(見Exp03的代碼)

class CBaseCls

{

public:

    int m_nBaseData;

 

    virtual void fun(int x)

    {

        printf("%d in BaseCls...\r\n", x);

    }

};

 

class CSubCls : public CBaseCls

{

public:

    int m_nSubData;

    // 由于它的參數(shù)返回值還有函數(shù)名等都與父類的虛函數(shù)相同,所以它也是虛函數(shù)

    void fun(int x)

    {

        printf("%d in subclass...\r\n", x);

    }

};

 

相信上面的代碼,你應(yīng)該能看明白吧,我們按照上面列出的規(guī)范,使用一下這兩個(gè)類:

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

{

//CSubCls *pSub = (CSubCls*)new CBaseCls;

//pSub->fun(5);

    CBaseCls objBase;

    CSubCls  objSub;

 

    CBaseCls *pBase = new CSubCls;

    pBase->fun(5);

 

      return 0;

}

運(yùn)行結(jié)果:

3、 虛函數(shù)的運(yùn)行機(jī)制

OK,我們調(diào)試一下這段代碼,看看虛函數(shù)的這鐘特性到底是怎么實(shí)現(xiàn)的:

32:       CBaseCls objBase;

0040EDDD   lea         ecx,[ebp-14h]                        ; CBaseClsthis指針

0040EDE0   call        @ILT+15(CBaseCls::CBaseCls) (00401014)    

33:       CSubCls  objSub;

0040EDE5   lea         ecx,[ebp-20h]

0040EDE8   call        @ILT+0(CSubCls::CSubCls) (00401005)

34:

35:       CBaseCls *pBase = new CSubCls;

0040EDED   push        0Ch

0040EDEF   call        operator new (00401350)                ; new一個(gè)空間

0040EDF4   add         esp,4

0040EDF7   mov         dword ptr [ebp-2Ch],eax

0040EDFA   mov         dword ptr [ebp-4],0

0040EE01   cmp         dword ptr [ebp-2Ch],0

0040EE05   je          main+64h (0040ee14)

0040EE07   mov         ecx,dword ptr [ebp-2Ch]               ; 子類的this

0040EE0A   call        @ILT+0(CSubCls::CSubCls) (00401005)  ; 調(diào)用子類構(gòu)造創(chuàng)建子類的臨時(shí)對象

0040EE0F   mov         dword ptr [ebp-70h],eax               ; 保存子類的this指針

0040EE12   jmp         main+6Bh (0040ee1b)

0040EE14   mov         dword ptr [ebp-70h],0

0040EE1B   mov         eax,dword ptr [ebp-70h]               ; 子類的this

0040EE1E   mov         dword ptr [ebp-28h],eax               ; 子類的this

0040EE21   mov         dword ptr [ebp-4],0FFFFFFFFh

0040EE28   mov         ecx,dword ptr [ebp-28h]               ; 子類的this

0040EE2B   mov         dword ptr [ebp-24h],ecx               ; 可見,[ebp-24h]中是子類的this

36:       pBase->fun(5);

0040EE2E   mov         esi,esp

0040EE30   push        5

0040EE32   mov         eax,dword ptr [ebp-24h]

0040EE35   mov         edx,dword ptr [eax]               ; this指針去內(nèi)容(也就是虛函數(shù)指針表簡稱虛表)

0040EE37   mov         ecx,dword ptr [ebp-24h]        ; 傳遞this指針

0040EE3A   call        dword ptr [edx]                        ; 調(diào)用虛表的第0項(xiàng)函數(shù)

0040EE3C   cmp         esi,esp

 

         這時(shí),我們應(yīng)該發(fā)現(xiàn),我們對象的內(nèi)存結(jié)構(gòu)應(yīng)該是這樣的(比以前的內(nèi)存結(jié)構(gòu)多了個(gè)虛函數(shù)表的指針):

由此可知,this指針指向一個(gè)函數(shù)表的首地址,這個(gè)表的每一項(xiàng)都是一個(gè)函數(shù)地址(函數(shù)指針),換句話說,虛函數(shù)的指針都被存放在this指向的這個(gè)虛函數(shù)表中。

 

現(xiàn)在,我們再回頭看上述的程序,猜測一下他的編譯過程。

A、   編譯父類時(shí),先編譯代碼,最后把虛函數(shù)的首地址加入到虛函數(shù)表中。并將虛表的首地址作為本類的第一個(gè)數(shù)據(jù)成員

B、   編譯子類時(shí),先編譯子類的代碼,再把父類的虛函數(shù)表拷貝過來,檢查子類中重新實(shí)現(xiàn)了那些虛函數(shù),一次在子類的虛表中將重新實(shí)現(xiàn)的虛函數(shù)表項(xiàng)覆蓋掉并增加子類中心實(shí)現(xiàn)的虛函數(shù)地址。

那它的執(zhí)行過程已經(jīng)是可以明白的說明了:

A、 我們先分析下上述代碼的執(zhí)行:

CBaseCls *pBase = new CSubCls; // 讓一個(gè)父類指針指向子類的實(shí)例

pBase->fun(5); //調(diào)用虛函數(shù)時(shí),傳遞的是子類的this指針,也就是子類的虛表

再根據(jù)我們上面的分析,很自然的,代碼將調(diào)用子類的函數(shù),輸出的結(jié)果也自然的是子類虛函數(shù)的結(jié)果。

B、 倘若,我們將調(diào)用的代碼換一下:用子類的指針指向父類的實(shí)例。

CSubCls *pSub = new CBaseCls; // 編譯出錯(cuò),需要強(qiáng)轉(zhuǎn)

pSub ->fun(5); //輸出的是父類的虛函數(shù)的結(jié)果

編譯器不支持父類對象向子類類型的轉(zhuǎn)換,我們想一下它們的內(nèi)存結(jié)構(gòu)就可以知道,一般子類的數(shù)據(jù)成員比父類的多,父類想子類轉(zhuǎn)換以后,其指針取成員會存在安全隱患。

C、 由此,我們可以得出結(jié)論:

a)         調(diào)用虛函數(shù)時(shí),傳遞不同類實(shí)例的this指針,就調(diào)用傳遞this對象的虛函數(shù)。

b)        虛函數(shù)的調(diào)用方式:

用基類的指針或引用指向子類的實(shí)例,通過基類的指針調(diào)用子類的虛函數(shù)。

                     說明:一定要用指針或引用去調(diào)用虛函數(shù),否則可能會失去虛函數(shù)的特性。

4、 模擬實(shí)現(xiàn)虛函數(shù)機(jī)制

到這里,我想你一定對虛函數(shù)有一定的了解了,為了加深印象,我們不妨手工用C語言類模擬一個(gè)虛函數(shù)出來(代碼見Exp04:

#include <stdio.h>

 

// 定義函數(shù)指針

class CPerson;

typedef void (*PFUN_TYPE)();

typedef void (CPerson::*PBASEFUN_TYPE)();

 

// 基類的成員。

class CPerson

{

public:

    PBASEFUN_TYPE *m_pFunPoint;//定義函數(shù)指針

 

    CPerson()

    {

           m_pFunPoint = (PBASEFUN_TYPE*)new PFUN_TYPE[2]; // 保存虛函數(shù)指針表

           m_pFunPoint[0] = (PBASEFUN_TYPE)vsayHello;      // 填充虛表項(xiàng)

           m_pFunPoint[1] = (PBASEFUN_TYPE)vsayGoodbye;

    }

 

    ~CPerson()

    {

        // 釋放資源,防止內(nèi)存泄露

           delete [] m_pFunPoint;

    }

 

    void sayHello()

    {

           printf("person::Hello\r\n");

    }

 

    void sayGoodbye()

    {

           printf("person::Goodbye\r\n");

    }

 

    void vsayHello()

    {

           sayHello();

    }

   

    void vsayGoodbye()

    {

           sayGoodbye();

    }

};

 

class CStudent:public CPerson

{

public:

   

  CStudent()

    {

      // 填充虛表項(xiàng),覆蓋父類的成員地址

           m_pFunPoint[0] = (PBASEFUN_TYPE)vsayHello;     

           m_pFunPoint[1] = (PBASEFUN_TYPE)vsayGoodbye;

    }

 

    void sayHello()

    {

           printf("CStudent::Hello\r\n");

    }

 

    void sayGoodbye()

    {

           printf("CStudent::Goodbye\r\n");

    }

 

    void vsayHello()

    {

           sayHello();

    }

   

    void vsayGoodbye()

    {

           sayGoodbye();

    }

};

 

int main()

{

    CStudent objStu;

    CPerson  objPer;

    CPerson *pobjPer = &objStu; // 用基類指針指向子類對象

 

    objPer.vsayHello();         // 用基類對象直接調(diào)用

    objPer.vsayGoodbye();

 

    (pobjPer->*pobjPer->m_pFunPoint[0])();  // 用基類指針調(diào)用

    (pobjPer->*pobjPer->m_pFunPoint[1])();

 

    return 0;

}

  運(yùn)行結(jié)果:


四、       淺析類的多繼承

一個(gè)類可以從多個(gè)基類中派生,也就是說:一個(gè)類可以同時(shí)擁有多個(gè)類的特性,是的,他有多個(gè)基類。這樣的繼承結(jié)構(gòu)叫作“多繼承”,最典型的例子就是 沙發(fā)-床了:

1、 基本概念

相信上圖描述的結(jié)構(gòu)大家應(yīng)該都可以看明白的,SleepSofa類繼承自BedSofa兩個(gè)類,因此,SleepSofa類擁有這兩個(gè)類的特性,但在實(shí)際編碼中會存在如下幾個(gè)問題。

a)         SleepSofa類該如何定義?

Class SleepSofa : public Bed, public Sofa

{

       ….

}

                                   構(gòu)造順序?yàn)椋?/span>Bed à sofa à sleepsofa (也就是書寫的順序)

                    

b)        BedSofa類中都有Weight屬性頁都有GetWeightSetWeight方法,在SleepSofa類中使用這些屬性和方法時(shí),如何確定調(diào)用的是哪個(gè)類的成員?

可以使用完全限定名的方式,比如:

Sleepsofa objsofa;

Objsofa.Bed::SetWeight(); // 給方法加上一個(gè)作用域,問題就解決了。

2、 虛繼承

上節(jié)對多繼承作了大概的描述,相信大家對SleepSofa類有了大概的認(rèn)識,我們回頭仔細(xì)看下Furniture類:

 

倘若,我們定義一個(gè)SleepSofa對象,讓我們分析一下它的構(gòu)造過程:它會構(gòu)造Bed類和Sofa類,但Bed類和Sofa類都有一個(gè)父類,因此Furniture類被構(gòu)造了兩次,這是不合理的,因此,我們引入了虛繼承的概念。

 

class Furniture{……};

class Bed : virtual public Furniture{……}; // 這里我們使用虛繼承

class Sofa : virtual public Furniture{……};// 這里我們使用虛繼承

 

class sleepSofa : public Bed, public Sofa {……};

                     這樣,Furniture類就之構(gòu)造一次了……

3、 總結(jié)下繼承情況中子類對象的內(nèi)存結(jié)構(gòu)

A.        單繼承情況下子類實(shí)例的內(nèi)存結(jié)構(gòu)

// 描述單繼承情況下子類實(shí)例的內(nèi)存結(jié)構(gòu)

#include "stdafx.h"

 

class A

{

public:

    A(){m_A = 0;}

    virtual fun1(){};

    int m_A;

};

 

class B:public A

{

public:

    B(){m_B = 1;}

    virtual fun1(){};

    virtual fun2(){};

    int m_B;

};

 

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

{

    B* pB = new B;

 

       return 0;

}

B.        多繼承情況下子類實(shí)例的內(nèi)存結(jié)構(gòu)

// 描述多繼承情況下子類實(shí)例的內(nèi)存結(jié)構(gòu)

#include "stdafx.h"

#include <stdio.h>

 

class A

{

public:

 

    A(){m_A = 1;};

    ~A(){};

    virtual int funA(){printf("in funA\r\n"); return 0;};

    int m_A;

};

 

class B

{

public:

    B(){m_B = 2;};

    ~B(){};

    virtual int funB(){printf("in funB\r\n"); return 0;};

    int m_B;

};

 

class C

{

public:

    C(){m_C = 3;};

    ~C(){};

    virtual int funC(){printf("in funC\r\n"); return 0;};

    int m_C;

};

 

class D:public A,public B,public C

{

public:

    D(){m_D = 4;};

    ~D(){};

    virtual int funD(){printf("in funD\r\n"); return 0;};

    int m_D;

};

C.        部分虛繼承的情況下子類實(shí)例的內(nèi)存結(jié)構(gòu)

// 描述部分虛繼承的情況下,子類實(shí)例的內(nèi)存結(jié)構(gòu)

#include "stdafx.h"

class A

{

public:

  A(){m_A = 0;};

  virtual funA(){};

  int m_A;

};

 

class B

{

public:

  B(){m_B = 1;};

  virtual funB(){};

  int m_B;

};

 

class C

{

public:

  C(){m_C = 2;};

  virtual funC(){};

  int m_C;

};

 

class D:virtual public A,public B,public C

{

public:

    D(){m_D = 3;};

    virtual funD(){};

    int m_D;

};

 

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

{

    D* pD = new D;

 

       return 0;

}

 

D.       全部虛繼承的情況下,子類實(shí)例的內(nèi)存結(jié)構(gòu)

// 描述全部虛繼承的情況下,子類實(shí)例的內(nèi)存結(jié)構(gòu)

 

#include "stdafx.h"

class A

{

public:

    A(){m_A = 0;}

    virtual funA(){};

    int m_A;

};

 

class B

{

public:

    B(){m_B = 1;}

    virtual funB(){};

    int m_B;

};

 

class C:virtual public A,virtual public B

{

public:

    C(){m_C = 2;}

    virtual funC(){};

    int m_C;

};

 

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

{

    C* pC = new C;

 

       return 0;

}

 

E.        菱形結(jié)構(gòu)繼承關(guān)系下子類實(shí)例的內(nèi)存結(jié)構(gòu)

// 描述菱形結(jié)構(gòu)繼承關(guān)系下子類實(shí)例的內(nèi)存結(jié)構(gòu)

#include "stdafx.h"

 

class A

{

public:

    A(){m_A = 0;}

    virtual funA(){};

    int m_A;

};

 

class B :virtual public A

{

public:

    B(){m_B = 1;}

    virtual funB(){};

    int m_B;

};

 

class C :virtual public A

{

public:

    C(){m_C = 2;}

    virtual funC(){};

    int m_C;

}; 

   

class D: public B, public C

{

public:

      D(){m_D = 3;}

      virtual funD(){};

      int m_D;

};

 

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

{

        D* pD = new D;

        return 0;

}

 

 

                            上圖中,多出兩個(gè)未知的地址,它們并不是類成員的地址,如果我們跟蹤它,得到的結(jié)果如下圖:





如果留意觀察這兩個(gè)地址,就會發(fā)現(xiàn),它們都緊跟著虛繼承的兩個(gè)子類:

A00425024 à B類的虛表指針,B類虛繼承與A

B00425020 à C類的虛表指針,C類虛繼承與A

 

知道了這兩點(diǎn),我們先跟蹤0x00425030這個(gè)地址,我們得到一個(gè)-40x0C兩個(gè)數(shù),這兩個(gè)數(shù)字很難不讓我們想到這個(gè)是偏移。

0x00425030 所在的位置減去4,就是C類的虛表指針。

0x00425030 所在的位置加上C,就是A類的虛表指針。

 

同理,我們在看3C這個(gè)位置:

0x0042503C 所在的位置減去4,就是B類的虛表指針。

0x0042503C 所在的位置加上0x18,就是A類的虛表指針

 

由此,我們可以大膽的猜測,這個(gè)偏移表是用來關(guān)聯(lián)虛繼承的基類和子類的,比如:

0x00425030這個(gè)偏移表,將A類和C類的虛繼承關(guān)系聯(lián)系到了一起,0x0042503C這個(gè)偏移表則是把A類和B類聯(lián)系到了一起。

4、 總結(jié)下多繼承情況下對象的構(gòu)造順序

A.        虛繼承的構(gòu)造函數(shù)按照被繼承的順序先構(gòu)造。

B.        非虛繼承的構(gòu)造函數(shù)按照被繼承的順序再構(gòu)造。

C.        成員對象的構(gòu)造函數(shù)按照聲明順序構(gòu)造。

D.       類自己的構(gòu)造函數(shù)最后構(gòu)造。

五、       學(xué)習(xí)小結(jié)

本專題本來是想分成兩個(gè)專題分別講述類的繼承和多態(tài)性的。可是由于這兩個(gè)特性聯(lián)系的實(shí)在是太緊密,這樣穿插在一起,我著實(shí)沒想到更好的分類方法。索性將這兩個(gè)特性放在一個(gè)專題中一并講述。

 

但是我的言語表達(dá)能力實(shí)在是有限,總是不能把文章中的知識點(diǎn)講述到我想象中的那樣簡單、清晰,這個(gè)專題,不求能教給大家點(diǎn)什么,只希望在大家的學(xué)習(xí)過程中能起到墊腳石的作用。我就心滿意足了。

 

本專題的內(nèi)容可以說是C++語言的精華所在,講述的知識雖然重要,但也僅限于我現(xiàn)在這個(gè)知識層面上的理解,或許過些日子又會發(fā)現(xiàn)更多更重要的知識我沒有講到或者講的不對……

 

如果你在閱讀本文章個(gè)過程中,如果發(fā)現(xiàn)我哪里講的不對,麻煩通知我,以便我及時(shí)改正,以免誤人子弟……

 

 

—— besterChen   

2010520星期四

Feedback

# re: 笨鳥先飛學(xué)編程系列之八 淺析C++的繼承與多態(tài)性  回復(fù)  更多評論   

2010-05-20 22:25 by OnTheWay
還沒仔細(xì)看,但是能夠?qū)戇@么多就很不錯(cuò)了。
加油!

# re: 笨鳥先飛學(xué)編程系列之八 淺析C++的繼承與多態(tài)性  回復(fù)  更多評論   

2010-05-20 22:27 by 小時(shí)候可靚了
圖文并貌,很認(rèn)真哦。。頂一個(gè)!!

# re: 笨鳥先飛學(xué)編程系列之八 淺析C++的繼承與多態(tài)性  回復(fù)  更多評論   

2010-07-30 06:21 by hoodlum1980
看看還是不錯(cuò)的,支持。

# re: 笨鳥先飛學(xué)編程系列之八 淺析C++的繼承與多態(tài)性  回復(fù)  更多評論   

2011-06-01 10:45 by 李逵
好,謝謝,正需要研究
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲另类春色国产| 久久精品二区亚洲w码| 亚洲欧美日本国产专区一区| 亚洲人成网站色ww在线| 亚洲成人资源网| 亚洲欧洲另类| 亚洲成人自拍视频| 国产一区在线观看视频| 欧美日韩国产成人在线观看 | 亚洲视频日本| 亚洲视频1区2区| 欧美一区二区大片| 久久蜜臀精品av| 欧美午夜电影完整版| 国产欧美日韩视频一区二区三区| 狠狠v欧美v日韩v亚洲ⅴ| 亚洲国产美国国产综合一区二区| 99国产精品视频免费观看一公开 | 亚洲精品国久久99热| 一区二区av| 久久久久久久久久久久久女国产乱 | 狠久久av成人天堂| 在线视频精品一区| 久久亚洲一区二区三区四区| 亚洲经典一区| 欧美专区福利在线| 欧美日韩国产综合视频在线观看| 国产日韩av高清| 日韩亚洲在线观看| 久久偷窥视频| 在线亚洲高清视频| 欧美freesex8一10精品| 国产欧美一区二区三区久久人妖| 99re6热只有精品免费观看| 欧美在线国产| aa级大片欧美三级| 六月婷婷一区| 国产一区二区观看| 亚洲尤物视频在线| 亚洲精品国产系列| 欧美凹凸一区二区三区视频| 国语自产精品视频在线看一大j8 | 一区二区日韩伦理片| 久久综合综合久久综合| 国产日韩欧美电影在线观看| 一区二区三区视频在线观看| 欧美国产亚洲精品久久久8v| 久久国产精品99国产| 国产精品日本精品| 99精品福利视频| 亚洲第一精品电影| 久久人体大胆视频| 精品白丝av| 久久久免费精品| 欧美影院精品一区| 嫩草国产精品入口| 亚洲欧美美女| 欧美激情a∨在线视频播放| 亚洲第一视频| 另类天堂av| 久久免费高清| 91久久极品少妇xxxxⅹ软件| 欧美激情精品久久久久久变态| 久久久久国产精品人| 黄色精品在线看| 猫咪成人在线观看| 久久一区视频| 亚洲欧洲一区二区三区| 欧美成人免费va影院高清| 老司机精品福利视频| 日韩视频在线观看一区二区| 亚洲欧洲日本mm| 欧美日韩午夜在线| 午夜精品视频在线| 欧美中文字幕不卡| 亚洲国产天堂网精品网站| 亚洲人成人99网站| 欧美午夜剧场| 久久久人成影片一区二区三区| 欧美一区三区三区高中清蜜桃| 在线播放精品| 最新日韩在线| 国产午夜精品久久久| 免费短视频成人日韩| 欧美国产日韩a欧美在线观看| 这里只有视频精品| 欧美一区二区高清在线观看| 亚洲电影自拍| 一区二区三区精密机械公司| 国产日韩欧美精品在线| 欧美高清一区| 国产精品xnxxcom| 麻豆国产精品va在线观看不卡| 欧美搞黄网站| 久久精品女人的天堂av| 欧美二区在线播放| 欧美伊人影院| 欧美精品久久久久久| 久久国产精品久久久久久| 免费成人高清| 欧美在线免费| 欧美极品在线观看| 久久精品理论片| 欧美午夜视频网站| 欧美大片免费观看| 国产精品午夜在线| 亚洲第一视频| 国产亚洲一区二区三区在线播放| 欧美成人黑人xx视频免费观看| 欧美性猛片xxxx免费看久爱| 欧美91精品| 国产亚洲一级高清| 亚洲天堂偷拍| 日韩小视频在线观看| 欧美一级一区| 亚洲直播在线一区| 欧美精品91| 欧美电影在线播放| 国产一区观看| 国产精品va| 欧美一区二区三区久久精品| 欧美激情无毛| 巨乳诱惑日韩免费av| 国产精品久久久亚洲一区| 欧美激情性爽国产精品17p| 国产欧美va欧美va香蕉在| 91久久国产综合久久91精品网站| 国产伪娘ts一区| 亚洲欧美日韩中文视频| 在线综合+亚洲+欧美中文字幕| 欧美二区在线| 欧美国产综合视频| 国内成人精品一区| 亚洲欧美日韩在线| 欧美一级午夜免费电影| 欧美日韩成人在线观看| 欧美freesex8一10精品| 国产在线精品二区| 欧美在线网站| 久久av免费一区| 国产精品欧美日韩一区| 午夜精品久久久久久久99热浪潮| 亚欧成人在线| 国产精品久久久久久久久久三级| 日韩午夜在线电影| 日韩视频第一页| 欧美国产日本在线| 日韩视频免费在线| 中国成人在线视频| 国产精品久久久久7777婷婷| 99国产一区二区三精品乱码| 亚洲天堂网在线观看| 欧美性色aⅴ视频一区日韩精品| 亚洲午夜女主播在线直播| 午夜一区二区三区在线观看| 国产欧美一区二区精品秋霞影院| 性久久久久久久久| 女生裸体视频一区二区三区| 在线看日韩欧美| 免费成人高清视频| 99热免费精品在线观看| 亚洲欧美日韩在线不卡| 国产日韩精品一区观看| 久久久91精品国产一区二区精品| 免费看黄裸体一级大秀欧美| 日韩视频一区二区在线观看 | 亚洲第一区在线| 一区二区三区日韩欧美| 9色porny自拍视频一区二区| 一区二区三区欧美日韩| 欧美一区二区在线| 亚洲欧美自拍偷拍| 亚洲一区二区高清视频| 欧美激情一级片一区二区| 国产一区二区三区免费观看| 在线国产精品一区| 亚洲国产成人久久综合| 亚洲欧美日韩国产综合精品二区| 久久av老司机精品网站导航| 亚洲国产成人一区| 欧美日韩在线电影| 欧美一级大片在线观看| 欧美大片免费观看| 亚欧成人在线| 一级日韩一区在线观看| 国产亚洲欧美日韩美女| 老牛嫩草一区二区三区日本| 一区二区冒白浆视频| 蜜桃久久精品一区二区| 午夜精品久久久久| 亚洲黑丝一区二区| 国产日韩精品在线| 欧美三级网址| 欧美freesex交免费视频| 午夜影院日韩| 一本色道综合亚洲| 亚洲福利视频一区| 久久亚洲精品欧美| 欧美一区二区免费观在线| 亚洲免费精品|