程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 深度探索C++對象模型(6)

深度探索C++對象模型(6)

編輯:C++入門知識

  我們現在還在和構造函數打交道,以前寫程序時怎麼根本沒有考慮過構造函數的事情呢?原來編譯器為我們做了這麼多的事情,我們都不知道.,要想完全搞明白,看來還需要一段時間.我們繼續向下走,進入一個新的章節.每當雷神看完一章後,總是期盼下一章節,因為這意味又一個新的裡程開始了.對於這本書更是感覺強烈,因為全書總共才7章.

  在第三章一開始,雷神就吃了一驚..書上給出了一個例子:

  class X{};

  class Y:public virtual class X{};

  class Z:public virtual class X{};

  class A:public Y,public Z{};

  下面的結果會因為機器,以及編譯有關,不同的情況會產生不同的結果.(怎麼會是這樣?)

  sizeof X; //結果為1

  sizeof Y; //結果為8

  sizeof Z; //結果為8

  sizeof A; //結果為12

  一個沒有任何成員的類,大小居然不是0.

  為什麼?

  首先一個沒有明顯的含有成員的類,它的大小不是0,因為實際上它不是空的,它被編譯器安插了一個char,為的是使這個類的兩個對象能夠在內存中被分配獨一無二的地址.至於兩個派生的類Y和Z,因為語言本身造成的負擔,還有編譯器對於特殊情況進行的優化處理,再有Alignment的限制,因此結果變成了8.這個8是怎麼組成的?

  4個bytes用來存放指針,什麼指針?指向virtual base class subobject的指針呀.

  一個同class X一樣的char.它占了1 個bytes.

  然後受到Alignment的限制,所以填補了3個bytes.

  4+1+3=8

  不過需要注意的是不同的編譯器Y和Z大小的結果也會不同.因為新的編譯器會將一個空的virtual base class看做是派生類對象的開頭部分,因此派生類有了member,因此也就不必分配char的那一個bytes.也就用不到填補的3個bytes,因此有可能在某些編譯器中,class Y和class Z的大小為4.

  最後看看A.根據我們對class Y的分析可以得出以下算式:

  4+4+1+3=12;

  不是我們想象的16,而是12.如果換成我們上面說的新的編譯器來編譯,結果很有可能是8.

  雷神1、4、8……的說了一堆,也不知大家明白與否,但是這第三章,讀起來確實比前兩章順多了。我們繼續我們來看Data Member 的Binding,現在我們對數據成員的綁定只需要記住一個防御性風格:始終把嵌套類型的聲明放在class的開始部分,這樣做可以確保非直覺綁定的正確性。看下面的一個例子:

  

typedef int length; //zai
class point3d
{
public:
//length被決議成global typedef 也就是int
//_val被決議成Point3d::_val
void mumble(length val){_val=val;}
length mumble(){return _val;}
//……
private:
//length必須在這個class對它的第一個參考操作之前被看見
//這樣聲明將使先前的參考操作不合法
typedef float length;
length _val;
//……
};

  怎麼成了抄書了,雷神也不知不覺,可能是在這章的理解上比較容易些吧,不用去想個看的見摸的著的東西比劃。好象小朋友學算術,一位數的計算不用掰手指頭,可是兩位數或者三位數的計算,手指頭加上腳指頭還是不夠。學習就是這麼回事。理解力和抽象能力很重要。回來繼續學習。

  通過這一章我還知道了。數據成員的布局。數據成員的存取。並且對Static data members有了進一步的了解,在class的生命周期中,靜態成員被看作是全局變量,每一個member的存取不會導致任何空間或效率上的額外負擔。不論是從一個復雜的繼承關系中繼承還是直接聲明的,Static data member都只會有一個實體。並且有著非常直接的存取路徑。另外如果兩個類都聲明了一個相同名字的靜態成員變量,那麼編譯器會通過一種算法,為我們解決名字沖突的問題。而非靜態的成員變量的存去實際上是通過implicit class object(this指針)來完成的。例如

  

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 origin;

  origin._y=0.0;

  //地址&origin._y將等於

  &origin+(&Point3d::_y-1);

  目的是使編譯系統能夠區分出以下兩種情況:

  一個指向數據成員的指針,用來指出類的第一個成員。

  一個指向數據成員的指針,沒有指出任何成員。

  這是什麼意思?什麼是指向數據成員的指針。書上的例子:

  

class Point3d
{
public:
virtual ~Point3d();
//……
protected:
static Point3d origin;//靜態的數據成員,位置在class object之外
float x,y,z;//每個float是4bytes
}

  &Point3d::z; //這個值是什麼?

  我們在這篇文章開始的時候已經知道了還有一個vptr,不過vptr的位置也許在對象的開始,也許在對象的結尾部。所以上面的操作的值應該是8或者12(如果vptr在前面的話)。但實際上取會的值被加上了1。原因是必須要區別一個不指向任何成員的指針,和一個指向第一個成員的指針。又有點不好理解了,舉個例子:

  想象你和你的另外兩個朋友合住一個三室一廳的房子,你住在第一間。如果你給一個你們三個人共同的朋友的地址你可以給房號就行了。不用給出你們的任意一個人的那間房子號(不指向任何成員)。但如果你給你的一個私人朋友地址,你會給出房間號和你的那個房間號。為了使這個地址有區別,你必須有一個廳來作為偏移量(offset)。不知道大家明白這個例子嗎,也許這個例子會影響你的正確思維。那就太糟糕了。不過我還是喜歡這樣想問題,也許不太准確,但可以幫助我,因為想象一個內存空間比想象一個三居室要難好幾點兒。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved