Konwersja w górę hierarchii
Część sekcji Programowanie obiektowe ścieżki C w Coddy. Lekcja 31 z 61.
Rzutowanie w górę polega na traktowaniu konkretnego typu (pochodnego) jako jego bardziej ogólnego typu (bazowego). W modelu kompozycji języka C oznacza to przekazanie do funkcji oczekującej typu bazowego wskaźnika do struktury, która osadza typ bazowy.
Rozważ bazową strukturę Vehicle i strukturę Car, która ją zawiera:
typedef struct {
int speed;
} Vehicle;
typedef struct {
Vehicle vehicle; // Struktura bazowa jako pierwsze pole
int doors;
} Car;Teraz wyobraź sobie funkcję ogólną, która działa z dowolnym pojazdem:
void accelerate(Vehicle* v, int amount) {
v->speed += amount;
}Dzięki regule pierwszego elementu członkowskiego możemy przekazać do tej funkcji Car*, rzutując go na Vehicle*:
Car my_car = {{0}, 4}; // speed=0, doors=4
accelerate((Vehicle*)&my_car, 50);
printf("Speed: %d\n", my_car.vehicle.speed); // Wynik: 50To jest rzutowanie w górę: przechodzimy „w górę” od konkretnego typu do ogólnego. Funkcja accelerate nie wie, że działa z obiektem typu Car; widzi tylko obiekt typu Vehicle. Dzięki temu możesz pisać kod wielokrotnego użytku, który działa na typie bazowym, przyjmując dowolny typ pochodny, który go osadza.
Wyzwanie
ŁatwyZbudujmy prosty system transportu, który pokazuje działanie rzutowania w górę. Utworzysz bazowy typ Vehicle i bardziej szczegółowy typ Motorcycle, a następnie pokażesz, jak ogólna funkcja może obsługiwać oba typy za pomocą rzutowania wskaźników.
Zaletą rzutowania w górę jest to, że możesz pisać funkcje dla typu bazowego, a będą one bez problemu działać z każdym typem pochodnym, który go zawiera. Twój motocykl „jest” pojazdem, więc każda funkcja oczekująca Vehicle* powinna móc go przetworzyć.
Rozmieścisz swój kod w trzech plikach:
vehicle.h: Zdefiniuj hierarchię struktur z zabezpieczeniami przed wielokrotnym dołączeniem. Utwórz strukturęVehiclez dwoma polami:speed(liczbą całkowitą określającą aktualną prędkość) imax_speed(liczbą całkowitą określającą maksymalną prędkość pojazdu). Następnie zdefiniuj strukturęMotorcycle, która zawieraVehiclejako pierwsze pole i dodaje polehas_sidecar(liczbę całkowitą, gdzie 1 oznacza tak, a 0 — nie). Zadeklaruj dwie funkcje:boost, która przyjmujeVehicle*i wartość, o którą należy zwiększyć prędkość, orazprint_motorcycle, która przyjmujeMotorcycle*.vehicle.c: Zaimplementuj obie funkcje. Funkcjaboostpowinna zwiększać prędkość pojazdu o podaną wartość, ale ograniczać ją domax_speed; jeśli nowa prędkość przekroczyłaby maksimum, ustaw ją namax_speed. Funkcjaprint_motorcyclepowinna wyświetlać wszystkie informacje o motocyklu, w tym dane zawartej w nim struktury pojazdu.main.c: Utwórz obiektMotorcyclei zainicjalizuj wszystkie jego pola. Następnie zademonstruj rzutowanie w górę, wywołując funkcjęboost: zrzućMotorcycle*naVehicle*, aby przekazać go do tej ogólnej funkcji. Po zwiększeniu prędkości wyświetl informacje o motocyklu, aby zobaczyć wynik.
Otrzymasz cztery dane wejściowe: prędkość początkową motocykla, jego maksymalną prędkość, informację, czy ma boczny wózek (1 lub 0), oraz wartość zwiększenia prędkości.
Wynik powinien wyglądać tak:
Speed: 75
Max Speed: 120
Sidecar: YesGdzie 75 to prędkość po zwiększeniu (początkowe 50 + zwiększenie o 25), 120 to maksymalna prędkość, a Yes lub No wskazuje, czy motocykl ma boczny wózek. Jeśli zwiększenie prędkości spowodowałoby przekroczenie max_speed, prędkość powinna zostać ograniczona do max_speed.
Najważniejsze jest to, że funkcja boost nic nie wie o motocyklach. Obsługuje wyłącznie Vehicle. Dzięki rzutowaniu w górę motocykl może jednak użyć tej ogólnej funkcji, aby zmodyfikować zawarte w nim dane pojazdu.
Spróbuj swoich sił
#include <stdio.h>
#include "vehicle.h"
int main() {
int initial_speed, max_speed, has_sidecar, boost_amount;
// Wczytaj dane wejściowe
scanf("%d", &initial_speed);
scanf("%d", &max_speed);
scanf("%d", &has_sidecar);
scanf("%d", &boost_amount);
// TODO: Utwórz Motorcycle i zainicjalizuj wszystkie jego pola
// Pamiętaj: Motorcycle zawiera Vehicle jako swój pierwszy element
// TODO: Zademonstruj rzutowanie w górę przez wywołanie boost
// Rzutuj Motorcycle* na Vehicle*, aby przekazać go do funkcji boost
// TODO: Wyświetl informacje o motocyklu
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania modularnego
Pliki nagłówkoweZabezpieczenia przed wielokrotnym dołączeniemPliki źródłoweFunkcje statycznePodsumowanie: kalkulator modularny4Enkapsulacja
Koncepcja niejawnych wskaźnikówDefiniowanie niejawnych strukturGettery i setteryWalidacja w setterachPowtórka: tajemnicze pudełko2Obiekty i metody
Struktury jako obiektyWskaźnik „self”Poprawność constWskaźnik czy wartośćMetody pomocniczePodsumowanie: menedżer punktów5Projekt: Proste konto bankowe
Konfiguracja projektuImplementacja konta8Polimorfizm
Wskaźniki do funkcji w strukturachSymulowanie metodKoncepcja interfejsuImplementowanie interfejsówIteracja polimorficznaPodsumowanie: Greeter11Wzorce projektowe w C
Wzorzec SingletonWzorzec fabrykiWzorzec iteratoraPodsumowanie: fabryka loggera3Cykl życia obiektu
Wzorzec konstruktoraWzorzec destruktoraInicjalizacja na stosieKopia głębokaPodsumowanie: klasa opakowująca ciąg znaków6Dziedziczenie przez kompozycję
Osadzanie strukturZasada pierwszego elementuDostęp do elementów klasy nadrzędnejKonwersja w górę hierarchiiPodsumowanie: hierarchia kształtów9Projekt: Rysownik kształtów
Przegląd projektuImplementacja kołaImplementacja prostokątaZastosowanie polimorfizmuKontener kształtówPoćwicz samodzielnie: Kompilator C online