抽象类与纯虚函数
更新时间:2026-08-27。本文是
languages/cpp/主题入门层第 30 篇。把虚函数写成= 0就成了纯虚函数,含纯虚函数的类叫抽象类——它规定了"子类必须实现什么",自己却无法实例化。这是 C++ 定义接口的方式,也是大型项目解耦的基石。
本文要回答的问题
- 纯虚函数和普通虚函数差在哪?
- 抽象类为什么不能实例化?
- 抽象类和"接口"是什么关系?
一、纯虚函数:只给签名,不给实现
class Shape {
public:
virtual double area() const = 0; // 纯虚函数:= 0,不写实现
};
class Circle : public Shape {
double r_;
public:
Circle(double r) : r_(r) {}
double area() const override { return 3.14159 * r_ * r_; } // 必须实现
};virtual ... = 0 就是纯虚函数——只声明"有什么操作",不给出实现。它的潜台词是:"Shape 这个概念没有统一的 area 算法,你来定义具体的形状类时,自己实现。"
含至少一个纯虚函数的类叫抽象类:
- 不能实例化:
Shape s;编译错误——"面积公式都没有,怎么算?" - 可以定义
Shape*指针 /Shape&引用(指向派生类对象); - 派生类必须实现所有纯虚函数,否则派生类自己也是抽象类。
二、抽象类 = 接口契约
抽象类规定的是"子类必须提供什么"。任何 Circle / Square 只要继承 Shape,就得给 area() 一个说法,否则连对象都建不出来——这是编译器帮你执行的接口契约。
void print_area(const Shape& s) { // 参数用抽象类的引用
std::cout << s.area() << "\n"; // 不管具体是 Circle 还是 Square
}
Circle c(5.0);
Square sq(3.0);
print_area(c); // 多态:调用各自实现
print_area(sq);这就是面向对象编程的核心组织方式:调用方只依赖抽象类(接口),不依赖具体类(实现)。以后新增 Triangle,print_area 一行不改——这叫开闭原则(对扩展开放、对修改封闭),大型项目解耦的根基。
三、纯虚析构函数:也要写实现
纯虚析构函数是个特例,值得单独说:
class Shape {
public:
virtual ~Shape() = 0; // 纯虚析构
};
Shape::~Shape() {} // 必须给实现!否则链接错误析构函数能写成纯虚(目的是把类变成抽象类,禁止实例化),但析构函数必须有函数体——因为派生类析构时会调用基类析构,没有实现就链接失败。写实现的方式是在类外 Shape::~Shape() {}。
四、抽象类 vs 纯接口类
C++ 没有 Java 的 interface 关键字,"接口"用抽象类表达。约定上有两种风格:
| 风格 | 写法 | 用途 |
|---|---|---|
| 抽象基类 | 部分纯虚 + 部分实现 + 成员数据 | 提供公共代码 + 留扩展点 |
| 纯接口类 | 全部纯虚 + 无成员数据 | 纯约定,Java 的 interface 等价物 |
// 纯接口风格:只定契约
class Drawable {
public:
virtual void draw() const = 0;
virtual ~Drawable() = default;
};接口类通常配合虚析构 + 全部纯虚使用。真实项目里两种都常见:工具库爱用抽象基类(省重复代码),插件系统、依赖倒置爱用纯接口(彻底解耦)。
五、和 C 对比
| 对比项 | C | C++ |
|---|---|---|
| 接口 | 函数指针结构体(手搓虚表) | 纯虚函数(语言级) |
| 强制实现 | 无(忘了就是空指针崩溃) | 派生类不实现就无法实例化 |
| 抽象类型 | void* + 约定 | 抽象类(类型安全) |
C 里做"接口"是函数指针结构体(内核 ops 结构体),漏实现一个指针就是空指针调用。C++ 的抽象类是语言级:漏实现 = 编译错误,类型安全、强制到位。
六、与本站主线衔接
- 抽象类的虚表里纯虚函数是空槽,机制见专家层虚函数与虚表;
- 下一篇:对象切片——多态最隐蔽的坑,按值传基类参数时发生了什么;
- 接口与依赖注入在大型项目里的应用,见高手层设计模式相关篇。
一句话总结
virtual f() = 0 是纯虚函数:只声明接口不提供实现,含它的类就是抽象类,不能实例化、但可以定义指针/引用指向派生类;派生类必须实现全部纯虚函数才能创建对象;抽象类把"子类必须提供什么"变成编译器强制执行的契约,调用方只依赖抽象类即实现解耦;纯虚析构函数必须有实现,否则链接报错。
上一篇:override 与 final 下一篇:对象切片与多态陷阱