什么是继承?
简单来说,继承就是实现类代码复用的一种思想。
为什么要用继承?
不同类之间可能有相同/相似的类成员,把这部分成员提取出来,可以作为复用的基类。调高代码复用率。
继承的使用
我们定义了2个类,Father(父类/基类),Child(子类/派生类)
继承方式有三种,分别是pubilc/protected/private继承。
如何确定父类成员的使用权限?
取继承方式和父类成员使用权限中较小者来决定父类成员的使用权限。
(private<protected<public)
例如,Child以public方式继承Father,父类成员的private不能被访问,protected成员只能在子类中被访问,public可以在全局被访问。
关于切割/切片
1.派生类对象/指针/引用可以赋值给基类对象/指针/引用
2.基类指针/引用可以赋值给派生类指针/引用,但要强制类型转换
关于隐藏/重定义
1.若子类和父类存在同名成员,子类对象会优先访问子类中的同名成员
2.构成隐藏/重定义的条件,父类与子类中存在同名成员
关于子类的构造函数
//创建一个对象 Child c1
//程序运行结果
//子类的构造函数v2
//运行结果
结论:
1.不显示调用父类的构造函数,编译器会在初始化列表处自动调用;显示调用父类的构造函数,编译器不再调用。
2.编译器会先初始化父类,因为初始化列表的调用顺序和声明有关
关于子类的析构函数
//程序运行结果
//子类的析构函数v2
//运行结果
结论:
1.无论我们是否显示调用了析构函数,编译器都会调用析构函数
2.先构造的后析构,后构造的先析构
菱形继承中的二义性、数据冗余问题
什么是菱形继承?如下图
菱形继承的问题?
1.二义性
由于Child1与Child2都继承了Father的成员,Grandchild又继承自Child1、Child2,那么当Grandchild访问Father中的成员时就会产生二义性,究竟是访问Child1里的Father成员,还是Child2里的Father成员呢?
解决方案1,指明访问路径
为什么g1._name 没有报错?
因为grandchild类中有一个成员_name,编译器可以在g1类域中找到对应的变量,因而没有报错。
编译器寻找变量遵循“就近原则”
2.数据冗余
grandchild类对象存放了两个相同的Father
解决方案,虚拟继承
写法
此时,访问Father成员不再需要指定路径,也不再存有两份Father
实现原理
虚继承后,Child1对象和Child2对象将共用同一份Father,Child1、Child2此时不再存Father,
而是存了一个虚基表指针,指向一张虚基表,虚基表的内容是当前类地址距离基类Father的距离。
如图:(为了方便,新建了一个菱形继承的模型)
这是菱形继承后d的内存图
这是d中B的虚基表指针所指向的内容,是十六进制的14,也就是10进制的20,这意味着B与基类A相差20的距离
验证:0X001FFBA8-0x001FFB94=14
虚基表的作用?
//------------------------------------------------------------------------------------------------------------------------------//
2023.10.17补充
虚拟继承是专门针对菱形继承的
还是以上图为例,有以下语句 d._a=6;这用的上虚基表吗?用不上(对比一下d._a=6与b2->_a的汇编)
那么虚基表是干啥的呢?B是D的基类,当B类型的指针指向D类型时,如果该指针要访问_a就需要虚基表
无论b1是指向B类型还是指向D类型,他访问_a的操作都是一样的,即访问虚基表所指向的偏移量,找到对应的_a
一个类继承另一个类,是继承他的成员,两个类之间还是相互独立的。