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

聚星亭

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

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

 

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

 

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

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

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

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

4、      淺析類的多繼承

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

 

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

 

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

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

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

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

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

假設(shè),我們現(xiàn)在想要寫一個函數(shù)(如Exp01),它即可以計算整型數(shù)據(jù)又可以計算浮點數(shù),那樣我們就得寫兩個求和函數(shù),對于更復(fù)雜的情況,我們可能需要寫更多的函數(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ū)分各個函數(shù)的呢?相信聰明的你一定根據(jù)上面的要求推測出來了,是的,名稱粉碎。很簡單,我們來驗證一下我的說法,繼續(xù)打開Exp01工程,點擊菜單欄的”project”à”settings”:

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

 

B、缺省參數(shù)

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

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

 

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

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

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

double sum (float nNum2 = 10float nNum1);

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

2、 淺析運算符重載

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

 

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

 

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

返回值  operator 運算符(參數(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)然,運算符重載也存在一些限制,在我們編碼的過程中需要注意一下:

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

B、 “::. .*”運算符不可以被重載。

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

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

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

F、 保留運算符原本的含義

二、       構(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增加一個成員函數(shù),來計算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)限的說明及其改變

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

       基類訪問屬性

繼承權(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)一個類中的成員變量為publi權(quán)限,但它子類繼承它時采用了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)限的前提是:基類成員在子類中是可見的,沒有被隔離。

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

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

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

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

 

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

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

 

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

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

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

 

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

 

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

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

b)        若基類中有一個虛函數(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ù)時,必須要通過基類對象的指針或者引用來完成調(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ī)范,使用一下這兩個類:

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;

}

運行結(jié)果:

3、 虛函數(shù)的運行機制

OK,我們調(diào)試一下這段代碼,看看虛函數(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一個空間

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)建子類的臨時對象

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項函數(shù)

0040EE3C   cmp         esi,esp

 

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

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

 

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

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

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

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

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

CBaseCls *pBase = new CSubCls; // 讓一個父類指針指向子類的實例

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

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

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

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

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

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

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

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

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

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

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

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

到這里,我想你一定對虛函數(shù)有一定的了解了,為了加深印象,我們不妨手工用C語言類模擬一個虛函數(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;      // 填充虛表項

           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()

    {

      // 填充虛表項,覆蓋父類的成員地址

           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;

}

  運行結(jié)果:


四、       淺析類的多繼承

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

1、 基本概念

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

a)         SleepSofa類該如何定義?

Class SleepSofa : public Bed, public Sofa

{

       ….

}

                                   構(gòu)造順序為:Bed à sofa à sleepsofa (也就是書寫的順序)

                    

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

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

Sleepsofa objsofa;

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

2、 虛繼承

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

 

倘若,我們定義一個SleepSofa對象,讓我們分析一下它的構(gòu)造過程:它會構(gòu)造Bed類和Sofa類,但Bed類和Sofa類都有一個父類,因此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.        單繼承情況下子類實例的內(nèi)存結(jié)構(gòu)

// 描述單繼承情況下子類實例的內(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.        多繼承情況下子類實例的內(nèi)存結(jié)構(gòu)

// 描述多繼承情況下子類實例的內(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.        部分虛繼承的情況下子類實例的內(nèi)存結(jié)構(gòu)

// 描述部分虛繼承的情況下,子類實例的內(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.       全部虛繼承的情況下,子類實例的內(nèi)存結(jié)構(gòu)

// 描述全部虛繼承的情況下,子類實例的內(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)系下子類實例的內(nèi)存結(jié)構(gòu)

// 描述菱形結(jié)構(gòu)繼承關(guān)系下子類實例的內(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;

}

 

 

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





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

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

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

 

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

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

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

 

同理,我們在看3C這個位置:

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

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

 

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

0x00425030這個偏移表,將A類和C類的虛繼承關(guān)系聯(lián)系到了一起,0x0042503C這個偏移表則是把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é)

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

 

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

 

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

 

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

 

 

—— 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 小時候可靚了
圖文并貌,很認(rèn)真哦。。頂一個!!

# 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>
            欧美成人久久| 久久精品综合网| 久久国产精品毛片| 亚洲欧洲日本mm| 欧美一级免费视频| 欧美性久久久| 在线视频你懂得一区 | 亚洲精品美女久久久久| 久久精品视频在线| 国产色爱av资源综合区| 欧美在线网站| 午夜日韩av| 国产视频一区在线观看一区免费| 亚洲欧美国产高清| 亚洲一区二区三区精品动漫| 国产精品午夜电影| 久久国产精品久久久久久| 午夜免费日韩视频| 狠狠色综合网站久久久久久久| 久久一区视频| 免费观看日韩| 在线视频日韩精品| 亚洲男女自偷自拍| 一区二区三区亚洲| 亚洲国产一成人久久精品| 欧美日韩免费在线视频| 欧美一级在线播放| 久久久精品999| 99re热这里只有精品视频| 亚洲精品无人区| 国产精品亚洲综合色区韩国| 麻豆久久婷婷| 欧美日韩国产小视频在线观看| 亚洲永久免费观看| 欧美在线播放视频| 亚洲精品久久久久久久久久久久久 | 国产一区二三区| 亚洲第一页在线| 国产精品电影观看| 久久亚洲精品伦理| 欧美日韩视频一区二区三区| 久久久精品性| 欧美精品在线看| 久久国产综合精品| 欧美极品一区| 久久精品亚洲一区二区三区浴池| 蜜桃av一区二区| 欧美一区二区三区精品| 欧美高清视频一区| 久久久国产精品一区| 欧美激情亚洲精品| 久久久综合香蕉尹人综合网| 欧美日韩国产一级片| 毛片av中文字幕一区二区| 国产精品对白刺激久久久| 欧美成人第一页| 国产欧美日韩精品一区| 最新精品在线| 亚洲成人直播| 欧美午夜电影在线| 久久青青草原一区二区| 欧美日本不卡高清| 另类成人小视频在线| 国产精品久久久久aaaa樱花| 亚洲福利视频一区| 黄色成人小视频| 亚洲男女自偷自拍| 亚洲在线第一页| 欧美国产日韩精品| 女女同性精品视频| 国产亚洲精品一区二555| 亚洲日本免费| 亚洲国产精品va在看黑人| 午夜视频一区在线观看| 亚洲一区视频| 欧美日韩国语| 亚洲国产综合在线| 在线免费观看欧美| 久久爱另类一区二区小说| 亚洲欧美中文字幕| 国产精品久久久一区麻豆最新章节 | 在线观看欧美激情| 久久久精品一区二区三区| 久久精品99国产精品酒店日本| 欧美日韩免费在线观看| 亚洲精品一区二区在线观看| 亚洲伦理在线免费看| 欧美ab在线视频| 亚洲高清不卡一区| 亚洲美女黄网| 欧美精品一区二区三区在线看午夜 | 亚洲一区二区网站| 欧美三区在线| 亚洲字幕在线观看| 欧美在线在线| 国外精品视频| 久久久久久久久久看片| 六月丁香综合| 亚洲欧洲在线播放| 欧美日韩国产成人在线观看| 一区二区欧美精品| 欧美在线亚洲一区| 黄色成人在线网站| 久久最新视频| 亚洲日本在线观看| 亚洲欧美日韩在线不卡| 国内视频精品| 免费视频一区| 亚洲最新色图| 久久久九九九九| 亚洲国产精品一区制服丝袜| 欧美xx视频| 亚洲天堂男人| 久久亚洲精品欧美| 99re6这里只有精品| 国产精品久久久久久五月尺| 欧美中文日韩| 最新国产拍偷乱拍精品| 欧美一区二区| 亚洲激情专区| 国产精品色午夜在线观看| 久久久青草青青国产亚洲免观| 久久精品国亚洲| 一区二区三区www| 久久九九电影| 亚洲国产人成综合网站| 欧美日韩在线免费观看| 午夜精品一区二区三区在线播放| 久久综合网hezyo| 99热这里只有成人精品国产| 国产精品视频网站| 媚黑女一区二区| 亚欧成人精品| 亚洲精品在线观看免费| 另类成人小视频在线| 亚洲午夜激情在线| 精品粉嫩aⅴ一区二区三区四区| 欧美日韩一区三区| 久久综合九色欧美综合狠狠| 亚洲一区二区三区四区中文 | 久久www成人_看片免费不卡| 亚洲精品欧洲| 精品福利免费观看| 国产精品蜜臀在线观看| 免费在线观看成人av| 欧美亚洲在线视频| 一区二区三区国产精品| 亚洲第一精品福利| 老牛国产精品一区的观看方式| 午夜精品视频在线观看一区二区| 91久久线看在观草草青青| 国产女人18毛片水18精品| 欧美日韩天天操| 欧美高清自拍一区| 噜噜噜91成人网| 久久久精品网| 欧美中文字幕视频| 亚洲一区二区三区在线| 中日韩美女免费视频网址在线观看| 欧美国产亚洲另类动漫| 久久伊人亚洲| 久久亚洲欧美| 久久久成人精品| 久久精品国产精品亚洲| 欧美亚洲一区二区三区| 亚洲新中文字幕| 亚洲视频视频在线| 亚洲一区二区三区免费观看 | 亚洲新中文字幕| 亚洲视频你懂的| 99国产精品久久久久老师| 亚洲精品乱码久久久久久| 亚洲黄色成人网| 亚洲国产婷婷综合在线精品| 亚洲二区在线观看| 欧美激情在线狂野欧美精品| 欧美福利精品| 亚洲福利在线视频| 亚洲国产精品第一区二区| 91久久国产综合久久| 日韩网站免费观看| 正在播放日韩| 亚洲专区在线| 久久国产精品黑丝| 久久夜色精品| 欧美国产日韩xxxxx| 欧美理论电影网| 欧美日本网站| 国产精品久久久久免费a∨| 国产精品午夜av在线| 国产综合久久久久影院| 亚洲国产mv| 亚洲私人影院| 久久久91精品国产| 免费在线观看成人av| 欧美激情精品久久久久久免费印度| 欧美日韩国产色视频| 国产精品日韩精品| 好看的亚洲午夜视频在线| 91久久久久久久久|