这是一个C语言结构,需要打印该信息时需要另外定义接口:Point3d_print(const point3d *pd);
但在C++中,可能采用了封装形式:
像C++那样给数据进行class的封装之后,布局的成本到底增加了多少呢?答案是没有增加成本。
3个数据成员直接内涵在一个class对象里,就像C的struct一样,而成员函数虽然含在class声明里,却不在对象之中。
C++在布局以及存取时间上主要的额外负担是由virtual引起:
1. Virtual function:用以支持一个有效率的“执行期绑定”;
2. Virtual base class:用以实现“多次出现在继承体系中的base class,有一个单一而被共享的实体”;
在C++中,有2种类成员,静态的和非静态的;以及3种类成员函数,静态的、非静态的、虚拟的。
1 class Point 2 { 3 public: 4 Point(float xval); 5 virtual ~Point(); 6 7 float x() const; 8 static int PointCount(); 9 protected: 10 virtual ostream& print(ostream &os) const; 11 private: 12 float _x; 13 static int _pointCount; 14 };
-----------------------------------简单对象模型(没有被应用和实现)-----------------------------------
这个类的对象模型是一系列的slots,每一个slots指向一个members。members按照其声明次序,依次被指定一个slots。
每一个data member或function member都有自己的一个slot。
这是简单的对象模式,其中member都不放在对象中,只有指向member的指针才放在对象内。这么做避免了members的不同类型而导致的不同存储空间需求。
对象中的成员都是以slot索引值来寻址,其中_x成员的索引是6,_point_count的成员索引是7,所以一个对象的大小就很容易的被计算出来:
对象大小 = 指针大小 * 所声明的成员数量;
-----------------------------------表格驱动对象模型(没有被应用和实现)-----------------------------------
还是因为避免members的类型不同而导致的空间需求,另外种模型是将所有与members有关的信息抽象出来。
做成2个表:“数据成员表” 和 “函数成员表”,对象本身则内涵指向这2个表的指针:
1. 数据成员表保存数据,占用数据类型的相应内存空间;
2. 函数成员表保存slots,指向每一个函数地址;
虽然这个模型未能被采用,但virtual function却采用了这个模型。
-----------------------------------C++对象模型(被采用了)-----------------------------------
什么是C++模型呢?
原作者将简单对象模型进行了改动,并对内存空间和存取时间都做了优化。此模型中:
1. non-static data member被配置在每一个对象中;
2. static data member则被放置在对象之外;
3. non-static 和 static 的function members都被放在每个对象之外;
4. virtual functions则采用了表格驱动的模型:每一个类生成了一堆指向virtual function的指针,这些指针放在virtual functions table中,而对象则内部保存了一个指针,指向该table
优点在于:存取时间和空间的效率;
缺点在于:非静态数据成员有修改,则也需要重新编译;
C++支持单一继承:
class Library_materials {...};
class Book : public Liarary_materials {...};
class Rental_book : public Book {...};
C++支持多重继承:
class istream : public istream, public ostream {...};
虚拟继承:
class istream : virtual public ios {...};
class ostream : virtual public ios {...};
虚拟继承的情况下,基类无论被继承多少次(n个派生类),永远只会存在一个实体。
那么,对象模型对编程来说有什么影响?
对象模型的不同,导致:
“现有的程序代码必须修改” 或 “必须加入新的代码”;
1 X foobar() 2 { 3 X xx; 4 X *px = new X; 5 6 // foo()是一个虚函数 7 xx.foo(); 8 px->foo(); 9 10 delete px; 11 return xx; 12 }
这个函数可能在内部被转化为:
1 void foobar(X &_result) 2 { 3 //构造_result 4 _result.X::X(); 5 6 //扩展X *px = new X 7 px = _new(sizeof(X)); 8 if(px != 0) 9 px->X::X(); 10 11 //扩展xx.foo()但不使用virtual机制 12 foo(&_result); 13 14 //使用virtual机制扩展px->foo() 15 (*px->vtbl[2])(px) 16 17 //扩展delete px 18 if(px != 0) { 19 (*px->vtbl[1])(px); 20 _delete(px); 21 } 22 23 //不需使用named return statement 24 //不需要摧毁local object xx 25 return; 26 }
C++支持的设计模型:
1. 程序模型:和C一样的开发。就像字符串处理中,使用字符数组(char str[] = “abcdefg”)和字符指针(char *str)。
2. 数据抽象模型(ADT)。
3. 面向对象模型:数据和方法通过一个抽象的class封装起来。
1 //描述object:不确定类型 2 Librar_materials *px = retrieve_some_meterial(); 3 Librar_materials &rx = *px; 4 5 //描述已知类型 6 Librar_materials dx = *px;
指针px指向了一个未知类型的对象地址;
引用rx被赋予一个未知类型的对象地址;
这个对象可能是Librar_materials类型对象,也有可能是它的一个子类型;
但dx一定是Librar_materials类型的对象;
所以,对于对象的多态操作,要求对象必须通过一个"指针"或"引用"来存取(动态完成);
C++中,多态只存在于一个public class体系中,也就是说,px只能指向自我类型的对象,或者就是派生类对象。
C++支持多态的形式:
1. 隐含的转化操作:
shape *ps = new circle();
2. 通过virtual function机制:
ps->rotate;
3. 通过dynamic_cast和typeid运算符:
if(circle *pc = dynamic_cast<circle *> (ps))...
C++指针的类型:
对象指针?数据类型指针?模板数组指针?
指针都需要足够的内存来放置一个机器地址,而指向各不同类型的指针,在于其所对应的类型:
所以,一个指针可以指向一个地址(比如1000),单该地址的跨度空间(1000~1xxx?)是根据指针类型决定的。由此可见,一个void*类型的指针,确实能够指向一个地址,单无论如何都无法确认空间跨度,所以也无法用该指针来操作某个object。
所谓转型,并非改变一个指针所指向的地址,而只影响“该指针所指出的内存大小和内容”的解释方式。
加上多态之后.....:
1 class zooAnimal 2 { 3 public: 4 zooAnimal(); 5 virtual ~zooAnimal(); 6 virtual void rotate(); 7 protected: 8 int loc; 9 String name; 10 };
1 class bear : public zooAnimal 2 { 3 public: 4 bear(); 5 ~bear(); 6 void rotate(); 7 virtual void dance(); 8 protected: 9 enum Dances{...}; 10 Dances dances_known; 11 int cell_block; 12 } 13 bear b("youyou"); 14 bear *pb = &b; 15 bear &rb = *pb;
首先,对象b所占用的空间为 sizeof(zooAnimal) + sizeof(enum)(4字节) + sizeof(int)(4字节);
而指针pb自身只占用4个字节的地址空间,其指向zooAnimal类型地址的首地址;而引用rb被赋予了pb的地址,所以rb自身的地址和pb的地址是同一个地址;
1 bear b; 2 zooAnimal *pz = &b; 3 bear *pb = &b; 4 //那么指针pz和pb都是被指向了对象b的首地址 5 //他们区别是什么?
是的,区别就是涵盖的地址跨度不同,pb的跨度是整个bear对象,而pz则是bear对象的zooAnimal空间部分。当然,我们也不能通过pz指针来操作属于bear的任何成员。
除非我们通过virtual机制.....
1 //不合法:cell_block并不属于zooAnimal的一个member 2 //即使pz确实指向了bear对象的首地址 3 pz->cell_block; 4 5 //经过一个强制类型转换,可以操作! 6 //强制调整操作的内存空间跨度 7 ((bear *)pz)->cell_block; 8 9 //动态类型转换:dynamic_cast<> 10 //和上面的强制转换区别在于运行时期的转换,而非静态转换 11 if(bear *pb2 = dynamic_cast<bear *>(pz)) 12 pb2->cell_block; 13 14 //可以!cell_block就是bear类型的一个成员 15 pb->cell_block;
当我们写上:
pz->rotate();时,pz的类型将在编译时期决定。
在每一个执行点,编译器都会进行一个决定:pz所指定的对象类型决定了rotate()所调用的实体。类型信息的封装并不在pz中,而是在link中(在对象的vptr和vptbl中)。
之所以指针和引用能够支持多态,就是因为它们并不会引发内存中任何“与类型有关的内存委托操作”,会受到改变的只有它们所指向的内存“大小和内容解释方式”而已。
“与类型有关的内存委托操作”:当一个基类对象被直接初始化为一个基类对象时(其实就是基类指针被指向子类对象),子类对象会自动被执行切割,塞入较小的内存空间中。
意思就是说明该指针被初始化时就决定了对象空间大小和内容,这时候如果调用该指针进行操作,则只能操作基类的操作。(静态)
但如果声明了virtual,那么就只有当该指针被动态操作时(调用时),才会决定指针所指向的对象空间大小(就是该指针所指向的对象空间所记录的空间大小信息)——子类对象。
1 zooAnimal za; 2 zooAnimal *pza; 3 4 bear b; 5 panda *pp = new panda; 6 7 pza = &b;