文章目录
- 1. 多态的概念
- 2. 多态的定义及实现
- 2.1多态的构成条件
- 2.2 虚函数
- 2.3 虚函数的重写
- 2.4 虚函数重写的两个例外
- 2.4.1 协变(基类与派生类虚函数返回值类型不同)
- 2.4.2 析构函数的重写(基类与派生类析构函数的名字不同)
- 2.5 重载、覆盖(重写)、隐藏(重定义)的对比
- 3. C++11 override 和 final
- 4. 抽象类
- 4.1 概念
- 4.2 接口继承和实现继承
1. 多态的概念
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
举个例子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。
2. 多态的定义及实现
2.1多态的构成条件
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。Person对象买票全价,Student对象买票半价。
那么在继承中要构成多态有两个条件:
1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
下面我们就用买票来举这个多态的例子。
2.2 虚函数
虚函数:即被virtual修饰的类成员函数称为虚函数。
2.3 虚函数的重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用void BuyTicket()。
然后我们必须通过基类的指针或者引用调用虚函数:
最后我们写一个菜单:
我们来看一下运行结果:
这就体现了不同的对象用基类的指针或引用去调用,可以产生不同的行为。
刚才我们说到了子类不加virtual也是可以的,我们来看一下这道题目:
A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确
那么它的结果是什么呢?
此时,子类去调用了test函数,test函数里面是A* this,而this指向的是B。
而this去调用func时,也就形成了多态的条件。
因为this指向的是B,所以它会指向子类的func函数。
但是这里我们要注意一点:普通函数的继承就是实现继承,子类继承了函数的实现,可以直接使用。但是虚函数的继承是接口继承,重写函数的实现。
因为它是接口继承,所以它继承的是A的数据,缺省值也是继承A的。
2.4 虚函数重写的两个例外
2.4.1 协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
举个例子:
我们看一下这个例子的运行情况:
虽然两个虚函数的返回值不同,但也调用了不同的行为。
但我们要注意:协变的规则:1.返回的必须是父子关系。
协变的规则:2.返回的必须是父子关系的指针或引用,对象不行。
2.4.2 析构函数的重写(基类与派生类析构函数的名字不同)
举个例子:
从结果中,我们可以看到两次delete,都只调用了父类的析构函数。没有去调用子类的析构。那么我们想析构student,但是没有析构,就可能造成内存泄漏。如果我们想这样正确的去调用,就要用到多态。
可能会有的同学会问:它们析构名字不一样,为什么会形成多态呢?
这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。
所以,如果设计一个类,可能会作为基类,其次析构函数最好定义为虚函数。
2.5 重载、覆盖(重写)、隐藏(重定义)的对比
3. C++11 override 和 final
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。
final:1. 修饰虚函数,表示该虚函数不能再被重写。
2. 修饰基类,表示该基类不能被继承。
override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
4. 抽象类
4.1 概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
派生类继承后也不能实例化出对象。
只有重写纯虚函数,派生类才能实例化出对象。
纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
虽然纯虚函数不能定义对象,但是可以定义指针或引用。
4.2 接口继承和实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。