La règle du premier membre
Fait partie de la section Object Oriented Programming du Journey C de Coddy — leçon 29 sur 61.
Lorsque vous imbriquez une struct en tant que premier membre d'une autre struct, quelque chose de puissant se produit : l'adresse mémoire de la struct externe est identique à l'adresse mémoire de son premier membre. Ceci est garanti par la norme C.
Considérons notre exemple précédent :
typedef struct {
int id;
} Parent;
typedef struct {
Parent parent; // Premier membre
int grade;
} Child;Lorsque vous créez une variable Child, le membre Parent se situe au tout début de la mémoire du Child. Cela signifie qu'un pointeur vers Child pointe vers la même adresse exacte qu'un pointeur vers son membre Parent.
Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr; // Cast sécurisé !Les deux pointeurs contiennent la même adresse. Cela vous permet de passer un Child* à n'importe quelle fonction attendant un Parent* :
void print_id(Parent* p) {
printf("ID: %d\n", p->id);
}
// Dans main :
Child c;
c.parent.id = 42;
print_id((Parent*)&c); // Fonctionne parfaitementCette technique est le fondement de l'héritage en C. Une fonction écrite pour le type de base peut opérer sur n'importe quel type dérivé, tant que la base est incorporée en premier. Le transtypage est sûr car les agencements de mémoire s'alignent parfaitement.
Défi
FacileExplorons la règle du premier membre en construisant une hiérarchie animale simple. Vous allez créer une structure de base Animal et une structure dérivée Dog, puis démontrer comment un pointeur vers Dog peut être converti en toute sécurité en un pointeur vers Animal.
L'idée clé que vous allez appliquer : lorsque Animal est le premier membre de Dog, les deux structures partagent la même adresse mémoire de départ. Cela signifie que vous pouvez passer un Dog* à n'importe quelle fonction attendant un Animal* en convertissant le pointeur.
Vous organiserez votre code sur trois fichiers :
animal.h: Définissez les deux structures avec des gardes d'inclusion. Votre structureAnimaldoit avoir un seul champ :legs(un entier représentant le nombre de pattes). Votre structureDogdoit intégrerAnimalcomme son premier membre et ajouter un champname(un tableau de caractères de 50 caractères). Déclarez également une fonctionprint_animalqui prend un paramètreAnimal*.animal.c: Implémentez la fonctionprint_animal. Cette fonction travaille avec le type de base — elle ne connaît queAnimalet affiche le nombre de pattes de l'animal.main.c: Créez une variableDoget remplissez ses champs (à la fois les pattes de l'animal intégré et le nom du chien). Démontrez ensuite la règle du premier membre en convertissant votreDog*en unAnimal*et en le passant àprint_animal. Affichez également le nom du chien séparément pour montrer l'ensemble complet.
Vous recevrez deux entrées : le nom du chien (une chaîne de caractères) et le nombre de pattes (un entier).
Votre sortie devrait ressembler à ceci :
Dog: Buddy
Legs: 4Où Buddy est le nom du chien et 4 est le nombre de pattes. Affichez d'abord le nom du chien depuis main.c, puis appelez print_animal avec le pointeur converti pour afficher les pattes.
Cela démontre comment une fonction écrite pour le type de base (Animal) peut fonctionner de manière transparente avec un type dérivé (Dog) grâce à la conversion de pointeur — le fondement de l'héritage en C.
Aide-mémoire
Lorsqu'une structure est imbriquée en tant que premier membre d'une autre structure, les deux structures partagent la même adresse mémoire de départ. Ceci est garanti par la norme C.
typedef struct {
int id;
} Parent;
typedef struct {
Parent parent; // Premier membre
int grade;
} Child;Un pointeur vers la structure externe peut être converti en toute sécurité en un pointeur vers son premier membre :
Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr; // Cast sécurisé - même adresseCela permet de passer un pointeur de type dérivé à des fonctions attendant le type de base :
void print_id(Parent* p) {
printf("ID: %d\n", p->id);
}
Child c;
c.parent.id = 42;
print_id((Parent*)&c); // Fonctionne car les dispositions mémoire s'alignentCette technique est le fondement de l'héritage en C, permettant aux fonctions écrites pour les types de base d'opérer sur les types dérivés.
Essayez vous-même
#include <stdio.h>
#include <string.h>
#include "animal.h"
int main() {
// Lire l'entrée
char name[50];
int legs;
scanf("%s", name);
scanf("%d", &legs);
// TODO: Créer une variable Dog
// TODO: Définir le nom du chien en utilisant strcpy
// TODO: Définir le nombre de pattes (accès via l'Animal incorporé)
// TODO: Afficher le nom du chien au format : "Dog: <name>"
// TODO: Caster Dog* en Animal* et appeler print_animal
// Ceci démontre la règle du premier membre !
return 0;
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Object Oriented Programming
1Bases de la programmation modulaire
Fichiers d'en-têteGardes d'inclusionFichiers sourcesFonctions statiquesRécapitulatif : Calculatrice modulaire4Encapsulation
Concept des pointeurs opaquesDéfinir des structures opaquesGetters et settersValidation dans les settersRécapitulatif : La boîte secrète2Objets et méthodes
Structs comme objetsLe pointeur 'Self'Rigueur du mot-clé constPointeur vs ValeurMéthodes utilitairesRécapitulatif : Point Manager5Projet : Compte bancaire simple
Configuration du projetImplémentation du compte8Polymorphisme
Pointeurs de fonctions dans les structuresSimulation de méthodesLe concept d'interfaceImplémentation d'interfacesItération polymorphiqueRécapitulatif : Greeter11Patrons de conception en C
Patron SingletonPatron FabriquePatron ItérateurRécapitulatif : Logger Factory3Cycle de vie des objets
Pattern de constructeurPattern de destructeurInitialisation sur la pileCopie profondeRécapitulatif : String Wrapper6Héritage par composition
Imbrication de structLa règle du premier membreAccès aux membres parentsUpcastingRécapitulatif : Hiérarchie des formes9Projet : Dessinateur de formes
Aperçu du projetImplémentation du cercle