關於《深度探索C++對象模型》停頓了半個月,今天繼續啃這個骨頭,我的學習進入了第四章,函數的語意學。先做個復習C++支持三種成員函數:靜態、虛、和非靜態。每一種函數的調用方式都不同,當然他們的作用也會有區別,一般來說我們只要掌握根據我們的需要正確的使用這三種類型的成員函數便可以了,至於內部是如何運做的我們可以不知。但是《深度探索C++對象模型》正是讓我們對這些不知道的東西進行深度探索的一本書。通過前面的學習,我想我知道了一些以前不知道的東西,但是感覺並沒有提高多少,也許是我對此書的學習還停留在一個比較膚淺的層次上吧。我想我應該會抽時間再看幾遍。有些跑題了,因為雷神想說明一下,這些筆記只是雷神看書是的一些想法的記錄,如果你再看僅供參考,因為我本人好象也只探索了不是很深的程度。 我們的在設計和使用類時最常用的便是非靜態成員函數,使用成員函數是為了封裝和隱藏我們的數據,我想這是成員函數和外部函數的最明顯的區別。但是他們的效率是否有不同呢?我們不會想為了保護我們的數據而使用成員函數,最後確導致效率降低的結果。讓我們看看非靜態成員函數在實際的執行時被編譯器搞成了什麼樣子。
float magnitude3d(const Point3d *_this){…}
//這是一個外部函數,它有參數。表示它間接的取得坐標(Point3d)成員。
float Point3d::mangnitude3d() const {…}
//這是一個成員函數,它直接取得坐標(Point3d)的成員。
表面上看,似乎成員函數的效率高很多,但實際上他們的效率真的想我們想象的那樣嗎?非也。實際上一個成員函數被內部轉化成了外部函數。
1、 一個this指針被加入到成員函數的參數中,為的是能夠使類的對象調用這個函數。
2、 將對所有非靜態數據成員的存取操作改為由this來存取。
3、 對函數的名稱進行重新的處理,使它成為程序中獨一無二的。
這時後,經過以上的轉換,成員函數已經成為了非成員函數。
float Point3d::mangnitude3d() const {…}//成員函數將被變成下面的樣子
//偽碼
mangnitude3d__7Point3dFv(register Point3d * const this)
{
return sqrt(this->_x * this->x+
this->_y * this->y+
this->_z * this->z);
}
調用此函數的操作也被轉換
obj. mangnitude3d()
被轉換成:
mangnitude3d__7Point3dFv(*obj);
怎麼樣看出來了吧,和我們開始聲明的非成員函數沒有區別了。因此得出結論:兩個鐵球同時落地。
一般來說,一個成員的名稱前面會被加上類的名稱,形成唯一的命名。實際上在對成員名稱做處理時,除了加上了類名,還會將參數的鏈表一並加上,這樣才能保證結果是獨一無二的。
我們在來看看靜態成員函數。我們有這樣的概念,成員函數的調用必須是用類的對象,象這樣obj.fun();或者這樣ptr->fun().但實際上,只有一個或多個靜態數據成員被成員函數存取時才需要類的對象。類的對象提供一個指針this,用來將用到的非靜態數據成員綁定到類對象對應的成員上。如果沒有用到任何一個成員數據,就不需要用到this指針,也就沒有必要通過類的對象來調用一個成員函數。而且我們還知道靜態數據成員是在類之外的,可以被視做全局變量的,只不過它只在一個類的生命范圍內可見。(參考前面的筆記)。而且一般來說我們會將靜態的數據成員聲明為一個非Public。這樣我們便必須提供一個或多個成員函數用來存取這個成員。雖然我們可以不依靠類的對象存取靜態數據成員,但是這個可以用來存取靜態成員的函數確實必須綁定在類的對象上的。為了更加好的解決這個問題,cfront2.0引入了靜態成員函數的概念。
靜態成員函數是沒有this指針的。因為它不需要通過類的對象來調用。而且它不能直接存取類中的非靜態成員。並且不能夠被聲明為virtual,const,volatile.如果取得一個靜態成員函數的地址,那麼我們獲得的是這個函數在內存中的位置。(非靜態成員函數的地址我們獲得的是一個指向這個類成員函數的指針,函數指針)。可以看到由於靜態成員函數沒有this指針,和非成員函數非常的相似。
有了前面幾章的基礎,好象這些描述理解起來也不很費勁,而且我們的思路可以跟著書上所說的一路傾瀉下來,這便是讀書的樂趣所在了,如果一本書讀起來都想讀第一章時那樣費勁,我想我讀不下去的可能性會很高。
繼續我們的學習,下面書上開始將虛函數了。我們知道虛函數是C++的一個很重要的特性,面向對象的多態便是由虛函數實現的。多態的概念是一個用一個public base class的指針(或者引用),尋址出一個派生類對象。虛函數實現的模型是這樣。每一個類都有一個虛函數表,它包含類中有作用的虛函數的地址,當類產生對象時會有一個指針,指向虛函數表。為了支持虛函數的機制,便有了“執行期多態”的形式。
下面這樣。
我們可以定義一個基類的指針。
Point *ptr;
然後在執行期使他尋址出我們需要的對象。可以是
ptr =new Point2d;
還可以是
ptr=new Pont3d;
ptr這個指針負責使程序在任何地方都可以采用一組由基類派生的類型。這種多態形式是消極的,因為它必須在編譯時期完成。與之對應的是一種多態的積極形式,即在執行期完成用指針或引用查找我們的一個派生類的對象。
象下面這樣:
ptr->z();
要想達到我們目的,這個函數z()應該是虛函數,並且還應該知道ptr所指的對象的真實類型,以便我們選擇z()的實體。以及z()實體的位置,以便我們能夠調用它。這些工作編譯器都會為我們做好,編譯器是如何做的呢?
我們已知每一個類會有一個虛函數表,這個表中含有對應類的對象的所有虛函數實體的地址,並且可能會改寫一個基類的虛函數實體。如果沒有改寫基類存在的虛函數實體,則會繼承基類的函數實體,這還沒完,還會有一個pure_virtual_called()的函數實體。每一個虛函數不論是繼承的還是改寫的,都會被指派一個固定的索引值,這個索引在整個繼承體系中保持與特定的虛函數關聯。
說明:當沒有改寫基類的虛函數時,該函數的實體地址是被拷貝到派生類的虛函數表中的。
這樣我們便實現了執行期的積極多態。這種形式的特點是,我們從頭到尾都不知道ptr指針指向了那一個對象類型,基類?派生類1?派生類2?我們不知道,也不需要知道。我們只需要知道ptr指向的虛函數表。而且我們也不知道z()函數的實體會被調用,我們只知道z()函數的函數地址被放在虛函數表中的位置。
總結:在單一繼承的體系中,虛函數機制是一種很有效率的機制。我們判斷一個類是否支持多態,只需要看它有沒有虛函數便可以了。 好了今天就到這裡,雷神必須加快學習這本書的速度了,好象現在也可以快一些了。