多态分为两类
- 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
- 动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
虚函数
class Animal {
public:
//speak 函数就是虚函数
//函数前面加上 virtual 关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
virtual void speak() {
cout << "animal" << endl;
}
};
class Cat : public Animal {
public:
void speak() {
cout << "cat" << endl;
}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void method(Animal& animal)
{
animal.speak();
}
void test()
{
Cat cat;
method(cat);// 输出 animal
}
多态满足条件:
- 1、有继承关系
- 2、子类重写父类中的虚函数
多态使用:
父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
多态的原理剖析
当父类对象有一个虚函数时,它的对象会有一个 vfptr (虚函数(表)指针),vfptr 指向 vftable(虚函数表),而 vftable 存放的是虚函数的地址,当子类对象继承父类并重写虚函数时,子类对象的 vftable 存放的变成子类重写的函数的地址
纯虚函数与抽象类
纯虚函数语法:
virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
虚析构与纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
// 实现
类名::~类名(){}
总结:
-
虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
-
如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
-
拥有纯虚析构函数的类也属于抽象类