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.2a3  Controles de formato

§1  Introducción

Las cuestiones relativas al formato en las rutinas E/S de la Librería Estándar, están controladas por dos mecanismos, el primero de los cuales puede considerarse principal:

  • Controles generales.  Entre otros, pueden ajustarse aspectos como:

    • El ancho de un campo de salida y ajustar la salida dentro de este ancho.
    • El carácter que se utilizará para los ajustes anteriores
    • La precisión y el formato para los números fraccionarios, e indicar si el punto decimal debe ser incluido
    • Indicar si se indicará explícitamente el signo en todos los casos de representación numérica.
    • Si se desea saltar los espacios en blanco cuando se lee (extrae) desde un flujo de entrada.
    • El formato para los números enteros (decimal, octal o hexadecimal).
  • Particularizaciones del formato para adaptar el de ciertos campos a determinadas convenciones culturales. Esta parte se realiza mediante el locale imbuido en el flujo. Por ejemplo, signo de los separadores de miles y decimales, formato de fechas, Etc.


En este capítulos nos ceñiremos a los primeros, controles generales, de los que a excepción de dos, precisión y ancho de campo, el resto de aspectos del formato, están controlados por el estado de los bits de la propiedad fmtflags de ios_base [1], cuyo significado individual ya ha sido comentado ( 5.3.2a).  En el presente capítulo se comentan brevemente una serie de métodos auxiliares que permiten actuar sobre el contenido de la máscara de formato.

§2  flags()

Dos métodos para controlar el valor de fmtflags:

fmtflags flags() const;

Devuelve el valor de la máscara de formato utilizada en entradas y salidas.

fmtflags flags(fmtflags fmtfl);

Guarda la máscara anterior y establece una nueva según el valor pasado en el argumento. A continuación devuelve la anterior.

Ejemplo:

#include<iostream>

#include<fstream>

 

using namespace std;

void whatFormat (ifstream::fmtflags); // funcion auxiliar

 

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

   ifstream stream("myFile.txt");   // un fichero de texto

   ifstream::fmtflags formato = stream.flags();

   whatFormat(formato);

   return EXIT_SUCCESS;

}

 

void whatFormat (ios_base::fmtflags format) {

   if (format & ios::boolalpha) cout << "boolalfa; ";

   if (format & ios::dec) cout << "dec; ";

   if (format & ios::fixed) cout << "fixed; ";

   if (format & ios::hex) cout << "hex; ";

   if (format & ios::internal) cout << "internal; ";

   if (format & ios::left) cout << "left; ";

   if (format & ios::oct) cout << "oct; ";

   if (format & ios::right) cout << "right; ";

   if (format & ios::scientific) cout << "scientific; ";

   if (format & ios::showbase) cout << "showbase; ";

   if (format & ios::showpoint) cout << "showpoint; ";

   if (format & ios::showpos) cout << "showpos; ";

   if (format & ios::skipws) cout << "skipws; ";

   if (format & ios::unitbuf) cout << "unitbuf; ";

   if (format & ios::uppercase) cout << "uppercase; ";

   if (format & ios::adjustfield) cout << "adjustfield; ";

   if (format & ios::basefield) cout << "basefield; ";

   if (format & ios::floatfield) cout << "floatfield; ";

}

Salida:

dec; skipws; basefield;

Comentario:

El programa incluye las directivas #include<iostream> e #include<istream>. Aunque probablemente el compilador lo transformará en un entero sin signo, la forma correcta de definir el tipo del argumento de whatFormat, es el indicado.

§3  setf()

Existen tres métodos adicionales para control de la máscara de formato:

   fmtflags setf(fmtflags fmtfl);

Establece el valor de la máscara de acuerdo con el valor pasado como argumento. A continuación devuelve el valor de la anterior. Como puede verse, el comportamiento es idéntico al de flags() con argumento. Ejemplo:

std::ios::fmtflags format = std::cout.setf(std::ios::unitbuf);


   fmtflags setf(fmtflags fmtfl, fmtflags mask);

Guarda la máscara anterior, limpia la máscara y establece un nuevo valor que es el AND lógico ( 4.9.3) de ambos argumentos ( fmtfl & mask ). Finalmente devuelve el valor guardado previamente.

Ejemplo:

std::ios::fmtflags format = std::cout.setf(std::ios::hex, std::ios::uppercase);


Las sentencias que siguen son exactamente equivalentes. Observe que la segunda y tercera realizan un modelado previo de la constante 0 utilizada como argumento ( 4.9.9).

std::cout.setf(mask);

std::cout.setf((std::ios::fmtflags)0, mask);

std::cout.setf(std::ios::fmtflags(0), mask);


  void unsetf(fmtflags mask);

Limpia la máscara de formato utilizando el patrón del argumento mask. Esto significa que, en la máscara de formato actual, se pondrán a cero todos los bits cuyo valor correspondiente en mask esté seteado (su valor sea 1).

Ejemplo:

ios_base::fmtflags formato = cout.flags();

cout.clear(formato);

cout << "Hola!!...";                  // L.3 Error!!

cout.flags(formato);                  // L.5

cout.clear(); cout << "Hola!!";      // Ok.

En la segunda línea se "limpian" todos los bits de la máscara de formato actual (por defecto) del flujo estándar de salida cout.( 5.3) y en la siguiente se pretende realizar una salida formateada por dicho flujo. El resultado es un error (no se escribe nada en pantalla) porque no existe un criterio a adoptar en cuanto al formato.

En L.5 se restablece el valor inicial, a partir de aquí el programa muestra adecuadamente los datos. Observe que ha sido necesario insertar la sentencia L.6 para limpiar iostate ( 5.3.2a), mediante el método clear() de basic_ios ( 5.3.2.b), ya que a partir del error goodbit estaba en falso, e impedía cualquier operación de E/S por el flujo.

Nota: antes de realizar ninguna E/S importante por un flujo, es aconsejable verificar que su estado es Ok. mediante la comprobación de que ios::goodbit == true. En caso necesario el bit debe ser "limpiado".

§4  Otros controles

Además de los aspectos controlados por fmtflags ( 5.3.2a), se dispone de una serie de métodos que controlan aspectos complementarios del formateo de E/S:

§4.1  Precisión

Dos métodos para controlar el número de cifras después del punto (o coma) decimal.

streamsize precision() const;

streamsize precision(streamsize prec);

El primero devuelve el valor actual de la variable que controla el número de dígitos después del punto decimal, que se incluirá en las operaciones de salida de números fraccionarios (por defecto se adopta el valor 6). La segunda devuelve el estado actual y establece uno nuevo según el valor del argumento prec. [5]

Ejemplo:

float f1 = 3.14, f2 = 3.00;

ifstream::fmtflags formato = cout.flags();

cout.flags(formato | ios::scientific );

cout << f1 << "; " << f2 << endl;

    // -> 3.140000e+00; 3.000000e+00

cout.precision(4); cout << f1 << "; " << f2 << endl;

    // -> 3.1400e+00; 3.0000e+00

§4.2  Ancho de campo

El ancho de campo es el mínimo número de caracteres que se utilizarán para ciertas salidas (incluyendo cifras decimales si las hay). Su valor por defecto es cero, y existen dos métodos para controlarlo cuyo funcionamiento es similar al caso anterior. El primero interroga el valor actual, el segundo lo fija a un valor nuevo y devuelve el valor previo [2].

streamsize width() const;

streamsize width(streamsize wide);

En caso que el ancho actual sea menor que el establecido, se rellenan los espacios faltantes con un carácter particular (por defecto 0x20). Este valor puede ser modificado mediante el método fill ( 5.3.2b) de basic_ios.

Ejemplo:

int x = 64, y = 32;

std::cout << x;

std::cout.width(10);

std::cout << y;

std::cout << x << std::endl;

Salida:

64       3264

Comentario:

La salida pone de manifiesto una curiosa propiedad de este método: después de efectuada la salida 32 con ancho 10, el ancho vuelve a ser 0 (el valor por defecto). Es una excepción respecto al resto de modificaciones de formato, que son permanentes. Como se indica en el siguiente epígrafe, las E/S que utilizan este especificador de ancho, siguen una lógica un poco más complicada.

§4.2a  Peculiaridades

En las operaciones de E/S formateadas, existe cierta interrelación entre el ancho del campo y el tipo del objeto leído/escrito. Todas las operaciones de salidas formateadas (realizadas por insertores 5.3.3b), resetean el ancho al valor inicial después de la primera operación (es el caso del ejemplo anterior).

Las operaciones de entrada formateadas (realizadas por extractores) siguen un comportamiento distinto según sean entidades numéricas o cadenas de caracteres. Las entradas numéricas leen cualquier número de caracteres, hasta encontrar el primer "whitespace" ( 5.3.3b), con independencia de la anchura seleccionada en ese momento. El ancho se mantiene después de la operación.

Las entradas de entidades de cualquier otro tipo siguen el comportamiento descrito para las numéricas.

Ejemplo:

using namespace std;

 

double x;

cin.width(5); cin >> x;

      // se introduce la secuencia: 9876543210

cout.flags(std::cout.flags() | ios::fixed);    // L.4

cout.precision(0);

cout << x;

      // se obtiene la salida: 9876543210

Comentario: Las sentencias L4 y L5 solo sirven para proporcionar a la salida el aspecto apetecido.

Las entradas de cadenas de caracteres (que se almacenan en matrices) utilizan el especificador de ancho y a continuación, lo resetean al valor inicial. Ejemplo:

char m1[50];

cin.width(5);

cin >> m1;

   // se introduce 9876543210

cout << m1;

  // se obtiene: 9876

Comentario:

El extractor respeta el ancho establecido al estilo clásico, esto es, introduce en m1 una cadena de cinco caracteres terminada en nulo, razón por la cual, la salida solo muestra los 4 primeros.

Ejemplo:

char m1[50], m2[50];

cin.width(5);

cin >> m1 >> m2;

    // Entrada: 9876543210 ASDFGHJKL

cout << m1 << endl;

    // Salida: 9876

cout << m2 << endl;

    // Salida: 543210

Comentario:

La primera entrada responde a mecánica expuesta en el ejemplo anterior. El primer extractor es responsable de la extracción de los 4 primeros caracteres de la secuencia. A continuación se procede con el segundo ( >> m2 ), que procede con la extracción de los restantes. Aquí no tiene ya efecto la limitación width(5), pero el proceso termina porque se encuentra un "whitespace".

La extracción de cadenas de caracteres en tipos como char c[50]; o char* pc = "_____"; son las que podríamos llamar de "estilo clásico" (C). Tienen el inconveniente de que es necesario conocer de antemano el tamaño máximo de la cadena a extraer y reservar el espacio correspondiente. Es el caso del ejemplo anterior, en el que hemos previsto un tamaño máximo de 50 caracteres para las matrices m1 y m2, esta técnica entraña un riesgo potencial; que la secuencia contenga una cadena de longitud superior al previsto, en cuyo caso se machacarían la zona de memoria contiguas a la matriz [9]. Para evitarlo es conveniente limitar el ancho de lectura antes de cada entrada, adecuándolo al espacio de almacenamiento previsto:

char m1[size];

cin.width(size);

cin >> m1;    // Ok. operación segura

Si la operación es encadenada (como en el último ejemplo), entonces es necesario utilizar manipuladores ( 5.3.3c):

char m1[50], m2[50];

cin >> setw(50) >> m1 >> setw(50) >> m2;    // Ok. operación segura

Una solución alternativa es utilizar un objeto string para almacenar la entrada [7]. Los objetos de este tipo tienen posibilidad de crecer, de forma dinámica, para ajustarse al contenido. Sin embargo, no estaría de más instalar un cierto control por si la longitud de la cadena supera un valor considerado de seguridad.

string s1;

cin >> s1;    // Ok. operación segura

if (s.length() > limit) {  // aún más segura

  ...

}

  Inicio.


[1]  Son los aspectos que dependen de variables booleanas, del tipo cierto/falso, si/no. Etc.

[2]  En el caso de E/S formateadas, también puede utilizarse setw(), un manipulador estándar ( 5.3.3c1), para establecer el ancho del campo.

[5]  En los flujos formateados también puede utilizarse setprecision(n), un manipulador estándar ( 5.3.3c1), para establecer el número de cifras decimales del campo.

[7]  Un string es un contenedor, definido en la STL, para contener cadenas de caracteres. La clase dispone de versiones sobrecargadas de la mayoría de operadores, incluyendo los de inserción (<<) y extracción (>>), lo que permite una manipulación muy cómoda de cadenas de caracteres.

[9]  Son los clásicos problemas de desbordamiento de buffer.