3.1 数据成员的绑定
1. 全局数据与类内部数据成员的关系:类内部对于内联成员函数的数据成员绑定操作会在整个类声明完成之后才发生,即屏蔽了全局数据;
2. 类成员函数的参数表问题:参数表中的名称在其第一次出现时被适当地决议完成,这样可能会造成外部名称与嵌套类型名称相冲突,如下例中的length在两个成员函数中都被决议为全局类型int,当后续包涵length的嵌套类型声明出现时,就将稍早的绑定标为非法:
typedef int length;
class Point3D {
public:
void mumble( length val ) { _val = val; }
length mumble() { return _val; }
private:
typedef float length;
length _val;
};
避免出现这种错误的方法是:将嵌套类型声明始终放在类的起始处。
3.2 数据成员的布局
1. nonstatic数据成员在类对象中的排列顺序和其被声明的顺序一样,任何中间介入的static数据成员都不会放入类对象中,而是放于程序的数据段内,与各个类对象无关;
2. 在同一个接入(即private,public,protected)段内的成员排列不一定连续,中间的边界对齐字节可以放于其间;
3. 一般而言,vptr都放在所有明确声明的数据成员的最后,但没有强制要求;
4. 允许编译器将多个接入段之中的数据成员自由排列,不必在乎他们出现在类声明中的次序;
5. 随意声明的接入段限定词的多少(即可以多次声明接入限定词)不会增加类对象的大小。
3.3 数据成员的存取
本节分为静态数据成员,非静态数据成员以及继承体系下的数据成员的存取方式及效率。
3.3.1 static数据成员
Static数据成员被视为一个全局变量,但是其可见性只局限于定义它的类内部,每一个成员的存取权限(public,protected,private)以及与类的关联,并不会导致任何空间上或执行时间上的额外负担,不论是个别的类对象还是静态数据成员本身。
每一个static数据成员只有一个实体,放于程序的数据段之内,每次程序对其存取,都会被内部转化为对该唯一的外部实体的直接参考操作,并不需要通过类对象,如origin.chunksize = 250;等效于Point3D::chunksize = 250;
即使static数据成员是从复杂继承关系中继承而来的成员,其操作仍然是这么直接有效。
若取一个静态数据成员的地址,得到的是一个指向其数据类型的指针,而不是一个指向其类成员的指针。
注意:不管对于单继承,多继承,虚拟继承,多态,static数据成员的操作都是这么直接高效,不需要其他任何开销。
3.3.2 nonstatic数据成员
Nonstatic数据成员直接存放于每一个类对象中,除非经由明确的显式或隐式的类对象,没有办法直接存取他们,在成员函数中直接处理一个nonstatic数据成员,就会发生隐式类对象的操作,如:
Point3D Point3D::transtate( const Point3D& pt ) {
x += pt.x;
y += pt.y;
z += pt.z;
}
实际上操作如下:
Point3D Point3D::transtate( const Point3D* this, const Point3D& pt ) {
this.x += pt.x;
this.y += pt.y;
this.z += pt.z;
}
对nonstatic数据成员进行存取操作,编译器需要把类对象的起始地址加上数据成员的偏移量,如origin._y = 0.0;那么地址&origin._y等效于&origin + (&Point3D::_y – 1);
每一个nonstatic数据成员的偏移量在编译时期即可获得,甚至如果成员属于一个基类子对象(派生自单一或多重继承串链)也是一样。因此存取一个nonstatic成员数据,其效率和存取一个C结构成员或一个非派生类的成员是一样的。
对于指针操作,如Point3D* pt; pt->_x = 0.0;其执行效率在_x是一个结构成员,类成员,单一继承与多重继承下完全相同。但如果是虚基类的成员,则效率要低一些。
注意:由于表现多态的虚函数问题并不与数据成员有关,因此在继承体系中是否涉及虚函数,对数据成员的存取操作不产生任何影响。