1、问题分析
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。但是我们也有解决办法:将父类中的析构函数改为虚析构或者纯虚析构。当然再了解如何解决这个具体问题之前,还需要知道以下四点知识。
-
虚析构和纯虚析构共性:第一点是可以解决父类指针释放子类对象;第二点是都需要有具体的函数实现。
-
虚析构和纯虚析构区别:如果是纯虚析构,该类属于抽象类,无法实例化对象。
-
虚析构语法:
virtual~类名(){}
-
纯虚析构语法:
virtual~类名() = 0 ; 类名::~类名(){}
2、代码展现
那代码如何实现呢?是下面这段代码吗?
#include<iostream>
using namespace std;
class Anmial
{
//纯虚函数
virtual void speak() = 0;
};
class Cat :public Anmial
{
//子类重写父类的speak()函数,不要省略virtual,否则无法打印;
virtual void speak()
{
cout << "小猫在说话" << endl;
}
};
void test01()
{
//父类指针指向子类对象
Anmial* anmial = new Cat;
//anmial调用子类函数
anmial->speak();
//由于创建在堆区,所以需要程序员手动释放
delete anmial;
}
int main()
{
test01();
system("pause");
return 0;
}
当然不是这段代码,由于我的大意,忘记在Anmial 和Cat,这两个类中加上public,结果就出现报错。
那么这个报错解决了,再想一下,如果我们想给小猫起个名字,那代码应该怎么写呢?可以自己先尝试改一下,之后不妨看一下,下面这段代码!
#include<iostream>
using namespace std;
#include<string>
class Anmial
{
public:
Anmial()
{
cout << "父类Anmial的构造函数调用" << endl;
}
~Anmial()
{
cout << "父类Anmial的析构函数调用" << endl;
}
//纯虚函数
virtual void speak() = 0;
};
class Cat :public Anmial
{
public:
Cat(string name)
{
cout << "子类Cat的构造函数调用" << endl;
m_Name = new string(name);
}
//子类重写父类的speak()函数,不要省略virtual,否则无法打印;
virtual void speak()
{
cout << * m_Name<<"小猫在说话" << endl;
}
string* m_Name; //在堆区创建一个名字,用指针来维护它
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
};
void test01()
{
//父类指针指向子类对象
Anmial* anmial = new Cat("Tom");
//anmial调用speak函数
anmial->speak();
//由于创建在堆区,所以需要程序员手动释放
delete anmial;
}
int main()
{
test01();
system("pause");
return 0;
}
既然,我们已经写到这一步,为什么在VS运行结果却没有“子类Cat析构函数调用”这句话呢?
其实是这样的,父类指针再析构时,不会调用子类中析构函数,导致子类如果有堆区属性,会出现内存泄漏。既然说到这,我们也有解决办法,就是在父类析构函数前面virtual。如:virtual ~Anmail()。
3、完整的代码展示
#include<iostream>
using namespace std;
#include<string>
class Animal
{
public:
Animal()
{
cout << "父类Anmial的构造函数调用" << endl;
}
//虚析构 可以解决,父类指针释放子类对象时不干净的问题
/*virtual ~Animal() //
{
cout << "父类Anmial的析构函数调用" << endl;
}*/
//虚析构和纯虚析构只需要一个就可以了
//纯虚析构,需要声明,也需要实现
//有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
virtual ~Animal() = 0;
纯虚函数
virtual void speak() = 0;
};
Animal :: ~Animal()
{
cout << "animal纯虚析构函数调用" << endl;
}
class Cat :public Animal
{
public:
Cat(string name)
{
cout << "子类Cat的构造函数调用" << endl;
m_Name = new string(name);
}
// 子类重写父类的speak()函数,不要省略virtual,否则无法打印;
virtual void speak()
{
cout << * m_Name<<"小猫在说话" << endl;
}
string* m_Name; //在堆区创建一个名字,用指针来维护它
~Cat()
{
if (m_Name != NULL)
{
cout << "子类中Cat析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
};
void test01()
{
//父类指针指向子类对象
Animal* animal = new Cat("Tom");
anmial调用speak函数
animal->speak();
//由于创建在堆区,所以需要程序员手动释放
//父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
分析如下:
4、回顾
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象。
2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构。
3.拥有纯虚析构函数的类也属于抽象类。