Die First-Member-Regel
Teil des Abschnitts Object Oriented Programming der C-Journey von Coddy — Lektion 29 von 61.
Wenn Sie ein struct als erstes Mitglied eines anderen struct einbetten, passiert etwas Mächtiges: Die Speicheradresse des äußeren struct ist identisch mit der Speicheradresse seines ersten Mitglieds. Dies wird durch den C-Standard garantiert.
Betrachten wir unser vorheriges Beispiel:
typedef struct {
int id;
} Parent;
typedef struct {
Parent parent; // Erstes Element
int grade;
} Child;Wenn Sie eine Child-Variable erstellen, befindet sich das Parent-Member ganz am Anfang des Speichers von Child. Das bedeutet, dass ein Zeiger auf Child auf genau dieselbe Adresse zeigt wie ein Zeiger auf sein Parent-Member.
Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr; // Sicherer Cast!Beide Pointer halten dieselbe Adresse. Dies ermöglicht es Ihnen, einen Child* an jede Funktion zu übergeben, die einen Parent* erwartet:
void print_id(Parent* p) {
printf("ID: %d\n", p->id);
}
// In main:
Child c;
c.parent.id = 42;
print_id((Parent*)&c); // Funktioniert einwandfreiDiese Technik ist die Grundlage der Vererbung in C. Eine für den Basistyp geschriebene Funktion kann mit jedem abgeleiteten Typ arbeiten, solange die Basis zuerst eingebettet ist. Der Cast ist sicher, da die Speicherlayouts perfekt aufeinander abgestimmt sind.
Aufgabe
EinfachLassen Sie uns die "First Member Rule" (Regel des ersten Elements) untersuchen, indem wir eine einfache Tierhierarchie aufbauen. Sie werden eine Basis-Struktur Animal und eine abgeleitete Struktur Dog erstellen und dann demonstrieren, wie ein Zeiger auf Dog sicher in einen Zeiger auf Animal umgewandelt werden kann.
Die entscheidende Erkenntnis, die Sie anwenden werden: Wenn Animal das erste Mitglied von Dog ist, teilen sich beide Strukturen dieselbe Startadresse im Speicher. Das bedeutet, dass Sie einen Dog* an jede Funktion übergeben können, die einen Animal* erwartet, indem Sie den Zeiger casten.
Sie werden Ihren Code auf drei Dateien verteilen:
animal.h: Definieren Sie beide Strukturen mit Include-Guards. IhreAnimal-Struktur sollte ein einzelnes Feld haben:legs(ein Integer, der die Anzahl der Beine darstellt). IhreDog-Struktur sollteAnimalals ihr erstes Mitglied einbetten und ein Feldname(ein Zeichen-Array von 50 Zeichen) hinzufügen. Deklarieren Sie außerdem eine Funktionprint_animal, die einenAnimal*-Parameter entgegennimmt.animal.c: Implementieren Sie die Funktionprint_animal. Diese Funktion arbeitet mit dem Basistyp – sie kennt nurAnimalund gibt aus, wie viele Beine das Tier hat.main.c: Erstellen Sie eineDog-Variable und füllen Sie deren Felder (sowohl die Beine des eingebetteten Tieres als auch den Namen des Hundes). Demonstrieren Sie dann die "First Member Rule", indem Sie IhrenDog*in einenAnimal*casten und anprint_animalübergeben. Geben Sie außerdem den Namen des Hundes separat aus, um das Gesamtbild zu zeigen.
Sie erhalten zwei Eingaben: den Namen des Hundes (einen String) und die Anzahl der Beine (einen Integer).
Ihre Ausgabe sollte wie folgt aussehen:
Dog: Buddy
Legs: 4Wobei Buddy der Name des Hundes und 4 die Anzahl der Beine ist. Geben Sie zuerst den Namen des Hundes aus main.c aus und rufen Sie dann print_animal mit dem gecasteten Zeiger auf, um die Beine anzuzeigen.
Dies demonstriert, wie eine für den Basistyp (Animal) geschriebene Funktion nahtlos mit einem abgeleiteten Typ (Dog) durch Pointer-Casting zusammenarbeiten kann – die Grundlage der Vererbung in C.
Spickzettel
Wenn ein Struct als erstes Element in ein anderes Struct eingebettet ist, teilen sich beide Structs dieselbe Speicheradresse am Anfang. Dies wird durch den C-Standard garantiert.
typedef struct {
int id;
} Parent;
typedef struct {
Parent parent; // First member
int grade;
} Child;Ein Pointer auf das äußere Struct kann sicher in einen Pointer auf sein erstes Element umgewandelt (gecastet) werden:
Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr; // Safe cast - same addressDies ermöglicht es, einen Pointer eines abgeleiteten Typs an Funktionen zu übergeben, die den Basistyp erwarten:
void print_id(Parent* p) {
printf("ID: %d\n", p->id);
}
Child c;
c.parent.id = 42;
print_id((Parent*)&c); // Works because memory layouts alignDiese Technik ist die Grundlage für Vererbung in C und ermöglicht es, dass Funktionen, die für Basistypen geschrieben wurden, auch mit abgeleiteten Typen arbeiten können.
Probier es selbst
#include <stdio.h>
#include <string.h>
#include "animal.h"
int main() {
// Eingabe lesen
char name[50];
int legs;
scanf("%s", name);
scanf("%d", &legs);
// TODO: Eine Dog Variable erstellen
// TODO: Den Namen des Hundes mit strcpy setzen
// TODO: Die Anzahl der Beine setzen (Zugriff über das eingebettete Animal)
// TODO: Den Namen des Hundes im Format "Dog: <name>" ausgeben
// TODO: Dog* zu Animal* casten und print_animal aufrufen
// Dies demonstriert die "First Member Rule"!
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Object Oriented Programming
1Grundlagen der modularen Programmierung
Header-DateienInclude GuardsQuelldateienStatische FunktionenWiederholung: Modularer Taschenrechner4Kapselung
Konzept der Opaque PointersOpaque Structs definierenGetter und SetterValidierung in SetternRückblick: Die geheime Box2Objekte und Methoden
Structs als ObjekteDer 'Self'-PointerConst-CorrectnessPointer vs. WertHilfsmethodenZusammenfassung: Point Manager5Projekt: Einfaches Bankkonto
Projekt-SetupImplementierung des Kontos3Objekt-Lebenszyklus
Konstruktor-MusterDestruktor-MusterStack-InitialisierungTiefe KopieRückblick: String-Wrapper6Vererbung durch Komposition
Struct-EinbettungDie First-Member-RegelZugriff auf Parent-MemberUpcastingRückblick: Formenhierarchie9Projekt: Formen-Zeichner
ProjektübersichtKreis-Implementierung