C++ proporciona potentes mecanismos para interactuar con el sistema de archivos, permitiendo a los desarrolladores leer y escribir datos de manera persistente. La librería estándar <fstream> es fundamental para estas operaciones, ofreciendo clases para manejar flujos de entrada y salida de archivos.
Manejo de Flujos de Archivo: Apertura y Cierre
La base de las operaciones con archivos en C++ reside en la gestión de flujos. Un flujo de archivo actúa como un puente entre el programa y el archivo en el disco.
Conceptos Fundamentales
- Archivo: Una colección de datos almacenada de forma permanente en un medio de almacenamiento. Pueden ser de texto (caracteres legibles) o binarios (datos crudos).
- Flujo de Archivo: Una secuencia de datos que se mueve hacia o desde un archivo. En C++, se utilizan objetos de las clases
ifstream(entrada),ofstream(salida) yfstream(ambas).
Apertura de Archivos
Para interactuar con un archivo, primero debe ser abierto. Esto se logra instanciando un objeto de flujo de archivo y utilizando su método open(). Es crucial especificar el nombre del archivo y el modo de apertura (por ejemplo, std::ios::in para leer, std::ios::out para escribir).
#include <fstream>
#include <iostream>
int main() {
std::ifstream lectorArchivo; // Objeto para leer desde un archivo
const char* nombreArchivo = "datos_entrada.txt";
// Abrir el archivo en modo lectura
lectorArchivo.open(nombreArchivo, std::ios::in);
if (!lectorArchivo.is_open()) { // Verificar si la apertura fue exitosa
std::cerr << "Error: No se pudo abrir el archivo '" << nombreArchivo << "' para lectura." << std::endl;
return 1; // Salir si hay un error
}
// Aquí irían las operaciones de lectura del archivo
std::cout << "Archivo '" << nombreArchivo << "' abierto correctamente para lectura." << std::endl;
// ... operaciones de lectura ...
lectorArchivo.close(); // Cerrar el archivo
return 0;
}
Cierre de Archivos
Una vez que se han completado las operaciones de lectura o escritura, es imperativo cerrar el archivo utilizando el método close(). Esto asegura que todos los datos en búfer se escriban en el disco y libera los recursos del sistema asociados con el archivo.
// Asumiendo que 'escritorArchivo' es un objeto std::ofstream previamente abierto
escritorArchivo.close();
Omitir el cierre de un archivo puede llevar a la pérdida de datos o a un estado inconsistente.
Lectura y Escritura de Datos
La librería <fstream> facilita tanto la lectura como la escritura de diversos tipos de datos en archivos.
Lectura de Archivos de Texto
Se pueden leer archivos de texto línea por línea o extraer datos individuales utilizando operadores de extracción (>>). La función std::getline es particularmente útil para leer líneas completas, incluyendo espacios en blanco.
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream miLector("mi_archivo_texto.txt");
if (!miLector) {
std::cerr << "Error al abrir el archivo para lectura." << std::endl;
return 1;
}
std::string lineaActual;
std::cout << "Contenido del archivo:" << std::endl;
while (std::getline(miLector, lineaActual)) {
std::cout << lineaActual << std::endl;
}
miLector.close();
return 0;
}
Escritura en Archivos de Texto
Para escribir datos en un archivo de texto, se utiliza un objeto std::ofstream y el operador de inserción (<<). Si el archivo no existe, se creará. Si existe, su contenido será sobrescrito por defecto (a menos que se especifique el modo std::ios::app para añadir al final).
#include <fstream>
#include <iostream>
int main() {
std::ofstream miEscritor("salida_texto.txt");
if (!miEscritor) {
std::cerr << "Error al abrir el archivo para escritura." << std::endl;
return 1;
}
miEscritor << "Primera línea de texto." << std::endl;
miEscritor << "Segunda línea con un número: " << 12345 << std::endl;
miEscritor.close();
std::cout << "Datos escritos en salida_texto.txt." << std::endl;
return 0;
}
Archivos Binarios
La lectura y escritura de archivos binarios difiere de los archivos de texto en que los datos se manejan como secuencias de bytes crudos, sin interpretación de caracteres. Es fundamental usar el modo std::ios::binary. Las operaciones read() y write() se utilizan para transferir bloques de memoria directamente al archivo y viceversa.
#include <fstream>
#include <iostream>
#include <vector>
int main() {
// Escritura en archivo binario
std::ofstream archivoBinarioEscritura("datos_binarios.bin", std::ios::binary | std::ios::out);
if (!archivoBinarioEscritura) {
std::cerr << "Error al abrir archivo binario para escritura." << std::endl;
return 1;
}
std::vector<int> numeros = {10, 20, 30, 40, 50};
// Escribir el contenido del vector como bytes
archivoBinarioEscritura.write(reinterpret_cast<const char*>(numeros.data()), numeros.size() * sizeof(int));
archivoBinarioEscritura.close();
std::cout << "Datos binarios escritos." << std::endl;
// Lectura desde archivo binario
std::ifstream archivoBinarioLectura("datos_binarios.bin", std::ios::binary | std::ios::in);
if (!archivoBinarioLectura) {
std::cerr << "Error al abrir archivo binario para lectura." << std::endl;
return 1;
}
std::vector<int> numerosLeidos(numeros.size());
// Leer los datos binarios en el vector
archivoBinarioLectura.read(reinterpret_cast<char*>(numerosLeidos.data()), numerosLeidos.size() * sizeof(int));
archivoBinarioLectura.close();
std::cout << "Datos binarios leídos: ";
for (int num : numerosLeidos) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
El uso de reinterpret_cast<char*> es necesario para convertir punteros a tipos de datos específicos (como int* o std::vector<int>::pointer) a un puntero de caracteres (char*), que es lo que esperan los métodos read y write.
Diferencias Clave: Archivos de Texto vs. Binarios
La elección entre trabajar con archivos de texto o binarios depende de la naturaleza de los datos y los requisitos de la aplicación.
- Archivos de Texto: Contienen caracteres legibles por humanos. Son adecuados para configuraciones, logs y cualquier dato que necesite inspección directa. Las conversinoes de formato (como saltos de línea) pueden ocurrir automáticamente entre plataformas.
- Archivos Binarios: Almacenan datos en su representación cruda, sin interpretaciones. Son más eficientes en espacio y velocidad para datos no textuales (imágenes, audio, estructuras de datos complejas) y garantizan la integridad exacta de los datos tal como se almacenaron.
Ejemplos Prácticos y Ejercicios
A continuación, se presentan ejemplos que combinan lectura, escritura y el manejo de diferentes tipos de archivos.
Ejemplo 1: Copiar Contenido de un Archivo
Este ejemplo lee un archivo de texto y copia su contenido a otro archivo.
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream origen("origen.txt");
std::ofstream destino("destino.txt");
if (!origen || !destino) {
std::cerr << "Error al abrir uno de los archivos." << std::endl;
return 1;
}
std::string linea;
while (std::getline(origen, linea)) {
destino << linea << std::endl;
}
origen.close();
destino.close();
std::cout << "Archivo copiado exitosamente." << std::endl;
return 0;
}
Ejemplo 2: Procesamiento de Datos Numéricos Binarios
Se escribe un arreglo de enteros en un archivo binario y luego se lee para verificar los datos.
#include <fstream>
#include <iostream>
#include <vector>
int main() {
const std::string nombreArchivoBin = "numeros.bin";
std::vector<double> datosEscritura = {1.1, 2.2, 3.3, 4.4, 5.5};
// Escribir datos binarios
std::ofstream outBin(nombreArchivoBin, std::ios::binary | std::ios::out);
if (!outBin) {
std::cerr << "Error al abrir " << nombreArchivoBin << " para escritura." << std::endl;
return 1;
}
outBin.write(reinterpret_cast<const char*>(datosEscritura.data()), datosEscritura.size() * sizeof(double));
outBin.close();
// Leer datos binarios
std::ifstream inBin(nombreArchivoBin, std::ios::binary | std::ios::in);
if (!inBin) {
std::cerr << "Error al abrir " << nombreArchivoBin << " para lectura." << std::endl;
return 1;
}
std::vector<double> datosLeidos(datosEscritura.size());
inBin.read(reinterpret_cast<char*>(datosLeidos.data()), datosLeidos.size() * sizeof(double));
inBin.close();
std::cout << "Datos binarios leídos: ";
for (double d : datosLeidos) {
std::cout << d << " ";
}
std::cout << std::endl;
return 0;
}
Ejercicio 1: Guardar Entrada de Usuario
Escriba un programa que solicite al usuario ingresar texto y lo guarde en un archivo llamado entrada_usuario.log.
#include <fstream>
#include <iostream>
#include <string>
#include <ctime> // Para añadir timestamp
int main() {
std::string textoUsuario;
std::cout << "Por favor, ingrese el texto a guardar (presione Enter para finalizar la línea):" << std::endl;
std::getline(std::cin, textoUsuario);
std::ofstream logFile("entrada_usuario.log", std::ios::app); // Usar std::ios::app para añadir al final
if (!logFile) {
std::cerr << "Error: No se pudo abrir el archivo de log." << std::endl;
return 1;
}
// Opcional: Añadir timestamp
std::time_t now = std::time(nullptr);
char timestamp[20];
std::strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", std::localtime(&now));
logFile << "[" << timestamp << "] " << textoUsuario << std::endl;
logFile.close();
std::cout << "Texto guardado exitosamente en entrada_usuario.log" << std::endl;
return 0;
}
Ejercicio 2: Sumar Números de un Archivo
Lea un archivo de texto (numeros.txt) que contiene números (uno por línea) y calcule su suma total.
#include <fstream>
#include <iostream>
#include <string>
int main() {
const std::string nombreArchivoNumeros = "numeros.txt";
std::ifstream numReader(nombreArchivoNumeros);
if (!numReader) {
std::cerr << "Error: No se pudo abrir el archivo '" << nombreArchivoNumeros << "'." << std::endl;
return 1;
}
double sumaTotal = 0.0;
double numeroActual;
std::string linea;
// Intentar leer línea por línea y convertir a double
while (std::getline(numReader, linea)) {
try {
numeroActual = std::stod(linea); // Convertir la línea a double
sumaTotal += numeroActual;
} catch (const std::invalid_argument& ia) {
std::cerr << "Advertencia: Línea no válida o no numérica ignorada: " << linea << std::endl;
} catch (const std::out_of_range& oor) {
std::cerr << "Advertencia: Número fuera de rango ignorado: " << linea << std::endl;
}
}
numReader.close();
std::cout << "La suma total de los números en " << nombreArchivoNumeros << " es: " << sumaTotal << std::endl;
return 0;
}
Ejercicio 3: Gestión de Registros de Empleados en Binario
Defina una estructura Empleado y cree funciones para escribir y leer registros de empleados en un archivo binario.
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
struct Empleado {
int id;
char nombre[50]; // Campo de tamaño fijo para simplificar la escritura binaria
double salario;
};
// Función para añadir un empleado a un archivo binario
bool agregarEmpleado(const std::string& nombreArchivo, const Empleado& emp) {
std::ofstream archivo(nombreArchivo, std::ios::binary | std::ios::app);
if (!archivo) {
std::cerr << "Error: No se pudo abrir el archivo " << nombreArchivo << " para añadir." << std::endl;
return false;
}
// Escribir el struct directamente. Asegurarse que el tamaño sea consistente.
archivo.write(reinterpret_cast<const char*>(&emp), sizeof(Empleado));
archivo.close();
return true;
}
// Función para buscar un empleado por ID en un archivo binario
Empleado buscarEmpleadoPorID(const std::string& nombreArchivo, int targetID) {
Empleado empleadoEncontrado = {-1, "", 0.0}; // Valor por defecto indicando no encontrado
std::ifstream archivo(nombreArchivo, std::ios::binary);
if (!archivo) {
std::cerr << "Error: No se pudo abrir el archivo " << nombreArchivo << " para búsqueda." << std::endl;
return empleadoEncontrado;
}
Empleado empleadoActual;
while (archivo.read(reinterpret_cast<char*>(&empleadoActual), sizeof(Empleado))) {
if (empleadoActual.id == targetID) {
empleadoEncontrado = empleadoActual;
break; // Salir del bucle una vez encontrado
}
}
archivo.close();
return empleadoEncontrado;
}
int main() {
const std::string archivoEmpleados = "empleados.dat";
// Crear datos de ejemplo
Empleado emp1 = {101, "Ana Garcia", 55000.0};
Empleado emp2 = {102, "Luis Perez", 62000.0};
// Escribir empleados en el archivo
agregarEmpleado(archivoEmpleados, emp1);
agregarEmpleado(archivoEmpleados, emp2);
std::cout << "Datos de empleados guardados en " << archivoEmpleados << std::endl;
// Buscar un empleado
int idBuscado = 101;
Empleado resultado = buscarEmpleadoPorID(archivoEmpleados, idBuscado);
if (resultado.id != -1) {
std::cout << "Empleado encontrado - ID: " << resultado.id << ", Nombre: " << resultado.nombre << ", Salario: " << resultado.salario << std::endl;
} else {
std::cout << "Empleado con ID " << idBuscado << " no encontrado." << std::endl;
}
return 0;
}
Nota: El uso de char nombre[50] como un arreglo de tamaño fijo es una forma común de manejar cadenas de texto en estructuras binarias para simplificar la escritura y lectura, pero requiere cuidado para evitar desbordamientos y gestionar el tamaño.
Ejercicio 4: Combinar Archivos
Escriba un programa que combine el contenido de dos archivos de texto (parte1.txt y parte2.txt) en un nuevo archivo (combinado.txt).
#include <fstream>
#include <iostream>
#include <string>
// Función auxiliar para copiar el contenido de un archivo a otro
bool copiarContenidoArchivo(std::ifstream& origen, std::ofstream& destino) {
std::string linea;
while (std::getline(origen, linea)) {
destino << linea << std::endl;
}
return true;
}
int main() {
std::ifstream archivo1("parte1.txt");
std::ifstream archivo2("parte2.txt");
std::ofstream archivoCombinado("combinado.txt");
if (!archivo1 || !archivo2 || !archivoCombinado) {
std::cerr << "Error: Al menos uno de los archivos no pudo ser abierto/creado." << std::endl;
return 1;
}
std::cout << "Combinando archivos..." << std::endl;
// Copiar contenido del primer archivo
copiarContenidoArchivo(archivo1, archivoCombinado);
// Copiar contenido del segundo archivo
copiarContenidoArchivo(archivo2, archivoCombinado);
archivo1.close();
archivo2.close();
archivoCombinado.close();
std::cout << "Archivos combinados exitosamente en 'combinado.txt'." << std::endl;
return 0;
}
Este programa lee secuencialmente el contenido de parte1.txt y lo escribe en combinado.txt, seguido del contenido de parte2.txt.