深度探索C++对象模型之(四)

来源:互联网 发布:西安软件开发工资 编辑:程序博客网 时间:2024/06/06 12:22

=====================================================================

如果喜欢,请关注:JellyThink | 思想的果冻

更多原创精彩博文,尽在www.jellythink.com

还可以关注新浪微博:http://weibo.com/u/1887014677

=====================================================================

学习C++中指针的类型
我们都知道,在C++中的指针的作用是很厉害的。指针的类型有很多种的,每个指针在内存中都是占用4字节,指针是指向一块内存的,现在我定义一个指向类A的指针是如何与一个整数的指针或一个指向字符串的指针是如何区别的。比如,我定义一下的指针:
int *pointer1;char *pointer2;float *pointer3;Vector<int> *pointer4;...
最终,以上的指针对编译器来说,是如何区别的呢?(都是4字节)以内存的需求的观点来说,没有什么不同的!是的,它们都需要足够的内存来放置一个指针,再强调一次:都是4字节。“指向不同类型之各指针”间的差异,既不在其指针表示法不同,也不在其内容(代表一个地址)不同,而是在其所寻址出来的object类型不同。也就是说,“指针类型”会教导编译器如何解释某个特定地址中的内存内容及其大小;一个指向地址1000的整数指针,在32位机器上,将涵盖的地址空间1000~1003;那么一个指向地址1000而类型为void×的指针,将涵盖怎样的地址空间呢?是的,如果类型是void×的指针,我们不知道的,编译器也不知道该干什么。这就是为什么一个类型为void×的指针只能够持有一个地址,而不能通过它操作所指之object的原因。所以在使用void×指针时,需要进行转换,但是这种转换大部分情况下它并不改变一个指针所含的真正地址,它只影响“被指出之内存的大小和其内容”的解释方式。

这一切都很简单的工作着,但是一旦加入了多肽,事情就会变的复杂起来。但是,作为程序员

的我们必须要去了解编译器是如何处理这些复杂的事情的。
以下经过代码来进行说明:

#include "stdafx.h"#include <iostream>using namespace std;class A{public:     A(){}     virtual ~A(){}     virtual void print()     {          cout<<"I am from class A"<<endl;     }private:     int num;     char *name;};class B:public A{public:     B(){}     virtual ~B(){}     void print()     {          cout<<"I am from class B"<<endl;     }private:     float price;     char *school;};void main(){     B b;     B *pb = &b;     B &rb1 = *pb;     B &rb2 = b;     // OK,Let's go     cout<<sizeof(b)<<endl;     cout<<sizeof(pb)<<endl;     // 在《深入浅出C++对象模型》一书中书引用需要4字节的内存空间,实际上此处是类B的内存大小     cout<<sizeof(rb1)<<endl;     cout<<sizeof(rb2)<<endl;}


好了,假设我的B b放在地址1000处,一个B指针和一个A指针有什么不同呢?
B b;B *pb = &b;B &rb1 = *pb;A *pa = &b;
它们每个都指向B对象的第一个byte处,差别是,pb所涵盖的地址包含整个B类的对象,而pa所涵盖的地址只包含B类对象中的和A地址重复部分。
除了A类中出现的对象,不能够使用pa来直接处理B的任何members。唯一例外是通过virtual机制。
#include "stdafx.h"#include <iostream>using namespace std;class A{public :      A(){}       virtual ~A(){}       virtual void print()      {            cout<< "I am from A::print" <<endl;      }       void printA()      {            cout<< "I am from A::printA" <<endl;      }private :       int num;       char *name;};class B:public A{public :      B(){}       virtual ~B(){}       void print()      {            cout<< "I am from B::print" <<endl;      }       void printB()      {            cout<< "I am from B::printB" <<endl;      }private :       float price;       char *school;};void main(){      B b;      A *pa = &b;       // 不合法,printB不是A的一个member       // 虽然我们知道pa目前指向一个B object       // pa->printB();       // OK;经过一个显式的downcast操作没有问题      ( static_cast <B *>(pa))->printB();       // 下面这种更好,但是它是一个run-time operation       if (B *pb = dynamic_cast <B *>(pa))      {            pb->printB();      }}
当我写这条语句时,pa->printA();编译器会干什么?pa的类型将在编译器时期决定一下两点:
1.固定的可用接口;也就是说,pa只能调用A的public接口
2.确定该接口的访问级别

现在来看看以下的这种情况:
B b;A a = b; // 这里会发生什么?a.print(); // call whose print()

当将B的object赋值给A的object,会发生什么?看图:

是的,从内存来说,首先发生了截断。A对象的内存大小肯定比B的要小,这样直接赋值,会发生内存截断。此时,使用A对象调用类A中的虚函数,调用的是类A的函数,可见,此时多肽是没有效果的,这说明什么?这样直接赋值,发生内存截断是不支持多肽的,而此时vptr的内存地址也变了。看图:

但是,B的对象的vptr的地址是没有变化的。这一切都是编译器在背后搞的鬼。
void main(){      B b;      A *pa = &b;       // 不合法,printB不是A的一个member       // 虽然我们知道pa目前指向一个B object       // pa->printB();       // OK;经过一个显式的downcast操作没有问题      ( static_cast <B *>(pa))->printB();       // 下面这种更好,但是它是一个run-time operation       if (B *pb = dynamic_cast <B *>(pa))      {            pb->printB();      }      A &a = b;      a.print();      A a1 = b;      a1.print();}

将main函数改成这样,运行。我会发现,C++是通过class的pointers和references来支持多肽的。那这是为什么呢?一个pointers和references之所以支持多肽,是因为它们并不引发内存中的类型转换的操作,受到改变的只是它们所指向的内存的“大小和内容解释方式”而已。永远记住,C++是通过class的pointers和references来支持多肽的。

2012/1/2 东软大连

=====================================================================

如果喜欢,请关注:JellyThink | 思想的果冻

更多原创精彩博文,尽在www.jellythink.com

还可以关注新浪微博:http://weibo.com/u/1887014677

=====================================================================


原创粉丝点击