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

聚星亭

吾笨笨且懶散兮 急須改之而奮進(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)試、排錯都難免與這些粉碎后的函數(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)莫名其妙的錯誤,下面我簡單的列舉一下大家了解就好。

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

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

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

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

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_na、m_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; // 編譯出錯,需要強(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è)子類:

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

B、00425020 à 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ū)戇@么多就很不錯了。
加油!

# 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
看看還是不錯的,支持。

# 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>
            亚洲九九爱视频| 免费在线看成人av| 久久亚洲国产成人| 久久手机精品视频| 久久久久久久尹人综合网亚洲| 亚洲女人天堂av| 亚洲欧美在线免费观看| 欧美有码在线视频| 巨乳诱惑日韩免费av| 欧美电影在线观看| 亚洲精品日韩欧美| 午夜精品久久久久久| 欧美日韩综合| 老司机久久99久久精品播放免费| 另类图片国产| 欧美精品videossex性护士| 欧美日韩亚洲系列| 国产综合亚洲精品一区二| 亚洲福利精品| 午夜天堂精品久久久久| 久久只有精品| 国产精品99久久久久久白浆小说 | 国产视频精品免费播放| 在线不卡中文字幕播放| 一区二区三区高清不卡| 久久精品一本| av成人老司机| 男女激情久久| 国产精品婷婷午夜在线观看| 亚洲国产精品电影在线观看| 香蕉成人伊视频在线观看| 亚洲国产精品一区在线观看不卡| 亚洲欧洲另类| 久久九九精品99国产精品| 欧美日韩国产123区| 一区二区三区中文在线观看| 亚洲欧美第一页| 亚洲国产免费| 久久免费视频在线观看| 国产精品视频久久| 一区二区三区视频观看| 亚洲高清一二三区| 久久久久久一区二区三区| 国产情人综合久久777777| 亚洲视频一二区| 亚洲国产婷婷香蕉久久久久久99| 久久国产99| 国产欧美日韩在线播放| 亚洲在线1234| 一区二区免费在线播放| 欧美另类人妖| 日韩视频一区二区在线观看 | 裸体丰满少妇做受久久99精品| 亚洲图片欧美一区| 欧美日韩mv| 妖精成人www高清在线观看| 欧美成人精品激情在线观看| 久久蜜桃香蕉精品一区二区三区| 国产日韩一级二级三级| 亚洲欧美怡红院| 亚洲主播在线| 国产丝袜一区二区| 久久久久久九九九九| 欧美专区中文字幕| 激情久久婷婷| 欧美风情在线观看| 欧美成人亚洲成人| 国产日产精品一区二区三区四区的观看方式 | 国产欧美日韩在线播放| 欧美伊人久久久久久午夜久久久久| 亚洲精品四区| 国产精品久久久久久久久久免费看| 亚洲一区二区精品在线| 99国产精品视频免费观看| 国产精品jizz在线观看美国 | 激情欧美一区二区三区| 麻豆成人综合网| 欧美freesex8一10精品| 一区二区电影免费观看| 亚洲素人一区二区| 国产自产精品| 亚洲国产一成人久久精品| 欧美日韩在线视频首页| 欧美一级电影久久| 久久日韩精品| 亚洲一级在线观看| 欧美在线一级va免费观看| 亚洲精品影视| 西瓜成人精品人成网站| 91久久久久| 亚洲一区二区视频在线观看| 雨宫琴音一区二区在线| 日韩午夜电影| 黄色精品一区| 99国产精品久久| 在线成人欧美| 亚洲一区二区黄色| 亚洲国产日韩综合一区| 中文久久乱码一区二区| 精品成人一区二区三区四区| 亚洲精品中文字幕在线观看| 国产自产2019最新不卡| 日韩一区二区高清| 亚洲二区在线| 午夜国产精品影院在线观看| 亚洲免费观看高清完整版在线观看| 午夜精品区一区二区三| 日韩亚洲欧美成人| 久久精品系列| 亚洲欧美在线aaa| 欧美福利在线| 久久亚洲视频| 国产日本精品| 一区二区三区久久| 亚洲精品一区二区在线观看| 久久精品人人爽| 欧美在线啊v| 国产精品九九久久久久久久| 91久久综合| 亚洲黄页视频免费观看| 久久国产天堂福利天堂| 欧美一区二区免费| 欧美午夜精品久久久久久孕妇| 亚洲成在线观看| 欧美精品一区二区精品网| 国内成人精品2018免费看| 一区二区三区蜜桃网| 99成人在线| 欧美国产亚洲视频| 欧美激情第10页| 1024日韩| 久久久久久久网| 久久午夜精品一区二区| 国产人成一区二区三区影院| 亚洲欧美国产高清va在线播| 亚洲欧美日韩天堂| 国产精品青草久久久久福利99| 宅男精品视频| 午夜精品福利视频| 国产精品一级| 亚洲欧美日韩系列| 欧美在线一二三区| 国产亚洲欧美一区| 久久国产精品色婷婷| 久久综合给合久久狠狠色| 尤物精品在线| 欧美精品aa| 国产精品99久久久久久人| 亚洲欧美日韩网| 国产视频一区在线观看| 久久精品国产亚洲高清剧情介绍| 久久只有精品| 日韩亚洲欧美成人| 国产精品毛片一区二区三区| 香港成人在线视频| 免费黄网站欧美| 99国产精品视频免费观看一公开 | 亚洲综合国产激情另类一区| 欧美中文在线字幕| 一区精品在线| 欧美精品一区二区三| 亚洲亚洲精品在线观看 | 欧美激情精品久久久久久黑人| 亚洲精品乱码久久久久久按摩观| 欧美日韩国产综合网| 亚洲综合精品自拍| 欧美成人一区二免费视频软件| av成人免费观看| 国产亚洲精品久久久久久| 久热精品视频在线| av成人免费在线观看| 久久久精品2019中文字幕神马| 亚洲欧洲精品一区二区三区不卡 | 亚洲国产专区校园欧美| 亚洲欧美日韩精品综合在线观看 | 欧美在线免费看| 亚洲精品久久久久久一区二区| 国产精品久久久久9999| 久久天堂精品| 亚洲午夜视频| 亚洲电影视频在线| 欧美在线播放| 一本色道久久综合亚洲精品高清 | 欧美夫妇交换俱乐部在线观看| 亚洲国产日韩一级| 午夜精品在线看| 亚洲日本理论电影| 国产亚洲精品综合一区91| 欧美精品一区二区高清在线观看| 欧美一区视频| 亚洲图片欧美一区| 亚洲日产国产精品| 久久综合久久美利坚合众国| 亚洲欧美日韩国产中文在线| 日韩一区二区免费高清| 亚洲福利视频一区二区| 黑人巨大精品欧美一区二区小视频| 国产精品国产馆在线真实露脸| 美女主播一区| 久久精品一区二区三区中文字幕|