Clases para operaciones con archivos en C++
Para realizar entrada y salida de caracteres en archivos, C++ proporciona las siguientes clases de flujo:
ofstream: Clase de flujo para escribir en archivos.ifstream: Clase de flujo para leer de archivos.fstream: Clase de flujo para tanto lectura como escritura en archivos.
Estas clases se derivan directa o indirectamente de istream y ostream, las mismas que usamos con cin y cout. Por tanto, podemos emplear flujos de archivo de manera similar, pero debemos asociarlos a archivos físicos. Veamos un ejemplo inicial:
// Ejemplo básico de operaciones con archivos
#include <iostream>
#include <fstream>
int main() {
std::ofstream flujo_salida;
flujo_salida.open("registro.txt");
if (flujo_salida.is_open()) {
flujo_salida << "Texto grabado en el archivo.\n";
flujo_salida.close();
} else {
std::cerr << "No se pudo abrir el archivo." << std::endl;
}
return 0;
}
Este código crea el archivo registro.txt y escribe una línea, similar a usar cout, pero con el flujo de archivo.
Abrir un archivo
La primera operación típica con un flujo de archivo es asociarlo a un archivo real, lo que se conoce como abrir el archivo. Un archivo abierto se representa en el programa mediante un objeto de flujo, y cualquier operación de E/S en ese objeto se aplica al archivo físico.
Para abrir un archivo, usamos la función miembro open:
open(nombre_archivo, modo);
Donde nombre_archivo es una cadena con el nombre del archivo, y modo es un parámetro opcional con combinaciones de indicadores como ios::in (lectura), ios::out (escritura), ios::binary (binario), ios::ate (posicionar al final), ios::app (adjuntar) y ios::trunc (truncar). Estos indicadores se combinan con el operador OR (|). Ejemplo:
std::ofstream flujo;
flujo.open("datos.bin", std::ios::out | std::ios::app | std::ios::binary);
Cada clase tiene un modo predeterminado: ofstream usa ios::out, ifstream usa ios::in, y fstream usa ios::in. Al pasar un modo explícito, se sobrescribe el predeterminado para fstream.
Los flujos en modo binario tratan datos sin formateo, mientras que los de texto pueden aplicar transformaciones. Para verificar si un archivo se abrió correctamente, usamos is_open:
if (flujo.is_open()) {
// Proceder con operaciones
}
También podemos abrir un archivo en el constructor:
std::ofstream flujo_salida("registro.txt", std::ios::out);
Cerrar un archivo
Tras finalizar las operaciones, debemos cerrar el archivo para liberar recursos. Esto se hace con la función miembro close:
flujo.close();
Al cerrar, se vacían los buffers y el archivo queda disponible para otros procesos. Si un objeto de flujo se dsetruye mientras está abierto, el destructor llama a close automáticamente.
Archivos de texto
Los flujos de archivos de texto no incluyen el indicador ios::binary. Para escribir en ellos, usamos el operador de inserción:
// Escritura en archivo de texto
#include <iostream>
#include <fstream>
int main() {
std::ofstream archivo("mensaje.txt");
if (archivo.is_open()) {
archivo << "Primera línea.\n";
archivo << "Segunda línea.\n";
archivo.close();
} else {
std::cerr << "Error al abrir el archivo." << std::endl;
}
return 0;
}
Para leer de un archivo de texto, usamos getline o el operador de extracción. Ejemplo de lectura línea por línea:
// Lectura de archivo de texto
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::string linea;
std::ifstream archivo("mensaje.txt");
if (archivo.is_open()) {
while (std::getline(archivo, linea)) {
std::cout << linea << '\n';
}
archivo.close();
} else {
std::cerr << "No se pudo leer el archivo." << std::endl;
}
return 0;
}
El bucle continúa mientras getline devuelva una referencia válida al flujo, indicando que hay más datos.
Verificar estados del flujo
Existen funciones miembro para comprobar estados específicos:
bad(): Devuelve true si ocurre un error grave en lectura/escritura.fail(): Similar abad(), pero también para errores de formato.eof(): True si se alcanza el final del archivo durante la lectura.good(): False si cualquier otro indicador es true; es el estado genérico.
La función clear() reinicia estos indicadores de estado.
Posicionamiento en flujos
Los flujos mantienen posiciones internas: ifstream tiene una posición de lectura (get), ofstream una de escritura (put), y fstream ambas. Estas posiciones se gestionan con:
tellg()ytellp(): Devuelven la posición actual de lectura o escritura comostreampos.seekg()yseekp(): Cambian la posición de lectura o escritura. Pueden tomar una posición absoluta o un desplazamiento relativo conios::beg,ios::curoios::end.
Ejemplo para obtener el tamaño de un archivo:
// Calcular tamaño de archivo
#include <iostream>
#include <fstream>
int main() {
std::streampos inicio, fin;
std::ifstream archivo("datos.bin", std::ios::binary | std::ios::ate);
if (archivo.is_open()) {
inicio = archivo.tellg();
archivo.seekg(0, std::ios::end);
fin = archivo.tellg();
archivo.close();
std::cout << "Tamaño: " << (fin - inicio) << " bytes." << std::endl;
} else {
std::cerr << "Error al abrir el archivo." << std::endl;
}
return 0;
}
Los tipos streampos y streamoff se usan para estas operaciones y son alias de tipos miembro de la clase flujo.
Archivos binarios
Para archivos binarios, usar operadores de inserción/extracción no es eficiente. En su lugar, empleamos las funciones read y write para lectura/escritura secuencial de bloques de datos. Ejemplo de lectura completa:
// Lectura completa de archivo binario
#include <iostream>
#include <fstream>
int main() {
std::streampos tam;
char* bloque;
std::ifstream archivo("imagen.bin", std::ios::in | std::ios::binary | std::ios::ate);
if (archivo.is_open()) {
tam = archivo.tellg();
bloque = new char[tam];
archivo.seekg(0, std::ios::beg);
archivo.read(bloque, tam);
archivo.close();
std::cout << "Contenido cargado en memoria." << std::endl;
delete[] bloque;
} else {
std::cerr << "No se pudo abrir el archivo binario." << std::endl;
}
return 0;
}
Aquí, se asigna memoria dinámica para almacenar todo el archivo tras determinar su tamaño con ios::ate.
Buffers y sincronización
Los flujos de archivo usan un buffer interno (streambuf) como intermediario. La sincronización, que escribe los datos al medio físico, ocurre al cerrar el archivo, al llenarse el buffer, o explícitamente con manipuladores como flush y endl, o la función sync().
Procesamiento de argumentos de línea de comandos
En C y C++, los argumentos de línea de comandos se pasan a main mediante los parámetros argc (conteo de argumentos) y argv (vector de argumentos). argc es un entero que indica el número de argumentos, incluyendo el nombre del programa. argv es un arreglo de cadenas que contiene los argumentos.
Ejemplo básico para mostrar argumentos:
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << "Número de argumentos: " << argc << std::endl;
for (int i = 0; i < argc; ++i) {
std::cout << "Argumento " << i << ": " << argv[i] << std::endl;
}
return 0;
}
Los argumentos permiten modificar el comportamiento del programa sin interacción del usuario, útil en scripts.
Análisis de parámetros
Podemos verificar el número de argumentos para informar al usuario sobre el uso correcto. Ejemplo con opciones:
#include <iostream>
#include <string>
#include <vector>
static void mostrar_uso(const std::string& nombre) {
std::cerr << "Uso: " << nombre << " [opciones] FUENTE DESTINO\n"
<< "Opciones:\n"
<< " -h, --help Mostrar ayuda\n"
<< " -d, --destino Especificar destino\n";
}
int main(int argc, char* argv[]) {
if (argc < 3) {
mostrar_uso(argv[0]);
return 1;
}
std::vector<std::string> fuentes;
std::string destino;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "-h" || arg == "--help") {
mostrar_uso(argv[0]);
return 0;
} else if (arg == "-d" || arg == "--destino") {
if (i + 1 < argc) {
destino = argv[++i];
} else {
std::cerr << "La opción --destino requiere un argumento." << std::endl;
return 1;
}
} else {
fuentes.push_back(arg);
}
}
// Lógica de movimiento omitida por simplicidad
std::cout << "Destino: " << destino << ", Fuentes: " << fuentes.size() << std::endl;
return 0;
}
Este ejemplo maneja opciones cortas y largas, y permite múltiples fuentes.
Uso de getopt para análisis avanzado
Para un análisis más robusto, se pueden usar funciones como getopt y getopt_long, disponibles en sistemas POSIX. Estas simplifican el manejo de opciones complejas, con soporte para argumentos requeridos y opcionales.