8.6 多态
多态表示“多种形态”。在 C++ 中,运行期多态让同一个调用可以根据真实对象类型选择不同实现。
关键条件是:
- 基类中有至少一个
virtual函数; - 派生类重写这个函数;
- 通过基类指针或基类引用进行调用。
没有指针或引用时,C++ 可能只按你保存的静态类型处理对象。没有 virtual 时,C++ 会使用编译期已经确定的函数。
通过基类指针进行虚函数派发
想象一个工资界面,需要打印多种员工的工资。调用者不应该为每一种员工都写一个 if。
cpp
class Employee {
public:
virtual ~Employee() = default;
virtual std::string name() const = 0;
virtual double pay() const = 0;
};
class FullTimeEmployee : public Employee {
private:
std::string name_;
double monthly_salary_;
public:
FullTimeEmployee(std::string name, double monthly_salary)
: name_(name), monthly_salary_(monthly_salary) {}
std::string name() const override { return name_; }
double pay() const override { return monthly_salary_; }
};调用者可以使用统一接口:
cpp
FullTimeEmployee alice("Alice", 5000.0);
PartTimeEmployee bob("Bob", 40.0, 80);
Employee* employees[] = { &alice, &bob };
for (Employee* employee : employees) {
std::cout << employee->name() << ": " << employee->pay() << std::endl;
}表达式 employee->pay() 会分派到真实对象的重写版本。如果 employee 指向 FullTimeEmployee,就调用 FullTimeEmployee::pay;如果指向 PartTimeEmployee,就调用 PartTimeEmployee::pay。
正在加载交互实验...
正在加载概念检查...
正在加载本节练习...
对象切片
多态依赖真实派生对象被保留下来。如果你把派生对象按值复制进基类对象,派生类部分会被切掉,这叫 object slicing。
cpp
class Product {
public:
virtual std::string label() const {
return "generic product";
}
};
class Food : public Product {
public:
std::string label() const override {
return "fresh food";
}
};
Food food;
Product copy = food; // 切片:只复制 Product 部分
std::cout << copy.label(); // 输出 generic product为了保留多态,使用引用、指针或智能指针:
cpp
Food food;
Product& ref = food;
std::cout << ref.label(); // 输出 fresh food对象本身没有变,变的是你保存对象的方式。保存方式决定了 C++ 是否还能看见派生类行为。
正在加载交互实验...
正在加载概念检查...
正在加载本节练习...
虚析构函数和接口清理
如果基类用于多态,析构函数通常应该是 virtual。
cpp
class Exporter {
public:
virtual ~Exporter() = default;
virtual std::string export_line(const std::string& value) const = 0;
};当你通过基类指针删除对象时,这一点非常重要。
cpp
Exporter* exporter = new JsonExporter();
delete exporter; // 只有 Exporter 有虚析构函数时才安全现代 C++ 中通常会使用智能指针,而不是裸 new 和 delete,但原则不变:基类必须允许清理过程抵达真实派生对象。没有虚析构函数的多态基类,是常见资源 bug 来源。
多态最有效的时候,基类接口通常小而有意义。调用者不再问“这到底是哪种具体类型”,而是问“我需要它提供什么行为”。
cpp
void save_line(const Exporter& exporter, const std::string& value) {
std::cout << exporter.export_line(value) << std::endl;
}正在加载概念检查...
正在加载本节练习...