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

Beginning to 編程

VC++ 方面編程文章

 

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


 

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

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

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

1.   函數的覆蓋

我們先看一個例子:

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的程序中沒有定義虛函數。考慮一下例1-1的程序執(zhí)行的結果是什么?

       答案是輸出:animal breathe

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

1、  編譯的角度

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

2、  內存模型的角度

我們給出了fish對象內存模型,如下圖所示:







圖1- 1 fish類對象的內存模型

我們構造fish類的對象時,首先要調用animal類的構造函數去構造animal類的對象,然后才調用fish類的構造函數完成自身部分的構造,從而拼接出一個完整的fish對象。當我們將fish類的對象轉換為animal類型時,該對象就被認為是原對象整個內存模型的上半部分,也就是圖1-1中的“animal的對象所占內存”。那么當我們利用類型轉換后的對象指針去調用它的方法時,當然也就是調用它所在的內存中的方法。因此,出現圖2.13所示的結果,也就順理成章了。

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

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

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

下面修改例1-1的代碼,將animal類中的breathe()函數聲明為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ā)現結果是“fish bubble”,也就是根據對象的類型調用了正確的函數。

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

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

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

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

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

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

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

總結(基類有虛函數):

1、  每一個類都有虛表。

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

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

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

我在論壇的VC教學視頻版面發(fā)了帖子,是模擬MFC類庫的例子寫的,主要是說明在基類的構造函數中保存的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類的構造函數中將this指針保存到pbase全局變量中。在定義全局對象aa,即調用derived aa;時,要調用基類的構造函數,先構造基類的部分,然后是子類的部分,由這兩部分拼接出完整的對象aa。這個this指針指向的當然也就是aa對象,那么我們main()函數中利用pbase調用fn(),因為pbase實際指向的是aa對象,而aa對象內部的虛表指針指向的是自身的虛表,最終調用的當然是derived類中的fn()函數。

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

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

4.   參考資料:

1、文章《在VC6.0中虛函數的實現方法》,作者:backer ,網址:

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

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

5.   后記

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


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

導航

統計

常用鏈接

留言簿(4)

隨筆分類

隨筆檔案

文章檔案

相冊

BlogDev

搜索

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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资源网站| 国产精品综合不卡av| 亚洲综合清纯丝袜自拍| 日韩视频欧美视频| 欧美极品色图| 中文一区二区在线观看| 亚洲乱码国产乱码精品精| a4yy欧美一区二区三区| 亚洲国产欧洲综合997久久| 欧美日韩1080p| 一区二区三区国产精华| 亚洲精品乱码久久久久久久久| 欧美成人激情视频| 一区二区三区高清视频在线观看| 亚洲精品国精品久久99热一| 欧美日本韩国一区| 亚洲天堂网在线观看| 亚洲女与黑人做爰| 亚洲高清不卡在线| 99在线精品视频| 国产综合精品一区| 中日韩美女免费视频网站在线观看| 国产精品久久久久久久一区探花 | 亚洲高清一区二| 欧美视频在线观看免费| 欧美在线综合视频| 久久手机精品视频| 性伦欧美刺激片在线观看| 免费观看久久久4p| 久久成人综合网| 国产精品视频xxx| 亚洲黄色大片| 亚洲国产精品成人综合| 一区二区久久久久久| 亚洲经典在线看| 另类人畜视频在线| 久久这里只有精品视频首页| 国产欧美在线视频| 午夜精品一区二区三区电影天堂| 亚洲视频每日更新| 欧美日韩精品免费观看| 国产欧美一区二区精品忘忧草| 亚洲福利在线视频| 久久一区二区视频| 久久久久欧美精品| 黄色成人在线网站| 欧美成人国产| 亚洲午夜精品一区二区| 欧美一级电影久久| 在线看国产日韩| 欧美大片国产精品| 9色精品在线| 欧美一区二区三区四区视频| 国产婷婷色一区二区三区| 久久久久久九九九九| 亚洲国产日韩一区| 欧美一区二区三区电影在线观看| 黄页网站一区| 欧美日韩成人| 亚洲综合三区| 亚洲国产日韩欧美| 亚洲女爱视频在线| 伊人蜜桃色噜噜激情综合| 欧美紧缚bdsm在线视频| 亚洲欧美日本伦理| 夜夜精品视频| 久久一区二区三区四区| 亚洲精品日韩在线| 国内精品久久久久久久果冻传媒 | 亚洲欧美美女| 亚洲精品在线一区二区| 欧美成人免费小视频| 午夜天堂精品久久久久 | 亚洲在线视频免费观看| 欧美1区2区视频| 欧美一区二区日韩一区二区| 亚洲高清自拍| 在线观看亚洲一区| 国产一区二区成人| 中文精品99久久国产香蕉| 久久久久久亚洲精品中文字幕 | 国产精品久久久久久久久久尿| 久久国产免费| 久久久夜精品| 蜜臀av性久久久久蜜臀aⅴ四虎| 欧美一级大片在线免费观看| 亚洲欧美在线播放| 欧美一级专区| 久久精品91久久香蕉加勒比| 午夜在线视频观看日韩17c| 亚洲欧美日韩国产另类专区| 亚洲综合日韩在线| 久久视频一区| 欧美女人交a| 国内精品视频久久| 亚洲精品日韩久久| 性欧美超级视频| 欧美 日韩 国产在线| 91久久精品www人人做人人爽| 亚洲精品乱码久久久久久黑人| 亚洲亚洲精品在线观看| 欧美大胆成人| 国产精品天天摸av网| 亚洲美女精品成人在线视频| 午夜激情一区| 亚洲精品一区二区三区四区高清| 亚洲自拍偷拍麻豆| 欧美福利精品| 亚洲国产成人91精品| 久久精品一本| 亚洲专区在线| 国产精品乱人伦中文| 夜夜嗨av一区二区三区| 亚洲国产精品视频一区| 久久精品国产亚洲aⅴ| 国产精品丝袜91| 亚洲欧美在线磁力| 夜夜爽av福利精品导航 | 一片黄亚洲嫩模| 欧美精品电影| 妖精视频成人观看www| 亚洲精品美女免费| 欧美日韩精品免费观看视一区二区 | 久久久女女女女999久久| 国产日韩欧美不卡| 久久免费精品视频| 久久久亚洲午夜电影| 亚洲精品久久| 亚洲性图久久| 在线观看一区二区精品视频| 欧美国产日韩xxxxx| 欧美精品在线观看一区二区| 在线视频一区观看| 性色av一区二区三区| 亚洲二区精品| 亚洲小少妇裸体bbw| 激情欧美一区二区三区在线观看| 欧美国产成人在线| 国产精品久久久久久久电影| 久久综合亚州| 欧美色视频日本高清在线观看| 欧美专区第一页| 欧美精品一区二区久久婷婷| 欧美一区永久视频免费观看| 久久综合给合久久狠狠色| 亚洲视频欧美视频| 久久久亚洲一区| 久久久美女艺术照精彩视频福利播放 | 亚洲欧美日韩精品综合在线观看| 亚洲人成网站777色婷婷| 久久久久99| 久久久久久久久久久成人| 欧美岛国激情| 国产欧美日韩在线播放| 亚洲第一综合天堂另类专| 国产视频久久久久| 亚洲一区二区网站| 欧美在线资源| 国产人妖伪娘一区91| 国产精品99久久久久久久久久久久 | 国产精品一区二区a| 中国成人在线视频| 欧美一二三区精品| 国产视频亚洲精品| 久久九九电影| 免费亚洲一区|