Menu
CoddyTech

向上转型

本课属于 Coddy C 学习旅程的“面向对象编程”单元。第 31 课,共 61 课。

向上转型是指将特定的(派生)类型视为更通用的(基)类型。在 C 的组合模型中,这意味着将一个指向嵌入了基类型的结构体的指针传递给一个需要基类型的函数。

考虑一个 Vehicle 基础结构体,以及一个包含它的 Car:

typedef struct {
    int speed;
} Vehicle;

typedef struct {
    Vehicle vehicle;  // 将基类放在第一个成员位置
    int doors;
} Car;

现在,想象一个适用于任意车辆的通用函数:

void accelerate(Vehicle* v, int amount) {
    v->speed += amount;
}

借助首成员规则,我们可以通过将 Car* 强制转换为 Vehicle*,把它传递给这个函数:

Car my_car = {{0}, 4};  // speed=0,doors=4
accelerate((Vehicle*)&my_car, 50);
printf("Speed: %d\n", my_car.vehicle.speed);  // 输出:50

这就是向上转型:我们从一种具体类型“向上”转为一种通用类型。函数 accelerate 不知道自己处理的是 Car,它只看到一个 Vehicle。这样,你就可以编写可复用的代码,让它处理基类型,同时接受任何嵌入该基类型的派生类型。

challenge icon

挑战

简单

让我们构建一个简单的交通系统,展示向上转型的实际效果。你将创建一个基础类型 Vehicle 和一个更具体的类型 Motorcycle,然后展示如何通过指针类型转换,让通用函数同时处理这两种类型。

向上转型的优势在于,你可以为基础类型编写函数,这些函数也能顺畅地处理任何嵌入该基础类型的派生类型。你的摩托车“是一种”车辆,因此,任何接收 Vehicle* 的函数都应该能够处理它。

你需要将代码分成 3 个文件:

  • vehicle.h:使用包含保护定义结构体层次结构。创建一个 Vehicle 结构体,包含两个字段:speed(表示当前速度的整数)和 max_speed(表示车辆最高速度的整数)。然后定义一个 Motorcycle 结构体,将 Vehicle 嵌入为它的第一个成员,并添加一个 has_sidecar 字段(一个整数,1 表示有,0 表示没有)。声明两个函数:boost,接收一个 Vehicle* 和一个用于提高速度的数值;以及 print_motorcycle,接收一个 Motorcycle*。
  • vehicle.c:实现这两个函数。你的 boost 函数应该按给定的数值提高车辆速度,但速度不能超过 max_speed。如果新速度会超过最高速度,就将速度设为 max_speed。你的 print_motorcycle 函数应该显示摩托车的所有信息,包括它嵌入的车辆数据。
  • main.c:创建一个 Motorcycle 并初始化它的所有字段。然后通过调用 boost 函数来演示向上转型:将你的 Motorcycle* 转换为 Vehicle*,再传给这个通用函数。提高速度后,打印摩托车的信息,查看结果。

你会收到 4 个输入:摩托车的初始速度、最高速度、它是否有侧边车斗(1 或 0),以及提速数值。

输出应如下所示:

Speed: 75
Max Speed: 120
Sidecar: Yes

其中,75 是提速后的速度(初始速度 50 + 提速 25),120 是最高速度,Yes 或 No 表示摩托车是否有侧边车斗。如果提速会使速度超过 max_speed,就应该将速度限制在 max_speed。

这里的关键是,boost 并不了解摩托车。它只处理 Vehicle。但通过向上转型,你的摩托车可以使用这个通用函数来修改它嵌入的车辆数据。

亲手试试

#include <stdio.h>
#include "vehicle.h"

int main() {
    int initial_speed, max_speed, has_sidecar, boost_amount;
    
    // 读取输入
    scanf("%d", &initial_speed);
    scanf("%d", &max_speed);
    scanf("%d", &has_sidecar);
    scanf("%d", &boost_amount);
    
    // TODO: 创建一个 Motorcycle,并初始化它的所有字段
    // 记住:Motorcycle 的第一个成员是嵌入的 Vehicle
    
    // TODO: 通过调用 boost 演示向上转型
    // 将你的 Motorcycle* 转换为 Vehicle*,再传给 boost 函数
    
    // TODO: 打印摩托车的信息
    
    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C 在线编译器