亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
查看: 12085 | 回復: 7
打印 上一主題 下一主題

[C++] 基類成員函數(shù)能否調用基類的純虛函數(shù) [復制鏈接]

論壇徽章:
0
跳轉到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2010-05-27 13:47 |只看該作者 |倒序瀏覽
請問,基類中的成員函數(shù)能否調用本基類的純虛函數(shù)哪?
如果可以,那被調用的函數(shù)就是在派生類中定義的。 基類是通過什么找到這個派生類中定義的成員函數(shù)?

論壇徽章:
324
射手座
日期:2013-08-23 12:04:38射手座
日期:2013-08-23 16:18:12未羊
日期:2013-08-30 14:33:15水瓶座
日期:2013-09-02 16:44:31摩羯座
日期:2013-09-25 09:33:52雙子座
日期:2013-09-26 12:21:10金牛座
日期:2013-10-14 09:08:49申猴
日期:2013-10-16 13:09:43子鼠
日期:2013-10-17 23:23:19射手座
日期:2013-10-18 13:00:27金牛座
日期:2013-10-18 15:47:57午馬
日期:2013-10-18 21:43:38
2 [報告]
發(fā)表于 2010-05-27 13:57 |只看該作者
虛函數(shù)表

論壇徽章:
0
3 [報告]
發(fā)表于 2010-05-27 13:59 |只看該作者
google一下,立即明白。。。

論壇徽章:
0
4 [報告]
發(fā)表于 2010-05-27 14:03 |只看該作者
能否詳細點哪??

論壇徽章:
0
5 [報告]
發(fā)表于 2010-05-27 14:07 |只看該作者
理解虛函數(shù)( virtual function )的幾個關鍵點:
1.       理解早綁定(early binding)、晚綁定(late binding)。所謂early binding:On compile time,就能明確一個函數(shù)調用是對哪個對象的哪個成員函數(shù)進行的,即編譯時就曉得了確定的函數(shù)地址;所謂late binding:On compile time,對函數(shù)(虛函數(shù))的調用被搞成了:pObj->_vptr->vtable[],從而導致不到runtime,完全不知道實際函數(shù)地址。直到程序運行時,執(zhí)行到這里,去vtable里拿到函數(shù)地址,才曉得。其實,原理很簡單,只是單看這些名詞的話會覺得好像很magic一樣。
2.       理解虛函數(shù)賴以生存的底層機制:vptr + vtable。虛函數(shù)的運行時實現(xiàn)采用了VPTR/VTBL的形式,這項技術的基礎:
①編譯器在后臺為每個包含虛函數(shù)的類產生一個靜態(tài)函數(shù)指針數(shù)組(虛函數(shù)表),在這個類或者它的基類中定義的每一個虛函數(shù)都有一個相應的函數(shù)指針。
②每個包含虛函數(shù)的類的每一個實例包含一個不可見的數(shù)據(jù)成員vptr(虛函數(shù)指針),這個指針被構造函數(shù)自動初始化,指向類的vtbl(虛函數(shù)表)
③當客戶調用虛函數(shù)的時候,編譯器產生代碼反指向到vptr,索引到vtbl中,然后在指定的位置上找到函數(shù)指針,并發(fā)出調用。
參考下面轉載文章:
虛函數(shù)是在類中被聲明為virtual的成員函數(shù),當編譯器看到通過指針或引用調用此類函數(shù)時,對其執(zhí)行晚綁定,即通過指針(或引用)指向的類的類型信息來決定該函數(shù)是哪個類的。通常此類指針或引用都聲明為基類的,它可以指向基類或派生類的對象。
多態(tài)指同一個方法根據(jù)其所屬的不同對象可以有不同的行為(根據(jù)自己理解,不知這么說是否嚴謹)。
舉個例子說明虛函數(shù)、多態(tài)、早綁定和晚綁定:
  李氏兩兄妹(哥哥和妹妹)參加姓氏運動會(不同姓氏組隊參加),哥哥男子項目比賽,妹妹參加女子項目比賽,開幕式有一個參賽隊伍代表發(fā)言儀式,兄妹倆都想去露露臉,可只能一人去,最終他們決定到時抓鬮決定,而組委會也不反對,它才不關心是哥哥還是妹妹來發(fā)言,只要派一個姓李的來說兩句話就行。運動會如期舉行,妹妹抓鬮獲得代表李家發(fā)言的機會,哥哥參加了男子項目比賽,妹妹參加了女子項目比賽。比賽結果就不是我們關心的了。
現(xiàn)在讓我們來做個類比(只討論與運動會相關的話題):
(1)類的設計:
  李氏兄妹屬于李氏家族,李氏是基類(這里還是抽象的純基類),李氏又派生出兩個子類(李氏男和李氏女),李氏男會所有男子項目的比賽(李氏男的成員函數(shù)),李氏女會所有女子項目的比賽(李氏女的成員函數(shù))。姓李的人都會發(fā)言(基類虛函數(shù)),李氏男和李氏女繼承自李氏當然也會發(fā)言,只是男女說話聲音不一樣,內容也會又差異,給人感覺不同(李氏男和李氏女分別重新定義發(fā)言這個虛函數(shù))。李氏兩兄妹就是李氏男和李氏女兩個類的實體。
(2)程序設計:
李氏兄妹填寫參賽報名表。
(3)編譯:
  李氏兄妹的參賽報名表被上交給組委會(編譯器),哥哥和妹妹分別參加男子和女子的比賽,組委會一看就明白了(早綁定),只是發(fā)言人選不明確,組委會看到報名表上寫的是“李家代表”(基類指針),組委會不能確定到底是誰,就做了個備注:如果是男的,就是哥哥李某某;如果是女的,就是妹妹李某某(晚綁定)。組委會做好其它準備工作后,就等運動會開始了(編譯完畢)。
(4)程序運行:
運動會開始了(程序開始運行),開幕式上我們聽到了李家妹妹的發(fā)言,如果是哥哥運氣好抓鬮勝出,我們將聽到哥哥的發(fā)言(多態(tài))。然后就是看到兄妹倆參加比賽了。。。
但愿這個比喻說清楚了虛函數(shù)、多態(tài)、早綁定和晚綁定的概念和它們之間的關系。再說一下,早綁定指編譯器在編譯期間即知道對象的具體類型并確定此對象調用成員函數(shù)的確切地址;而晚綁定是根據(jù)指針所指對象的類型信息得到類的虛函數(shù)表指針進而確定調用成員函數(shù)的確切地址。
  
2、揭密晚綁定的秘密
編譯器到底做了什么實現(xiàn)的虛函數(shù)的晚綁定呢?我們來探個究竟。
編譯器對每個包含虛函數(shù)的類創(chuàng)建一個表(稱為V TA B L E)。在V TA B L E中,編譯器放置特定類的虛函數(shù)地址。在每個帶有虛函數(shù)的類中,編譯器秘密地置一指針,稱為v p o i n t e r(縮寫為V P T R),指向這個對象的V TA B L E。通過基類指針做虛函數(shù)調用時(也就是做多態(tài)調用時),編譯器靜態(tài)地插入取得這個V P T R,并在V TA B L E表中查找函數(shù)地址的代碼,這樣就能調用正確的函數(shù)使晚捆綁發(fā)生。為每個類設置V TA B L E、初始化V P T R、為虛函數(shù)調用插入代碼,所有這些都是自動發(fā)生的,所以我們不必擔心這些。利用虛函數(shù),這個對象的合適的函數(shù)就能被調用,哪怕在編譯器還不知道這個對象的特定類型的情況下。(《C++編程思想》)
————這段話紅色加粗部分似乎有點問題,我個人的理解看后面的總結。
在任何類中不存在顯示的類型信息,可對象中必須存放類信息,否則類型不可能在運行時建立。那這個類信息是什么呢?我們來看下面幾個類:
class no_virtual
{
public:
     void fun1() const{}
     int  fun2() const { return a; }
private:
     int a;
}
class one_virtual
{
public:
     virtual void fun1() const{}
     int  fun2() const { return a; }
private:
     int a;
}
class two_virtual
{
public:
     virtual void fun1() const{}
     virtual int  fun2() const { return a; }
private:
     int a;
}
以上三個類中:
no_virtual沒有虛函數(shù),sizeof(no_virtual)=4,類no_virtual的長度就是其成員變量整型a的長度;
one_virtual有一個虛函數(shù),sizeof(one_virtual)=8;
two_virtual 有兩個虛函數(shù),sizeof(two_virtual)=8; 有一個虛函數(shù)和兩個虛函數(shù)的類的長度沒有區(qū)別,其實它們的長度就是no_virtual的長度加一個void指針的長度,它反映出,如果有一個或多個虛函數(shù),編譯器在這個結構中插入一個指針( V P T R)。在one_virtual 和 two_virtual之間沒有區(qū)別。這是因為V P T R指向一個存放地址的表,只需要一個指針,因為所有虛函數(shù)地址都包含在這個表中。
這個VPTR就可以看作類的類型信息。
那我們來看看編譯器是怎么建立VPTR指向的這個虛函數(shù)表的。先看下面兩個類:
class base
{
public:
     void bfun(){}
     virtual void vfun1(){}
     virtual int vfun2(){}
private:
     int a;
}
class derived : public base
{
public:
     void dfun(){}
     virtual void vfun1(){}
     virtual int vfun3(){}
private:
     int b;
}
兩個類VPTR指向的虛函數(shù)表(VTABLE)分別如下:
base類
                       ——————
VPTR——> |&base::vfun1 |
                       ——————
                  |&base::vfun2 |
                   ——————
      
derived類
                       ———————
VPTR——> |&derived::vfun1 |
                       ———————
                   |&base::vfun2    |
                   ———————
                   |&derived::vfun3 |
                    ———————
     
  每當創(chuàng)建一個包含有虛函數(shù)的類或從包含有虛函數(shù)的類派生一個類時,編譯器就為這個類創(chuàng)建一個VTABLE,如上圖所示。在這個表中,編譯器放置了在這個類中或在它的基類中所有已聲明為virtual的函數(shù)的地址。如果在這個派生類中沒有對在基類中聲明為virtual的函數(shù)進行重新定義,編譯器就使用基類的這個虛函數(shù)地址。(在derived的VTABLE中,vfun2的入口就是這種情況。)然后編譯器在這個類中放置VPTR。當使用簡單繼承時,對于每個對象只有一個VPTR。VPTR必須被初始化為指向相應的VTABLE,這在構造函數(shù)中發(fā)生。
一旦VPTR被初始化為指向相應的VTABLE,對象就"知道"它自己是什么類型。但只有當虛函數(shù)被調用時這種自我認知才有用。
個人總結如下:
1、從包含虛函數(shù)的類派生一個類時,編譯器就為該類創(chuàng)建一個VTABLE。其每一個表項是該類的虛函數(shù)地址。
2、在定義該派生類對象時,先調用其基類的構造函數(shù),然后再初始化VPTR,最后再調用派生類的構造函數(shù)(從二進制的視野來看,所謂基類子類是一個大結構體,其中this指針開頭的四個字節(jié)存放虛函數(shù)表頭指針。執(zhí)行子類的構造函數(shù)的時候,首先調用基類構造函數(shù),this指針作為參數(shù),在基類構造函數(shù)中填入基類的vptr,然后回到子類的構造函數(shù),填入子類的vptr,覆蓋基類填入的vptr。如此以來完成vptr的初始化。)
3、在實現(xiàn)動態(tài)綁定時,不能直接采用類對象,而一定要采用指針或者引用。因為采用類對象傳值方式,有臨時基類對象的產生,而采用指針,則是通過指針來訪問外部的派生類對象的VPTR來達到訪問派生類虛函數(shù)的結果。
VPTR 常常位于對象的開頭,編譯器能很容易地取到VPTR的值,從而確定VTABLE的位置。VPTR總指向VTABLE的開始地址,所有基類和它的子類的虛函數(shù)地址(子類自己定義的虛函數(shù)除外)在VTABLE中存儲的位置總是相同的,如上面base類和derived類的VTABLE中vfun1和vfun2 的地址總是按相同的順序存儲。編譯器知道vfun1位于VPTR處,vfun2位于VPTR+1處,因此在用基類指針調用虛函數(shù)時,編譯器首先獲取指針指向對象的類型信息(VPTR),然后就去調用虛函數(shù)。如一個base類指針pBase指向了一個derived對象,那pBase->vfun2 ()被編譯器翻譯為 VPTR+1 的調用,因為虛函數(shù)vfun2的地址在VTABLE中位于索引為1的位置上。同理,pBase->vfun3 ()被編譯器翻譯為 VPTR+2的調用。這就是所謂的晚綁定。
我們來看一下虛函數(shù)調用的匯編代碼,以加深理解。
void test(base* pBase)
{
  pBase->vfun2();
}
int main(int argc, char* argv[])
{
  derived td;
  

  test(&td);
  
  return 0;
}
derived td;編譯生成的匯編代碼如下:
  mov DWORD PTR _td$[esp+24], OFFSET FLAT?_7derived@@6B@ ; derived::`vftable'
  由編譯器的注釋可知,此時PTR _td$[esp+24]中存儲的就是derived類的VTABLE地址。
  
test(&td);編譯生成的匯編代碼如下:
  lea eax, DWORD PTR _td$[esp+24]   
  mov DWORD PTR __$EHRec$[esp+32], 0
  push eax
  call ?test@@YAXPAVbase@@@Z   ; test
  調用test函數(shù)時完成了如下工作:取對象td的地址,將其壓棧,然后調用test。
pBase->vfun2();編譯生成的匯編代碼如下:
   mov ecx, DWORD PTR _pBase$[esp-4]
  mov eax, DWORD PTR [ecx]
  jmp DWORD PTR [eax+4]
   首先從棧中取出pBase指針指向的對象地址賦給ecx,然后取對象開頭的指針變量中的地址賦給eax,此時eax的值即為VPTR的值,也就是 VTABLE的地址。最后就是調用虛函數(shù)了,由于vfun2位于VTABLE的第二個位置,相當于 VPTR+1,每個函數(shù)指針是4個字節(jié)長,所以最后的調用被編譯器翻譯為 jmp DWORD PTR [eax+4]。如果是調用pBase->vfun1(),這句就該被編譯為 jmp DWORD PTR [eax]。

論壇徽章:
0
6 [報告]
發(fā)表于 2010-05-27 15:07 |只看該作者
看懂了。 謝謝

論壇徽章:
0
7 [報告]
發(fā)表于 2010-05-28 09:49 |只看該作者
提示: 作者被禁止或刪除 內容自動屏蔽

論壇徽章:
0
8 [報告]
發(fā)表于 2010-05-28 12:15 |只看該作者
可惜用的是VC++的例子,發(fā)在本版的話要是G++的就更好了。
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復

  

北京盛拓優(yōu)訊信息技術有限公司. 版權所有 京ICP備16024965號-6 北京市公安局海淀分局網監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報專區(qū)
中國互聯(lián)網協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關心和支持過ChinaUnix的朋友們 轉載本站內容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP