Data 语义学(1)

来源:互联网 发布:mysql定时任务 编辑:程序博客网 时间:2024/06/06 11:32

一、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\n",&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) 中对各种情况的介绍),我们多多注意就行了!

 

0 0
原创粉丝点击