C++--继承与多态

  • 虚函数
    在C++中,使用关键字virtual声明函数为虚函数,那么在对象创建调用构造函数里就会隐式的生成一个虚表指针(它被定义在对象首地址的前4个字节处,有虚函数的对象大小要多加4字节,就是多了一个指针的大小,因此虚函数必须是成员函数),它指向一个虚函数列表并初始化,这个虚函数列表其实就是一个指针数组,保存着这个对象所有虚函数的首地址。我们的多态(继承中)其实就是通过虚函数实现的,反过来虚函数就是为了多态而设定的。在继承中,虚函数的表项会被继承(重写了就会替换),但这个虚表指针不会被继承,这就使得同名函数可以有不同的实现,即多态。

  • 虚函数的访问
    当访问对象的虚函数时,就会根据对象的首地址,取出对应虚表元素(函数指针),得到对应的虚函数首地址,调用执行。这个过程是个间接调用过程,需要多次寻址才能完成。这种通过虚表间接寻址访问的方式其实只有在使用对象的指针或引用来调用虚函数时才会出现,而对于直接使用对象调用自身的虚函数时,因为已经明确是自身,根本没有构成多态性,所以编译器做了优化,没有必要去查表,而是像一般函数一样直接调用,提高程序执行效率。所以对于函数的访问一般就是有两种方式:(1)编译成全局的直接调用(2)通过虚表间接调用。

  • 继承
    直观点讲,继承就是派生类对象里拷贝了一份”无名的”父类(无名是指编译器要把本来属于基类成员的作用域替换为派生类自己的成员),除了构造与析构函数外,基类其他所有数据成员与函数成员都会继承下来,但派生类中只能访问继承下来的public,protected属性成员,这些限制是由编译器操作的,而内存中是全有保存的。对于派生类对象数据的内存表现则是:先存的基类数据,再存的子对象数据,最后存自己的数据。
    再来谈谈为什么构造与析构不能继承下来,个人认为主要是C++主张自己的东西自己管理,所以用层次更加清楚的结构。对于派生类对象的创建,先调用基类的构造函数,再子对象的构造函数,最后是自己的构造函数。而对应的释放则恰好相反。
    注释:(1)不管是基类成员函数还是派生类成员函数,在编译时都会编译成全局的 void fun(this, int a,…);这种形式(除虚函数),到时调用就是通过调用者对象this去匹配的,所以具体是调用的基类里的还是派生类里的就看调用者是谁。(2)变量的名字只在编译时用,运行的时候,不看变量名,在内存中也没有什么名字的概念,都是通过地址查找的。(3)类是一个抽象的概念,而对象才是具体实例,讨论内存表现是说对象,不同对象之间又是单独开辟内存空间的,不要把类与对象的概念混淆了。

  • 虚析构
    如果继承中基类析构没有加virtual,那么对于基类指针指派生类,在释放这个指针时,会根据指针类型(调用者)用全局的方式匹配调用基类的析构函数,而不会调用派生类中的析构函数,造成内存泄露危险。如果基类析构加了virtual,那么就是另一种调用析构函数的形式–虚函数列表。通过指针所指的虚表里去查找,所以这时释放就是调用的对应派生类中的析构函数。为了避免上面的情况,好的习惯是不管有没有继承,析构都加virtual,方便以后可能更新和维护。

  • 多重继承
    即一个派生类对应有多个基类的情况。这时注意一个情况,派生类有多少个父类(而且每个父类里都有virtual),那么它就对应有多少个虚表,即每个父类的虚表都在派生类中对应继承了一个。内存中表现是,按照继承顺序,虚表指针1–父类1数据–虚表指针2 –父类2数据…–自己数据(可能也要看具体什么编译器)。

  • 纯虚函数
    如virtual void fun() = NULL;通过虚函数实现,不过它只有声明没有定义,因此也就没有函数首地址,为了防止我们调用纯虚函数,编译器将虚表中本来要保存纯虚函数首地址的项替换成函数_purecall,用于结束程序,并发出错误编码信息0x19。带有纯虚函数的类叫做抽象类,不能被实例化。一般就是用来在基类中定义接口而具体的实现由它的派生类去做(体现多态)。
    注释:在基类中定义了纯虚函数接口,那么在其派生类中必须实现它,不然编译不会通过。

  • 虚基类
    发生在多重多级继承中,如B,C继承于A,而D又继承于B,C。正常情况下,D中是会有两份A的,这不合理。所以用到我们所说的虚基类,即在B,C继承方式前加virtual标记,这个关键字占4字节(64位机8字节),用于做标志,使D中只拷贝一份A对象。
    注释:派生类大小 = 所有父类大小 + 自己的大小(注意字节对齐规则)

  • 虚表指针指定时机
    在构造函数时指定了一次,在析构函数时又再指定了一次。为什么要在析构函数里再重新指定虚表指针呢?就是因为在派生类对象析构时,是先析构自己再基类对象,而虚表指针是指当前对象,当执行到父类的析构函数时,当然虚表也必须改为父类的虚表。为了防止这种未知情况,编译器统一在构造和析构时都重新指定虚表指针为当前对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值