博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++的继承与多态
阅读量:5918 次
发布时间:2019-06-19

本文共 2740 字,大约阅读时间需要 9 分钟。

hot3.png

◆ 概念介绍

继承:为了代码的重用,保留基类的原本结构,并新增派生类的部分,同时可能覆盖(overide)基类的某些成员。

多态:一种将不同的特殊行为和单个泛化记号相关联的能力,分为静态多态和动态多态。

◆ 继承:

一个派生类可以通过继承获得基类的所有成员,而无需再次定义它们。分为public、protected和private三种继承方式,前两种方式保持基类的所有成员的属性不变,且派生类可以访问基类的public和protected成员,但仍然不能访问基类的private成员;private继承将使得基类的所有成员在派生类中表现为private属性。

声明一个派生类对象,即在构造派生类对象时,遵循基类的接口,构造基类子对象,构造派生类增加的部分。其中的组成由下图所示:

当出现菱形继承时,例如下图所示:

要构造一个SleepSofa对象,就要构造一个Sofa和一个Bed子对象,这其中又同时构造了两次Furniture对象,这是不合理的。因此Bed和Sofa类要对Furniture类进行虚继承(virtual public Furniture)来避免这种状况。

 

◆ 多态:

静态多态:在编译时期就已经确定了的行为,例如带变量的宏,模板,函数重载,运算符重载,拷贝构造等。

动态多态:在运行时期才能确定调用的行为。例如虚函数调用机制。本部分主要讨论的是动态多态。虚函数是实现动态多态的机制,其核心理念就是通过基类指针来访问派生类定义的成员。成员函数在基类为虚函数时,在派生类同样也是虚函数。纯虚函数是指不希望基类对象调用的成员函数,需要派生类覆盖实现这样的纯虚函数。(注:如果某个成员函数在基类中没有用virtual关键字修饰,即普通函数,而在派生类中却又有完全相同的成员函数声明,两个函数即使有相同的名字和相同的参数类型与数量,这两个函数也是完全不同的函数,因为类的作用域不同)

虚函数表(vtable):每个类都拥有一个虚函数表,虚函数表中罗列了该类中所有虚函数的地址,排列顺序按声明顺序排列,例如这样两个类

复制代码
class Base{    virtual void f() {}    virtual void g() {}    //其他成员};Base b;
复制代码

复制代码
class Derive : public Base{    void f() {}    virtual void d() {}    //其他成员};Derive d;
复制代码

 

虚表指针(vptr):每个类有一个虚表指针,当利用一个基类的指针绑定基类或者派生类对象时,程序运行时调用某个虚函数成员,会根据对象的类型去初始化虚指针,从而虚表指针会从正确的虚函数表中寻找对应的函数进行动态绑定,因此可以达到从基类指针调用派生类成员的效果。

那么为什么需要虚指针和虚函数表来实现动态多态呢?因为无论是什么函数,包括类内的虚函数和非虚函数,都会储存在内存中的代码段。但是当编译器在编译时,就可以确定普通函数和非虚函数的入口地址,以及其调用的信息,所以这指的是常量指针。当遇到动态多态时,虚函数真正的入口地址的指针要在运行时根据对象的类型才能确定,所以要通过虚指针从虚函数表中找虚函数对应的入口地址。

当然,用基类指针绑定的子类对象,只能通过这个基类指针调用基类中的成员,因为作用域仅限于基类的子对象,子类新增的部分是看不见的。

总结为下面这个例程:

复制代码
#include 
using std::cout;using std::endl;class Base{ public: void fun() { cout << "Base::fun()" << endl; } virtual void vfun() { cout << "Base::virtual fun()" << endl; }};class Derive : public Base{ public: void fun() { cout << "Derive::fun()" << endl; } virtual void vfun() { cout << "Derive::virtual fun()" << endl; } void dfun() { cout << "Derive::dfun()" << endl; }};int main(){ Base* bp = new Base(); Base* dp = new Derive(); bp->fun(); bp->vfun(); dp->fun(); dp->vfun(); //dp->dfun(); //编译错误:基类指针指向子类中基类的子对象 //不能看到子类的成员 delete bp; delete dp; return 0;}
复制代码

输出为:

可以看出,bp绑定一个基类对象,调用自己的成员无异议;dp绑定的是一个子类对象,因此调用fun()时,由于dp是一个基类指针,作用域在于基类中,所以调用的是基类的fun(),而调用vfun()是通过动态绑定调用虚函数表中被子类覆盖的Derive::vfun(),而如果要调用dfun()时则会出现编译错误,因为子类独有成员基类指针不可见。

注:在解有关动态多态的题时,只要把握住一点:这个指针指向的到底是基类对象还是子类对象,如果是基类对象,则调用基类的成员函数,如果是子类对象,则要考虑到这个虚成员函数是否被子类中的成员覆盖掉,即是否产生了动态绑定。另外还有一点,从子类对象强制类型转换为基类对象是允许的,而相反地要从基类对象强制转换成子类对象是错误的(编译不通过)。

复制代码
Base* dp1 = new Derive(); Derive* dp2 = (Derive*) dp1; //基类指针指向的是子类对象,可以强制转化为子类指针Base* bp1 = new Base();Derive* bp2 = (Base*) bp1; //错误,[Error] invalid conversion from 'Base*' to 'Derive*' [-fpermissive]                           //基类指针指向的是基类对象,不能强制转化为子类指针
复制代码

转载于:https://my.oschina.net/u/920274/blog/3017375

你可能感兴趣的文章
十年学会编程
查看>>
【leetcode】84. Largest Rectangle in Histogram 最大面积的覆盖矩阵
查看>>
如何使 Laravel 项目中的 URL 更友好化
查看>>
bmc-watchdog: fiid_obj_get: 'timer_state': data not available
查看>>
find与xargs的用法
查看>>
还在十字路口的运营商,数字化转型或许只缺一个云开放实验室
查看>>
算法学习之路|A除以B
查看>>
mac php 图片验证码无法显示问题,gd库没有freetype的问题
查看>>
有那么一些想法哪
查看>>
配置性能警报、熟练安装与使用PCAnyWhere远程控制工具
查看>>
python基础
查看>>
MATLAB实现系统传递函数模型的建立与转换
查看>>
Python中的字典及举例
查看>>
js 中时间格式化的几种方法
查看>>
程序的本质在于逻辑
查看>>
没有脱离实践能力的程序设计基础
查看>>
内核中的UDP socket流程(7)——udp_sendmsg
查看>>
LoadRunner levels of integration with web pages
查看>>
模拟MBR扇区故障
查看>>
lzg_ad:Regsvr32.exe实用小技巧
查看>>