Professional Documents
Culture Documents
Introducción
Todos los programadores que llegan al C++ desde C están acostumbrados a utilizar las instrucciones de
entrada/salida definidas en el archivo cabecera stdio.h (printf, fprintf, fputs, puts, fwrite...). Esta biblioteca
también está presente en C++, pero no parece ser la adecuada en un lenguaje como C++ en el que los nuevos
tipos tienen un comportamiento completamente equivalente al de los tipos de datos estándar.
El mecanismo de clases de C++ permite crear un sistema consistente y ampliable para los mecanismos de
entrada/salida. Este sistema se conoce como biblioteca de flujos1. Estas clases sirven como mecanismo para
manejar los tipos básicos, pero, además, se puede modificar ampliándolas para incorporar los tipos de datos
definidos por el usuario, como ya se vio en capítulos anteriores cuando se trató la sobrecarga de los flujos de
entrada/salida en C++.
La eficiencia de las funciones de la biblioteca de C++ es otro punto a su favor, sobretodo si se las compara con
las de la biblioteca C:
El tipo del objeto es conocido en C++ de forma estática por el compilador, en lugar de tener que
comprobar de forma dinámica los campos % como sucedía en C.
Con C++ se tiene más rapidez. Por ejemplo printf utilizada en C, es básicamente un intérprete de un
pequeño lenguaje constituido principalmente por campos %.
1
Flujo es la traducción de stream.
C++ ofrece un mecanismo extensible para los nuevos tipos de datos definidos por el usuario. En
lenguaje C sería caótico si todo el mundo se pusiera a añadir de forma simultánea nuevos campos %
incompatibles. En C++ los tipos definidos por el usuario (clases) se parecen y actúan como los tipos
básicos.
El sistema de entrada/salida de C++ es un sistema de clases. Esto significa que un usuario puede definir
“algo nuevo” que parezca y se comporte como streams.
Las sentencias de entrada/salida en C++ tienen mayor legibilidad que las de C.
Streams
El concepto de stream o flujo no es nuevo en C++. ANSI C utiliza este concepto para indicar un tipo de puerto
abstracto a través del cual datos no estructurados pueden caminar de forma unidireccional o bidireccional2. Los
flujos en ANSI C se consideran una construcción de entrada/salida de bajo nivel.
Por su parte C++ fue diseñado para el manejo de clases, así los flujos de C++ se encuentran definidos mediante
una jerarquía de clases completa que se distribuye con el compilador. Las versiones antiguas3 de C++ se
distribuían con una jerarquía de clases de streams que actualmente es obsoleta. Las versiones C++ de AT&T
desde la versión 2.0 utilizan la nueva biblioteca iostream.
C++ da a los streams un significado más abstracto. Un programa C++ visualiza entrada o salida como un flujo
de datos. En la entrada un programa extrae bytes de un flujo de entrada, y en la salida un programa inserta bytes
en un flujo de salida. Así, un flujo se puede considerar como una secuencia lineal de bytes con un significado.
Los bytes pueden formar una representación binaria de datos carácter o numéricos. Los bytes de entrada pueden
venir del teclado, pero también pueden proceder de otro dispositivo de almacenamiento como un disco duro u
otro programa. Y de forma análoga los bytes del flujo de salida pueden ir destinados a la pantalla, a una
impresora, a un fichero, o a otro programa. Este enfoque permite que un programa C++ trate la entrada de un
teclado de igual forma que se trata la entrada desde un fichero. El programa C++ examina simplemente el flujo
de bytes, sin necesidad de conocer cuál es la procedencia de los mismos. Esto mismo ocurre en la salida, y ésta
se podrá procesar de forma independiente del lugar a donde vayan destinados los datos.
Esto quiere decir que los flujos necesitan para funcionar dos conexiones, una en cada extremo.
2
Los streams constituyen un concepto muy familiar para todos los programadores de C bajo UNIX.
3
Hasta la versión 1.2.
Si los datos se transfieren sin modificarse entre un extremo y otro, la operación de entrada/salida se denomina
binaria o sin formato.
Si por el contrario los datos, que son una representación interna de la máquina (por ejemplo long), se modifican
en la transferencia entre ambos extremos, y son una representación de caracteres de texto (por ejemplo ASCII),
la operación de entrada/salida se denomina entrada/salida con formato.
La biblioteca iostream soporta ambas operaciones y permite que el programador controle operaciones
específicas de formato mediante el uso de lo que en C++ se conoce como manipuladores.
La biblioteca iostream emplea para las operaciones de entrada/salida buffers. Recordar que un buffer es un
bloque de memoria que se emplea como almacenamiento temporal o intermedio para la transferencia de
información de un dispositivo a un programa o viceversa. Típicamente dispositivos como unidades de disco
transfieren información en bloques de 512 bytes, 1 Kbyte, o incluso mayores.
La biblioteca iostream
La biblioteca iostream es la biblioteca de entrada/salida estándar en C++. El acceso a esta biblioteca se realiza
mediante el archivo cabecera iostream.h4, que se ha venido utilizando de forma reiterada en todos los ejemplos
que se han visto hasta el momento. El sistema de entrada/salida en C++, está definido para trabajar con
diferentes dispositivos (al igual que ocurre en ANSI C). Cada dispositivo se convierte en un dispositivo lógico y se
denomina flujo o stream. Los flujos forman el interfaz común entre el programa, el dispositivo y el usuario. A
este sistema de entrada/salida se le conoce como sistema de archivos a través de buffers. La estructura de los
archivos puede variar con los distintos dispositivos, pero los flujos se comportan siempre de la misma forma5.
Flujos predefinidos
Los siguientes flujos u objetos6 se abren de forma automática cuando se ejecuta un programa C++ y se ha
incluido el fichero de cabecera iostream.h.
4
Las versiones antiguas de la biblioteca de flujos utilizan el fichero cabecera stream.h. En aquellas versiones donde existan
ambos archivos stream.h define el conjunto completo de facilidades, mientras que iostream.h define un subconjunto que es
compatible con las bibliotecas de flujo antiguas. Las versiones modernas de C++ soportan sólo iostream.h.
5
En C y C++ los archivos son considerados como simples flujos de bytes.
6
Ya que un stream no es más que un objeto que hace de puente entre el resto de los objetos de una aplicación y el
dispositivo en el que van a ser almacenados.
Estos flujos se asocian a la consola por defecto, pero pueden ser redirigidos a otros archivos o dispositivos.
Estos cuatro flujos se abren de forma automática antes de que la función main se ejecute, y se cierran justo
después que la función main ha terminado. Por lo tanto no es preciso preocuparse de abrir o cerrar estos flujos.
cerr presenta una ventaja sobre clog, y es que los buffers de salida se limpian cada vez que se utiliza cerr, de
modo que la salida está disponible de forma inmediata en el dispositivo externo, que por defecto es la pantalla.
La lista no incluye al tipo char, ya que este tipo es sinónimo de unsigned char o de signed char, dependiendo
del compilador.
La clase ostream proporciona una definición de la función operator << para cada uno de los tipos anteriores. El
operador de inserción es un operador binario que retorna una referencia a un objeto ostream.
Un ejemplo del uso del operador inserción es:
Del ejemplo se puede deducir que el operador de inserción se puede encadenar. Como es natural el operador de
inserción no realiza un salto de línea, esto hay que hacerlo de forma explícita, bien al estilo C mediante la
secuencia de escape \n, bien al estilo C++ con el manipulador endl.
La sentencia cout que aparece en la función Ejemplo se escribiría de la siguiente forma si se quisiera emplear el
manipulador endl.
cout << "Mi nombre es: " << nombre << " y tengo "
<< edad << "años." << endl;
El manipulador endl tiene una ventaja sobre la secuencia de escape \n, ya que además de insertar una nueva
línea en el flujo, también vacía el buffer de salida. Por lo tanto es equivalente a utilizar \n seguido de fflush.
7
Introducir objetos dentro de un flujo se conoce como inserción.
8
Sacar objetos de un stream se denomina extracción.
#include <iostream.h>
Como se puede ver en el ejemplo la función miembro se puede utilizar con caracteres o con enteros, así
cout.put(77); visualizaría una M. También es posible concatenar varias llamadas como sucedía con el operador
<<, e incluso combinar en la misma sentencia el método put con el operador inserción.
Por su parte el método write se utiliza para insertar una secuencia de caracteres en el flujo. Tiene dos prototipos:
ostream & write (const signed char *, int);
ostream & write (const unsigned char *, int);
Ejemplo:
#include <iostream.h>
#include <string.h>
// Ejemplo de concatenación
cout.write(c1, strlen(c1)).put(32).
write(c2, strlen(c2)).put(32).
write(c3, strlen(c3)).put('\n');
// Saca 25 caracteres
cout.write(c2, 25) << endl; // Sacará basura, pues la longitud de
// la cadena c2 es menor que 25
char t;
cin >> t;
El símbolo >> señala la dirección del flujo, y lee los caracteres hasta que encuentra uno que no es parte del tipo
requerido (habitualmente espacios, tabuladores o retorno de carro). Este operador se puede utilizar en cascada.
int a, b, c, d, e;
float n;
cin >> a >> b >> c >> d >> e >> n;
Los resultados de pasar tipos incorrectos en la entrada de datos son imprevisibles. A diferencia de scanf al
operador de extracción no se le debe especificar la dirección de la variable, así la siguiente sentencia será un
error.
Un método general de utilizar cin para introducir caracteres es colocarlo dentro de un bucle while, del cual se
saldrá cuando se alcance el fin de fichero9.
El operador de entrada >> al igual que ocurre con scanf tiene un inconveniente que lo hace inadecuado cuando
se trata de leer cadenas con espacios en blanco en su interior, ya que sólo lee una cadena hasta que encuentra la
primera ocurrencia del espacio en blanco. Siempre el carácter nulo que marca el final de las cadenas en C es
añadido al final de la cadena que se lea.
Función get
La clase istream contiene la función miembro get para la introducción de cadenas completas de caracteres,
incluyendo espacios en blanco. Esta función no realiza conversiones, simplemente acepta una cadena de
caracteres, y los sitúa en la variable adecuada. Realmente get es una familia de funciones sobrecargadas, alguno
de los prototipos son:
Así, esta función aceptará caracteres hasta que se alcance la longitud máxima o se introduzca el carácter
de terminación.
Ejemplo:
#include <iostream.h>
9
Ctrl-Z en MSDOS, y Ctrl-D en UNIX.
El siguiente es un ejemplo de la ejecución del programa. En negrita las entradas del usuario.
--123--
El programa anterior leerá cadenas de tres caracteres, o hasta que se dé un retorno de carro, o hasta que se
encuentre un fin de fichero válido. Así, en el ejemplo se introduce la cadena 1234567, y la variable cadena cad
queda cargada sólo con el valor 123.
Esta función puede llegar a ser bastante problemática si no se tiene un poco de cuidado, ya que get deja el
carácter de terminación de la entrada en el buffer, y se toma en la siguiente sentencia de entrada de datos.
Así el siguiente programa no funcionaría como debiera, ya que la segunda cadena nunca se llega a pedir, al
tomarse en esta segunda entrada de datos el carácter de terminación de la primera (el carácter intro). Por ello en
la segunda entrada de datos lo que se asume que se ha tecleado una cadena vacía.
#include <iostream.h>
El siguiente es un ejemplo de la ejecución del programa. En negrita las entradas del usuario.
Función getline
Esta función miembro de istream opera de forma análoga a la función get, siendo su prototipo:
istream & getline (char *, int, char ='\n');
Su diferencia con la función miembro get es que el carácter de terminación se lee antes de que se añada el
carácter '\0'. En consecuencia esta función elimina el carácter de terminación del buffer de entrada.
Con lo que ahora se puede solucionar el problema que se había planteado antes.
#include <iostream.h>
El siguiente es un ejemplo de la ejecución del programa. En negrita las entradas del usuario.
-abcdefghi-
-123-
Función ignore
Esta es otra de las funciones miembro de la clase iostream. Su prototipo es:
Su cometido es saltarse n caracteres del stream de entrada, o hasta que encuentre el carácter delim. Así la
sentencia.
cin.ignore (25, '\n');
Lee e ignora los siguientes 25 caracteres o hasta que se produzca el primer salto de línea.
Ejemplo:
#include <iostream.h>
const int L=25;
cin.get(c);
cout << "El siguiente carácter de entrada es: "
<< c << endl << endl;
cin.ignore(L, '\n'); // Se desprecia el resto de la línea
cin.get(c);
cout << "El siguiente carácter de entrada es: "
<< c << endl;
}
El siguiente es un ejemplo de la ejecución del programa. En negrita las entradas del usuario.
Observar que mientras que getline descarta el carácter límite % en la entrada, get no lo hace.
Función read
Esta función lee un número dado de bytes, almacenándolos en la posición especificada. Sus prototipos son:
Al contrario que get y getline, read no añade ningún carácter a la entrada, por lo que no convierte la entrada a
formato cadena.
Función putback
Esta función inserta un carácter de nuevo en la cadena de entrada. Dicho carácter se convierte en el primer
carácter leído por la siguiente sentencia de entrada. Su prototipo es:
Función peek
Esta función devuelve el siguiente carácter de la entrada sin ser extraído de la cadena de entrada. Devuelve EOF
si no hay más caracteres en el flujo. Su prototipo es:
int peek();
Ejemplo:
#include <iostream.h>
#include <process.h>
if (encontrado) {
cin.get(c);
cout << "\nEl siguiente carácter de la entrada es " << c << endl;
}
else {
cout << "Se ha llegado al final de la entrada." << endl;
exit(0);
}
if (encontrado) {
cin.get(c);
cout << "\nEl siguiente carácter de la entrada es " << c << endl;
}
else {
cout << "Se ha llegado al final de la entrada." << endl;
exit(0);
}
cin.get(c);
cout << "\nEl siguiente carácter de la entrada es " << c << endl;
}
El siguiente es un ejemplo de la ejecución del programa. En negrita las entradas del usuario.
Clase ios
Dentro de esta clase se han definido los métodos para realizar una amplia gama de operaciones de formato con la
salida. Así se tienen funciones para el control de la base, la anchura del campo, carácter de relleno, etc.
int width();
int width(int i);
El primero de ellos se limita a informar del estado actual de la anchura del campo, mientras que el segundo
establece la anchura del campo a i caracteres, y devuelve el valor de la anchura anterior.
Como width es un método tiene que utilizar un objeto, como pueden ser cout y cin.
Cuando se emplea con cin tiene el sentido de limitar el número de caracteres que se van a leer.
Por su parte cuando se utiliza con cout, si el contenido de la variable es menor que la anchura fijada del campo,
se visualizará el contenido de dicha variable justificado a la derecha de un campo de longitud la que se haya
fijado, y el resto del campo se rellenará con espacios. Si por el contrario la longitud del contenido de la variable
es mayor que la longitud del campo, se ignora la configuración de width y se muestra el valor completo de la
variable. Por tanto, nunca se pierde información.
El valor por defecto para la anchura es 0. Esto no significa que no se tenga reservado espacio para la salida de la
variable, sino que se utilice el mínimo número de caracteres necesarios.
Ejemplo:
La salida del programa anterior es, después de haber sido compilado con Microsoft Visual C++ .NET:
Precisión: precision
Para establecer el número de dígitos de coma flotante después del punto decimal se utiliza la función miembro
precision. Sus prototipos son:
int precision ()
int precision (int)
El primer prototipo se corresponde con una función miembro que retorna el valor de la variable de estado de
precisión. El segundo prototipo representa a la función que fija la precisión a un valor determinado. y devuelve
el estado anterior. El valor por defecto es 0, y significa que los números en coma flotante se representan hasta
con 6 dígitos decimales, y si el número tiene más decimales el último dígito se redondea.
Ejemplo:
#include <iostream.h>
Relleno: fill
Las partes no utilizadas del campo de salida son rellenadas por el objeto cout con espacios en blanco. Para
cambiar esta situación por defecto se puede usar la función miembro fill pudiendo fijar un carácter de relleno.
Esta función tiene dos prototipos:
Como en los casos anteriores, el primer prototipo devuelve el valor de la variable de estado de relleno actual, y
el segundo lo cambia y devuelve el anterior.
Como ejemplo se va a modificar el programa anterior, de forma que cuando se muestre el valor de la precisión
se haga siempre con dos dígitos, y de ser un número de un dígito, se rellene con ceros.
Indicadores de formato
Cada objeto derivado de ios contiene una variable de estado de indicadores. Estos indicadores o banderas
controlan el formato de entrada y salida. Asociada a cada indicador existe una constante, nombre que representa
una máscara de bits, que se emplea cuando se accede y cambia el indicador de una variable de estado específica.
ios::internal 0x0008 Rellenar números con espacios después de los indicadores de base
(8 - bit 4)
Los anteriores indicadores se manejan por medio de las funciones siguientes: setf, unsetf, y flags.
long ios::setf(long bis, long flags) Informa de todos, establece un grupo (pone a 0 los
primeros, después pone a 1 los segundos)
En la clase ios existen tres constantes que se utilizan como segundo parámetros de setf y sirven para seleccionar
los grupos de indicadores.
Ejemplo 1:
#include <iostream.h>
+12.000000
+3
-3.000000
+12
3
Ejemplo 2:
#include <iostream.h>
Ejemplo 3:
#include <iostream.h>
cout << "i= " << i << "\tnum= " << num << endl;
Manipuladores
Hasta ahora se han visto varias formas de manejo del formato, bastante potentes, pero un poco engorrosas de
emplear. C++ ofrece un enfoque más amigable con lo que se denominan manipuladores.
Los manipuladores son funciones especiales que se pueden emplear con los operadores de inserción y extracción
para formatear la entrada y la salida.
Los manipuladores dec, hex, oct, ws, endl, ends, y flush se encuentran definidos en iostream.h, el resto lo
están en iomanip.h.
Ejemplo:
#include <iostream.h>
#include <iomanip>
using namespace std;
Esto va a la derecha
Esto está en la izquierda
Ahora unos numeritos:
a 12 10
10###20###16
Ejercicios resueltos
1. Crear un pequeño programa en el cual dependiendo de una variable de control, los mensajes salgan por la
salida estándar (cout), o por la salida de error (cerr), pero empleando para ello la misma sentencia.
#include <iostream.h>
if (bandera)
s=&cout;
else
s=&cerr;