Menu
Coddy logo textTech

Zasada pierwszego elementu

Część sekcji Programowanie obiektowe ścieżki C w Coddy. Lekcja 29 z 61.

Gdy osadzasz strukturę jako pierwszy element innej struktury, dzieje się coś niezwykłego: adres pamięci zewnętrznej struktury jest identyczny z adresem pamięci jej pierwszego elementu. Gwarantuje to standard C.

Rozważmy nasz poprzedni przykład:

typedef struct {
    int id;
} Parent;

typedef struct {
    Parent parent;  // Pierwsze pole
    int grade;
} Child;

Gdy tworzysz zmienną Child, składowa Parent znajduje się na samym początku pamięci obiektu Child. Oznacza to, że wskaźnik do Child wskazuje dokładnie ten sam adres co wskaźnik do jego składowej Parent.

Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr;  // Bezpieczne rzutowanie!

Oba wskaźniki przechowują ten sam adres. Dzięki temu możesz przekazać Child* do dowolnej funkcji oczekującej Parent*:

void print_id(Parent* p) {
    printf("ID: %d\n", p->id);
}

// W main:
Child c;
c.parent.id = 42;
print_id((Parent*)&c);  // Działa doskonale

Ta technika stanowi podstawę dziedziczenia w C. Funkcja napisana dla typu bazowego może działać na dowolnym typie pochodnym, o ile typ bazowy jest osadzony jako pierwszy. Rzutowanie jest bezpieczne, ponieważ układy pamięci są idealnie zgodne.

challenge icon

Wyzwanie

Łatwy

Poznajmy zasadę pierwszego pola, tworząc prostą hierarchię zwierząt. Utworzysz bazową strukturę Animal i pochodną strukturę Dog, a następnie pokażesz, jak bezpiecznie rzutować wskaźnik do Dog na wskaźnik do Animal.

Najważniejsza kwestia: gdy Animal jest pierwszym polem struktury Dog, obie struktury mają ten sam początkowy adres w pamięci. Oznacza to, że możesz przekazać Dog* do dowolnej funkcji oczekującej Animal*, rzutując wskaźnik.

Rozmieścisz kod w trzech plikach:

  • animal.h: Zdefiniuj obie struktury z osłonami przed wielokrotnym dołączaniem. Struktura Animal powinna mieć jedno pole: legs (liczbę całkowitą określającą liczbę nóg). Struktura Dog powinna zawierać Animal jako pierwsze pole i dodatkowe pole name (tablicę znaków o długości 50 znaków). Zadeklaruj także funkcję print_animal, która przyjmuje parametr Animal*.
  • animal.c: Zaimplementuj funkcję print_animal. Ta funkcja działa z typem bazowym. Wie tylko o Animal i wypisuje, ile nóg ma zwierzę.
  • main.c: Utwórz zmienną typu Dog i wypełnij jej pola (zarówno liczbę nóg osadzonego zwierzęcia, jak i imię psa). Następnie zademonstruj zasadę pierwszego pola, rzutując wskaźnik Dog* na Animal* i przekazując go do print_animal. Wypisz też osobno imię psa, aby pokazać pełny obraz.

Otrzymasz dwa dane wejściowe: imię psa (ciąg znaków) oraz liczbę nóg (liczbę całkowitą).

Wynik powinien wyglądać tak:

Dog: Buddy
Legs: 4

W tym przykładzie Buddy to imię psa, a 4 to liczba jego nóg. Najpierw wypisz imię psa z main.c, a następnie wywołaj print_animal z rzutowanym wskaźnikiem, aby wyświetlić liczbę nóg.

To pokazuje, jak funkcja napisana dla typu bazowego (Animal) może bezproblemowo działać z typem pochodnym (Dog) dzięki rzutowaniu wskaźników: to podstawa dziedziczenia w C.

Spróbuj swoich sił

#include <stdio.h>
#include <string.h>
#include "animal.h"

int main() {
    // Wczytaj dane wejściowe
    char name[50];
    int legs;
    scanf("%s", name);
    scanf("%d", &legs);
    
    // TODO: Utwórz zmienną typu Dog
    
    // TODO: Ustaw imię psa za pomocą strcpy
    
    // TODO: Ustaw liczbę nóg (uzyskaj dostęp przez osadzony Animal)
    
    // TODO: Wypisz imię psa w formacie: "Dog: <name>"
    
    // TODO: Rzutuj Dog* na Animal* i wywołaj print_animal
    // To pokazuje zasadę pierwszego pola!
    
    return 0;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C online