继承
表示方法
继承意义:减少重复代码
继承格式:class 子类(派生类): 继承方式 父类(基类) { 子类新成员 }
继承结构:两部分,一部分是继承父类的共性成员,一部分是自己类内新增的个性成员。
继承方式
public 、 protected 、 private 三种
public继承:但父类的private数据不可访问,子类原封不动继承父类的其他数据和数据权限
protected继承:父类中的private数据不可访问,父类中其他数据按protected权限继承。
private继承:父类中的private数据不可访问,父类中的其他数据以private类型继承。
虽然父类private访问不到,但仍然继承存下,只是被编译器隐藏了(利用开发人员命令提示工具可以查看这种对象模型),父类所有非静态成员都会被继承下去。
构造顺序 :先构造父类,在构造子类。析构相反。
同名成员(子类与父类):
直接访问是子类对象 (p1.age),加上作用域是父类对象(p1.base::age)。同名静态对象的处理和非静态对象一样,只是静态对象的访问有两种方式。
(子类中一旦出现父类同名函数,继承的父类同名函数就会被全部隐藏,无论有无参数。这时调用父类同名函数就必须需要作用域。)
菱形继承问题:继承过程为121的情况
以之前的知识,最底层类的两个父类中的同名变量会有二义性问题,并且这种二义性可能不是我们期待的(我们只需要一个数据,且这个数据很可能同样来自上一级父类的继承),还会造成资源浪费。
处理这种问题需要关键字virtual,在继承两个类时希望拥有一个共享的数据时,在两个同名成员类继承前加上 virtual 实现“虚继承”。(如class sheep :virtual public animal),其父类为虚基类。
虚继承会使继承的成员数据以虚基类指针(vbptr)指向的地址是虚基类表格(vbtable),虚基类表格会记录子类指针本身与父类数据的偏移量,根据偏移量能够将指针指向一个虚拟基类,虚拟基类里的唯一数据——该数据为同级类间的共享数据(这个共享数据当然要独立不同于父类中的数据)。
(我的理解:类似把继承的父类中数据拷贝构造出了一个虚拟基类,让两个同级类以虚基类指针和虚基类表相配合来指向这个虚拟基类,虚拟基类中的数据是共享的惟一的,供同级类使用)
———————————————————————————————————————————
多态
特点:一个接口,多个形态。多态性是将接口与实现进行分离。
应用:多态代码组织结构清晰、可读性强且利于前期与后期的扩展、维护,编写程序尽量用多态。
多态分为两类:静态多态与动态多态
静态多态 | 地址绑定早,编译阶段确定函数地址 | 函数重载,运算符重载,函数名复用 |
动态多态 | 地址绑定晚,运行阶段确定函数地址 | 派生类,虚函数 |
抽象类:有纯虚函数或纯虚析构的类则称为抽象类。
抽象类特点:无法实例化对象。且子类在继承后必须重写抽象父类里的纯虚函数(),否则子类也将是抽象类。(纯虚函数的目的是为了实现多态,如果不重写,多态就没有意义了)
动态多态(虚函数)
条件:①继承关系(子类会把父类的vfptr继承下来)②重写父类中虚函数(返回值形参名称都一样,只是加了virtual前缀。子类可写可不写)
使用:父类 使用 引用或指针 来执行子类对象
形式:virtual 特定的指针形式——虚函数指针(占4个字节)。指向虚函数表,表内存储虚函数地址。
原理:子类的虚函数地址会覆盖掉表内原来的父类虚函数地址,一旦覆盖发生,父类虚函数指针指向子类虚函数地址,就会发生多态。父类与子类之间的转换不需要强制类型转换,它们之间是互通的。(可以用父类指针指向子类对象)
纯虚函数
应用场景:多态思路构建程序,父类是一个高度抽象的类而不直接调用
纯虚函数格式: 正常虚函数类后 + “= 0”
虚析构与纯虚析构
应用场景:父类指针释放以后并不会释放子类对象(如果子类是堆区属性,就会内存泄漏)。 虚析构和纯虚析构可解决父类指针释放子类对象不干净的问题。当然,如果子类中没有堆区数据,也就没有必要使用虚析构了。
虚析构和虚函数的不同之处在于:虚析构的两种方法都需要有具体的函数实现,因为要通过函数实现达成“释放”父类成员和子类成员的目的,而不能仅仅是空的(没有意义)。
格式
①虚析构:在析构函数前加上virtual 例:virtual void ~Person(){函数体 delete .......}
②纯虚析构: 类内声明virtual void ~person() = 0; 类外定义实现 。
例: Person::~person(){ 函数体 delete ......}