一、資料成員的繫結(the binding of a data member)
先看一段**:
//某個標頭檔案,從某處含入
extern float x;
class point3d
void x(float new_x)
private:
float x,y,z;
};
現在的問題是:在類的兩個成員函式x中,被返回和設定的x是哪乙個呢?是extern宣告的x還是類的成員變數x呢?
在現在的編譯器中,一般都指的是類中的成員變數x。但是在c++最早的編譯器中則不是這樣做的。因此導致了兩種程式設計風格:
(1)把所有的資料成員放在類的宣告的起始處。
(2)所有的內聯函式都放在類宣告之外。
雖然他們的必要性已經自c++2.0之後消失了,但是這種程式設計風格在今天依然存在。這個古來的語言規則被稱為「member rewriting rule」,意思是「乙個內聯函式實體,在整個類宣告未被完全看見之前,是不會被評估求值的」。c++標準中的效果是:乙個內聯函式在類宣告之後立即被定義的話,依然對他評估求值,但是是在整個類宣告完成之後才開始。
但是對於成員函式的引數列表則不是這樣的。引數列表中的引數還是i會在它們第一次遇到時被決議。例如:
typedef int length;
class point3d
length mumble()
private:
typedef float length;
length _val;
};
兩個函式umber中的length會被決議為int,但是後續出現的length宣告則會使先前的操作不合法。
因此,我們最好把巢狀型別宣告放在類的起始處。
二、資料成員的布局(data member layout)
先看下面乙個類的定義:
class point3d
;
資料成員的布局情況是:
(1)非靜態成員在類物件中的排列順序和宣告順序一致,任何在其中間宣告的靜態成員都不會被放進物件布局中。
(2)靜態資料成員存放在程式的data segment中,和個別類物件無關。
注:data segement(來自維基百科):
是程式的虛位址空間的一部分,包含全域性變數和靜態變數,其大小由程式在執行之前程式設計師所放置的變數決定。
program memory包括三部分:
(1)data segment(data + bss + head)l
(2)stack
(3)code segment
c++標準規定,在同乙個訪問塊即private、public、protected等區段中,成員的排列只需符合較晚出現的成員在類物件中有較高的位址即可。也就是說,並不一定要連續排列。
什麼東西可能介於被宣告的成員之間的?由於對齊可能需要填補一些位元組。
編譯器還會合成一些內部使用的資料成員,以支援整個物件模型。比如指向虛函式表的指標vptr。vptr的放置位置由編譯器決定。傳統是放在類的所宣告的成員的最後。
c++標準允許編譯器把多個訪問塊指標的資料成員自由排列,而不必在乎在類宣告中的次序。
當前的眾多編譯器都是把乙個以上的訪問塊連鎖在一起,依照宣告次序形成乙個連續區塊。而且訪問塊的多少不會帶來額外負擔。
例如在乙個訪問塊中宣告4個成員和在4個訪問塊中分別宣告乙個成員得到的物件大小相同。
判斷乙個類中兩個成員誰先誰後可用如下**實現:
templatechar* acess_order(data_type1 class_type::*mem1,
data_type1 class_type::*mem2)
三、資料成員的訪問靜態資料成員每乙個 靜態資料成員只有乙個實體,被各個類物件所共享。它被存放在程式的data segment中,並被視為乙個全域性變數,值在類宣告範圍之內。注意:每一靜態成員的訪問許可(private、protected和public)以及與類的關聯並不會導致任何空間或執行時間上的額外負擔。
每次程式對靜態成員的呼叫,都會被內部轉化為該唯一的實體的直接操作。所以通過指標或物件來呼叫靜態成員,效果完全相同。比如
point3d origin,*pt = &origin;
origin.x = 0.0;
pt ->x = 0;
這也是c++中通過指標和通過物件訪問資料成員結果完全相同的唯一情況。這是因為,靜態資料成員並不在類物件之中,訪問靜態成員不需要通過類物件。通過操作符「.」進行訪問只是語法上的一種便宜行事而已。
如果靜態資料成員是從乙個複雜關係中繼承而來的,那麼程式中也依然只有乙個實體,訪問依然是直接的。
如果取乙個靜態資料成員的位址,會得到乙個指向其資料型別的指標,而不是指向其類成員的指標,因為靜態成員並不在類物件之中。
比如:
#include #include using namespace std;
class point3d
;int main()
程式輸出:
如果有兩個類,都宣告了同乙個靜態成員比如sta_mem,當它們都被放在程式的data segment時,會導致名稱衝突,編譯器會進行解決:暗中對每乙個靜態資料成員編碼以獲得唯一的程式識別**。
class base1
;//const成員在建構函式初始化
const int a;
static int b;
const static int c=9;//const static成員在類內初始化
};class base2
;//const成員在建構函式初始化
const int a;
static int b;
const static int c=9;//const static成員在類內初始化
};int base1::b=9;//static成員在類外初始化,可以修改
int base2::b=10;//static成員在類外初始化,可以修改
int main()
事實上x、y、z的訪問是經由this指標完成的。其函式引數應該是:
void point3d::translate(point3d * const this,const point3d &pt)
每乙個非靜態資料成員的偏移量在編譯時期即可獲得,即便這個成員屬於乙個基類(派生自單一或者多重繼承串鏈)子物件也是一樣的。
因此,訪問乙個非靜態資料成員的效率和訪問乙個c struct成員或者非繼承類的成員是相同的。
下面看看虛擬繼承。虛擬繼承將為經由基類子物件訪問類成員匯入一層新的間接性。比如
point3d *pt1;
point3d pt2;
pt1 ->_x = 0;
pt2._x = 0.1;
從類指標訪問和從類物件訪問有什麼重大差異?
當類point3d是乙個繼承類,在其繼承結構中有乙個虛擬基類,並且被訪問的成員是從該基類繼承未來的時候,就會有大的差異。
因為在這個時候我們不知道pt1到底指向那種類型別,也就不知道這個成員的真正偏移量。所以訪問操作要被延遲至執行期,經由乙個額外的導引,才能解決。
但是用類物件來訪問則不會導致這樣的問題。
《物件導向程式設計 C 》類資料成員和類成員函式
c 類中有一種型別成員,屬於類本身,而不屬於類的物件,這種型別成員成為類成員,而屬於物件的成員成為物件成員或例項成員。可以使用關鍵字static來建立乙個類成員。class c unsigned c n 0 在外部定義時不需要加static在類宣告內部宣告的static資料成員必須在任何程式塊之外定...
C 物件導向 類成員函式this指標
每個類成員函式都只涉及乙個物件,即呼叫它的物件。但有時候方法可能涉及到兩個物件,在這種情況下需要使用c 的 this 指標 假設將方法命名為topval 則函式呼叫stock1.topval 將訪問stock1的物件資料 stock2.topval 將訪問stock2的物件資料 如果希望該方法的兩個...
c 物件導向(四) 類的成員
這裡首先介紹類的訪問控制和構造 析構函式,然後介紹屬性,方法 類的成員包括常量 變數 屬性 方法 事件 操作符 建構函式 析構函式等。從訪問控制來看,與類的修飾符類似,類的修飾符用於規定這個類的訪問控制,成員的修飾符就是規定類中成員的訪問控制。修飾符包括 public 允許類外部對這個成員進行訪問 ...