Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


5.3.3a  E/S sin formato

§1  Presentación

En este capítulo abordaremos el manejo de las E/S no formateadas a ficheros. Podrá comprobar que las clases encargadas de estas operaciones heredan sus posibilidades de sus ancestros, las superclases de la jerarquía iostreams cuyos detalles ya han sido comentados. En consecuencia, no deberían ser necesarias ningunas aclaraciones adicionales aquí. Sin embargo, incluimos en este capítulo una recopilación de las propiedades y métodos de uso más frecuente en las manipulaciones estándar con ficheros, junto con algunos ejemplos aclaratorios.

No olvide que las filestreams componen una jerarquía de clases cuyas capas superiores son pura interfaz ( 4.11.8c), de forma que en la práctica se utilizan derivando objetos de las tres subclases que hemos indicado en la sinopsis ( 5.3.3).  Así pues, aunque en las explicaciones que siguen, decimos que el método "tal" pertenece a la súper clase "cual", a efectos prácticos puede considerar, como así es en realidad ( 4.11.2b), que todos pertenecen al objeto-flujo que controla las operaciones.

Para empezar, para que el lector pueda hacerse una idea de conjunto, incluimos un ejemplo compilable que representa el proceso de crear un flujo; asociarlo con un fichero; escribir algo en él, para posteriormente recuperarlo y obtener el resultado en el dispositivo estándar de salida. El resto del capítulo se dedica a exponer el porqué y el cómo de los procesos implicados.

#include <iostream>
#include <fstream>
#include <conio.h>

int main() {
   // objeto para controlar el formateo de un flujo de salida de caracteres
   std::ofstream ofs;
   // puntero a un objeto que contiene el buffer de flujo y controlará las operaciones de E/S (transporte)
   std::filebuf* filbPtr;
   // asociar el buffer de flujo con el flujo de salida
   filbPtr = ofs.rdbuf();
   // asociar el flujo a un fichero de disco y abrirlo para escritura
   filbPtr->open ("primerTest.txt", std::ios::out);
   // escribir una cadena de caracteres en el fichero
   ofs << "Contenido a escribir en el fichero\n" << std::endl;

   // cerrar el fichero
   filbPtr->close();

   // proceso simétroco del anterior para un flujo de entrada
   std::ifstream ifs;
   std::filebuf* filb;
   filb = ifs.rdbuf();
   filb->open ("primerTest.txt", std::ios::in);

   char ch = '\0';
   int counter = 0;
   while (!ifs.eof()) {        // bucle de lectura
      ifs.get (ch);

      // enviar caracteres leídos al dispositivo estándar de salida
      std::cout << ch;
      ++counter;
   }
   filb->close();
   std::cout << std::endl;     // forzar vaciado del buffer de entrada
   std::cout << "El fichero contiene: " << counter << " caracteres\n" << std::endl;
   std::cout << "Press Enter to exit" << std::endl;

   // esperar un carácter por el dispositivo estándar de entrada (teclado)
   std::cin.get();
   return 0;
}

§2  ios_base

La clase ios_base, de la que derivan todos los streams, dispone de miembros que permiten controlar distintos aspectos de la operación.  Los más interesantes son:

§2.1  state 

Esta propiedad es en realidad es una máscara de bits ("bitmask") cuyo valor controla algunos aspectos del flujo ( 5.3.2a). Sus bits tienen los significados sigientes:

  • goodbit  Flujo Ok. integridad correcta.

  • badbit  Indica una pérdida de integridad en la secuencia del flujo.

  • eofbit  Indica que la operación ha alcanzado el final de una secuencia (por ejemplo fin de fichero).

  • failbit  Indica que la operación de entrada ha no ha podido leer los caracteres esperados, o que una salida no ha podido generar los caracteres deseados.

El valor de state puede ser interrogado mediante el método rdstate(); aunque el valor devuelto debe ser interpretado analizando sus bits individuales mediante las constantes anteriores. Como veremos a continuación, algunos métodos chequean directamente sus bits; otros en cambio se basan en estos valores para su funcionamiento. Por ejemplo:

  • eof()  devuelve true si el eofbit está seteado (1) y false en caso contrario.

  • good() devuelve true si todos los bits están a cero.

  • fail()  devuelve true si está seteado failbit o badbit. false en caso contrario.

  • bad()  devuelve true si batbit está seteado (1).

  • clear()  Este método responde a la declaración void clear(iostate state = goodbit);. Su invocación suele realizarse utilizando el valor por defecto (sin argumentos). Su comportamiento es como sigue:  

    Si  (rdstate() & exeptions()) == 0 la función termina sin ninguna acción (es estado ya estaba "limpio"). En caso contrario, se lanza una excepción del tipo basic_ios::failure.

    Después que la invocación ha terminado, si rdbuf() != 0 (existe buffer intermedio), state = rdstate(). En caso contrario rdstate() = state | ios_base::badbit.

  • setstate()  Su declaración es void setstate(iostate state);.  Realiza la invocación clear( rdstate() | state). Lo que equivale a añadir a la máscara el patrón de bits pasados como argumento. Por ejemplo, la invocación que sigue pone a 1 el eofbit de de la propiedad state de objeto:

    objeto.setstate(ios_base::eofbit);

Como ejemplo de lo anterior, supongamos que tenemos un filestream myStream asociado a un fichero (más adelante explicaremos como hacerlo):

if (mySteam.eof())

   cout << "Alcanzado fin de fichero!!" << endl;


if (! myStream.good()) {

   if (myStream.rdstate() == std::ios::badbit) {

        cout << "Error fatal en el flujo!!" << endl;


   } else if (myStream.rdstate() == std::ios::failbit) {

        cout << "Error de operación en el flujo!!" << endl;


   } else {

        cout << "Errores varios en el flujo!!" << endl;

   }

}

§2.2  openmode 

Respecto a esta propiedad, son pertinentes las mismas observaciones que en el caso anterior; es una máscara cuyos bits controlan la forma de apertura del fichero y es manejada a través del método open() .  El significado de sus bits es el siguiente:

  • app          Desplazar ("seek") al final del fichero antes de escribir (añadir al final).

  • ate          Abrir y desplazar al final del fichero inmediatamente después.

  • binary    Relizar las operaciones E/S de flujo en modo binario.

  • in            Abrir en modo lectura ("Input").

  • out         Abrir para escritura ("Output").

  • trunc      Modo de sobreescritura. Borrar cualquier contenido previo del fichero.


La tabla adjunta muestra la correspondencia entre las formas de apertura STL y las utilizadas por las rutinas fopen de la librería clásica (observe que no existe equivalente "Clásico" al modo ate).

bynary in out trunc app stdio equivalente Descripción
    +     "w" Abrir para escritura. Si el fichero existe con anterioridad será sobreescrito. Si no existe será creado. En la librería clásica, el modo (binario o texto) depende de la variable globlal _fmode. En la STL es modo texto.
    +   + "a" Abrir en modo escritura para añadir al final. Si el fichero no existe es creado. Idénticas consideraciones sobre el modo (binario/texto) que en el caso anterior.
    + +   "w" Abrir para escritura. Si el fichero existe con anterioridad será sobreescrito. Si no existe será creado.  Idem. Idem. respecto al modo.
  +       "r" Abrir para lectura. Si el fichero no existe se produce un error.  Idem. Idem. respecto al modo.
  + +     "r+" Abrir un fichero existente para lectura y escritura. Si el fichero no existe se produce un error.  Idem. Idem. respecto al modo.
  + + +   "w+" Abrir para lectura/escritura. Si el fichero existe con anterioridad será sobreescrito. Si no existe será creado.  Idem. Idem. respecto al modo.
+   +     "wb" Abrir para escritura en modo binario. Si el fichero existe con anterioridad será sobreescrito. Si no existe será creado.
+   +   + "ab" Abrir en modo escritura binaria para añadir al final. Si el fichero no existe es creado.
+   + +   "wb" Abrir para escritura en modo binario. Si el fichero existe con anterioridad será sobreescrito. Si no existe será creado.
+ +       "rb" Abrir para lectura en modo binario. Si el fichero no existe se produce un error.
+ + +     "r+b" Abrir un fichero existente para lectura y escritura en modo binario.
+ + + +   "w+b" Abrir para lectura/escritura en modo binario. Si el fichero existe con anterioridad será sobreescrito. Si no existe será creado.


Nota: Los ficheros abiertos en modo lectura/escritura permiten ambos tipos de operaciones. Sin embargo en algunos casos pueden existir limitaciones para su encadenamiento [4].

  • La escritura (salida) no puede estar directamente seguida de una lectura (entrada) sin que exista un desplazamiento intermedio del puntero interno.

  • La lectura no puede estar directamente seguida de una salida sin un displazamiento intermedio del puntero interno, a menos que la lectura alcance el fin del fichero.

§2.3  seekdir

Esta propiedad define la forma en que se realizarán los desplazamientos del puntero interno ("File pointer") para los subsecuentes procesos de lectura y escritura. La propiedad es controlada de forma indirecta, mediante los métodos open() y seekg() . El significado de sus bits es el indicado (entre paréntesis el modo equivalente de la librería clásica):

  • beg    Solicitar un dsplazamiento ("seek") del puntero respecto del principio del fichero (SEEK_SET).

  • cur    Solicitar un desplazamiento respecto de la posición actual (SEEK_CUR).

  • end    Solicitar un desplazamiento respecto del final del fichero (SEEK_END).

§3  basic_ifstream
§3.1  Construcción de filestreams

El estándar establece que basic_ifstream tiene dos constructores (el comportamiento de ofstream y fstream es análogo):

basic_ifstream();

explicit basic_ifstream (const char* s, ios_base::openmode mode = ios_base::in);


El primero es un constructor por defecto, que puede utilizarse sin argumentos. El segundo es un constructor explicit ( 4.11.2d1) en el que se pueden definir dos argumentos: el nombre del fichero (s) y el modo de apertura (in). Este último permite asociar el objeto creado  con un fichero determinado, en el momento de su creación (estos constructores invocan a su vez al constructor del filebuf correspondiente al objeto que se instancia)  [1].

Nota:  Algunos compiladores proporcionan opciones extra. Por ejemplo C++Builder proporciona dos constructores adicionales y un tercer argumento en el constructor explicit. Este último determina los permisos de lectura/escritura/ejecución del fichero, lo que tiene sentido en ambientes Unix y Linux, sin embargo en Windows y DOS su utilidad es limitada, dado que los ficheros son siempre "legibles" y no disponen de atributo de ejecución.

Por su parte los constructores adicionales permiten manejar tuberías ("pipes"), conectores ("sockets") o cualquier otro dispositivo Unix que pueda ser accedido mediante un descriptor del fichero.

Ejemplos

#include <fstream>

 

char* fileName = "D:\\doc\\file1.TXT";

 

ifstream objIn;                                        // constructor por defecto

ofstream objOut (fileName, std::ios_base::app);        // constructor explícito

fstream objInOut ("File3.txt", std::ios_base::trunc);  // Ídem.


El código anterior crea tres objetos-flujo; uno de entrada, otro de salida y el tercero bidireccional. Observe que objIn no está asociado a ningún fichero, así como la forma de utilizar el segundo argumento para señalar el modo de apertura deseado .  Observe también que el valor openmode es un patrón de bits, por lo que es posible utilizar los operadores de manejo de bits ( 4.9.3) para componer un valor adecuado. Por ejemplo:

fstream objInOut("somefile.txt", std::ios_base::in | std::ios_base::out | std::ios_base::app);

Como es usual es la STL, ios_base tiene también su alias (ios), de modo que la sentencia anterior puede adoptar la forma:

fstream objInOut("somefile.txt", std::ios::in | std::ios::out | std::ios::app);


Naturalmente la elección de un openmode inadecuado produce un error de apertura. Por ejemplo:

ifstream objIn ("someFile", std::ios::trunc);    // Error!! modo no permitido a este flujo


Si queremos utilizar esta forma con el flujo ifstream, es necesario abrirlo también para escritura [2]:

ifstream objIn ("someFile", std::ios::out | std::ios::trunc); // Ok.

§3.1.1  Valores por defecto

Como puede verse, el argumento in, del constructor de ifstream, que determina el modo de apertura del fichero, tiene un valor por defecto. Esto ocurre también con el constructor de ofstream y con los métodos open() correspondientes (ver a continuación ). El valor es distinto en cada caso. Como no podía ser de otra forma, para ifstream es modo lectura (ios::in) y para ofstream es ios::out. En cambio el constructor de fstream no dispone de un valor por defecto para su segundo argumento.

§3.2  Destrucción de filestreams

Por supuesto los filestreams disponen de sus correspondientes destructores (virtuales) para desasignar los objetos creados. También es posible desligar el flujo del dispositivo (fichero) mediante el método close().  Sin embargo, como hemos indicado repetidamente, el diseño de la STL permite que no sea necesario preocuparse por estos aspectos a no ser que el programador decida eliminar manualmente algún objeto.

Ejemplo:

#include <iostream>
using namespace std;
{

   ofstream flujo1 (file1, ios::out | ios::binary);  // Creado flujo1
   ifstream flujo2 (file2, ios::in | ios::binary);   // Creado flujo2

   ...

   flujo2.close();            // Ok desligado flujo2 de file2

   ...

   flujo2.~ifstream();        // Ok destruido flujo2

   ...

}   // desligado flujo1 de file1 y destruido

§3.3  open()

 Es posible asociar el objeto-flujo a un fichero después de su creación mediante el método open() que responde al siguiente prototipo:

void open(const char* s, ios_base::openmode mode = ios_base::in);

Ejemplo

#include <iostream>
using namespace std;

ifstream objFile;                    // objeto-flujo sin fichero asociado

objFile.open("somefile.txt");        // se asocia con un fichero

if (objFile.is_open())

   cout << "Fichero abierto satisfactoriamente" << endl;
else
   cout << "No se ha podido abrir el Fichero!!" << endl;

Como se ha indicado, el método open() del ifstream objFile, tiene un argumento por defecto; en este caso su valor es ios::in.  Es significativo que la sentencia de apertura anterior podría efectuarse directamente sobre el filebuf asociado (el que controla el flujo). En consecuencia, podría sustituirse sin problema por la siguiente:

objFile.rdbuf()->open("somefile.txt", ios::in ); // se asocia el fichero

Observe que en esta última sentencia se utiliza el método open() de la clase basic_filebuf (en realidad el anterior invoca a este).

§3.4  is_open()

Es posible verificar el resultado de una apertura mediante el método is_open(), que devuelve un tipo bool con el resultado. Por ejemplo:

#include <fstream>

#include <iostream>

using namespace std;

 

ifstream objFile ("somefile.txt");

if (objFile.is_open())

   cout << "Fichero abierto satisfactoriamente" << endl;
else
   cout << "No se ha podido abrir el Fichero!!" << endl;


Si se crea un flujo sin asociarlo a ningún fichero, no es posible utilizar is_open() con él (ya sabemos que no ha sido abierto ningún fichero). En cambio si es posible usar el método good() .  Por ejemplo:

#include <iostream>
using namespace std;

ofstream streamOut;              // flujo sin fichero asociado

if (!streamOut.good()) {
   cout << "Error al crear flujo de salida!!" << endl;
   return EXIT_FAILURE;
}

streamOut.open("newFile.txt");   // por defecto es ios::out
if (!streamOut.is_open()) {
   cout << "Error al abrir Fichero destino!!" << endl;
   return EXIT_FAILURE;
}

Después de la primera sentencia existe un objeto en memoria, pero el fichero no ha sido creado todavía. Después de la apertura el fichero ha sido creado y asociado con el objeto-flujo.

§3.5  rdbuf()

El buffer auxiliar filebuf, que controla el transporte, puede ser accedido mediante el método rdbuf(), que devuelve un puntero a dicho objeto.

basic_filebuf<charT,traits>* rdbuf() const;

Generalmente el objeto filebuf no es accedido directamente, sino a través de los métodos de los objetos filestream, que realizan estos accesos automáticamente. No obstante, en caso de que fuese necesario, rdbuf() permite acceder a las propiedades y métodos del filebuf de cualquier flujo.

§4  basic_istream

La finalidad de esta super-clase es ayudar a leer e interpretar flujos de entrada controlados por un streambuf.  Sus métodos y propiedades permiten controlar aspectos específicos de los flujos originados en los ficheros asociados a los filestreams.  A continuación se relacionan las herramientas más interesantes que ofrece esta super-clase. Salvo indicación en contrario, en las descripciones que siguen suponemos que si la operación no finaliza con éxito, se realiza una invocación a setstate(x) con el argumento x adecuado a la situación presentada (fin de fichero, error, Etc.)

§4.1  read()

istream_type& read (char_type* s, streamsize n);

Extrae caracteres del flujo y los guardada en posiciones sucesivas de una matriz cuyo primer elemento es señalado por el argumento s. La extracción continua hasta que ocurre alguna de las circunstancias siguientes:

  • Se han almacenado n caracteres

  • Se encuentra un fin de fichero (EOF) en la secuencia de entrada.

Aunque puede ser utilizada con los filestreams, en el siguiente ejemplo utilizamos el método con un iostream;  para leer una cadena de 50 caracteres del teclado (flujo estándar de entrada cin  5.3) y meterla en una matriz que posteriormente es enviada al flujo estándar de salida (pantalla).

char line[50];
cin.read(line, 50);

cout << line << endl;

§4.2  readsome()

streamsize readsome(char_type* s, streamsize n);

Este método es similar al anterior; extrae caracteres de un fichero y los almacena en una matriz señalada por el puntero s. devolviendo el número de caracteres almacenados.

§4.3  get()

Este método permite extraer una secuencia de caracteres de un fichero. El proceso se realiza con los caracteres de la secuencia de entrada hasta que se encuentra un cierto carácter delim que funciona como delimitador. Existen distintas versiones que se diferencian en los detalles de su funcionamiento. En caso de que la extracción no pueda efectuarse, se produce una invocación setstate(failbit).

Las versiones disponibles son las siguientes:

    int_type get();

Extrae el próximo carácter de la secuencia, devolviendo un entero equivalente.


    basic_istream<charT,traits>& get(char_type& c);

Extrae el próximo carácter de la secuencia y lo asigna al argumento c, pasado por referencia ( 4.4.5).


    basic_istream<charT,traits>& get(char_type* s, streamsize n, char_type delim );

    basic_istream<charT,traits>& get(char_type* s, streamsize n);

La segunda es equivalente a la primera suponiendo que delim es el carácter ancho '\n' (salto de línea LF 3.2.3e en formato ancho), de forma que equivale a [3]:

get(s, n, widen('\n'));


Ambas funciones extraen caracteres del flujo y los guardan en posiciones sucesivas de una matriz cuyo primer elemento es señalado por el puntero s. La extracción continua hasta que ocurre alguna de las circunstancias siguientes:

  • Se han almacenado n-1 caracteres.

  • Se encuentra un fin de fichero ( traits::eof() ) en la secuencia de entrada.

  • El próximo carácter c a extraer coincide con delim (c == delim). En cuyo caso el carácter no es extraído.


    basic_istream<charT, traits>& get(basic_streambuf<char_type,traits>& sb, char_type delim );

    basic_istream<charT, traits>& get(basic_streambuf<char_type,traits>& sb );

Ambos método  extraen caracteres del fichero de entrada y los insertan en la secuencia controlada por el argumento sb (pasado por referencia). El segunda forma es equivalente a la primera suponiendo que delim es widen('\n').

El proceso de extracción se detiene cuando se alcanza alguna de las condiciones siguientes:

  • Se encuentra un fin de fichero ( traits::eof() ) en la secuencia de entrada.

  • Se presenta un fallo en el proceso de inserción, en cuyo caso el carácter que debía insertarse no es extraído del fichero de entrada.

  • El próximo carácter c a extraer coincide con delim (c == delim). En cuyo caso el carácter no es extraído.

  • Ocurre una excepción. En cuyo caso la excepción es capturada pero no relanzada ( 1.6.1).

Ejemplo

El siguiente código utiliza esta última forma del método para copiar el contenido del fichero oldFile.txt, que está en el lugar indicado por oldFile, en el fichero newFile.txt (en el directorio del ejecutable).

#include <iostream>
using namespace std;

char* oldFile = "D:\\Doc\\oldFile.TXT";
char* newFile = "newFile.txt";

ofstream oStreamOut (newFile, ios::out | ios::binary | ios::trunc);    // L4. 
ifstream oStreamIn (oldFile, ios::in | ios::binary);                   // L5.

oStreamIn.get(*oStreamOut.rdbuf(), '\x0');        // L7.

Comentario

Las sentencias L4 y L5 instancian sendos filestreams.  El primero es de salida; el segundo, de entrada, se encargará de leer el fichero original.

La copia se realiza en la sentencia L7. Observe que utilizamos el método get sobre el flujo de entrada y que  el carácter delimitador delim lo hacemos igual a nulo (es un fichero de texto y esperamos que no contenga ningún carácter nulo en su interior).

El punto más significativo es como se ha definido el argumento sb. Observe que en la descripción del método hemos indicado (siguiendo textualmente el Estándar) "... y los inserta en la secuencia controlada por el argumento sb".  Recordemos ( 5.3) que la capa de transporte, encargada de controlar estos aspectos, se encapsula en una jerarquía de clases derivada de la superclase basic_streambuf.  Es precisamente nuestro caso; vemos que en efecto, el especificador utilizado, basic_streambuf<char_type,traits>& sb, es la referencia a un objeto tipo basic_streambuf.  Este objeto asociado al flujo de salida, es proporcionado por el método rdbuf() de basic_istream .  Como el método devuelve un puntero, es necesaria una deferencia para obtener el objeto ( 4.9.11).


Como habrá observado, utilizando un bucle y un delimitador adecuado, los métodos get() son especialmente útiles para leer los elementos de un fichero en cualquier forma de agrupación: carácter a carácter, por palabras o por líneas. Por ejemplo, puede usar un espacio como delimitador para leer palabras. El siguiente ejemplo utiliza esta técnica para leer secuencialmente palabras de un fichero y volcarlas en pantalla.

#include <iostream>
using namespace std;

char c = '\0';

char word[50];

ifstream streamI ("someFile.txt");  


while ( !streamI.eof() ) {            // bucle de lectura
   streamIn.get (word, 50, '\x20');
   cout << word << endl;
   do {

      streamIn.get(c);
   } while (c == '\x20');
   streamIn.unget();
}

Hemos supuesto que las palabras del fichero tienen menos de 50 caracteres y el único punto a comentar es la secuencia do... while del bucle de lectura. La aparición del primer carácter espacio (20h) detiene la secuencia de lectura y se muestra la primera palabra, pero el delimitador no es extraído, de forma que se detendría la secuencia. Para esto se utiliza el get del bucle do... while, que se encarga de extraer cualquier número de espacios de separación entre las palabras de la secuencia. Sin embargo, las características de este bucle ( 4.10.3) hacen que se detenga después de haber extraído el primer carácter válido. Para evitar que la próxima palabra aparezca desprovista de su carácter inicial, se utiliza el método unget(), que devuelve al filebuffer el último carácter extraído y hace retroceder el puntero de lectura.

Notabasic_istream dispone también de un método putback(): int_type sputbackc(char_type c); que permite devolver un carácter a la secuencia de entrada y retroceder en una posición el puntero de lectura. La diferencia con el anterior es que el carácter puede ser cualquiera, no necesariamente el último leído y que la secuencia de entrada debe ser de naturaleza no volátil. Se dispone así mismo de un método, peek(), que permite obtener un carácter sin hacer avanzar el puntero de lectura.


Por supuesto, las palabras obtenidas en el ejemplo podrían ser objeto de una manipulación posterior, para separar tokens unidos por puntos, comas, NL, CR o cualquier otro tipo de caracteres de puntuación y el bucle interior debería ser afinado para detectar un posible fin de fichero. Sin embargo, esta rutina puede servir como un primer paso del proceso de "parsing" del fichero.

§4.4  ignore()

basic_istream<charT,traits>& ignore(int n = 1, int_type delim = traits::eof());

Esta función es muy parecida a la anterior, aunque en esta ocasión los caracteres extraídos son descartados. También aquí es posible establecer el número de caracteres extraídos n y el delimitador delim.  Observe que ambos argumentos disponen de valores por defecto, de forma que invocando el método sin argumentos se descarta el próximo carácter.

El proceso de extracción finaliza cuando se presenta alguna de las circunstancias siguientes:

  • Se han extraído n caracteres (a condición de que n != numeric_limits<int>::max()).

  • Se encuentre un fin de fichero en la secuencia de entrada, en cuyo caso se realiza una invocación setstate(eofbit).

  • El próximo carácter c a extraer coincide con delim (c == delim). En cuyo caso es extraído y descartado.

Observe que si el valor delim se establece por defecto,  traits::eof(), la última condición no se alcanza nunca.

§4.5  getline()

Este método permite extraer una secuencia de caracteres de un fichero especificando dos límites: el número n de caracteres a extraer o un cierto carácter delim que funciona como delimitador. Existen dos versiones:

basic_istream<charT,traits>& getline(char_type* s, streamsize n, char_type delim);

basic_istream<charT,traits>& getline(char_type* s, streamsize n);


La segunda es equivalente a la primera suponiendo que delim es el carácter ancho '\n', de forma que equivale a:

getline(s, n, widen('\n'));


Ambas formas extraen caracteres de fichero y los almacenan en posiciones sucesivas de una matriz señalada por el puntero s.  El proceso finaliza cuando se cumpla alguna de las circunstancias que se indican (la comprobación se realiza en el orden indicado):

  • Se encuentra un fin de fichero ( traits::eof() ) en la secuencia de entrada.

  • El próximo carácter c a extraer coincide con delim (c == delim). En cuyo caso es extraído (cuenta en gcount()) pero no es almacenado.

  • Se han almacenado n-1 caracteres.

Cualquiera que sea el final, la función almacena a continuación un carácter nulo en la siguiente posición de la matriz.

Nota: gcount() es un método de basic_istream que devuelve el número de caracteres leídos en la última operación de lectura (input) no formateada. Ver ejemplo a continuación

Comentario

Es evidente que, salvo que establezcamos un bucle, o definamos un delimitador que no exista en el fichero, la función es incapaz de leer por sí misma un fichero completo si este contiene caracteres delim intercalados. En cambio, puede ser muy útil para leer sucesivamente líneas de un fichero. La única precaución es establecer el delimitador adecuado y una matriz de entrada (señalada por s) de tamaño suficiente para la línea más larga que pueda encontrarse.

La finalización correcta se señala mediante una invocación setstate(eofbit). En caso de error se sigue el procedimiento estándar (excepciones 5.3.3).

Ejemplo

char line[50];
cin.getline(line, 50);


Aunque puede utilizarse con ficheros, en este ejemplo se utiliza con el flujo estándar de entrada cin (generalmente el teclado). La función termina cuando se han introducido 49 caracteres o se pulsa LF (0Ah), en cuyo momento el buffer line contiene la secuencia de caracteres introducida terminada en un carácter nulo ( 3.2.3f).

Ejemplo

#include <fstream>
#include <iostream>
#include <conio.h>        // necesario para la función getch()

using namespace std;
char* filePath = "D:\\Doc\\Informacion del Sistema.TXT";
char line[250];

int main ( ) {            // ==============


   ifstream oStreamFi (filePath, ios::in | ios::binary );
   if (!oStreamFi.is_open()) {
       cout << "Error al abrir el Fichero!!" << endl;

        system("PAUSE");
       return EXIT_FAILURE; 
   }

   cout << "\r\nPulse cualquier tecla para comenzar\r\n";
   for( ; ; ) if(getch()!=0) break;
   unsigned int lCounter = 0, cCounter = 0;
    

   while ( oStreamFi.good() ) {            // bucle de lectura
       oStreamFi.getline(line, 250, '\r');
       cout << line;
       ++ lCounter;

       cCounter += oStreamFi.gcount();
   }


   cout << "\r\nTotal " << lCounter << " lineas y "
        << cCounter << " caracteres" << endl;    

   system("PAUSE");
   return EXIT_SUCCESS;
}

Comentario

Este programa lee un fichero de disco en modo binario y lo muestra en pantalla, incluyendo información sobre el número de líneas y total de caracteres leídos. Observe el uso de los métodos good() para finalizar el bucle cuando se alcanza el fin de fichero y de gcount() para incrementar el contador de caracteres.

El control del bucle puede realizarse también mediante el método eof() , de forma que la sentencia puede ser sustituida por esta otra:

while ( !oStreamFi.eof() ) {    // bucle de lectura-bis

§4.6  peek()

int_type peek();

Este método es similar a get().  La diferencia es que este no hace avanzar el puntero interno, de forma que una posterior operación volvería a incluir el mismo carácter. Son pertinentes aquí las mismas observaciones respecto al tipo devuelto.

§4.7  seekg()

Existen dos versiones de este método cuyos comportamientos son similares;  en ambos casos se devuelve una referencia al propio objeto (*this) y se permite desplazar el puntero de la secuencia en el valor definido por pos.

basic_istream<charT,traits>& seekg(pos_type pos);

basic_istream<charT,traits>& seekg(off_type& pos, ios_base::seekdir mode);

En primer lugar se comprueba si es posible el desplazamiento, verificando que fail() devuelve false .  En cuyo caso se realizan respectivamente las invocaciones rdbuf()->pubseekpos(pos) o rdbuf()->pubseekoff(pos,mode). El argumento mode de la segunda forma, permite también definir el modo de desplazamiento (seekdir ).


  Este método no puede usarse (no está definido) para los flujos de salida (ofstreams ) que disponen de su propio método de posicionamiento.  Ejemplo:

ofstream stream ("SomeFile");
stream.seek(n);       // Error de compilación!!


Recuerde que cada operación de lectura/escritura avanza automáticamente el puntero interno ("file pointer") hasta que se alcanza el final del fichero, de forma que determinadas combinaciones de apertura-lectura-desplazamiento ("seek") son permitidas y otras no. Considere atentamente los siguientes casos:

char c = '\0';            // se repite en los demás ejemplos

ifstream streamIn ("SomeFile", ios::in);
for (int i=1; i<100; ++i) streamIn.get(c);

cout << c;                // Ok.

La secuencia anterior lee los primeros 100 caracteres del fichero y los muestra en pantalla. Observe que, por defecto, la apertura coloca el puntero al principio del stream.

ifstream streamIn ("SomeFile", ios::in | ios::ate);
for (int i=1; i<100; ++i) streamIn.get(c);      // Error!!

Esta apertura coloca el puntero al final del fichero, por lo que no son posibles desplazamientos (ni lecturas) posteriores.

ifstream streamIn ("SomeFile", ios::in);

streamIn.seekg(-100);
for (int i=1; i<100; ++i) streamIn.get(c);      // Error!!

Tampoco es posible esta combinación. La apertura coloca el puntero al comienzo de la secuencia, lo que imposibilita un desplazamiento ("seek") negativo (hacia atrás). El error producido hace que los desplazamientos que seguirían a las lecturas de la última sentencia queden bloqueados.

ifstream streamIn ("SomeFile", ios::in);

streamIn.seekg(-100, ios::beg);
for (int i=1; i<100; ++i) streamIn.get(c);      // Error!!

Es equivalente a la anterior.

ifstream streamIn ("SomeFile", ios::in);

streamIn.seekg(-100, ios::cur);
for (int i=1; i<100; ++i) streamIn.get(c);      // Error!!

Esto tampoco es posible. Observe que, en este caso, ios::beg e ios::cur son equivalentes.

ifstream streamIn ("SomeFile", ios::in | ios::ate);

streamIn.seekg(-100, ios::end);
for (int i=1; i<100; ++i) streamIn.get(c);      // Ok!!

Se leen los últimos 100 caracteres del fichero y se muestran en pantalla.

ifstream streamIn ("SomeFile", ios::in | ios::ate);

streamIn.seekg(-100, ios::cur);
for (int i=1; i<100; ++i) streamIn.get(c);      // Ok!!

Es análoga a la anterior. Observe que en este caso ios::end e ios::cur son equivalentes.

ifstream streamIn ("SomeFile", ios::in);

streamIn.seekg(-100, ios::end);
for (int i=1; i<100; ++i) streamIn.get(c);      // Ok!!

Equivalente a la anterior. La apertura ha dejado el puntero señalando al inicio de la secuencia, pero el desplazamiento indicado en la segunda sentencia se realiza respecto del final.

Observe que seekg(n, mode) es independiente de la posición anterior del puntero. Lo que no es independiente son los movimientos posteriores, seekg(x), o el valor n respecto de mode.

Las formas de posicionarse al principio y final del fichero después de abierto, con independencia de la posición actual, son:

streamIn.seekg(0, ios::beg);

streamIn.seekg(0, ios::end);


En el siguiente ejemplo, se ha construido una función que devuelve el carácter de posición n de un filestream. La función permite definir la forma de considerar el desplazamiento n en función del segundo argumento.

ifstream myStream ("myFile");

...

cout << "El carácter 120: " << readChar(myStream, 119, ios::beg);

...

 

char readChar (ifstream& stream, ifstream::pos_type n, ifstream::seekdir mode ) {
  stream.seekg(n, mode);
  return (char) get();
}


Observe la forma correcta de definir el tipo de ambos argumentos (el valor n, probablemente será transformado en un entero por el compilador). Observe también que ha sido necesario un "casting" para devolver un carácter ya que el método get() devuelve un entero. Los valores posibles para el segundo argumento serían: ios::beg; ios::cur e ios::end

Cuando en una rutina de lectura se alcanza el final de fichero, el método eof() devuelve true, y el badbit de state puede activarse, con el resultado de que puede deshabilitar el mecanismo de desplazamiento. Por ejemplo:

char c = '\0';

ifstream streamI ("someFile");  

while ( !streamI.eof() ) {       // bucle de lectura
   oStreamFi.getline(line, 250, '\r');
    ...

}

streamI.seekg(ios::beg);
stream.get(c);   // Posible error!!

Después de un bucle como el anterior, C++Builder funciona correctamente leyendo en c el primer carácter de la secuencia. Sin embargo, el compilador GNU Cpp  3.1-20030804-1 (versión Mingw para Windows) lee caracteres incorrectos. Una solución en este caso es limpiar el badbit mediante una invocación al método clear(), después de lo cual la rutina funciona correctamente. En este último caso el código quedaría como sigue: 

char c = '\0';

ifstream streamI ("someFile" );  

while ( !streamI.eof() ) {       // bucle de lectura
   oStreamFi.getline(line, 250, '\r');
    ...

}

streamI.clear();                 // quizás necesario

streamI.seekg(ios::beg);
stream.get(c);                   // Ok.!!


§4.8  tellg()

pos_type tellg();

Este método devuelve la posición actual del puntero interno dentro de la secuencia de entrada, o -1 en caso de fallo. En realidad la invocación se transforma en la de un método del buffer intermedio: rdbuf()->pubseekoff(0,cur,in).

El siguiente ejemplo utiliza tellg() para calcular el tamaño de un fichero.

ifstream myStream ("somefile");              // abrir fichero

myStream.seekg(0, ios::end);                 // ir al final
ifstream::pos_type pos = myStream.tellg();   // ver que posición es esta
cout << "Longitud del fichero: " << pos << " Bytes" << endl;


Observe la definición de pos en la tercera línea. Aunque probablemente el compilador lo transformará en un unsigned long, esta es la forma correcta de declarar su tipo.

§5  basic_ostream

Esta superclase proporciona los métodos para las operaciones de salida (escritura) en los dispositivos conectados con los filestreams.  Al igual que ocurre con las operaciones de entrada, también existen dos grupos de métodos que permiten salidas formateadas y no formateadas. Los primeros se denominan insertores, aunque ambos grupos "insertan" caracteres en el dispositivo de salida a través de un buffer intermedio ( 5.3.2f).

Los métodos de salida también construyen un objeto centinela de la clase classbasic_ostream::sentry, para garantizar la inicialización segura, controlar el estado del flujo y bloquearlo si se trabaja en una aplicación multihebra.

La mecánica de salidas es análoga a la de entradas, con la diferencia de que se dispone de métodos específicos para la escritura.  Estos métodos también aquí realizan una invocación a setstate(x) con el argumento x adecuado a la situación presentada (fin de fichero, error, Etc.) si la operación no finaliza con éxito.

Para las salidas no formateadas se dispone de los siguientes métodos:

§5.1 flush()

ostream_type& flush();

La función devuelve una referencia al objeto-flujo (*this). Su efecto es vaciar el almacenamiento intermedio escribiendo todo su contenido en el fichero de salida. En realidad la operación se realiza invocando un método del objeto-buffer, de forma que la invocación

myOstream.flush();

se transforma en:

myOstream.rdbuf()->pubsync();

A fin de garantizar que los datos serán escritos en el dispositivo de salida y prevenir contra caídas intempestivas del sistema, después de transacciones importantes, es aconsejable utilizar este método, especialmente si el fichero va a permanecer abierto [5].

§5.2  write()

basic_ostream& write(const char_type* s, streamsize n);

La función va tomando caracteres de posiciones sucesivas de la matriz señalada por el puntero s y las inserta en el dispositivo de salida hasta que se alcanza alguna de las condiciones siguientes:

  • Se insertan n caracteres

  • La inserción falla, en cuyo caso se invoca setstate(badbit). 

Observe que es responsabilidad del programador garantizar que el valor n concuerda con el tamaño de la matriz s utilizada.

§5.3  put()

basic_ostream<charT,traits>& put(char_type c);

El efecto es insertar el carácter c en la secuencia de salida. En caso de fallo se sigue el procedimiento habitual.

§5.4  seekp()

Este método tiene dos formas y su comportamiento es análogo al de seekg() en los flujos de entrada .  En ambos casos se devuelve una referencia al propio objeto (*this) y se desplaza el puntero de la secuencia en el valor definido por pos.  El argumento mode de la segunda forma, permite definir el modo de desplazamiento (seekdir ).

basic_ostream<charT,traits>& seekp(pos_type& pos);
basic_ostream<charT,traits>& seekp(off_type& pos, ios_base::seekdir mode);

Una vez comprobado que el desplazamiento es posible, verificando que fail() devuelve false , se realiza una invocación al método correspondiente del buffer intermedio (que controla estos aspectos del flujo), de forma que las invocaciones a estos métodos se transforman en rdbuf()->pubseekpos(pos) y rdbuf()->pubseekoff(pos,mode) respectivamente.

§5.5  tellp()

pos_type tellp();

Este método devuelve la posición actual del puntero interno, o -1 en caso de error. La llamada se transforma en la invocación a un método del bufer intermedio: rdbuf()->pubseekoff(0,cur, out).

El ejemplo que sigue utiliza los métodos anteriores para escribir cierto contenido en un fichero

ofstream stream ("newFile.txt", ios::out | ios::trunc);
char* contenido = "AEIOU";
char fin = 'F';


stream.seekp(100, ios::beg);      // desplazar puntero
stream.write(contenido, 5);       // escribir

stream.put(fin);                  // escribir
ofstream.flush();                 // forzar escritura del buffer

 

stream.seekp(0, ios::end);        // ir al final del fichero
ofstream::pos_type pos = stream.tellp();         // comprobar posición

cout << "Tamaño del fichero: " << pos << endl;   // resultado

Salida:

Tamaño del fichero: 106

Comentario

El modo de apertura elegido borra cualquier fichero anterior del mismo nombre, así que después de ejecutada la primera sentencia, su tamaño es cero. Observe que la matriz contenido tiene realmente seis caracteres (termina en un carácter nulo 3.2.3f), pero hemos indicado 5 en la cantidad a escribir.

Como el primer seekp() desplaza el inicio de la escritura más allá del final actual, el tamaño aumenta en una cantidad mayor que los caracteres realmente escritos. Como resultado, el contenido del fichero es basura en los 100 primeros caracteres (seguramente trozos de ficheros antiguos ya borrados), seguidos de la secuencia "AEIOUF".

Señalar finalmente que si el programa fuese corto, el vaciado de buffers no sería necesario, se realizaría automáticamente en cuanto se cerrara el fichero, stream.close(), o cuando el objeto stream fuese destruido (al salir de la función actual).


El ejemplo que sigue puede servir como comprobación del supuesto comentado anteriormente ( 5.3.1), al tratar las diferencias entre las operaciones de flujo formateadas y no formateadas.

char* S1 = "AEIO";
float S2 = 12.5;
char* pS2 = (char*) &S2;      // L.3
cout.write(S1, 4);            // L.4
cout.write(pS2, 4);           // L.5
cout << endl;                 // L.6

cout << S1 << S2 << endl;

Salida:

AEIO  HA
AEIO12.5

Comentario

Las dos primeras líneas preparan los valores de partida. La salida no-formateada está representada por las líneas 4 y 5, mientras que la versión formateada se realiza en la última sentencia. La sentencia L.6 simplemente inserta una nueva línea y fuerza la escritura del buffer de salida. La hemos colocado para separar ambas salidas.

Los únicos puntos a destacar en el programa son: comprobar como la clase cout dispone también de métodos para operaciones planas, y el artificio empleado para utilizar el método write() con un float. Como este método espera un puntero a matriz de caracteres, lo definimos en L.3, procurando que señale al principio del almacenamiento de S2 y realizando el modelado necesario para que el compilador no proteste.

  Inicio.


[1]  Recordar que el objeto creado por ambos constructores representa un flujo ("Stream") y que en la terminología Unix, el hecho de conectar (asociar) un flujo a un fichero equivale a abrirlo (open).

[2]  La razón por la que una instancia de ifstream  (que oficialmente es una clase para controlar flujos de lectura) puede abrirse para escritura, es algo que habría que preguntar a los miembros del Comité de Estandarización.

[3]  widen( 5.3.2b) es un método de basic_ios que acepta un carácter estrecho y devuelve su versión ancha ( 2.2.1a1).

[4]  Estas advertencias corresponden a la librería clásica de E/S de C++Builder, pero no está demás tenerlas en cuenta porque la STL está construida sobre la anterior.

[5]  Al tratar de las salidas formateadas (insertores) veremos que, en estos casos, el forzado de escritura del bufer se realiza mediante manipuladores.

[6]  En los objetos wifstream, int_type es wchar_t.