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

聚星亭

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

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

 

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

 

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

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

2、      構(gòu)建類(lèi)與類(lèi)之間的父子關(guān)系及其訪(fǎng)問(wèn)限制

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

4、      淺析類(lèi)的多繼承

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

 

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

 

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

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

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

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

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

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

int sum_int(int nNum1, int nNum2)

{

    return nNum1 + nNum2;

}

 

double sum_float(float nNum1, float nNum2)

{

    return nNum1 + nNum2;

}

 

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

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

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

 

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

int sum (int nNum1, int nNum2)

{

    return nNum1 + nNum2;

}

 

double sum (float nNum1, float nNum2)

{

    return nNum1 + nNum2;

}

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

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

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è)诰帉?xiě)C++程序的時(shí)候,調(diào)試、排錯(cuò)都難免與這些粉碎后的函數(shù)名打交道,好多的朋友為了解決這個(gè)問(wèn)題想出了各種方法,記得在看雪壇子上有一篇名叫《史上最牛資料助你解惑c++調(diào)試》的文章,大致上把粉碎后的函數(shù)名各部分的含義都解釋出來(lái)了,其實(shí)沒(méi)有這個(gè)必要的,我們用的VC開(kāi)發(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寫(xiě)過(guò)程序的朋友一定知道,好多的函數(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ò)誤,下面我簡(jiǎn)單的列舉一下大家了解就好。

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

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

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

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

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

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

 

比如,我們要讓一個(gè)日期對(duì)象減去另一個(gè)日期對(duì)象以便得到他們之間的時(shí)間差。再如:我們要讓一個(gè)字符串通過(guò)“+”來(lái)連接另一個(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)算符重載也存在一些限制,在我們編碼的過(guò)程中需要注意一下:

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

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

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

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

E、 只能針對(duì)自定義的類(lèi)型做重載。

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

二、       構(gòu)建類(lèi)與類(lèi)之間的父子關(guān)系及其訪(fǎng)問(wèn)限制

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

看代碼:

// 基類(lè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ù),來(lái)計(jì)算m_nA+m_nB+m_nC的和:

int  SubCls::GetSum()

{

    //return m_na+m_nb+m_nc; //  沒(méi)有權(quán)限訪(fǎng)問(wèn)m_na、m_nb

    return  GetAValue()+ GetBValue() + m_nc;

}

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

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

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

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

       基類(lèi)訪(fǎng)問(wèn)屬性

繼承權(quán)限

public

protected

private

public

public

protected

不可訪(fǎng)問(wèn)

protected

protected

protected

不可訪(fǎng)問(wèn)

private

private

private

不可訪(fǎng)問(wèn)

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

 

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

class BaseCls 

{

public:

    int  m_nPub;

};

 

class SubCls : private BaseCls 

{

public:

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

}

                注意,調(diào)整成員訪(fǎng)問(wèn)權(quán)限的前提是:基類(lèi)成員在子類(lèi)中是可見(jiàn)的,沒(méi)有被隔離。

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

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

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

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

 

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

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

 

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

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

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

 

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

 

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

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

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

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

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

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

如:下來(lái)程序(見(jiàn)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ù)名等都與父類(lèi)的虛函數(shù)相同,所以它也是虛函數(shù)。

    void fun(int x)

    {

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

    }

};

 

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

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]               ; 子類(lèi)的this

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

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

0040EE12   jmp         main+6Bh (0040ee1b)

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

0040EE1B   mov         eax,dword ptr [ebp-70h]               ; 子類(lèi)的this

0040EE1E   mov         dword ptr [ebp-28h],eax               ; 子類(lèi)的this

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

0040EE28   mov         ecx,dword ptr [ebp-28h]               ; 子類(lèi)的this

0040EE2B   mov         dword ptr [ebp-24h],ecx               ; 可見(jiàn),[ebp-24h]中是子類(lèi)的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ù)指針表簡(jiǎn)稱(chēng)虛表)

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),我們對(duì)象的內(nèi)存結(jié)構(gòu)應(yīng)該是這樣的(比以前的內(nèi)存結(jié)構(gòu)多了個(gè)虛函數(shù)表的指針):

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

 

現(xiàn)在,我們?cè)倩仡^看上述的程序,猜測(cè)一下他的編譯過(guò)程。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#include <stdio.h>

 

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

class CPerson;

typedef void (*PFUN_TYPE)();

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

 

// 基類(lèi)的成員。

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),覆蓋父類(lèi)的成員地址

           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; // 用基類(lèi)指針指向子類(lèi)對(duì)象

 

    objPer.vsayHello();         // 用基類(lèi)對(duì)象直接調(diào)用

    objPer.vsayGoodbye();

 

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

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

 

    return 0;

}

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


四、       淺析類(lèi)的多繼承

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

1、 基本概念

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

a)         SleepSofa類(lèi)該如何定義?

Class SleepSofa : public Bed, public Sofa

{

       ….

}

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

                    

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

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

Sleepsofa objsofa;

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

2、 虛繼承

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

 

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

 

class Furniture{……};

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

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

 

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

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

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

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

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

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

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

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

// 描述菱形結(jié)構(gòu)繼承關(guān)系下子類(lèi)實(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è)未知的地址,它們并不是類(lèi)成員的地址,如果我們跟蹤它,得到的結(jié)果如下圖:





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

A、00425024 à B類(lèi)的虛表指針,B類(lèi)虛繼承與A類(lèi)

B00425020 à C類(lèi)的虛表指針,C類(lèi)虛繼承與A類(lèi)

 

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

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

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

 

同理,我們?cè)诳?/span>3C這個(gè)位置:

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

0x0042503C 所在的位置加上0x18,就是A類(lèi)的虛表指針

 

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

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

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

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

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

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

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

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

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

 

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

 

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

 

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

 

 

—— besterChen   

2010520星期四

Feedback

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

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

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

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

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

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

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

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>
            在线视频欧美一区| 国产亚洲精品美女| 一区二区三区偷拍| 亚洲在线观看免费| 国产精品九九| 亚洲欧美日韩系列| 午夜视频在线观看一区二区| 久久久噜噜噜久噜久久| 国产精品99久久久久久久久 | 久久性天堂网| 洋洋av久久久久久久一区| 99re6这里只有精品| 国产欧美一区二区精品性| 蜜桃av一区| 国产欧美va欧美va香蕉在| 久久精品噜噜噜成人av农村| 欧美日本一区二区三区| 欧美中文在线视频| 欧美成人久久| 久久久久久久一区二区| 欧美韩日高清| 久久影院亚洲| 国产精品国产a| 99亚洲精品| 一区二区日韩免费看| 美女视频网站黄色亚洲| 午夜精品久久久久| 欧美日韩国产不卡| 亚洲激情不卡| 亚洲福利视频一区| 久久久久久久91| 久久免费一区| 狠狠操狠狠色综合网| 欧美一二三视频| 亚洲欧美日本精品| 国产欧美欧美| 欧美在线视频不卡| 久久综合九色九九| 影音先锋一区| 麻豆成人在线| 日韩视频在线播放| 在线亚洲伦理| 亚洲一二三四区| 欧美在线视频a| 在线免费观看欧美| 久久久久久久久久久久久9999| 久久综合久色欧美综合狠狠| 国产一区自拍视频| 欧美成人免费小视频| 亚洲黑丝在线| 欧美一区二区高清| 激情另类综合| 欧美精品久久久久久久久老牛影院| 欧美福利电影在线观看| 一区二区三区欧美在线观看| 国产精品国产三级国产专区53| 久久久美女艺术照精彩视频福利播放 | 亚洲国产精品一区二区www在线| 日韩亚洲不卡在线| 一区二区三区在线免费视频| 欧美日韩一级大片网址| 欧美中在线观看| 中文精品视频| 亚洲国产电影| 免费成人小视频| 亚洲欧美在线播放| 亚洲精品国产精品国自产在线 | 亚洲国产精品传媒在线观看| 中文国产成人精品久久一| 亚洲二区三区四区| 牛夜精品久久久久久久99黑人 | 亚洲午夜精品久久久久久浪潮| 久久裸体艺术| 免费视频一区二区三区在线观看| 午夜日韩激情| 国产欧美日韩精品丝袜高跟鞋 | 亚洲美女色禁图| av成人免费观看| 中文欧美日韩| 亚洲欧美电影院| 麻豆久久婷婷| 欧美伊人久久久久久午夜久久久久| 影音欧美亚洲| 夜夜夜久久久| 久久久www成人免费精品| 美国成人直播| 日韩小视频在线观看专区| 在线视频一区二区| 久久免费视频在线观看| 欧美jizzhd精品欧美喷水| 国产精品久久久久aaaa樱花| 国产一区久久| 99亚洲一区二区| 美日韩丰满少妇在线观看| 亚洲黄色大片| 中文av一区二区| 美女主播一区| 亚洲欧美日韩在线| 亚洲国产精品999| 性8sex亚洲区入口| 久久女同精品一区二区| 亚洲图片在线观看| 羞羞答答国产精品www一本| 亚洲久久视频| 国产精品99免视看9| 亚洲国产一区在线| 久久频这里精品99香蕉| 亚洲欧美国产制服动漫| 国精产品99永久一区一区| 亚洲一二三级电影| 99精品国产在热久久| 国产精品mm| 欧美淫片网站| 嫩草国产精品入口| 99re66热这里只有精品3直播| 欧美日韩精品一区二区三区| 亚洲精品一区二区三区樱花| 亚洲电影毛片| 国产精品草草| 亚洲自拍偷拍色片视频| 午夜亚洲性色视频| 亚洲高清久久网| 亚洲美女在线看| 在线免费观看一区二区三区| 欧美激情一区二区三区蜜桃视频| 欧美精品久久久久久| 久久电影一区| 欧美日韩亚洲高清一区二区| 久久精品首页| 欧美激情精品久久久久久蜜臀| 亚洲天堂成人| 久久精品一区二区国产| 亚洲精选国产| 免费黄网站欧美| 久久久久国产一区二区三区四区| 女生裸体视频一区二区三区| 老司机一区二区三区| 欧美精品三级在线观看| 麻豆精品91| 韩国av一区二区三区在线观看| 老司机午夜精品视频| 欧美三级韩国三级日本三斤| 欧美成人亚洲成人| 曰韩精品一区二区| 亚洲女ⅴideoshd黑人| 亚洲视频在线观看视频| 老司机一区二区| 亚洲国产精品久久91精品| 尤物九九久久国产精品的分类| 亚洲欧美www| 久久国产精品亚洲va麻豆| 国产精品久久久久7777婷婷| 亚洲最新中文字幕| 日韩一级在线观看| 欧美亚洲不卡| 久久成人18免费网站| 欧美成人精品三级在线观看| 最新亚洲一区| 国产精品家庭影院| 久久精品99国产精品| 久久综合九色综合欧美狠狠| 国内精品久久久久久久97牛牛| 欧美中在线观看| 激情综合色综合久久综合| 欧美专区日韩专区| 亚洲乱码国产乱码精品精天堂| 亚洲欧美综合网| 亚洲二区精品| 国产精品网站一区| 久久久久成人精品| 亚洲狼人综合| 亚洲欧洲精品成人久久奇米网| 亚洲欧美成人在线| 精品99视频| 国产精品久久久久久久久久直播| 麻豆精品在线观看| 久久福利毛片| 亚洲性色视频| 亚洲国产精品成人久久综合一区| 香蕉久久精品日日躁夜夜躁| 亚洲精品久久久久久久久久久久 | 亚洲毛片在线观看| 亚洲国产91精品在线观看| 国产欧美三级| 国产九九精品视频| 国产精品久久夜| 国产视频不卡| 极品尤物av久久免费看| 国产精品一区二区久久久| 国产欧美日韩另类视频免费观看| 国产精品亚洲а∨天堂免在线| 欧美视频在线免费| 国产精品久久久久久久浪潮网站| 欧美亚洲第一页| 国产欧美一区二区精品婷婷| 国产日本欧洲亚洲| 亚洲看片免费| 欧美一区二区三区成人| 免费欧美日韩国产三级电影| 日韩视频在线一区二区三区|