多态
多态是C++面向对象三大特性之一。
多态分为两类:
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定------编译阶段确定函数地址
- 动态多态的函数地址晚绑定------运行阶段确定函数地址
cpp
#include<iostream>
using namespace std;
class Animal {
public:
//虚函数
virtual void speak() {
cout << "动物在说话" << endl;
}
};
class Cat : public Animal {
public:
void speak() {
cout << "小猫在喵喵" << endl;
}
};
class Dog : public Animal {
public:
void speak() {
cout << "小狗在汪汪" << endl;
}
};
//地址早绑定,在编译阶段确定函数地址
void doSpeak(Animal& animal) {
animal.speak();
}
int main() {
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
return 0;
}
动态多态满足条件:
- 有继承关系
- 子类重写父类的虚函数
重写:函数返回值类型 函数名称 参数列表 完全相同
动态多态使用:
父类的指针或引用,指向子类的对象。
当子类重写父类的虚函数时
子类中的虚函数表内部会替换成子类的虚函数地址
多态优点
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
开闭原则:对扩展进行开放,对修改进行关闭。
纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容。
因此可以将虚函数改为纯虚函数。
纯虚函数语法:
cpp
virtual 返回值类型 函数名(参数列表) = 0;
当类中有了纯虚函数,这个类也称为抽象类。
抽象类无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
由于传入的对象不同,一个接口有多种形态
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
纯虚析构需要声明也需要实现
有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
cpp
#include<iostream>
using namespace std;
class Animal {
public:
virtual void speak() = 0;
Animal() {
cout << "Animal构造函数调用" << endl;
}
//利用虚析构可以解决,父类指针释放子类对象时不干净的问题
/*virtual ~Animal() {
cout << "Animal虚析构函数调用" << endl;
}*/
virtual ~Animal() = 0;
};
Animal::~Animal() {
cout << "Animal纯虚析构函数调用" << endl;
}
class Cat : public Animal {
public:
Cat(string name) {
cout << "Cat构造函数调用" << endl;
m_Name = new string(name);
}
virtual void speak() {
cout << *m_Name << "小猫喵喵" << endl;
}
~Cat() {
if (m_Name != NULL) {
delete m_Name;
m_Name = NULL;
cout << "Cat析构函数调用" << endl;
}
}
string *m_Name;
};
int main() {
Animal* animal = new Cat("Tom");
animal->speak();
//父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,会产生内存泄漏
delete animal;
return 0;
}
虚析构或纯虚析构都是用来解决通过父类指针释放子类对象的问题。
如果子类没有堆区数据,可以不写虚析构或纯虚析构。
拥有纯虚析构函数的类也属于抽象类