Menu
CoddyTech

Upcasting

Часть раздела Объектно-ориентированное программирование путешествия по C на Coddy. Урок 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*, должна уметь его обработать.

Ты разместишь код в трёх файлах:

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

Тебе будут переданы четыре значения: начальная скорость мотоцикла, его максимальная скорость, есть ли у него коляска (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