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

posts - 195,  comments - 30,  trackbacks - 0

http://hi.baidu.com/daping_zhang/blog/item/e87163d06c42818fa0ec9cfc.html
多態(Polymorphism)是面向對象的核心概念,本文以C++為例,討論多態的具體實現。C++中多態可以分為基于繼承和虛函數的動態多態以及基于模板的靜態多態,如果沒有特別指明,本文中出現的多態都是指前者,也就是基于繼承和虛函數的動態多態。至于什么是多態,在面向對象中如何使用多態,使用多態的好處等等問題,如果大家感興趣的話,可以找本面向對象的書來看看。
    為了方便說明,下面舉一個簡單的使用多態的例子(From [1] ):

class Shape
{
protected:
  int m_x;    // X coordinate
  int m_y;  // Y coordinate
public:
  // Pure virtual function for drawing
  virtual void Draw() = 0;  

  // A regular virtual function
  virtual void MoveTo(int newX, int newY);

 // Regular method, not overridable.
  void Erase();

  // Constructor for Shape
  Shape(int x, int y); 

 // Virtual destructor for Shape
  virtual ~Shape();
};
// Circle class declaration
class Circle : public Shape
{
private:
   int m_radius;    // Radius of the circle 
public:
   // Override to draw a circle
   virtual void Draw();    

   // Constructor for Circle
   Circle(int x, int y, int radius);

  // Destructor for Circle
   virtual ~Circle();
};
// Shape constructor implementation
Shape::Shape(int x, int y)
{
   m_x = x;
   m_y = y;
}
// Shape destructor implementation
Shape::~Shape()
{
//...
}
 // Circle constructor implementation
Circle::Circle(int x, int y, int radius) : Shape (x, y)
{
   m_radius = radius;
}

// Circle destructor implementation
Circle::~Circle()
{
//...
}

// Circle override of the pure virtual Draw method.
void Circle::Draw()
{
   glib_draw_circle(m_x, m_y, m_radius);
}

main()
{
  // Define a circle with a center at (50,100) and a radius of 25
  Shape *pShape = new Circle(50, 100, 25);

  // Define a circle with a center at (5,5) and a radius of 2
  Circle aCircle(5,5, 2);

  // Various operations on a Circle via a Shape pointer
  //Polymorphism
  pShape->Draw();
  pShape->MoveTo(100, 100);

  pShape->Erase();
  delete pShape;

 // Invoking the Draw method directly
  aCircle.Draw();
}   

     例子中使用到多態的代碼以黑體標出了,它們一個很明顯的特征就是通過一個基類的指針(或者引用)來調用不同子類的方法。
     那么,現在的問題是,這個功能是怎樣實現的呢?我們可以先來大概猜測一下:對于一般的方法調用,到了匯編代碼這一層次的時候,一般都是使用 Call funcaddr 這樣的指令進行調用,其中funcaddr是要調用函數的地址。按理來說,當我使用指針pShape來調用Draw的時候,編譯器應該將Shape::Draw的地址賦給funcaddr,然后Call 指令就可以直接調用Shape::Draw了,這就跟用pShape來調用Shape::Erase一樣。但是,運行結果卻告訴我們,編譯器賦給funcaddr的值卻是Circle::Drawde的值。這就說明,編譯器在對待Draw方法和Erase方法時使用了雙重標準。那么究竟是誰有這么大的法力,使編譯器這個鐵面無私的判官都要另眼相看呢?virtual!!
     
Clever!!正是virtual這個關鍵字一手導演了這一出“乾坤大挪移”的好戲。說道這里,我們先要明確兩個概念:靜態綁定和動態綁定。
    1、靜態綁定(static bingding),也叫早期綁定,簡單來說就是編譯器在編譯期間就明確知道所要調用的方法,并將該方法的地址賦給了Call指令的funcaddr。因此,運行期間直接使用Call指令就可調用到相應的方法。
    2、動態綁定(dynamic binding),也叫晚期綁定,與靜態綁定不同,在編譯期間,編譯器并不能明確知道究竟要調用的是哪一個方法,而這,要知道運行期間使用的具體是哪個對象才能決定。
    好了,有了這兩個概念以后,我們就可以說,virtual的作用就是告訴編譯器:我要進行動態綁定!編譯器當然會尊重你的意見,而且為了完成你這個要求,編譯器還要做很多的事情:編譯器自動在聲明了virtual方法的類中插入一個指針vptr和一個數據結構VTable(vptr用以指向VTable;VTable是一個指針數組,里面存放著函數的地址),并保證二者遵守下面的規則:
    1、VTable中只能存放聲明為virtual的方法,其它方法不能存放在里面。在上面的例子中,Shape的VTable中就只有Draw,MoveTo和~Shape。方法Erase的地址并不能存放在VTable中。此外,如果方法是純虛函數,如 Draw,那么同樣要在VTable中保留相應的位置,但是由于純虛函數沒有函數體,因此該位置中并不存放Draw的地址,而是可以選擇存放一個出錯處理的函數的地址,當該位置被意外調用時,可以用出錯函數進行相應的處理。
    2、派生類的VTalbe中記錄的從基類中繼承下來的虛函數地址的索引號必須跟該虛函數在基類VTable中的索引號保持一致。如在上例中,如果在Shape的VTalbe中,Draw為 1 號, MoveTo 2 號,~Shape為 3 號,那么,不管這些方法在Circle中是按照什么順序定義的,Circle的VTable中都必須保證Draw為 1 號,MoveTo為 2號。至于 3號,這里是~Circle。為什么不是~Shape啊?嘿嘿,忘啦,析構函數不會繼承的。
    3、vptr是由編譯器自動插入生成的,因此編譯器必須負責為其進行初始化。初始化的時間選在對象創建時,而地點就在構造函數中。因此,編譯器必須保證每個類至少有一個構造函數,若沒有,自動為其生成一個默認構造函數。
     4、vptr通常放在對象的起始處,也就是Addr(obj) == Addr(obj.vptr)。
    你看,天下果然沒有免費的午餐,為了實現動態綁定,編譯器要為我們默默干了這么多的臟話累活。如果你想體驗一下編譯器的辛勞,那么可以嘗試用C語言模擬一下上面的行為,【1】中就有這么一個例子。好了,現在萬事具備,只欠東風了。編譯,連接,載入,GO!當程序執行到 pShape->Draw()的時候,上面的設施也開始起作用了。。
    前面已經提到,晚期綁定時之所以不能確定調用哪個函數,是因為具體的對象不確定。好了,當運行到pShape->Draw()時,對象出來了,它由pShape指針標出。我們找到這個對象后,就可以找到它里面的vptr(在對象的起始處),有了vptr后,我們就找到了VTable,調用的函數就在眼前了。。等等,VTable中方法那么多,我究竟使用哪個呢?不用著急,編譯器早已為我們做好了記錄:編譯器在創建VTable時,已經為每個virtual函數安排好了座次,并且把這個索引號記錄了下來。因此,當編譯器解析到pShape->Draw()的時候,它已經悄悄的將函數的名字用索引號來代替了。這時候,我們通過這個索引號就可以在VTable中得到一個函數地址,Call it!
    在這里,我們就體會到為什么會有第二條規定了,通常,我們都是用基類的指針來引用派生類的對象,但是不管具體對象是哪個派生類的,我們都可以使用相同的索引號來取得對應的函數實現。
     現實中有一個例子其實跟這個蠻像的:報警電話有110,119,120(VTable中不同的方法)。不同地方的人撥打不同的號碼所產生的結果都是不一樣的。譬如,在三環外的一個人(具體對象)跟一環內的一個人(另外一個具體對象)打119,最后調用的消防隊肯定是不一樣的,這就是多態了。這是怎么實現的呢,每個人都知道一個報警中心(VTable,里面有三個方法 110,119,120)。如果三環外的一個人需要火警搶險(一個具體對象)時,它就撥打119,但是他肯定不知道最后是哪一個消防隊會出現的。這得有報警中心來決定,報警中心通過這個具體對象(例子中就是具體位置了)以及他說撥打的電話號碼(可以理解成索引號),報警中心可以確定應該調度哪一個消防隊進行搶險(不同的動作)。
     這樣,通過vptr和VTable的幫助,我們就實現了C++的動態綁定。當然,這僅僅是單繼承時的情況,多重繼承的處理要相對復雜一點,下面簡要說一下最簡單的多重繼承的情況,至于虛繼承的情況,有興趣的朋友可以看看 Lippman的《Inside the C++ Object Model》,這里暫時就不展開了。(主要是自己還沒搞清楚,況且現在多重繼承都不怎么使用了,虛繼承應用的機會就更少了)
     首先,我要先說一下多重繼承下對象的內存布局,也就是說該對象是如何存放本身的數據的。

class Cute
{
public:
 int i;
 virtual void cute(){ cout<<"Cute cute"<<endl; }
};
class Pet
{
public:
   int j;
   virtual void say(){ cout<<"Pet say"<<endl;  }
};
class Dog : public Cute,public Pet
{
public:
 int z;
 void cute(){ cout<<"Dog cute"<<endl; }
 void say(){ cout<<"Dog say"<<endl;  }
};

    在上面這個例子中,一個Dog對象在內存中的布局如下所示:                    

Dog

Vptr1

Cute::i

Vptr2

Pet::j

Dog::z


     也就是說,在Dog對象中,會存在兩個vptr,每一個跟所繼承的父類相對應。如果我們要想實現多態,就必須在對象中準確地找到相應的vptr,以調用不同的方法。但是,如果根據單繼承時的邏輯,也就是vptr放在指針指向位置的起始處,那么,要在多重繼承情況下實現,我們必須保證在將一個派生類的指針隱式或者顯式地轉換成一個父類的指針時,得到的結果指向相應派生類數據在Dog對象中的起始位置。幸好,這工作編譯器已經幫我們完成了。上面的例子中,如果Dog向上轉換成Pet的話,編譯器會自動計算Pet數據在Dog對象中的偏移量,該偏移量加上Dog對象的起始位置,就是Pet數據的實際地址了。

int main()
{
 Dog* d = new Dog();
 cout<<"Dog object addr : "<<d<<endl;
 Cute* c = d;
 cout<<"Cute type addr : "<<c<<endl;
 Pet* p = d;
 cout<<"Pet type addr : "<<p<<endl;
 delete d;
}
output:
Dog object addr : 0x3d24b0
Cute type addr : 0x3d24b0
Pet type addr : 0x3d24b8   // 正好指向Dog對象的vptr2處,也就是Pet的數據

      好了,既然編譯器幫我們自動完成了不同父類的地址轉換,我們調用虛函數的過程也就跟單繼承統一起來了:通過具體對象,找到vptr(通常指針的起始位置,因此Cute找到的是vptr1,而Pet找到的是vptr2),通過vptr,我們找到VTable,然后根據編譯時得到的VTable索引號,我們取得相應的函數地址,接著就可以馬上調用了。

      在這里,順便也提一下兩個特殊的方法在多態中的特別之處吧:第一個是構造函數,在構造函數中調用虛函數是不會有多態行為的,例子如下:

class Pet
{
public:
   Pet(){ sayHello(); }
   void say(){ sayHello(); }

   virtual void sayHello()
   {
     cout<<"Pet sayHello"<<endl;
   }
   
};
class Dog : public Pet
{
public:
   Dog(){};
   void sayHello()
   {
     cout<<"Dog sayHello"<<endl;
   }
};
int main()
{
 Pet* p = new Dog();
 p->sayHello();
 delete p;
}
output:
Pet sayHello //直接調用的是Pet的sayHello()
Dog sayHello //多態

     第二個就是析構函數,使用多態的時候,我們經常使用基類的指針來引用派生類的對象,如果是動態創建的,對象使用完后,我們使用delete來釋放對象。但是,如果我們不注意的話,會有意想不到的情況發生。

class Pet
{
public:
   ~Pet(){ cout<<"Pet destructor"<<endl;  }
  //virtual ~Pet(){ cout<<"Pet virtual destructor"<<endl;  }
};
class Dog : public Pet
{
public:
   ~Dog(){ cout<<"Dog destructor"<<endl;};
   //virtual ~Dog(){ cout<<"Dog virtual destructor"<<endl;  }
};
int main()
{
 Pet* p = new Dog();
 delete p;
}
output:
Pet destructor  //糟了,Dog的析構函數沒有調用,memory leak!

如果我們將析構函數改成virtual以后,結果如下
Dog virtual destructor
Pet virtual destructor   // That's OK!

    所以,如果一個類設計用來被繼承的話,那么它的析構函數應該被聲明為virtual的。

Reference:
[1] Comparing C++ and C (Inheritance and Virtual Functions)  
[2] C++對象布局及多態實現的探索 
[3] Multiple inheritance and the this pointer 講述多重繼承下的類型轉換問題
[4] Memory Layout for Multiple and Virtual Inheritance 詳細描述了多重菱形多重繼承下的對象內存布局以及類型轉換 

后記:當我完成了本篇%90的時候,我試圖提交,誰知道登陸太久沒有動作,session超時,讓我重新登陸,然后提交的內容就全部不見了,剩下最開始的%10。。。。當時的心情啊,用呂大哥的話來說就是:尋死的心都有了。

posted on 2011-04-08 22:09 luis 閱讀(393) 評論(0)  編輯 收藏 引用

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


<2011年3月>
272812345
6789101112
13141516171819
20212223242526
272829303112
3456789

常用鏈接

留言簿(3)

隨筆分類

隨筆檔案

文章分類

文章檔案

友情鏈接

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            久久中文精品| 99re8这里有精品热视频免费| 国产精品乱码久久久久久| 一区二区三区免费在线观看| 亚洲黄色精品| 久久男女视频| 久久久久.com| 欧美性jizz18性欧美| 亚洲成在人线av| 午夜精品视频在线| 另类人畜视频在线| 毛片av中文字幕一区二区| 欧美日本高清一区| 91久久黄色| 99国产精品一区| 欧美国产在线电影| 亚洲黄色高清| 日韩视频专区| 欧美日韩国产限制| 亚洲免费在线视频| 久久尤物电影视频在线观看| 欧美午夜在线| 久久国产欧美精品| 欧美激情国产日韩| 妖精视频成人观看www| 欧美日韩亚洲视频| 亚洲蜜桃精久久久久久久| 亚洲免费视频一区二区| 国产一区深夜福利| 欧美精品一区三区| 亚洲乱码国产乱码精品精| 国产精品第2页| 一区在线观看| 欧美日韩亚洲综合一区| 亚洲欧美国产毛片在线| 女人天堂亚洲aⅴ在线观看| 亚洲作爱视频| 狠狠色综合播放一区二区| 欧美日韩影院| 噜噜噜久久亚洲精品国产品小说| 亚洲欧美日韩网| 欧美激情国产精品| 久久精品亚洲一区二区三区浴池| 亚洲精品资源| 亚洲国产精品123| 国产日韩在线看片| 国产精品视频一| 国产精品久久久久久久浪潮网站| 欧美福利专区| 欧美成人精品在线观看| 老司机午夜精品视频在线观看| 亚洲男女自偷自拍| 西西裸体人体做爰大胆久久久| 亚洲欧美电影院| 欧美中日韩免费视频| 欧美在线一二三| 猛干欧美女孩| 欧美视频1区| 国产一区二区三区奇米久涩| 国产一区二区三区日韩| 在线欧美日韩国产| 日韩一级在线| 国产精品免费区二区三区观看| 欧美久久久久久| 欧美午夜激情视频| 国模套图日韩精品一区二区| 国产综合香蕉五月婷在线| 亚洲第一黄色网| 亚洲婷婷综合久久一本伊一区| 欧美在线一二三区| 亚洲国产综合在线| 亚洲欧美综合一区| 欧美成人免费全部| 国产欧美日韩一区二区三区在线| 极品日韩av| 欧美综合国产| 一区二区三区日韩欧美| 久久综合亚洲社区| 国内精品久久久久影院优| 在线性视频日韩欧美| 亚洲国产mv| 美女福利精品视频| 在线观看欧美日本| 亚洲午夜一区二区| 最新中文字幕亚洲| 久久久最新网址| 精品av久久707| 久久久999| 久久精品国产亚洲aⅴ| 国产精品视频内| 欧美在线免费| 香蕉乱码成人久久天堂爱免费| 国产精品国码视频| 亚洲在线免费视频| 一本大道av伊人久久综合| 欧美精品久久一区二区| 亚洲日本欧美在线| 欧美福利影院| 夜夜爽av福利精品导航| 欧美韩国日本一区| 欧美国产欧美亚洲国产日韩mv天天看完整 | 欧美一区二区观看视频| 亚洲视频一区在线| 国产欧美1区2区3区| 麻豆精品精华液| 欧美激情aaaa| 久久精品亚洲国产奇米99| 久久久久成人精品| 在线视频精品一区| 欧美中文字幕在线播放| 亚洲激情网站免费观看| 午夜日韩av| 在线午夜精品| 欧美成人福利视频| 久久婷婷综合激情| 欧美日韩理论| 欧美jizz19性欧美| 国产一区二区欧美日韩| 亚洲精品小视频| ●精品国产综合乱码久久久久| 亚洲伦伦在线| 亚洲伦理在线| 免费一级欧美在线大片| 国产精品豆花视频| 亚洲黄网站在线观看| 亚洲国产精品视频| 美女国产一区| 亚洲国产精品一区在线观看不卡 | 亚洲午夜精品17c| 欧美激情综合亚洲一二区| 欧美第十八页| 亚洲激情自拍| 欧美久久久久久久久久| 欧美国产精品va在线观看| 国内一区二区在线视频观看| 亚洲欧美日本国产有色| 欧美一区二区三区在线播放| 国产精品私拍pans大尺度在线 | 久久偷看各类wc女厕嘘嘘偷窃| 国产精品午夜春色av| 亚洲一区二区黄| 欧美一区三区三区高中清蜜桃 | 欧美日韩国产va另类| 亚洲精品影院在线观看| 亚洲欧美日本国产专区一区| 国产精品视频区| 免费看成人av| 亚洲免费一区二区| 亚洲欧洲美洲综合色网| 欧美一级黄色网| 日韩视频中午一区| 一区免费观看| 亚洲精品人人| 香蕉av福利精品导航| 亚洲精品偷拍| 精品1区2区3区4区| 国产精品一区二区黑丝| 免费在线观看日韩欧美| 久久精品亚洲一区二区三区浴池 | 亚洲人午夜精品| 国产亚洲人成网站在线观看| 欧美日韩国产一中文字不卡| 免费成人av在线看| 亚洲欧美三级伦理| 国内精品视频在线观看| 国产亚洲欧美一区在线观看| 国产精品区一区二区三区| 欧美婷婷在线| 欧美丝袜一区二区三区| 欧美性色综合| 欧美视频你懂的| 欧美三级黄美女| 国产亚洲观看| 怡红院精品视频在线观看极品| 在线不卡中文字幕播放| 亚洲经典视频在线观看| 99pao成人国产永久免费视频| 亚洲图片欧美一区| 久久www免费人成看片高清| 老司机精品导航| 一本色道久久88亚洲综合88| 亚洲午夜视频| 免费一区视频| 国产视频精品免费播放| 亚洲国产视频一区二区| 在线亚洲观看| 久久婷婷蜜乳一本欲蜜臀| 亚洲欧洲一区二区三区| 欧美亚洲一区二区三区| 欧美精品国产一区| 韩国成人理伦片免费播放| 亚洲美女黄色片| 久久亚洲一区二区| 亚洲一区二区视频在线| 美女视频黄a大片欧美| 中文欧美在线视频| 噜噜噜在线观看免费视频日韩| 国产欧美精品日韩精品| 99综合视频| 99视频日韩|