class Point3d{ public: //...... float x; static list*freelist; float y; static const int chunksize = 250; float z; };
Point3d origin,*pt; origin.x = 0; pt->x = 0;通過origin存取x與通過pt存取x有什麼差異?
//我們知道chunksize 為Point3d中的一個靜態數據成員 origin.x == 250;//訪問chunksize 並判斷 pt->x == 250; // 訪問chunksize 並判斷
class A{ static int x; }; class B{ static int x; };
origin.chunksize == 250 ; //===>>被編譯器轉化為 Point3d::chunksize == 250; pt->chunksize == 250; //===>>被編譯器轉化為 Point3d::chunksize == 250
class Point3d{ public: //...... float x; float y; float z; };
Point3d origin; //那麼地址&origin.x等於多少?
cout<<"&origin: "<<&origin<
程序運行結果:
運行結果是不是已經很清楚 了?訪問對象中的數據成員即是在對象起始地址的基礎上增加一個偏移量: &Z喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcmlnaW4mIzQzOygmYW1wO1BvaW50M2Q6OngtMSkKtvjV4rj2xqvSxsG/1Nqx4NLryrHG2ry0v8m78daqoaMKudjT2sDg1tC1xLPJ1LG6r8r9ttTT2sr9vt2zydSxtcS3w87KyOfPwqO6CjxwcmUgY2xhc3M9"brush:java;">//我們假設Point3d中有一個成員函數如下 Point3d Point3d::translate(const Point3d &pt){ x += pt.x; y += pt.y; z += pt.z; }
Point3d Point3d::translate(Point3d *const this,const Point3d &pt){ this->x += pt.x; this->y += pt.y; this->z += pt.z; }
Point3d *pt3d;pt3d->x = 0;//效率如何呢? 答曰,其執行效率在x為struct member,class member,單一繼承,多繼承的情況下完全相同。但如果x是一個virtual base class member,存取速度稍慢一些。 老問題:Point3d origin,*pt; origin.x = 0; pt->x = 0; 通過origin存取x與通過pt存取x有無重大差異?
答案是當Point3d繼承自一個virtual base class,而x又是這個virtual base class的一個member時會有差異。這個時候我們不確定pt指向的class類型(即它到底指向的是派生類還是基類對象?)也就不知道編譯時候這個member真正的偏移值。 所以這個存取必須延遲至執行期,經由額外間接引導才能訪問。 然而,origin不會有這些問題。因為其class類型是確定的,無疑為Point3d,而virtual base class中的member的偏移值在編譯的時候已經固定。所以origin.x可以毫無壓力的做到。 好了,關於data member就言盡於此吧。如果大家還想知道更深層次的內容,可以查閱相關資料。 下面我們來看看成員函數的問題。
3 成員函數
上文不是說明member functions 有三種:nonstatic,static和virtual,我們就按這個順序一一討論吧。
3.1 nonstatic 成員函數
C++的設計准則之一就是成員函數必須與普通非成員函數有相同的執行效率,同時外界又不能訪問類中的nonstatic member functions 那麼它是怎麼做到的呢? 道理很簡單,編譯器暗地裡已經向member函數實例轉換為對等的nonmember 函數實例。 舉個例子://假設Point3d中有如下一個成員函數 Point3d Point3d::magnitude(){ //具體實現不是我們所關心的 } //被編譯器轉化為(此處先不涉及name-mangling)===>> Point3d Point3d::magnitude(Point3d *const this){ //具體實現不是我們所關心的 } //如果member function為const,則被轉化為(此處先不涉及name-mangling)==>> Point3d Point3d::magnitude(const Point3d *const this){ //具體實現不是我們所關心的 }
是的,你沒有看錯,編譯器會在member function的參數列表中加入一個指向該對象本身的this指針。至於在參數列表的頭部還是尾部加入則可不比深究。所以,外界無法訪問到member functions,因為參數列表不匹配。 然後再有mangling生成一個新的函數名,成為一個獨一無二的外部函數。所以即使參數列表匹配也無法進行訪問,因為函數名字也改變了。 老問題:Point3d obj,*pt; pt = &obj; obj.magnitude(); pt->magnitude();
大家覺得上述兩種函數的調用有無重大差異? 下面,我們來看看經過編譯器的mangling算法轉化後的樣子。obj.magnitude(); //==>> magnitude_7Point3dFv(&obj); pt->magnitude(); //==>> magnitude_7Point3dFv(pt);
顯然,幾乎沒有什麼區別。 大家現在是不是對nonstatic member function有一定的了解了呢?那麼,我們接著看static member functions吧。
3.2 靜態成員函數
static member functions與nonstatic member functions的重大差異在於static member functions沒有this指針。那麼,必然導致以下結果: 1 它不能直接存取class中的nonstatic data members; 2 其不能被聲明為const,volatile或virtual。 3 其不需要經由對象來調用,雖然我們一般都是用對象在調用之。 一個static member function 幾乎就是經過mangling的nonstatic member function。 我們來看看mangling對static member function的轉化://假設count()為Point3d中的一個static member function unsigned int Point3d::count(){ //..... } //===>> unsigned int count_5Point3dSFV(){ }
函數名中的大寫字母S就代表著static。 我們還有一個證據,看下面的例子:&point3d::count()
大家猜猜得到的值得類型是什麼樣子的?unsigned int (Point3d::*)()還是unsigned int (*)() ? 答案顯然是後者,static member function俨然已是半個nonmember function了。 那麼我們再來看看obj.count(); pt->count();
兩者有無重大差異? 顯然沒有了this指針以後,count()會被轉化為一般的nonmember 函數:count_7Point3dSFV();
兩者的調用幾乎一樣。
3.3 虛成員函數
我們大家都知道的是對象中會有一個虛表指針,對應的虛表中有各個虛函數的slot。 這個地方水有點深,我不想討論那麼深,原因有二: 1 自己沒把握把這個地方說透。 2 並不是所有人都對那麼深的東西感興趣。 感興趣的朋友可以查閱相關資料。 虛成員函數與nonstatic 成員函數的區別在於其存在於虛表中。 我們直接看下面的例子://假設 Point3d 中的第一個虛函數為normalize(),那麼 Point3d obj,*pt; pt = &obj; pt->normalize(); obj.normalize();
pt->normalize();要想知道具體函數調用normalize()是哪個,就必須得知道pt所指對象的類型。在這個過程中我們需要知道兩個信息: 1 pt所指對象的類型信息。 2 virtual function的偏移量。 一般做法是將這兩樣信息加入虛表中,即可在編譯期間獲知其具體調用。然而,visual studio 2010似乎不是這樣做的。其具體做法還有待考究。 上述說的是單一繼承,多重繼承的時候會麻煩一些。 在vs2010下面,一個derived class內含n-1個額外的virtual table ,n表示其上一層base class的個數(單一繼承不會有額外的virtual table)。 我們來看一個例子:class Base1{ public: Base1(); virtual ~base1(); virtual Base1 *clone()const; protected: float data_Base1; }; class Base2{ public: Bsae2(); virtual ~Base2(); virtual Base2 *clone()const; protected: float data_Base2; }; class Derived:public Base1,public Base2{ public: Derived(); virtual ~Derived(); virtual Derived *clone()const; protected: float data_Derived; };
內存布局圖如下所示:
我們來看下面一組操作:
Base2 *phase2 = new Derived;
編譯器會將上述代碼翻譯如下:Derived *tmp = new Derived; Base2 *phase2 = tmp? tmp+sizeof(Base1):0;
新的Derived對象的地址必須調整以指向其Base2子對象。大家現在是否明白了基類指針釋放子類對象的時候如果不將析構函數聲明為虛函數就不能釋放完全的原因了吧! 然而,對於sun編譯器來說,上述形式並不適用,其為了調節執行期間連接器的效率,將多個virtual table連鎖為一個。感興趣的朋友自行查閱相關資料。 我們這裡沒有討論虛擬繼承下的virtual function。 接著上面的話題:
pt->normalize(); obj.normalize();
兩者區別在哪? 首先,pt->normalize();被內部轉化為:(*pt->vptr[0])(pt);這點毋庸置疑。 vptr為指向虛表的指針,0為內部偏移量,pt為zhis指針。 obj.normalize();被內部轉化為:(*obj.vptr[0])(&obj);真是這樣嗎?顯然不是。因為沒必要。 上述由obj調用的函數實例,只可以是Point3d::normalize();經過一個對象調用virtual function總是被編譯器視為像對待一般nonstatic member function一樣。 所以obj.normalize()被內部轉化為normalize_7Point3dFV(&obj); 至此,已大體說完。你現在看到class是否有種赤裸裸的感覺呢?