Volver a Labs
Avanzado

Estructuras en C — struct, typedef y gestión de memoria

2–3 horas Actualizado Junio 2026
C Struct Memoria Estructuras de Datos Sistemas

Introducción

C no tiene clases, pero tiene structs — una forma de agrupar variables de distintos tipos bajo un mismo nombre. Son el mecanismo que permite modelar entidades del mundo real: una Persona con nombre y edad, un Producto con precio y stock, un nodo de una lista enlazada.

Combinadas con punteros y gestión dinámica de memoria, las structs son la base de todas las estructuras de datos clásicas: listas enlazadas, pilas, colas, árboles, tablas hash.

¿Qué vas a aprender en este lab?

  • Declarar e inicializar structs
  • Usar typedef para simplificar la sintaxis
  • Pasar structs a funciones por valor y por puntero
  • Anidar structs
  • Combinar structs con malloc para estructuras dinámicas
  • Implementar una lista enlazada simple

Requisitos previos: fundamentos de C y punteros (especialmente malloc, free, y aritmética de punteros).


1. Declaración básica

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

struct Persona {
    char   nombre[50];
    int    edad;
    double altura;
};

int main(void) {
    /* Declarar e inicializar con inicializador designado (C99) */
    struct Persona julian = {
        .nombre = "Julian",
        .edad   = 25,
        .altura = 1.78
    };

    /* Acceso con el operador punto */
    printf("Nombre: %s\n",  julian.nombre);  /* Julian */
    printf("Edad:   %d\n",  julian.edad);    /* 25 */
    printf("Altura: %.2f\n", julian.altura); /* 1.78 */

    /* Modificar un campo */
    julian.edad = 26;

    return 0;
}

sizeof de una struct

printf("Tamaño de Persona: %zu bytes\n", sizeof(struct Persona));
/* 50 + 4 + 8 = 62, pero el compilador puede añadir padding para alinear
   → normalmente 64 bytes */

2. typedef — simplificar la sintaxis

Sin typedef debes escribir struct Persona cada vez. Con typedef defines un alias:

typedef struct {
    char   nombre[50];
    int    edad;
    double altura;
} Persona;   /* Persona es ahora un tipo como int o double */

/* Ahora puedes escribir: */
Persona julian = { .nombre = "Julian", .edad = 25, .altura = 1.78 };
Persona ana    = { .nombre = "Ana",    .edad = 30, .altura = 1.65 };

Para structs que se referencian a sí mismas (como nodos de lista), necesitas el nombre de la struct:

typedef struct Nodo {
    int          valor;
    struct Nodo *siguiente;   /* apunta a otro Nodo — necesita el nombre */
} Nodo;

3. Structs y funciones

Paso por valor (copia)

void imprimir_persona(Persona p) {
    printf("%-20s %3d años  %.2fm\n", p.nombre, p.edad, p.altura);
}

/* Se pasa una copia completa — modificar dentro no afecta al original */
Persona julian = { .nombre = "Julian", .edad = 25, .altura = 1.78 };
imprimir_persona(julian);

Paso por puntero (referencia — eficiente y modificable)

void cumpleanios(Persona *p) {
    p->edad++;   /* operador flecha: equivale a (*p).edad++ */
}

void inicializar(Persona *p, const char *nombre, int edad, double altura) {
    strncpy(p->nombre, nombre, sizeof(p->nombre) - 1);
    p->nombre[sizeof(p->nombre) - 1] = '\0';  /* asegura terminación */
    p->edad   = edad;
    p->altura = altura;
}

Persona p;
inicializar(&p, "Carlos", 28, 1.75);
cumpleanios(&p);
printf("%s tiene %d años\n", p->nombre, p->edad);  /* Carlos tiene 29 años */

Regla de oro: pasa structs por puntero (const Persona *p para solo lectura, Persona *p para modificar) — es más eficiente que copiar toda la struct.


4. Arrays de structs

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

typedef struct {
    char   nombre[30];
    double precio;
    int    stock;
} Producto;

void imprimir_catalogo(const Producto *productos, int n) {
    printf("%-25s %8s %6s\n", "Producto", "Precio", "Stock");
    printf("%-25s %8s %6s\n", "--------", "------", "-----");
    for (int i = 0; i < n; i++) {
        printf("%-25s %8.2f %6d\n",
               productos[i].nombre,
               productos[i].precio,
               productos[i].stock);
    }
}

double valor_inventario(const Producto *productos, int n) {
    double total = 0.0;
    for (int i = 0; i < n; i++) {
        total += productos[i].precio * productos[i].stock;
    }
    return total;
}

int main(void) {
    Producto catalogo[] = {
        { .nombre = "Laptop",   .precio = 1200.0, .stock = 5 },
        { .nombre = "Mouse",    .precio =   25.0, .stock = 50 },
        { .nombre = "Teclado",  .precio =   80.0, .stock = 30 },
        { .nombre = "Monitor",  .precio =  400.0, .stock = 10 },
    };
    int n = sizeof(catalogo) / sizeof(catalogo[0]);

    imprimir_catalogo(catalogo, n);
    printf("\nValor total: $%.2f\n", valor_inventario(catalogo, n));

    return 0;
}

Ejercicio 1

Amplía el programa de catálogo con:

  • buscar_por_nombre(Producto *cat, int n, const char *nombre) → puntero al producto o NULL
  • ordenar_por_precio(Producto *cat, int n) → ordena en su lugar con qsort
  • aplicar_descuento(Producto *cat, int n, double porcentaje) → reduce precio de todos

5. Structs anidadas

typedef struct {
    double latitud;
    double longitud;
} Coordenada;

typedef struct {
    char       nombre[50];
    char       pais[30];
    int        poblacion;
    Coordenada ubicacion;
} Ciudad;

Ciudad bogota = {
    .nombre    = "Bogotá",
    .pais      = "Colombia",
    .poblacion = 7412566,
    .ubicacion = { .latitud = 4.7110, .longitud = -74.0721 }
};

printf("%s está en (%.4f, %.4f)\n",
       bogota.nombre,
       bogota.ubicacion.latitud,
       bogota.ubicacion.longitud);
/* Bogotá está en (4.7110, -74.0721) */

6. Structs con memoria dinámica

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char  *nombre;   /* puntero — tamaño dinámico */
    int    edad;
    char **emails;   /* array dinámico de strings */
    int    num_emails;
} Usuario;

Usuario *usuario_crear(const char *nombre, int edad) {
    Usuario *u = malloc(sizeof(Usuario));
    if (!u) return NULL;

    u->nombre = malloc(strlen(nombre) + 1);
    strcpy(u->nombre, nombre);

    u->edad       = edad;
    u->emails     = NULL;
    u->num_emails = 0;

    return u;
}

void usuario_agregar_email(Usuario *u, const char *email) {
    u->emails = realloc(u->emails, (u->num_emails + 1) * sizeof(char *));
    u->emails[u->num_emails] = malloc(strlen(email) + 1);
    strcpy(u->emails[u->num_emails], email);
    u->num_emails++;
}

void usuario_destruir(Usuario *u) {
    if (!u) return;
    free(u->nombre);
    for (int i = 0; i < u->num_emails; i++) free(u->emails[i]);
    free(u->emails);
    free(u);
}

int main(void) {
    Usuario *u = usuario_crear("Julian", 25);
    usuario_agregar_email(u, "[email protected]");
    usuario_agregar_email(u, "[email protected]");

    printf("Usuario: %s (%d años)\n", u->nombre, u->edad);
    for (int i = 0; i < u->num_emails; i++) {
        printf("  Email %d: %s\n", i + 1, u->emails[i]);
    }

    usuario_destruir(u);
    return 0;
}

7. Lista enlazada

Una lista enlazada es la primera estructura de datos dinámica que se construye con structs y punteros. Cada nodo contiene un valor y un puntero al siguiente nodo.

#include <stdio.h>
#include <stdlib.h>

typedef struct Nodo {
    int          valor;
    struct Nodo *siguiente;
} Nodo;

/* Crear un nodo */
Nodo *nodo_crear(int valor) {
    Nodo *n = malloc(sizeof(Nodo));
    n->valor     = valor;
    n->siguiente = NULL;
    return n;
}

/* Insertar al frente — O(1) */
Nodo *lista_insertar_frente(Nodo *cabeza, int valor) {
    Nodo *nuevo   = nodo_crear(valor);
    nuevo->siguiente = cabeza;
    return nuevo;   /* la nueva cabeza */
}

/* Insertar al final — O(n) */
Nodo *lista_insertar_final(Nodo *cabeza, int valor) {
    Nodo *nuevo = nodo_crear(valor);
    if (!cabeza) return nuevo;

    Nodo *actual = cabeza;
    while (actual->siguiente) actual = actual->siguiente;
    actual->siguiente = nuevo;
    return cabeza;
}

/* Eliminar un valor */
Nodo *lista_eliminar(Nodo *cabeza, int valor) {
    if (!cabeza) return NULL;

    if (cabeza->valor == valor) {
        Nodo *nueva_cabeza = cabeza->siguiente;
        free(cabeza);
        return nueva_cabeza;
    }

    Nodo *actual = cabeza;
    while (actual->siguiente && actual->siguiente->valor != valor) {
        actual = actual->siguiente;
    }
    if (actual->siguiente) {
        Nodo *a_eliminar   = actual->siguiente;
        actual->siguiente  = a_eliminar->siguiente;
        free(a_eliminar);
    }
    return cabeza;
}

/* Imprimir */
void lista_imprimir(const Nodo *cabeza) {
    for (const Nodo *n = cabeza; n; n = n->siguiente) {
        printf("%d", n->valor);
        if (n->siguiente) printf(" → ");
    }
    printf(" → NULL\n");
}

/* Liberar toda la lista */
void lista_destruir(Nodo *cabeza) {
    while (cabeza) {
        Nodo *siguiente = cabeza->siguiente;
        free(cabeza);
        cabeza = siguiente;
    }
}

int main(void) {
    Nodo *lista = NULL;

    lista = lista_insertar_final(lista, 10);
    lista = lista_insertar_final(lista, 20);
    lista = lista_insertar_final(lista, 30);
    lista = lista_insertar_frente(lista, 5);
    lista_imprimir(lista);  /* 5 → 10 → 20 → 30 → NULL */

    lista = lista_eliminar(lista, 20);
    lista_imprimir(lista);  /* 5 → 10 → 30 → NULL */

    lista_destruir(lista);
    return 0;
}

Ejercicio 2

Amplía la lista enlazada con:

  • lista_longitud(Nodo *cabeza) → número de nodos
  • lista_invertir(Nodo *cabeza) → invierte la lista en su lugar y devuelve la nueva cabeza
  • lista_buscar(Nodo *cabeza, int valor) → puntero al nodo con ese valor o NULL
  • lista_a_array(Nodo *cabeza, int *n) → aloja un array dinámico con los valores y guarda el tamaño en *n

8. enum y union

enum — constantes con nombre

typedef enum {
    LUNES = 1,
    MARTES,
    MIERCOLES,
    JUEVES,
    VIERNES,
    SABADO,
    DOMINGO
} DiaSemana;

DiaSemana hoy = MIERCOLES;

if (hoy == SABADO || hoy == DOMINGO) {
    printf("Fin de semana\n");
} else {
    printf("Día laboral: %d\n", hoy);  /* 3 */
}

union — misma memoria, distintos tipos

Un union almacena varios tipos en el mismo espacio de memoria. Solo un campo está activo a la vez. Útil en protocolos de red, parseo binario y código de sistemas.

typedef union {
    int    entero;
    float  decimal;
    char   bytes[4];
} Valor32;

Valor32 v;
v.entero = 0x41200000;  /* representación binaria de 10.0f en IEEE 754 */
printf("Como float:  %f\n", v.decimal);   /* 10.000000 */
printf("Primer byte: 0x%02X\n", (unsigned char)v.bytes[0]);

/* sizeof(union) = sizeof del miembro más grande */
printf("Tamaño: %zu\n", sizeof(Valor32));  /* 4 */

9. Errores Comunes

Error 1 — Usar struct después de free

Persona *p = malloc(sizeof(Persona));
strcpy(p->nombre, "Julian");
free(p);

/* p->nombre ahora apunta a memoria liberada */
printf("%s\n", p->nombre);  /* comportamiento indefinido */

/* */
free(p);
p = NULL;

Error 2 — Copiar structs con punteros internos (shallow copy)

typedef struct { char *nombre; } Persona;

Persona original;
original.nombre = malloc(50);
strcpy(original.nombre, "Julian");

/* Copia superficial: ambas comparten el mismo puntero nombre */
Persona copia = original;
free(original.nombre);  /* libera también el nombre de copia */
printf("%s\n", copia.nombre);  /* USE AFTER FREE */

/* Copia profunda: duplica el string */
Persona copia;
copia.nombre = malloc(strlen(original.nombre) + 1);
strcpy(copia.nombre, original.nombre);

Error 3 — Olvidar liberar campos dinámicos antes de liberar la struct

/* Memory leak: el campo nombre no se libera */
free(usuario);

/* Libera los campos primero, luego la struct */
free(usuario->nombre);
free(usuario->emails);
free(usuario);

Resumen

Concepto Sintaxis
Declarar struct typedef struct { ... } Nombre;
Acceso por valor p.campo
Acceso por puntero p->campo o (*p).campo
Struct dinámica Nombre *p = malloc(sizeof(Nombre));
Liberar free(campos_dinámicos); free(p);
enum typedef enum { A, B, C } Tipo;
union typedef union { int i; float f; } U;

Próximos pasos

  • Listas dobles y circulares: extensiones naturales de la lista enlazada
  • Árbol binario de búsqueda: structs recursivas con dos punteros hijo
  • Tabla hash: combina arrays de punteros a listas enlazadas para acceso O(1)
  • C para sistemas: sockets, señales, procesos con fork y exec