多态分为两类:
1、静态多态——函数重载和运算符重载,复用函数名
2、动态多态——派生类和虚函数实行运行多态
静态多态和动态多态区别:
1、静态多态的函数地址早绑定——编译阶段确定函数地址
2、动态多态的函数地址晚绑定——运行阶段确定函数地址
动态多态的条件:
1、有继承关系
2、子类重写父类的虚函数
地址早绑定时:
class animal{
public:
void speak(){
cout<<"11111"<<endl;
}
};
地址晚绑定时:
class animal{
public:
virtual void speak(){
cout<<"11111"<<endl;
}
};
class cat : public animal{
public:
void speak(){
cout<<"cat11111"<<endl
}
}
void dospeak(animal &a){//父类做形参,可以直接传递子类
a.speak();
}//早绑定时,此时传递参数为cat类,那么一定输出11111
//晚绑定时,此时传递参数为cat类,那么一定输出cat11111
动态多态的底层原理 :
1、vfptr指向记录虚函数的地址
2、当子类重写了父类的虚函数,那么vfptr指向的虚函数地址覆盖为子函数(此时的子函数不写虚函数关键字也是虚函数)
class animal{
public:
void speak(){
cout<<1111<<endl;
}
};//类的函数和变量分开存储,函数只有一份;此时相当于空对象,为一字节
class animal{
public:
virtual void speak(){
cout<<1111<<endl;
}
};//此时增加了一个虚函数指针vfptr——占四个字节
纯虚函数和抽象类
纯虚函数:
virtual 返回值类型 函数名 (参数列表) = 0;//一般是基本不会被调用的父类虚函数
抽象类:
有纯虚函数的类叫抽象类
抽象类的特点:
1、无法实例化对象
2、子类必须重写抽象类的纯虚函数,否则也是抽象类
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构函数
解决方法:虚析构和纯虚析构
class animal{
public:
virtual void speak(){
cout<<"11111"<<endl;
}
};
class cat : public animal{
public:
void speak(){
cout<<*name<<" cat11111"<<endl
}
~cat(){
delete name;
name = NULL;
}
string *name;
}
animal * a = new cat("tom");
a->speak();
delete a;//父类指针在析构时,不会调用子类的析构函数,导致子类在堆区的数据发生内存泄漏
利用虚析构解决父类指针释放子类对象时不干净的问题
virtual animal::~animal(){
}
纯虚析构:
virtual animal::~animal() = 0;