Lista de structs en C: ejercicio resuelto

Lista de structs en C: ejercicio resuelto

Si buscas lista de structs en C ejercicio resuelto, aquí tienes la lista enlazada simple donde cada nodo contiene una struct Contacto y el puntero al siguiente nodo. Este patrón combina la gestión de punteros con datos estructurados: hay que liberar tanto los campos dinámicos de la struct como el nodo mismo.

Enunciado

Define una struct Contacto con campos nombre (array de 32 caracteres), telefono (array de 16 caracteres) y siguiente (puntero al siguiente nodo). Implementa:

  1. lista_agregar(cabeza, nombre, tel): inserta un nodo al final.
  2. lista_buscar(cabeza, nombre): devuelve el puntero al nodo con ese nombre o NULL.
  3. lista_eliminar(cabeza, nombre): elimina el nodo con ese nombre.
  4. lista_imprimir(cabeza): imprime todos los contactos.
  5. lista_liberar(cabeza): libera todos los nodos.

Solución en C

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_NOM 32
#define MAX_TEL 16

typedef struct Contacto {
    char nombre[MAX_NOM];
    char telefono[MAX_TEL];
    struct Contacto *siguiente;
} Contacto;

Contacto *lista_agregar(Contacto *cabeza, const char *nom, const char *tel) {
    Contacto *nuevo = malloc(sizeof(Contacto));
    if (!nuevo) return cabeza;

    strncpy(nuevo->nombre,   nom, MAX_NOM - 1); nuevo->nombre[MAX_NOM - 1]   = '\0';
    strncpy(nuevo->telefono, tel, MAX_TEL - 1); nuevo->telefono[MAX_TEL - 1] = '\0';
    nuevo->siguiente = NULL;

    if (!cabeza) return nuevo;

    /* Recorrer hasta el último nodo */
    Contacto *cur = cabeza;
    while (cur->siguiente) cur = cur->siguiente;
    cur->siguiente = nuevo;
    return cabeza;
}

Contacto *lista_buscar(Contacto *cabeza, const char *nom) {
    for (Contacto *cur = cabeza; cur; cur = cur->siguiente)
        if (strncmp(cur->nombre, nom, MAX_NOM) == 0) return cur;
    return NULL;
}

Contacto *lista_eliminar(Contacto *cabeza, const char *nom) {
    Contacto *prev = NULL, *cur = cabeza;
    while (cur) {
        if (strncmp(cur->nombre, nom, MAX_NOM) == 0) {
            if (prev) prev->siguiente = cur->siguiente;
            else       cabeza         = cur->siguiente;
            free(cur);
            return cabeza;
        }
        prev = cur; cur = cur->siguiente;
    }
    return cabeza;   /* no encontrado */
}

void lista_imprimir(const Contacto *cabeza) {
    int i = 1;
    for (const Contacto *cur = cabeza; cur; cur = cur->siguiente, i++)
        printf("  %d. %-20s %s\n", i, cur->nombre, cur->telefono);
}

void lista_liberar(Contacto *cabeza) {
    Contacto *cur = cabeza;
    while (cur) {
        Contacto *sig = cur->siguiente;
        free(cur);
        cur = sig;
    }
}

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

    lista = lista_agregar(lista, "Ana Martínez",  "600-111-222");
    lista = lista_agregar(lista, "Luis García",   "611-333-444");
    lista = lista_agregar(lista, "Sofía Pérez",   "622-555-666");
    lista = lista_agregar(lista, "Carlos López",  "633-777-888");

    printf("Lista completa:\n");
    lista_imprimir(lista);

    Contacto *c = lista_buscar(lista, "Luis García");
    printf("\nBuscar 'Luis García': %s\n", c ? c->telefono : "no encontrado");

    printf("\nEliminando 'Ana Martínez'...\n");
    lista = lista_eliminar(lista, "Ana Martínez");
    lista_imprimir(lista);

    printf("\nEliminando 'Carlos López'...\n");
    lista = lista_eliminar(lista, "Carlos López");
    lista_imprimir(lista);

    lista_liberar(lista);
    return 0;
}

Resultado esperado

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Lista completa:
  1. Ana Martínez          600-111-222
  2. Luis García           611-333-444
  3. Sofía Pérez           622-555-666
  4. Carlos López          633-777-888

Buscar 'Luis García': 611-333-444

Eliminando 'Ana Martínez'...
  1. Luis García           611-333-444
  2. Sofía Pérez           622-555-666
  3. Carlos López          633-777-888

Eliminando 'Carlos López'...
  1. Luis García           611-333-444
  2. Sofía Pérez           622-555-666

Errores frecuentes

  • No devolver la nueva cabeza cuando se elimina el primer nodo: si el nodo a eliminar es la cabeza, hay que actualizar cabeza = cur->siguiente y devolver el nuevo valor al llamador.
  • Liberar cur antes de guardar cur->siguiente: una vez liberado, cur->siguiente es comportamiento indefinido.
  • No actualizar el puntero siguiente del nodo anterior al eliminar: deja un puntero colgante que apunta al nodo liberado.
  • No liberar todos los nodos al final: cada nodo fue asignado con malloc; hay que recorrer la lista y liberar cada uno.

Aplicación práctica

La lista enlazada de structs es el patrón básico de muchas estructuras de datos reales: tablas hash con encadenamiento para resolver colisiones, colas de eventos en sistemas operativos, gestores de plugins que añaden/eliminan módulos dinámicamente y cachés LRU donde se reordena o elimina la entrada más antigua.

Siguiente ejercicio recomendado

Práctica guiada y libro completo

Si quieres una ruta completa con progresión real de dificultad:

FAQ

¿Por qué lista_eliminar y lista_agregar devuelven la nueva cabeza?

Porque en C no hay referencias: si la cabeza cambia (se elimina el primer nodo o se inserta cuando la lista está vacía), el llamador debe recibir el nuevo puntero. El patrón idiomático es lista = lista_eliminar(lista, nombre).

¿Cuándo usar lista enlazada en lugar de array dinámico?

La lista es preferible cuando las inserciones/eliminaciones en el medio son frecuentes (O(1) con el puntero al nodo previo) y no necesitas acceso aleatorio. El array dinámico es mejor cuando necesitas acceso por índice (O(1)) o cuando la memoria contigua importa para el rendimiento de caché.

¿Cómo detectar fugas de memoria en listas enlazadas?

Con Valgrind: valgrind --leak-check=full ./programa. Cada nodo asignado con malloc que no se libere aparecerá como “definitely lost”. AddressSanitizer (gcc -fsanitize=address) también detecta accesos a memoria ya liberada (use-after-free).