1.什么是多态?
同一事物在不同场景下的表现形式
2.实现多态的条件?
(1)基类必须包含虚函数
(2)派生类必须对基类的虚函数进行重写
(3)必须通过基类对象的指针或者引用调用虚函数
注意:当通过基类的指针或者引用调用重写的虚函数时,指向基类就调用基类的虚函数,指向派生类就调用派生类的虚函数,这种现象就叫做多态;在代码编译阶段,编译器不能确定调用那个类的虚函数,只有在代码运行阶段才能确定到底应该调用哪个类的虚函数
3.什么是重写?
当在派生类中定义了一个与基类函数名相同,参数列表相同,返回值相同(协变除外)的虚函数时,那么子类的虚函数就重写了父类的虚函数
4.什么是协变?
构成重写有个例外,即返回值类型可以不同,但基类必须返回基类的指针或者引用,派生类必须返回派生类的指针或者引用
在实现行为模式时,用继承和多态搞定是没问题,但是在尝试使用泛型化的类来实现时,就需要了解重写中的协变与逆变的相关知识了
协变:从具体到抽象(从派生类到基类,关键字应使用out,相当于返回值)
逆变:从抽象到具体(从基类到派生类,关键字应使用in,相当于入参)
class泛型编程的时候不支持协变
5.另外一个特例,函数名可以不同:
但前提是虚函数必须是析构函数
6.基类和派生类虚函数的访问权限可以不同
7.确保构成重写的措施
实际中我们建议多使用纯虚函数+ overrid的方式来强制重写虚函数,因为虚函数的意义就是实现多态,如果没有重写,虚函数就没有意义;final 修饰基类的虚函数不能被派生类重写;override 修饰派生类虚函数强制完成重写(帮助检查派生类对基类的那个虚函数是否构成重写),如果没有重写会编译报错;但这两个关键字一般加在派生类虚函数的后面;如果将基类中的函数给成虚函数,即用virtual关键字修饰了,则在此基类的派生类中必须对该函数进行重写;
8.建议将基类中的析构函数设置成虚函数
原因是:如果派生类中管理了资源,则有可能造成派生类中资源泄露的问题。
9.如果重写失败,则基类和派生类的同名函数之间的关系构成同名隐藏(重定义)
10.抽象类---》包含纯虚函数的类称为抽象类;
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
11.多态的分类
两种类型:
静态多态:程序在编译期间确定函数的具体行为-----静态绑定(早绑定)
函数重载:(一种特殊的多态)
模版:
动态多态:程序在运行期间确定函数的具体行为-----动态绑定(晚绑定)
动态多态的实现条件:
1.基类中必须具有虚函数,在派生类中必须对基类的虚函数进行重写
2.必须通过基类对象的指针或者引用调用虚函数
表现:传递不同子类的对象,程序运行期间调用具体引用对象对应类的虚函数
以上两个条件必须同时满足,缺一不可
12.对象的类型:
静态类型:对象在声明时的类型,在编译期间可以确定的类型
动态类型:指针实际指向或者引用的具体类类型
13.涉及到重写:
如果要构成重写:构成重写的两个函数必须都是虚函数,并且一个在基类中,一个在派生类中构成重写的两个函数的函数名,参数列表,必须相同
例外:协变:基类虚函数返回基类对象的指针或引用,派生类的虚函数返回派生类对象的指针或者引用(返回值类型不同);析构函数:如果基类的析构函数是虚函数,但派生类中的析构函数也是虚函数,则基类和派生类中的两个析构函数构成重写,但他们的函数名不同(建议在继承体系中将派生类的析构函数设置成虚函数,派生类中管理了资源,基类的指针或者引用指向子类的对象)
基类和派生类的虚函数的访问权限可以不同(没有直接调用派生类中的析构函数,而是通过虚函数表找到派生类的对基类虚函数的重写函数;如果将基类的析构函数给成私有的,则报错,不可以,原因是编译期间会将指向派生类的指针认为是基类的指针)重写失败,则无法实现多态,有可能构成同名隐藏
14.让编译器检查派生类是否重写了基类的虚函数:
override:
C++98 中无法解决,但在C++11中可以通过override关键字修饰派生类中的虚函数,检查是否是基类同名函数的重写,如果未构成重写,则出错;(帮助程序员检查是否对基类中的某个虚函数重写了)
在基类中查找与override修饰的成员函数 原型相同的虚函数,如果有:编译成功;
没有找到:编译失败;
final:
final修饰类,表明该类不能被继承;
final修饰虚函数,表明该虚函数不能在子类中被继承
15.动态多态的实现原理:
普通的单继承:
带有虚函数的类与普通类对象的区别:
带有虚函数的类比普通类对象多了四个字节,存放指向虚表的指针,简称虚表指针,指向虚函数表格(虚表--->构建次序,按照虚函数在类中的声明次序存放在虚表中)
一个类只有一个虚表指针,即同一个类定义的多个对象共用同一份虚表,所有对象的前四个字节的内容都相同,但不能说明一个类只有一张虚表,一个类可能存在多张虚表
派生类不会与基类共享同一份虚表,只要类中有虚函数,编译器就会为这个类生成虚表,即类前四个字节中的内容不同,虚表指针不同;
派生类虚表的构建过程:
1.将基类虚表中虚函数拷贝一份放置到派生类的虚表中
2.如果派生类重写了基类的某个虚函数,用派生类自己的虚函数替换相同偏移量位置的基类虚函数
3.对于派生类新增加的虚函数按照其在类中的声明次序增加到派生类虚表的最后
16.多态的调用原理:
1.从对象的前四个字节中取虚表的地址
2.传递this指针
3.从虚表中取虚函数的地址
4.调用虚函数
17.子类对基类的继承方式:
public:is-a的关系,可以将子类对象看成是一个基类对象,子类对象可以给基类赋值,赋值兼容规则
protected:
子类和基类是has-a的关系,包含的关系、内嵌、内聚、组合关系,一个类中包含了另一个类的对象
继承体现了代码复用的思想,不建议用继承实现代码复用;可以在子类的protected中用基类Base b;定义一个基类对象,这样的话可以通过将b封装在函数中复用基类的函数;这样不会破坏类的封装性,这种方法称为组合;
继承实现代码复用的缺点:破坏了类的封装性(在子类中可以访问基类的protected成员),不好维护
18.怎么通过C语言实现多态??