A Regra do Primeiro Membro
Parte da seção Object Oriented Programming do Journey de C da Coddy — lição 29 de 61.
Quando você incorpora uma struct como o primeiro membro de outra struct, algo poderoso acontece: o endereço de memória da struct externa é idêntico ao endereço de memória de seu primeiro membro. Isso é garantido pelo padrão C.
Considere nosso exemplo anterior:
typedef struct {
int id;
} Parent;
typedef struct {
Parent parent; // First member
int grade;
} Child;Quando você cria uma variável Child, o membro Parent fica logo no início da memória do Child. Isso significa que um ponteiro para Child aponta para exatamente o mesmo endereço que um ponteiro para o seu membro Parent.
Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr; // Cast seguro!Ambos os ponteiros contêm o mesmo endereço. Isso permite que você passe um Child* para qualquer função que espere um Parent*:
void print_id(Parent* p) {
printf("ID: %d\n", p->id);
}
// No main:
Child c;
c.parent.id = 42;
print_id((Parent*)&c); // Funciona perfeitamenteEsta técnica é a base da herança em C. Uma função escrita para o tipo base pode operar em qualquer tipo derivado, desde que a base seja incorporada primeiro. O cast é seguro porque os layouts de memória se alinham perfeitamente.
Desafio
FácilVamos explorar a regra do primeiro membro construindo uma hierarquia simples de animais. Você criará uma struct base Animal e uma struct derivada Dog, e então demonstrará como um ponteiro para Dog pode ser convertido com segurança para um ponteiro para Animal.
O conceito principal que você aplicará: quando Animal é o primeiro membro de Dog, ambas as structs compartilham o mesmo endereço de memória inicial. Isso significa que você pode passar um Dog* para qualquer função que espere um Animal* através da conversão (casting) do ponteiro.
Você organizará seu código em três arquivos:
animal.h: Defina ambas as structs com guards de inclusão. Sua structAnimaldeve ter um único campo:legs(um inteiro representando o número de patas). Sua structDogdeve incorporarAnimalcomo seu primeiro membro e adicionar um camponame(um array de caracteres de 50 posições). Também declare uma funçãoprint_animalque recebe um parâmetroAnimal*.animal.c: Implemente a funçãoprint_animal. Esta função trabalha com o tipo base — ela só conheceAnimale imprime quantas patas o animal possui.main.c: Crie uma variávelDoge preencha seus campos (tanto as patas do animal incorporado quanto o nome do cão). Em seguida, demonstre a regra do primeiro membro convertendo seuDog*para umAnimal*e passando-o paraprint_animal. Também imprima o nome do cão separadamente para mostrar o quadro completo.
Você receberá duas entradas: o nome do cão (uma string) e o número de patas (um inteiro).
Sua saída deve ser parecida com esta:
Dog: Buddy
Legs: 4Onde Buddy é o nome do cão e 4 é o número de patas. Imprima o nome do cão primeiro a partir de main.c, depois chame print_animal com o ponteiro convertido para exibir as patas.
Isso demonstra como uma função escrita para o tipo base (Animal) pode funcionar perfeitamente com um tipo derivado (Dog) através da conversão de ponteiro — a base da herança em C.
Folha de consulta
Quando uma struct é incorporada como o primeiro membro de outra struct, ambas as structs compartilham o mesmo endereço de memória inicial. Isso é garantido pelo padrão C.
typedef struct {
int id;
} Parent;
typedef struct {
Parent parent; // Primeiro membro
int grade;
} Child;Um ponteiro para a struct externa pode ser convertido com segurança para um ponteiro para seu primeiro membro:
Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr; // Conversão segura - mesmo endereçoIsso permite passar um ponteiro de tipo derivado para funções que esperam o 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 os layouts de memória se alinhamEsta técnica é a base da herança em C, permitindo que funções escritas para tipos base operem em tipos derivados.
Experimente você mesmo
#include <stdio.h>
#include <string.h>
#include "animal.h"
int main() {
// Ler entrada
char name[50];
int legs;
scanf("%s", name);
scanf("%d", &legs);
// TODO: Criar uma variável Dog
// TODO: Definir o nome do cachorro usando strcpy
// TODO: Definir o número de pernas (acesso através do Animal incorporado)
// TODO: Imprimir o nome do cachorro no formato: "Dog: <name>"
// TODO: Realizar o cast de Dog* para Animal* e chamar print_animal
// Isso demonstra a regra do primeiro membro!
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Object Oriented Programming
1Fundamentos da Programação Modular
Arquivos de CabeçalhoInclude GuardsArquivos-FonteFunções EstáticasRecapitulação: Calculadora Modular4Encapsulamento
Conceito de Ponteiros OpacosDefinindo Structs OpacasGetters e SettersValidação em SettersRecapitulação: Caixa Secreta2Objetos e Métodos
Structs como ObjetosO Ponteiro 'Self'Const CorrectnessPonteiro vs ValorMétodos AuxiliaresRecapitulação: Point Manager5Projeto: Conta Bancária Simples
Configuração do ProjetoImplementação da Conta3Ciclo de Vida de Objetos
Padrão de ConstrutorPadrão de DestrutorInicialização na StackCópia ProfundaRecapitulação: String Wrapper6Herança via Composição
Incorporação de StructsA Regra do Primeiro MembroAcessando Membros PaiUpcastingRecapitulação: Hierarquia de Formas9Projeto: Desenhador de Formas
Visão Geral do ProjetoImplementação do Círculo