5.3.2c basic_istream
§1 Sinopsis
La finalidad de esta super-clase es ayudar a leer e interpretar flujos de entrada controlados por un streambuf ( 5.3.2f). Sus métodos y propiedades permiten controlar aspectos específicos de los flujos originados en los ficheros (dispositivos) asociados a los filestreams.
basic_istream dispone de métodos para entradas formateadas y no-formateadas. Aunque todas ellas realizan operaciones de entrada ("extracción"), aquí nos centraremos en las entradas no formateadas, dejando las primeras (denominadas extractores) para el capítulo dedicado a las E/S formateadas ( 5.3.3b).
En atención a su capacidad para las operaciones de entrada ("input"), a los miembros de esta clase se los denomina de forma genérica istreams (input-streams).
§2 Interfaz
template <class charT, class traits = char_traits<charT> >
class basic_istream : virtual public basic_ios<charT,traits> {
public:: // Tipos derivados de basic_ios:
typedef charT char_type;
typedef typename traits::int_type int_type;
typedef typename traits::pos_type pos_type;
typedef typename traits::off_type off_type;
typedef traits traits_type;
// Constructor / destructor:
explicit basic_istream(basic_streambuf<charT,traits>* sb);
virtual ~basic_istream();
class sentry; // Clase centinela
// 15 métodos para entradas formateadas (extractores) ( 5.3.3b)
// Entradas no formateadas:
basic_istream<charT,traits>& get(char_type& c);
basic_istream<charT,traits>& get(char_type* s, streamsize n);
basic_istream<charT,traits>&
get(char_type* s, streamsize n, char_type delim);
basic_istream<charT,traits>&
get(basic_streambuf<char_type,traits>& sb);
basic_istream<charT,traits>&
get(basic_streambuf<char_type,traits>& sb,
char_type delim);
basic_istream<charT,traits>&
getline(char_type* s, streamsize n);
basic_istream<charT,traits>&
getline(char_type* s, streamsize n, char_type delim);
basic_istream<charT,traits>&
ignore(streamsize n = 1, int_type delim = traits::eof());
basic_istream<charT,traits>& read(char_type* s, streamsize n);
streamsize readsome(char_type* s, streamsize n);
basic_istream<charT,traits>& putback(char_type c);
basic_istream<charT,traits>& unget();
basic_istream<charT,traits>& seekg(pos_type);
basic_istream<charT,traits>& seekg(off_type, ios_base::seekdir);
};
Además de los 15 extractores definidos como función-miembro, se definen seis
funciones genéricas adicionales para entrada de caracteres. Son
funciones-operador operator>>() definidas como funciones externas
( 5.3.3b).
§2.1 Comentario
La primera acción de estos métodos es construir un objeto de la clase classbasic_istream::sentry. Una vez creado, el método obtiene la entrada requerida. Como indica su nombre (guarda o centinela), la función del objeto sentry es garantizar una inicialización segura; controlar el estado del flujo y bloquearlo si se trabaja en una aplicación multihebra. Después que cada operación es completada con éxito, el método good( 5.3.2b) de basic_ios, devuelve cierto; en caso de problemas devuelve falso.
Ejemplo
ifstream objIn (filePath, ios::out | ios::trunc);
cout << (objIn.good()? "good OK": "good MAL") << endl;
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.)
§3 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 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. El comportamiento es como sigue:
Si !good() se realiza una invocación setstate(failbit) y termina la ejecución.
Si rdbuf()->in_avail() == -1, se realiza una invocación setstate(eofbit) y no se realiza ninguna extracción.
Si rdbuf()->in_avail() == 0, no se realiza extracción.
Si rdbuf()->in_avail() > 0, se extrae el número de caracteres señalado por la expresión min(rdbuf()->in_avail(), n)). Es decir, se leen n caracteres o hasta que se agota el buffer.
Nota: Ver método good( 5.3.2b). Ver método setstate ( 5.3.2b).
in_avail(
5.3.2f)
es un método de la superclase basic_streambuf, que controlan el
flujo. Esta función devuelve el número de caracteres disponibles en el flujo
de entrada si existe alguno. En caso contrario devuelve el número de los que existen en el buffer interno.
§5 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 (ver también peek( ). Observe que, aunque algún tutorial señala que devuelve un carácter, en rigor devuelve un tipo int equivalente [1]. La verificación es muy sencilla utilizando un fichero que contenga algún texto:
istream stream ("fichero.txt");
cout << "El valor es " << stream.get();
Comprobará que se obtiene un resultado númérico, el valor decimal del primer carácter ASCII del fichero. Desde luego puede efectuarse una verificación más rigurosa ( 4.9.14):
istream stream ("fichero.txt");
ifstream::int_type lectura = stream.get();
const type_info & refx = typeid(lectura);
cout << "El tipo es " << refx.name(); //-> El tipo es int
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 forma 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:
get(s, n, widen('\n'));
Nota: 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).
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 ( 5.3.2) 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).
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 que la capa de transporte, encargada de controlar estos aspectos, se encapsula en una jerarquía de clases derivada de la superclase basic_streambuf ( 5.3.2f). 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( 5.3.2b) de basic_ios. 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.
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.
Nota: basic_istream dispone también de un método basic_istream<charT,traits>& putback(char_type c); que permite devolver un carácter c 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. En realidad putback() se limita a la invocación del método sputbackc( 5.3.2f) del buffer intermedio mediante el puntero devuelto por rdbuf( 5.3.2b), de forma que la invocación tiene la forma:
this->rdbuf()->sputback(c);
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.
§6 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 ( 5.3.2), la última condición no se alcanza nunca.
§7 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() 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 ( 5.3.2b).
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 ( 5.3.2b) 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( 5.3.2b), de forma que la sentencia puede ser sustituida por esta otra:
while ( !oStreamFi.eof() ) { // bucle de lectura-bis
§8 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.
§9 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( 5.3.2b) 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 ( 5.3.2a).
Este método no puede usarse (no está definido) para los flujos de salida (ofstreams 5.3.2d) 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.
§11 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.
§12 int sync();
Si rdbuf( 5.3.2b) devuelve un puntero nulo, el método devuelve -1. En caso contrario, invoca rdbuf()->pubsync( 5.3.2f). Si esta última devuelve -1, invoca setstate(badbit), que puede lanzar una excepción ios_base::failure ( 5.3.2b), y devuelve traits::eof(). En caso contrario, devuelve cero.
La misión de este método es sincronizar el dispositivo externo (fichero) con el buffer interno utilizando uno de sus métodos, pubsync() a través del puntero proporcionado por rdbuf().
[1] En los objetos wifstream, int_type es wchar_t.