第一章 关于对象
前言
在C语言里面,数据和函数是分开的,它们之间的关联是通过参数和返回值来取得联系。如:
typedef struct point3d
{
float x;
float y;
float z;
} Point3d;
void print_point3d(const Point3d *p)
{
printf("%g %g %g", p->x, p->y, p->z);
}
但是在c++中Point3d可以用 一种抽象的数据类型(abstract data type ADT)来实现
class Point3d
{
public:
Point3d(float x = 0.0, float y = 0.0, float z = 0.0):_x(x), _y(y), _z(z) {}
// ... etc ...
private:
float _x;
float _y;
float _z;
};
从软件工程角度来看,使用ADT的数据封装比使用c语言中的函数+全局数据要好。但是,加上封装之后会有怎样的布局成本呢?
加上封装后的布局成本
- 对于普通类来说,没有额外成本。
- 对于含有virtual机制的类来说,主要有以下两种额外负担。
- virtual function 机制 用来支持 运行时绑定
- virtual base class 虚基类,用来支持同个类出现在多个继承关系中,有一个单一而被共享的实体(B、C继承A,D又继承B和C)。
c++对象模式
在c++中,成员函数有两种,为
- static
- nostatic
成员函数有三种,分别为 - static
- nostatic
- virtual
c++ 的内存模型:非静态数据成员被配置于每一个类对象中,静态数据成员放在所有的对象之外,静态和非静态函数成员也被放在所有的对象之外。虚函数则以以下两步获得支持:
- 每个类产生一堆指向虚函数的指针,放在一个表格中,这个表格被称为虚表(virtual table)。
- 每一个实例化 的对象,都被添加了一个指针,指向虚表。这个指针通常被称作vptr。这个vptr的设置(赋值)和重置通常由每一个类的构造函数,析构函数和拷贝运算符自动完成。每一个类的type_info (用来支持runtime type identification, RTTI) 也保存在虚表中,通常放在虚表的初始位置。
加上继承
c++ 支持单继承,多重继承,虚拟继承(virtual. 在此种情况下, 基类不管在继承链中被派生多少次,永远只会存在一个实体)
c++ 最初采用的继承模型并不运用任何间接性:基类对象数据成员直接被放在派生类对象中当中,自c++2.0(注意,不是c++20)起引入新导入的虚基类。有关内容将在后续讨论。
C++中的多态
c++中,对于对象的多态操作要求对象必须经由一个pointer或者reference来存取。例如:
derived_class *pc = new base_class();
derived_class &rc = base_class();
对象所占用的内存大小
一个实例化后的对象大小是多少呢?一般而言要有:
- 非静态数据成员大小之和
- 内存对齐(alignment)而填充的空间(可能在数据成员之间,也可能在末尾)。
- 为了支持virtual 而产生的额外负担。
指针的类型
一个指向对象的指针和一个指向普通数据(int)的指针有什么不同呢?从内存的角度来说,没什么不同,他们都占据32位或者64位的空间。它们的差异,在于寻址出来的对象不同。也就是说,指针类型会教导百年一起如何解释一个特定内存区域的大小和内容
加上多态之后
下面的Bear指针和ZooAnimal指针有什么不同呢?
Bear b;
ZooAnimal *pz = &b;
Bear *pb = &b;
它们都指向Bear b 的第一个字节。差别是,pb所覆盖的地址保护整个b对象,而pz只包含b中ZooAnimal那一部分。