Menu
Coddy logo textTech

Upcasting

Часть раздела Object Oriented Programming путешествия по C на Coddy — урок 31 из 61.

Апкастинг (Upcasting) — это процесс обращения со специфическим (производным) типом как с его более общим (базовым) типом. В модели композиции языка C это означает передачу указателя на struct, которая содержит в себе базовый тип, функции, ожидающей этот базовый тип.

Рассмотрим базовую структуру 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};  // скорость=0, двери=4
accelerate((Vehicle*)&my_car, 50);
printf("Speed: %d\n", my_car.vehicle.speed);  // Вывод: 50

Это восходящее приведение типов (upcasting) — мы перемещаемся «вверх» от конкретного типа к общему. Функция accelerate не знает, что она работает с Car; она видит только Vehicle. Это позволяет писать переиспользуемый код, который работает с базовым типом, принимая при этом любой производный тип, который его встраивает.

challenge icon

Задание

Легко

Давайте создадим простую транспортную систему, которая демонстрирует восходящее приведение типов (upcasting) в действии. Вы создадите базовый тип Vehicle и более специфичный тип Motorcycle, а затем покажете, как универсальная функция может работать с обоими типами через приведение указателей.

Сила восходящего приведения заключается в том, что вы можете писать функции для базового типа, и они будут беспрепятственно работать с любым производным типом, который его включает. Ваш мотоцикл «является» транспортным средством, поэтому любая функция, ожидающая Vehicle*, должна иметь возможность его обработать.

Вы организуете свой код в трех файлах:

  • vehicle.h: Определите иерархию структур с использованием include guards. Создайте структуру 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*, чтобы передать его в эту универсальную функцию. После ускорения выведите информацию о мотоцикле, чтобы увидеть результат.

Вы получите четыре входных значения: начальную скорость мотоцикла, его максимальную скорость, наличие коляски (1 или 0) и величину ускорения.

Ваш вывод должен выглядеть следующим образом:

Speed: 75
Max Speed: 120
Sidecar: Yes

Где 75 — это скорость после ускорения (начальная 50 + ускорение 25), 120 — максимальная скорость, а Yes или No указывает на наличие коляски у мотоцикла. Если ускорение превысит максимальную скорость, скорость должна быть ограничена значением max_speed.

Ключевой момент здесь заключается в том, что boost ничего не знает о мотоциклах — она работает только с Vehicle. Тем не менее, благодаря восходящему приведению, ваш мотоцикл может использовать эту универсальную функцию для изменения данных встроенного транспортного средства.

Шпаргалка

Восходящее преобразование типов (upcasting) рассматривает конкретный (производный) тип как его более общий (базовый) тип путем передачи указателя на структуру, которая включает в себя базовый тип, в функцию, ожидающую базовый тип.

Определите базовую структуру и производную структуру, которая включает её в качестве первого члена:

typedef struct {
    int speed;
} Vehicle;

typedef struct {
    Vehicle vehicle;  // Базовый тип как первый член
    int doors;
} Car;

Создайте функцию, которая работает с базовым типом:

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

Приведите указатель производного типа к указателю базового типа для вызова функции:

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

Это позволяет писать переиспользуемый код, который оперирует базовым типом, принимая при этом любой производный тип, который его включает.

Попробуйте сами

#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: Продемонстрировать восходящее приведение типов (upcasting), вызвав boost
    // Приведите ваш Motorcycle* к Vehicle*, чтобы передать его в функцию boost
    
    // TODO: Вывести информацию о мотоцикле
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming