c++对象(二)

来源:互联网 发布:网络上豆汁是什么意思 编辑:程序博客网 时间:2024/06/15 19:37

考虑下面这种多态情况:

    //thing1的类型已经确定为Library的一个object    Library thing1;     //class Book : public Library{...};    Book book;    //thing1 不是一个Book!    //book被裁切了。    thing1 = book;    //调用的是Library::check_in    thing1.check_in();    //OK:现在thing2参考到book    Library &thing2 = book;    //OK:现在引发的是Book::check_in()    thing2.check_in();

Book类继承自Library类,如果直接将子类Book的对象赋值给父类对象thing1,则thing1的类型在编译期间就已经确定,其所占的内存就是Library对象的内存,子类对象book会被裁切掉一部分来适应thing1的大小,thing1所调用的函数也只能是Library中的函数,不存在多态的可能。
只有通过pointer和reference的间接处理,才支持面向对象程序设计所需的多态性质。在以上的例子中,thing2的运用就是一个良好的例证。
在面向对象中,需要处理一个未知实例,它的类型虽然有所界定,却有无穷可能。这组类型受限于其继承体系,然而该体系理论上没有深度和广度的限制。原则上,被指定的object的真实类型在每一个特定执行点之前,是无法解析的。在c++中,只有通过pointers和references的操作才能够完成。举个例子:

    //retrieve_some_material函数返回Library的某个子类指针    Library *px = retrieve_some_material();     Library &rx = *px;    //描述已知物:不可能有令人惊讶的结果产生    Library dx = *px;

对于px和rx,你没有办法确定的说出其到底指向何种类型的objects,只能够说它要么是一个Library,要么是其一个子类型。但对于dx,我们可以明确的说它是Library的一个object。
在c++中,多态只存在于一个个的public class体系中。C++通过下列方法支持多态:
1.经由一组隐式的转换操作。例如把一个子类的指针转化为一个指向其父类的指针:
Library *ps = new Book();
2.经由虚函数机制:
ps->check_in();
3.经由dynamic_cast和typeid运算符:

if( Book *pc = dynamic_cast<Book*>(ps)) ...

多态的主要用途是经由一个共同的接口来影响类型的封装,这个接口通常被定义在一个抽象的base class中。例如在Library中,就可以为Book,Video,Puppet等子类型定义一个接口。这个共享接口是以虚函数机制引发的,它可以在执行期根据object的真正类型解析出到底是哪一个函数被调用。

那么,需要多少内存才能够表现一个class object? 一般而言要有:
1.其非静态成员函数的总和大小;
2.加上任何由于alignment的需求而填补上去的空间;
3.加上为了支持virtual而由内部产生的任何额外负担;通常为指向虚函数表的指针(vptr);

对于指针,不管它指向哪一种数据类型,指针本身所需的内存大小是固定的(通常是4字节)。但是,一个指向Library的指针是如何与一个指向整数的指针相区别的呢?
以内存需求的观点来看,没有什么不同!它们都需要足够的内存来放置一个机器地址(通常4字节)。“指向不同类型之各指针”间的差异,既不在其指针表示法不同,也不在其内容(代表一个地址)不同,而是在其所寻址出来的object类型不同。也就是说,“指针类型”会教导编译器如何解释某个特定地址中的内存内容及其大小:
int *pi; //对于一个指向地址1000的整数指针,在32位机器上,将涵盖地址空间1000~1003;
Library *px; //对于一个Library,如果Library的对象所需的内存空间为16个字节的话,则px指针将横跨地址1000~1015;

加上多态之后

class ZooAnimal{public:    ZooAnimal();    virtual ~ZooAnimal();    virtual void rotate();protected:    int loc;    string name;};class Bear : public ZooAnimal{public:    Bear();    ~Bear();    void rotate();    virtual void dance();protected:    enum Dances {...};    Dances dances_known;    int cell_block;};Bear b("Yogi");Bear *pb = &b;Bear &rb = *pb;

如上的继承体系中,b,pb,rb会有怎样的内存需求呢? 不管是pointer和references都只需要一个word的空间(在32位机器上是4-bytes)。Bear object需要24bytes,也就是父类ZooAnimal的16Bytes加上Bear所带来的8bytes;可能的布局如下:
这里写图片描述
我们假设Bear object放在地址1000处,一个Bear指针和一个ZooAnimal指针会有什么不同呢?

Bear b;ZooAnimal *pz = &b;Bear *pb = &b;

它们每个都指向Bear object的第一个byte。在上面提到过,“指针类型”会教导编译器如何解释某个特定地址中的内存内容及其大小。对于pb,所涵盖的地址包含整个Bear object,从1000~1024;对于pz,所涵盖的地址只包含Bear object中的父类ZooAnimal部分,从1000~1016; 因此,除了ZooAnimal中出现的members,你不能够通过pz来直接处理Bear的任何members。唯一例外是通过virtual机制:

    // 不合法:cell_block 不是ZooAnimal的一个member,    //虽然我们知道pz目前指向一个Bear Object.    pz->cell_block;    //Ok:经过一个显示的转换操作就没问题!    (static_cast< Bear *> (pz))->cell_block;    //下面这样更好,但它是一个run-time operation (成本较高)    if (Bear *pb2 = dynamic_cast<Bear*>(pz))    {        pb2->cell_block;    }    //Ok:因为cell_block是Bear的一个member。    pb->cell_block;

当我们写

pz->rotate(); 

时,在每一个执行点,pz所指向的object类型可以决定rotate()所调用的实例。类型信息的封装并不是维护与pz之中,而是维护于vptr和vptr所指的虚函数表之间。因此,pz->rotate()会执行Bear 中的rotate函数。

0 0
原创粉丝点击