C++中的抽象类 - 详解
一、什么叫做抽象类
1、 定义
抽象类,英文名称Abstract Class是指包含至少一个纯虚函数(Pure Virtual Function) 的类。纯虚函数是一种未提供具体实现、仅声明接口的虚函数,语法为:
virtual 返回类型 函数名(参数列表) = 0; // "=0" 表示纯虚函数
抽象类的核心作用是定义接口规范,强制派生类实现其声明的接口,自身无法实例化对象(不能创建抽象类的对象)。
一段比较专业化的解释,给大家看的云里雾里,继续阅读下,大家就会了解了。
下面我们一步一步拆解出来看
1.1 、是指包含至少一个纯虚函数(Pure Virtual Function) 的类
先来了解什么是纯虚函数,纯虚函数,的前提是必须是一个虚函数,即使用关键字Virtual修饰的函数。而纯这个性质,则使用赋值等于0的表达式来实现。
virtual 返回类型 函数名(参数列表) = 0; // "=0" 表示纯虚函数
如定义一个返回值int类型的,包含一个参数x的纯虚函数
virtual int PV_Fun(int x) = 0; // "=0" 表示纯虚函数
这里需要注意,虚函数从函数功能上来说,可以分为
(1)纯虚成员函数
(2)纯虚析构函数
1.2、纯虚函数是一种未提供具体实现、仅声明接口的虚函数
未提供具体实现,还是以1.1中的代码为例:
virtual int PV_Fun(int x) = 0;
注意看赋值号,=0,这是纯的定义,这里有C语言基础的同学就会问,这里虚纯函数怎么成为左值了?回到1.1中括号里补充的说明,这里的=不是赋值号,记住这点即可。
不提供具体实现,那么我定义这个函数的作用是什么喃?另外我需不需要在其他地方提供具体实现?
先回答,确实需要在其他地方提供具体实现。就是在这个类的子类中提供具体实现,看代码
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { // 实现基类的纯虚函数
return 3.14 * radius * radius;
}
};
double area() override { // 实现基类的纯虚函数
return 3.14 * radius * radius;
}这段代码就是提供了,具体的实现,再强调一遍,这里的定义可以在前面加上virtual或者不加,在{}前加上关键字override,即说明是对虚函数具体的实现。代码编译时会自动生成虚函数表。
此外,还有两点需要注意:
必须被继承:抽象类的意义在于通过派生类实现其纯虚函数,从而实现多态。
派生类需实现所有纯虚函数:若派生类未完全实现基类的纯虚函数,则该派生类仍为抽象类(也不能实例化)。
1.3 抽象类中提供了接口,这句话该如何理解
先看一段资料中的解释,“抽象类定义模块间的交互接口,确保不同实现类遵循统一规范”
我们还是以上面代码为例:
在图形库中,Shape作为抽象类定义area()计算体积、draw()画出图形等接口,派生类Circle、Rectangle分别实现具体逻辑;
这也很好理解,circle是圆形,面积计算公式是piR平方,Rectangle是矩形,面积计算公式是宽*长。具体画法上更是不同。所以这两个函数必须在具体的类中实现。
此时又会有人问?那只要定义一个虚函数,也就能实现这样的功能,为什么还要搞一个抽象类?还要搞一个纯虚函数。
这就涉及到,1.2最后讲解的两个注意点:

这两点语法就规定了,抽象类必须被继承,不然编译错误,派生类需实现所有纯虚函数。这两点就是不能虚函数无法比拟的。
以工作的场景为例:基类是领导,派生类是他底下的员工。使用虚函数实现多态,就像是领导事无巨细的管所有事务,它会给派生类说今天干什么事,然后自己干一遍,让员工跟着这个模板干。而抽象类的领导则是,我只管布置任务,具体怎么实现,它不会干预。本质上来说,这种不干预的机制下,只要员工能力合格,工作上的效率会更高。
1.4 抽象类的其他操作与应用
这一点很重要,我们之前说,抽象类无法被实例化,但是可万万没说,抽象类不能被定义为指针。直接看代码:
int main() {
Shape* shape = new Circle(2.0); // 基类指针指向派生类对象(多态)
cout << "Area: " << shape->area() << endl; // 调用Circle的area()
delete shape;
return 0;
}
Shape* shape则是定义了一个抽象类的指针,然后将这个基抽象类指向其派生类。。
以下内容抽象类,可以正常包含,需要注意!!
- 正常的成员变量、普通成员函数(虚或非虚)、静态成员;
- 嵌套其他抽象类、正常类;
- 包含结构体、共用体等复合类型。
二、抽象类中的比较特殊的纯虚析构函数
2.1. 定义
纯虚析构函数是指被声明为纯虚函数的析构函数,语法为:
class Base {
public:
virtual ~Base() = 0; // 纯虚析构函数
};
注意:纯虚析构函数必须提供定义(这是它与普通纯虚函数的关键区别),否则会导致链接错误。定义方式为类外实现:
Base::~Base() { // 纯虚析构函数的定义
// 释放基类资源(若有)
}
这里的代码,可能有点误导人,注意这个纯虚析构函数的定义,一定要放在class外,且不能放在其他class中
2.2. 核心特性
使类成为抽象类:包含纯虚析构函数的类是抽象类(即使没有其他纯虚函数),无法实例化。示例:
class Base { public: virtual ~Base() = 0; // 纯虚析构函数,使Base成为抽象类 }; Base::~Base() {} // 必须定义 int main() { Base b; // 编译错误:抽象类不能实例化 return 0; }同时具备虚析构函数的特性:纯虚析构函数本质是虚析构函数(即释放内存),因此在多态场景中,通过基类指针删除派生类对象时,会正确调用派生类析构函数,再调用基类析构函数(包括纯虚析构的定义),避免资源泄漏。
示例:
class Base { public: virtual ~Base() = 0; // 纯虚析构函数(同时是虚析构) }; Base::~Base() { // 定义纯虚析构 cout << "Base destructor called" << endl; } class Derived : public Base { private: int* data; public: Derived() : data(new int) {} ~Derived() override { // 派生类析构(自动为虚函数) delete data; cout << "Derived destructor called" << endl; } }; int main() { Base* ptr = new Derived(); // 基类指针指向派生类对象 delete ptr; // 多态删除 return 0; }输出结果:
Derived destructor called // 先调用派生类析构 Base destructor called // 再调用基类纯虚析构的定义
注意和构造函数的继承,不一样,构造是先调用基类,基类调用完成,再来调用派生类的构造函数。
此外,还需要注意:构造函数,是不存在“虚”的状态,更不存在“纯虚”状态。
2.3. 为什么需要纯虚析构函数?
主要用于两种场景的结合:
- 需要将类定义为抽象类(禁止实例化),但类中没有其他适合作为纯虚函数的接口(即类的核心逻辑不需要其他纯虚函数);
- 同时需要确保析构函数的多态性(避免派生类资源泄漏)。
例如,一个作为接口的基类,仅需要派生类实现构造逻辑,而无需其他接口,但又必须禁止基类实例化,此时可用纯虚析构函数。
4. 常见误区
- 误以为纯虚析构函数可以不定义:普通纯虚函数(如
virtual void f() = 0)可以不定义(由派生类实现),但纯虚析构函数必须定义。因为派生类的析构函数会自动调用基类析构函数,若基类纯虚析构无定义,链接时会报错(未找到函数实现)。 - 混淆纯虚析构与普通虚析构:纯虚析构是 “纯虚 + 虚析构” 的结合,既让类抽象,又保证析构多态;普通虚析构仅保证析构多态,不使类抽象。
三、抽象类与纯虚析构函数的关系
- 纯虚析构函数是抽象类的一种特殊情况:包含纯虚析构函数的类必然是抽象类;
- 抽象类不一定需要纯虚析构函数:多数抽象类通过其他纯虚函数(如
area()、run())成为抽象类,仅在特殊场景下使用纯虚析构。
总结
- 抽象类是含纯虚函数的类,用于定义接口,禁止实例化,需派生类实现接口;
- 纯虚析构函数是特殊的纯虚函数,既使类成为抽象类,又保证析构多态性,且必须提供定义;
- 两者结合可满足 “接口抽象 + 析构安全” 的设计需求,是 C++ 多态和资源管理的重要工具。

浙公网安备 33010602011771号