• Data 语义学(1)


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

  • 相关阅读:
    大型运输行业实战_day12_1_权限管理实现
    大型运输行业实战_day11_2_事务理论与实际生产配置事务管理
    大型运输行业实战_day11_1_aop理论与aop实际业务操作
    SpringBoot目录文件结构和官方推荐的目录规范、静态资源访问
    SpringBoot常用的注解
    Spring Boot面试题
    Spring常见面试题总结
    dubbo面试题及答案
    nginx面试题及答案
    Mybatis面试题及答案
  • 原文地址:https://www.cnblogs.com/zhuwbox/p/3425896.html
Copyright © 2020-2023  润新知