• <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>

            聚星亭

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

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

             

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

             

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

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

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

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

            4、      淺析類的多繼承

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

             

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

             

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

            一、       體驗(yàn)類的靜態(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)在想要寫一個(gè)函數(shù)(如Exp01),它即可以計(jì)算整型數(shù)據(jù)又可以計(jì)算浮點(diǎn)數(shù),那樣我們就得寫兩個(gè)求和函數(shù),對(duì)于更復(fù)雜的情況,我們可能需要寫更多的函數(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ù)就可以寫成:

            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)了,是的,名稱粉碎。很簡(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è)诰帉?/span>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寫過(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ù)只要寫在函數(shù)聲明中即可。

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

            double sum (float nNum2 = 10float 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++中,要想讓我們定義的類對(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ì)自定義的類型做重載。

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

            二、       構(gòu)建類與類之間的父子關(guān)系及其訪問(wè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ù),來(lái)計(jì)算m_nA+m_nB+m_nC的和:

            int  SubCls::GetSum()

            {

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

                return  GetAValue()+ GetBValue() + m_nc;

            }

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

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

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

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

                   基類訪問(wèn)屬性

            繼承權(quán)限

            public

            protected

            private

            public

            public

            protected

            不可訪問(wèn)

            protected

            protected

            protected

            不可訪問(wèn)

            private

            private

            private

            不可訪問(wèn)

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

             

            當(dāng)一個(gè)類中的成員變量為publi權(quán)限,但它子類繼承它時(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)整成員訪問(wèn)權(quán)限的前提是:基類成員在子類中是可見(jiàn)的,沒(méi)有被隔離。

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

            之前我們講述的多態(tài)性,像函數(shù)重載,運(yùn)算符重載,拷貝構(gòu)造等都是在編譯器完成的多態(tài)性,我們稱之為靜態(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)合

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

             

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

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

             

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

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

            說(shuō)了一堆的廢話,或許你真的知道虛函數(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)        若基類中有一個(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í),必須要通過(guò)基類對(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ù)名等都與父類的虛函數(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í)對(duì)象

            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               ; 可見(jiàn),[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ù)指針表簡(jiǎn)稱虛表)

            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ù)指針),換句話說(shuō),虛函數(shù)的指針都被存放在this指向的這個(gè)虛函數(shù)表中。

             

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

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

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

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

            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é)果

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

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

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

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

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

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

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

            到這里,我想你一定對(duì)虛函數(shù)有一定的了解了,為了加深印象,我們不妨手工用C語(yǔ)言類模擬一個(gè)虛函數(shù)出來(lái)(代碼見(jiàn)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; // 用基類指針指向子類對(duì)象

             

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

                objPer.vsayGoodbye();

             

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

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

             

                return 0;

            }

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


            四、       淺析類的多繼承

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

            1、 基本概念

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

            a)         SleepSofa類該如何定義?

            Class SleepSofa : public Bed, public Sofa

            {

                   ….

            }

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

                                

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

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

            Sleepsofa objsofa;

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

            2、 虛繼承

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

             

            倘若,我們定義一個(gè)SleepSofa對(duì)象,讓我們分析一下它的構(gòu)造過(guò)程:它會(huì)構(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é)下繼承情況中子類對(duì)象的內(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è)地址,就會(huì)發(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類的虛表指針。

             

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

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

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

             

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

            0x00425030這個(gè)偏移表,將A類和C類的虛繼承關(guān)系聯(lián)系到了一起,0x0042503C這個(gè)偏移表則是把A類和B類聯(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.       類自己的構(gòu)造函數(shù)最后構(gòu)造。

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

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

             

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

             

            本專題的內(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 李逵
            好,謝謝,正需要研究
            久久久精品人妻无码专区不卡 | 超级碰碰碰碰97久久久久| 亚洲国产成人精品91久久久| 久久九九兔免费精品6| 国产精品美女久久久| 热综合一本伊人久久精品| 久久久精品2019免费观看| 国产亚洲成人久久| 亚洲欧美伊人久久综合一区二区 | 一本大道久久东京热无码AV| 久久精品毛片免费观看| 久久婷婷色综合一区二区| 人人狠狠综合久久88成人| 青青热久久国产久精品| 久久亚洲精精品中文字幕| 久久国产精品一区| 亚洲av伊人久久综合密臀性色| 91精品日韩人妻无码久久不卡| 伊人色综合久久天天人手人婷| 亚洲AV无码一区东京热久久| 国产三级精品久久| 久久国产乱子伦免费精品| 要久久爱在线免费观看| 国产成人AV综合久久| 久久精品国产亚洲AV嫖农村妇女 | 一本一道久久综合狠狠老| 精品人妻伦一二三区久久| 99久久国产热无码精品免费| 色播久久人人爽人人爽人人片AV| 国产精品久久久久乳精品爆 | 国产aⅴ激情无码久久| 久久久久亚洲AV无码去区首| 久久综合九色综合欧美狠狠| 久久综合亚洲色一区二区三区| 久久精品成人免费观看97| 国产精品毛片久久久久久久| 亚洲AV乱码久久精品蜜桃| 国产精品成人久久久| 亚洲国产成人精品91久久久 | 精品国产一区二区三区久久| 日韩精品久久久久久久电影蜜臀|