多态的分类
- 静态多态:函数重载和运算符重载
- 动态多态:派生类和虚函数
静态多态和动态多态的区别:
-
静态多态:函数地址早绑定,编译阶段确定函数地址
-
动态多态:函数地址晚绑定,运行阶段确定函数地址
class animal{ public: void speak(){ cout<<"animai"<<endl; } }; class cat:public{ public: void speak(){ cout<<"cat"<<endl; } }; void test(animal &a){ a.speak(); } int main(){ Cat cat; test(cat); //输出animal,地址早绑定,test()在编译阶段已经确定了函数的地址 } 如果需要函数地址晚绑定:在基类的函数前面加上virtual class animal{ public: //虚函数 virtual void speak(){ cout<<"animai"<<endl; } };
动态多态的满足条件
- 继承关系
- 子类要重写父类的虚函数
- 父类的引用或指针指向子类的对象(如test()中形参是父类的引用,而传入的是子类对象)
多态的底层原理
纯虚函数和抽象类
纯虚函数:virtual 返回值类型 函数名(参数)=0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写纯虚函数,否则也属于抽象类
析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码
解决方式:将父类中的析构函数改为虚析构或纯虚析构
虚析构和纯虚析构拱形:
-
可以解决父类指针释放子类对象
-
都需要有具体的函数实现 虚析构和纯虚析构区别:
-
如果是纯虚析构,该类属于抽象类,无法实例化对象
class animal{ public: virtual void speak()=0; animal(){} ~animal(){} }; class cat:public animal{ public: cat(string name){ this.name=new string(name); } virtual void speak(){ cout<<*name<<": cat"<<endl; } string *name; ~cat(){ if(name!=NULL){ delete name; name=NULL; } } }; void test(){ animal* a=new Cat("tom"); a->speak(); delete a; } //父类指针在析构的时候,不会调用子类的析构函数,导致子类中堆区属性没有被释放,内存泄漏 //解决方式:在animal的析构函数前面加上virtual,就会先执行子类中的析构函数 //由于基类中可能也有堆区开辟的数据,因此纯虚析构中必须要有代码实现,eg: animal{ ~animal()=0; } animal::~animal(){ cout<<"xxx"<<endl; }