Curso de C · Módulo 12 de 12

Proyecto final en C: listas, pilas y agenda

Qué aprenderás en este módulo

juntar todo lo aprendido en un programa real.

Incluye 4 ejercicios con pistas y solución y un quiz de 5 preguntas. Tiempo estimado: 120 minutos.

1. Estructuras de datos básicas

Programar bien es, en gran parte, elegir cómo guardar los datos.

Estructura Acceso Insertar/borrar Cuándo
Array / array dinámico v[i] O(1) al final O(1) amortizado; en medio O(n) por defecto: lo más simple y rápido
Lista enlazada O(n) O(1) si ya tienes el nodo inserciones/borrados frecuentes en medio
Pila (LIFO) solo el tope push/pop O(1) deshacer, recorrer, evaluar expresiones
Cola (FIFO) frente encolar/desencolar O(1) turnos, buffers

Lista enlazada

typedef struct Nodo {
    int dato;
    struct Nodo *sig;      // NULL en el último
} Nodo;
cabeza ──▶ [1|•]──▶ [2|•]──▶ [3|NULL]

Reglas de supervivencia: 1. Dibuja los nodos y flechas en papel antes de escribir. 2. Cuando cambies un enlace, guarda antes el que vas a perder (Nodo *sig = p->sig;). 3. Siempre trata el caso lista vacía (cabeza == NULL). 4. Para liberar: guarda sig, luego free(p).

Invertir una lista: tres punteros (prev, actual, sig) y un bucle que va girando cada flecha.

Pila como TAD

Una pila solo expone push, pop, vacia. Por dentro puede ser un array dinámico o una lista; quien la usa no lo sabe ni le importa (encapsulación). Se usa para validar paréntesis, evaluar expresiones, deshacer acciones...

2. Cómo atacar un proyecto

  1. Entiende el problema: entradas, salidas, casos raros (vacío, duplicados, errores).
  2. Diseña los datos primero: structs y qué funciones los manejan.
  3. Escribe funciones pequeñas y pruébalas de una en una (empieza por buscar).
  4. Prueba antes de seguir: un programa de 100 líneas que falla es mucho más difícil de depurar que 5 trozos de 20 comprobados.
  5. Maneja errores: malloc→NULL, fopen→NULL, entrada inválida.
  6. Libera todo; comprueba con -fsanitize=address.

3. El proyecto: agenda de contactos (12.3 y 12.4)

Un intérprete de comandos con memoria dinámica y persistencia:

add ana 111      -> ok
add ana 999      -> duplicado
find ana         -> ana: 111
list             -> (todos, por orden alfabético)
guardar a.txt    -> guardados 1
cargar a.txt     -> cargados 1

Usa: struct, array dinámico con realloc (cap 4, ×2), strcmp, memmove, qsort, ficheros de texto con ; como separador, y funciones pequeñas con una única responsabilidad.

4. Siguientes pasos (después del curso)

Con la base de C que tienes, puedes ir en varias direcciones:

  • Estructuras avanzadas: tablas hash, árboles binarios de búsqueda, grafos.
  • Sistema: procesos, señales, sockets (programación de red), hilos (pthreads).
  • Embebido: Arduino/ESP32, STM32 (C puro sobre hardware).
  • Juegos/gráficos: SDL2 o raylib (muy amigable para C).
  • Lectura: The C Programming Language (Kernighan & Ritchie), C Programming: A Modern Approach (K. N. King), y el sitio https://en.cppreference.com/w/c

Retos propuestos para consolidar

  1. Tres en raya en consola (matriz 3×3 y comprobación de ganador).
  2. Gestor de tareas (el proyecto de la agenda, pero con prioridades y fechas).
  3. Calculadora de expresiones (3 + 4 * (2 - 1)) usando dos pilas.
  4. Contador de frecuencia de palabras de un fichero con una tabla hash propia.
  5. Mini intérprete de un lenguaje de 5 instrucciones.

Ejercicios prácticos del módulo 12

Programar se aprende programando: intenta cada ejercicio antes de mirar las pistas, y usa la solución solo para comparar. Marca los que resuelvas y tu progreso se guarda en este navegador.

12.1 Lista enlazada
Implementa una lista enlazada simple de enteros. El main lee numeros hasta 0 (que no se guarda), los inserta AL FINAL, imprime la lista, la invierte (cambiando los enlaces, sin crear nodos nuevos), imprime de nuevo y libera todo: 1 2 3 3 2 1 Funciones a completar: insertar_final, imprimir, invertir, liberar.

Entrada de ejemplo

1 2 3 0

Salida esperada

1 2 3
3 2 1

Tu punto de partida

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

typedef struct Nodo {
    int dato;
    struct Nodo *sig;
} Nodo;

/* Anade x al final y devuelve la cabeza de la lista (puede ser el nodo nuevo si estaba vacia). */
Nodo *insertar_final(Nodo *cabeza, int x) {
    // TODO
    return cabeza;
}

/* Imprime los datos separados por espacio y un salto de linea al final. */
void imprimir(const Nodo *n) {
    // TODO
}

/* Invierte los enlaces y devuelve la nueva cabeza. */
Nodo *invertir(Nodo *cabeza) {
    // TODO
    return cabeza;
}

void liberar(Nodo *cabeza) {
    // TODO
}

int main(void) {
    Nodo *lista = NULL;
    int x;
    while (scanf("%d", &x) == 1 && x != 0) lista = insertar_final(lista, x);
    imprimir(lista);
    lista = invertir(lista);
    imprimir(lista);
    liberar(lista);
    return 0;
}

Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.

Pista 1

Dibuja la lista en papel: cada nodo es una caja con el dato y una flecha al siguiente; el ultimo apunta a NULL.

Pista 2

insertar_final: reserva el nodo, y si hay cabeza recorre con while (p->sig) p = p->sig; para llegar al ultimo.

Pista 3

invertir necesita 3 punteros: prev (empieza NULL), actual y sig (guardado ANTES de cambiar actual->sig).

Pista 4

liberar: guarda p->sig antes de hacer free(p) (despues de liberar ya no puedes leerlo).

Ver solución (inténtalo antes)
#include <stdio.h>
#include <stdlib.h>

typedef struct Nodo {
    int dato;
    struct Nodo *sig;
} Nodo;

Nodo *insertar_final(Nodo *cabeza, int x) {
    Nodo *nuevo = malloc(sizeof *nuevo);
    if (!nuevo) return cabeza;
    nuevo->dato = x;
    nuevo->sig = NULL;
    if (!cabeza) return nuevo;
    Nodo *p = cabeza;
    while (p->sig) p = p->sig;
    p->sig = nuevo;
    return cabeza;
}

void imprimir(const Nodo *n) {
    for (; n; n = n->sig) printf("%d%s", n->dato, n->sig ? " " : "");
    printf("\n");
}

Nodo *invertir(Nodo *cabeza) {
    Nodo *prev = NULL;
    while (cabeza) {
        Nodo *sig = cabeza->sig;
        cabeza->sig = prev;
        prev = cabeza;
        cabeza = sig;
    }
    return prev;
}

void liberar(Nodo *cabeza) {
    while (cabeza) {
        Nodo *sig = cabeza->sig;
        free(cabeza);
        cabeza = sig;
    }
}

int main(void) {
    Nodo *lista = NULL;
    int x;
    while (scanf("%d", &x) == 1 && x != 0) lista = insertar_final(lista, x);
    imprimir(lista);
    lista = invertir(lista);
    imprimir(lista);
    liberar(lista);
    return 0;
}
12.2 Pila (TAD) y parentesis balanceados
Implementa un TIPO ABSTRACTO DE DATOS: una pila de caracteres con array dinamico que crece. pila.h -> interfaz publica (struct opaco), ya escrita pila.c -> TU TRABAJO: define el struct y las funciones main.c -> comprueba si cada linea de la entrada tiene los ()[]{} bien balanceados Para cada linea imprime "balanceado" o "no balanceado".

Entrada de ejemplo

(a[b]{c})

Salida esperada

balanceado

Tu punto de partida

pila.h

#ifndef PILA_H
#define PILA_H

/* Pila de caracteres. El struct es OPACO: los usuarios no ven su interior. */
typedef struct Pila Pila;

Pila *pila_crear(void);
void pila_destruir(Pila *p);
int pila_vacia(const Pila *p);
void pila_push(Pila *p, char c);
char pila_pop(Pila *p);   /* precondicion: la pila no esta vacia */

#endif

pila.c

#include <stdlib.h>
#include "pila.h"

struct Pila {
    char *datos;
    int n;
    int cap;
};

Pila *pila_crear(void) {
    // TODO: reserva el struct (y el array con capacidad inicial, p.ej. 8)
    return NULL;
}

void pila_destruir(Pila *p) {
    // TODO: libera el array y el struct
}

int pila_vacia(const Pila *p) {
    // TODO
    return 1;
}

void pila_push(Pila *p, char c) {
    // TODO: si n == cap, duplica con realloc
}

char pila_pop(Pila *p) {
    // TODO
    return 0;
}

main.c

#include <stdio.h>
#include "pila.h"

static char abre_de(char c) {
    switch (c) {
        case ')': return '(';
        case ']': return '[';
        case '}': return '{';
    }
    return 0;
}

int main(void) {
    char s[256];
    while (fgets(s, sizeof s, stdin)) {
        Pila *p = pila_crear();
        int ok = 1;
        for (int i = 0; s[i] && ok; i++) {
            char c = s[i];
            if (c == '(' || c == '[' || c == '{')
                pila_push(p, c);
            else if (abre_de(c)) {
                if (pila_vacia(p) || pila_pop(p) != abre_de(c)) ok = 0;
            }
        }
        if (!pila_vacia(p)) ok = 0;
        printf("%s\n", ok ? "balanceado" : "no balanceado");
        pila_destruir(p);
    }
    return 0;
}

Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.

Pista 1

El 'tope' es la posicion n-1: push guarda en datos[n] y hace n++; pop hace --n y devuelve datos[n].

Pista 2

pila_crear necesita DOS reservas: el struct y el array. Libera ambos en pila_destruir (primero el array).

Pista 3

Crecimiento: if (p->n == p->cap) { cap *= 2; datos = realloc(datos, cap); }

Ver solución (inténtalo antes)

pila.c

#include <stdlib.h>
#include "pila.h"

struct Pila {
    char *datos;
    int n;
    int cap;
};

Pila *pila_crear(void) {
    Pila *p = malloc(sizeof *p);
    if (!p) return NULL;
    p->cap = 8;
    p->n = 0;
    p->datos = malloc(p->cap);
    if (!p->datos) { free(p); return NULL; }
    return p;
}

void pila_destruir(Pila *p) {
    if (!p) return;
    free(p->datos);
    free(p);
}

int pila_vacia(const Pila *p) {
    return p->n == 0;
}

void pila_push(Pila *p, char c) {
    if (p->n == p->cap) {
        int ncap = p->cap * 2;
        char *tmp = realloc(p->datos, ncap);
        if (!tmp) return;
        p->datos = tmp;
        p->cap = ncap;
    }
    p->datos[p->n++] = c;
}

char pila_pop(Pila *p) {
    return p->datos[--p->n];
}
12.3 PROYECTO: agenda de contactos (en memoria)
Proyecto integrador: una agenda con comandos por la entrada estandar. El main (interprete de comandos) ya esta escrito; tu implementas el motor con un array dinamico de structs: add NOMBRE TEL -> "ok", o "duplicado" si ya existe ese nombre find NOMBRE -> "NOMBRE: TEL" o "no existe" del NOMBRE -> "ok" o "no existe" list -> todos, ordenados alfabeticamente, "NOMBRE: TEL" por linea count -> numero de contactos quit Completa las 5 funciones agenda_*. Usa todo lo aprendido: structs, punteros, malloc/realloc/free, cadenas, memmove y qsort.

Entrada de ejemplo

add ana 111
add luis 222
add ana 333
find ana
find pepe
list
del luis
count
del luis
quit

Salida esperada

ok
ok
duplicado
ana: 111
no existe
ana: 111
luis: 222
ok
1
no existe

Tu punto de partida

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

typedef struct {
    char nombre[32];
    char tel[16];
} Contacto;

typedef struct {
    Contacto *v;   /* array dinamico */
    int n;         /* usados */
    int cap;       /* reservados */
} Agenda;

static int cmp_contacto(const void *a, const void *b) {
    return strcmp(((const Contacto *)a)->nombre, ((const Contacto *)b)->nombre);
}

/* Devuelve un puntero al contacto con ese nombre, o NULL si no existe. */
Contacto *agenda_buscar(const Agenda *a, const char *nombre) {
    // TODO
    return NULL;
}

/* Anade un contacto. Devuelve 1 si lo anade, 0 si ya existia ese nombre. Crece con realloc. */
int agenda_agregar(Agenda *a, const char *nombre, const char *tel) {
    // TODO
    return 0;
}

/* Borra el contacto. Devuelve 1 si existia, 0 si no. (pista: memmove para tapar el hueco) */
int agenda_borrar(Agenda *a, const char *nombre) {
    // TODO
    return 0;
}

/* Imprime "nombre: tel" por orden alfabetico (usa qsort sobre una COPIA con cmp_contacto). */
void agenda_listar(const Agenda *a) {
    // TODO
}

void agenda_liberar(Agenda *a) {
    // TODO
}

int main(void) {
    Agenda ag = {NULL, 0, 0};
    char cmd[16], nombre[32], tel[16];
    while (scanf("%15s", cmd) == 1) {
        if (strcmp(cmd, "add") == 0) {
            scanf("%31s %15s", nombre, tel);
            printf("%s\n", agenda_agregar(&ag, nombre, tel) ? "ok" : "duplicado");
        } else if (strcmp(cmd, "find") == 0) {
            scanf("%31s", nombre);
            Contacto *c = agenda_buscar(&ag, nombre);
            if (c) printf("%s: %s\n", c->nombre, c->tel);
            else printf("no existe\n");
        } else if (strcmp(cmd, "del") == 0) {
            scanf("%31s", nombre);
            printf("%s\n", agenda_borrar(&ag, nombre) ? "ok" : "no existe");
        } else if (strcmp(cmd, "list") == 0) {
            agenda_listar(&ag);
        } else if (strcmp(cmd, "count") == 0) {
            printf("%d\n", ag.n);
        } else if (strcmp(cmd, "quit") == 0) {
            break;
        }
    }
    agenda_liberar(&ag);
    return 0;
}

Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.

Pista 1

Empieza por agenda_buscar (recorrido lineal con strcmp): casi todo lo demas la usa.

Pista 2

agenda_agregar: si n == cap crece (cap ? cap*2 : 4) con realloc en un puntero temporal. Copia las cadenas con snprintf o strncpy.

Pista 3

agenda_borrar: calcula el indice con aritmetica de punteros (c - a->v), y tapa el hueco con memmove(&v[i], &v[i+1], (n-i-1)*sizeof v[0]).

Pista 4

agenda_listar: copia el array (malloc+memcpy), ordena la copia con qsort y libera la copia al acabar.

Ver solución (inténtalo antes)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char nombre[32];
    char tel[16];
} Contacto;

typedef struct {
    Contacto *v;   /* array dinamico */
    int n;         /* usados */
    int cap;       /* reservados */
} Agenda;

static int cmp_contacto(const void *a, const void *b) {
    return strcmp(((const Contacto *)a)->nombre, ((const Contacto *)b)->nombre);
}

Contacto *agenda_buscar(const Agenda *a, const char *nombre) {
    for (int i = 0; i < a->n; i++)
        if (strcmp(a->v[i].nombre, nombre) == 0) return &a->v[i];
    return NULL;
}

int agenda_agregar(Agenda *a, const char *nombre, const char *tel) {
    if (agenda_buscar(a, nombre)) return 0;
    if (a->n == a->cap) {
        int ncap = a->cap ? a->cap * 2 : 4;
        Contacto *tmp = realloc(a->v, ncap * sizeof *tmp);
        if (!tmp) return 0;
        a->v = tmp;
        a->cap = ncap;
    }
    snprintf(a->v[a->n].nombre, sizeof a->v[a->n].nombre, "%s", nombre);
    snprintf(a->v[a->n].tel, sizeof a->v[a->n].tel, "%s", tel);
    a->n++;
    return 1;
}

int agenda_borrar(Agenda *a, const char *nombre) {
    Contacto *c = agenda_buscar(a, nombre);
    if (!c) return 0;
    int i = (int)(c - a->v);
    memmove(&a->v[i], &a->v[i + 1], (a->n - i - 1) * sizeof a->v[0]);
    a->n--;
    return 1;
}

void agenda_listar(const Agenda *a) {
    if (a->n == 0) return;
    Contacto *copia = malloc(a->n * sizeof *copia);
    if (!copia) return;
    memcpy(copia, a->v, a->n * sizeof *copia);
    qsort(copia, a->n, sizeof *copia, cmp_contacto);
    for (int i = 0; i < a->n; i++) printf("%s: %s\n", copia[i].nombre, copia[i].tel);
    free(copia);
}

void agenda_liberar(Agenda *a) {
    free(a->v);
    a->v = NULL;
    a->n = a->cap = 0;
}

int main(void) {
    Agenda ag = {NULL, 0, 0};
    char cmd[16], nombre[32], tel[16];
    while (scanf("%15s", cmd) == 1) {
        if (strcmp(cmd, "add") == 0) {
            scanf("%31s %15s", nombre, tel);
            printf("%s\n", agenda_agregar(&ag, nombre, tel) ? "ok" : "duplicado");
        } else if (strcmp(cmd, "find") == 0) {
            scanf("%31s", nombre);
            Contacto *c = agenda_buscar(&ag, nombre);
            if (c) printf("%s: %s\n", c->nombre, c->tel);
            else printf("no existe\n");
        } else if (strcmp(cmd, "del") == 0) {
            scanf("%31s", nombre);
            printf("%s\n", agenda_borrar(&ag, nombre) ? "ok" : "no existe");
        } else if (strcmp(cmd, "list") == 0) {
            agenda_listar(&ag);
        } else if (strcmp(cmd, "count") == 0) {
            printf("%d\n", ag.n);
        } else if (strcmp(cmd, "quit") == 0) {
            break;
        }
    }
    agenda_liberar(&ag);
    return 0;
}
12.4 PROYECTO: guardar y cargar la agenda
Ultimo paso del proyecto: persistencia. El codigo de 12.3 ya esta resuelto en tu fichero; anade: guardar FICHERO -> escribe "NOMBRE;TEL" por linea; imprime "guardados N" o "error de fichero" cargar FICHERO -> VACIA la agenda y carga el fichero; imprime "cargados N" o "error de fichero" Implementa agenda_guardar y agenda_cargar (los comandos en main ya estan conectados). Cuando termines tienes un programa real de ~120 lineas: tu primer proyecto en C.

Entrada de ejemplo

add ana 111
add luis 222
guardar a.txt
del ana
cargar a.txt
list
quit

Salida esperada

ok
ok
guardados 2
ok
cargados 2
ana: 111
luis: 222

Tu punto de partida

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

typedef struct {
    char nombre[32];
    char tel[16];
} Contacto;

typedef struct {
    Contacto *v;   /* array dinamico */
    int n;         /* usados */
    int cap;       /* reservados */
} Agenda;

static int cmp_contacto(const void *a, const void *b) {
    return strcmp(((const Contacto *)a)->nombre, ((const Contacto *)b)->nombre);
}

Contacto *agenda_buscar(const Agenda *a, const char *nombre) {
    for (int i = 0; i < a->n; i++)
        if (strcmp(a->v[i].nombre, nombre) == 0) return &a->v[i];
    return NULL;
}

int agenda_agregar(Agenda *a, const char *nombre, const char *tel) {
    if (agenda_buscar(a, nombre)) return 0;
    if (a->n == a->cap) {
        int ncap = a->cap ? a->cap * 2 : 4;
        Contacto *tmp = realloc(a->v, ncap * sizeof *tmp);
        if (!tmp) return 0;
        a->v = tmp;
        a->cap = ncap;
    }
    snprintf(a->v[a->n].nombre, sizeof a->v[a->n].nombre, "%s", nombre);
    snprintf(a->v[a->n].tel, sizeof a->v[a->n].tel, "%s", tel);
    a->n++;
    return 1;
}

int agenda_borrar(Agenda *a, const char *nombre) {
    Contacto *c = agenda_buscar(a, nombre);
    if (!c) return 0;
    int i = (int)(c - a->v);
    memmove(&a->v[i], &a->v[i + 1], (a->n - i - 1) * sizeof a->v[0]);
    a->n--;
    return 1;
}

void agenda_listar(const Agenda *a) {
    if (a->n == 0) return;
    Contacto *copia = malloc(a->n * sizeof *copia);
    if (!copia) return;
    memcpy(copia, a->v, a->n * sizeof *copia);
    qsort(copia, a->n, sizeof *copia, cmp_contacto);
    for (int i = 0; i < a->n; i++) printf("%s: %s\n", copia[i].nombre, copia[i].tel);
    free(copia);
}

void agenda_liberar(Agenda *a) {
    free(a->v);
    a->v = NULL;
    a->n = a->cap = 0;
}

/* Guarda la agenda en un fichero de texto, una linea "nombre;tel" por contacto.
   Devuelve cuantos contactos guardo, o -1 si no pudo abrir el fichero. */
int agenda_guardar(const Agenda *a, const char *fichero) {
    // TODO
    return -1;
}

/* Vacia la agenda y carga el fichero. Devuelve cuantos cargo, o -1 si no existe. */
int agenda_cargar(Agenda *a, const char *fichero) {
    // TODO (reutiliza agenda_agregar)
    return -1;
}

int main(void) {
    Agenda ag = {NULL, 0, 0};
    char cmd[16], nombre[32], tel[16];
    char fichero[64];
    while (scanf("%15s", cmd) == 1) {
        if (strcmp(cmd, "add") == 0) {
            scanf("%31s %15s", nombre, tel);
            printf("%s\n", agenda_agregar(&ag, nombre, tel) ? "ok" : "duplicado");
        } else if (strcmp(cmd, "find") == 0) {
            scanf("%31s", nombre);
            Contacto *c = agenda_buscar(&ag, nombre);
            if (c) printf("%s: %s\n", c->nombre, c->tel);
            else printf("no existe\n");
        } else if (strcmp(cmd, "del") == 0) {
            scanf("%31s", nombre);
            printf("%s\n", agenda_borrar(&ag, nombre) ? "ok" : "no existe");
        } else if (strcmp(cmd, "list") == 0) {
            agenda_listar(&ag);
        } else if (strcmp(cmd, "count") == 0) {
            printf("%d\n", ag.n);
        } else if (strcmp(cmd, "guardar") == 0) {
            scanf("%63s", fichero);
            int k = agenda_guardar(&ag, fichero);
            if (k < 0) printf("error de fichero\n");
            else printf("guardados %d\n", k);
        } else if (strcmp(cmd, "cargar") == 0) {
            scanf("%63s", fichero);
            int k = agenda_cargar(&ag, fichero);
            if (k < 0) printf("error de fichero\n");
            else printf("cargados %d\n", k);
        } else if (strcmp(cmd, "quit") == 0) {
            break;
        }
    }
    agenda_liberar(&ag);
    return 0;
}

Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.

Pista 1

guardar: fopen("w"), un fprintf(f, "%s;%s\n", ...) por contacto, fclose. Devuelve a->n.

Pista 2

cargar: fopen("r"); pon a->n = 0 (vaciar) y lee con fscanf(f, " %31[^;];%15s", nombre, tel) == 2 en bucle (el espacio inicial del formato salta el salto de linea anterior).

Pista 3

Por cada registro leido llama a agenda_agregar, que ya sabe crecer el array.

Ver solución (inténtalo antes)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char nombre[32];
    char tel[16];
} Contacto;

typedef struct {
    Contacto *v;   /* array dinamico */
    int n;         /* usados */
    int cap;       /* reservados */
} Agenda;

static int cmp_contacto(const void *a, const void *b) {
    return strcmp(((const Contacto *)a)->nombre, ((const Contacto *)b)->nombre);
}

Contacto *agenda_buscar(const Agenda *a, const char *nombre) {
    for (int i = 0; i < a->n; i++)
        if (strcmp(a->v[i].nombre, nombre) == 0) return &a->v[i];
    return NULL;
}

int agenda_agregar(Agenda *a, const char *nombre, const char *tel) {
    if (agenda_buscar(a, nombre)) return 0;
    if (a->n == a->cap) {
        int ncap = a->cap ? a->cap * 2 : 4;
        Contacto *tmp = realloc(a->v, ncap * sizeof *tmp);
        if (!tmp) return 0;
        a->v = tmp;
        a->cap = ncap;
    }
    snprintf(a->v[a->n].nombre, sizeof a->v[a->n].nombre, "%s", nombre);
    snprintf(a->v[a->n].tel, sizeof a->v[a->n].tel, "%s", tel);
    a->n++;
    return 1;
}

int agenda_borrar(Agenda *a, const char *nombre) {
    Contacto *c = agenda_buscar(a, nombre);
    if (!c) return 0;
    int i = (int)(c - a->v);
    memmove(&a->v[i], &a->v[i + 1], (a->n - i - 1) * sizeof a->v[0]);
    a->n--;
    return 1;
}

void agenda_listar(const Agenda *a) {
    if (a->n == 0) return;
    Contacto *copia = malloc(a->n * sizeof *copia);
    if (!copia) return;
    memcpy(copia, a->v, a->n * sizeof *copia);
    qsort(copia, a->n, sizeof *copia, cmp_contacto);
    for (int i = 0; i < a->n; i++) printf("%s: %s\n", copia[i].nombre, copia[i].tel);
    free(copia);
}

void agenda_liberar(Agenda *a) {
    free(a->v);
    a->v = NULL;
    a->n = a->cap = 0;
}

int agenda_guardar(const Agenda *a, const char *fichero) {
    FILE *f = fopen(fichero, "w");
    if (!f) return -1;
    for (int i = 0; i < a->n; i++) fprintf(f, "%s;%s\n", a->v[i].nombre, a->v[i].tel);
    fclose(f);
    return a->n;
}

int agenda_cargar(Agenda *a, const char *fichero) {
    FILE *f = fopen(fichero, "r");
    if (!f) return -1;
    a->n = 0;
    char nombre[32], tel[16];
    int cargados = 0;
    while (fscanf(f, " %31[^;];%15s", nombre, tel) == 2)
        if (agenda_agregar(a, nombre, tel)) cargados++;
    fclose(f);
    return cargados;
}

int main(void) {
    Agenda ag = {NULL, 0, 0};
    char cmd[16], nombre[32], tel[16];
    char fichero[64];
    while (scanf("%15s", cmd) == 1) {
        if (strcmp(cmd, "add") == 0) {
            scanf("%31s %15s", nombre, tel);
            printf("%s\n", agenda_agregar(&ag, nombre, tel) ? "ok" : "duplicado");
        } else if (strcmp(cmd, "find") == 0) {
            scanf("%31s", nombre);
            Contacto *c = agenda_buscar(&ag, nombre);
            if (c) printf("%s: %s\n", c->nombre, c->tel);
            else printf("no existe\n");
        } else if (strcmp(cmd, "del") == 0) {
            scanf("%31s", nombre);
            printf("%s\n", agenda_borrar(&ag, nombre) ? "ok" : "no existe");
        } else if (strcmp(cmd, "list") == 0) {
            agenda_listar(&ag);
        } else if (strcmp(cmd, "count") == 0) {
            printf("%d\n", ag.n);
        } else if (strcmp(cmd, "guardar") == 0) {
            scanf("%63s", fichero);
            int k = agenda_guardar(&ag, fichero);
            if (k < 0) printf("error de fichero\n");
            else printf("guardados %d\n", k);
        } else if (strcmp(cmd, "cargar") == 0) {
            scanf("%63s", fichero);
            int k = agenda_cargar(&ag, fichero);
            if (k < 0) printf("error de fichero\n");
            else printf("cargados %d\n", k);
        } else if (strcmp(cmd, "quit") == 0) {
            break;
        }
    }
    agenda_liberar(&ag);
    return 0;
}

Quiz del módulo 12

5 preguntas para comprobar que has fijado los conceptos. Las opciones se barajan cada vez.