STREAMS (2)

SOBRECARGA DE EXTRACTORES

Para sobrecargar un extractor, se emplea en general la misma aproximación que se ha seguido para sobrecargar un insertador. Por ejemplo, este extractor admite coordenadas en 3-D. Obsérvese que además se las pide al usuario.

 

  • // Toma valores tridimensionales extractor.
    istream &operator>>(istream &stream, tres_d &obj)
  • {

  • cout << "Escriba los valores X,Y,Z: "
    stream >> obj.x >> obj.y >> obj.z;
    return stream;
  • }
 

Los extractores deben proporcionar una referencia de un objeto del tipo istream. Además, el primer parámetro tiene que ser una referencia de un objeto del tipo istream. Observe que el segundo parámetro es una referencia. Esto es necesario para que su valor pueda ser modificado. La forma general de un extractor es la que se muestra a continuación:

 

  • istream &operator>>(istream &stream, tipo_objeto &obj)
  • {

  • // ponga aquí su código para el extractor
    return stream;
  • }
 

Lo que sigue es un programa que hace una demostración del extractor para objetos del tipo tres_d:

  • #include <iostream.h>
    class tres_d
  • {

  • int x, y, z; // coordenadas 3-d
    public:
    tres_d(int a, int b, int c)
    {

  • x=a; y=b, z=c;
    }

  • friend ostream &operator<<(ostream &stream, tres_d obj);
    friend istream &operator>>(istream &stream, tres_d &obj);
  • };
 
  • // Muestra coordenadas X, Y, Z insertor.
    ostream &operator<<(ostream &stream, tres_d obj)
    {

  • stream << obj.x << ", " ;
    stream << obj.y << ", " ;
    stream << obj.z << "\n";
    return stream; // proporciona el stream
    }
 
  • // Toma valores tridimensionales extractor
    istream &operator>>(istream &stream, tres_d &obj)
    {

  • cout << "Introduzca los valores X,Y,Z : "
    stream >> obj.x >> obj.y >> obj.z;
    return stream;
    }
 

  • main n(void)
  • {

  • tres_d a(1,2,3);
    cout<< a;
    cin >> a;
    cout << <;
    return 0;
  • }
 

Analogamente a los insertadores, las funciones de extractor no pueden ser miembros de la clase sobre la cual están diseñados para operar. Pueden ser friends, como se muestra en el ejemplo, o pueden ser simplemente funciones independientes.
Exceptuando el hecho de que hay que proporcionar una referencia de un objeto del tipo istream, se puede hacer lo que se quiera dentro de la función de extracción. Sin embargo, en aras de la estructura y de la claridad, lo mejor es limitar las acciones de los extractores a la operación de introducción de datos.

FORMATO DE LA ENTRADA Y SALIDA

Como ya es sabido, si se usa printf(), se puede controlar el formato de la información que se muestra en la pantalla. Por ejemplo, se pueden especificar las amplitudes de los campos, y el ajuste a la izquierda o a la derecha. Se puede obtener el mismo tipo de formato utilizando la aproximación de C++ a la E/S. Hay dos maneras de dar formato a la salida. La primera utiliza funciones miembro de la clase ios. La segunda utiliza un tipo especial de función denominada manipulador. Vamos a empezar examinando los formatos que utilizan funciones miembro de ios, y después hablaremos de los manipuladores.

Formato utilizando funciones miembro de ios

La enumeración que puede verse a continuación está definida en IOSTREAM.H:

  • // indicadores de formato
    enum
  • {

  • skipws = OxO001,
    left = Ox0002,
    right = Ox0004,
    internal = Ox0008,
    dec = OxO010,
    oct = Ox0020,
    hex = Ox0040,
    showbase = Ox0080,
    showpoint = OxO100,
    uppercase = Ox0200,
    showpos = Ox0400,
    scientific = Ox0800,
    fixed = OxlO00,
    unitbuf = Ox2000,
    stdio = Ox4000
};

Los valores que se definen en esta enumeración se utilizan para activar o desactivar los indicadores que controlan algunos de los aspectos en que se da formato a la información en un stream.
Cuando el indicador skipws está activado, los caracteres de espacio en blanco (los espacios, tabuladores y nueva línea) se descartan cuando se hace una entrada procedente de un stream. Cuando skipws se pone a cero, los caracteres de espacio en blanco no se descartan.
Cuando el indicador left están activado, la salida se ajusta a la izquierda. Cuando está activado right, la salida se ajusta a la derecha. Cuando está activado el indicador internal, entonces los valores numéricos se rellenan para completar un campo insertando espacios entre el signo o carácter de base que haya. (La forma de especificar la amplitud de un campo se verá en breve.) Por defecto, los valores numéricos se muestran en la base en que estén representados. Sin embargo, se puede ignorar este comportamiento que se produce por defecto. Por ejemplo, para escribir algo en decimal, active el indicador dec. Si se activa oct, se da lugar a que la salida se muestre en octal. Si se activa hex, se hace que la salida se muestre en hexadecimal. Si se activa showbase, se da lugar a que se muestre la base de los valores numéricos.
Por defecto, cuando se muestra la notación científica, la "e" va en minúsculas Además, cuando se muestra un valor hexadecimal, la "x" va en minúsculas. Cuan do uppercase está activado, se muestran en mayúsculas estos caracteres. Al activar showpos se consigue que se muestre un signo más a la izquierda delante de los valores enteros positivos. Si se activa showpoint, se da lugar a que se muestre un punto decimal y una cola de ceros para toda la salida de coma flotante, sea o no necesario.
Al activar el indicador scientific, los valores de coma flotante se muestran utilizando notación científica. Cuando se activa fixed, los valores de coma flotante se muestran utilizando la notación normal, y por defecto se muestran seis decimales. Cuando no está activado ninguno de estos indicadores, el compilador selecciona un método adecuado.
Por razones que van más allá del alcance de estos apunten, el rendimiento del sistema de E/S de C++ mejora cuando unitbuf está activado. Este indicador está activado por defecto en Turbo C++.
Cuando stdio está activado, todos los streams se vuelcan cada vez que hay una salida. Volcar un stream hace que se escriba la salida en el dispositivo físico que este asociado al stream. Los indicadores de formato están contenidos en un entero long. Para activar un indicador, se utiliza la función setf(), cuya forma más común es la que se muestra a continuación:

long setf(long indicadores),

Esta función proporciona los valores anteriores de los indicadores de formato y activa los indicadores que se especifiquen en indicadores. Por ejemplo, para activar el indicador showbase, se puede utilizar esta sentencia:

  • stream.setf(ios::showbase);

Aquí, stream es el stream que deba ser afectado. Por ejemplo, este programa activa tanto el indicador showpos como el scientific.

 

  • #include <iostream.h>
    main(void)
  • {

  • cout.setf(ios::showpos);
    cout.setf(ios::scientific);
    cout << 123 << " " << 123.23 << " " ;
    return 0;
  • }
 

La salida que produce este programa es la que se muestra a continuación:

  • +123 +1.2323e+02

Utilización de la biblioteca de clases de E/S de C++ 453

Se puede hacer un OR conjunto de tantos indicadores como se desee en una misma llamada. Por ejemplo, se puede modificar el programa de forma que sólo se haga una llamada a setf(), haciendo un OR de scientific y showpos, como se indica a continuación:

  • cout. setf(ios::scientific I ios::showpos);

Para desactivar un indicador, se utiliza la función unsetf(), cuyo prototipo se muestra a continuación:

long unsetf(long indicadores);

La función proporciona los valores anteriores de los indicadores y desactiva aquellos indicadores que estén especificados en indicadores. A veces es útil conocer los valores en curso de los indicadores. Se pueden obtener los valores en curso de los indicadores utilizando la función flags(), cuyo prototipo es el que se muestra a continuación:

long flags(void);

Esta función proporciona el valor en curso de los indicadores con respecto al stream asociado. Esta forma de flags() fija como valores de los indicadores aquellos que estén especificados en indicadores, y proporciona los valores anteriores de los indicadores:

  • long flags(long indicadores);

Para ver la forma en que funcionan flags() y unsetf(), examine este programa. Contiene una función llamada mostrarindicadores() que muestra el estado de los indicadores:

 

  • #include <iostream.h>
    void mostrarindicadores(long indic);
    main(void)
  • {

  • long indic;
    indic cout.flags();
    mostrarindicadores(indic);
    cout.setf(ios::showpos);
    cout.setf(ios::scientific);
    indic cout.flags();
    mostrarindicadores(indic);
    cout.unsetf(ios::scientific);
    indic = co~t.flags();
    mostrarindicadores(indic);
    return O;
  • }
 
  • void mostrarindicadores(long indic)
  • {

  • long i;
    for(i=Ox4000; i; i = i > 1)

  • if(i & indic) cout << "1 " ;
    else cout <<"0 ";
  • }
  •  
 

Cuando se ejecuta, este programa produce la siguiente salida:

  • 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1
    0 1 0 1 1 0 0 0 0 0 0 0 0 0 1
    0 1 0 0 1 0 0 0 0 0 0 0 0 0 1

Además de dar valores a los indicadores de formato, se puede definir también la amplitud del campo, el carácter que se va a utilizar como relleno, y el número de dígitos que se van a mostrar después del punto decimal, utilizando las funciones siguientes:

  • int width(int lon);
    char fill(char car);
    int precision(int num);

La función width() proporciona la amplitud actual del campo y da a la amplitud del campo el valor lon. Por defecto, la amplitud del campo varía, dependiendo del número de caracteres que se necesiten para representar los datos. La función fill() proporciona el carácter de relleno en curso, que por defecto es el espacio, y hace que el carácter de relleno en curso pase a ser car. El carácter de relleno es el carácter que se usa para rellenar la salida de modo que se complete la amplitud de campo especificada. La función precision() proporciona el número de dígitos que se muestran después del punto decimal y da a ese valor el valor num. Véase un programa que hace una demostración de estas tres funciones:

 

  • #include <iostream.h>
    main(void)
  • {

  • cout.setf(ios::showpos);
    cout.setf(ios::scientific);
    cout << 123 << " " << 123.23 << "\n";
    cout.precision(2); // dos dígitos después del punto decimal
    cout.width(10); // en un campo de diez caracteres
    cout << 123 << " " << 123.23 << "\n";
    cout.fill(`#'); // rellenar con #
    cout.width(10); // en un campo de diez caracteres
    cout << 123 << " " << 123.23;
    return 0;
  • }
 

El programa muestra la salida siguiente:

  • +123 +1.2323e+02
    +123 +1.23e+02
    ######+123 +1.23e+02

Utilización de manipuladores

El sistema de E/S de C++ contiene una segunda forma de alterar los parámetros de formato de un stream. Esta forma utiliza funciones especiales llamadas manipuladores, que se pueden incluir en una sentencia de E/S. Los manipuladores estandard se muestran en la Tabla 2~1. Para acceder a estos manipuladores, es preciso incluir IOMANIP.H en el programa. Todos los manipuladores tienen como argumento el stream al cual estén afectando, y proporciona ese mismo stream. De esta manera, se puede utilizar un manipulador como parte de una expresión de E/S. Véase un ejemplo de programa que utiliza manipuladores para cambiar el formato de la salida:

 

  • #include <iostream.h>
    #include <iomanip.h>
    main(void)
  • {

  • cout << setprecision(2) << 1000.243 << endl;
    cout << setw(20) << "Hola, Mundo.";
    return 0;
  • }
 

El programa produce la salida siguiente:

  • 1000.24
    Hola, Mundo.

Obsérvese la forma en que aparecen los manipuladores en la cadena de operaciones de E/S. Observase también que cuando un manipulador no admite argumentos, tal como el endl() del ejemplo, no va seguido por paréntesis. La razón de esto es que lo que se pasa al operador << sobrecargado es la dirección de la función.

 

: Manipuladores del C++

Manipulador

Propósito

Entrada/Salida

dec

Da formato decimal a datos numéricos

entrada y salida

endl

Saca un carácter nueva línea y vuelca el stream

Salida

ends

Saca un carácter nulo

Salida

flush

Vuelca un stream

Salida

hex

Da formato hexadecimal a datos numéricos

entrada y salida

oct

Da formato octal a datos numéricos

entrada y salida

reseteiosflags(long f)

Desactiva los indicadores dados en f

entrada y salida

setbase(int b)

Fija como base el valor b

entrada y salida

setfill(int ch)

Fija como carácter de relleno el valor ch

entrada y salida

seteiosflags(long f)

Activa los indicadores que se especifican en f

entrada y salida

setprecision(int p)

Determina las cifras que van tras el punto decimal.

entrada y salida

setw(int w)

Da a la amplitud del campo el valor w

entrada y salida

ws

ignora los espacios iniciales

entrada

Este programa utiliza setiosflags() a fin de activar los indicadores scientific y showpos:

 

  • #include <iostream.h>
    #include <iomanip.h>
    main(void)
  • {

  • cout << setiosflags(ios::showpos);
    cout << setiosflags(ios::scientific);
    cout << 123 << " " << 123.23;
    return 0;
  • }
 

Este programa utiliza ws para ignorar los caracteres iniciales de espacio en blanco que pueda haber cuando se introduce una cadena en s:

 

  • #include <iostream.h>
    main(void)
  • {

  • char s[80];
    cin >> ws >>s;
    cout << s;
  • }
 

Creación de funciones manipuladoras propias

Es posible crear funciones manipuladoras propias. Las más fáciles de crear son aquellas que no admiten argumentos, y éste es el tipo de manipuladores que vamos a aprender a crear aquí. (La creación de manipuladores parametrizados va más allá del alcance de estos apuntes.) Todas las funciones de manipulación de salida que no tienen argumentos tienen el esqueleto siguiente:

  • ostream &nombre_manipulador(ostream &stream)
  • {

  • // aquí viene nuestro código
    return stream;
  • }
 

Aquí, nombre_manipulador es el nombre del manipulador. Es importante entender que aunque el manipulador tiene como argumento único un puntero del stream sobre el cual actúa, no se utiliza ningún argumento cuando se inserta el manipulador en una operación de salida.
Este programa crea un manipulador llamado preparar() que activa el ajuste a la izquierda, da a la amplitud del campo el valor 10 y especifica que el carácter de relleno va a ser el signo de dólar:

 

  • #include <iostream.h>
    #include <iomanip.h>
    ostream &preparar(ostream &stream)
  • {

  • stream.setf(ios::left);
    stream << setw(10) << setfill(`$');
    return stream;
  • }
 
  • main(void)
  • {

  • cout << 10 << " " << preparar << 10;
    return 0;
  • }
 

Los manipuladores a la medida son útiles por dos razones. En primer lugar, quizá se necesite llevar a cabo una operación de E/S en un dispositivo para el cual no son aplicables ninguno de los manipuladores predefinidos; por ejemplo, un trazador. En este caso, crear manipuladores propios hará cómodo llevar una salida al dispositivo. En segundo lugar, quizá se encuentre con que está repitiendo la misma secuencia de operaciones muchas veces. Se pueden consolidar todas estas operaciones en un solo manipulador, como mostraba el programa anterior.
Todos los manipuladores de entrada que no admiten argumentos tienen el esqueleto que se muestra a continuación:

  • istream &nombre-manipulador(istream &stream)
  • {

  • // aquí va nuestro código
    return stream;
  • }
 

Por ejemplo, este programa crea el manipulador pedir(), que convierte en hexadecimal su entrada, y que pide al usuario que escriba un valor en hexadecimal:

 

  • #include <iostream.h>
    #include <iomanip.h>
    stream &pedir(istream &stream)
  • {

  • cout << "Escriba un número con formato hexadecimal: ";
    return stream;
  • }
 
  • main(void)
  • {

  • int i;
    return 0;
  • }