C++梁哥笔记day15
类与对象
一、非自动继承的函数
不是所有的函数都能自动从基类继承到派生类中。构造函数和析构函数用来处理对象的创建和析构操作,构造和析构函数只知道对他们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建。另外operator=
也不能被继承,因为它完成类似构造函数的行为,父类创建的对象=知道怎么取赋值,但是子类中有自己新的成员,子类创建的对象=是不知道怎么去赋值的。在继承的过程中,如果没有创建这些函数,编译器会自动生成他们,默认的浅拷贝。
二、子类中静态成员与父类同名分析
2.1 静态成员函数和非静态成员变量
1 | class Base{ |
运行结果:
2.2 静态成员函数和非静态成员函数
1 | class Base{ |
运行结果:
三、多继承
3.1 多继承概念
我们可以从一个类继承,我们也可以同时从多个类继承,这就是多继承。但是由于多继承非常受争议,从多个类继承可能会导致函数、变量等同名导致较多的歧义。
一般我们要减少使用多继承,所有的多继承都可以使用单继承来完成。
多继承格式:
1 | class 子类名:继承方式 父类1,继承方式 父类2,继承方式 父类3···{ |
案例1:
1 | class Base1{ |
运行结果:
多继承会带来一些二义性的问题,如果两个基类中有同名的函数或者变量,那么通过派生类对象去访问这个函数或者变量的时候就不能明确到底调用从基类1继承的版本还是从基类2中继承的版本?
解决方法就是加作用域显示指定调用哪个基类的版本。
3.2 菱形继承(具有公共祖先)
两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石型继承。
这种继承所带来的问题:
- 羊继承了动物的数据和函数,驼同样继承了动物的数据和函数,当草泥马调用数据或者函数时,就会产生二义性。
- 草泥马继承自动物的数据和函数继承了两份,其实我们应该清楚,这份数据或函数我们只需要一份就可以。
上述问题如何解决?对于调用二义性,那么可通过指定调用那么基类的方式即加作用域来解决;那么重复继承怎么解决呢?对于这种菱形继承所带来的两个问题,C++为我们提供了一种方式,采用虚基类。
普通继承方式下利用作用域解决问题1:
1 | class Animal{ |
运行结果:
3.3 菱形继承在普通继承方式下的内存布局
利用vs studio查看Animal类
1 | class Animal{ |
Sheep类
1 | class Sheep:public Animal{ |
Tuo类
1 | class Tuo:public Animal{ |
SheepTuo类
1 | class SheepTuo:public Sheep,public Tuo{ |
3.4 虚继承 解决菱形继承的问题
加virtual
修饰继承方式
1 | //虚继承格式 |
3.5 菱形继承在虚继承方式下的内存布局
Animal类
1 | class Animal{ |
Sheep类
1 | class Sheep:virtual public Animal{ |
Tuo类
1 | class Tuo:virtual public Animal{ |
SheepTuo类
1 | class SheepTuo:public Sheep,public Tuo{ |
通过内存图,Animal是菱形最顶层的类,内存布局图没有发生改变。Sheep和Tuo通过虚继承的方式派生自Animal,从这两个类的内存布局图中可以看出编译器为我们的对象中增加了一个vbptr(virtual base pointer),vbptr指向了一张表,这张表保存了当前的虚指针相对于虚基类Animal的首地址的偏移量
。SheepTuo派生于Sheep和Tuo,继承了两个基类的vbptr指针,并调整了vbptr与虚基类的首地址的偏移量。
由此可见编译器帮我们做了一些幕后工作,是的这种菱形问题在继承时候能,只继承一份数据,并且解决了二义性的问题。现在模型就变成了Sheep、Tuo和SheepTuo三个类共享了一份Animal数据。
当使用虚继承时,虚基类是被共享的,也就是在继承体系中无论被继承多少次,类内存模型中只会出现一个虚基类的子对象(这和多继承是完全不同的)。即使共享虚基类,但是必须要有一个类来完成基类的初始化(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化呢?C++标准中选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),但是虚基类的初始化是由最后的子类完成,其他的初始化语句都不会调用。
vbptr
(虚基类指针)其中v是virtual虚 b是base基类 ptr是指针vbtable
(虚基类表)vbptr
指向虚基类表 虚基类表存放的是vbptr与虚基类的首地址的偏移量
总结:之所以产生vbptr
和vbtable
的目的是保证不管继承多少个虚基类,数据只有一份。
3.6 通过C语言的方式去操作虚继承(了解)
1 |
|
3.7 虚继承总结
注意:虚继承只能解决具备公有祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承带来的二义性问题。
工程开发中真正意义上的多继承是几乎不被使用的,因为多重继承带来的代码复杂性远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法上,任何多继承都可以用单继承来代替。