【深度探索C++對象模型】第一章 關於對象
本書組織: 第 1 章,關於對象(Object Lessons),介紹 C++ 對象的基礎概念,給讀者一個粗略的了解。 第 2 章,構造函數語意學(The Semantics of Constructors),構造函數什麽時候會被編譯器合成?它給我們的程序效率帶來了如何的影響? 第 3 章。Data語意學(The Semantics of Data)
第 5 章,構造、析構、拷貝語意學(Semantics of Construction, Destruction, and Copy),探討怎樣支持 class 對象模型,以及 object 的生命周期。
第 6 章,運行期語意學(Runtime Semantics),暫時對象的生與死,new 與 delete 的支持。
第 7 章,在對象模型的頂端(On the Cusp of the Object Model)
在 C 語言中,“數據”和“處理數據的操作(函數)”是分開來聲明的。也就是說,語言本身並沒有支持“數據和函數”之間的關聯性。
我們把這樣的程序方法稱為“程序性的”。比如。我們聲明一個 struct Point3d:
typedef struct _Point3d { float x; float y; float z; } Point3d; |
void Point3d_print( const Point3d* pd ) { printf("(%g, %g, %g)", pd->x, pd->y, pd->z); } //%g和%G是實數的輸出格式符號。它是自己主動選擇%f和%e兩種格式中較短的格式輸出。而且不輸出數字後面沒有意義的零。 |
class Point
{
public:
Point( float x = 0.0 ) : _x(x) {}
float x() { return _x; } void x( float val ) { _x = val; } // ... protected: float _x; }; class Point2d : public Point { public: Point2d( float x = 0.0, float y = 0.0 ) : Point( x ), _y( y ) {} // ... protected: float _y; } class Point3d : public Point2d { public: Point3d( float x = 0.0, float y = 0.0, float z = 0.0 ) : Point2d( x, y ), _z( z ) {} // ... protected: float _z; } |
後面你將看到,C++ 在布局和存取時間上基本的負擔 是由 virtual 引起的。包含 虛函數 以及 虛基類。 二、C++ 的對象模型 首先,C++ 中。
2種成員變量(class data members):靜態的(static) 和 非靜態的(non-static); 3種成員函數(class member functions):靜態的、非靜態的 和 虛擬的(virtual)。 |
class Point
{
public:
Point( float valx );
virtual ~Point();
float x() const;
static int PointCount();
protected: virtual ostream& print( ostream &os ) const; float _x; static int _point_count; }; |
通常這個指針被稱為 vptr。
vptr 的設定和重置都有每個 class 的 構造函數、析構函數、拷貝以及復制運算符。每個 class 所關聯的 type_info object( 用以支持 runtime type identification, RTTI )也經由 virtual table 被指出來,一般是放在表格的第一個 slot 處。
三、C++ 怎樣支持多態
1. 經由一組隱含的轉化操作。比如。把一個 派生類 的指針轉化為一個指向其 public base type 的指針:
shape* ps = new circle();
2. 經由 virtual functions 機制:
ps->rotate();
3. 經由 dynamic_cast 和 typeid 運算符:
if ( circle *pc = dynamic_cast< circle* >(ps) )...
多態的主要用途。是經由一個共同的接口。來影響類型的封裝,我們一般會把這個接口定義在一個抽象基類裏面。然後再在派生類裏重寫這個接口。
四、須要多少內存來表現一個 class object?
猜想以下的代碼的 sizeof 結果會是?
.eg.1.
class Base { public: Base(); ~Base(); }; // sizeof(Base) = ? |
class Base
{
public:
Base();
~Base();
protected: double m_Double; int m_Int; char m_BaseName; }; // sizeof(Base) = ? |
此外,須要註意的是。一個指針(或是一個 reference)。無論它僅僅想哪一種數據類型。指針本身所需內存大小是固定的。比方。在 win32下,一個指針的大小就是4個字節(byte)。 問題的答案:
第一題: 答案是1。
class Base 裏僅僅有構造函數和析構函數,由前面的內容所知,class 的 member functions 並不存放在 class 以及事實上例內,因此,sizeof(Base) = 1。是的,結構不是0,而是1,原因是由於,class 的不同實例,在內存中的地址各不同樣,一個字節僅僅是作為占位符,用來給不同的實例分配不同的內存地址的。
第二題:答案是16。
double 類型的變量占用8個字節。int 占了4個字節,char 僅僅占一個字節。但這裏它會按 int 進行對齊,Base 內部會填補3個字節的內存大小。 最後的大小就是 8 + 4 + 1 + 3 = 16。 大家能夠調整三個成員變量的位置,看看結果會有什麽不同。 |
Base* p_Base; int* p_Int; vector<string> * p_vs; |
也就是說,“指針類型”會教導編譯器怎樣解釋某個特定地址中的內存內容及其涵蓋大小。
比方:一個指向 int 的指針,如果其地址是 1000。在32位及其上。將涵蓋地址空間 1000~1003. 那麽。一個指向地址 1000 的 void* 的指針,將涵蓋如何的地址空間呢?沒錯,我們並不知道。這就是為什麽一個類型為 void* 的指針。僅僅可以含有一個地址,而不可以通過它操作所指的 object 的緣故。 所以。轉型(cast)事實上是一種編譯器指令,它所做的,並非改變指針所含的真正地址,而是教導編譯器該去怎樣解釋指針所涵蓋的地址空間。 六、小結 第一章——關於對象。本章初步介紹了C++的對象模型是如何的,後面的章節將繼續討論這個對象模型的底層實現機制。 在讀完本篇文章之後。你應該理解:
- 怎樣計算 sizeof(classA) 的大小;
- 了解 class 的內存布局。
【深度探索C++對象模型】第一章 關於對象