1. 什么是多态?
多态(Polymorphism)是面向对象编程的三大特性之一(封装、继承、多态)。它允许使用统一的接口来处理不同类型的对象,具体执行哪个操作由对象的实际类型决定。
在C++中,多态主要分为两种:
- 编译时多态(静态多态):通过函数重载和运算符重载实现,在编译时就能确定调用哪个函数。
- 运行时多态(动态多态):通过虚函数和继承实现,在程序运行时根据对象的实际类型决定调用哪个函数。
本文重点讨论运行时多态,这是C++面向对象编程中最核心的特性之一。
2. 虚函数的工作原理
虚函数(virtual function)是实现运行时多态的关键机制。当基类中的成员函数被声明为虚函数时,派生类可以重写(override)该函数,通过基类指针或引用调用时,会根据实际指向的对象类型来调用相应的函数版本。
2.1 虚函数表(vtable)
每个包含虚函数的类都有一个虚函数表(vtable),这是一个函数指针数组,存储了该类所有虚函数的地址。每个对象则包含一个指向其类vtable的指针(vptr)。
2.2 虚函数声明与重写
#include <iostream> using namespace std; // 基类 class Animal { public: // 虚函数声明 virtual void makeSound() { cout << "动物发出声音" << endl; } virtual ~Animal() {} // 虚析构函数 }; // 派生类 class Dog : public Animal { public: // 重写虚函数 void makeSound() override { cout << "汪汪汪!" << endl; } }; // 另一个派生类 class Cat : public Animal { public: void makeSound() override { cout << "喵喵喵!" << endl; } }; int main() { Animal* animal1 = new Dog(); Animal* animal2 = new Cat(); // 通过基类指针调用虚函数 animal1->makeSound(); // 输出:汪汪汪! animal2->makeSound(); // 输出:喵喵喵! delete animal1; delete animal2; return 0; }3. 多态的实现机制
3.1 基类指针/引用的多态行为
#include <iostream> #include <vector> using namespace std; class Shape { public: virtual double area() const = 0; // 纯虚函数 virtual void draw() const { cout << "绘制形状" << endl; } virtual ~Shape() {} }; class Circle : public Shape { private: double radius; public: Circle(double r) : radius(r) {} double area() const override { return 3.14159 * radius * radius; } void draw() const override { cout << "绘制圆形,半径:" << radius << endl; } }; class Rectangle : public Shape { private: double width, height; public: Rectangle(double w, double h) : width(w), height(h) {} double area() const override { return width * height; } void draw() const override { cout << "绘制矩形,宽:" << width << ",高:" << height << endl; } }; int main() { vector<Shape*> shapes; shapes.push_back(new Circle(5.0)); shapes.push_back(new Rectangle(4.0, 6.0)); shapes.push_back(new Circle(3.0)); // 统一接口处理不同类型的对象 for (Shape* shape : shapes) { shape->draw(); cout << "面积:" << shape->area() << endl; } // 清理内存 for (Shape* shape : shapes) { delete shape; } return 0; }3.2 虚析构函数的重要性
当通过基类指针删除派生类对象时,如果基类析构函数不是虚函数,只会调用基类的析构函数,导致派生类部分的内存泄漏。
class Base { public: virtual ~Base() { // 虚析构函数 cout << "Base析构函数" << endl; } }; class Derived : public Base { public: ~Derived() { cout << "Derived析构函数" << endl; } }; int main() { Base* ptr = new Derived(); delete ptr; // 正确调用Derived和Base的析构函数 return 0; }4. 纯虚函数与抽象类
纯虚函数是在基类中声明但没有定义的虚函数,语法为:virtual 返回类型 函数名(参数列表) = 0;
包含纯虚函数的类称为抽象类,不能实例化对象,只能作为基类被继承。
// 抽象类 class Vehicle { public: virtual void start() = 0; // 纯虚函数 virtual void stop() = 0; // 纯虚函数 virtual ~Vehicle() {} }; class Car : public Vehicle { public: void start() override { cout << "汽车启动" << endl; } void stop() override { cout << "汽车停止" << endl; } }; class Bike : public Vehicle { public: void start() override { cout << "自行车开始骑行" << endl; } void stop() override { cout << "自行车停止" << endl; } };5. 多态的应用场景与优势
5.1 应用场景
- 插件系统:统一接口,动态加载不同插件
- GUI框架:统一处理各种控件的事件
- 游戏开发:统一处理不同类型的游戏对象
- 设计模式:工厂模式、策略模式、观察者模式等
5.2 优势
- 代码复用:通过基类接口统一处理
- 扩展性:新增派生类不影响现有代码
- 灵活性:运行时动态决定行为
- 可维护性:降低模块间的耦合度
6. 注意事项
- 只有通过指针或引用调用虚函数才会发生多态
- 构造函数中调用虚函数不会发生多态(此时vptr可能还未初始化)
- 内联函数不能是虚函数(但编译器可能优化)
- 静态成员函数不能是虚函数
- 友元函数不能是虚函数
- 使用
override关键字(C++11)可以明确表示重写,提高代码可读性 - 使用
final关键字(C++11)可以禁止派生类重写
7. 总结
C++的多态通过虚函数机制实现,核心是虚函数表和虚函数指针。多态允许使用基类接口操作派生类对象,提高了代码的灵活性、可扩展性和可维护性。正确使用虚函数、虚析构函数和纯虚函数,是编写高质量面向对象C++代码的关键。