
在现实世界中,我们常通过 “抽象” 简化复杂事物。例如,“动物” 是一个抽象概念,它不指代具体的猫或狗,但定义了所有动物的共同行为(如 “进食”“移动”)。这种思维迁移到编程领域,就形成了面向对象的 抽象(Abstraction)特性:通过定义抽象类和纯虚函数,隐藏具体实现细节,仅暴露必要接口,使代码更易维护、扩展和复用。
抽象是面向对象编程(OOP)的四大特性之一(其他为封装、继承、多态),其核心是:
通过抽象类和纯虚函数,定义一组 “必须实现的功能”,但不提供具体实现。派生类必须根据自身特性完成这些功能的实现。
抽象的作用主要体现在三方面:
在 C++ 中,抽象通过 抽象类(Abstract Class)和纯虚函数(Pure Virtual Function) 实现:
①纯虚函数:强制实现的 “契约”
纯虚函数是在基类中声明但不提供实现的虚函数,语法为 virtual 函数签名 = 0;。其作用是强制派生类必须重写该函数,否则派生类仍为抽象类(无法实例化)。
②抽象类:包含纯虚函数的类
包含至少一个纯虚函数的类称为抽象类。抽象类无法直接实例化(不能创建对象),只能作为基类被继承,由派生类提供纯虚函数的具体实现。
语法示例:图形抽象类
假设我们要设计一个图形库,所有图形(圆形、矩形、三角形等)必须支持 “计算面积” 和 “绘制” 功能。此时可定义抽象类 Shape:
#include <iostream>
#include <string>
using namespace std;
// 抽象类:图形
class Shape {
public:
// 纯虚函数:获取图形名称
virtual string getName() const = 0;
// 纯虚函数:计算面积
virtual double getArea() const = 0;
// 纯虚函数:绘制图形(抽象行为)
virtual void draw() const = 0;
// 虚析构函数(抽象类必须声明)
virtual ~Shape() {}
};Shape 是抽象类,因为包含 3 个纯虚函数(getName()、getArea()、draw())。Shape s; 会编译错误)。派生类必须实现抽象类的所有纯虚函数,否则仍为抽象类。以下是两个具体实现:
示例 1:圆形类(Circle)
class Circle : public Shape {
private:
double radius; // 半径
public:
// 构造函数
Circle(double r) : radius(r) {}
// 实现纯虚函数:获取名称
string getName() const override {
return "Circle";
}
// 实现纯虚函数:计算面积(πr²)
double getArea() const override {
return 3.14159 * radius * radius;
}
// 实现纯虚函数:绘制图形(控制台输出)
void draw() const override {
cout << "Drawing a circle with radius " << radius << endl;
}
};示例 2:矩形类(Rectangle)
class Rectangle : public Shape {
private:
double width; // 宽
double height; // 高
public:
Rectangle(double w, double h) : width(w), height(h) {}
string getName() const override {
return "Rectangle";
}
double getArea() const override {
return width * height;
}
void draw() const override {
cout << "Drawing a rectangle with width " << width
<< " and height " << height << endl;
}
};通过抽象类的指针或引用,可以统一操作所有派生类对象,实现多态。例如:
void printShapeInfo(const Shape& shape) {
cout << "Name: " << shape.getName()
<< ", Area: " << shape.getArea() << endl;
shape.draw();
cout << "------------------------" << endl;
}
int main() {
// 抽象类指针指向派生类对象(多态)
Shape* shape1 = new Circle(5);
Shape* shape2 = new Rectangle(4, 6);
printShapeInfo(*shape1);
printShapeInfo(*shape2);
// 释放内存(虚析构函数确保正确释放)
delete shape1;
delete shape2;
return 0;
}运行结果:

virtual ~Shape() {})。否则,通过基类指针删除派生类对象时,只会调用基类析构函数,导致派生类资源未释放(内存泄漏)。在 Java 或 C# 中,“接口(Interface)” 是完全抽象的类(所有方法都是抽象的)。C++ 虽无 “接口” 关键字,但可通过纯抽象类(所有成员函数都是纯虚函数)模拟接口。
纯抽象类是指所有成员函数都是纯虚函数,且没有任何成员变量的抽象类。它定义了一组 “必须实现的功能”,但不提供任何实现细节,是最严格的抽象形式。
假设需要设计一个跨平台的设备驱动框架,不同设备(如打印机、扫描仪)需要实现统一的接口。此时可定义纯抽象类 DeviceDriver:
// 纯抽象类:设备驱动接口
class DeviceDriver {
public:
// 纯虚函数:连接设备
virtual bool connect() = 0;
// 纯虚函数:断开连接
virtual void disconnect() = 0;
// 纯虚函数:发送数据
virtual void sendData(const string& data) = 0;
// 纯虚函数:接收数据
virtual string receiveData() = 0;
// 虚析构函数
virtual ~DeviceDriver() {}
};派生类:打印机驱动
class PrinterDriver : public DeviceDriver {
private:
string deviceName;
public:
PrinterDriver(const string& name) : deviceName(name) {}
bool connect() override {
cout << "Connecting to printer: " << deviceName << endl;
return true; // 模拟连接成功
}
void disconnect() override {
cout << "Disconnecting printer: " << deviceName << endl;
}
void sendData(const string& data) override {
cout << "Printing: " << data << endl;
}
string receiveData() override {
return "Print job completed"; // 模拟接收状态
}
};派生类:扫描仪驱动
class ScannerDriver : public DeviceDriver {
private:
string deviceName;
public:
ScannerDriver(const string& name) : deviceName(name) {}
bool connect() override {
cout << "Connecting to scanner: " << deviceName << endl;
return true;
}
void disconnect() override {
cout << "Disconnecting scanner: " << deviceName << endl;
}
void sendData(const string& data) override {
cout << "Scanner received command: " << data << endl;
}
string receiveData() override {
return "Scan result: 1024x768 image"; // 模拟扫描结果
}
};统一调用接口
void useDevice(DeviceDriver& driver) {
if (driver.connect()) {
driver.sendData("Test command");
cout << "Received: " << driver.receiveData() << endl;
driver.disconnect();
}
}
int main() {
PrinterDriver printer("HP LaserJet");
ScannerDriver scanner("Epson Perfection");
useDevice(printer);
useDevice(scanner);
return 0;
}运行结果:

特性 | 抽象类 | 纯抽象类(接口) |
|---|---|---|
成员变量 | 可以有 | 不能有(否则非 “纯”) |
普通成员函数 | 可以有(提供默认实现) | 不能有(所有函数都是纯虚) |
构造函数 | 可以有 | 可以有(但无成员变量时意义不大) |
继承方式 | 单继承(C++ 不支持多继承抽象类) | 可通过多继承模拟多接口 |
抽象类的设计需遵循面向对象设计的核心原则,其中 依赖倒置原则(DIP)和里氏替换原则(LSP)是关键。
高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。
例如,图形绘制程序(高层模块)不应直接依赖具体图形类(如Circle、Rectangle),而应依赖抽象类Shape。这样,新增图形类型(如Triangle)时,程序只需修改Shape的派生类,无需调整高层逻辑。
所有引用基类的地方必须能透明地使用其派生类的对象。
抽象类的派生类必须完全实现基类的接口,且行为符合预期。例如,若抽象类Shape的getArea()返回面积,派生类Circle的getArea()不能返回周长,否则违反 LSP。
客户端不应该依赖它不需要的接口。抽象类应设计为小而精的接口,而非大而全的 “胖接口”。
例如,若抽象类Shape同时包含draw2D()和draw3D(),但Circle是二维图形,无需draw3D(),则应拆分为Shape2D和Shape3D两个抽象类,避免派生类实现不必要的函数。
微软基础类库(MFC)中的CObject是典型的抽象类,定义了序列化、动态类型识别等接口。所有 MFC 类(如窗口类CWnd、文档类CDocument)都继承自CObject,确保统一的行为。
许多开发工具(如 VS Code、IntelliJ)通过抽象类定义插件接口。开发者只需实现接口,即可扩展工具功能(如添加新语言支持)。
游戏中的角色(玩家、敌人、NPC)可通过抽象类Character定义共同行为(如attack()、defend()),派生类实现具体行为(如Warrior的近战攻击、Mage的魔法攻击)。
抽象类可以有构造函数,用于初始化基类成员。例如:
class Shape {
protected:
string color; // 基类成员变量
public:
Shape(const string& c) : color(c) {} // 构造函数
virtual ~Shape() {}
};
class Circle : public Shape {
public:
Circle(double r, const string& c) : Shape(c), radius(r) {} // 调用基类构造函数
};是的!若派生类未重写所有纯虚函数,它仍是抽象类,无法实例化。例如:
class Shape {
public:
virtual void draw() = 0;
virtual void erase() = 0;
};
class Line : public Shape {
public:
void draw() override { /* 实现draw */ }
// 未实现erase() → Line仍是抽象类
};
// Line line; 编译错误:无法实例化抽象类若抽象类的析构函数非虚,通过基类指针删除派生类对象时,不会调用派生类的析构函数,导致资源泄漏。例如:
class Shape {
public:
~Shape() {} // 非虚析构函数 → 危险!
};
class Circle : public Shape {
private:
int* data; // 动态分配的资源
public:
Circle() { data = new int[100]; }
~Circle() { delete[] data; } // 派生类析构函数不会被调用!
};
int main() {
Shape* shape = new Circle();
delete shape; // 仅调用Shape的析构函数,data未释放 → 内存泄漏
return 0;
}正确做法:将抽象类的析构函数声明为虚函数:
class Shape {
public:
virtual ~Shape() {} // 虚析构函数
};为了更直观地展示抽象类的应用,我们设计一个跨平台日志系统,支持 Windows 和 Linux 系统,通过抽象类统一日志接口。
#include <iostream>
#include <string>
#include <ctime>
using namespace std;
// 抽象类:日志接口
class Logger {
public:
// 纯虚函数:记录信息日志
virtual void info(const string& message) = 0;
// 纯虚函数:记录错误日志
virtual void error(const string& message) = 0;
// 虚析构函数
virtual ~Logger() {}
protected:
// 辅助函数:获取当前时间字符串
string getCurrentTime() {
time_t now = time(0);
char buf[80];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&now));
return buf;
}
};// Windows特定日志(使用Windows API)
class WindowsLogger : public Logger {
public:
void info(const string& message) override {
cout << "[" << getCurrentTime() << "] [INFO] " << message << endl;
// 实际开发中可调用OutputDebugString等Windows API
}
void error(const string& message) override {
cerr << "[" << getCurrentTime() << "] [ERROR] " << message << endl;
// 实际开发中可调用MessageBox等API
}
};// Linux特定日志(使用syslog)
class LinuxLogger : public Logger {
public:
void info(const string& message) override {
cout << "[" << getCurrentTime() << "] [INFO] " << message << endl;
// 实际开发中可调用syslog(LOG_INFO, "%s", message.c_str())
}
void error(const string& message) override {
cerr << "[" << getCurrentTime() << "] [ERROR] " << message << endl;
// 实际开发中可调用syslog(LOG_ERR, "%s", message.c_str())
}
};class LogManager {
private:
unique_ptr<Logger> logger; // 抽象类指针管理具体实现
public:
// 根据平台初始化日志实现
LogManager(bool isWindows) {
if (isWindows) {
logger = make_unique<WindowsLogger>();
} else {
logger = make_unique<LinuxLogger>();
}
}
void logInfo(const string& message) {
logger->info(message);
}
void logError(const string& message) {
logger->error(message);
}
};int main() {
// 模拟Windows平台
LogManager winLog(true);
winLog.logInfo("Application started");
winLog.logError("Failed to read config file");
// 模拟Linux平台
LogManager linuxLog(false);
linuxLog.logInfo("Application started");
linuxLog.logError("Disk space low");
return 0;
}抽象是 C++ 面向对象编程的 “设计蓝图”,通过定义抽象类和纯虚函数,我们可以:
实践建议:
通过掌握抽象机制,开发者能更高效地设计出可扩展、易维护的复杂系统,这正是面向对象编程的魅力所在。