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

            Beginning to 編程

            VC++ 方面編程文章

             

            C++的多態(tài)性實現(xiàn)機制剖析 /轉(zhuǎn)孫鑫


             

            ――即VC++視頻第三課this指針詳細(xì)說明

            作者:孫鑫 時間:2006年1月12日星期四

            要更好地理解C++的多態(tài)性,我們需要弄清楚函數(shù)覆蓋的調(diào)用機制,因此,首先我們介紹一下函數(shù)的覆蓋。

            1.   函數(shù)的覆蓋

            我們先看一個例子:

            1- 1

            #include <iostream.h>

            class animal

            {

            public:

                   void sleep()

                   {

                          cout<<"animal sleep"<<endl;

                   }

                   void breathe()

                   {

                          cout<<"animal breathe"<<endl;

                   }

            };

            class fish:public animal

            {

            public:

                   void breathe()

                   {

                          cout<<"fish bubble"<<endl;

                   }

            };

            void main()

            {

                   fish fh;

                   animal *pAn=&fh;

                   pAn->breathe();

            }

                   注意,在例1-1的程序中沒有定義虛函數(shù)。考慮一下例1-1的程序執(zhí)行的結(jié)果是什么?

                   答案是輸出:animal breathe

                   在類fish中重寫了breathe()函數(shù),我們可以稱為函數(shù)的覆蓋。在main()函數(shù)中首先定義了一個fish對象fh,接著定義了一個指向animal的指針變量pAn,將fh的地址賦給了指針變量pAn,然后利用該變量調(diào)用pAn->breathe()。許多學(xué)員往往將這種情況和C++的多態(tài)性搞混淆,認(rèn)為fh實際上是fish類的對象,應(yīng)該是調(diào)用fish類的breathe(),輸出“fish bubble”,然后結(jié)果卻不是這樣。下面我們從兩個方面來講述原因。

            1、  編譯的角度

            C++編譯器在編譯的時候,要確定每個對象調(diào)用的函數(shù)的地址,這稱為早期綁定(early binding),當(dāng)我們將fish類的對象fh的地址賦給pAn時,C++編譯器進行了類型轉(zhuǎn)換,此時C++編譯器認(rèn)為變量pAn保存就是animal對象的地址。當(dāng)在main()函數(shù)中執(zhí)行pAn->breathe()時,調(diào)用的當(dāng)然就是animal對象的breathe函數(shù)。

            2、  內(nèi)存模型的角度

            我們給出了fish對象內(nèi)存模型,如下圖所示:







            圖1- 1 fish類對象的內(nèi)存模型

            我們構(gòu)造fish類的對象時,首先要調(diào)用animal類的構(gòu)造函數(shù)去構(gòu)造animal類的對象,然后才調(diào)用fish類的構(gòu)造函數(shù)完成自身部分的構(gòu)造,從而拼接出一個完整的fish對象。當(dāng)我們將fish類的對象轉(zhuǎn)換為animal類型時,該對象就被認(rèn)為是原對象整個內(nèi)存模型的上半部分,也就是圖1-1中的“animal的對象所占內(nèi)存”。那么當(dāng)我們利用類型轉(zhuǎn)換后的對象指針去調(diào)用它的方法時,當(dāng)然也就是調(diào)用它所在的內(nèi)存中的方法。因此,出現(xiàn)圖2.13所示的結(jié)果,也就順理成章了。

            2.   多態(tài)性和虛函數(shù)

            正如很多學(xué)員所想,在例1-1的程序中,我們知道pAn實際指向的是fish類的對象,我們希望輸出的結(jié)果是魚的呼吸方法,即調(diào)用fish類的breathe方法。這個時候,就該輪到虛函數(shù)登場了。

            前面輸出的結(jié)果是因為編譯器在編譯的時候,就已經(jīng)確定了對象調(diào)用的函數(shù)的地址,要解決這個問題就要使用遲綁定(late binding)技術(shù)。當(dāng)編譯器使用遲綁定時,就會在運行時再去確定對象的類型以及正確的調(diào)用函數(shù)。而要讓編譯器采用遲綁定,就要在基類中聲明函數(shù)時使用virtual關(guān)鍵字(注意,這是必須的,很多學(xué)員就是因為沒有使用虛函數(shù)而寫出很多錯誤的例子),這樣的函數(shù)我們稱為虛函數(shù)。一旦某個函數(shù)在基類中聲明為virtual,那么在所有的派生類中該函數(shù)都是virtual,而不需要再顯示的聲明為virtual

            下面修改例1-1的代碼,將animal類中的breathe()函數(shù)聲明為virtual,如下:

            1- 2

            #include <iostream.h>

            class animal

            {

            public:

                   void sleep()

                   {

                          cout<<"animal sleep"<<endl;

                   }

                   virtual void breathe()

                   {

                          cout<<"animal breathe"<<endl;

                   }

            };

            class fish:public animal

            {

            public:

                   void breathe()

                   {

                          cout<<"fish bubble"<<endl;

                   }

            };

            void main()

            {

                   fish fh;

                   animal *pAn=&fh;

                   pAn->breathe();

            }

            大家可以再次運行這個程序,你會發(fā)現(xiàn)結(jié)果是“fish bubble”,也就是根據(jù)對象的類型調(diào)用了正確的函數(shù)。

            那么當(dāng)我們將breathe()聲明為virtual時,在背后發(fā)生了什么呢?

            編譯器在編譯的時候,發(fā)現(xiàn)animal類中有虛函數(shù),此時編譯器會為每個包含虛函數(shù)的類創(chuàng)建一個虛表(即vtable),該表是一個一維數(shù)組,在這個數(shù)組中存放每個虛函數(shù)的地址。對于例1-2的程序,animal和fish類都包含了一個虛函數(shù)breathe(),因此編譯器會為這兩個類都建立一個虛表,如下圖所示:

            圖1- 2 animal類和fish類的虛表

                   那么如何定位虛表呢?編譯器另外還為每個類提供了一個虛表指針(即vptr),這個指針指向了對象的虛表。在程序運行時,根據(jù)對象的類型去初始化vptr,從而讓vptr正確的指向所屬類的虛表,從而在調(diào)用虛函數(shù)時,就能夠找到正確的函數(shù)。對于例1-2的程序,由于pAn實際指向的對象類型是fish,因此vptr指向的fish類的vtable,當(dāng)調(diào)用pAn->breathe()時,根據(jù)虛表中的函數(shù)地址找到的就是fish類的breathe()函數(shù)。

            正是由于每個對象調(diào)用的虛函數(shù)都是通過虛表指針來索引的,也就決定了虛表指針的正確初始化是非常重要的。換句話說,在虛表指針沒有正確初始化之前,我們不能夠去調(diào)用虛函數(shù)。那么虛表指針在什么時候,或者說在什么地方初始化呢?

            答案是在構(gòu)造函數(shù)中進行虛表的創(chuàng)建和虛表指針的初始化。還記得構(gòu)造函數(shù)的調(diào)用順序嗎,在構(gòu)造子類對象時,要先調(diào)用父類的構(gòu)造函數(shù),此時編譯器只“看到了”父類,并不知道后面是否后還有繼承者,它初始化父類的虛表指針,該虛表指針指向父類的虛表。當(dāng)執(zhí)行子類的構(gòu)造函數(shù)時,子類的虛表指針被初始化,指向自身的虛表。對于例2-2的程序來說,當(dāng)fish類的fh對象構(gòu)造完畢后,其內(nèi)部的虛表指針也就被初始化為指向fish類的虛表。在類型轉(zhuǎn)換后,調(diào)用pAn->breathe(),由于pAn實際指向的是fish類的對象,該對象內(nèi)部的虛表指針指向的是fish類的虛表,因此最終調(diào)用的是fish類的breathe()函數(shù)。

            要注意:對于虛函數(shù)調(diào)用來說,每一個對象內(nèi)部都有一個虛表指針,該虛表指針被初始化為本類的虛表。所以在程序中,不管你的對象類型如何轉(zhuǎn)換,但該對象內(nèi)部的虛表指針是固定的,所以呢,才能實現(xiàn)動態(tài)的對象函數(shù)調(diào)用,這就是C++多態(tài)性實現(xiàn)的原理。

            總結(jié)(基類有虛函數(shù)):

            1、  每一個類都有虛表。

            2、  虛表可以繼承,如果子類沒有重寫虛函數(shù),那么子類虛表中仍然會有該函數(shù)的地址,只不過這個地址指向的是基類的虛函數(shù)實現(xiàn)。如果基類3個虛函數(shù),那么基類的虛表中就有三項(虛函數(shù)地址),派生類也會有虛表,至少有三項,如果重寫了相應(yīng)的虛函數(shù),那么虛表中的地址就會改變,指向自身的虛函數(shù)實現(xiàn)。如果派生類有自己的虛函數(shù),那么虛表中就會添加該項。

            3、  派生類的虛表中虛函數(shù)地址的排列順序和基類的虛表中虛函數(shù)地址排列順序相同。

            3.   VC視頻第三課this指針說明

            我在論壇的VC教學(xué)視頻版面發(fā)了帖子,是模擬MFC類庫的例子寫的,主要是說明在基類的構(gòu)造函數(shù)中保存的this指針是指向子類的,我們在看一下這個例子:

            例1- 3

            #include <iostream.h>

            class base;

            base * pbase;

            class base

            {

            public:

                   base()

                   {

                          pbase=this;

                          

                   }

                   virtual void fn()

                   {

                          cout<<"base"<<endl;

                   }

            };

            class derived:public base

            {

                   void fn()

                   {

                          cout<<"derived"<<endl;

                   }

            };

            derived aa;

            void main()

            {

                   pbase->fn();

            }

            我在base類的構(gòu)造函數(shù)中將this指針保存到pbase全局變量中。在定義全局對象aa,即調(diào)用derived aa;時,要調(diào)用基類的構(gòu)造函數(shù),先構(gòu)造基類的部分,然后是子類的部分,由這兩部分拼接出完整的對象aa。這個this指針指向的當(dāng)然也就是aa對象,那么我們main()函數(shù)中利用pbase調(diào)用fn(),因為pbase實際指向的是aa對象,而aa對象內(nèi)部的虛表指針指向的是自身的虛表,最終調(diào)用的當(dāng)然是derived類中的fn()函數(shù)。

            在這個例子中,由于我的疏忽,在derived類中聲明fn()函數(shù)時,忘了加public關(guān)鍵字,導(dǎo)致聲明為了private(默認(rèn)為private),但通過前面我們所講述的虛函數(shù)調(diào)用機制,我們也就明白了這個地方并不影響它輸出正確的結(jié)果。不知道這算不算C++的一個Bug,因為虛函數(shù)的調(diào)用是在運行時確定調(diào)用哪一個函數(shù),所以編譯器在編譯時,并不知道pbase指向的是aa對象,所以導(dǎo)致這個奇怪現(xiàn)象的發(fā)生。如果你直接用aa對象去調(diào)用,由于對象類型是確定的(注意aa是對象變量,不是指針變量),編譯器往往會采用早期綁定,在編譯時確定調(diào)用的函數(shù),于是就會發(fā)現(xiàn)fn()是私有的,不能直接調(diào)用。:)

            許多學(xué)員在寫這個例子時,直接在基類的構(gòu)造函數(shù)中調(diào)用虛函數(shù),前面已經(jīng)說了,在調(diào)用基類的構(gòu)造函數(shù)時,編譯器只“看到了”父類,并不知道后面是否后還有繼承者,它只是初始化父類的虛表指針,讓該虛表指針指向父類的虛表,所以你看到結(jié)果當(dāng)然不正確。只有在子類的構(gòu)造函數(shù)調(diào)用完畢后,整個虛表才構(gòu)建完畢,此時才能真正應(yīng)用C++的多態(tài)性。換句話說,我們不要在構(gòu)造函數(shù)中去調(diào)用虛函數(shù),當(dāng)然如果你只是想調(diào)用本類的函數(shù),也無所謂。

            4.   參考資料:

            1、文章《在VC6.0中虛函數(shù)的實現(xiàn)方法》,作者:backer ,網(wǎng)址:

            http://www.mybole.com.cn/bbs/dispbbs.asp?boardid=4&id=1012&star=1

            2、書《C++編程思想》 機械工業(yè)出版社

            5.   后記

            本想再寫詳細(xì)些,發(fā)現(xiàn)時間不夠,總是有很多事情,在加上水平也有限,想想還是以后再說吧。不過我相信,這些內(nèi)容也能夠幫助大家很好的理解了。也歡迎網(wǎng)友能夠繼續(xù)補充,大家可以鼓動鼓動backer,讓他從匯編的角度再給一個說明,哈哈,別說我說的。


            posted on 2006-03-09 10:56 Beginning to 編程 閱讀(366) 評論(0)  編輯 收藏 引用 所屬分類: 程序摘錄

            導(dǎo)航

            統(tǒng)計

            常用鏈接

            留言簿(4)

            隨筆分類

            隨筆檔案

            文章檔案

            相冊

            BlogDev

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            久久伊人精品一区二区三区| 日韩亚洲欧美久久久www综合网| 久久国产视频99电影| 热久久国产欧美一区二区精品| www亚洲欲色成人久久精品| 成人精品一区二区久久久| 99久久免费国产精品特黄| 99久久无色码中文字幕| 无码任你躁久久久久久久| av午夜福利一片免费看久久| 91精品国产色综久久| 精品国产乱码久久久久久呢 | 久久国产亚洲精品无码| 精品国产一区二区三区久久蜜臀| 国内精品久久久久影院薰衣草 | 精品久久久久久成人AV| 久久国产精品一区二区| 狠狠色婷婷久久一区二区| 99久久夜色精品国产网站| 亚洲精品国产字幕久久不卡| 午夜视频久久久久一区| 青青草国产精品久久久久| 久久国产免费观看精品3| 久久涩综合| 无码国内精品久久人妻麻豆按摩| 国产欧美久久一区二区| 久久婷婷国产剧情内射白浆| 久久婷婷五月综合色99啪ak| 日本精品久久久中文字幕| 国产综合久久久久久鬼色| 中文字幕乱码久久午夜| 精品久久久久久久久免费影院| 青草久久久国产线免观| 日日狠狠久久偷偷色综合免费| 久久福利青草精品资源站| 国产亚洲欧美成人久久片 | 国产成人精品久久免费动漫| 国产精品对白刺激久久久| 久久国产亚洲精品无码| 精品久久久久久综合日本| 精品综合久久久久久97超人|