jueves, 17 de agosto de 2017

Streams, entrada y salida de datos

Streams, entrada y salida de datos

En este capítulo abordaremos el tema de la manipulación de datos a través de los dispositivos de entrada y salida estándar por medio de ciertos componentes lógicos conocidos como: streams. A manera de definición, un stream es una especie de canal a través del cual fluyen los datos. Técnicamente, un stream es el enlace lógico utilizado por el programador en C, C++ para leer o escribir datos desde y hacia los dispositivos estándar conectados a la PC. Normalmente, el dispositivo estándar para manipular entradas es el teclado y este, en C++, está asociado al objeto cin; el dispositivo estándar de salida está asociado (generalmente) con la pantalla o monitor de despliegue de la PC y, el mismo, en C++ se puede acceder por medio del objeto cout. El dispositivo estándar para mensajes de error es el cerr, y el mismo está asociado por defecto con la pantalla o monitor de despliegue. Otro dispositivo estándar es la impresora, pero este último no es soportado por los objetos de la iostream. Los programadores que hayan tenido alguna experiencia al programar en C estándar, notarán que los tres objetos mencionados coinciden con los dispositivos: stdinstdout y stderr. La tabla que se muestra en seguida puede servirnos de base.

Tabla I/O : 01, dispositivos estándar de I/O
C estandarC++
stdincin
stdoutcout
stderrcerr
---clog

La iostream[editar]

La iostream es la biblioteca estándar en C++ para poder tener acceso a los dispositivos estándar de entrada y/o salida. En sus programas, si usted desea hacer uso de los objetos cincoutcerr y clog tendrá que incluir ( por medio de la directiva #include ) el uso de la biblioteca iostream. En la iostream se encuentran definidas las clases ios ( misma que es la base para las clases que implementen operaciones de entrada y/o salida de datos ), istream ( para operaciones de entrada ) y ostream ( para operaciones de salida ). Aparte de las clases mencionadas, en la iostream se encuentra una lista de variables y constantes ( atributos ) que son accesibles por el usuario a través del operador de ámbito ( :: ).

Streams automáticos[editar]

Si usted usa la directiva #include <iostream.h> o #include <iostream> en sus programas, automáticamente la iostream pone a su disposición los objetos cincoutclog y cerr en el ámbito estándar (std), de tal manera que usted puede comenzar a enviar o recibir información a través de los mismos sin siquiera preocuparse de su creación. Asi, un sencillo ejemplo del uso de los objetos mencionados se muestra en seguida.
// De nuevo con el hola mundo...
#include <iostream.h>
int main()
{
    std::cout << "Hola mundo";    // imprimir mensaje (en la pantalla)
    std::cin.get();    // lectura ( entrada del teclado )
    return 0;
}

Operadores de direccionamiento[editar]

Los operadores de direccionamiento son los encargados de manipular el flujo de datos desde o hacia el dispositivo referenciado por un stream específico. El operador de direccionamiento para salidas es una pareja de símbolos de "menor que" <<, y el operador de direccionamiento para entradas es una pareja de símbolos de "mayor que" >>. Los operadores de direccionamiento se colocan entre dos operandos, el primero es el Stream y el segundo es una variable o constante que proporciona o recibe los datos de la operación. Por ejemplo, en el siguiente programa y en la instrucción cout << "Entre su nombre: "; la constante "Entre su nombre: " es la fuente o quien proporciona los datos para el objeto cout. Mientras que en la instrucción cin >> nombre la variable nombre es el destino o quien recibe los datos provenientes del objeto cin.
// De nuevo con el hola mundo...
#include <iostream.h>
int main()
{
    char nombre[80];
    cout << "Entre su nombre: ";
    cin  >> nombre;
    cout << "Hola," << nombre;
    cin.get();
    return 0;
}
Observe que si en una misma línea de comando se desea leer o escribir sobre varios campos a la vez, no es necesario nombrar más de una vez al stream. Ejemplos:
cout << "Hola," << nombre;
cin >> A >> B >> C;

Banderas de I/O[editar]

En esta sección abordaremos de manera más directa el tema sobre el control de formato para los stream de C++. Específicamente, veremos las tres diferentes formas que existen en C++ para manipular las banderas relacionadas a los stream y que nos permitirán gobernar de una manera más precisa la forma para representar datos de salida y/o entrada. En ese sentido, veremos que la primera de las forma que nos permitirá el formateo sera a través de las funciones flags()setf() y unsetf(), la segunda y la tercera forma las encontraremos en ciertos manipuladores directos definidos en las bibliotecas <iostream> y <iomanip>.

Banderas de formato:[editar]

C++ define algunas banderas de formato para entradas y salidas estándar, las cuales pueden ser manipuladas a través de la funciones (métodos) flags()setf(), y unsetf(). Por ejemplo,
cout.setf(ios::left);
activa la justificación a la izquierda para todas las salidas dirigidas hacia cout.

A continuación se muestra una tabla de referencia de las banderas de I/O.
Tabla I/O : 02, banderas de formato
BanderaDescripción
boolalphaLos valores booleanos pueden ser leídos/escritos usando las palabras "true" y "false"
decLos valores numéricos se muestran en formato decimal
fixedNúmeros de punto flotante se despliegan en forma normal
hexLos valores numéricos se muestran en formato hexadecimal
leftLa salida es justificada por la izquierda
octLos valores numéricos se muestran en formato octal
rightLa salida es justificada por la derecha
scientificNúmeros de punto flotante se despliegan en notación científica
showbaseDespliega la base de todos los valores numéricos
showpointDespliega el punto decimal y extra ceros, aún cuando no sean
necesarios
showposDespliega el símbolo de más antes de valores positivos
skipwsDescarta caracteres de espaciado (espacios, tabuladores, nuevas líneas) cuando se lee desde un stream
unitbufDescarga el buffer después de cualquier inserción
uppercaseDespliega la "e" en notaciones científicas y la "x" en notaciones decimales como letras mayúsculas

Manipulando la lista de banderas de I/O de C++ (mostrada arriba) se pueden controlar los aspectos relacionados a la forma con la cual se desean presentar los datos en la salida. Por ejemplo, el programa que se muestra en seguida, activa la bandera boolalpha para mostrar los resultados de operaciones booleanas como "true" o "false" en lugar de "0" o "1" como es lo normal.
// Programación con C++
// programa Banderas01.cpp;
// probado en Dev-Cpp Versión 4.9.9.2

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    cout <<"\n0 > 1 ? "<<"\t"<<(0>1)<< endl;
    cout <<"\n5 > 1 ? "<<"\t"<<(5>1)<< endl;

    cout.setf(ios::boolalpha); // activar bandera
    cout <<"\n0 > 1 ? "<<"\t"<<(0>1)<< endl;
    cout <<"\n5 > 1 ? "<<"\t"<<(5>1)<< endl;

    cin.get();
    return 0;
}

Manipuladores[editar]

Las banderas también pueden manipularse directamente usando los siguientes manipuladores. Seguramente usted ya estará familiarizado con el manipulador endl, el mismo puede darle una idea de cómo son usados los manipuladores. Por ejemplo, usted puede establecer la bandera de números decimales usando el comando:
cout << dec;
La tabla que se muestra en seguida corresponde a los manipuladores definidos en <iostream>.

Tabla I/O : 03, manipuladores en <iostream>
ManipuladorDescripciónEntradaSalida
boolalphaActiva la bandera boolalphaXX
decActiva la bandera dec-imalXX
endlEscribe carácter de cambio de línea---X
endsEscribe el carácter null---X
fixedActiva la bandera fixed (para números reales)---X
flushDescargar el stream---X
hexActiva la bandera hex-adecimalXX
internalActiva la bandera interna---X
leftActiva la bandera left (izquierda)---X
noboolalphaDesactiva la bandera boolalphaXX
noshowbaseDesactiva la bandera showbase---X
noshowpointDesactiva la bandera showpoint---X
noshowposDesactiva la bandera showpos---X
noskipwsDesactiva la bandera skipwsX---
nounitbufDesactiva la bandera unitbuf---X
nouppercaseDesactiva la bandera uppercase---X
octActiva la bandera oct-alXX
rightActiva la bandera de justificar derecha---X
scientificActiva la bandera scientific---X
showbaseActiva la bandera showbase---X
showpointActiva la bandera showpoint---X
showposActiva la bandera showpos---X
skipwsActiva la bandera skipwsX---
unitbufActiva la bandera unitbuf---X
uppercaseActiva la bandera uppercase---X
wsLimpiar cualquier espacio al inicioX---

El programa que se muestra en seguida es un ejemplo de como emplear manipuladores directos para formatear salidas al stream estándar de salida ( cout ). En el mismo, se emplea el manipulador boolalpha para que los resultados de la operaciones logicas sean textuales, o sea, true o false y los manipuladores dechex y oct.
// Programación con C++
// programa Banderas02.cpp;
// probado en Dev-Cpp Versión 4.9.9.2

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
 cout << boolalpha;
 cout << "0 > 1 ? " << '\t' << (0 > 1) << endl;
 cout << "5 > 1 ? " << '\t' << (5 > 1) << endl;
 
 cout << dec << 2048 << endl;
 cout << hex << 2048 << endl;
 cout << oct << 2048 << dec << endl;

 cin.get();
 return 0;
}

Por último, y para terminar esta sección, hablaremos de los manipuladores definidos en la biblioteca <iomanip>. Estos operan directamente igual que los que se vierón anteriormente, salvo que son parametrizados, es decir, operan en línea de salida mediante el operador <<, pero los mismos operan a manera de funciones, o sea con parámetros.

Tabla I/O : 04, manipuladores en <iomanip>
ManipuladorDescripciónEntradaSalida
resetioflags( long f )Desactiva las banderas especificadas por fXX
setbase( int base )Establece la bases numérica a base---X
setfill( int ch )Establece carácter de relleno a ch---X
setioflags( long f )Activa las banderas especificadas por fXX
setprecision( int p )Establece el número de digitos de precisión a p---X
setw( int w )Establece la longitud de campo a w---X

Atendiendo a las tablas de banderas, así como a las tablas de manipuladores para las mismas mostradas arriba, usted puede (con practica y perceverancia) lograr salidas con muy buena presentación hacia los dispositivos estándar. Por ejemplo, el programa que se mostrará a continuación muestra una de las formas de emplear manipuladores para formatear números de punto flotante, en el programa se despliega una lista de valores numéricos y se establece el campo de salida a una longitud de 12 caracteres y dos decimales.
// Programación con C++
// programa Banderas03.cpp;
// probado en Dev-Cpp Versión 4.9.9.2

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
 double data[] = { 347.25, 45.75, 124.50, 456.80, 1500.90 };
 double total = 0;
 int ancho = 12;

 cout.precision(2);
 cout.setf(ios::fixed);

 for (int c = 0; c < 5; c++) {
  cout << setw(ancho) << data[c] << endl;
  total += data[c];
 }
 cout.fill('-');
 cout << setw(ancho) << "" << endl;
 cout.fill(' ');
 cout << setw(ancho) << total << endl;

 cout << "Por favor oprime Enter...";
 cin.get();
 return 0;
}

Streams para archivos o ficheros[editar]

Por definición, un archivo es una colección de datos almacenados en algún lugar. En C++, el programador puede considerar que un archivo es un stream, de tal manera que los objetos vistos en la sección anterior ( cin, cout, cerr y clog ) son una especie de archivo manipulados por streams. Este punto es de suma importancia, ya que todo lo que se ha mencionado acerca de los atributos, banderas y manipuladores; que son miembros de los objetos ( stream ) para entrada y salida estándar, son aplicables a los streams que usemos para trabajar con archivos en disco y/o cualquier otro dispositivo de almacenamiento. Hasta este momento, solamente se ha mencionado que los streams poseen banderas y que las mismas pueden alterarse a traves de los manipuladores, sin embargo, hace falta hablar acerca de los métodos o funciones que son miembros de dichos streams. Así, antes de ver un ejemplo para mostrar el uso de archivos en disco listaremos una tabla más, o sea, la de los métodos aplicables a los streams.

Tabla I/O : 05, métodos para objetos istream, ostream y fstream
FunciónDescripción
badtrue si ha ocurrido un error
clearlimpia las banderas de estado (status flags)
closecierra un stream
eoftrue si se alcanzó el fin de archivo
failtrue si ha ocurrido un error
fillestablecer manipulador de carácter de relleno
flagsaccesa o manipula las banderas de formato de un stream
flushvaciar el buffer de un stream
gcountnúmero de caracteres leidos durante la última operación de entrada
getlectura de caracteres
getlinelectura de una línea de caracteres
goodtrue si no ha ocurrido un error
ignoreleer y descartar caracteres
openabrir un stream de entrada y/o salida
peekverifica la siguiente entrada de carácter
precisionmanipula la precisión del stream
putescritura de caracteres
putbackregresar caracteres al stream
rdstateregresa la bandera de estado de stream
readlee datos de un stream hacia un buffer
seekgrealiza acceso aleatorio sobre un stream de entrada
seekprealiza acceso aleatorio sobre un stream de salida
setfcambiar las banderas de formato
tellglee el puntero del stream de entrada
tellplee el puntero del stream de salida
unsetflimpiar las banderas de formato
widthaccesa y manipula la longitud minima del campo
writeescritura datos desde un buffer hacia un stream

Abrir y cerrar archivo[editar]

A diferencia de los streams para dispositivos estándar, los cuales son creados y abiertos de manera automática, para trabajar con archivos en discos se debe primeramente "abrir el archivo", y luego de haber terminado de leer o escribir datos en el mismo, se debe "cerrar el archivo". En C++, en orden de trabajar con archivos en disco, podemos emplear las clases fstreamifstream, y ofstream. Si usted desea abrir un archivo específico en modo de lectura y escritura use un objeto de la clase fstream, por el contrario, si desea abrir un archivo solo para lectura o solo para escritura use objetos de la clase ifstream y ofstream, respectivamente.
La sintaxis para crear objetos de las tres clase mencionadas es:
'''streams para lectura y escritura'''
fstream();
fstream(const char*, int, int = filebuf::openprot);
fstream(int);
fstream(int _f, char*, int);

'''streams solo para lectura'''
ifstream();
ifstream(const char*, int, int = filebuf::openprot);
ifstream(int);
ifstream(int _f, char*, int);


'''streams solo para escritura'''
ofstream();
ofstream(const char*, int, int = filebuf::openprot);
ofstream(int);
ofstream(int _f, char*, int);
Observe que la sintaxis para crear objetos de las tres clases mostradas arriba es la misma. Es decir,
  1. El primer método constructor crea un objeto que no está (aún) asociado a un archivo en disco, en estos casos, se tendrá que usar el método stream.open("nombre de archivo"); para establecer la conexión entre el stream y el archivo en disco.
  2. El segundo método constructor crea un objeto asociado a un archivo en disco, en estos casos, el archivo en el disco queda abierto y asociado al stream. En este, el parámetro char * apunta a una cadena de caracteres con el nombre del archivo.
  3. El tecer método crea un stream a raiz de un identificador de archivo.
  4. El cuarto método crea un stream a raiz de un identificador de archivo, un buffer y tamaño de buffer específicos.
Para nuestro primer ejemplo usaremos el segundo de los constructores mencionados. Así, el programa que se muestra en seguida realiza las siguientes tareas:
  1. Crea y escribe sobre el archivo de texto test.$$$
  2. Abre y lee los datos del archivo test.$$
  3. Cierra el archivo test.$$
Para el caso que se presenta se debe prestar atención a los metodos:
  1. bad(), para verificar el estado de error del stream
  2. get(), para leer caracteres del stream
  3. put(), para escribir caracteres en el stream
  4. close(), para cerrar el archivo.
// Ejemplo de ifstream y ofstream
// En este programa se demuestra la forma de crear un archivo
// en disco por medio de objeto ofstream, y la manera abrir y
// leer un archivo por medio de un objeto ifstream.

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


using namespace std;

char *filename = "test.$$$";
char *data = "Esta línea de texto se guardará en el archivo test.$$$";

// crear un archivo en disco cuyo nombre es dado por filename
int crearArchivo(char *filename)
{
    ofstream fichero(filename); // crear o rescribir archivo
    // verificar la creación del archivo
    if ( fichero.bad() ) {
 cout << "Error al tratar de abrir archivo";
 cin.get();
 return 1;
    }

    // escribir datos al archivo
    for (unsigned int t = 0; t < strlen(data); t++ )
 fichero.put(data[t] );

    fichero.close();
    cout << "archivo creado exitosamente" << endl;
    return 0;
}

// abrir un archivo en disco cuyo nombre es dado por filename
int leerArchivo(char *filename)
{
    ifstream fichero(filename); // abrir archivo para lectura

    // verificar la apertura del archivo
    if ( fichero.bad() ) {
 cout << "Error al tratar de abrir archivo";
 cin.get();
 return 1;
    }

    // lectura de datos
    while ( ! fichero.eof() ) cout << (char)fichero.get();
    fichero.close();
    cout << endl << "archivo leido exitosamente" << endl;
    return 0;
}

int main()
{
    crearArchivo(filename);
    leerArchivo(filename);
    cout << endl << "Presione <Enter>...";
    cin.get();
    return 0;
}

Usando operadores de redirección ( <<, >> )
sobre streams asociados con archivos en disco.

No hay que olvidar que si un archivo es asociado a un stream las operaciones de entrada y/o salida para dicho archivo se hacen a travez del stream y, por lo tanto, se puede usar, no solo los operadores de redirección, sino también todos los metodos, manipuladores y atributos que se ha mencionado hasta este momento. Así, el objetivo del siguiente programa es demostrar como hacer uso del operador << sobre un archivo asociado a un stream de salida, y del operador >> sobre un archivo asociado a un stream de entrada. El ejemplo que se mostrará no es realmente útil, sin embargo cumple con su cometido, es decir, el punto de interes del mismo está en las líneas de código:
while ( ! fin.eof() ) {
    fin  >> temp;
    fout << temp ;
}
Encargadas de leer datos del archivo de entrada y de escribir los mismos en el archivo de salida.

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

char *filename = "test.$$$";

// abre y lee datos de un archivo en disco cuyo nombre es dado por filename
int leerArchivo(char *filename)
{
    ifstream fichero(filename); // abrir archivo para lectura

    // verificar la apertura del archivo
    if ( fichero.bad() ) {
 cout << "Error al tratar de abrir archivo";
 cin.get();
 return 1;
    }

    // lectura de datos
    while ( ! fichero.eof() ) cout << (char)fichero.get();
    fichero.close();
    cout << endl << "archivo leido exitosamente" << endl;
    return 0;
}

// crea una copia del archivo test.$$$ hacia test.bak
// Nota: ningún carácter de espaciado en el origen le es tranferido al destino
int backup(char *filename)
{
    char newname[80];
    int i = 0;

    // copia el nombre del archivo original y remplaza la extensión por "BAK".
    while (filename[i] != '.' ) newname[i] = filename[i++];
    newname[i] = '\0';
    strcat(newname, ".BAK");

    ifstream fin( filename );   // abrir archivo de entrada
    ofstream fout( newname, ios::trunc );  // abrir archivo de salida

    char temp;
    while ( ! fin.eof() ) {
      fin  >> temp;
      fout << temp ;
    }

    fin.close();
    fout.close();
    return 0;
}

int main()
{
    backup(filename);
    leerArchivo("test.bak");
    cout << endl << "Presione <Enter>...";
    cin.get();
    return 0;
}

No hay comentarios.:

Publicar un comentario

Sentencias de c++

Sentencias de c ++ 1  Sinopsis Las  sentencias  ("Statements") especifican y controlan el flujo de ejecución del programa. Si...