1. 多态的概念
1.1 概念
多态的概念:通俗来说,就是多态形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
例如:普通人买票,全价买票;学生买票,半价买票;军人买票,优先买票 。
2. 多态的定义及实现
2.1 多态的构成条件
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了 Person。Person对象买票全价,Student对象买票半价。
那么在继承中要构成多态还有两个条件:
1.必须通过基类的指针或者引用调用虚函数
2.被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
2.2 虚函数
虚函数:即被virtual修饰的成员函数称为虚函数。
2.3 虚函数的重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
派生类的虚函数不加virtual关键字时,也构成重写(因为继承后基类的虚函数被继承下来了,在派生类中依旧保持虚函数的属性),但是这种写法不是很规范,不建议使用。
完成虚函数的重写过后,通过父类指针或者父类的引用的调用,就可以指向谁调用谁。
通过上面的例子,会使有些同志觉得,那这不是和隐藏一样吗,无非是多个virtual?
(隐藏:父类和子类,有同名函数的时候,父类对象调用同名函数,调用到的是父类下的该函数;子类对象调用同名函数,调用到的是子类下的该函数。父子有同名函数,子想调父的该函数,就需要指定作用域。)
其实不是的。
①
下图中,去掉了virtual,父类和子类有同名函数BuyTicket,形成隐藏关系。隐藏是看类型,对象是什么类型,就调用该类型下的对应函数。
Person& p=s,即使进行了赋值类型转换,但是p是Person类型的,调用的就是“全价”。
②
下图,我们把virtual加上,子类重写了父类的虚函数,通过父类的指针或者父类的引用调用后,形成多态后,看的是指向的对象,指向谁的调用谁的。
Person& p=s,虽然p是Person类型,但是p指向的是s对象,所以调用的就是“半价”。
通过①和②例子,可以发现二者是不一样的。
而且隐藏并没有要求返回值和参数列表必须相同。
虚函数重写的两个例外:
1.协变
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或引用,称为协变。(实际中,其实不经常用到)
-------------------------------------------------------------------------------------------------------------------------
2. 析构函数的重写
基类和派生类的析构函数构成隐藏关系,虽然表面上他们名字不相同,但是实际上可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,所以他们本质上是一样的名字。所以如果在基类上加上virtual,就会构成虚函数。
析构函数最好是形成虚函数重写关系。原因如下。
所以在析构最好是虚函数状态。
2.4 C++11的override和final
1.final
修饰虚函数,表示虚函数不能再被重写
修饰类,表示该类不能被继承
2.override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写则编译报错。
2.5 重载、覆盖(重写)、隐藏(重定义)的对比
3. 抽象类
3.1 概念
在虚函数的后面写上=0,则这个函数称为纯虚函数。只要包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象,纯虚函数规范了派生类必须重写,另外纯虚函数更体现了接口继承。
---------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------
什么时候用抽象类?
3.2 接口继承是实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
4. 一写问答题
1.inline函数可以是虚函数吗?
答:可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去。
2. 静态成员可以是虚函数吗?
答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
3. 构造函数可以是虚函数吗?
答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。
4. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
答:可以,并且最好把基类的析构函数定义成虚函数。
5. 对象访问普通函数快还是虚函数更快?
答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
6. 虚函数表是在什么阶段生成的,存在哪的?
答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。
7. C++菱形继承的问题?虚继承的原理?
答:参考继承课件。注意这里不要把虚函数表和虚基表搞混了。
8. 什么是抽象类?抽象类的作用?
答:抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系。