前言
封装、继承、多态是面向对象语言的三大特性,通过前面的学习我们已经学习了解了封装,现在让我们一起探寻一下继承的奥妙吧
内容摘要
本文内容包括继承的概念和定义、详细解释了继承基类访问限定符的变化、基类和派生类的赋值转化、继承中的作用域问题,派生类调用默认构造函数的具体方式、继承中包含友元关系派生类通过继承会不会继承这个友元关系、静态成员和基类派生类的关系、多继承的概念、由多继承引发的菱形继承的数据冗余和二义性的解决方法,并分析了其底层实现的原理。希望通过这篇文章,能够给大家带来收获,若发现问题欢迎评论区指出。
继承的概念和定义
继承的概念
继承是面向对象语言代码复用的重要手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,也就是子类(派生类)通过继承复用父类(基类)的属性和方法,从而实现代码的复用
通过代码理解继承的概念
当我们需要一个学校教务系统管理时,像姓名、年龄这种信息无论是教职工和学生都是必备的信息,这时我们就可以将这种必备信心进行单独一个类存放这些信息,其他类通过继承这个基类将这些信息拿到然后自己进行使用**(派生类通过继承基类从而复用基类的代码)**,就可以避免 student 和 teacher 中再将这些必备信息重新进行声明定义,减少了代码的冗余,是代码变得更简洁。
为什么要使用继承
通过使用继承,能够减少代码的冗余,增加程序猿的开发效率,可以使代码更简洁、易于维护和扩展。
继承的定义
注意:这里继承方式和我们之前的访问限定符是完全两码事
访问限定符
- public: 公有访问,允许类外和派生类进行访问
- protected:保护访问,允许派生类进行访问,但是不允许类外进行访问
- private: 私有访问,仅仅可以支持基类自己进行访问,类外和派生类都不能进行访问
继承方式(继承基类成员访问限定符的变化)
- public(公有继承):使用公有继承时,基类中的 public 和 protect 成员依然是public 和 protect,而private 不能进行访问
- protected(保护继承):使用保护继承时,基类中的 public 和 protect 都是protect,private成员依然不能进行访问
- private(保护继承):使用保护继承,基类中的基类中的 public 和 protect 都是private,基类中的所有成员都不能进行访问
在继承基类成员访问方式的变化,当基类中的成员通过继承方式进行继承时,优先级顺序尾 private > protected > public 但是基类中的保护成员在派生类中是不可以被派生类进行访问的,private继承,将基类中的public和protected成员都变成private成员,此时的private成员是派生类自己的成员,可以在派生类中进行访问
使用关键字class进行创建的基类的默认继承方式是private,使用关键字struct创建的基类的默认继承方式是public
基类和派生类的赋值转换
派生类可以给基类的对象、指针、引用进行赋值,这个过程称为切片
基类类不能直接给派生进行赋值,但是可以通过指针或引用的强制类型转化给派生类指针或引用进行赋值,但是此时基类的指针必须是指向派生类的否则会出现指针越界行为
继承中的作用域
基类和派生类都有相对应的作用域,子类中如果出现和父类同名的成员,子类不能再直接进行访问和父类同名的这个成员,此现象称为隐藏, 这里注意不是不再能访问基类中的这个成员了,而是不能直接访问,要想进行访问需要使用 基类名称+ 访问限定符的形式进行访问
派生类的中的默认成员函数
派生类中的成员进行初始化调用构造函数时,会将继承基类的部分调用基类的构造函数进行初始化,派生类没有继承本身的成员调用派生类自己的构造函数
派生类中调用拷贝构造时,也是继承基类的部分调用基类的拷贝构造,剩下的调用派生类自己的拷贝构造
赋值重载也是派生类中继承基类的调基类的赋值重,自己本身的调用派生类本身的赋值重载
在进行构造时,派生类继承基类,调用派生类对象时基类的构造函数先调用,先调用的后析构,派生类的在进行析构时先析构
析构函数在这里比较特殊,析构函数后续在多态部分构成重写,重写的条件之一就是函数名相同,这里编译器会自动将基类和派生类中的析构函数进行修饰成同名destrutor,在继承中的作用域那我们了解,派生类和基类成员同名会构成隐藏,派生类中继承基类的部分将不在调用基类的析构函数,派生类只调用派生类自己的析构函数,假如基类中有资源申请时,这时候就会造成内存泄漏。因此基类中的析构函数需要进行+virtul进行处理
继承与友元
友元不能通过继承进行传递,基类中的友元函数,派生类通过继承基类并不会继承基类的友元函数
继承和静态成员
基类中的静态成员也不会被派生类继承,整个继承体系中只有这一个静态成员,无论派生出多少子类,都只有一个静态成员。
从监视窗口可以看出,静态成员是不属于具体的哪个类的,无论是基类还是派生类中都没有静态成员这个成员变量,从继承来看,派生类通过继承基类并不是静态成员变量没有被继承,而是基类中进行声明的静态成员并不在基类中。原因如下
静态成员在内存中存储的位置是静态区存储区,而普通变量存储在类的实例中,静态成员在类中进行声明,但是不存在在类的实例中,这就使得,静态成员和类有着某种关联,但是不是类的某个实例化的对象,这就完成了类实例的所有对象都共享这个静态成员
复杂的菱形继承和菱形虚拟继承
多继承
多继承的概念
多继承的顾名思义就是一个派生类同时继承两个及以上的基类,这是这个派生类的继承行为称为多继承,先被继承的基类会先调用构造函数进行初始化
菱形继承
菱形继承是多继承的一种特殊形式,如上图所受,菱形继承是C++涉及初期的一个"坑",因为引入多继承的概念后出现了菱形继承这种继承结构,这种结构会造成数据冗余和二义性, 助教这个类继承了学生和老师,学生和老师又继承了人这个类,那么助教这个类中就会出现两套人的基本信息,乍一看还是挺合理的,助教有两个姓名,当学生时有一个姓名,当是老师时又有一个姓名,但是当我们进行身份验证时,我们到底是使用学生身份时的信息还是继承老师这部分的信息,这就造成了数据的二义性 ;我们再往广了看,人的基本信息还包括性别和年龄,当助教继承学生和老师时,这两套信息都包含年龄和性别,要是认为有两个姓名还是比较合理,那么性别和年龄是继承哪个类都是一样的这就造成了数据的冗余
菱形继承产生的数据冗余和二义性的处理方法
通过使用虚继承的形式进行解决
虚继承的实现方法
虚拟继承底层实现的原理
虚拟继承底层实现的原理是通过需基表进行实现,下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下 面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指 向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量 可以找到下面的A。