3-1数据的绑定
1、一个inline函数实体,在整个class声明未被完全看见之前,是不会被评估求值的。
看下面的例子
<span style="font-size:14px;">class A{
int i;
public:
A() :i(0){}
int f() const { return g() + 1; }
int g() const { return i; }
};
main()
{
A a;
a.f();
}</span>
f()定义内调用了g(),而此时g()还没有声明。
但参看第一条规则,f和g的评估求值必须在类完全声明之后进行,故没有错误。
2、inline函数参数列表中的名称会在第一次遭遇时被决议完成,因此请始终把“嵌套类型(nested type)声明放在class的起始处”
<span style="font-size:14px;">typedef int length;
class Point3d
{
public:
void mumble(length val){_val=val;}//int
length mumble(){return _val;}//int
private:
typedef float length;
length _val;
};
</span>
<span style="font-size:14px;">//typedef int length;
class Point3d
{
public:
void mumble(length val){_val=val;}//int
length mumble(){return _val;}//int
private:
typedef float length;
length _val;
};
</span>
如下情况才是完全正确的
<span style="font-size:14px;">class Point3d
{
private:
typedef float length;
length _val;
public:
void mumble(length val){_val=val;}//int
length mumble(){return _val;}//int
};</span>
3-2 数据成员的布局
1、非静态数据成员(nostatic data members)在类对象的排列顺序和声明顺序一致。
2、静态数据成员(static data members)存放在数据段(data segment)中,和个别的类对象无关。
3、虚表指针(vptr)可能被安差在对象的头部或者尾端。
4、一般编译器将一个以上的access sections 安排在一起(public,private,protected)
data segment:用来存放程序中已初始化的全局变量的一块区域,属于静态分配。
3-3 数据成员的存取
静态数据成员(static data members):类体中的数据成员的声明加上static关键字,该数据成员就成为了该类的静态数据成员。遵循public/protected/private访问规则:
1、实际上是类域中的全局变量。所以静态数据成员的定义不应该放在头文件中,否则会引起重复定义错误。
2、静态数据成员被类的所有对象共享,在整个程序中只有唯一的一个实体。
3、静态数据成语的类型可以是所属类的类型,而普通数据成员不可。普通数据成员只能声明为所属类类型的指针或引用。(因为此时类还未构造完成)
4、若取一个static data member的地址,会得到一个指向其数据类型的指针,而不是一个指向其class member的指针,因为static member并不内含在一个class object中
取一个nonstatic data member的地址,会得到它在class中的offset,取一个真正绑定于class object身上的data member的地址,会得到该member在内存中的内存地址
<span style="font-size:14px;">#include <iostream>
using namespace std;
class A{
public:
int nonStaticMember;
static int staticMember;
};
int A::staticMember = 1;
int main()
{
A a;
a.staticMember = 9;
//cout << &A::nonStaticMember << endl;
//上面的写法错误,会得到错误结果
//正确写法,%p打印地址
printf("&A::nonStaticMember:%p\n", &A::nonStaticMember);
printf("&a.nonStaticMember:%p\n", &a.nonStaticMember);
printf("&A::staticMember:%p\n", A::staticMember);//这里不太明白,感觉就是data segment中地址????
return 0;
}</span>
非静态数据成员(nonstatic data member):直接存放在每一个class object中,对象中nonstatic member的存取是由this指针完成。
1、与对一个nonstatic data member进行存取操作,编译器要把class object的起始地址加上data member的偏移量
eg:
<span style="font-size:14px;">origin._y=0.0;
//会转化为
&origin + (&Point3d::_y-1);</span>
-1是为了区分一个指向数据成员的指针在指向类的第一个成员和没有指向任何成员的两种情况。
2、每一个nonstatic data member的偏移量在编译时期即可获知,即使member属于一个base class subobject。
3、虚拟集成为经由“base class subobject”存取class member导入一层新的间接性
eg:
<span style="font-size:14px;">Point3d *pt3d;
pt3d->_x=0.0;</span>
上述代码对于struct member、class member、单一集成、多重继承的情况下存取效率相同。
但对于virtual base class的member,存取速度会比较慢。
<span style="font-size:14px;">Point3d origin;
origin.x=0.0;
pt->x=0.0;</span>
上述两句的执行有什么重大差异?
当Point3d是一个derived class,而在其继承结构中有个virtual base class,并且被存取的member是一个从该virtual base class继承来的member时有重大差异,这时候,因为不知道pt必然指向哪一种class type(因此就不能再编译时期指导这个member的真正的位置),所有这个存取操作必须延迟至执行期,经由一个额外的间接导引,才能够解决,但如果使用origin。就不会有这些问题,其类型无疑是Point3d class,即使它继承自virtual base class,members的 offset位置也在编译时期就固定了。
3-4 "继承"与Data Member
1、c++保证出现derived class中的base class subject 有其完整原样性,即derived class的数据成员不会占用base class的alignment
2、vptr可以放在class尾部或者头部:
如果放在尾端:可以保留base class C struct的头像布局,可以和C程序代码兼容
如果放在头部:有助于在多重继承下,通过指向class members的指针调用virtual function.否则不仅从class object起始点开始量起的Offset必须在执行期备妥,甚至与class vptr之间的offset也必须备妥。
在Visual Studio 2013下实验,可以看见是把vptr放在头部的
<span style="font-size:14px;">class Vertex{
public:
virtual void f();
protected:
Vertex *next;
};</span>
3、对于一个多重派生对象,有的编译器是按声明次序排列其base class的布局
<span style="font-size:14px;">#include <iostream>
using namespace std;
class Point2d{
public:
//virtual void g();
protected:
float _x, _y;
};
class Point3d :public Point2d{
public:
protected:
float _z;
};
class Vertex{
public:
//virtual void f();
protected:
Vertex *next;
};
class Vertex3d:public Point3d,public Point2d,public Vertex{
public:
protected:
float mumble;
};
</span>
但是某些编译器设计有种优化技术,只要第二个(或后继)base class声明了一个virtual function,而第一个base class没有,酒吧多个base class的次序进行调换,这样可以在derived class中少产生一个vptr。
<span style="font-size:14px;">#include <iostream>
using namespace std;
class Point2d{
public:
//virtual void g();
protected:
float _x, _y;
};
class Point3d :public Point2d{
public:
protected:
float _z;
};
class Vertex{
public:
virtual void f();//有虚函数
protected:
Vertex *next;
};
class Vertex3d:public Point3d,public Point2d,public Vertex{
public:
protected:
float mumble;
};
</span>
下图可以看出,vertex class在前面
对于3、中,因为Point2d,Point3d有相同的其实地址,
<span style="font-size:14px;">Vertex v3d;
Vertex *pv;
Point2d *p2d;
Point3d *p3d;
</span>
对于
<span style="font-size:14px;">p2d=&v2d;
p3d=&v3d;</span>
只需要简单拷贝
而
<span style="font-size:14px;">Vertex3d *pv3d;
pv=pv3d;</span>
需要被转换为
<span style="font-size:14px;">pv=pv3d ? (Vertex*)((char*)pv3d)+sizeof(Point3d):0//条件判断方正pv3d为NULL</span>
3-6指向data members的指针
指向类成员(nonstatic)的指针并非指针。
1、普通指针使用*来标示,而指向类的数据成员的指针使用ClassName::*标示
2、一个指向成员的指针并不指向一个具体的内存位置,可以把它看做一个相对于起点的偏移量(offset)
#include <iostream>
using namespace std;
class C{
public:
C(int _a, int _b) :a(_a), b(_b){}
public:
int a,b;
};
int main()
{
C c(5, 1);
int C::*pa,C::*pb;//指向数据成员的指针,记得把其看做一个偏移量
pa = &C::a;
pb = &C::b;
pa = &C::a;//pa指向一个具体的内存地址
pa = &C::b;
printf("pa:%p\n", pa);
printf("pb:%p\n", pb);
C *pc = &c;//指向对象c的指针
printf("*pa:%d\n", c.*pa);//c.*pa实际是c+数据成员a的偏移量,得到了c.a的内存地址
c.*pa = 0;
printf("*pa:%d\n", c.*pa);
int b = pc->*pa;
printf("*b:%d\n", b);
return 0;
}
上面例子中,.*和->*都是对某个实际对象加上了该数据成员相对于起点的偏移量,得到了该对象中数据在内存中的实际地址
<span style="font-size:14px;">#include <iostream>
using namespace std;
class Base1{
public:
Base1(int v) :val1(v){}
int val1;
};
class Base2{
public:
Base2(int v) :val2(v){}
int val2;
};
struct Derived :Base1, Base2{
public:
Derived(int v1, int v2) :Base1(v1), Base2(v2){}
};
void func1(int Derived::*dmp, Derived *pd){
cout << "pd->*dmp:"<<pd->*dmp << endl;
printf("&Derived::*dmp:%p\n", dmp);
}
void func2(Derived *pd){
int Base2::*bmp = &Base2::val2;
printf("&Base2::*bmp:%p\n", bmp);
//func1(bmp?bmp+sizeof(Base1):0, pd);
func1(bmp, pd);
}
int main()
{
Derived d(1, 5);
func2(&d);
return 0;
}</span>
书上说bmp作为func1的第一个参数时
<span style="font-size:14px;">pd->*dmp</span>
将会取到Base::val1,但我在VS2013 和 codeblocks上运行的结果都取到了val2
而书上给出的改进
<span style="font-size:14px;">func1(bmp+sizeof(Base1),dp);
//进一步改进func1(bmp?bmp+sizeof(Base1):0,pd),以免bmp==NULL</span>
则编译错误
下面程序和作者得到相同结果,作者猜测编译器对此作出了特殊处理
#include <iostream>
using namespace std;
class Base1{
public:
Base1(int v) :val1(v){}
int val1;
};
class Base2{
public:
Base2(int v) :val2(v){}
int val2;
};
struct Derived :Base1, Base2{
public:
Derived(int v1, int v2) :Base1(v1), Base2(v2){}//offset都为0
public:
int vald;
};
int main()
{
printf("&Base1::val1 = %p\n", &Base1::val1);
printf("&Base2::val2 = %p\n", &Base2::val2);
printf("&Derived::val1 = %p\n", &Derived::val1);
printf("&Derived::val2 = %p\n", &Derived::val2);
printf("&Derived::vald = %p\n", &Derived::vald);
return 0;
}