关于对象

image

这是一个C语言结构,需要打印该信息时需要另外定义接口:Point3d_print(const point3d *pd);

但在C++中,可能采用了封装形式:

image

像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;

原文地址:https://www.cnblogs.com/davidsguo008/p/3645110.html