C++中的多态

一、定义

在不同继承关系的类对象中,去调用同一函数,产生了不同的行为,即为多态。

例如我们定义一个动物类,在其中定义一个cry()函数。由于动物分为很多种,所以我们在继承该类时还需要重新编写cry()函数,重新定义叫声;这种方式称为虚函数重写。

什么是虚函数?

多态性在C++中是通过虚函数实现的。

虚函数:就是父类允许被其子类重新定义的成员函数,而子类重新定义父类函数的做法,称为“覆盖”,或者称为“重写”。

子类重写父类中虚函数时,即使没有virtual声明,该重载函数也是虚函数。

虚函数:在类的成员函数前加virtual关键字。

什么是虚函数的重写?
虚函数的重写:派生类中有一个跟基类的完全相同的虚函数,我们就称子类的虚函数重写了基类的虚函数。“完全相同”是指:函数名、参数、返回值都相同。另外,虚函数的重写也叫做虚函数的覆盖。

class Animal //动物类
{
  public:
  virtual void cry()
   {
       cout << "动物在叫" << endl; //动物在叫
   }
};

class Dog : public Animal //狗
{
   public:
   virtual void cry() //子类完成对父类虚函数的重写
   {
       cout << "汪汪" << endl;//小狗再叫
   }
};


class Cat: public Animal //动物类
{
   public:
   virtual void cry() //子类完成对父类虚函数的重写
   {
       cout << "喵喵" << endl;//人在叫
   }
};

在继承中要构成多态还需要两个条件:
a. 调用函数的对象必须是指针或者引用
b. 被调用的函数必须是虚函数,且完成了虚函数的重写。

#include<iostream>
using namespace std;

class Animal //动物类
{
  public:
  virtual void cry()
   {
       cout << "动物在叫" << endl; //动物在叫
   }
};

class Dog : public Animal //狗
{
   public:
    void cry() //子类完成对父类虚函数的重写
   {
       cout << "汪汪" << endl;//小狗再叫
   }
};


class Cat: public Animal //动物类
{
   public:
   virtual void cry() //子类完成对父类虚函数的重写,且子函数中virtual可加可不加
   {
       cout << "喵喵" << endl;//人在叫
   }
};

void DoCry(Animal & animal)
 {
     animal.cry();
 }

void MakeCry(Animal * p)
 {
     p->cry();
 }

 int main()
{
   Animal st;
   Dog p;
   DoCry(st);//子类对象切片过去
   DoCry(p);//父类对象传地址

   Animal bt;
   Cat q;
   MakeCry(&bt);
   MakeCry(&q);
}

注意:

  • 不规范的重写行为。在派生类中重写的成员函数可以不加virtual关键字,也是构成重写,因为继承后基类的虚函数被继承下来,在派生类中依旧保持虚函数的属性,我们只是重写了它。这是非常不规范的,在平时尽量不要这样使用。若子类中的函数有virtual修饰,而父类中没有,则会构成函数隐藏。
  • 通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数,所以我们需要构构建虚函数。
  • C++中虚函数的唯一用处就是构成多态。C++提供多态的目的是:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量

二、纯虚函数与抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容 ;因此可以将虚函数声明为纯虚函数,语法格式为:

virtual 返回值类型 函数名 (函数参数) = 0;

纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。

最后的=0并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。

包含纯虚函数的类称为抽象类。之所以说它抽象,是因为它无法实例化,也就是无法创建对象。原因很明显,纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。抽象类通常是作为基类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。

#include<iostream>
using namespace std;

class Animal //动物类
{
  public:
  virtual void cry()=0;
};

class Dog : public Animal //狗
{
   public:
    void cry() //子类完成对父类虚函数的重写
   {
       cout << "汪汪" << endl;//小狗再叫
   }
};


class Cat: public Animal //动物类
{
   public:
   virtual void cry() //子类完成对父类虚函数的重写,且子函数中virtual可加可不加
   {
       cout << "喵喵" << endl;//人在叫
   }
};

void DoCry(Animal & animal)
 {
     animal.cry();
 }

void MakeCry(Animal * p)
 {
     p->cry();
 }

 int main()
{
   Dog p;
   DoCry(p);//父类对象传地址

   Cat q;
   MakeCry(&q);
}

注意: 

  •  一个纯虚函数就可以使类成为抽象基类,但是抽象基类中除了包含纯虚函数外,还可以包含其它的成员函数(虚函数或普通函数)和成员变量。
  • 只有类中的虚函数才能被声明为纯虚函数,普通成员函数和顶层函数均不能声明为纯虚函数。如下例所示:
//顶层函数不能被声明为纯虚函数
void fun() = 0;   //compile error
class base{
public :
    //普通成员函数不能被声明为纯虚函数
    void display() = 0;  //compile error
};

三、虚析构与纯虚析构

存在的目的:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

#include<iostream>
#include<string>
using namespace std;

class Animal {
    public:
         Animal()
         {
             cout << "Animal 构造函数调用!" << endl;
        }

     ~Animal()
         {
             cout << "Animal析构函数调用!" << endl;
        }
        virtual void Speak() = 0;
 };

class Cat : public Animal
 {
     public:
         Cat(string name)
         {
             cout << "Cat构造函数调用!" << endl;
              m_Name = new string(name);
         }

 virtual void Speak()
  {
      cout << *m_Name << "小猫在说话!" << endl;
    }

    ~Cat()
  {
     cout << "Cat析构函数调用!" << endl;
     if (this->m_Name != NULL)
        {
            delete m_Name;
     m_Name = NULL;
     }
  }

 public:
     string *m_Name;
    };


 void test01()
  {
       Animal *animal = new Cat("Tom");
       animal->Speak();
       delete animal;
   }

 int main()
{
      test01();
       return 0;
}






                

由上述程序可以发现子类的析构函数并没有调用。 父类指针在析构的时候,不会调用子类的析构函数,导致子类如果有堆区属性,会出现内存的泄露情况。如何解决此种问题呢?

1、将父类指针换成子类指针

#include<iostream>
#include<string>
using namespace std;

class Animal {
    public:
         Animal()
         {
             cout << "Animal 构造函数调用!" << endl;
        }

     ~Animal()
         {
             cout << "Animal析构函数调用!" << endl;
        }
        virtual void Speak() = 0;
 };

class Cat : public Animal
 {
     public:
         Cat(string name)
         {
             cout << "Cat构造函数调用!" << endl;
              m_Name = new string(name);
         }

 virtual void Speak()
  {
      cout << *m_Name << "小猫在说话!" << endl;
    }

    ~Cat()
  {
     cout << "Cat析构函数调用!" << endl;
     if (this->m_Name != NULL)
        {
            delete m_Name;
     m_Name = NULL;
     }
  }

 public:
     string *m_Name;
    };


 void test01()
  {
       Cat *animal = new Cat("Tom");//此处Animal换成Cat
       animal->Speak();
       delete animal;
   }

 int main()
{
      test01();
       return 0;
}

2.使用虚析构与纯虚构

#include<iostream>
#include<string>
using namespace std;

class Animal {
    public:
         Animal()
         {
             cout << "Animal 构造函数调用!" << endl;
        }


        virtual void Speak() = 0;
        //析构函数加上virtual关键字,变成虚析构函数
        virtual ~Animal()
        {
         cout << "Animal虚析构函数调用!" << endl;
        }
};

class Cat : public Animal
 {
     public:
         Cat(string name)
         {
             cout << "Cat构造函数调用!" << endl;
              m_Name = new string(name);
         }

 virtual void Speak()
  {
      cout << *m_Name << "小猫在说话!" << endl;
    }

    ~Cat()
  {
     cout << "Cat析构函数调用!" << endl;
     if (this->m_Name != NULL)
        {
            delete m_Name;
     m_Name = NULL;
     }
  }

 public:
     string *m_Name;
    };

 void test01()
  {
      Animal *animal = new Cat("Tom");
       animal->Speak();
          delete animal;
          }

int main()
 {
      test01();
      return 0;
}
#include<iostream>
#include<string>
using namespace std;

class Animal {
    public:
         Animal()
         {
             cout << "Animal 构造函数调用!" << endl;
        }


        virtual void Speak() = 0;
        //析构函数加上virtual关键字,变成虚析构函数
        //virtual ~Animal()
       // {
        // cout << "Animal虚析构函数调用!" << endl;
        //}
        virtual ~Animal() = 0;
         };
    Animal::~Animal()
     {
         cout << "Animal 纯虚析构函数调用!" << endl;
     };

//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。

class Cat : public Animal
 {
     public:
         Cat(string name)
         {
             cout << "Cat构造函数调用!" << endl;
              m_Name = new string(name);
         }

 virtual void Speak()
  {
      cout << *m_Name << "小猫在说话!" << endl;
    }

    ~Cat()
  {
     cout << "Cat析构函数调用!" << endl;
     if (this->m_Name != NULL)
        {
            delete m_Name;
     m_Name = NULL;
     }
  }

 public:
     string *m_Name;
    };

 void test01()
  {
      Animal *animal = new Cat("Tom");
       animal->Speak();
        //通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
         //怎么解决?给基类增加一个虚析构函数
          //虚析构函数就是用来解决通过父类指针释放子类对象
          delete animal;
          }

 int main()
  {
      test01();
       return 0;
}

注意:

  • 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  • 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  • 拥有纯虚析构函数的类也属于抽象类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值