一、Data Member 的绑定(The binding of Data Member)
1 extern float x; 2 3 class Point3d 4 { 5 public: 6 Point3d( float, float, float); 7 float X() const { return x; } 8 void X( float new_x ) const { x = new_x; } 9 // ... 10 private: 11 float x,y,z; 12 };
请问 Point3d::X()传回哪一个x?是class 内部那个还是外部(extern )的那个?大家肯定都会说传回 class 内部的那个,这个回答是真正确的。但是在早期的c++中可不一定哦,早期的C++的取用操作会指向 global x object,这出乎大家的意料。那么早期是如何预防这种错误呢?有以下两种办法:
(1). 将所有的data member 定义到 class 声明的起始处,以确保正确的绑定,如下:
1 extern float x; 2 3 class Point3d 4 { 5 float x,y,z; 6 public: 7 Point3d( float, float, float); 8 float X() const { return x; } 9 void X( float new_x ) const { x = new_x; } 10 // ... 11 };
(2). 把所有的 inline function,不管大小都放在 class 声明之外,即将 member functions 放在class 类里面声明,放到 class 外面进行定义,这里就不举例!
而今天的代码完全不用像上面那样需要进行考虑,因为现在的编译器对 member functions 的分析会直到整个 class 的声明都出现了才开始。因此在一个 inline member function 函数体内的一个 data member 绑定操作,会在整个 class 声明完成之后发生。但是,对于 member function 的 argument list 并不为真哦!请看下面这个例子:
1 #include <iostream> 2 3 using namespace std; 4 5 typedef int length; 6 7 class A 8 { 9 private: 10 typedef float length; 11 length num; 12 public: 13 void setNum( length _num ) 14 { 15 num = _num; 16 } 17 18 }; 19 20 int main() 21 { 22 system("pause"); 23 }
这个代码并没有运行结果,可以通过编译!但是编译器会给出一个提醒:
警告 1 warning C4244: “=”: 从“length”转换到“A::length”,可能丢失数据
这是为什么呢?因为你的成员变量 num 定义的是 float 型,而你的 setNum() 的参数 _num 是 int 型!咋进行赋值时会进行强制转化,这样编译器会给出如上的警告。由此可说明:对于 member function 的 argument list 并不是取用 class 的 typedef 的 length 而是 全局变量 length,所以在写这样的代码要小心了哦。如果你想要 argment list 的也是用 clas 内的 length ,就把内嵌的 typedef 定义放到他要被用到的地方的前面即可咯!
二、Data Member 的布局(Data Member Layout)
nonstatic data members 在 class object 中排列的顺序将和其被声明的顺序一致,任何的static data members都不会被放到对象布局之中。而是存放在程序的 数据段(data segment)而与个别的 class 无关。C++的标准是要求在同一个访问级别(也就是 public、private、protected)中,members 的排列只需符合“较晚出现的 members 在 class object 中具有较高地址“,并没要求他们是连续排列的。那么什么东西会被介于被声明的 members 之间呢?
a. members 的边界调整(alignment) 可能就需要填补一些 bytes. 这样就会隔开两个相连的member啦。
b. 编译器可能会合成一些内部使用的 data members 以支持整个对象模型。vptr 就是这一类东西,虽然没规定它放在哪个位置。但是一般 vptr 会被放在 class object 的声明的 members 的最后,不过也有编译器会把 vptr 放在 class object 的最前端---不太可能会插到 members 之间啦。
三、Data Member 的存取
(1). static data members
我们之前的博文说道不管是没有 class object 、有一个class object 或者 n 个object 这个类的 static data members 都已经存在于程序的 data segment 中了(前提是你得定义了这些 static data members 哈),并且只有一份!大家知道 static data members 可以通过class 的 "::"访问,也可以通过 class object 的"."访问,其实别看他和其他nonstatic 成员一样 通过"."访问,其实它根本不像 nonstatic data members 一样存储在 class object 中啦。这样只是为了方便操作,其实通过"::"访问才是原汁原味的!!!
既然 static data member 都是一份实体存在 程序的 datasegment 中,那么如果一个程序中的两个类有了同样名字的 static data member 就会导致名称冲突,怎么办?不要怕,编译器会对每个 static data member 进行编码(这种手法叫做:name-mangling),以获得一个独一无二的程序识别代码。当然,每个编译器的 name-mangling 做法都不一样,但无论怎么样他们要做的莫非一下两点:
a. 一种算法,推导出独一无二的名称。
b. 万一预编译系统(或编译工具)必须和使用者交谈,那些独一无二的名称就可以轻易的被推到回到原来的名称。(比如报错,提示说你这个 static data member 哪里错误,在给出名字的时候当然是用程序员的命的名称而不是编码后的名称!)
(2). nonstatic data members
nonstatic data members 的大小被存放在一个 class object中,并通过 "." 访问。看下面这个例子:
1 #include <iostream> 2 3 using namespace std; 4 5 typedef int length; 6 7 class A 8 { 9 public: 10 virtual void foo(){}; 11 int num1,num2,num3; 12 }; 13 14 int main() 15 { 16 A a; 17 A *pa = &a ; 18 printf(" &A::num2 = %p ",&A::num2); 19 cout << &a << endl; 20 cout << &a.num2 << endl; 21 cout << &(pa->num2) << endl; 22 return 0; 23 }
以下是输出结果:
我来解释一下:第一行是data member num2 在class 中的偏移量。第二行是 class A 在内存中的真实地址,第三行和第四行都是num2在内存中的真实地址。
大家发现图中的:0012FF40( num2 的地址 ) = 0012FF38( a 的首地址) + 00000008( num2 在 class A 中的偏移量); 没?
所以你不管是通过 class object 的 "." 访问或者通过class pointer 的 "->" 访问nonstatic data member,编译器都是通过:class object 的首地址 + 该 data member 在class 中的 offset (偏移量)。注意:在通过 class pointer ( 类指针)访问 nonstatic data member的时候,如果该 nonstatic data member 是 virtual base classs 的 data member ,那情况会有所不同哦!不过不用担心---虚继承总是不同于其他情况(具体请看 Data 语义学(2) 中对各种情况的介绍),我们多多注意就行了!