LP1 practica 2 registros en un archivo C++

 

Tutorial: Manejo de Registros en Archivos con C++



Trabajar con archivos es una habilidad esencial en la programación, y en C++ se logra principalmente a través de la librería fstream. Cuando hablamos de "registros", nos referimos a estructuras de datos que agrupan información relacionada, similar a una fila en una tabla de base de datos o un objeto en programación orientada a objetos. Al guardar registros en archivos, podemos persistir la información para que no se pierda al cerrar el programa.

Para manejar registros en archivos en C++, podemos utilizar dos enfoques principales: archivos de texto y archivos binarios. Cada uno tiene sus ventajas y desventajas.


1. Preparando el Entorno: la Librería fstream

Antes de empezar, es fundamental incluir la cabecera fstream, que proporciona las clases ifstream (para lectura de archivos), ofstream (para escritura de archivos) y fstream (para lectura y escritura).

C++
#include <iostream> // Para entrada/salida estándar (cout, cin)
#include <fstream>  // Para el manejo de archivos
#include <string>   // Para usar la clase string
#include <vector>   // Para almacenar registros si es necesario

2. Definición de un Registro (Estructura)

Para representar un registro, lo más común es usar una struct o una class. Por simplicidad, usaremos una struct en este tutorial.

C++
// Definición de la estructura para representar un "estudiante"
struct Estudiante {
    int id;
    char nombre[50]; // Usamos un arreglo de char para simplificar el manejo binario
    int edad;
};

Nota: Para archivos binarios, usar char[] en lugar de std::string en las estructuras es más sencillo, ya que std::string maneja memoria dinámica que requiere una serialización y deserialización más compleja. Para archivos de texto, std::string es perfectamente adecuado.


3. Manejo de Registros en Archivos de Texto

Los archivos de texto son legibles por humanos y son útiles para almacenar datos que no requieren una manipulación binaria compleja o para configuraciones.

a. Escribir Registros en un Archivo de Texto

Para escribir, abrimos el archivo en modo de escritura (std::ofstream). Cada registro se puede escribir en una línea separada, con los campos delimitados por un carácter (como una coma o un espacio).

C++
void escribirEstudianteTexto(const Estudiante& est, const std::string& nombreArchivo) {
    std::ofstream archivo(nombreArchivo, std::ios::app); // Abre en modo "append" (añadir al final)
    if (archivo.is_open()) {
        archivo << est.id << "," << est.nombre << "," << est.edad << std::endl;
        archivo.close();
        std::cout << "Estudiante '" << est.nombre << "' guardado en el archivo de texto." << std::endl;
    } else {
        std::cerr << "No se pudo abrir el archivo de texto para escritura." << std::endl;
    }
}
b. Leer Registros de un Archivo de Texto

Para leer, abrimos el archivo en modo de lectura (std::ifstream) y utilizamos la sobrecarga del operador >> o getline() para leer los datos.

C++
void leerEstudiantesTexto(const std::string& nombreArchivo) {
    std::ifstream archivo(nombreArchivo);
    if (archivo.is_open()) {
        std::string linea;
        std::cout << "\n--- Contenido del archivo de texto ---" << std::endl;
        while (std::getline(archivo, linea)) {
            // Aquí deberías parsear la línea para extraer id, nombre y edad
            // Un enfoque simple (sin manejar errores de formato) sería:
            size_t pos1 = linea.find(',');
            int id = std::stoi(linea.substr(0, pos1));
            size_t pos2 = linea.find(',', pos1 + 1);
            std::string nombre = linea.substr(pos1 + 1, pos2 - (pos1 + 1));
            int edad = std::stoi(linea.substr(pos2 + 1));

            std::cout << "ID: " << id << ", Nombre: " << nombre << ", Edad: " << edad << std::endl;
        }
        archivo.close();
    } else {
        std::cerr << "No se pudo abrir el archivo de texto para lectura." << std::endl;
    }
}

4. Manejo de Registros en Archivos Binarios

Los archivos binarios almacenan los datos tal como están en la memoria, lo que los hace más eficientes en espacio y velocidad de lectura/escritura, especialmente para grandes volúmenes de datos. Sin embargo, no son legibles directamente por humanos.

a. Escribir Registros en un Archivo Binario

Para escribir, usamos el método write() del objeto ofstream. Necesitamos convertir la estructura a un puntero a char y especificar el tamaño de la estructura.

C++
void escribirEstudianteBinario(const Estudiante& est, const std::string& nombreArchivo) {
    // std::ios::binary para modo binario, std::ios::app para añadir al final
    std::ofstream archivo(nombreArchivo, std::ios::out | std::ios::binary | std::ios::app);
    if (archivo.is_open()) {
        archivo.write(reinterpret_cast<const char*>(&est), sizeof(Estudiante));
        archivo.close();
        std::cout << "Estudiante '" << est.nombre << "' guardado en el archivo binario." << std::endl;
    } else {
        std::cerr << "No se pudo abrir el archivo binario para escritura." << std::endl;
    }
}
b. Leer Registros de un Archivo Binario

Para leer, usamos el método read() del objeto ifstream. Leemos bloques del tamaño de la estructura en una instancia de la misma.

C++
void leerEstudiantesBinario(const std::string& nombreArchivo) {
    std::ifstream archivo(nombreArchivo, std::ios::in | std::ios::binary);
    if (archivo.is_open()) {
        Estudiante est;
        std::cout << "\n--- Contenido del archivo binario ---" << std::endl;
        while (archivo.read(reinterpret_cast<char*>(&est), sizeof(Estudiante))) {
            std::cout << "ID: " << est.id << ", Nombre: " << est.nombre << ", Edad: " << est.edad << std::endl;
        }
        archivo.close();
    } else {
        std::cerr << "No se pudo abrir el archivo binario para lectura." << std::endl;
    }
}

5. Ejemplo Completo y main()

Aquí te presento un programa completo que demuestra el uso de ambas técnicas:

C++
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cstring> // Para strcpy_s o strcpy

// Definición de la estructura para representar un "estudiante"
struct Estudiante {
    int id;
    char nombre[50];
    int edad;
};

// --- Funciones para Archivos de Texto ---

void escribirEstudianteTexto(const Estudiante& est, const std::string& nombreArchivo) {
    std::ofstream archivo(nombreArchivo, std::ios::app); // Abre en modo "append"
    if (archivo.is_open()) {
        archivo << est.id << "," << est.nombre << "," << est.edad << std::endl;
        archivo.close();
        std::cout << "Estudiante '" << est.nombre << "' guardado en el archivo de texto." << std::endl;
    } else {
        std::cerr << "No se pudo abrir el archivo de texto para escritura." << std::endl;
    }
}

void leerEstudiantesTexto(const std::string& nombreArchivo) {
    std::ifstream archivo(nombreArchivo);
    if (archivo.is_open()) {
        std::string linea;
        std::cout << "\n--- Contenido del archivo de texto ---" << std::endl;
        while (std::getline(archivo, linea)) {
            size_t pos1 = linea.find(',');
            int id = std::stoi(linea.substr(0, pos1));
            size_t pos2 = linea.find(',', pos1 + 1);
            std::string nombre = linea.substr(pos1 + 1, pos2 - (pos1 + 1));
            int edad = std::stoi(linea.substr(pos2 + 1));

            std::cout << "ID: " << id << ", Nombre: " << nombre << ", Edad: " << edad << std::endl;
        }
        archivo.close();
    } else {
        std::cerr << "No se pudo abrir el archivo de texto para lectura." << std::endl;
    }
}

// --- Funciones para Archivos Binarios ---

void escribirEstudianteBinario(const Estudiante& est, const std::string& nombreArchivo) {
    std::ofstream archivo(nombreArchivo, std::ios::out | std::ios::binary | std::ios::app);
    if (archivo.is_open()) {
        archivo.write(reinterpret_cast<const char*>(&est), sizeof(Estudiante));
        archivo.close();
        std::cout << "Estudiante '" << est.nombre << "' guardado en el archivo binario." << std::endl;
    } else {
        std::cerr << "No se pudo abrir el archivo binario para escritura." << std::endl;
    }
}

void leerEstudiantesBinario(const std::string& nombreArchivo) {
    std::ifstream archivo(nombreArchivo, std::ios::in | std::ios::binary);
    if (archivo.is_open()) {
        Estudiante est;
        std::cout << "\n--- Contenido del archivo binario ---" << std::endl;
        while (archivo.read(reinterpret_cast<char*>(&est), sizeof(Estudiante))) {
            std::cout << "ID: " << est.id << ", Nombre: " << est.nombre << ", Edad: " << est.edad << std::endl;
        }
        archivo.close();
    } else {
        std::cerr << "No se pudo abrir el archivo binario para lectura." << std::endl;
    }
}

int main() {
    Estudiante est1 = {1, "Alicia", 20};
    Estudiante est2 = {2, "Carlos", 22};
    Estudiante est3 = {3, "Maria", 21};

    // --- Archivo de Texto ---
    std::string archivoTexto = "estudiantes.txt";
    escribirEstudianteTexto(est1, archivoTexto);
    escribirEstudianteTexto(est2, archivoTexto);
    leerEstudiantesTexto(archivoTexto);

    // --- Archivo Binario ---
    std::string archivoBinario = "estudiantes.dat";
    escribirEstudianteBinario(est1, archivoBinario);
    escribirEstudianteBinario(est3, archivoBinario); // Añadimos otro estudiante al binario
    leerEstudiantesBinario(archivoBinario);
    
    // --- Ejemplo de añadir un nuevo estudiante al archivo binario
    Estudiante est4 = {4, "Pedro", 23};
    escribirEstudianteBinario(est4, archivoBinario);
    leerEstudiantesBinario(archivoBinario);


    return 0;
}

Consideraciones Importantes:

  • Manejo de Errores: Siempre es crucial verificar si el archivo se abrió correctamente con archivo.is_open().
  • Modos de Apertura:
    • std::ios::out: Abre para escritura (sobrescribe si existe).
    • std::ios::in: Abre para lectura.
    • std::ios::app: Abre para escritura, pero añade al final del archivo si ya existe.
    • std::ios::binary: Abre el archivo en modo binario (sin traducciones de caracteres).
    • Puedes combinar modos con el operador |, por ejemplo, std::ios::out | std::ios::binary.
  • Cierre de Archivos: Es una buena práctica cerrar explícitamente los archivos con archivo.close() cuando hayas terminado de usarlos. Los destructores de los objetos fstream lo hacen automáticamente, pero cerrarlos manualmente puede liberar recursos antes.
  • std::string en Archivos Binarios: Si tu estructura contiene std::string, std::vector, u otros tipos que manejan memoria dinámica, el simple write y read no funcionará correctamente, ya que solo copiaría los punteros, no los datos a los que apuntan. En esos casos, necesitarías implementar un proceso de serialización y deserialización manual para guardar y cargar correctamente la longitud y el contenido de la cadena.
  • Portabilidad de Archivos Binarios: Los archivos binarios pueden no ser portables entre diferentes arquitecturas o sistemas operativos debido a diferencias en el endianness (orden de bytes) o el padding (relleno) de las estructuras.


Comentarios