虚函数(Virtual Function)和纯虚函数(Pure Virtual Function)
虚函数(Virtual Function)和纯虚函数(Pure Virtual Function)是实现多态的关键,但它们在使用和含义上有本质的区别。
我将通过一个清晰的对比和总结来解释它们的区别。
一、核心定义对比
特性 | 虚函数 (Virtual Function) | 纯虚函数 (Pure Virtual Function) |
---|---|---|
语法 | virtual ReturnType funcName(); | virtual ReturnType funcName() = 0; |
实现 | 有函数体(提供默认实现) | 无函数体(不提供实现) |
目的 | 允许派生类选择性地重写该函数 | 强制派生类必须重写该函数 |
所在类 | 可以是普通类或抽象类 | 必须是抽象类 |
类实例化 | 该类可以被实例化 | 该类不能被实例化 |
二、详细解释与示例
1. 虚函数 (Virtual Function)
虚函数在基类中有默认的实现。它告诉编译器:“这个函数可能会在派生类中被重写,但如果派生类不重写它,就使用我的这个默认版本。”
示例:
#include <iostream>class Animal {
public:// 虚函数:有默认实现virtual void makeSound() {std::cout << "Some generic animal sound" << std::endl;}// 虚析构函数是必须的!virtual ~Animal() {}
};class Dog : public Animal {
public:// 可以选择性重写void makeSound() override {std::cout << "Woof! Woof!" << std::endl;}
};class Cat : public Animal {// Cat 类选择不重写 makeSound()// 它将使用 Animal 类的默认实现
};int main() {Animal* myDog = new Dog();Animal* myCat = new Cat();myDog->makeSound(); // 输出: Woof! Woof! (调用重写后的版本)myCat->makeSound(); // 输出: Some generic animal sound (调用基类默认版本)delete myDog;delete myCat;return 0;
}
2. 纯虚函数 (Pure Virtual Function)
纯虚函数在基类中只有声明,没有实现(函数体 = 0
)。它告诉编译器和程序员:“这个函数只是一个接口规范,所有非抽象的派生类必须提供它自己的实现。”
包含纯虚函数的类称为抽象类,它不能创建对象实例,只能作为接口被继承。
示例:
#include <iostream>// 抽象类
class Shape {
public:// 纯虚函数:定义接口,没有实现virtual double getArea() const = 0;// 纯虚析构函数也必须提供实现(唯一特例)virtual ~Shape() = default;
};class Circle : public Shape {
private:double radius;
public:Circle(double r) : radius(r) {}// 必须重写纯虚函数double getArea() const override {return 3.14159 * radius * radius;}
};class Rectangle : public Shape {
private:double width, height;
public:Rectangle(double w, double h) : width(w), height(h) {}// 必须重写纯虚函数double getArea() const override {return width * height;}
};int main() {// Shape myShape; // 错误!不能实例化抽象类Shape* shape1 = new Circle(5.0);Shape* shape2 = new Rectangle(4.0, 6.0);std::cout << "Circle area: " << shape1->getArea() << std::endl;std::cout << "Rectangle area: " << shape2->getArea() << std::endl;delete shape1;delete shape2;return 0;
}
三、核心区别总结
方面 | 虚函数 | 纯虚函数 |
---|---|---|
实现 | 有默认实现 | 无实现,只有接口 |
重写要求 | 派生类可以重写(可选) | 派生类必须重写(强制) |
类性质 | 可以是具体类 | 使类成为抽象类 |
实例化 | 类可以被实例化 | 类不能被实例化 |
设计意图 | 提供可覆盖的默认行为 | 定义必须实现的接口规范 |
四、如何选择?
-
使用虚函数:当基类能提供一个有意义的默认实现,而派生类可能需要根据自己的特性修改这个行为时。
- 例子:
Animal::move()
可能有一个默认的“移动”实现,但Bird
类可能会重写它为“飞行”,Fish
类重写为“游泳”。
- 例子:
-
使用纯虚函数:当基类无法或不应该提供一个有意义的默认实现,它的唯一作用就是为所有派生类定义一个必须遵守的接口时。
- 例子:
Shape::getArea()
计算面积。基类Shape
根本无法知道如何计算一个“抽象形状”的面积,只有具体的Circle
、Rectangle
才知道自己面积的计算方法。
- 例子:
五、一个重要的特例:纯虚析构函数
纯虚析构函数是一个特例。即使它被声明为纯虚函数(virtual ~Base() = 0;
),你也必须为它提供一份实现(通常在类外定义)。这是因为派生类的析构函数会隐式调用基类的析构函数。
class AbstractBase {
public:virtual ~AbstractBase() = 0; // 纯虚析构函数声明
};
// 必须提供实现
AbstractBase::~AbstractBase() {// 实现代码(可以是空的)
}
一句话概括
虚函数是“你可以换掉我的实现”,而纯虚函数是“你必须提供你的实现”。