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

開源之路

憶往昔, 項羽不過江. 江東好風光! 今振臂一呼,率甲三千, 試問天!
posts - 86, comments - 55, trackbacks - 0, articles - 0
  C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

虛函數

Posted on 2006-07-18 15:54 江邊之鳥 閱讀(402) 評論(2)  編輯 收藏 引用
虛函數聯系到多態,多態聯系到繼承。所以本文中都是在繼承層次上做文章。沒了繼承,什么都沒得談。
?
下面是小弟對C++的虛函數這玩意兒的理解。
?
一,? 什么是虛函數(如果不知道虛函數為何物,但有急切的想知道,那你就應該從這里開始)
?
簡單地說,那些被virtual關鍵字修飾的成員函數,就是虛函數。虛函數的作用,用專業術語來解釋就是實現多態性(Polymorphism),多態性是將接口與實現進行分離;用形象的語言來解釋就是實現以共同的方法,但因個體差異而采用不同的策略。下面來看一段簡單的代碼
?
class A{
?
public:
?
??? void print(){ cout<<”This is A”<<endl;}
?
};
?
class B:public A{
?
public:
?
??? void print(){ cout<<”This is B”<<endl;}
?
};
?
int main(){?? //為了在以后便于區分,我這段main()代碼叫做main1
?
?? A a;
?
?? B b;
?
?? a.print();
?
?? b.print();
?
}
?
通過class A和class B的print()這個接口,可以看出這兩個class因個體的差異而采用了不同的策略,輸出的結果也是我們預料中的,分別是This is A和This is B。但這是否真正做到了多態性呢?No,多態還有個關鍵之處就是一切用指向基類的指針或引用來操作對象。那現在就把main()處的代碼改一改。
?
int main(){?? //main2
?
??? A a;
?
??? B b;
?
??? A* p1=&a;
?
??? A* p2=&b;
?
??? p1->print();
?
??? p2->print();
?
}
?
運行一下看看結果,喲呵,驀然回首,結果卻是兩個This is A。問題來了,p2明明指向的是class B的對象但卻是調用的class A的print()函數,這不是我們所期望的結果,那么解決這個問題就需要用到虛函數
?
class A{
?
public:
?
??? virtual void print(){ cout<<”This is A”<<endl;}? //現在成了虛函數了
?
};
?
class B:public A{
?
public:
?
??? void print(){ cout<<”This is B”<<endl;}? //這里需要在前面加上關鍵字virtual嗎?
?
};
?
毫無疑問,class A的成員函數print()已經成了虛函數,那么class B的print()成了虛函數了嗎?回答是Yes,我們只需在把基類的成員函數設為virtual,其派生類的相應的函數也會自動變為虛函數。所以,class B的print()也成了虛函數。那么對于在派生類的相應函數前是否需要用virtual關鍵字修飾,那就是你自己的問題了。
?
現在重新運行main2的代碼,這樣輸出的結果就是This is A和This is B了。
?
現在來消化一下,我作個簡單的總結,指向基類的指針在操作它的多態類對象時,會根據不同的類對象,調用其相應的函數,這個函數就是虛函數。
?
二,? 虛函數是如何做到的(如果你沒有看過《Inside The C++ Object Model》這本書,但又急切想知道,那你就應該從這里開始)
?
虛函數是如何做到因對象的不同而調用其相應的函數的呢?現在我們就來剖析虛函數。我們先定義兩個類
?
class A{?? //虛函數示例代碼
?
public:
?
?? virtual void fun(){cout<<1<<endl;}
?
?? virtual void fun2(){cout<<2<<endl;}
?
};
?
class B:public A{
?
public:
?
?? void fun(){cout<<3<<endl;}
?
?? void fun2(){cout<<4<<endl;}
?
};
?
由于這兩個類中有虛函數存在,所以編譯器就會為他們兩個分別插入一段你不知道的數據,并為他們分別創建一個表。那段數據叫做vptr指針,指向那個表。那個表叫做vtbl,每個類都有自己的vtbl,vtbl的作用就是保存自己類中虛函數的地址,我們可以把vtbl形象地看成一個數組,這個數組的每個元素存放的就是虛函數的地址,請看圖
?
?
?
通過上圖,可以看到這兩個vtbl分別為class A和class B服務。現在有了這個模型之后,我們來分析下面的代碼
?
A *p=new A;
?
p->fun();
?
毫無疑問,調用了A::fun(),但是A::fun()是如何被調用的呢?它像普通函數那樣直接跳轉到函數的代碼處嗎?No,其實是這樣的,首先是取出vptr的值,這個值就是vtbl的地址,再根據這個值來到vtbl這里,由于調用的函數A::fun()是第一個虛函數,所以取出vtbl第一個slot里的值,這個值就是A::fun()的地址了,最后調用這個函數。現在我們可以看出來了,只要vptr不同,指向的vtbl就不同,而不同的vtbl里裝著對應類的虛函數地址,所以這樣虛函數就可以完成它的任務。
?
而對于class A和class B來說,他們的vptr指針存放在何處呢?其實這個指針就放在他們各自的實例對象里。由于class A和class B都沒有數據成員,所以他們的實例對象里就只有一個vptr指針。通過上面的分析,現在我們來實作一段代碼,來描述這個帶有虛函數的類的簡單模型。
?
#include<iostream>
?
using namespace std;
?
//將上面“虛函數示例代碼”添加在這里
?
int main(){
?
? void (*fun)(A*);
?
? A *p=new B;
?
? long lVptrAddr;
?
? memcpy(&lVptrAddr,p,4);
?
? memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4);
?
? fun(p);
?
? delete p;
?
? system("pause");
?
}
?
用VC或Dev-C++編譯運行一下,看看結果是不是輸出3,如果不是,那么太陽明天肯定是從西邊出來。現在一步一步開始分析
?
void (*fun)(A*);? 這段定義了一個函數指針名字叫做fun,而且有一個A*類型的參數,這個函數指針待會兒用來保存從vtbl里取出的函數地址
?
A* p=new B;? 這個我不太了解,算了,不解釋這個了
?
long lVptrAddr;? 這個long類型的變量待會兒用來保存vptr的值
?
memcpy(&lVptrAddr,p,4);? 前面說了,他們的實例對象里只有vptr指針,所以我們就放心大膽地把p所指的4bytes內存里的東西復制到lVptrAddr中,所以復制出來的4bytes內容就是vptr的值,即vtbl的地址
?
現在有了vtbl的地址了,那么我們現在就取出vtbl第一個slot里的內容
?
memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4);? 取出vtbl第一個slot里的內容,并存放在函數指針fun里。需要注意的是lVptrAddr里面是vtbl的地址,但lVptrAddr不是指針,所以我們要把它先轉變成指針類型
?
fun(p);? 這里就調用了剛才取出的函數地址里的函數,也就是調用了B::fun()這個函數,也許你發現了為什么會有參數p,其實類成員函數調用時,會有個this指針,這個p就是那個this指針,只是在一般的調用中編譯器自動幫你處理了而已,而在這里則需要自己處理。
?
delete p;和system("pause");? 這個我不太了解,算了,不解釋這個了
?
如果調用B::fun2()怎么辦?那就取出vtbl的第二個slot里的值就行了
?
memcpy(&fun,reinterpret_cast<long*>(lVptrAddr+4),4); 為什么是加4呢?因為一個指針的長度是4bytes,所以加4。或者memcpy(&fun,reinterpret_cast<long*>(lVptrAddr)+1,4); 這更符合數組的用法,因為lVptrAddr被轉成了long*型別,所以+1就是往后移sizeof(long)的長度
?
三,? 以一段代碼開始
?
#include<iostream>
?
using namespace std;
?
class A{?? //虛函數示例代碼2
?
public:
?
? virtual void fun(){ cout<<"A::fun"<<endl;}
?
? virtual void fun2(){cout<<"A::fun2"<<endl;}
?
};
?
class B:public A{
?
public:
?
? void fun(){ cout<<"B::fun"<<endl;}
?
? void fun2(){ cout<<"B::fun2"<<endl;}
?
};? //end//虛函數示例代碼2
?
int main(){
?
void (A::*fun)();? //定義一個函數指針
?
A *p=new B;
?
fun=&A::fun;
?
(p->*fun)();
?
fun = &A::fun2;
?
(p->*fun)();
?
delete p;
?
system("pause");
?
}
?
你能估算出輸出結果嗎?如果你估算出的結果是A::fun和A::fun2,呵呵,恭喜恭喜,你中圈套了。其實真正的結果是B::fun和B::fun2,如果你想不通就接著往下看。給個提示,&A::fun和&A::fun2是真正獲得了虛函數的地址嗎?
?
首先我們回到第二部分,通過段實作代碼,得到一個“通用”的獲得虛函數地址的方法
?
#include<iostream>
?
using namespace std;
?
//將上面“虛函數示例代碼2”添加在這里
?
void CallVirtualFun(void* pThis,int index=0){
?
? void (*funptr)(void*);
?
? long lVptrAddr;
?
? memcpy(&lVptrAddr,pThis,4);
?
? memcpy(&funptr,reinterpret_cast<long*>(lVptrAddr)+index,4);
?
? funptr(pThis); //調用
?
}
?
int main(){
?
?? A* p=new B;
?
?? CallVirtualFun(p);? //調用虛函數p->fun()
?
?? CallVirtualFun(p,1);//調用虛函數p->fun2()
?
?? system("pause");
?
}
?
現在我們擁有一個“通用”的CallVirtualFun方法。
?
這個通用方法和第三部分開始處的代碼有何聯系呢?聯系很大。由于A::fun()和A::fun2()是虛函數,所以&A::fun和&A::fun2獲得的不是函數的地址,而是一段間接獲得虛函數地址的一段代碼的地址,我們形象地把這段代碼看作那段CallVirtualFun。編譯器在編譯時,會提供類似于CallVirtualFun這樣的代碼,當你調用虛函數時,其實就是先調用的那段類似CallVirtualFun的代碼,通過這段代碼,獲得虛函數地址后,最后調用虛函數,這樣就真正保證了多態性。同時大家都說虛函數的效率低,其原因就是,在調用虛函數之前,還調用了獲得虛函數地址的代碼。
?
?

最后的說明:本文的代碼可以用VC6和Dev-C++4.9.8.0通過編譯,且運行無問題。其他的編譯器小弟不敢保證。其中,里面的類比方法只能看成模型,因為不同的編譯器的低層實現是不同的。例如this指針,Dev-C++的gcc就是通過壓棧,當作參數傳遞,而VC的編譯器則通過取出地址保存在ecx中。所以這些類比方法不能當作具體實現。

Feedback

# re: 虛函數[未登錄]  回復  更多評論   

2007-05-02 08:27 by Ben
你的工作是干嗎的?

# re: 虛函數  回復  更多評論   

2007-05-09 10:21 by 江邊之鳥
普通的程序員@Ben

只有注冊用戶登錄后才能發表評論。
網站導航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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热精品在线观看| 亚洲人成网站999久久久综合| 在线观看成人小视频| 狠狠色综合网站久久久久久久| 国产亚洲精品一区二区| 国内精品久久久久久影视8| 一色屋精品视频在线观看网站| 精品不卡在线| 日韩一级大片在线| 午夜精品久久久久久久99樱桃| 久久激情五月丁香伊人| 欧美韩日一区二区| 亚洲少妇在线| 久久成人一区二区| 欧美韩日视频| 国产麻豆精品视频| 亚洲电影免费| 久久av二区| 噜噜噜91成人网| 欧美日韩精品一二三区| 国产精品试看| 亚洲国产裸拍裸体视频在线观看乱了| 日韩一级不卡| 玖玖在线精品| 一区二区三区日韩精品视频| 性欧美videos另类喷潮| 欧美精品在线一区二区| 国产一区二区av| 一区二区高清在线| 美女视频黄免费的久久| 一区二区欧美日韩| 猫咪成人在线观看| 国产日韩精品久久久| 亚洲精品久久久久久久久| 久久久久久夜| 一本色道久久88精品综合| 久久久噜噜噜久久久| 国产伦精品一区二区三区照片91| 亚洲精选视频免费看| 久久久www| 亚洲视频在线二区| 欧美极品在线播放| 91久久精品国产91性色tv| 久久精品国产清高在天天线| 日韩视频精品在线| 欧美激情亚洲自拍| 亚洲高清不卡| 理论片一区二区在线| 亚洲无玛一区| 国产精品二区二区三区| 亚洲视频专区在线| 亚洲精品久久嫩草网站秘色| 久久久免费av| 激情欧美丁香| 久久一区二区三区超碰国产精品| 亚洲免费伊人电影在线观看av| 欧美日韩免费网站| 亚洲一区二区欧美日韩| 亚洲三级免费观看| 欧美乱妇高清无乱码| 亚洲另类自拍| 日韩视频一区| 欧美午夜精品久久久| 亚洲小说春色综合另类电影| 夜夜嗨av一区二区三区中文字幕 | 日韩一级黄色大片| 欧美激情91| 欧美福利一区| 99在线热播精品免费| 亚洲美女福利视频网站| 欧美日韩精品一区二区在线播放 | 国产一区自拍视频| 午夜在线视频观看日韩17c| 日韩亚洲一区二区| 国产精品99免费看 | 久久噜噜噜精品国产亚洲综合| 午夜精品久久久久影视| 国产亚洲激情| 欧美国产综合视频| 欧美视频一区| 久久精品夜色噜噜亚洲a∨| 久久久久久久久岛国免费| 在线日韩欧美| 亚洲剧情一区二区| 国产精品乱码久久久久久| 久久久精品午夜少妇| 女生裸体视频一区二区三区| 99伊人成综合| 香蕉久久国产| 亚洲精品久久久一区二区三区| 在线视频欧美日韩| 伊人成人在线| 亚洲国产综合在线看不卡| 国产精品视频福利| 免费h精品视频在线播放| 欧美日韩国产在线看| 久久国产加勒比精品无码| 葵司免费一区二区三区四区五区| 亚洲日韩视频| 午夜精品久久久久久久久久久久| 亚洲国产日韩欧美在线动漫| 亚洲视频观看| 亚洲老板91色精品久久| 亚洲免费在线| 99精品国产热久久91蜜凸| 亚洲欧美在线高清| aⅴ色国产欧美| 美女精品在线| 久久久久久久久久久久久女国产乱| 欧美精品一区二区三区在线播放| 久久se精品一区二区| 欧美日韩国产综合视频在线观看 | 中日韩视频在线观看| 影音先锋欧美精品| 亚洲欧美在线一区| 亚洲无吗在线| 免费观看亚洲视频大全| 欧美一区激情视频在线观看| 欧美激情一区二区三区| 欧美高清在线视频观看不卡| 国产视频在线观看一区| 一本大道久久精品懂色aⅴ| 亚洲高清一区二| 久久激情综合| 久久亚洲捆绑美女| 国产深夜精品福利| 亚洲欧美另类国产| 欧美一区二区三区久久精品茉莉花| 欧美片第1页综合| 欧美日韩妖精视频| 一区二区三区偷拍| 久久一区亚洲| 久久亚洲精选| 黄色日韩精品| 久久久人成影片一区二区三区| 欧美综合二区| 国产欧美日韩综合一区在线播放 | 久久国产一区二区三区| 欧美一级一区| 国产欧美日韩不卡| 欧美一区三区三区高中清蜜桃| 欧美在线三级| 韩国一区二区三区在线观看| 欧美中文在线免费| 麻豆精品在线视频| 亚洲国产精品成人综合色在线婷婷 | 一区二区三区精品视频| 一区二区三区日韩在线观看| 欧美了一区在线观看| 艳女tv在线观看国产一区| 亚洲午夜激情网页| 国产精品影音先锋| 欧美在线网址| 欧美国产亚洲另类动漫| 在线视频欧美日韩精品| 国产精品国产三级国产普通话三级| 亚洲视频免费看| 久久久久久久波多野高潮日日| 在线观看日韩专区| 欧美 日韩 国产在线| 亚洲美女色禁图| 久久国产夜色精品鲁鲁99| 在线观看亚洲| 国产精品萝li| 久久综合给合久久狠狠狠97色69| 亚洲国产天堂久久国产91| 妖精视频成人观看www| 国产视频不卡| 欧美另类高清视频在线| 欧美夜福利tv在线| 亚洲激情网站| 久久久久久久精| 国产精品99久久久久久久久久久久 | 亚洲国产天堂久久综合网| 午夜伦理片一区| 亚洲欧洲日韩在线| 国产日韩精品一区观看| 欧美极品影院| 久久久福利视频| 亚洲视频久久| 亚洲黄色小视频| 久久麻豆一区二区| 亚洲一区欧美| 99re6热只有精品免费观看 | 亚洲欧洲一区二区天堂久久| 欧美一区二区三区在线| 亚洲另类在线一区| 精品99视频| 国产日本欧美一区二区三区在线| 欧美国产精品久久| 久久久久国内| 亚洲国产aⅴ天堂久久| 亚洲欧美电影院| 精品99一区二区| 国产欧美日韩专区发布| 欧美三级乱码| 免费观看欧美在线视频的网站| 欧美一区二区三区精品|