问题1:为什么要有虚析构的出现?
因为子类当中如果有开辟在堆区的数据,那么在进行父类的析构函数时,是不会进行子类的虚构函数的,那么子类开辟在堆区的数据就无法释放就会造成内存泄漏的问题,所以有了虚函数的出现。并且父类当中的虚函数是没有实际的意义的,所以我们通常将它写成纯虚析构。
问题2:虚析构和纯虚析构有什么相同之处
1.都可以解决内存泄露的问题2.都必须有具体的函数实现(因为可能父类中也有数据开辟到堆区需要进行内存的释放)
问题3:那虚析构和纯虚析构有什么不同?
不同之处在于,有纯虚析构的类属于抽象类,抽象类是无法实例化一个对象的
#include<iostream>//c++中标准的输入和输出流
using namespace std;//引用标准的名空间std
class animal
{public:
animal()
{
cout << "animal的构造函数调用" << endl;
}
virtual ~animal()
{
cout << "animal的析构函数调用" << endl;
}
virtual void speak() = 0;
string* n_name;
};
class cat:public animal
{
public:
cat(string name)
{
n_name = new string(name);//以name的这个数据在堆区开辟一块
//内存空间,因为new的是string类型的,所以返回的数据类型是一个
//string类型的指针,所以应该用一个指向字符串的指针的类型来接收
cout << "cat的构造函数的调用" << endl;
}
~cat()
{
cout << "cat的析构函数的调用" << endl;
if (n_name != NULL)
{
delete n_name;//将开辟在堆区的数据释放干净
//但是我们会发现如果不用纯虚析构函数的话,在析构父类对象
//的时候是不会析构子类对象的,所以子类开辟在堆区的数据会
//发生内存泄漏的情况
n_name = NULL;//再将这个指针置为空
}
}
void speak()
{
cout <<*n_name<< "小猫在说话" << endl;
}//因为n_name是一个指针,所以要进行解引用的操作
//解引用后出来后才是名字,否则是一个地址
};
void test01()
{
animal *ani=new cat("tom");//父类的指针指向子类的对象时,才考虑
//子类开辟在堆区的数据是否释放干净的情况,也就是说发生多态,且子类
//有开辟在堆区的数据时,才考虑用虚析构或者纯虚析构。
//如果子类没有堆区数据,那么可以不写虚析构或者纯虚析构
ani->speak();
delete ani;//开辟在堆区的数据手动开辟手动释放
}
int main(void)
{
test01();
system("pause");
//system("PAUSE")和system("pause")作用和效果一样,因为dos命令是不区分大小写的。
//该语句是暂停的意思:等待用户信号;不然控制台程序会一闪即过,你来不及看到执行结果
return 0;//程序正常运行返回一个0;
}
总结易错点
//1虚析构就是在该类的虚构函数的最前面加上一个virtual的关键字
//2.虚析构和纯虚析构的代码只能有一个
//3.如果将代码写为纯虚析构,那么在写它的申明的同时,还要在类外写它的实现
//在类外写它的实现时,必须要加上它的作用域(也就是类名)
//4.纯虚析构和纯虚函数不一样,纯虚函数只需要有一个申明即可,而纯虚析构
//还要在类外有一个它的具体实现
//5.有纯虚函数或者纯虚析构中的一个,该类就称做为抽象类(之所以称之为抽象类
//是因为无法实例化一个具体的对象);如果只写了虚析构,但是没有写出纯虚函数
//那么这个类不能称为抽象类,是可以实例化一个对象的