2.6. Lectura y escritura de ficheros

En C, el proceso de abrir y manipular ficheros requería un gran conocimiento del lenguaje para prepararle para la complejidad de las operaciones. Sin embargo, la librería iostream de C++ proporciona una forma simple de manejar ficheros, y por eso se puede presentar mucho antes de lo que se haría en C.

Para poder abrir un fichero para leer y escribir, debe incluir la librería fstream. Aunque eso implica la inclusión automática de la librería iostream, es prudente incluir iostream si planea usar cin, cout, etc.

Para abrir un fichero para lectura, debe crear un objeto ifstream que se usará como cin. Para crear un fichero de escritura, se crea un objeto ofstream que se comporta como cout. Una vez que tiene abierto el fichero puede leer o escribir en él como si usara cualquier objeto iostream. Así de simple, que es el objetivo, por supuesto.

Una de funciones las más útiles de la librería iostream es getline(), que permite leer una línea (terminada en nueva línea) y guardarla en un objeto string [37]. El primer argumento es el objeto ifstream del que se va a leer la información y el segundo argumento es el objeto string. Cuando termina la llamada a la función, el objeto string contiene la línea capturada.

Aquí hay un ejemplo que copia el contenido de un fichero en otro.

//: C02:Scopy.cpp
// Copy one file to another, a line at a time
#include <string>
#include <fstream>
using namespace std;

int main() {
  ifstream in("Scopy.cpp"); // Open for reading
  ofstream out("Scopy2.cpp"); // Open for writing
  string s;
  while(getline(in, s)) // Discards newline char
    out << s << "\n"; // ... must add it back
} ///:~

Listado 2.8. C02/Scopy.cpp


Para abrir los ficheros, únicamente debe controlar los nombres de fichero que se usan en la creación de los objetos ifstream y ofstream.

Aquí se presenta un nuevo concepto: el bucle while. Aunque será explicado en detalle en el siguiente capítulo, la idea básica consiste en que la expresión entre paréntesis que sigue al while controla la ejecución de la sentencia siguiente (pueden ser múltiples sentencias encerradas entre llaves). Mientras la expresión entre paréntesis (en este caso getline(in, s) produzca un resultado «verdadero», las sentencias controladas por el while se ejecutarán. getline() devuelve un valor que se puede interprer como «verdadero» si se ha leido otra línea de forma satisfactoria, y «falso» cuando se llega al final de la entrada. Eso implica que el while anterior lee todas las líneas del fichero de entrada y las envía al fichero de salida.

getline() lee los caracteres de cada línea hasta que descubre un salto de línea (el carácter de terminación se puede cambiar pero eso no se verá hasta el capítulo sobre iostreams del Volumen 2). Sin embargo, descarta el carácter de nueva línea y no lo almacena en el objeto string. Por lo que si queremos copiar el fichero de forma idéntica al original, debemos añadir el carácter de nueva línea como se muestra arriba.

Otro ejemplo interesante es copiar el fichero entero en un único objeto string:

//: C02:FillString.cpp
// Read an entire file into a single string
#include <string>
#include <iostream>
#include <fstream>
using namespace std;

int main() {
  ifstream in("FillString.cpp");
  string s, line;
  while(getline(in, line))
    s += line + "\n";
  cout << s;
} ///:~

Listado 2.9. C02/FillString.cpp


Debido a la naturaleza dinámica de los strings, no hay que preocuparse de la cantidad de memoria que hay que reservar para el string. Simplemente hay que añadir cosas y el string irá expandiéndose para dar cabida a lo que le introduzca.

Una de las cosas agradables de poner el fichero entero en una cadena es que la clase string proporciona funciones para la búsqueda y manipulación que le permiten modificar el fichero como si fuera una simple línea. Sin embargo, tiene sus limitaciones. Por un lado, a menudo, es conveniente tratar un fichero como una colección de líneas en vez de un gran bloque de texto. Por ejemplo, si quiere añadir numeración de líneas es mucho más fácil si tiene un objeto string distinto para cada línea. Para realizarlo, necesitamos otro concepto.



[37] Actualmente existen variantes de getline(), que se discutirán profusamente en el capítulo de iostreams en el Volumen 2