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

posts - 200, comments - 8, trackbacks - 0, articles - 0

作者:July。
出處:http://blog.csdn.net/v_JULY_v 

 

前奏

    有關虛函數的問題層出不窮,有關虛函數的文章千篇一律,那為何還要寫這一篇有關虛函數的文章呢?看完本文后,相信能懂其意義之所在。同時,原狂想曲系列已經更名為程序員編程藝術系列,因為不再只專注于“面試”,而在“編程”之上了。ok,如果有不正之處,望不吝賜教。謝謝。


第一節、一道簡單的虛函數的面試題
題目要求:寫出下面程序的運行結果?

  1. //謝謝董天喆提供的這道百度的面試題   
  2. #include <iostream>  
  3. using namespace std;  
  4. class A{  
  5.   public:virtual void p()   
  6.   {   
  7.     cout << "A" << endl;   
  8.   }  
  9. };  
  10.   
  11. class B : public A  
  12. {  
  13.   public:virtual void p()   
  14.   { cout << "B" << endl;  
  15.   }  
  16. };  
  17.   
  18. int main()   
  19. {  
  20.   A * a = new A;  
  21.   A * b = new B;  
  22.   a->p();  
  23.   b->p();  
  24.   delete a;  
  25.   delete b;      
  26.   return 0;  
  27. }  

 

    我想,這道面試題應該是考察虛函數相關知識的相對簡單的一道題目了。然后,希望你碰到此類有關虛函數的面試題,不論其難度是難是易,都能夠舉一反三,那么本章的目的也就達到了。ok,請跟著我的思路,咱們步步深入(上面程序的輸出結果為A B)。

 

 

第二節、有無虛函數的區別
      1、當上述程序中的函數p()不是虛函數,那么程序的運行結果是如何?即如下代碼所示:

class A
{
public:
 void p() 
 { 
  cout << "A" << endl; 
 }
 
};

class B : public A
{
public:
 void p() 
 { 
  cout << "B" << endl;
 }
};

對的,程序此時將輸出兩個A,A。為什么?
我們知道,在構造一個類的對象時,如果它有基類,那么首先將構造基類的對象,然后才構造派生類自己的對象。如上,A* a=new A,調用默認構造函數構造基類A對象,然后調用函數p(),a->p();輸出A,這點沒有問題。
    然后,A * b = new B;,構造了派生類對象B,B由于是基類A的派生類對象,所以會先構造基類A對象,然后再構造派生類對象,但由于當程序中函數是非虛函數調用時,B類對象對函數p()的調用時在編譯時就已靜態確定了,所以,不論基類指針b最終指向的是基類對象還是派生類對象,只要后面的對象調用的函數不是虛函數,那么就直接無視,而調用基類A的p()函數。

      2、那如果加上虛函數呢?即如最開始的那段程序那樣,程序的輸出結果,將是什么?
在此之前,我們還得明確以下兩點:
    a、通過基類引用或指針調用基類中定義的函數時,我們并不知道執行函數的對象的確切類型,執行函數的對象可能是基類類型的,也可能是派生類型的。
    b、如果調用非虛函數,則無論實際對象是什么類型,都執行基類類型所定義的函數(如上述第1點所述)。如果調用虛函數,則直到運行時才能確定調用哪個函數,運行的虛函數是引用所綁定的或指針所指向的對象所屬類型定義的版本。

根據上述b的觀點,我們知道,如果加上虛函數,如上面這道面試題,

class A
{
public:
 virtual void p() 
 { 
  cout << "A" << endl; 
 }
 
};

class B : public A
{
public:
 virtual void p() 
 { 
  cout << "B" << endl;
 }
};

int main() 
{
 A * a = new A;
 A * b = new B;
 a->p();
 b->p();
 delete a;
 delete b;
    return 0;
}

 

那么程序的輸出結果將是A B。

所以,至此,咱們的這道面試題已經解決。但虛函數的問題,還沒有解決。


第三節、虛函數的原理與本質
    我們已經知道,虛(virtual)函數的一般實現模型是:每一個類(class)有一個虛表(virtual table),內含該class之中有作用的虛(virtual)函數的地址,然后每個對象有一個vptr,指向虛表(virtual table)的所在。

請允許我援引自深度探索c++對象模型一書上的一個例子:

class Point { 
public: 
   virtual ~Point();  

   virtual Point& mult( float ) = 0; 

   float x() const { return _x; }     //非虛函數,不作存儲
   virtual float y() const { return 0; }  
   virtual float z() const { return 0; }  
   // ...

protected: 
   Point( float x = 0.0 ); 
   float _x; 
};

      1、在Point的對象pt中,有兩個東西,一個是數據成員_x,一個是_vptr_Point。其中_vptr_Point指向著virtual table point,而virtual table(虛表)point中存儲著以下東西:

  • virtual ~Point()被賦值slot 1,
  • mult() 將被賦值slot 2.
  • y() is 將被賦值slot 3
  • z() 將被賦值slot 4.

class Point2d : public Point { 
public: 
   Point2d( float x = 0.0, float y = 0.0 )  
      : Point( x ), _y( y ) {} 
   ~Point2d();   //1

   //改寫base class virtual functions 
   Point2d& mult( float );  //2
   float y() const { return _y; }  //3

protected: 
   float _y; 
};

      2、在Point2d的對象pt2d中,有三個東西,首先是繼承自基類pt對象的數據成員_x,然后是pt2d對象本身的數據成員_y,最后是_vptr_Point。其中_vptr_Point指向著virtual table point2d。由于Point2d繼承自Point,所以在virtual table point2d中存儲著:改寫了的其中的~Point2d()、Point2d& mult( float )、float y() const,以及未被改寫的Point::z()函數。

class Point3d: public Point2d { 
public: 
   Point3d( float x = 0.0, 
            float y = 0.0, float z = 0.0 ) 
      : Point2d( x, y ), _z( z ) {} 
   ~Point3d();

   // overridden base class virtual functions 
   Point3d& mult( float ); 
   float z() const { return _z; }

   // ... other operations ... 
protected: 
   float _z; 
};

      3、在Point3d的對象pt3d中,則有四個東西,一個是_x,一個是_vptr_Point,一個是_y,一個是_z。其中_vptr_Point指向著virtual table point3d。由于point3d繼承自point2d,所以在virtual table point3d中存儲著:已經改寫了的point3d的~Point3d(),point3d::mult()的函數地址,和z()函數的地址,以及未被改寫的point2d的y()函數地址。

ok,上述1、2、3所有情況的詳情,請參考下圖。

本文,日后可能會酌情考慮增補有關內容。ok,更多,可參考深度探索c++對象模型一書第四章。
最近幾章難度都比較小,是考慮到狂想曲有深有淺的原則,后續章節會逐步恢復到相應難度。

 

第四節、虛函數的布局與匯編層面的考察

      ivan、老夢的兩篇文章繼續對虛函數進行了一番深入,我看他們已經寫得很好了,我就不饒舌了。ok,請看:1、VC虛函數布局引發的問題,2、從匯編層面深度剖析C++虛函數、http://blog.csdn.net/linyt/archive/2011/04/20/6336762.aspx

 

第五節、虛函數表的詳解

    本節全部內容來自淄博的共享,非常感謝。注@molixiaogemao:只有發生繼承的時候且父類子類都有virtual的時候才會出現虛函數指針,請不要忘了虛函數出現的目的是為了實現多態
 

 一般繼承(無虛函數覆蓋)
 下面,再讓我們來看看繼承時的虛函數表是什么樣的。假設有如下所示的一個繼承關系:

請注意,在這個繼承關系中,子類沒有重載任何父類的函數。那么,在派生類的實例中,

 對于實例:Derive d; 的虛函數表如下:


我們從表中可以看到下面幾點,
 1)覆蓋的f()函數被放到了虛表中原來父類虛函數的位置。
 2)沒有被覆蓋的函數依舊。
 
 這樣,我們就可以看到對于下面這樣的程序,
 Base *b = new Derive();

b->f();

由b所指的內存中的虛函數表的f()的位置已經被Derive::f()函數地址所取代,
于是在實際調用發生時,是Derive::f()被調用了。這就實現了多態。


多重繼承(無虛函數覆蓋)

下面,再讓我們來看看多重繼承中的情況,假設有下面這樣一個類的繼承關系(注意:子類并沒有覆蓋父類的函數):


我們可以看到:
1) 每個父類都有自己的虛表。
2) 子類的成員函數被放到了第一個父類的表中。(所謂的第一個父類是按照聲明順序來判斷的)

這樣做就是為了解決不同的父類類型的指針指向同一個子類實例,而能夠調用到實際的函數。


多重繼承(有虛函數覆蓋)
下面我們再來看看,如果發生虛函數覆蓋的情況。
下圖中,我們在子類中覆蓋了父類的f()函數。


我們可以看見,三個父類虛函數表中的f()的位置被替換成了子類的函數指針。
這樣,我們就可以任一靜態類型的父類來指向子類,并調用子類的f()了。如:

Derive d;
Base1 *b1 = &d;
Base2 *b2 = &d;
Base3 *b3 = &d;
b1->f(); //Derive::f()
b2->f(); //Derive::f()
b3->f(); //Derive::f()
b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()

 

安全性
每次寫C++的文章,總免不了要批判一下C++。
這篇文章也不例外。通過上面的講述,相信我們對虛函數表有一個比較細致的了解了。
水可載舟,亦可覆舟。下面,讓我們來看看我們可以用虛函數表來干點什么壞事吧。

一、通過父類型的指針訪問子類自己的虛函數
我們知道,子類沒有重載父類的虛函數是一件毫無意義的事情。因為多態也是要基于函數重載的。
雖然在上面的圖中我們可以看到Base1的虛表中有Derive的虛函數,但我們根本不可能使用下面的語句來調用子類的自有虛函數:

Base1 *b1 = new Derive();
b1->g1(); //編譯出錯

任何妄圖使用父類指針想調用子類中的未覆蓋父類的成員函數的行為都會被編譯器視為非法,即基類指針不能調用子類自己定義的成員函數。所以,這樣的程序根本無法編譯通過。
但在運行時,我們可以通過指針的方式訪問虛函數表來達到違反C++語義的行為。
(關于這方面的嘗試,通過閱讀后面附錄的代碼,相信你可以做到這一點)

二、訪問non-public的虛函數
另外,如果父類的虛函數是private或是protected的,但這些非public的虛函數同樣會存在于虛函數表中,
所以,我們同樣可以使用訪問虛函數表的方式來訪問這些non-public的虛函數,這是很容易做到的。
如:

class Base {
private: 
 virtual void f() { cout << "Base::f" << endl; } 
};

class Derive : public Base{ 
};
typedef void(*Fun)(void);
void main() {
 Derive d;
 Fun pFun = (Fun)*((int*)*(int*)(&d)+0);
 pFun(); 
}

對上面粗體部分的解釋(@a && x):

1. (int*)(&d)取vptr地址,該地址存儲的是指向vtbl的指針
2. (int*)*(int*)(&d)取vtbl地址,該地址存儲的是虛函數表數組
3. (Fun)*((int*)*(int*)(&d) +0),取vtbl數組的第一個元素,即Base中第一個虛函數f的地址
4. (Fun)*((int*)*(int*)(&d) +1),取vtbl數組的第二個元素(這第4點,如下圖所示)。

下圖也能很清晰的說明一些東西(@5):


ok,再來看一個問題,如果一個子類重載的虛擬函數為privete,那么通過父類的指針可以訪問到它嗎?

#include <IOSTREAM>   
class B   
{    
public:    
    virtual void fun()      
    {     
        std::cout << "base fun called";     
    };    
};  

class D : public B    
{    
private:   
    virtual void fun()      
    {     
        std::cout << "driver fun called";    
    };    
};  

int main(int argc, char* argv[])   
{       
    B* p = new D();    
    p->fun();    
    return 0;    
}  

運行時會輸出 driver fun called

從這個實驗,可以更深入的了解虛擬函數編譯時的一些特征:
在編譯虛擬函數調用的時候,例如p->fun(); 只是按其靜態類型來處理的, 在這里p的類型就是B,不會考慮其實際指向的類型(動態類型)。

    也就是說,碰到p->fun();編譯器就當作調用B的fun來進行相應的檢查和處理。
因為在B里fun是public的,所以這里在“訪問控制檢查”這一關就完全可以通過了。
然后就會轉換成(*p->vptr[1])(p)這樣的方式處理, p實際指向的動態類型是D,
    所以p作為參數傳給fun后(類的非靜態成員函數都會編譯加一個指針參數,指向調用該函數的對象,我們平常用的this就是該指針的值), 實際運行時p->vptr[1]則獲取到的是D::fun()的地址,也就調用了該函數, 這也就是動態運行的機理。


為了進一步的實驗,可以將B里的fun改為private的,D里的改為public的,則編譯就會出錯。
C++的注意條款中有一條" 絕不重新定義繼承而來的缺省參數值" 
(Effective C++ Item37, never redefine a function's inherited default parameter value) 也是同樣的道理。

可以再做個實驗
class B   
{    
public:   
    virtual void fun(int i = 1)      
    {     
        std::cout << "base fun called, " << i;     
    };    
};  

class D : public B    
{    
private:    
    virtual void fun(int i = 2)      
    {     
        std::cout << "driver fun called, " << i;     
    };    
}; 

 

 

則運行會輸出driver fun called, 1

 

關于這一點,Effective上講的很清楚“virtual 函數系動態綁定, 而缺省參數卻是靜態綁定”,
也就是說在編譯的時候已經按照p的靜態類型處理其默認參數了,轉換成了(*p->vptr[1])(p, 1)這樣的方式。
 

補遺

   一個類如果有虛函數,不管是幾個虛函數,都會為這個類聲明一個虛函數表,這個虛表是一個含有虛函數的類的,不是說是類對象的。一個含有虛函數的類,不管有多少個數據成員,每個對象實例都有一個虛指針,在內存中,存放每個類對象的內存區,在內存區的頭部都是先存放這個指針變量的(準確的說,應該是:視編譯器具體情況而定),從第n(n視實際情況而定)個字節才是這個對象自己的東西。

 

下面再說下通過基類指針,調用虛函數所發生的一切:
One *p;
p->disp();

1、上來要取得類的虛表的指針,就是要得到,虛表的地址。存放類對象的內存區的前四個字節其實就是用來存放虛表的地址的。
2、得到虛表的地址后,從虛表那知道你調用的那個函數的入口地址。根據虛表提供的你要找的函數的地址。并調用函數;你要知道,那個虛表是一個存放指針變量的數組,并不是說,那個虛表中就是存放的虛函數的實體。

本章完。



青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            久久精品国产亚洲aⅴ| 在线视频精品一| 午夜在线观看欧美| 亚洲午夜在线视频| 一本大道久久精品懂色aⅴ| 日韩亚洲欧美成人一区| 在线天堂一区av电影| 一本一本久久a久久精品综合妖精| 亚洲精品久久嫩草网站秘色| 欧美国产综合视频| 亚洲精品黄网在线观看| 亚洲一区二区免费在线| 欧美一区二区三区日韩| 蜜臀va亚洲va欧美va天堂| 欧美剧在线免费观看网站| 国产精品美女一区二区| 狠狠色丁香婷综合久久| 亚洲精品国产欧美| 亚洲免费一在线| 老司机免费视频一区二区三区| 亚洲成人在线视频播放| 99精品99| 久久天天综合| 国产精品久久久久久超碰 | 亚洲私人影院| 久久国产精品久久国产精品| 媚黑女一区二区| 国产精品免费看| 亚洲精品国产精品乱码不99| 久久九九99| 日韩视频精品| 久久噜噜噜精品国产亚洲综合| 欧美日韩一区二区在线视频| 影音先锋亚洲精品| 午夜伦欧美伦电影理论片| 亚洲国产精品高清久久久| 午夜在线电影亚洲一区| 欧美三级午夜理伦三级中文幕| 红桃视频成人| 欧美亚洲一区在线| 欧美绝品在线观看成人午夜影视 | 一区二区免费在线播放| 久久久久久午夜| 国产日产精品一区二区三区四区的观看方式| 91久久综合| 欧美xxx成人| 久久国产手机看片| 国产精品久久久久久久久搜平片 | 蜜桃精品一区二区三区| 亚洲网站在线观看| 欧美涩涩视频| 中文欧美在线视频| 女女同性女同一区二区三区91| 亚洲青涩在线| 亚洲一区二区在线观看视频| 老司机午夜精品视频在线观看| 欧美日韩国产不卡在线看| 国产午夜久久| 亚洲伦理网站| 亚洲人成亚洲人成在线观看| 免费欧美电影| 亚洲精品三级| 欧美三级特黄| 国产一区二区毛片| 一区二区国产日产| 亚洲免费av电影| 欧美日韩国产在线一区| 中文亚洲字幕| 亚洲一区二区三区三| 国产精品区一区二区三区| 午夜亚洲性色福利视频| 亚洲已满18点击进入久久| 国产精品久久久| 久久gogo国模裸体人体| 欧美在线观看一区二区| 国内精品久久久久久久97牛牛| 久久全球大尺度高清视频| 久久成人18免费网站| 亚洲国产高清一区二区三区| 亚洲人成网站精品片在线观看| 欧美日韩视频在线一区二区| 亚洲欧美日韩国产综合在线 | 99亚洲一区二区| 99国内精品久久| 国产亚洲亚洲| 亚洲国产精品国自产拍av秋霞| 欧美日韩的一区二区| 午夜国产精品视频免费体验区| 亚洲女人小视频在线观看| 亚洲五月婷婷| 国产一区二区精品久久99| 欧美多人爱爱视频网站| 一区二区三区四区国产| 香蕉乱码成人久久天堂爱免费| 激情久久久久久久| 亚洲精品人人| 国产一区二区三区久久久| 亚洲电影一级黄| 国产精品青草久久久久福利99| 久久综合综合久久综合| 欧美日韩系列| 欧美高清视频在线播放| 国产精品欧美久久| 亚洲高清av| 国产精品午夜电影| 亚洲激情二区| 精品成人在线视频| 亚洲一区二区三区影院| 日韩亚洲欧美一区二区三区| 久久激情综合网| 性做久久久久久免费观看欧美| 欧美电影免费观看大全| 欧美专区在线| 国产精品日产欧美久久久久| 亚洲国产成人在线| 一区在线视频观看| 翔田千里一区二区| 亚洲欧美日韩精品在线| 欧美激情麻豆| 欧美阿v一级看视频| 国产美女一区| 亚洲欧美日韩精品在线| 亚洲午夜精品| 欧美午夜在线一二页| 亚洲三级影片| 99精品视频网| 欧美精品国产精品| 亚洲福利视频在线| 亚洲国产另类精品专区| 久久久精品国产一区二区三区 | 亚洲麻豆国产自偷在线| 久久综合九色综合欧美就去吻| 久久久国产亚洲精品| 国产欧美高清| 欧美亚洲一区二区在线观看| 午夜天堂精品久久久久| 国产精品久久久久免费a∨| 一区二区三区高清视频在线观看| 亚洲激情在线观看| 巨胸喷奶水www久久久免费动漫| 久久综合伊人77777蜜臀| 狠狠色丁香婷婷综合久久片| 欧美一区二区三区在线观看| 香蕉久久一区二区不卡无毒影院| 国产精品乱人伦中文| 亚洲一二三四久久| 久久亚洲综合| 欧美成黄导航| 亚洲福利精品| 欧美国产高清| 夜夜嗨av一区二区三区中文字幕| 欧美日韩精品免费观看视一区二区 | 91久久精品国产91久久性色tv| 乱人伦精品视频在线观看| 欧美成人亚洲成人| 亚洲精品一区二区三区蜜桃久| 欧美精品久久99| 亚洲视频二区| 老司机免费视频一区二区三区| 亚洲欧洲中文日韩久久av乱码| 欧美黄色一区| 亚洲午夜视频| 麻豆91精品| 中文在线不卡| 伊伊综合在线| 欧美午夜电影一区| 久久噜噜亚洲综合| 一区二区三区欧美成人| 久久久久久久波多野高潮日日| 亚洲国产欧洲综合997久久| 欧美色图天堂网| 久久久久久91香蕉国产| 亚洲精品裸体| 久久婷婷国产麻豆91天堂| 亚洲精品日产精品乱码不卡| 国产精品自拍在线| 免费视频一区| 西瓜成人精品人成网站| 亚洲国产精品成人综合色在线婷婷 | 欧美gay视频| 亚洲欧美久久久久一区二区三区| 久久综合中文色婷婷| 亚洲女同精品视频| 亚洲人成在线播放网站岛国| 国产精品日韩欧美一区二区三区| 久久久久99| 亚洲欧美国产制服动漫| 亚洲高清不卡一区| 久久嫩草精品久久久精品一| 亚洲午夜视频在线观看| 亚洲第一成人在线| 国产欧美一区二区三区国产幕精品| 欧美成人国产| 久久久久久久999精品视频| 中国亚洲黄色| 亚洲美女精品成人在线视频| 欧美成人在线影院| 久久精品一区二区三区不卡牛牛 | 亚洲电影毛片| 久久亚洲美女|