Estos problemas dejan claro que la E/S es una de las principales prioridades para la librería de clases estándar de C++. Como 'hello, worlod' es el primer programa que cualquiera escribe en un nuevo lenguaje, y porque la E/S es parte de virtualmente cualquier programa, la librería de E/S en C++ debe ser particularmente fácil de usar. Tembién tiene el reto mucho mayor de acomodar cualquier nueva clase. Por tanto, estas restricciones requieren que esta librería de clases fundamentales tengan un diseño realmente inspirado. Además de ganar en abstracción y claridad en su trabajo con las E/S y el formateo, en este capítulo verá lo potente que puede llegar a ser esta librería de C++.
Un stream
es un objeto que transporta y
formatea carácteres de un ancho fijo. Puede tener un
stream
de entrada (por medio de los descendientes
de la clase istream
), o un stream
de salida (con objetos derivados de
ostream
), o un stream
que
hace las dos cosas simultáneamente (con objetos derivados de
iostream
). La librería
iostream
provee tipos diferentes de estas clases:
ifstream
, ofstream
y
fstream
para ficheros, y
istringstream
,
ostringstream
, y
stringstream
para comunicarese con la clase
string
del estándar C++. Todas estas clases
stream
tiene prácticamente la misma interfaz, por
lo que usted puede usar streams de manera uniforme, aunque esté trabajando
con un fichero, la E/S estándar, una región de la memoria, o un objeto
string
. La única interfaz que aprenderá también
funciona para extensiones añadidas para soportar nuevas clases. Algunas
funciones implementan sus comandos de formateo, y algunas funciones leen y
escriben caracteres sin formatear.
Las clases stream
mencionadas antes son
actualmente especializaciones de plantillas, muchas como la clase estándar
string
son especializaciones de la plantilla
basic_string
. Las clases básicas en la jerarquia de
herencias son mostradas en la siguiente figura:
[11]
La clase ios_base
declara todo aquello que
es común a todos los stream
, independientemente del
tipo de carácteres que maneja el stream
. Estas
declaraciones son principalmente constantes y funciones para manejarlas,
algunas de ella las verá a durante este capítulo. El resto de clases son
plantillas que tienen un tipo de caracter subyacente como parámetro. La
clase istream
, por ejemplo, está definida a
continuación:
typedef basic_istream<char< istream;
Todas las clases mencionadas antes estan definidas de manera
similar. También hay definiciones de tipo para todas las clases de
stream
usando wchar_t
(la anchura de
este tipo de carácteres se discute en el Capítulo 3) en lugar de
char
. Miraremos esto al final de este capítulo. La plantilla
basic_ios
define funciones comunes para la entrada
y la salida, pero depende del tipo de carácter subyacente (no vamos a
usarlo mucho). La plantilla basic_istream
define
funciones genéricas para la entrada y basic_ostream
hace lo mismo para la salida. Las clases para ficheros y streams de
strings
introducidas después añaden funcionalidad
para sus tipos especificos de stream
.
En la librería de iostream
, se han
sobrecargado dos operadores para simplificar el uso de
iostreams
. El operador << se denomina
frecuentemente instertador para iostreams
, y el
operador >> se denomina frecuentemente extractor.
Los extractores analizan la información esperada por su objeto
destino de acuerdo con su tipo. Para ver un ejemplo de esto, puede usar el
objeto cin
, que es el equivalente de
iostream
de stdin
en C, esto es,
entrada estándar redireccionable. Este objeto viene predefinido cuando
usted incluye la cabecera <iostream>.
int i; cin >> i; float f; cin >> f; char c; cin >> c; char buf[100]; cin >> buf;
Existe un operador sobrecargado >> para cada tipo fundamental de dato. Usted también puede sobrecargar los suyos, como verá más adelante.
Para recuperar el contenido de las variables, puede usar el
objeto cout
(correspondiente con la salida
estándar; también existe un objeto cerr
correspondiente con la salida de error estándar) con el insertador
<<:
cout << "i = "; cout << i; cout << "\n"; cout << "f = "; cout << f; cout << "\n"; cout << "c = "; cout << c; cout << "\n"; cout << "buf = "; cout << buf; cout << "\n";
Esto es tedioso y no parece ser un gran avance sobre
printf()
, aparte de la mejora en la comprobación de tipos.
Afortunadamente, los insertadores y extractores sobrecargados están
diseñados para ser encadenados dentro de expresiones más complejas que son
mucho más fáciles de escribir (y leer):
cout << "i = " << i << endl; cout << "f = " << f << endl; cout << "c = " << c << endl; cout << "buf = " << buf << endl;
Definir insertadores y extractores para sus propias clases es simplemente una cuestion de sobrecargar los operadores asociados para hacer el trabajo correcto, de la siguente manera:
Hacer del primer parámetro una referencia no constante al
stream
(istream
para la
entrada, ostream
para la salida).
Realizar la operación de insertar/extraer datos hacia/desde
el stream
(procesando los componentes del
objeto).
Retornar una referencia al stream
El stream
no debe ser constante porque
el procesado de los datos del stream
cambian el
estado del stream
. Retornando el
stream
, usted permite el encadenado de operaciones
en una sentencia individual, como se mostró antes.
Como ejemplo, considere como representar la salida de un
objeto Date
en formato MM-DD-AAAA . El siguiente
insertador hace este trabajo:
ostream& operator<<(ostream& os, const Date& d) { char fillc = os.fill('0'); os << setw(2) << d.getMonth() << '-' << setw(2) << d.getDay() << '-' << setw(4) << setfill(fillc) << d.getYear(); return os; }
Esta función no puede ser miembro de la clase
Date
por que el operando de la izquierda <<
debe ser el stream
de salida. La función miembro
fill()
de ostream
cambia el
carácter de relleno usado cuando la anchura del campo de salida,
determinada por el manipulador setw()
, es mayor que
el necesitado por los datos. Usamos un caracter '0' ya que los meses
anteriores a Octubre mostrarán un cero en primer lugar, como '09' para
Septiembre. La funcion fill()
también retorna el
caracter de relleno anterior (que por defecto es un espacio en blanco)
para que podamos recuperarlo después con el manipulador
setfill()
. Discutiremos los manipuladores en
profundidad más adelante en este capítulo.
Los extractores requieren algo más cuidado porque las cosas
pueden ir mal con los datos de entrada. La manera de avisar sobre errores
en el stream
es activar el bit de error del
stream
, como se muestra a continuación:
istream& operator>>(istream& is, Date& d) { is >> d.month; char dash; is >> dash; if(dash != '-') is.setstate(ios::failbit); is >> d.day; is >> dash; if(dash != '-') is.setstate(ios::failbit); is >> d.year; return is; }
Cuando se activa el bit de error en un
stream
, todas las operaciones posteriores serán
ignoradas hasta que el stream
sea devuelto a un
estado correcto (explicado brevemente). Esto es porque el código de arriba
continua extrayendo incluso is ios::failbit
está activado.
Esta implementación es poco estricta ya que permite espacios en blanco
entre los numeros y guiones en la cadena de la fecha (por que el operador
>> ignora los espacios en blanco por defecto cuado lee tipos
fundamentales). La cadena de fecha a continuación es válida para este
extractor:
"08-10-2003" "8-10-2003" "08 - 10 - 2003"
Pero estas no:
"A-10-2003" // No alpha characters allowed "08%10/2003" // Only dashes allowed as a delimiter
Discutiremos los estados de los stream
en mayor profundidad en la sección 'Manejar errores de
stream
' después en este capítulo.