纯虚函数和抽象类
概念
首先引入“纯虚函数”和“抽象类”的概念,示例代码如下
代码语言:javascript复制#include<iostream>
using namespace std;
class Base //抽象类
{
public:
virtual void func() = 0; //纯虚函数
};
class Son1:public Base
{
public:
void func()
{
cout << "func()调用" << endl;
}
};
void test01()
{
//Base b;//抽象类无法实例化对象
Base* b = new Son1; //父类指针指向子类对象
b->func();
}
int main()
{
test01();
system("pause");
}
运行结果如下:
其中,virtual void func() = 0;
称为纯虚函数,也即是在成员函数的开头加上virtual
关键词,且没有函数实现,取而代之的是末尾的=0;
,而一旦类中有一个纯虚函数,则该类被称为抽象类,抽象类具有以下特点:
- 抽象类无法实例化对象
- 抽象类的子类必须重写父类中的纯虚函数,否则也为抽象类
目的
纯虚函数和抽象类的存在是为了更好的契合多态的思想。关于多态,简而言之就是用父类的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数
问题
纯虚函数的使用也会带来某些问题,由于实际调用时是父类指针指向子类对象,因此如果在子类中开辟了堆区数据,在析构时父类指针无法指向子类对象,即子类的析构函数不能够正常的被调用,这会带来内存泄漏的问题。例如下列代码:
代码语言:javascript复制#include<iostream>
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)
{
//子类中存在堆区数据
cName = new string(name);
cout << "Cat构造完成" << endl;
}
void speak()
{
cout << *cName << "猫在叫" << endl;
}
~Cat()
{
if (cName != NULL)
{
delete cName;
cName = NULL;
cout << "Cat析构完成" << endl;
}
}
string* cName;
};
void test01()
{
Animal* a = new Cat("Tom");
a->speak();
delete a;
}
int main()
{
test01();
system("pause");
}
运行结果如下:
可以看到子类Cat
的析构函数并未调用,要想解决该问题就需要继续引入“虚析构”与“纯虚析构”。
虚析构与纯虚析构
虚析构
虚析构的实现与虚函数一致,只需要在父类的析构函数前面加上virtual
关键字即可,只需要将前面代码中的Animal基类改成:
class Animal
{
public:
Animal()
{
cout << "Animal构造函数调用" << endl;
}
virtual ~Animal() //加virtual关键词变成虚析构
{
cout << "Animal虚析构函数调用" << endl;
}
virtual void speak() = 0;
};
此时运行结果为:
可以看到此时的Cat正常析构,堆区数据被正常释放!
纯虚析构
与纯虚函数实现类似,将Animal基类做如下改动:
代码语言:javascript复制class Animal
{
public:
Animal()
{
cout << "Animal构造函数调用" << endl;
}
virtual ~Animal() = 0; //纯虚析构
virtual void speak() = 0;
};
//必须类外实现
Animal::~Animal()
{
cout << "Animal纯虚析构函数调用" << endl;
}
值得注意的是,纯虚析构必须在类外具体实现,否则将无法完成编译。拥有纯虚析构的类也叫做抽象类,无法实例化对象。