在 C 语言中,“数据”和“处理数据的操作”(函数)是分开来声明的,也即,语言本身并没有支持“数据和函数”之间的关联性。我们把这种程序方法,称为程序性的(procedural),由一组“分布在各个以功能为导向的函数中”的算法所驱动,它们处理的是共同的外部数据。
也即过程式编程语言的特质是:数据和函数各自分开声明,不支持数据和函数之间的关联性。
举个例子,我们声明一个 struct Point3d,像这样:
typedef struct point3d
{
float x;
float y;
float z;
} Point3d;
欲打印一个 Point3d,可能需要定义这样一个函数:
void Point3d_print(const Point3d* pt)
{
printf("(%g, %g, %g)", pt->x, pt->y, pt->z);
}
或者,如果要更有效率一点,就定义一个宏:
#define Point3d_print(pd)\
printf("(%g, %g, %g)", (pd)->x, (pd)->y, (pd)->z); // (pd)->x
// 是避免传递进来的是 &p;
// -> 的优先级要高于 &
在 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) { }
float x() const{ return _x; }
float y() const{ return _y; }
float z() const{ return _z; }
void x(float x) { _x = x; }
...
private:
float _x;
float _y;
float _z;
};
将数据成员及对数据成员的存取函数置于一个类体之内,即为封装。
程序员看到 Point3d 转换到 C++ 之后,第一个可能的问题就是:加上封装之后,布局成本增加了多少?答案是,class Point3d 并没有增加成本。三个 data members 直接包含在每一个 class object 中,和 C struct 一样。而 member functions 虽然在 class 的声明之内,却不出现在 object 之中。每一个 non-inline member function 都会诞生一个函数实例。
Point3d 支持封装性质,这一点并未给它带来任何空间或执行期的不良后果,你即将看到,C++ 在布局以及存取时间上主要的额外负担是由 virtual 引起的。
C++ 中,
class data members:
class member functions:
考虑如下的函数声明:
class Point
{
public:
Point(float x);
virtual ~Point();
float x() const;
static int PointCount();
protected:
virtual std::ostream& print(std::ostream& os) const;
float _x;
static int _point_count;
};
这个 class Point 在机器中将会如何表现呢?也即,我们如何模塑(modeling)出各种 data members 和 function members 呢?
C++ 对象模型:
virtual functions 则以两个步骤支持:
(1)每一个 class 产生出一堆指向 virtual functions 的指针,放在表格之中,这个表格称为 virtual table(vtbl)
(2)每一个 class object 被安插一个指针,指向相关的 virtual table。通常这个指针被称为 vptr。vptr 的设置(setting)和重置(resetting)都由每一个类的constructor、destructor、copy assignment 运算符自动完成。每一个 class 所关联的 type_info object (用以支持 runtime_type identification,RTTI)也经由 virtual table 被指出来,通常放在表格的第一个 slot。
此 C++ 对象模型的: