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. Это позволяет писать переиспользуемый код, который работает с базовым типом, принимая при этом любой производный тип, который его встраивает.
Задание
ЛегкоДавайте создадим простую транспортную систему, которая демонстрирует восходящее приведение типов (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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация круга