1. 什么是虚函数
虚函数(Virtual Function)是 C++ 中用于实现多态的核心机制。在基类中使用 virtual 关键字声明的成员函数即为虚函数,它允许通过基类指针或引用调用派生类中重写的版本,从而实现运行时多态。
class Animal {
public:
// 虚函数:基类提供默认实现
virtual void speak() {
std::cout << "Animal speaks" << std::endl;
}
virtual ~Animal() {}
};
class Dog : public Animal {
public:
// 重写虚函数
void speak() override {
std::cout << "Dog barks" << std::endl;
}
};
int main() {
Animal* ptr = new Dog();
ptr->speak(); // 输出 "Dog barks",实现多态
delete ptr;
return 0;
}
虚函数通过虚函数表(vtable)实现。每个含有虚函数的类都维护一张虚函数表,表中存放指向各虚函数实现的函数指针。对象中保存一个指向 vtable 的指针(vptr),调用虚函数时通过 vptr 间接跳转,从而实现动态绑定。
2. 什么是纯虚函数
纯虚函数(Pure Virtual Function)是一种特殊的虚函数,它在基类中只声明而不提供实现(或定义为空),通过在声明末尾添加 = 0 来标识。含有纯虚函数的类称为抽象类,不能直接实例化,只能作为基类被派生类继承。
class Shape {
public:
// 纯虚函数:只声明,不实现
virtual double area() const = 0;
virtual ~Shape() {}
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// 派生类必须实现纯虚函数
double area() const override {
return 3.14159 * radius * radius;
}
};
派生类必须实现基类中的所有纯虚函数,否则该派生类仍然是抽象类,无法创建对象。
3. 虚函数与纯虚函数的区别
| 声明方式 | virtual 关键字 | virtual … = 0 |
| 基类是否提供实现 | 必须提供默认实现 | 通常不提供实现 |
| 基类能否实例化 | 可以 | 不可以(抽象类) |
| 派生类是否必须重写 | 可选(不重写则继承基类实现) | 必须重写 |
| 设计意图 | 提供可被覆盖的默认行为 | 定义接口规范,强制派生类实现 |
4. 虚函数的应用场景
4.1 实现运行时多态
虚函数最核心的应用是运行时多态。通过基类指针或引用调用派生类方法,使同一段代码可以处理不同类型的对象,这是面向对象设计的基石。
void makeSound(Animal& animal) {
animal.speak(); // 运行时动态绑定
}
int main() {
Dog dog;
Cat cat;
makeSound(dog); // 输出 "Dog barks"
makeSound(cat); // 输出 "Cat meows"
return 0;
}
4.2 基类提供默认行为,派生类按需覆盖
当基类能给出合理的默认实现,但允许派生类按需覆盖时,使用虚函数。例如图形界面框架中控件的绘制、游戏引擎中实体的更新逻辑等。
class Widget {
public:
virtual void draw() {
// 默认绘制逻辑
std::cout << "Drawing base widget" << std::endl;
}
};
class Button : public Widget {
public:
void draw() override {
// 按钮专属绘制
std::cout << "Drawing button" << std::endl;
}
};
4.3 析构函数声明为虚函数
当类被用作基类时,析构函数应声明为虚函数,确保通过基类指针删除派生类对象时能正确调用派生类析构函数,避免内存泄漏。
class Base {
public:
virtual ~Base() {
std::cout << "Base destructor" << std::endl;
}
};
class Derived : public Base {
public:
~Derived() override {
std::cout << "Derived destructor" << std::endl;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // 正确调用 Derived 和 Base 的析构函数
return 0;
}
5. 纯虚函数的应用场景
5.1 定义接口规范
纯虚函数最常见的用途是定义接口。基类只规定派生类必须提供哪些方法,不关心具体实现细节,从而实现接口与实现分离。这在插件化架构、驱动开发中非常常见。
class Database {
public:
virtual void connect(const std::string& url) = 0;
virtual void query(const std::string& sql) = 0;
virtual void close() = 0;
virtual ~Database() {}
};
class MySQL : public Database {
public:
void connect(const std::string& url) override { /* MySQL 实现 */ }
void query(const std::string& sql) override { /* MySQL 实现 */ }
void close() override { /* MySQL 实现 */ }
};
class PostgreSQL : public Database {
public:
void connect(const std::string& url) override { /* PostgreSQL 实现 */ }
void query(const std::string& sql) override { /* PostgreSQL 实现 */ }
void close() override { /* PostgreSQL 实现 */ }
};
5.2 模板方法模式
在模板方法模式中,基类定义算法的骨架,将某些步骤声明为纯虚函数,由派生类提供具体实现。基类控制流程,派生类定制细节。
class DataParser {
public:
void parse() {
openFile();
readHeader();
readBody(); // 纯虚函数,由派生类实现
closeFile();
}
protected:
virtual void readBody() = 0;
void openFile() { /* 通用实现 */ }
void closeFile() { /* 通用实现 */ }
};
class JSONParser : public DataParser {
protected:
void readBody() override {
// JSON 专属解析逻辑
}
};
5.3 强制派生类实现关键行为
当基类无法给出有意义的默认实现时,使用纯虚函数可以强制派生类必须实现该方法,避免因遗漏实现而导致的运行时错误。这在框架设计中尤为重要。
class Task {
public:
virtual void execute() = 0; // 每个任务必须实现执行逻辑
virtual ~Task() {}
};
class ReportTask : public Task {
public:
void execute() override {
// 生成报表
}
};
5.4 实现多态
纯虚函数同样支持多态。通过基类指针或引用调用派生类的方法,确保所有派生类都具备统一的行为入口,同时强制每个派生类提供自己的实现。
void printArea(const Shape& shape) {
std::cout << "Area: " << shape.area() << std::endl;
}
int main() {
Circle c(5.0);
Rectangle r(4.0, 6.0);
printArea(c); // 输出圆的面积
printArea(r); // 输出矩形的面积
return 0;
}
6. 总结
虚函数和纯虚函数是 C++ 面向对象编程中实现多态和抽象的两大核心工具。虚函数提供可被覆盖的默认实现,适合「基类有默认行为、派生类按需定制」的场景;纯虚函数定义接口规范,强制派生类实现,适合「基类只规定契约、不关心实现」的场景。理解两者的区别,有助于设计出结构清晰、扩展性强的代码架构。






