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