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:
- NO lo definimos nosotros.
- NO sabemos cómo es por dentro. (A menos que nos documentemos al respecto, viendo la librería estándar)
- Lo maneja la biblioteca estándar.
- Nosotros usamos el puntero.
Apertura de un archivo:
fopen
Sintaxis:
FILE *fopen(const char *nombre, const char *modo);
Ejemplo:
FILE *fp = fopen("datos.txt", "r");
¿Qué hace?:
- Busca el archivo.
- Prepara buffers.
- Devuelve un
FILE *
Si falla devuelve NULL.
Regla:
- TODO
fopenexitoso debe tener sufclose.
Cierre de un archivo:
fclose
Sintaxis:
int fclose(FILE *fp);
Es la función que:
- Cierra el archivo asociado al
FILE * - Vacía los buffers pendientes
- Libera recursos del sistema operativo
Sin fclose, el archivo NO está realmente terminado.
¿Qué pasa internamente cuando usamos fclose?
Aunque no lo vemos, pasan varias cosas:
- Se vacían los buffers:
- Lo escrito con
fputc,fprintf, etc. - Puede que todavía no esté en el disco.
- Lo escrito con
- Se libera memoria:
- Estructuras internas (FILE)
- Descriptores del sistema.
- Se libera el archivo:
- Otros programas pueden usarlo.
- Se asegura la integridad del archivo.
¿Qué pasa si NO llamamos a fclose?
Depende... y eso es lo peligroso.
Posibles consecuencias:
- El archivo queda incompleto.
- Los últimos datos no se escriben.
- Fugas de recursos.
- En algunos sistemas:
- El archivo puede quedar corrupto.
- O no aparecer.
- En programas chicos "parece funcionar".
- En programas reales mata su funcionamiento.
Valor de retorno de fclose:
int r = fclose(fp);
0 => OKEOF=> Error al cerrar
Ejemplo:
if (fclose(fp) == EOF)
{
printf("Error al cerrar el archivo\n");
}
Regla:
- Cada
fopenexitoso debe tener exactamente unfclose.
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 |
- "w": Borra el archivo si existe.
- "r": Falla si no existe.
Escritura de un carácter:
fputc
Sintaxis:
int fputc(int c, FILE *fp);
¿Qué hace?:
- Escribe un solo carácter en el archivo.
- Avanza el puntero de archivo.
- Devuelve:
- El carácter escrito (como
unsigned char) EOFsi hay error.
- El carácter escrito (como
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:
fclosefflush
Lectura de un carácter:
fgetc
Sintaxis:
int fgetc(FILE *fp);
¿Qué hace?:
- Lee un carácter del archivo.
- Avanza el puntero.
- Devuelve:
- El carácter leído.
EOFsi:- Llega al fin del archivo.
- Ocurre un error
- Esto es clave:
EOFno distingue un error de fin de archivo.
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:Devuelveint.EOF:NO es unchar.- Por eso la variable es
int.
Detección de fin de archivo:
EOF:
No es un carácter.
- Es un valor especial (normalmente
-1) - Forma correcta de utilizarlo:
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);
- Devuelve distinto de
0solo después de intentar leer y fallar porEOF. - NO se adelanta.
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);
- Devuelve distinto de 0 si ocurrió un error de I/O (Input/Output)
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?
- Escribe un string completo en el archivo.
- NO agrega
\nautomáticamente. - Devuelve:
- Valor no negativo si tuvo éxito.
EOFsi hubo error.
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 | sí |
| fputs | archivo | no |
Lectura de un string:
fgets
Sintaxis:
char *fgets(char *str, int n, FILE *fp);
¿Qué hace?:
- Lee hasta
n-1carácteres. - Se detiene si encuentra:
\nEOF
- Incluye el
\nsi lo lee. - Siempre agrega
'\0' - Devuelve:
strsi pudo leer algo.NULLsi no pudo (EOFo error)
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:
- Lee cómo máximo
n-1, sin = 100, lee hasta99 carácteres + \0 - Evita desbordes (overflow)
- A diferencia de
gets(Que está prohibida) - El
\nqueda adentro. Ejemplo:fgets(linea, 100, fp);
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?:
- Escribe texto formateado en un archivo.
- Devuelve:
- Cantidad de carácteres escritos.
- Valor negativo si hay error.
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:
%d,%f,%s,%c.%lf, paradouble%u,%ld, etc.
Lectura con formato:
fscanf
Sintaxis:
int fscanf(FILE *fp, const char *formato, ...);
¿Qué hace?:
Lee datos formateados desde un archivo.
Devuelve:
- Cantidad de elementos leídos correctamente.
EOFsi no pudo leer nada.
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:
- Controla cuánto entra.
- Nunca se pasa del buffer.
- Sabe exactamente cuándo falló.
sscanf:
- Trabaja sobre memoria, no sobre archivo.
- No avanza el puntero del archivo.
- Permite validar mejor lo leído.
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;
}