La regla del primer miembro
Parte de la sección Object Oriented Programming del Journey de C de Coddy — lección 29 de 61.
Cuando incrustas una estructura como el primer miembro de otra estructura, sucede algo poderoso: la dirección de memoria de la estructura externa es idéntica a la dirección de memoria de su primer miembro. Esto está garantizado por el estándar de C.
Considera nuestro ejemplo anterior:
typedef struct {
int id;
} Parent;
typedef struct {
Parent parent; // Primer miembro
int grade;
} Child;Cuando creas una variable Child, el miembro Parent se sitúa al principio de la memoria de Child. Esto significa que un puntero a Child apunta exactamente a la misma dirección que un puntero a su miembro Parent.
Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr; // ¡Conversión segura!Ambos punteros contienen la misma dirección. Esto le permite pasar un Child* a cualquier función que espere un Parent*:
void print_id(Parent* p) {
printf("ID: %d\n", p->id);
}
// En main:
Child c;
c.parent.id = 42;
print_id((Parent*)&c); // Funciona perfectamenteEsta técnica es la base de la herencia en C. Una función escrita para el tipo base puede operar en cualquier tipo derivado, siempre que la base esté incrustada primero. El cast es seguro porque las disposiciones de memoria se alinean perfectamente.
Desafío
FácilExploremos la regla del primer miembro construyendo una jerarquía animal simple. Crearás una estructura base Animal y una estructura derivada Dog, luego demostrarás cómo un puntero a Dog puede ser convertido de forma segura a un puntero a Animal.
La idea clave que aplicarás: cuando Animal es el primer miembro de Dog, ambas estructuras comparten la misma dirección de memoria inicial. Esto significa que puedes pasar un Dog* a cualquier función que espere un Animal* mediante la conversión del puntero.
Organizarás tu código en tres archivos:
animal.h: Define ambas estructuras con guardas de inclusión. Tu estructuraAnimaldebe tener un solo campo:legs(un entero que representa el número de patas). Tu estructuraDogdebe incrustarAnimalcomo su primer miembro y añadir un camponame(un arreglo de caracteres de 50 caracteres). También declara una funciónprint_animalque tome un parámetroAnimal*.animal.c: Implementa la funciónprint_animal. Esta función trabaja con el tipo base; solo conoceAnimale imprime cuántas patas tiene el animal.main.c: Crea una variableDogy completa sus campos (tanto las patas del animal incrustado como el nombre del perro). Luego demuestra la regla del primer miembro convirtiendo tuDog*a unAnimal*y pasándolo aprint_animal. También imprime el nombre del perro por separado para mostrar la imagen completa.
Recibirás dos entradas: el nombre del perro (una cadena) y el número de patas (un entero).
Tu salida debería verse así:
Dog: Buddy
Legs: 4Donde Buddy es el nombre del perro y 4 es el número de patas. Imprime primero el nombre del perro desde main.c, luego llama a print_animal con el puntero convertido para mostrar las patas.
Esto demuestra cómo una función escrita para el tipo base (Animal) puede trabajar sin problemas con un tipo derivado (Dog) a través de la conversión de punteros, la base de la herencia en C.
Hoja de referencia
Cuando una estructura se incrusta como el primer miembro de otra estructura, ambas estructuras comparten la misma dirección de memoria inicial. Esto está garantizado por el estándar de C.
typedef struct {
int id;
} Parent;
typedef struct {
Parent parent; // Primer miembro
int grade;
} Child;Un puntero a la estructura externa puede convertirse de forma segura (cast) a un puntero a su primer miembro:
Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr; // Conversión segura - misma direcciónEsto permite pasar un puntero de tipo derivado a funciones que esperan el tipo base:
void print_id(Parent* p) {
printf("ID: %d\n", p->id);
}
Child c;
c.parent.id = 42;
print_id((Parent*)&c); // Funciona porque los diseños de memoria se alineanEsta técnica es la base de la herencia en C, permitiendo que las funciones escritas para tipos base operen sobre tipos derivados.
Pruébalo tú mismo
#include <stdio.h>
#include <string.h>
#include "animal.h"
int main() {
// Leer la entrada
char name[50];
int legs;
scanf("%s", name);
scanf("%d", &legs);
// TODO: Crear una variable Dog
// TODO: Establecer el nombre del perro usando strcpy
// TODO: Establecer el número de patas (acceder a través del Animal embebido)
// TODO: Imprimir el nombre del perro en el formato: "Dog: <name>"
// TODO: Realizar un cast de Dog* a Animal* y llamar a print_animal
// ¡Esto demuestra la regla del primer miembro!
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1Fundamentos de programación modular
Archivos de cabeceraGuardas de inclusiónArchivos fuenteFunciones estáticasRepaso: Calculadora modular4Encapsulamiento
Concepto de punteros opacosDefinición de structs opacosGetters y SettersValidación en settersResumen: Caja secreta7Punteros a funciones
Declaración de punteros a funcionesLlamada a punteros a funcionesTypedef para punteros a funcionesPasar funciones como argumentosRepaso: Dispatch de calculadora10Contenedores genéricos
Repaso de punteros voidWrapper genéricoSwap genéricoComparación genéricaRepaso: Array genérico2Objetos y métodos
Structs como objetosEl puntero 'Self'Const CorrectnessPuntero vs. ValorMétodos auxiliaresResumen: Point Manager5Proyecto: Cuenta bancaria simple
Configuración del proyectoImplementación de la cuenta3Ciclo de vida de los objetos
Patrón ConstructorPatrón DestructorInicialización en StackCopia profundaResumen: String Wrapper6Herencia mediante composición
Incrustación de StructsLa regla del primer miembroAcceso a miembros padreUpcastingResumen: Jerarquía de formas9Proyecto: Dibujador de figuras
Descripción general del proyectoImplementación del círculo