跳到主要内容

多态

在本课程中,我们将学习如何根据对象的类型执行不同的逻辑,即我们将学习多态。

动机

在创建结构层次结构时,出现的主要问题之一是不同类型的对象以不同的方式处理相同的任务。例如,当涉及到车辆时,汽车加速的方式与飞机不同。这使得编写能够以统一方式处理不同类型车辆的代码变得困难。

🤔 不同车辆的不同逻辑
struct Vehicle
{
double speed;
void accelerate() {
std::cout << "Burning fuel\n"; // nothing too specific
}
};

struct Car
: Vehicle
{
void accelerate() {
std::cout << "Engaging the engine\n";
std::cout << "Pressing the gas pedal\n";
}
};

struct Airplane
: Vehicle
{
void accelerate() {
std::cout << "Engaging multiple engines\n";
std::cout << "Increasing the throttle\n";
}
};

我们将以游戏为例。假设玩家可以使用不同类型的车辆。重要的是以一致的方式处理玩家的输入,无论玩家当前使用的是哪种类型的车辆。例如,当玩家按下 W 键时,它应该加速。然而,由于我们希望每种类型的车辆运行不同的加速逻辑,因此提供了该方法的不同实现(请参阅上面的代码)。

简单实现

可以编写一个函数来处理玩家的输入并调用车辆的 accelerate() 方法

// prism-push-types:Vehicle
void handle_accelerate_button(Vehicle const& vehicle) {
std::cout << "Handling the accelerate button...\n";
vehicle.accelerate();
}

然而,这将调用 Vehicle 基类的 accelerate() 方法,该方法不具有实际车辆类型的特定行为。这意味着玩家的车辆不会按预期加速。

💢 意外行为
// prism-push-types:Car,Airplane
Car car;
Airplane airplane;

handle_accelerate_button(car);
handle_accelerate_button(airplane);

虚函数

解决此问题的方法是在**基类中**将 accelerate() 标记为虚函数。

struct Vehicle
{
double speed;
virtual void accelerate() {
std::cout << "Burning fuel\n"; // nothing too specific
}
};

通过之前的代码,我们遇到了所谓的**静态分派**,它导致了**方法遮蔽**。当派生类引入与基类中同名的函数,并且基类的函数**没有**被标记为 virtual 时,就会发生这种情况。

在前面的示例中,编译器看到的我们调用 accelerate() 的对象的类型是 Vehicle

void handle_accelerate_button(Vehicle const& vehicle)

为了调用正确的实现,程序必须首先确定对象的精确类型,这需要**动态分派**。这是通过在基结构中将 accelerate() 方法标记为 virtual 来完成的。

静态分派

在**静态分派**(默认)期间,编译器在编译时根据调用站点提供的对象类型确定要调用的函数。

💡 静态分派
// prism-push-types:Airplane
Airplane airplane;
airplane.accelerate();

动态分派

**动态分派**会导致编译器在运行时根据对象的实际类型确定要调用的函数。这会带来很小的性能开销,但目前不必过于担心。

假设 accelerate() 方法在基类中被标记为 virtual,以下代码将导致动态分派

💡 动态分派
// prism-push-types:Airplane,Vehicle
Airplane airplane;

// access the accelerate() method through a reference to the base class
Vehicle& vehicle = airplane;
vehicle.accelerate();

使用虚函数

局限性

多态

在本课程中,我们将学习如何根据对象的类型执行不同的逻辑,即我们将学习多态。

动机

在创建结构层次结构时,出现的主要问题之一是不同类型的对象以不同的方式处理相同的任务。例如,当涉及到车辆时,汽车加速的方式与飞机不同。这使得编写能够以统一方式处理不同类型车辆的代码变得困难。

🤔 不同车辆的不同逻辑
struct Vehicle
{
double speed;
void accelerate() {
std::cout << "Burning fuel\n"; // nothing too specific
}
};

struct Car
: Vehicle
{
void accelerate() {
std::cout << "Engaging the engine\n";
std::cout << "Pressing the gas pedal\n";
}
};

struct Airplane
: Vehicle
{
void accelerate() {
std::cout << "Engaging multiple engines\n";
std::cout << "Increasing the throttle\n";
}
};

我们将以游戏为例。假设玩家可以使用不同类型的车辆。重要的是以一致的方式处理玩家的输入,无论玩家当前使用的是哪种类型的车辆。例如,当玩家按下 W 键时,它应该加速。然而,由于我们希望每种类型的车辆运行不同的加速逻辑,因此提供了该方法的不同实现(请参阅上面的代码)。

简单实现

可以编写一个函数来处理玩家的输入并调用车辆的 accelerate() 方法

// prism-push-types:Vehicle
void handle_accelerate_button(Vehicle const& vehicle) {
std::cout << "Handling the accelerate button...\n";
vehicle.accelerate();
}

然而,这将调用 Vehicle 基类的 accelerate() 方法,该方法不具有实际车辆类型的特定行为。这意味着玩家的车辆不会按预期加速。

💢 意外行为
// prism-push-types:Car,Airplane
Car car;
Airplane airplane;

handle_accelerate_button(car);
handle_accelerate_button(airplane);

虚函数

解决此问题的方法是在**基类中**将 accelerate() 标记为虚函数。

struct Vehicle
{
double speed;
virtual void accelerate() {
std::cout << "Burning fuel\n"; // nothing too specific
}
};

通过之前的代码,我们遇到了所谓的**静态分派**,它导致了**方法遮蔽**。当派生类引入与基类中同名的函数,并且基类的函数**没有**被标记为 virtual 时,就会发生这种情况。

在前面的示例中,编译器看到的我们调用 accelerate() 的对象的类型是 Vehicle

void handle_accelerate_button(Vehicle const& vehicle)

为了调用正确的实现,程序必须首先确定对象的精确类型,这需要**动态分派**。这是通过在基结构中将 accelerate() 方法标记为 virtual 来完成的。

静态分派

在**静态分派**(默认)期间,编译器在编译时根据调用站点提供的对象类型确定要调用的函数。

💡 静态分派
// prism-push-types:Airplane
Airplane airplane;
airplane.accelerate();

动态分派

**动态分派**会导致编译器在运行时根据对象的实际类型确定要调用的函数。这会带来很小的性能开销,但目前不必过于担心。

假设 accelerate() 方法在基类中被标记为 virtual,以下代码将导致动态分派

💡 动态分派
// prism-push-types:Airplane,Vehicle
Airplane airplane;

// access the accelerate() method through a reference to the base class
Vehicle& vehicle = airplane;
vehicle.accelerate();

使用虚函数

局限性