← 目录 / 第十二章 · 类与面向对象 / 12.10 抽象类与纯虚函数

12.10 抽象类与纯虚函数

有时候父类只想定义一份"规范",强制所有子类必须实现某个功能,自己却不提供具体实现——这就是抽象类的用途。

本页目录
12.10.1 用插座类比理解抽象类

想象国家电气标准委员会制定了一份"插座规范":所有插座必须能供电,必须有接地保护,必须能承受额定电压……但这份规范本身不是一个具体的插座,你不能直接把它买回家用。

各大厂商根据这份规范,各自制造出五孔插座、USB 插座、防水插座……这些才是真正可以用的具体产品。

C++ 的抽象类正是这个"规范"的角色:它定义了子类必须实现哪些功能(接口),但自己不提供实现,也不能被直接实例化。

Shape(抽象类) 虚线边框表示"无法实例化" virtual Area() = 0 ← 纯虚函数 ❌ Shape s; 不能直接创建 Circle Area() { π×r² } Rectangle Area() { w×h } Triangle Area() { ½×b×h }
Shape 是抽象类(虚线边框),声明了"所有图形都要能算面积",但它自己不知道面积怎么算。
CircleRectangleTriangle 各自继承并实现 Area(),才是可以使用的具体类。
12.10.2 纯虚函数的语法

在虚函数的末尾加 = 0,它就变成了纯虚函数。含有至少一个纯虚函数的类,自动成为抽象类

C++ · 纯虚函数的写法
1class Shape {
2public:
3 virtual double Area() const = 0; // 纯虚函数:= 0 表示"只声明,不实现"
4 virtual void Draw() = 0; // 可以有多个纯虚函数
5 virtual ~Shape() {} // 析构函数仍然要实现(可以为空)
6};
7
8// 子类必须实现所有纯虚函数,否则子类也变成抽象类
9class Circle : public Shape {
10public:
11 double r;
12 Circle(double r) : r(r) {}
13 double Area() const override { return 3.14159 * r * r; }
14 void Draw() override { cout << "画一个圆" << endl; }
15};
12.10.3 抽象类不能实例化

含有纯虚函数的类不能直接创建对象——这是编译器强制的。但可以用抽象类的指针或引用来统一管理子类对象,这正是它最大的用途。

C++ · 抽象类只能用指针/引用,不能直接实例化
1// Shape s; ❌ 编译错误!抽象类不能实例化
2// Shape s = Circle; ❌ 同样不行
3
4Shape* p = new Circle(5.0); // ✅ 指针可以,指向子类对象
5cout << p->Area(); // 多态调用,输出圆的面积
6delete p;
7
8// 用 vector 统一管理多种图形
9vector<Shape*> shapes;
10shapes.push_back(new Circle(3.0));
11shapes.push_back(new Rectangle(4.0, 5.0));
12shapes.push_back(new Triangle(6.0, 3.0));
13
14for (Shape* s : shapes)
15 cout << s->Area() << endl; // 多态:各自调用自己的实现
16// 输出:28.27... 20.0 9.0
💡
这正是抽象类最强大的地方:用一个 vector<Shape*> 就能装下所有种类的图形,循环里一行代码对所有图形调用 Area()——每种图形各自执行自己的计算逻辑,完全不需要写 if-else 判断类型。这种设计让添加新图形类型变得极其容易,不需要修改任何已有代码。
12.10.4 完整示例:图形面积计算
C++ · 完整的抽象类示例
1#include <iostream>
2#include <vector>
3using namespace std;
4
5// ── 抽象基类 ──
6class Shape {
7public:
8 string name;
9 Shape(string n) : name(n) {}
10 virtual double Area() const = 0;
11 virtual double Perimeter() const = 0;
12 void Print() const { // 普通函数,可以有实现
13 cout << name << ": 面积=" << Area()
14 << " 周长=" << Perimeter() << endl;
15 }
16 virtual ~Shape() {}
17};
18
19// ── 具体子类 ──
20class Circle : public Shape {
21 double r;
22public:
23 Circle(double r) : Shape("圆形"), r(r) {}
24 double Area() const override { return 3.14159 * r * r; }
25 double Perimeter() const override { return 2 * 3.14159 * r; }
26};
27
28class Rectangle : public Shape {
29 double w, h;
30public:
31 Rectangle(double w, double h) : Shape("矩形"), w(w), h(h) {}
32 double Area() const override { return w * h; }
33 double Perimeter() const override { return 2 * (w + h); }
34};
35
36int main() {
37 vector<Shape*> shapes = {
38 new Circle(5),
39 new Rectangle(4, 6),
40 };
41 for (Shape* s : shapes) {
42 s->Print(); // 多态:各自调用正确的 Area 和 Perimeter
43 delete s;
44 }
45 // 输出:
46 // 圆形: 面积=78.5397 周长=31.4159
47 // 矩形: 面积=24 周长=20
48}
💡
注意第 12~15 行:Print() 是抽象类里的普通函数(不是纯虚函数),它可以有实现,并且在实现里调用了纯虚函数 Area()Perimeter()。这是一个经典的模板方法模式——父类定义流程,子类填充细节。
12.10.5 抽象类 vs 普通基类
📋 抽象类(含纯虚函数)
・不能直接创建对象
・子类必须实现所有纯虚函数,否则子类也变抽象类
・定义接口规范,强制约束子类行为
・适合"所有子类都必须有这个功能,但实现方式各不同"的场景
🧱 普通基类(含 virtual 函数)
・可以直接创建对象
・子类可以选择覆盖,也可以不覆盖(用父类默认实现)
・提供默认行为,子类按需定制
・适合"大多数子类行为相同,少数需要特殊处理"的场景
12.10.6 第十二章总结

恭喜你学完了第十二章的全部内容!我们从最基础的 class 定义出发,一步步走完了面向对象编程的完整体系。

📖
第十二章核心概念回顾
12.1struct vs class:唯一区别是默认访问权限,class 默认 private。
12.2 — 成员变量 + 成员函数 + this 指针:对象的基本结构。
12.3public / private / protected:封装的三把锁,getter/setter 提供受控的访问。
12.4 — 构造函数自动初始化,析构函数自动清理;初始化列表更高效。
12.5operator< 配合 sort 是竞赛中最常用的运算符重载。
12.6 — 浅拷贝只复制指针地址,含指针成员时必须自定义深拷贝;遵循三法则。
12.7static 成员属于整个类,不依赖任何对象;必须在类外定义。
12.8 — 子类继承父类,构造先父后子,析构先子后父;protected 专为继承设计。
12.9virtual 实现动态绑定;父类析构函数必须是虚的;用 override 防手误。
12.10 — 纯虚函数 = 0 定义接口规范,抽象类不能实例化,只能用指针/引用统一管理子类。
🎉
第十二章已完成 · 类与面向对象
你已经掌握了 C++ 面向对象编程的完整体系——从类的基本结构、封装、继承,到多态和抽象类设计。这些知识是理解大型工程代码和深入学习数据结构与设计模式的基础。