多态的分类

  • 静态多态:函数重载和运算符重载
  • 动态多态:派生类和虚函数

静态多态和动态多态的区别:

  • 静态多态:函数地址早绑定,编译阶段确定函数地址

  • 动态多态:函数地址晚绑定,运行阶段确定函数地址

      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;
              }
      };
      
    

动态多态的满足条件

  1. 继承关系
  2. 子类要重写父类的虚函数
  3. 父类的引用或指针指向子类的对象(如test()中形参是父类的引用,而传入的是子类对象)

多态的底层原理

alt

纯虚函数和抽象类

纯虚函数: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;
      }