C++进阶——多态

一、什么是多态

多态,简单来说,就是当不同类型的对象调用同一个函数时,会产生不同的反应。

二、多态构成条件

1、必须通过基类的引用或指针调用虚函数

2、调用的虚函数必须是被派生类重写了的

三、虚函数

1、什么是虚函数

被 virtual 修饰的成员函数就是虚函数。但要注意的是,虚函数的 virtual 跟继承里的 virtual 是任何没关系的。

2、虚函数的覆盖(重写)

(1)覆盖 VS 隐藏 VS 重载

i. 覆盖(重写):

派生类的函数直接把原先从基类继承下来的函数盖住了,就是说在因为派生类里已经没有基类的同名函数了,因此派生类里只能访问派生类的函数,不能访问从基类继承下来的函数。但是,需要注意的是,虚函数重写只会重写它的函数体,而它的参数列表、函数名还是用回基类的

ii. 隐藏(重定义):

派生类只是把从基类继承下来的函数隐藏了,但派生类里依旧存在从基类继承下来的函数(只是表面上看不见而已),并且派生类里是可以访问到从基类继承下来的函数的(只不过访问时要指定类域而已)。

iii. 重载:

函数重载表面上就是可见的,而且同时只会发生在同一个类域里。

(2)“三同”条件

即派生类某个成员函数的函数名、参数类型和顺序、返回类型跟基类某个成员函数的都相同,那派生类的这个成员函数就符合“三同”原则。

(3)虚函数覆盖的条件

只要符合三同原则,虚函数就会覆盖。

3、虚函数覆盖的三大例外

(1)析构函数

Q:为什么析构虚函数的函数名在编译时统一改为 destructor ?

A:因为继承有切片赋值操作的原因,虽然是基类的指针或引用调用析构函数,但这个指针或引用实际的类型即可能是基类,也可能是派生类。如果实际类型是基类,那基类对象调用基类的析构函数,那确实没问题;但如果实际类型是派生类的话,派生类对象调用基类的析构函数而不调用派生类的析构函数,这妥妥地内存泄漏啊!因此只有改为同一个函数名,使析构函数构成多态调用,那就算实际类型是派生类的基类指针或引用在调用析构函数时照样调的是派生类的析构函数。

(2)协变

当派生类的虚函数的返回类型是基类的引用或指针时,只要函数名、参数列表和基类的相同,那么照样构成覆盖。

(3)派生类重写的虚函数可不加 virtual

4、关键字 final & override

(1)final

修饰虚函数,表示该虚函数不能被继承

(2)override

检查派生类虚函数是否覆盖(重写)该虚函数,如果没有则编译报错

四、虚函数表

只有虚函数才会进入虚函数表。换句话说,虚函数表就是用来存储该类的虚函数的。且由于虚函数表不可修改性,虚函数表是储存在常量区的。

(1)单继承关系的虚函数表

单继承关系的虚函数表

(2)多继承关系的虚函数表

每继承一个类,就继承这个类的虚函数表,因此如果这个类继承了两个类,那么这个派生类就会有两张虚函数表。举个例子,假如 Derive 继承了 Base1 和 Base2,那么 Derive 也继承了 Base1 和 Base2 的虚函数表;如果 Derive 同时重写了 Base1 和 Base2 的虚函数,那么 Base1 和 Base2 的虚函数表都记录了 Derive 的虚函数。

C++进阶——多态_第1张图片

但是为什么这两个虚函数表里的 func1 的地址不一样呢,它们不是同一个函数吗?其实它们就是同一个函数,而且 base1 的虚函数表里的 func1 就是 func1 真正的地址。那么 base2 里 func1 的地址又是什么呢?其实啊,base2 里 func1 的地址就是为了调整 base2 类型指针的指向的,因为在继承里有了切片赋值操作,所以 base2 类型的指针赋值时并不会指向 Derive 里 Base1 的部分,因此需要一个函数来调整 base2 类型的指针指向 base1 部分,而这个错误的地址实际上就是用来调整 base2 指针的函数地址。 

你可能感兴趣的:(c++,开发语言)