0. 基本语法
多态是面向对象的三大特性之一。
多态:多态即调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
多态分为两类
- 静态多态:函数重载 和 运算符重载 属于静态多态,是对函数名的复用。
- 动态多态:派生类和虚函数实现运行时多态。
- 静态多态的函数地址早绑定, 编译阶段确定函数地址。
- 动态多态的函数地址晚绑定, 运行阶段确定函数地址。
引入虚函数,就是为了通过函数重写实现多态的效果。
动态多态需要满足的条件:
- 出现在继承当中
- 子类重写父类的虚函数,注意,必须是虚函数,重写非虚函数的话实质是函数覆盖了。
动态多态的使用条件:
- 父类的指针或者引用,指向了子类对象。
#include <iostream>
#include <cstdio>
using namespace std;
class base{
public:
virtual void speak(){
cout << "base在说话" << endl;
}
};
class son : public base {
public:
void speak(){ // 重写了父类的虚函数 实现了多态
cout << "son在说话" << endl;
}
};
// C++中 有自动的父子类 类型转换
void speak(base &person){ // 使用引用触发多态
person.speak();
}
int main(){
base b;
son s;
speak(s);
return 0;
}
1. 多态的原理
类中定义了虚函数的话,实例化一个对象 a 以后,就会在 a 中产生一个虚函数指针vfptr,指向虚函数表(vftable)。
简而言之,子类在虚函数表中替换了重写函数的那条记录。
2. 纯虚函数和抽象类
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0
,无需大括号。
- 当类中有了一旦有了纯虚函数,这个类就成为了抽象类
抽象类特点:
- 抽象类无法实例化对象,但可以指向子类对象。
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类,就无法实例化对象。
class base{
public:
virtual void speak() = 0; // 纯虚函数
int a;
};
class son : public base {
public:
void speak(){ // 重写了父类的虚函数 实现了多态
cout << "son在说话" << endl;
}
};
int main(){
base *b = new son; // 父类指针指向了子类 这就实现了多态
son ss; // 子类直接实例化出一个对象
return 0;
}
3. 虚析构和纯虚析构
作用:
- 解决通过父类指针无法释放子类对象的问题。
- 如果子类中没有堆区数据,可以不写虚析构或纯虚析构
- 拥有纯虚析构也属于抽象类,无法直接实例化对象。
问题:多态使用时(父类指针或引用指向了子类),如果子类中有属性开辟到了堆区,那么父类指针在释放时无法调用子类的析构代码。
解决办法:将父类中的析构函数改为虚的或者纯虚的。
测试代码:
#include <iostream>
#include <cstdio>
using namespace std;
class base{
public:
virtual void f() = 0; // 纯虚函数
base(){
cout << "base 构造函数" << endl;
}
~base(){
cout << "base析构" << endl;
}
};
class son : public base {
public:
void f(){
cout << "void f fuction in son" << endl;
}
son(){
cout << "son构造函数" << endl;
}
~son(){
cout << "son析构" << endl;
}
};
int main(){
base *b = new son;
delete b;
return 0;
}
输出:
base构造
son构造
base析构
在base析构前边,没有son析构,如果子类中在堆区开了空间,那就会造成内存泄漏。
此时,如果把base类中的析构函数加上virtual
修饰,变成一个虚函数,那么在释放父类指针的时候,就会触发子类的析构函数了,把子类释放掉。
- 一个纯虚的析构函数,除了有声明,还必须要有具体实现,类内声明,类外定义