Page 80
un bloque como aceptor de errores. La idea es que probamos a ejecutar un bloque y si se
producen errores los recogemos. En el resto de bloques del programa no se podrán recoger
errores.
Lanzamiento de excepciones: throw
Si dentro de una funció n detectamos un error lanzamos una excepció n poniendo la palabra
throw
y un parámetro de un tipo determinado, es como si ejecutáramos un return de un objeto (una
cadena, un entero o una clase definida por nosotros).
Por ejemplo:
f() {
...
int *i;
if ((i= new int) == NULL)
throw "Error al reservar la memoria para i"; // no hacen falta paréntesis,
// es como en return
...
}
si la funció n f() fue invocada desde g() y esta a su vez desde h(), el error se irá pasando entre
ellas hasta que se recoja.
Recogida: catch
Para recoger un error empleamos la pseudofunció n catch, esta instrucció n se pone como si
fuera una funció n, con catch y un parámetro de un tipo determinado entre paré ntesis, despué s
abrimos llave, escribimos el có digo de gestió n del error y cerramos la llave.
Por ejemplo si la funció n h() trataba el error anterior:
h() {
...
catch (char *ce) {
cout << "He recibido un error que dice : " << ce;
}
...
}
Podemos poner varios bloques catch seguidos, cada uno recogerá un error de un tipo distinto.
El orden de los bloques es el orden en el que se recogen las excepciones:
h() {
...
catch (char *ce) {
... // tratamos errores que lanzan cadenas
}
catch (int ee) {
... // tratamos errores que lanzan enteros
}
...
}
Si queremos que un catch trate más de un tipo de errores, podemos poner tres puntos
(parámetros indefinidos):
h() {
...
catch (char *ce) {
... // tratamos errores que lanzan cadenas
}
catch (...) {
... // tratamos el resto de errores
}
...
}
74

Page 81
El bloque de prueba: try
El tratamiento de errores visto hasta ahora es muy limitado, ya que no tenemos forma de
especificar donde se pueden producir errores (en que bloques del programa). La forma de
especificar donde se pueden producir errores que queremos recoger es emplear bloques try, que
son bloques delimitados poniendo la palabra try y luego poniendo entre llaves el có digo que
queremos probar. Despué s del bloque try se ponen los bloques catch para tratar los errores que
se hayan podido producir:
h() {
...
g(); // si produce un error, se le pasa al que llamo a h()
try {
g(); // si produce un error lo tratamos nosotros
}
catch (int i){
...
}
catch (...){
...
}
z();
}
En realidad só lo podemos recoger errores despué s de un bloque try, por lo que los catch
siempre van asociados a los try. Si una funció n que no está dentro de un bloque de prueba
recibe un error la pasa a su nivel superior hasta que llegue a una llamada producida dentro de un
bloque de prueba que trate el error o salga del programa principal.
Si en un bloque try se produce un error que no es tratado por sus catch, tambié n pasamos el
error hacia arriba.
Cuando se recoge un error con un catch no se retorna al sitio que lo origino, sino que se sigue
con el có digo que hay despué s del ú ltimo catch asociado al try donde se acepto el error. En el
ejemplo se ejecutaría la funció n z().
La lista throw
Podemos especificar los tipos de excepciones que puede lanzar una funció n, poniendo despué s
del prototipo de la funció n la lista throw , que no es más que la palabra throw seguida de una lista
de tipos separada por comas y entre paré ntesis:
void f () throw (char*, int); // f sólo lanza cadenas y enteros
Si una funció n lanza una excepció n que no este en su lista de tipos se produce un error de
ejecució n. Si ponemos una lista vacía la funció n no puede lanzar excepciones.
Funciones terminate() y unexpected()
Existen situaciones en las que un programa debe terminar abruptamente por que el manejo de
excepciones no puede encontrar un manejador para una excepció n lanzada, cuando la pila está
corrompida (y no podemos ejecutar los mecanismos de excepció n) o cuando un destructor
llamado por una excepció n provoca otra excepció n.
En estos casos el programa llama automáticamente a una funció n llamada terminate() que no
retorna nada y no tiene parámetros. Esta funció n llama a otra que podemos especificar nosotros
mediante la llamada a una funció n denominada set_terminate(). Esta funció n acepta como
parámetro punteros a funciones del tipo:
f () { // función sin parámetros que no retorna nada (ni void)
...
}
set_terminate (&f); // f() es la función que llamará terminate.
75

Page 82
La funció n por defecto de terminate() es abort() que termina la ejecució n sin hacer nada.
La funció n unexpected() se llama cuando una funció n lanza una excepció n que no está en su
lista throw, y hace lo mismo que terminate(), es decir, llama a una funció n. Podemos
especificar a cual usando la funció n set_unexpected() que acepta punteros al mismo tipo de
funciones que set_terminate(). La funció n por defecto de unexpected() es terminate().
ENTRADA Y SALIDA
Introducció n
Casi todos los lenguajes de alto nivel disponen de bibliotecas estándar de funciones para
gestionar la Entrada/Salida de datos, tanto para teclado/pantalla como ficheros. El C++ no
emplea esta estrategia, es decir, no define una biblioteca de funciones, sino que define una
biblioteca de clases que se puede expandir y mejorar si la aplicació n lo requiere.
La idea es que las operaciones de entrada y salida se aplican a objetos de unas clases
determinadas, empleando la sobrecarga de operadores como mé todo para indicar la forma de
introducir y extraer datos hacia o desde la E/S a nuestro programa.
La forma de trabajar con la E/S hace que sea posible el chequeo de tipos de entrada y de salida,
que tengamos una forma uniforme de leer y escribir variables de todos los tipos (incluso clases)
e incluso que podamos tratar de forma similar la entrada salida para distintos dispositivos.
El concepto fundamental en C++ para tratar la entrada/salida es la noció n de stream que se
puede traducir como flujo o corriente. La idea es que existe un flujo de datos entre nuestro
programa y el exterior, y los streams son los encargados de transportar la informació n, serán
como un canal por el que mandamos y recibimos informació n.
El C++ define streams para gestionar la E/S de teclado y pantalla (entrada y salida estándar), la
E/S de ficheros e incluso la gestió n de E/S de cadenas de caracteres.
Primero estudiaremos la E/S entre nosotros y la máquina (teclado y pantalla) y luego veremos la
gestió n de ficheros y cadenas. Hablaremos primero de la entrada y la salida simples y luego
comentaremos las posibilidades de formateo y el uso de los manipuladores de E/S.
Objetos Stream
El C++ define (al incluir la cabecera <iostream.h>) una serie de clases stream, las más
importantes son istream, ostream y iostream, que definen streams de entrada, de salida y de
entrada/salida respectivamente. Además de definir las clases, en esta cabecera se declaran una
serie de objetos estándar que serán los que utilizaremos con más frecuencia:
cin Objeto que recibe la entrada por teclado, pertenece a la clase istream
cout Objeto que genera la salida por pantalla, pertenece a ostream
cerr Objeto para salida de errores, es un ostream que inicialmente saca sus
mensajes por pantalla, aunque se puede redirigir.
clog Es igual que cerr, pero gestiona los buffers de forma diferente
Entrada y salida
En este punto describiremos la clase ios, que es la clase empleada para definir objetos de tipo
stream para manejar la E/S. Primero veremos la descripció n de la clase y luego veremos que
cosas podemos utilizar para conocer el estado del stream, las posibilidades de formateo de E/S y
una lista de funciones especiales de acceso a Streams.
La clase ios
class ios {
ostream* tie(ostream* s); // Liga dos streams
// podemos ligar entrada con salida
76

Page 83
ostream* tie();
int width(int w); // Pone la longitud de campo
int width() const; // Devuelve la longitud
char fill(char); // Pone carácter de relleno
char fill() const; // Devuelve carácter de relleno
long flags(long f); // Pone los flags del stream
long flags() const; // Devuelve los flags del stream
setf(long setbits, long field);
setf(long);
unsetf(long);
int precision(int); // Pone la precisión de los reales
int precision() const; // Devuelve la precisión de los reales
// Funciones de estado del stream
int rdstate() const;
int eof() const;
int fail() const;
int bad() const;
int good() const;
void clear(int i=0);
operator void *(); // Retorna NULL si failbit, badbit o hardfail están a uno
int operator !(); // Retorna verdadero si failbit, badbit o hardfail están a uno
};
Estado de la E/S.
enum io_state {
goodbit = OxO0, // Estado normal
eofbit = OxOl, // Al final del stream
failbit = Ox02, /* La última operación de E/S ha fallado. El stream se puede volver a
usar si se recupera el error. */
badbit = Ox04, /* La última operación es inválida. El stream se puede volver a usar
si se recupera el error */
hardfail = Ox08 // Error irrecuperable
};
Flags
Podemos modificarlos con las funciones setf() y unsetf().
__rendered_path__43
FLAG
ESTADO
SIGNIFICADO
__rendered_path__43
skipws
SI
Ignorar caracteres blancos en la entrada
__rendered_path__44
left
NO
Salida justificada a la izquierda
__rendered_path__43
right
NO
Salida justificada a la derecha
__rendered_path__45
internal
NO
Usar 'padding' después del signo o de la indicación de
__rendered_path__43
base. Esto quiere decir que el signo se pone
__rendered_path__46
justificado a la izquierda y el número justificado a
__rendered_path__43
la derecha. El espacio del medio se llena con el
__rendered_path__43
carácter de relleno.
__rendered_path__47
dec
SI
Base en decimal. Equivalente a la función dec
__rendered_path__47
oct
NO
Base en octal. Equivalente a la función oct
__rendered_path__47
hex
NO
Base en hexadecimal. Equivalente a la función hex
__rendered_path__47
showbase
NO
Usar indicador de base en la salida. Por ejemplo si
__rendered_path__49__rendered_path__44
hex está ON, un número saldrá precedido de 0x y en
__rendered_path__44__rendered_path__45
hexadecimal. En octal saldría precedido únicamente de
__rendered_path__49__rendered_path__46
un 0
__rendered_path__45
showpoint
NO
Poner en la salida el punto decimal y ceros a la
__rendered_path__49
derecha del punto (formato de coma) aunque no sea
__rendered_path__46
necesario para números reales
__rendered_path__49
uppercase
NO
Usar mayúsculas para los números hexadecimales en la
__rendered_path__47
salida y la E del exponente
__rendered_path__47
showpos
NO
Poner un '+' a todos los números positivos en la
__rendered_path__47
salida
__rendered_path__47
scientific
NO
Usar notación exponencial en los números reales
__rendered_path__43
fixed
NO
Usar notación fija en los números reales
__rendered_path__44
unitbuf
NO
Volcar todos los streams después de la salida
__rendered_path__43
stdio
NO
Volcar cout y cerr después de la salida
__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__58__rendered_path__58__rendered_path__58__rendered_path__58__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__66__rendered_path__66__rendered_path__66__rendered_path__66__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__70__rendered_path__70__rendered_path__70__rendered_path__70__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__73__rendered_path__73__rendered_path__73__rendered_path__73__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__73__rendered_path__73__rendered_path__73__rendered_path__73__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__47__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__79__rendered_path__79__rendered_path__79__rendered_path__79__rendered_path__43__rendered_path__44__rendered_path__43__rendered_path__45__rendered_path__43__rendered_path__46__rendered_path__43__rendered_path__47__rendered_path__43__rendered_path__43__rendered_path__44__rendered_path__47__rendered_path__43__rendered_path__45__rendered_path__47__rendered_path__43__rendered_path__46__rendered_path__47__rendered_path__43__rendered_path__43
77

Page 84
Manipuladores
__rendered_path__4
MANIPULADOR
SENTIDO
ACCIÓN
__rendered_path__4
dec
E/S
Pone la base a decimal hasta nueva orden
__rendered_path__5
oct
E/S
Lo mismo a octal
__rendered_path__4
hex
E/S
Lo mismo a hexadecimal
__rendered_path__6
ws
E
Extrae los caracteres blancos del stream. Se
__rendered_path__4
usa cuando skipws está a 0
__rendered_path__7
endl
S
Inserta una nueva línea. La única diferencia
__rendered_path__4
con el carácter '\n' es que endl vuelca el
__rendered_path__4
stream
__rendered_path__8
ends
S
Inserta un carácter nulo. Completamente
__rendered_path__8
equivalente a ' \ 0 ' y vuelca el string
__rendered_path__8
flush
S
Vuelca el stream
__rendered_path__8
setw(int)
E/S
Pone el ancho del campo. (Equivalente a
__rendered_path__10__rendered_path__5
width()). Tope máximo para la entrada y mínimo
__rendered_path__5__rendered_path__6
para la salida
__rendered_path__10__rendered_path__7
setfill(char)
S
Pone el carácter de relleno. Por defecto es el
__rendered_path__6
carácter espacio ' '. (Equivalente a fill())
__rendered_path__10
setprecision(int)
S
Pone la precisión a N dígitos después del
__rendered_path__7
punto decimal. (Equivalente a precision())
__rendered_path__10
setbase(int)
E/S
Pone la base (0, 8, 10, 16). 8 10 y 16 son
__rendered_path__8
equivalentes a oct, dec y hex que hemos visto
__rendered_path__8
antes. 0 implica dec en la salida y en la
__rendered_path__8
entrada significa que podemos usar las
__rendered_path__8
convenciones del C++ para introducir los
__rendered_path__4
números en distintas bases (0N: octal. 0xN:
__rendered_path__5
hexadecimal, N: decimal)
__rendered_path__4
resetiosflags(long)
Equivalente a unsetf()
__rendered_path__6
setiosflags(long)
Equivalente a setf()
__rendered_path__4
Otras funciones de acceso a Streams
__rendered_path__7
ostream& put(char);
__rendered_path__4
Inserta un carácter en el stream de salida. Retorna el stream de salida.
__rendered_path__8
int get();
__rendered_path__8
Extrae el siguiente carácter del stream de entrada y lo retorna. Se retorna EOF si está
__rendered_path__8
vacío.
__rendered_path__8
int peek();
__rendered_path__4
Lo mismo que get() pero sin extraer el carácter.
__rendered_path__5
istream& putback(char);
__rendered_path__4
Pone de vuelta un carácter en el stream de entrada. En caso de querer meter otro carácter
__rendered_path__6
dará error. Retorna el stream de entrada.
__rendered_path__4
istream& qet(char &):
__rendered_path__7
Extrae el siguiente carácter del stream de entrada. Retorna el stream de entrada.
__rendered_path__4
istream& get(char *s, int n, char t= '\n');
__rendered_path__8
Extrae hasta n caracteres en s parando cuando se encuentra el carácter t o bien cuando se
__rendered_path__8
llega a fin de fichero o hasta que se han leído (n-1 ) caracteres . El carácter t no se
__rendered_path__8
almacena en s. pero sí un '\0' final en s. Retorna el stream de entrada. Falla sólo si no
__rendered_path__8
se extrae ningún carácter.
__rendered_path__4
istream& getline(char *s, int n, char t= '\n');
__rendered_path__5
Igual que la anterior pero en el caso de que se encuentre t se extrae y se añade en s.
__rendered_path__4
istream& ignore(lnt n, int t= EOF);
__rendered_path__6
Extrae y descarta hasta n caracteres o hasta que el carácter t se encuentre. El carácter t
__rendered_path__4
se saca del stream. Se retorna el stream de entrada.
__rendered_path__7
int gcount();
__rendered_path__4
Retorna el número de caracteres extraídos en la última extracción.
__rendered_path__15
ostream& flush();
__rendered_path__15
Vuelca el contenido del stream. Esto es vacía el buffer en la salida.
__rendered_path__15
itream& read(char *s, int n);
__rendered_path__15__rendered_path__4__rendered_path__5__rendered_path__4__rendered_path__6__rendered_path__4__rendered_path__7__rendered_path__4__rendered_path__20__rendered_path__20__rendered_path__20__rendered_path__20__rendered_path__4__rendered_path__5__rendered_path__4__rendered_path__6__rendered_path__4__rendered_path__7__rendered_path__4__rendered_path__15__rendered_path__15__rendered_path__15__rendered_path__15__rendered_path__4__rendered_path__5__rendered_path__4__rendered_path__6__rendered_path__4__rendered_path__7__rendered_path__4__rendered_path__8__rendered_path__8__rendered_path__8__rendered_path__8__rendered_path__4__rendered_path__5__rendered_path__4__rendered_path__6__rendered_path__4__rendered_path__7__rendered_path__4__rendered_path__20__rendered_path__20__rendered_path__20__rendered_path__20__rendered_path__4__rendered_path__5__rendered_path__4__rendered_path__6__rendered_path__4__rendered_path__7__rendered_path__4__rendered_path__15__rendered_path__15__rendered_path__15__rendered_path__15__rendered_path__4__rendered_path__5__rendered_path__4__rendered_path__6__rendered_path__4__rendered_path__7__rendered_path__4__rendered_path__15__rendered_path__15__rendered_path__15__rendered_path__15__rendered_path__4__rendered_path__5__rendered_path__4__rendered_path__6__rendered_path__4__rendered_path__7__rendered_path__4__rendered_path__39__rendered_path__39__rendered_path__39__rendered_path__39__rendered_path__4__rendered_path__5__rendered_path__4__rendered_path__6__rendered_path__4__rendered_path__7__rendered_path__4__rendered_path__41__rendered_path__41__rendered_path__41__rendered_path__41__rendered_path__4__rendered_path__5__rendered_path__4__rendered_path__6__rendered_path__4__rendered_path__7__rendered_path__4__rendered_path__8__rendered_path__4__rendered_path__4__rendered_path__5__rendered_path__8__rendered_path__4__rendered_path__6__rendered_path__8__rendered_path__4__rendered_path__7__rendered_path__8__rendered_path__4__rendered_path__4
78

Page 85
Extrae n caracteres y los copia en s. Utilizar gcount() para ver cuantos caracteres han
sido extraídos si la lectura termina en error.
ostream& seekp(streampos);
istream& seekg(streampos);
Posicionan el buffer de entrada o salida a la posición absoluta pasada como parámetro.
ostream& seekp(streamoff, seek_dir);
istream& seekg(streamoff, seek_dir);
Posiciona el buffer de entrada o salida relativamente en función del parámetro seek_dir:
enum seek_dir {
beg=0, // relativo al principio
cur=l, // relativo a la posición actual
end=2 // relativo al fin de fichero
}
streampos tellp();
Devuelve la posición actual del buffer de salida.
streampos tellg();
Posición actual del buffer de entrada.
ostream& write(const char* s, int n);
Inserta n caracteres en el stream de salida (caracteres nulos incluidos).
Ficheros
Apertura y cierre
fstream::fstream()
Constructor por defecto. Inicializa un objeto de stream sin abrir un fichero.
fstream::fstream(const char *f, int ap, int p= S_IREAD | S_IWRITE);
Constructor que crea un objeto fstream y abre un fichero f con el modo de apertura ap
y el modo de protección p.
fstream::~fstream()
Destructor que vuelca el buffer del fichero y cierra el fichero (si no se ha cerrado ya).
fstream::open(const char *f, int ap, int p= S_IREAD | S_IWRITE);
Abre el fichero f con el modo de apertura ap y con el modo de protección p.
int fstream::is_open();
Retorna distinto de cero si el fichero está abierto.
fstream::close();
Cierra el fichero si no está ya cerrado.
Modos de apertura
__rendered_path__39
MODO
SIGNIFICADO
__rendered_path__39
in
Abierto para lectura
__rendered_path__40
out
Abierto para escritura
__rendered_path__39
ate
Colocarse al final del fichero
__rendered_path__41
app
Modo append. Toda la escritura ocurre al final del fichero
__rendered_path__39
trunc
Borra el contenido del fichero al abrir si ya existe. Es el valor por
__rendered_path__39
defecto si sólo se especifica out
__rendered_path__42
nocreate
El fichero debe existir en el momento de la apertura, si no, falla
__rendered_path__42
noreplace
El fichero no debe existir en el momento de la apertura, si no, falla
__rendered_path__42
binary
Los caracteres '\r' y '\n' no son convertidos. Activar cuando se
__rendered_path__44__rendered_path__40
trabaje con ficheros de datos binarios. Cuando se trabaje con textos
__rendered_path__40__rendered_path__41
dejarlo por defecto que es desactivado
__rendered_path__44
Modos de protecció n
__rendered_path__41
Los modos de protecció n dependen del sistema operativo y por tanto no existe una definició n
__rendered_path__44
estándar de los mismos. Só lo se definen:
__rendered_path__42
S_IREAD Permiso de lectura
__rendered_path__42__rendered_path__42__rendered_path__39__rendered_path__40__rendered_path__39__rendered_path__41__rendered_path__39__rendered_path__42__rendered_path__42__rendered_path__42__rendered_path__39__rendered_path__40__rendered_path__39__rendered_path__41__rendered_path__39__rendered_path__42__rendered_path__42__rendered_path__42__rendered_path__39__rendered_path__40__rendered_path__39__rendered_path__41__rendered_path__39__rendered_path__42__rendered_path__42__rendered_path__42__rendered_path__39__rendered_path__40__rendered_path__39__rendered_path__41__rendered_path__39__rendered_path__51__rendered_path__51__rendered_path__51__rendered_path__39__rendered_path__40__rendered_path__39__rendered_path__41__rendered_path__39__rendered_path__42__rendered_path__42__rendered_path__42__rendered_path__39__rendered_path__40__rendered_path__39__rendered_path__41__rendered_path__39__rendered_path__54__rendered_path__54__rendered_path__54__rendered_path__39__rendered_path__40__rendered_path__39__rendered_path__41__rendered_path__39__rendered_path__58__rendered_path__39__rendered_path__39__rendered_path__40__rendered_path__58__rendered_path__39__rendered_path__41__rendered_path__58__rendered_path__39__rendered_path__39
79

Page 86
S_IWRITE Permiso de escritura
Otras funciones de gestió n de ficheros
fstream (int fh)
Construye un stream usando un descriptor de fichero abierto existente descrito por fh.
attach(int fh);
Liga un stream con el descriptor fh. Si el stream ya está ligado da un error failbit.
fstream(int fh, char *p, int l);
Permite construir un stream con buffer que se liga a fh. p apunta a un buffer de l byte de
longitud. Si p==NULL o l==O el stream no utilizará buffer.
setbuf(char *p, int l);
Permite cambiar el buffer y la longitud. Si p==NULL o l==O el stream pasará a no tener
buffer.
istream& seekg(long offset, seek_dir mode= ios::beg);
ostream& seekp(long offset, seek_dir mode= ios::beg);
long tellg();
long tellp();
istream& read(signed char *s, int nbytes);
istream& read(unsigned char *s, int nbytes);
istream& read(void *p, int nbytes);
ostream& write(const signed char *s, int nbytes);
ostream& write(const unsigned char *s, int nbytes);
ostream& write(void p, int nbytes);
PROGRAMACIÓN EN C++
A continuació n daremos unas nociones sobre lo que debe ser la programació n en C++. Estas
ideas no son las ú nicas aceptables, só lo pretendo que os sirvan como referencia inicial hasta que
encontré is lo que más se acomode a vuestra forma de trabajar. Hay una frase que leí una vez
que resume esto ú ltimo: "Los estándares son buenos, cada uno debería tener el suyo".
El proceso de desarrollo
Dentro de la metodología de programació n clásica se definen una serie de fases en el proceso de
desarrollo de aplicaciones. No es mi intenció n repetirlas ahora, só lo quiero indicar que las
nuevas metodologías de programació n orientadas a objetos han modificado la forma de trabajar
en estas etapas. El llamado ciclo de vida del software exigía una serie de etapas a la hora de
corregir o modificar los programas, trabajando sobre todas las etapas del proceso de desarrollo.
A mi modo de ver estas etapas siguen existiendo de una manera u otra, pero el trabajo sobre el
análisis y diseño (que antes eran textos y diagramas, no có digo) es ahora posible realizarlo
sobre la codificació n: la idea de clase y objeto obliga a que los programas tengan una estructura
muy similar a la descrita en las fases de análisis y diseño, por lo que un có digo bien
documentado junto con herramientas que trabajan sobre el có digo (el browser, por ejemplo, nos
muestra la estructura de las jerarquías de clases de nuestro programa) puede considerarse un
modelo del análisis y el diseño (sobre todo del diseño, pero las clases nos dan idea del tipo de
análisis realizado).
Mantenibilidad y documentació n
Para mantener programas en C++ de forma adecuada debemos tener varias cosas en cuenta
mientras programamos: es imprescindible un análisis y un diseño antes de implementar las
clases, y todo este trabajo debe estar reflejado en la documentació n del programa. Además, la
estructura de clases nos permite una prueba de có digo mucho más fácil, podemos verificar clase
a clase y mé todo a mé todo sin que ello afecte al resto del programa.
Debemos documentar el có digo abundantemente, es decir, debemos comentar todo lo que
podamos los programas, explicando el sentido de las variables y objetos o la forma de
implementar determinados algoritmos.
80

Page 87
Diseño e implementació n
Debemos diseñar las aplicaciones en una serie de niveles diferentes: diseño de gestió n de la
informació n, diseño de la interface de la aplicació n, etc.
A la hora de hacer programas es importante separar la parte de interface con el usuario de la
parte realmente computacional de nuestra aplicació n. Todo lo que hagamos a nivel de gestió n de
ficheros y datos debe ser lo más independiente posible de la interface de usuario en la que
trabajamos. El hacer así las cosas nos permite realizar clases reutilizables y transportables a
distintos entornos. Una vez tenemos bien definidas las clases de forma independiente de la
interface con el usuario podemos definir esta e integrar una cosa y otra de la forma más simple y
elegante posible.
Esta separació n nos permitirá diseñar programas para SO con interface textual y transportarla a
SO con ventanas y menú s sin cambios en la funcionalidad de la aplicació n.
Elecció n de clases
Un buen diseño en C++ (o en cualquier lenguaje orientado a objetos), pasa por un buen análisis
de las clases que deben crearse para resolver nuestro programa. Una idea para identificar que
deben ser clases, que deben ser objetos, que deben ser atributos o mé todos de una clase y como
deben relacionarse unas clases con otras es estudiar una descripció n textual del problema a
resolver. Por norma general los conceptos abstractos representarán clases, las características de
estos conceptos (nombres) serán atributos, y las acciones (verbos) serán mé todos. Las
características que se refieran a más de un concepto nos definirán de alguna manera las
relaciones de parentesco entre las clases y los conceptos relativos a casos concretos definirán los
objetos de necesitamos.
Todo esto es muy vago, existen metodologías que pretenden ser sistemáticas a la hora de elegir
clases y definir sus miembros, pero yo no acabo de ver claro como se pueden aplicar a casos
concretos. Quizás la elecció n de clases y jerarquías tenga un poco de intuitivo. De cualquier
forma, es fácil ver como definir las clases una vez tenemos un primer modelo de las clases para
tratar un problema e intentamos bosquejar que tipo de flujo de control necesitamos para
resolverlo.
Interfaces e implementació n
Cuando definamos las clases de nuestra aplicació n debemos intentar separar muy bien lo que es
la interface externa de nuestra clase (la declaració n de su parte protegida y pú blica) de la
implementació n de la clase (declaració n de miembros privados y definició n de mé todos).
Incluyo en la parte de implementació n los miembros privados porque estos só lo son importantes
para los mé todos y funciones amigas de la clase, no para los usuarios de la clase. La correcta
separació n entre una cosa y otra permite que nuestra clase sea fácil de usar, de modificar y de
transportar.
LIBRERÍAS DE CLASES
Como hemos visto, el C++ nos permite crear clases y jerarquías de clases reutilizables, es decir,
las clases que definimos para programas concretos pueden utilizarse en otros programas si la
definició n es lo suficientemente general. Lo cierto es que existen una serie de clases que se
pueden reutilizar siempre: las clases que definen aspectos de la interface con el SO (ventanas,
menú s, gestió n de eventos, etc.) y las clases contenedor (pilas, colas, árboles, etc.). Existe otra
serie de clases que pueden reutilizarse en aplicaciones concretas (clases para definir figuras
geomé tricas en 2 y 3 dimensiones para aplicaciones de dibujo, clases para gestionar documentos
de texto con formato en editores de texto, etc.).
En este bloque comentaremos algunas cosas a tener en cuenta a la hora de diseñar y trabajar con
bibliotecas de clases.
81