Curso de C · Módulo 10 de 12

Ficheros en C: leer y escribir archivos

Qué aprenderás en este módulo

que tus datos sobrevivan al programa.

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

1. El ciclo: abrir → usar → cerrar

FILE *f = fopen("datos.txt", "r");
if (f == NULL) {                       // SIEMPRE comprobar
    perror("datos.txt");               // imprime el motivo ("No such file...")
    return 1;
}
... leer / escribir ...
fclose(f);

Modos de fopen

Modo Significado Si no existe Si existe
"r" lectura falla (NULL) lee desde el inicio
"w" escritura lo crea lo borra (trunca)
"a" añadir al final lo crea conserva y añade
"r+" leer y escribir falla conserva
"rb", "wb"... igual, en binario

En Windows usa b para ficheros binarios; en texto, \r\n se traduce a \n automáticamente.

2. Ficheros de texto

Función Para
fprintf(f, "%d %s\n", n, s) escribir con formato
fscanf(f, "%d", &n) leer con formato (devuelve cuántos leyó)
fgets(buf, tam, f) leer una línea (devuelve NULL al acabar)
fputs(s, f) / fputc(c, f) escribir cadena / carácter
fgetc(f) leer un carácter (devuelve int; EOF al final)

Bucles de lectura típicos:

// línea a línea
char linea[256];
while (fgets(linea, sizeof linea, f)) { ... }

// valores
int x;
while (fscanf(f, "%d", &x) == 1) { ... }

// carácter a carácter
int ch;                                    // int, no char (por EOF)
while ((ch = fgetc(f)) != EOF) { ... }

⚠️ No hagas while (!feof(f)): feof solo se activa después de intentar leer más allá del final, y procesarás una iteración de más. Comprueba el valor de retorno de la lectura.

Truco de fscanf para CSV

fscanf(f, " %31[^;];%d", nombre, &edad): %[^;] lee hasta el ;; el espacio inicial salta saltos de línea pendientes.

3. Ficheros binarios

Vuelcas bytes tal cual están en memoria: rápido y compacto.

fwrite(arr, sizeof arr[0], n, f);          // escribe n elementos
size_t leidos = fread(arr, sizeof arr[0], n, f);   // devuelve cuántos leyó

Ventajas: velocidad y simplicidad. Inconvenientes: no se puede abrir con un editor, y no es portable entre sistemas (tamaños, endianness, padding). Perfecto para cachés y datos propios; mal para intercambiar.

4. Posición dentro del fichero

ftell(f) (dónde estoy), fseek(f, desplazamiento, SEEK_SET/SEEK_CUR/SEEK_END), rewind(f). Truco para saber el tamaño: fseek(f, 0, SEEK_END); long n = ftell(f); rewind(f);

5. Entrada/salida estándar son ficheros

stdin, stdout, stderr son FILE * ya abiertos. Los errores van a stderr: fprintf(stderr, "Error: ...\n"). Así se pueden redirigir por separado (prog > salida.txt).

6. Argumentos de línea de comandos

int main(int argc, char *argv[]) {
    if (argc < 2) { fprintf(stderr, "Uso: %s fichero\n", argv[0]); return 1; }
    FILE *f = fopen(argv[1], "r");

Buenas prácticas

  • Comprueba todos los fopen; cierra todo lo que abras.
  • Guarda de forma atómica lo importante: escribe a un fichero temporal y luego renómbralo (rename).
  • Texto > binario salvo que necesites velocidad: puedes depurar con cualquier editor.

Prueba esto

  1. Programa copiar origen destino que copie un fichero byte a byte.
  2. Cuenta las líneas de un fichero con fgets (cuidado con líneas más largas que el buffer).
  3. Guarda y carga un array de double en binario y compara.

Ejercicios prácticos del módulo 10

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.

10.1 Escribir y volver a leer
Lee n y n enteros. Guardalos en el fichero "datos.txt" (uno por linea) con fopen/fprintf/fclose. Despues reabre el fichero para LEER, suma los valores leyendolos con fscanf y muestra: suma=21

Entrada de ejemplo

6
1 2 3 4 5 6

Salida esperada

suma=21

Tu punto de partida

#include <stdio.h>

int main(void) {
    int n, x;
    scanf("%d", &n);
    // TODO 1: abre "datos.txt" en modo "w", escribe los n numeros, cierra

    // TODO 2: abre "datos.txt" en modo "r", suma con fscanf hasta EOF, cierra

    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

fopen devuelve NULL si falla: compruebalo siempre.

Pista 2

fprintf(f, ...) es como printf pero escribe en el fichero f.

Pista 3

fscanf devuelve cuantos valores leyo: while (fscanf(f, "%d", &x) == 1) { ... }

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

int main(void) {
    int n, x;
    scanf("%d", &n);
    FILE *f = fopen("datos.txt", "w");
    if (!f) return 1;
    for (int i = 0; i < n; i++) {
        scanf("%d", &x);
        fprintf(f, "%d\n", x);
    }
    fclose(f);

    f = fopen("datos.txt", "r");
    if (!f) return 1;
    long long suma = 0;
    while (fscanf(f, "%d", &x) == 1) suma += x;
    fclose(f);
    printf("suma=%lld\n", suma);
    return 0;
}
10.2 Estilo wc: lineas, palabras, caracteres
El fichero "texto.txt" ya existe en el directorio de trabajo. Leelo caracter a caracter con fgetc y muestra cuantas lineas (numero de '\n'), palabras (secuencias de caracteres no blancos) y caracteres tiene, como hace el comando wc: lineas=2 palabras=5 caracteres=25 Si el fichero no existe, imprime: error: no se puede abrir texto.txt

Entrada de ejemplo

(ninguna)

Salida esperada

lineas=2 palabras=5 caracteres=25

Tu punto de partida

#include <stdio.h>
#include <ctype.h>

int main(void) {
    FILE *f = fopen("texto.txt", "r");
    // TODO: comprueba f, recorre con fgetc, cuenta, cierra e imprime

    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

fgetc devuelve un int (no un char) porque EOF es -1: guarda el resultado en un int.

Pista 2

Para contar palabras, recuerda si 'estas dentro de una palabra': al pasar de blanco a no-blanco, cuenta una.

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

int main(void) {
    FILE *f = fopen("texto.txt", "r");
    if (!f) {
        printf("error: no se puede abrir texto.txt\n");
        return 0;
    }
    int ch, lineas = 0, palabras = 0, chars = 0, dentro = 0;
    while ((ch = fgetc(f)) != EOF) {
        chars++;
        if (ch == '\n') lineas++;
        if (isspace(ch)) dentro = 0;
        else if (!dentro) { dentro = 1; palabras++; }
    }
    fclose(f);
    printf("lineas=%d palabras=%d caracteres=%d\n", lineas, palabras, chars);
    return 0;
}
10.3 Ficheros binarios con structs
Lee n y despues n lineas "nombre edad". Guarda el array de structs de golpe en "gente.bin" con fwrite. Reabre el fichero, cargalo con fread en OTRO array, y muestra cada registro como: Ana (30)

Entrada de ejemplo

2
Ana 30
Luis 25

Salida esperada

Ana (30)
Luis (25)

Tu punto de partida

#include <stdio.h>

typedef struct {
    char nombre[32];
    int edad;
} Persona;

int main(void) {
    Persona p[20], q[20];
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%31s %d", p[i].nombre, &p[i].edad);
    // TODO: fwrite(p, sizeof(Persona), n, f) en "gente.bin" (modo "wb")
    // TODO: fread(q, sizeof(Persona), n, f) desde "gente.bin" (modo "rb")
    // TODO: imprime q

    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

Modos binarios: "wb" y "rb" (la b importa en Windows).

Pista 2

fwrite/fread devuelven cuantos ELEMENTOS procesaron: comparalo con n para detectar errores.

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

typedef struct {
    char nombre[32];
    int edad;
} Persona;

int main(void) {
    Persona p[20], q[20];
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%31s %d", p[i].nombre, &p[i].edad);

    FILE *f = fopen("gente.bin", "wb");
    if (!f) return 1;
    fwrite(p, sizeof(Persona), n, f);
    fclose(f);

    f = fopen("gente.bin", "rb");
    if (!f) return 1;
    size_t leidos = fread(q, sizeof(Persona), n, f);
    fclose(f);
    for (size_t i = 0; i < leidos; i++) printf("%s (%d)\n", q[i].nombre, q[i].edad);
    return 0;
}
10.4 Agenda persistente
Mini agenda con ficheros. Lee comandos de la entrada estandar hasta "salir": add NOMBRE TELEFONO -> anade una linea "NOMBRE;TELEFONO" AL FINAL de "agenda.csv" (modo "a") list -> muestra cada linea del fichero como "NOMBRE: TELEFONO" Si el fichero no existe al hacer list, no imprime nada (add lo crea).

Entrada de ejemplo

add ana 111
add luis 222
list
salir

Salida esperada

ana: 111
luis: 222

Tu punto de partida

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

int main(void) {
    char cmd[16], nombre[64], tel[32];
    while (scanf("%15s", cmd) == 1 && strcmp(cmd, "salir") != 0) {
        if (strcmp(cmd, "add") == 0) {
            scanf("%63s %31s", nombre, tel);
            // TODO: fopen("agenda.csv", "a"), fprintf "%s;%s\n", fclose
        } else if (strcmp(cmd, "list") == 0) {
            // TODO: si el fichero existe, lee cada linea con fscanf(" %63[^;];%31s") e imprime "nombre: tel"
        }
    }
    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

Modo "a" (append) escribe al final sin borrar lo anterior.

Pista 2

%63[^;] lee todo hasta el siguiente ';' (como un %s que se detiene en ;). Luego el ; literal del formato lo consume. Pon un ESPACIO antes del % (" %63[^;]..."): salta el que quedo de la linea anterior.

Pista 3

Abre y cierra el fichero dentro de cada comando: asi los datos quedan guardados aunque el programa muera.

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

int main(void) {
    char cmd[16], nombre[64], tel[32];
    while (scanf("%15s", cmd) == 1 && strcmp(cmd, "salir") != 0) {
        if (strcmp(cmd, "add") == 0) {
            scanf("%63s %31s", nombre, tel);
            FILE *f = fopen("agenda.csv", "a");
            if (f) {
                fprintf(f, "%s;%s\n", nombre, tel);
                fclose(f);
            }
        } else if (strcmp(cmd, "list") == 0) {
            FILE *f = fopen("agenda.csv", "r");
            if (f) {
                while (fscanf(f, " %63[^;];%31s", nombre, tel) == 2)
                    printf("%s: %s\n", nombre, tel);
                fclose(f);
            }
        }
    }
    return 0;
}

Quiz del módulo 10

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