多态是C++面向对象三大特性之一
多态分为两类:
1.静态多态:函数重载和运算符重载属于静态多态
2.动态多态:派生类与虚函数实现运行时多态
区别:
静态多态的函数地址早绑定-编译阶段确定函数地址
动态多态的函数地址晚绑定-运行阶段确定函数地址
class Animal{
public:
void speak(){
cout<<"动物在说话"<<endl;
}
}
class Cat:public Animal{
public:
void speak(){
cout<<"小猫在说话"<<endl;
}
}
//属于函数重载,地址早绑定,所以不管传入的是什么类,输出的都是Animal类
void Speak(Animal & animal){//Animal &animal=cat
animal.speak();
}
int main(){
Cat cat;
Speak(cat)
system("pause");
return 0;
}
上述代码输出的结果是:动物在说话
若想实现输出:小猫在说话。需要采用动态多态
class Animal{
public:
//加上virtual关键字,成为虚函数,这样在执行函数的时候,就可以实现地址晚绑定
virtual void speak(){
cout<<"动物在说话"<<endl;
}
}
//根据传入的不用类,就会执行该类中的同名函数
class Cat:public Animal{
public:
void speak(){
cout<<"小猫在说话"<<endl;
}
}
动态多态满足条件:
1.有继承关系
2.子类要重写父类中的虚函数,完全一样,virtual可加可不不加
动态多态的使用:
父类的指针或引用 指向子类对象
下面讲解一下多态的原理:
class Animal{
public:
virtual void speak(){
cout<<"动物在说话"<<endl;
}
}
首先看上面的代码,上面这个类一共占用4个字节,实际上,在Animal中有一个指针,名称为vfptr(虚函数指针),v-virtual f-function ptr-pointer,该指针指向vftable(虚函数表),在该表中储存 的是虚函数的地址,即&Animal::speak.
我们在定义它的一个子类:
class Cat:public Animal{
public:
//重写父类中的虚函数,virtual可加可不加
void speak(){
cout<<"小猫在说话"<<endl;
}
//若是在 Cat类中什么 都不写,那么Cat类中虚函数表记录的是 &Animal::speak
}
此时,在 Cat类中,在vftable(虚函数表)中储存 的地址既是&Cat::speak,而不再是&Animal::speak。也就能输出我们想要的结果。
在多态中,通常父类中的虚函数的实现都是毫无意义的,主要都是 调用子类中重写的内容。
因此可以将父类中的虚函数改为纯虚函数。
语法:virtual 返回值类型 函数名(参数列表)= 0;
当类中有了纯虚函数,这个类也称为抽象类。
抽象类特点:
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
**
虚析构和纯虚析构
**
多态使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法调用子类中的析构函数
解决方式:将父类中的析构函数改为虚析构或纯虚析构,这样父类指针在释放时就可以 调用子类的析构函数,继而能释放的开辟在子类中的空间。
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现
区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
如果写成纯虚函数,必须要在类外实现一下,即使是空实现。
class Base{
public:
virtual ~Base()=0;
}
Base::~Base(){
}