1、纯虚函数和抽象类
在之前发布的博客《深度剖析C++中多态的原理》一文中已经提到了多态的概念,即不同的对象去完成某个行为时会产生不同的状态。因此在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的函数,因此可以将虚函数改为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
抽象类就是指当一个类中有了纯虚函数或纯虚析构函数时(纯虚析构后面会有讲解),这个类就称作抽象类。抽象类有以下特点:
- 无法实例化对象(无论采用何种方式)
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
以下时代码示例,读者可自行运行查看
#include<iostream>
using namespace std;
class Base
{
public:
virtual void func() = 0;
};
class Son :public Base
{
public:
virtual void func()
{
cout << "func调用" << endl;
};
};
void test01()
{
//Base base;错误示例,抽象类无法实例化对象
Base* base = NULL;
//base = new Base;错误示例,抽象类无法实例化对象
base = new Son;//在堆区初始化一个Son类的对象,用base指对其进行维护
base->func();
delete base;//记得销毁
}
int main() {
test01();
system("pause");
return 0;
}
2、虚析构和纯虚析构
在使用多态时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,此时就用到了虚析构函数,只需将父类中的析构函数改为虚析构函数或者纯虚析构函数就可以解决父类指针无法释放子类对象的问题。
虚析构和纯虚析构的相同之处:
- 可以解决父类指针释放子类对象的问题
- 都需要有具体的函数实现
虚析构和纯虚析构的不同之处:如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名(){}`
纯虚析构语法: virtual ~类名() = 0; 类名::~类名(){}`
在纯虚析构语法中黄色字体部分在类外实现,以下为代码示例:
class Animal
{
public:
Animal()
{
cout << "Animal的构造函数" << endl;
}
//虚析构
/*virtual ~Animal()
{
cout << "Animal的析构函数" << endl;
}*/
virtual ~Animal() = 0;//纯虚析构函数
virtual void speak() = 0;
};
//类外实现纯虚析构函数
Animal:: ~Animal() {
cout << "Animal的纯虚析构函数" << endl;
}
注:无论是虚析构函数还是纯虚析构函数,都必须有具体的函数实现,因为父类中可能也有属性开辟到堆区,需要调用析构函数来释放堆区数据
下面通过代码运行结果来说明虚析构或纯虚析构的作用:
#include<iostream>
using namespace std;
#include<string>
class Animal
{
public:
Animal()
{
cout << "Animal的构造函数" << endl;
}
//虚析构
/* virtual ~Animal()
{
cout << "Animal的析构函数" << 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);
}
~Cat()
{
cout << "cat的析构函数" << endl;
if (this->m_name != NULL) {
delete m_name;
m_name = NULL;
}
}
virtual void speak()
{
cout << *m_name<<"小猫在说话" << endl;
}
public:
string *m_name;
};
void test01()
{
Animal* animal = new Cat("Tom");// 利用父类指针指向子类对象,代表多态的发生
animal->speak();
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
在上述代码中,子类Cat中有在堆区存放的成员变量m_name,当父类中的析构函数不是虚构函数或纯虚析构函数时,delete animal;这条语句是无法释放子类在堆区中的数据的,运行结果如下图所示 :在图片中可以看到子类的析构函数并未被调用。
将父类中的析构函数改为虚析构或纯虚析构函数后,父类指针在释放后(即执行 delete animal;语句后)便可以调用子类的析构函数,对子类在堆区中的数据进行释放,如下图所示:
以上就是我学习后总结的知识,如有不恰当的部分,欢迎指正!