多态的概念
多肽在宏观上就是对某件事情的产生的多种行为;理论上多态就是多种形态。代码层面多态是重写函数的实现。
虚函数
在函数前面加上virtual就是虚函数了
虚函数的重写/覆盖的条件
1.两个虚函数分别位于基类和派生类的作用域
2.函数名/参数类型/返回值(协变除外)一样
3.两个都是虚函数
协变
协变是虚函数重写的一个特例,当两个虚函数的返回值是为任意父子关系的指针,此时也是虚函数重写。
func函数就构成了协变
虚函数的接口继承(构成多态是有效)
当父类的函数是虚函数时,子类通过继承后,会将父类虚函数的声明继承下来。
如果形成多态,B中的就是
virtual void print(int val = 0)
形成多态的条件
1.完成虚函数的重写/覆盖
2.通过父类指针/父类的引用调用虚函数
为什么通过父类对象调用不行呢?
当我们用子类通过切片给父类时,虚函数表是不能拷贝给父类的,因为如果把虚函数表拷贝给父类后,当我们调用成员函数就无法确定调用的是父类的还是子类的。
多态在析构函数的运用
如图场景析构函数如果没有构成多态的话,析构行为就会发生错误
所以在继承里讲过析构函数会被编译器认为都是destrutor,将析构函数写成虚函数,这样的话在调用析构函数时就会构成多态,从而成功的析构。
纯虚函数
在函数末尾加上=0就是纯虚函数;
抽象类
含有纯虚函数的类叫做抽象类。
抽象类不能实例化出对象,强制性要求子类重写虚函数,否则会编译报错。
抽象类可以有构造,拷贝构造,赋值重载,析构。
如图C就是抽象类,继承C的类必须对print进行重写,否在是无法实例化出对象。
多态的原理
在类中如果写了虚函数,则会生成一个虚函数指针数组来存放虚函数的地址,而类中则会有_vfptr指针指向这个虚函数指针数组。
单继承
多继承
C类继承了A类和B类后,对A的func1进行了重写,从上图可以看到重写后,C中A的func1变成C重写的;这样当我们用A*(指向C类对象的)指针调用func1,就是C重写的了,这样就实现了多态。
注意:
1.先继承的类在上面,上图先继承A,所以A就在上面
2.当我们在C类中写了A和B中都没有的虚函数时,虚函数会存储在优先继承的类的虚函数表内,可以通过内存查看,监视窗口看不到。
3.虚函数表指针时在对象调用构造函数时完成初始化的
4.虚函数表在编译期间就确定好了
5.虚函数表存在于代码段
如何查看虚函数
通过上面的接口就可以查看虚函数表内的虚函数了。
当A和B里面有相同的函数,C对这个函数进行了重写会怎么样呢?
从上图我们可以发现A中的func1和B中的func1的地址不同,但是实际上A和B调用的是同一个func1,因为编译器对B进行了封装,那又为什么要封装呢,由于当我们用B*指针(指向的是C类中B这一部分)访问时this指针并不是指向C类的起始位置,所以要修正this指针的位置。
注意:当A和B都有相同的虚函数,C继承了A和B,C一定要对这个虚函数进行重写,不然编译器会报函数不明确的错误
菱形继承和多态