加上封装后的布局成本
封装后没有增加布局成本。数据成员会内含在每一个类对象中,但是数据成员不会,每个非内联成。员函数只有一个函数实体。而只有一或零个定义的内联函数只在使用者身上产生函数实体。C++的布局成本和存取时间上的负担主要由虚机制引起,比如虚函数、虚基类啥的
C++对象模式
以下类
class Point
{
public:
print(float xval);
virtual ~Point();
float x() const;
static PointCount();
protected:
virtual ostream& print(ostream &os) const;
float _x;
static int _pint_count;
};
在机器中会怎样存储呢?
简单对象模式
每个对象都有一系列的槽,每个槽指向一个成员。注意成员不在对象中,而是指向成员的指针放在对象中
目的:降低C++编译器的设计复杂度
缺点:空间和执行期效率低
表格驱动对象模型(双表模型)
数据成员放一个表格,成员函数放一个表格。对象只含有指向两个表格的指针,数据成员表格包含的是数据本身,成员函数表格包含的是指向函数的指针。
目的:为了对所有类的所有对象都有一致的表达方式
C++对象模型
非静态数据成员在对象内,静态数据成员在对象外;静态和非静态成员函数都在对象外。
类产生一堆指向虚函数的指针放在一个表里,叫做vtbl,然后每个类对象有一个指针vptr,指向这个vtbl。
优点:空间、存取时间效率高
缺点:如果非静态数据成员有所更改,则应用程序代码得重新编译。
加上继承
虚拟继承中不管基类在派生链中被继承几次,都只会存在一个实体
ios
/ \
ostream istream
\ /
iostream
//iostream只存在一个ios
那么这种情况下一个派生类如何塑造自己基类的实体呢?在简单对象模型中,每一个基类可以被派生类对象中的一个槽指出,这个槽存有基类子对象的地址。另一种基表模型,每个对象都有一个基表,同时有一个指向基表的指针bptr。
关键字带来的差异
一般而言struct应该作为一个数据集合体使用,没有private、protected什么的,也就是纯C的用法。这种用途应该和C++的使用者自定义类型区分开来。
对象的差异
C++程序设计模型支持三种程序设计典范:
- 程序模型
C的东西C++也支持,比如char*字符串以及它的函数集(定义在C函数库中) - 抽象数据类型模型(ADT)
类以及类中的接口,比如string - 面向对象模型
此模型中有一些彼此相关的类型,通过一个抽象的基类(提供共通接口)被封装起来
不要混合使用三种典范:
/*
假设有类关系
Library_material
|
Book
*/
Book book;
Library_material thing1 = book;
Library_material &thing2 = book;
thing1.check_in();
thing2.check_in();
上面的例子中对于thing1把book的派生类部分切割了,只留下了基类的部分,调用的是Library_material的check_in(),反应的是ADT典范的行为;thing2通过引用调用了book的check_in(),才是面向对象程序设计多态性质的体现。
C++有三种方法支持多态
- 把派生类的指针转换为指向基类的指针
Library_material *p = new book(); - 使用虚函数机制
p->check_in() - 使用dynamic_cast和typeid运算符
if (Book *pb = dynamic_cast<Book*>(p))
需要多少内存才能表现一个类对象
非静态数据成员的总和,以及内存对齐进行的填充(32位计算机就要将需要的内存填充到4的倍数)最后加上支持虚机制而由内部产生的负担
指针的类型
一个指向类的指针和一个指向int的指针有什么不同吗?从需要多少内存的角度来讲没有区别,它们都需要一个字来存放一个地址。它们之间的差异主要在不同的指针类型会告诉编译器怎么解释被指出的内存大小及其内容
class Bear : public ZooAnimal //继承16B
{
public:
Bear();
~Bear();
//...
void rotate();
virtual void dance();
//...
protected:
enum Dance {...};
Dance dances_known; //4B
int cell_block; //4B
}
Bear b; //24字节
ZooAnimal *pz = &b; //4字节
Bear *pb = &b; //4字节
//虽然pz和pb都指向bear的首地址
//但是pb的类型告诉编译器,自己涵盖的地址包含整个Bear
//而pz的类型告诉编译器,自己涵盖的地址只包含Bear中的ZooAnimal部分
有没有想过为什么赋值操作为什么不能将右边对象的vptr赋给左边对象的vptr?
Bear b;
ZooAnimal za = b;
//这里为什么不能调用b的rotate
za.rotate();
因为编译器会在初始化和指定(将一个类对象指定给另一个类对象)之间做出选择,编译器确保如果za含有一个或以上的vptr,那些vptr的内容不会被b初始化或改变