Un archivo binario es un fichero que guarda bytes tal como los maneja el programa, sin convertirlos a texto legible.
Hasta ahora hemos trabajado con archivos de texto (.txt). Son geniales porque si los abres con el Bloc de Notas, puedes entender lo que dicen. “Luis”, “35”, “100”.
Pero los archivos de texto tienen dos grandes problemas:
- Son lentos: Para guardar el número
123456, el ordenador tiene que convertir ese entero binario en caracteres ASCII ‘1’, ‘2’, ‘3’… y al leerlo, hacer el proceso inverso (parsing). - Ocupan más: El número
123456ocupa 4 bytes en memoria (int), pero 6 bytes en texto (un byte por carácter).
Para aplicaciones de alto rendimiento, guardados de juegos, o procesamiento de imágenes, usamos Archivos Binarios.
En los ejemplos más sencillos, copiamos la representación en memoria al archivo sin convertirla a texto. Esta representación no siempre es portable entre arquitecturas o compiladores.
El modo std::ios::binary
Para trabajar en binario, usamos las mismas clases ofstream e ifstream, pero debemos especificar una bandera crucial en el constructor: std::ios::binary.
// Abrir para escritura binaria
std::ofstream archivo("datos.bin", std::ios::binary);
Si olvidas el flag std::ios::binary, el sistema operativo podría intentar “arreglar” los saltos de línea (cambiando \n por \r\n en Windows), corrompiendo tus datos binarios.
Escritura con el método .write()
En binario no usamos el operador <<. Usamos el método .write().
Este método espera dos cosas:
- Dónde empiezan los datos en memoria (un puntero a char).
- Cuántos bytes queremos copiar.
Para esto usamos el reinterpret_cast, que suele asustar un poco la primera vez.
Como .write() espera un char* (porque trabaja con bytes), si queremos guardar un int o un double, tenemos que “engañar” a la función diciéndole: “Trata la dirección de este entero como si fuera un puñado de bytes”.
int numero = 12345;
std::ofstream archivo("entero.bin", std::ios::binary);
if (archivo.is_open()) {
// reinterpret_cast<const char*>(&variable) -> Dame la dirección como bytes
// sizeof(variable) -> Cuánto ocupa
archivo.write(reinterpret_cast<const char*>(&numero), sizeof(numero));
archivo.close();
}
Lectura con el método .read()
La lectura es simétrica. Usamos .read(), le damos la dirección de memoria donde queremos volcar los datos y cuántos bytes leer.
int numeroLeido = 0;
std::ifstream archivo("entero.bin", std::ios::binary);
if (archivo.is_open()) {
archivo.read(reinterpret_cast<char*>(&numeroLeido), sizeof(numeroLeido));
std::cout << "Leido: " << numeroLeido << "\n"; // 12345
}
Guardar estructuras de datos planos
Lo potente de los archivos binarios es que podemos guardar estructuras enteras de golpe. Imaginemos una estructura de configuración:
struct Configuracion {
int id;
double velocidad;
bool pantallaCompleta;
};
// ... en el main ...
Configuracion config = { 1, 99.5, true };
std::ofstream salida("config.dat", std::ios::binary);
salida.write(reinterpret_cast<const char*>(&config), sizeof(Configuracion));
¡Boom! Hemos guardado toda la estructura en una sola línea de código. Sin bucles, sin formatear texto.
El peligro de los punteros y objetos complejos
Solo debes volcar directamente tipos trivially copyable. Para otros tipos necesitas definir una serialización.
Si tu clase o estructura contiene punteros o contenedores dinámicos como std::string o std::vector, NO PUEDES usar .write() directamente sobre el objeto.
struct Jugador {
std::string nombre; // ¡PELIGRO! Esto gestiona memoria dinámica
int nivel;
};
Jugador j; j.nombre = "Luis";
// ❌ ERROR FATAL
archivo.write((char*)&j, sizeof(j));
¿Por qué falla?
Un std::string gestiona su propia representación interna, que puede incluir punteros, tamaño, capacidad o almacenamiento local según la implementación. Si vuelcas el objeto j, guardas esa representación interna, no un formato estable con el texto “Luis”.
Cuando cargues ese archivo mañana, esa dirección de memoria no será válida o apuntará a basura. Tu programa crasheará.
¿Cómo se guarda un std::string o vector?
Tienes que serializarlo manualmente:
- Guardar primero la longitud del string (un
int). - Guardar los caracteres del string.
// Guardar std::string
size_t len = nombre.size();
archivo.write((char*)&len, sizeof(len)); // 1. Longitud
archivo.write(nombre.c_str(), len); // 2. Datos
Para cerrar este bloque de archivos, nos queda un detalle importante. Hemos estado usando nombres de archivos como cadenas de texto, pero C++ tiene una forma mucho más elegante y segura de manejar rutas y directorios.
Continúa con el siguiente artículo: