多态按照字面意思就是“多种状态”,可以简单地概括为“一个接口,多种状态”,程序在运行时才决定调用的函数内容,既同一个接口会根据传入的参数的个体差异,而采取不同的策略。
多态:同一调用语句在父类和子类间使用时具有不同的表现形式,可以使用同一段代码处理不同类型的对象,提高代码的复用性
多态的实现要有三个前提条件:
(1)要有公有继承
(2)基类引用/指针指向派生类对象
(3)派生类覆盖基类的成员函数
函数覆盖:
函数覆盖是通过虚函数实现的,用virtual关键字修饰成员函数,这样的成员函数就是虚函数,虚函数支持函数覆盖(不是函数隐藏)。函数覆盖是使用多态的前提
在C++11中可以通过override关键字来验证函数覆盖是否成功,只需要使用此关键字修饰派生类的新覆盖的函数即可。
虚函数的性质:
(1)虚函数具有传递性,当基类中某个成员函数设置为虚函数后,派生类中覆盖此函数的同名函数(函数名称相同、参数列表完全相同、返回值类型相关)——也自动变为虚函数
(2)只有成员函数和析构函数可以设置为虚函数
(3)如果成员函数声明与定义分离,只需要使用virtual修饰声明处即可
代码示例:
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat() // Qt Creator中虚函数使用斜体字
{
cout << "动物吃东西" << endl;
}
};
class Dog:public Animal
{
public:
void eat()
{
cout << "狗吃骨头" << endl;
}
};
int main()
{
Dog d;
d.eat();
d.Animal::eat(); // 调用基类被覆盖的函数
return 0;
}
多态的基本使用:
多态的使用可以让一个继承家族的类使用同一个函数,极大提升了代码编程效率,但是使用多态后,派生类对象也会舍弃其独有的功能。
代码示例:
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat() // Qt Creator中虚函数使用斜体字
{
cout << "动物吃东西" << endl;
}
};
class Dog:public Animal
{
public:
void eat()
{
cout << "狗吃骨头" << endl;
}
void guard() // 狗类新增非虚函数
{
cout << "狗能看门" << endl;
}
};
class Wolf:public Animal
{
public:
void eat()
{
cout << "狼吃肉" << endl;
}
};
class Husky:public Dog
{
public:
void eat() override // 可以验证覆盖的有效性
{
cout << "哈士奇吃家具" << endl;
}
};
// 基于引用的多态函数
void test_eat1(Animal& a)
{
a.eat();
}
// 基于指针的多态函数
void test_eat2(Animal* a)
{
a->eat();
}
int main()
{
Animal a;
Dog d;
Wolf w;
Husky h;
test_eat1(a); // 动物吃东西
test_eat1(d); // 狗吃骨头
test_eat1(w); // 狼吃肉
test_eat1(h); // 哈士奇吃家具
Animal* ap = new Animal;
Dog* dp = new Dog;
Wolf* wp = new Wolf;
Husky* hp = new Husky;
test_eat2(ap); // 动物吃东西
test_eat2(dp); // 狗吃骨头
test_eat2(wp); // 狼吃肉
test_eat2(hp); // 哈士奇吃家具
// 多态的本质
Animal& a0 = d;
a0.eat(); // 狗吃骨头
// a0.guard(); 使用多态时,不支持派生类独有的功能
return 0;
}
在设计类时如果不写析构函数,编译器也会自动生成一个空的析构函数,如果这个作为根源基类(根源基类没有基类),可能会在多态时存在内存泄露的隐患,因此建议一个类的析构函数统一手写并设置虚析构函数,除非这个类不会作为其他类的基类
动态类型绑定:
多态之所以可以生效,是因为动态类型绑定的原理
当一个类有虚函数时,这个类会生成一个虚函数表,记录所有虚函数,同时此类的对象中有一个隐藏的成员变量,虚函数表指针,这个成员变量指向本类的虚函数表
当使用了基类的引用或指针指向派生类对象时,编译器会自动生成一段检查对象真正类型的代码,在程序运行时,通过对象的虚函数表指针去查找表记录的虚函数的调用地址
使用多态会降低程序的运行效率