Archivos - FILE *

Parte 1

Acceso secuencial

¿Qué es un archivo?

Una secuencia de bytes almacenada en algún dispositivo (disco, pendrive, etc) a la que se accede de forma secuencial o aleatoria mediante un flujo (stream).

FILE *:

En C NUNCA trabajamos directamente con el archivo. Siempre trabajamos con un puntero a una estructura interna:

FILE *fp;

Ese FILE:

Apertura de un archivo:

fopen

Sintaxis:

FILE *fopen(const char *nombre, const char *modo);

Ejemplo:

FILE *fp = fopen("datos.txt", "r");

¿Qué hace?:

Si falla devuelve NULL.

Regla:

Cierre de un archivo:

fclose

Sintaxis:

int fclose(FILE *fp);

Es la función que:

Sin fclose, el archivo NO está realmente terminado.

¿Qué pasa internamente cuando usamos fclose?

Aunque no lo vemos, pasan varias cosas:

¿Qué pasa si NO llamamos a fclose?

Depende... y eso es lo peligroso.

Posibles consecuencias:

Valor de retorno de fclose:

int r = fclose(fp);

Ejemplo:


if (fclose(fp) == EOF)
{
    printf("Error al cerrar el archivo\n");
}

Regla:

  • Cada fopen exitoso debe tener exactamente un fclose.

Modos de apertura:

Modo Significado
"r" Leer (debe existir)
"w" Escribir (borra o crea)
"a" Agregar al final
"r+" Leer y escribir
"w+" Leer y escribir (borra lo existente)
"a+" Leer y escribir al final

Escritura de un carácter:

fputc

Sintaxis:

int fputc(int c, FILE *fp);

¿Qué hace?:

Ejemplo:


FILE *fp = fopen("salida.txt", "w");

if (fp == NULL)
    return 1;

fputc('H', fp);
fputc('o', fp);
fputc('l', fp);
fputc('a', fp);
fputc('\n', fp);

fclose(fp);

Contenido del archivo:

Hola

IMPORTANTE: fputc NO escribe inmediatamente al disco.

  • Va al buffer.
  • Se garantiza la escritura real con:
    • fclose
    • fflush

Lectura de un carácter:

fgetc

Sintaxis:

int fgetc(FILE *fp);

¿Qué hace?:

Ejemplo:


int c;
FILE *fp = fopen("salida.txt", "r");

if (fp == NULL)
    return 1;

while ((c = fgetc(fp)) != EOF)
{
    putchar(c);
}

fclose(fp);

OBSERVACION:

  • fgetc: Devuelve int.
  • EOF: NO es un char.
  • Por eso la variable es int.

Detección de fin de archivo:

EOF:

No es un carácter.

while ((c = fgetc(fp)) != EOF)

Para distinguir Fin de archivo vs Error de lectura, utilizamos feof Función que pregunta: ¿Fin de archivo?.

Sintaxis:

int feof(FILE *fp);

Forma incorrecta, error clásico:


while (!feof(fp)) // MAL
{
    c = fgetc(fp);
}

feof se usa después, no para controlar el loop.

ferror - ¿Error?:

Sintaxis:

int ferror(FILE *fp);

Patrón correcto de lectura robusta:

Ejemplo:


int c;

while ((c = fgetc(fp)) != EOF)
{
  putchar(c);
}

if (feof(fp)) // Detecta fin
{
  printf("\nFin de archivo alcanzado\n");
}
else if (ferror(fp)) // Detecta error
{
  printf("\nError de lectura\n");
}

Este es el patrón canónico en C.

Escritura de un string:

fputs

Sintaxis:

int fputs(const char *str, FILE *fp);

¿Qué hace?

Ejemplo:


FILE *fp = fopen("texto.txt", "w");

if (fp == NULL)
  return 1;

fputs("Hola mundo", fp);
fputs("\n", fp);
fputs("Segunda linea", fp);

fclose(fp);

Contenido del archivo:


Hola mundo
Segunda linea

Diferencia clave con puts:

Función Destino Agrega n
puts stdout
fputs archivo no

Lectura de un string:

fgets

Sintaxis:

char *fgets(char *str, int n, FILE *fp);

¿Qué hace?:

Ejemplo:


FILE *fp = fopen("texto.txt", "r");

if (fp == NULL)
    return 1;

char linea[100]; // 99 char + \0

while (fgets(linea, sizeof(linea), fp) != NULL)
{
    printf("%s", linea);
}
fclose(fp);

Salida:


Hola mundo
Segunda linea

IMPORTANTE:

Si el archivo tiene:

Hola

El buffer queda:

"Hola\n\0"

Por eso muchas veces se ve:


linea[strcspn(linea, "\n")] = '\0';
NULL = no se leyo nada.

No significa solo EOF, puede ser error también.

Patrón correcto:


if (fgets(linea, sizeof(linea), fp) == NULL)
{
    if (feof(fp))
        printf("Fin de archivo\n");
    else
        printf("Error\n");
}

Comparación:

Función Nivel(abstracción) Uso
fgetc bajo(carácter) lectura simple
fgets medio(línea completa) lectura segura
fscanf alto(formateado) lectura con formato

Escritura con formato:

fprintf

Sintaxis:

int fprintf(FILE *fp, const char *formato, ...);

¿Qué hace?:

Ejemplo:


FILE *fp = fopen("datos.txt", "w");
if (fp == NULL)
  return 1;

int edad = 25;
float altura = 1.75;

fprintf(fp, "Edad: %d\n", edad);
fprintf(fp, "Altura: %.2f\n", altura);

fclose(fp);

Contenido del archivo:


Edad: 25
Altura: 1.75

Formatos válidos:

Los mismos que printf:

Lectura con formato:

fscanf

Sintaxis:

int fscanf(FILE *fp, const char *formato, ...);

¿Qué hace?:

Lee datos formateados desde un archivo.

Devuelve:

Ejemplo:

Archivo persona.txt:

25 1.75

Código:


FILE *fp = fopen("persona.txt", "r");
if (fp == NULL)
  return 1;

int edad;
float altura;

fscanf(fp, "%d %f", &edad, &altura);

printf("Edad=%d ALtura %.2f\n", edad, altura);

fclose(fp);

Peligros reales de fscanf:

No controla límites de strings


char nombre[20];
fscanf(fp, "%s", nombre); // Peligro si el nombre es largo

Mejor:

fscanf(fp, "%19s", nombre);

No detecta bien fin de archivo

Error típico:


while (!feof(fp))
{
  fscanf(fp, "%d", &x);
}

Patrón correcto con fscanf:


while (fscanf(fp, "%d", &x) == 1)
{
    printf("%d\n", x);
}

O con múltiples datos:


while (fscanf(fp, "%d %f", &a, &b) == 2)
{
    ...
}

El == cantidad de campos, es clave.

fscanf vs fgets + sscanf:

Función Ventaja Problema
fscanf directo frágil
fgets+sscanf segura hay que parsear

Por eso en código serio se suele hacer:


fgets(linea, sizeof(linea), fp);
sscanf(linea, "%d %f", &a, &b);

fgets:

sscanf:

Ejemplo:


char linea[100];
int a;
float b;
while (fgets(linea, sizeof(linea), fp))
{
    if (sscanf(linea, "%d %f", &a, &b) == 2)
    {
        printf("a=%d b=%.2f\n", a, b);
    }
    else
    {
        printf("Linea mal formada\n");
    }
}

Esto no se puede hacer limpio con fscanf Pero se suele usar fscanf

Teoría aplicada

Makefile:


CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -pedantic

PROG = files
OBJ = main.o

$(PROG): $(OBJ)
	$(CC) $(OBJ) -o $(PROG)

$(OBJ): main.c
	$(CC) $(CFLAGS) -c main.c

.PHONY: clean
clean:
	del $(OBJ) $(PROG).exe *.txt

Código:


// gcc -std=c11 -Wall -Wextra -pedantic main.c -o files

#include <stdio.h>

int main(void)
{
    /* ---- DECLARACION ---- */
    FILE *fp;

    /* ---- APERTURA Y ESCRITURA DE ARCHIVO fopen fputc ---- */
    fp = fopen("datos.txt", "w");

    if (fp == NULL)
    {
        printf("No se pudo abrir el archivo\n");
        return 1;
    }
    
     /* ---- ESCRITURA DE CARACTERES fputc ---- */
    fputc('H', fp);
    fputc('o', fp);
    fputc('l', fp);
    fputc('a', fp);
    fputc('\n', fp);

    /* ---- ESCRITURA DE STRING fputs ---- */
    fputs("Linea uno\n", fp);

    /* ---- ESCRITURA DE CON FORMATO fprintf ---- */
    fprintf(fp, "Edad: %d Altura: %.2f\n", 25, 1.75);
    
    fclose(fp); // SIEMPRE cerrar

    /* ---- LECTURA CARACTER A CARACTER ---- */
    fp = fopen("datos.txt", "r");

    if (fp == NULL)
        return 1;
    
    int c;
    printf("Lectura con fgetc:\n");

    while ((c = fgetc(fp)) != EOF)
    {
        putchar(c);
    }

    if (feof(fp))
        printf("\n[Fin de archivo detectado]\n");
    else if (ferror(fp))
        printf("\n[Error de lectura]\n");

    fclose(fp); // SIEMPRE cerrar

    /* ---- LECTURA POR LINEAS + PARSEO ---- */
    fp = fopen("datos.txt", "r");

    if (fp == NULL)
        return 1;

    char linea[100];
    int edad;
    float altura;

    printf("\nLectura con fgets + sscanf:\n");

    while(fgets(linea, sizeof(linea), fp) != NULL)
    {
        printf("Linea cruda: %s", linea);

        /* PARSEO SEGURO */
        if (sscanf(linea, "Edad: %d Altura: %f", &edad, &altura) == 2)
        {
            printf("-> Parseado: edad=%d altura=%.2f\n", edad, altura);
        }
    }

    if (feof(fp))
    {
        printf("Fin de archivo\n");
    }
    else if (ferror(fp))
    {
        printf("Error de lectura\n");
    }
    fclose(fp);

    return 0;
}

Continúa Parte 2

Ejercicios de Archivos