1.3.1 封装支持
封装是面向对象方法的重要原则——把对象的属性和行为(数据操作)结合为一个独立的整体,并尽可能地隐藏对象的内部实现细节,外部只能通过对象的公有成员函数访问对象。编译器对于封装的处理相对来说比较简单,只要确定好怎么处理成员函数和成员变量就能正确地处理类。
编译器对于成员函数的处理方法是把成员函数转化成类似于C语言中的普通函数,转化之后编译器就能像编译C语言的函数一样编译成员函数。转化的规则也非常简单,就是为成员函数增加一个额外的参数。例如我们前面提到的CShape类中有一个成员函数void setCenter(double xAxis, double yAxis),编译器首先对这个函数进行转化,然后再进行编译。转化后的函数形式为void setCenter(CShape * const this, double xAxis, double yAxis),这就解决了成员函数的编译问题。
注意
这也是在面向对象语言的成员函数中可以通过this指针访问对象成员变量的原因。因为每一个this指针实际上指向一个具体的对象,这个对象是成员函数的隐式参数之一。
编译器对成员变量的处理非常简单,直接按照对象的内存布局产生对象即可。比如CPoint类实例化的对象布局如图1-7所示。
图1-7 简单对象的内存布局
另外需要提到的是,编译器按照对象的成员变量组织对象的内存布局,在这个过程中并不关心对象成员变量的修饰符(如private、protected和public)。也就是说,当内存布局组织好以后,编译器无法控制内存的访问,那么private的成员变量可以通过“某些特殊”手段被非本类的成员函数访问。成员变量和成员函数的修饰符的访问规则是编译器在编译过程进行处理,不涉及程序运行时。
因为CShape中存在虚函数,所以编译器在实例化对象的时候会增加一个额外指针的空间用于存储虚函数表的地址。虚函数表中存放的是函数的地址,这个指针的目的是支持多态,下面会详细介绍。CShape类实例化的对象布局如图1-8所示。
图1-8 包含虚函数对象的内存布局
注意
vptr的位置和编译器实现有关,有些编译器将vptr放在对象布局的起始位置,有些则将vptr放在对象内存布局的最后。