在C++中,虚函数(Virtual Function)和纯虚函数(Pure Virtual Function)都是实现多态性的重要工具。虽然它们在概念上相关,但有着不同的用途和特性。以下将详细解释它们的区别、用途以及如何在实际编程中使用它们。
1. 基本定义
虚函数(Virtual Function)
虚函数是在基类中声明的,可以在派生类中被重写(覆盖)的函数。它允许通过基类指针或引用调用派生类中的函数实现,从而实现运行时多态。
class Base {
public:virtual void display() {std::cout << "Display from Base" << std::endl;}
};
纯虚函数(Pure Virtual Function)
纯虚函数是一个没有实现的虚函数,基类中声明为纯虚函数的类被称为抽象类。纯虚函数强制派生类必须提供该函数的具体实现,否则派生类也将成为抽象类,无法实例化。
class AbstractBase {
public:virtual void display() = 0; // 纯虚函数
};
2. 主要区别
特性 | 虚函数(Virtual Function) | 纯虚函数(Pure Virtual Function) |
---|---|---|
定义方式 | virtual 返回类型 函数名(参数列表) | virtual 返回类型 函数名(参数列表) = 0; |
实现 | 可以在基类中提供默认实现 | 不提供实现,必须在派生类中实现 |
类类型 | 基类可以是具体类(可实例化) | 基类必须是抽象类(不可实例化) |
强制性 | 派生类可以选择是否重写 | 派生类必须重写,否则也是抽象类 |
用途 | 提供默认行为,允许派生类覆盖 | 定义接口,强制派生类实现特定行为 |
3. 详细说明
虚函数的特点
-
多态性:
- 虚函数允许在运行时决定调用哪个函数实现,这称为运行时多态。
- 通过基类指针或引用,可以调用派生类中重写的函数。
-
默认实现:
- 基类中可以为虚函数提供默认实现,派生类可以选择是否覆盖。
- 如果派生类没有重写虚函数,调用基类的实现。
-
非抽象类:
- 包含虚函数的类仍然是具体类,可以被实例化。
-
语法:
class Base { public:virtual void display() {std::cout << "Display from Base" << std::endl;} };class Derived : public Base { public:void display() override { // 重写虚函数std::cout << "Display from Derived" << std::endl;} };
纯虚函数的特点
-
抽象性:
- 包含纯虚函数的类是抽象类,不能被实例化。
- 纯虚函数没有实现,派生类必须提供具体实现。
-
接口定义:
- 纯虚函数用于定义接口,确保派生类实现特定的行为。
- 类似于其他语言中的接口(如Java中的
interface
)。
-
强制重写:
- 派生类如果不实现所有继承的纯虚函数,仍然是抽象类,无法实例化。
-
语法:
class AbstractBase { public:virtual void display() = 0; // 纯虚函数virtual ~AbstractBase() = default; // 虚析构函数 };class Derived : public AbstractBase { public:void display() override { // 必须实现纯虚函数std::cout << "Display from Derived" << std::endl;} };
4. 使用场景对比
虚函数的使用场景
- 提供默认行为:基类提供一个通用的实现,派生类可以根据需要选择是否覆盖。
- 可选覆盖:派生类可以继承基类的行为,或者根据需求提供特定实现。
示例:
#include <iostream>
#include <vector>class Animal {
public:virtual void makeSound() {std::cout << "Some generic animal sound." << std::endl;}
};class Dog : public Animal {
public:void makeSound() override { // 重写虚函数std::cout << "Woof!" << std::endl;}
};class Cat : public Animal {// 不重写 makeSound,使用基类的默认实现
};int main() {std::vector<Animal*> animals = { new Dog(), new Cat() };for(auto animal : animals) {animal->makeSound();}// 输出:// Woof!// Some generic animal sound.// 清理内存for(auto animal : animals) {delete animal;}return 0;
}
纯虚函数的使用场景
- 定义接口:创建一个接口,确保所有派生类实现特定的函数。
- 强制实现:确保所有具体类实现某些关键功能,避免遗漏。
示例:
#include <iostream>
#include <vector>// 抽象基类
class Shape {
public:virtual void draw() = 0; // 纯虚函数,强制派生类实现virtual ~Shape() = default;
};// 具体类
class Circle : public Shape {
public:void draw() override {std::cout << "Drawing a Circle." << std::endl;}
};class Rectangle : public Shape {
public:void draw() override {std::cout << "Drawing a Rectangle." << std::endl;}
};int main() {// Shape s; // 错误:无法实例化抽象类std::vector<Shape*> shapes = { new Circle(), new Rectangle() };for(auto shape : shapes) {shape->draw();}// 输出:// Drawing a Circle.// Drawing a Rectangle.// 清理内存for(auto shape : shapes) {delete shape;}return 0;
}
5. 示例对比
为了更直观地理解虚函数和纯虚函数的区别,以下通过两个完整的示例进行对比。
示例1:使用虚函数
#include <iostream>
#include <vector>class Printer {
public:virtual void print() {std::cout << "Printing from Printer" << std::endl;}
};class LaserPrinter : public Printer {
public:void print() override {std::cout << "Printing from LaserPrinter" << std::endl;}
};class InkjetPrinter : public Printer {
public:void print() override {std::cout << "Printing from InkjetPrinter" << std::endl;}
};int main() {std::vector<Printer*> printers = { new Printer(), new LaserPrinter(), new InkjetPrinter() };for(auto printer : printers) {printer->print();}// 输出:// Printing from Printer// Printing from LaserPrinter// Printing from InkjetPrinter// 清理内存for(auto printer : printers) {delete printer;}return 0;
}
说明:
Printer
类提供了一个默认的print
实现。LaserPrinter
和InkjetPrinter
类重写了print
函数。- 当调用
print
时,基类和派生类的实现都会被调用,具体取决于对象的实际类型。
示例2:使用纯虚函数
#include <iostream>
#include <vector>// 抽象基类
class Vehicle {
public:virtual void startEngine() = 0; // 纯虚函数virtual ~Vehicle() = default;
};// 具体类
class Car : public Vehicle {
public:void startEngine() override {std::cout << "Car engine started." << std::endl;}
};class Motorcycle : public Vehicle {
public:void startEngine() override {std::cout << "Motorcycle engine started." << std::endl;}
};int main() {// Vehicle v; // 错误:无法实例化抽象类std::vector<Vehicle*> vehicles = { new Car(), new Motorcycle() };for(auto vehicle : vehicles) {vehicle->startEngine();}// 输出:// Car engine started.// Motorcycle engine started.// 清理内存for(auto vehicle : vehicles) {delete vehicle;}return 0;
}
说明:
Vehicle
类是一个抽象类,定义了一个纯虚函数startEngine
。Car
和Motorcycle
类必须实现startEngine
函数。- 通过基类指针调用
startEngine
,实现了多态性。
6. 关键点总结
特性 | 虚函数(Virtual Function) | 纯虚函数(Pure Virtual Function) |
---|---|---|
定义 | virtual void func(); | virtual void func() = 0; |
实现 | 可以在基类中实现 | 不在基类中实现,派生类必须实现 |
类类型 | 基类可以实例化 | 基类是抽象类,不能实例化 |
强制性 | 派生类可以选择实现或使用基类实现 | 派生类必须实现,或者继续保持抽象 |
典型用途 | 提供可选的默认行为 | 定义接口,强制派生类实现特定行为 |
关键点
-
虚函数提供灵活性:
- 允许基类提供默认实现,派生类可以根据需要选择覆盖或继承。
- 适用于需要共享某些通用行为,但允许具体类进行定制的场景。
-
纯虚函数强制实现:
- 定义接口或抽象基类,确保所有具体类实现特定功能。
- 适用于需要统一接口但行为由具体类定义的场景。
-
抽象类与具体类:
- 含有纯虚函数的类是抽象类,无法实例化,只能作为基类。
- 含有虚函数但无纯虚函数的类是具体类,可以实例化。
-
多态性的实现:
- 虚函数和纯虚函数都是实现多态性的手段。
- 通过基类指针或引用,可以调用具体类的实现,增强代码的灵活性和可扩展性。
7. 进一步阅读与学习
- C++ 官方文档:深入了解虚函数和纯虚函数.
- 设计模式:学习如何在设计模式中使用虚函数和纯虚函数,如模板方法模式和策略模式.
- C++ 多态性:理解运行时多态的工作原理及其在面向对象编程中的应用。