Resumen
Todo lo que un programa calcula desaparece cuando termina, salvo que lo deje escrito en algún lugar que le sobreviva. Esta clase presenta ese lugar, el archivo de texto, y la idea que permite usarlo sin aprender nada radicalmente nuevo: un archivo se lee y se escribe como un flujo, con las mismas operaciones que el teclado y la pantalla. Se estudian std::ifstream y std::ofstream, la comprobación de que un archivo se abrió antes de usarlo y la carpeta de trabajo, que decide dónde se busca; el bucle de lectura correcto, while (std::getline(in, line)), frente al bucle con eof(), que procesa un dato de más, y la razón exacta de ese dato; la escritura con std::println, el modo de añadir y el cierre al salir del bloque. Cierran la clase los formatos de línea: separar campos con comas, validarlos con std::from_chars y decidir, en la especificación, qué hacer con las líneas mal formadas.
Objetivos de aprendizaje
- Abrir un archivo para leer o para escribir y comprobar que se abrió antes de usarlo, distinguiendo el archivo que no existe del que se busca en otra carpeta.
- Leer un archivo línea por línea con
while (std::getline(in, line))y explicar, siguiendo el estado del flujo, por quéwhile (!in.eof())procesa un dato de más. - Escribir resultados en un archivo con
std::println(out, …), eligiendo entre reemplazar su contenido y añadir al final. - Procesar un archivo de valores separados por comas en un vector de registros, validando cada línea e informando las inválidas con su número.
Un archivo es un flujo
La persistencia
Las variables de un programa viven en la memoria, y la memoria de un programa se libera cuando termina: la próxima ejecución empieza sin recordar nada. Para que un dato sobreviva al programa que lo produjo, debe quedar en un medio permanente, y el medio de uso general es el archivo: una secuencia de bytes con nombre, guardada en el disco, que el sistema operativo conserva entre ejecuciones. Un archivo de texto es aquel cuyos bytes son caracteres, en UTF-8 como se vio en la clase El texto no es ASCII, organizados en líneas separadas por el carácter '\n'. Es el formato más modesto y el más duradero: cualquier editor lo abre, cualquier lenguaje lo lee, y una persona puede revisarlo a simple vista.
Ahora bien, C++ no trata el archivo como una cosa distinta del teclado. Desde la clase Entrada, salida y errores, std::cin es un flujo de entrada: una fuente de caracteres que se consumen en orden, con un estado que registra si la última operación falló. Un archivo abierto para leer es otro flujo de entrada, de tipo std::ifstream (de input file stream); un archivo abierto para escribir, un flujo de salida, de tipo std::ofstream. Ambos se declaran en la cabecera <fstream>. De ello se sigue que todo lo aprendido sobre std::cin en la clase Leer bien la entrada, el operador >>, std::getline y el estado de error, vale sin cambios para un archivo.
Abrir y comprobar
Un archivo se abre al crear el flujo, con el nombre del archivo entre llaves: std::ifstream in{"grades.txt"};. Abrir puede fallar (el archivo no existe, no hay permiso para leerlo, el nombre está mal escrito), y el fallo no detiene el programa ni produce ningún mensaje: deja el flujo en estado de error. Un flujo en error, usado como condición, vale false; por tanto, la comprobación es if (!in), y debe hacerse antes de la primera lectura. El programa siguiente intenta abrir un archivo que no existe.
#include <fstream>#include <iostream>#include <print>int main(){ std::ifstream in{"grades.txt"}; if (!in) { std::println(std::cerr, "No se pudo abrir grades.txt"); return 1; } int first{}; in >> first; std::println("Primera nota: {}", first);}No se pudo abrir grades.txtEl programa terminó con el código 1.
Tres detalles merecen atención. El primero es std::cerr (de <iostream>), el flujo de errores: escribe en la pantalla como la salida estándar, pero es un canal distinto, de modo que los mensajes de error no se mezclan con los resultados cuando la salida se redirige a un archivo; std::println acepta como primer argumento el flujo en que debe escribir. El segundo es el return 1;: un código de terminación distinto de cero, como se vio en la clase Anatomía de un programa en C++, avisa a quien ejecutó el programa que algo salió mal. El tercero es lo que habría ocurrido sin la comprobación: in >> first sobre un flujo en error no hace nada, y el programa habría escrito «Primera nota: 0» con toda tranquilidad. Un resultado falso con apariencia de verdadero es peor que un mensaje de error; de donde la regla de esta clase: todo archivo se comprueba al abrirlo.
Rutas relativas y la carpeta de trabajo
El nombre "grades.txt" no dice en qué carpeta está el archivo. Un nombre así es una ruta relativa, y se resuelve a partir de la carpeta de trabajo: la carpeta desde la que se ejecuta el programa, que no es necesariamente la carpeta del ejecutable ni la del código fuente. El programa siguiente, guardado en la carpeta project junto a un archivo data.txt, abre ese archivo y escribe su primera línea; se ejecutó dos veces, primero desde project y después desde la carpeta superior.
$ cd project$ g++ -std=c++23 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -g -fsanitize=address,undefined -fno-sanitize-recover=undefined main.cpp -o main$ ./mainPrimera línea: Hola desde el archivo$ cd ..$ ./project/mainNo se pudo abrir data.txtEl ejecutable es el mismo, y el archivo sigue en su lugar; lo único que cambió es la carpeta de trabajo, y con ella el lugar donde se busca data.txt. Esta es la causa más frecuente del «no encuentra el archivo». Un editor que ejecuta el programa con un botón elige por su cuenta la carpeta de trabajo (en Visual Studio Code suele ser la carpeta abierta en el editor, aunque depende de la configuración de la tarea), de modo que conviene ejecutar desde la terminal, en la carpeta de los datos, hasta saber qué hace el editor. La tentación contraria, escribir en el código una ruta absoluta como "/home/ana/curso/data.txt" o "C:\\Users\\Ana\\data.txt", resuelve el problema en una sola máquina y lo crea en todas las demás: el programa deja de funcionar en cuanto lo ejecuta otra persona o se mueve la carpeta. Lo recomendable es la ruta relativa con una carpeta de trabajo conocida o, mejor, el nombre del archivo como dato que el usuario proporciona.
Cargando el contenido…