何为多态
多态分为编译时多态(静态) 和运行时多态(动态)。
静态的多态 主要就是函数重载和函数模板,它们是通过传不同参数以调用不同函数,达到多种形态的效果。
动态的多态 比较复杂,也是本文的主要讲解目标。如果学过继承,那应该知道一个父类是可以拥有多个子类的,这些子类都会包含有父类的 "特征"。 比如说:一个叫做 "动物" 的父类,它里面有一个成员函数名为 "叫",功能是发出叫声,对于它的不同子类,如 "狗" 或 "猫" 的对象,在调用这个成员函数时,应该要执行出不同的效果,"狗" 调用 "叫" 应该输出 "汪汪","猫" 应该输出 "喵喵",这是通过对一个行为传不同的对象,达到多种形态。
(接下来的内容中,如果没有特别说明,在提及多态时,指的都是动态的多态。)
多态的必要条件
要实现多态,必须满足以下条件:
1.被调用的函数一定是虚函数。
2.必须是父类的指针或引用去调用。
虚函数 是实现多态最关键的部分之一,多态的逻辑全靠它来完善,为了实现多态,子类会对父类中的函数进行重写 (后面讲),重写的子类函数和父类函数的返回值 ,函数名,形参都会和父类相同,为了防止混乱。就要用到虚函数。
虚函数 和之前讲过的虚继承在表面上十分相似,都是用 virtual 进行修饰。

(要实现多态的父类函数必须设置为虚函数,子类可以设置也可以不设置。)
在有 virtual 修饰函数的父类中会生成一张虚函数表 ,里面存放着父类的虚函数的地址 。子类的同名函数无论有没有用 virtual 修饰,都会继承父类的的虚函数表 ,里面也包含着子类自己的虚函数地址。 子类会对父类的虚函数进行重写(构成重写要具备 函数名,返回值,参数 相同切父类函数为虚函数),并用重写后的函数的地址覆盖原本的函数地址。当使用父类指针或引用去调用时,调用到的就是经过重写后的函数,由此达成多态效果。

(图中展示的方法比较 low ,既然有多态其实可以用更优的方法去实现,但这样的例子比较适合理解。)
重写对于析构函数是特别:只要父类的析构函数是虚函数,甚至我们知道父类的析构和子类的析构的函数名是不同的(正常重写要求函数为虚函数且函数名相同),只要在子类中定义了析构函数,那么就构成重写,这是C++的规定,但没有规定如何实现,所以各大编译器各显神通,但一般都是通过名字修饰的方式来实现。
没有 virtual 修饰的析构只会调用父类的析构,会有内存泄漏:

有 virtual 修饰的析构,调用子类和父类的析构,没有内存泄漏:

还有一个值得注意的点是:重写后的函数使用的是自己的实现,但声明仍然是父类的声明。也就是说,如果父类和子类的函数参数中都设置缺省值,进行调用时仍是根据父类的缺省值来实现函数功能的。

重载,重写和隐藏的区分
在我的 C++ 继承 博客中讲到了隐藏 这个概念,隐藏其实不难理解,就是子类中和父类同名函数会将父类中的函数暂时"屏蔽",导致无法调用,但可以通过作用域解析符::来显式调用。
那为什么不在继承部分对它讲解呢?如果观察地比较仔细的话,应该已经发现刚才提到的重写 和隐藏在 "外形" 上好像长得一模一样,同时又和类和对象时学到的重载,为了更好地讲解和区分,所以把隐藏放到多态部分和另外两个一起讲解,先来一张表格清晰地展示它们的异同:

可以看到它们仨在 "函数名" 这个地方都是要求相同的,但重载的作用域和另外两个不同,所以很好区分,真正要区分的是重写和隐藏。
可能你想通过是否有 virtual 来区分重写和隐藏,但仔细看,有无 virtual 对隐藏是无关的,所以不能区分。那怎么办?可以这么记忆:重写 的条件非常严苛,必须要求函数名,返回值,参数 都相同且父类有 virtual ,才构成重写,协变另说,一旦有一个条件不符合,就是隐藏。
协变 是重写中的异类,协变的父类返回父类的指针/引用,子类则返回子类的指针/引用 。它的特殊好像把我刚才所述都推翻了,好像无法判断重写和隐藏了。但实际上只要把协变当作重写的一个变种,记作:除了普通重写和协变这种特殊重写之外,就是隐藏,就可以了。
不过从上面可以看出区分重写和隐藏还是比较麻烦的,而且如果你因为疏忽导致本来想写成重写的函数写成了隐藏,编译器可不会帮你报错,于是 C++11 引入 override 关键字来显示表示:我要重写。如果不构成重写会报错。

final 关键字在继承时就有提到,被它修饰的类会变成 "最终类" ,无法继承。在这里 final 也会发挥它的 "最终作用" :被它修饰的函数不能重写。

抽象类和纯虚函数
在现实世界中,有些东西是不能形成具体对象的,比如:冷热,高低。但一些东西可以有这些特性,我们把不能形成具体对象的东西认为是抽象的,C++中也有一种类叫做抽象类,它们是无法被实例化的,但是可以被继承。
抽象类的核心是纯虚函数,纯虚函数就是在虚函数后面加上=0。

抽象类的子类通常需要对抽象类的纯虚函数进行重写,但也可以不重写,这时子类也是抽象类。