You are on page 1of 208

Parte VI

Estructura de datos
en C, C
++
y Java
Organizacin
de datos
en un archivo
Captulo
Contenido
Registros
Organizacin de archivos
Archivos con funcin de direccionamiento hash
Archivos secuenciales indexados
Ordenacin de archivos: ordenacin externa
Codificacin del algoritmo de mezcla directa
Resumen
Ejercicios
Problemas
Introduccin
Grandes cantidades de datos se almacenan nor malmente en dispositivos de memoria externa. En este
captulo se realiza una introduccin a la organi zacin y gestin de datos estructurados sobre dis-
positivos de almacenamiento secundario, tales como discos magnticos, CD... Las tcnicas requeridas
para gestionar datos en archivos son diferentes de las tcnicas que estructuran los datos en memoria
principal, aunque se construyen con ayuda de estructuras utilizadas en esta memoria.
Los algoritmos de ordenacin de arrays no se pueden aplicar, normalmente, si la cantidad de datos
a ordenar no caben en la memoria principal de la computadora y estn en un dispositivo de almacena-
miento externo. Es necesa rio aplicar nuevas tcnicas de ordenacin que se complementen con las ya
estudiadas. Entre las tc nicas ms importantes destaca la fusin o mezcla. Mezclar significa combinar
dos (o ms) secuencias en una sola secuencia ordenada por medio de una seleccin repetida entre los
componentes accesi bles en ese momento.
31
934 Captulo 31 Organizacin de datos en un archivo
Conceptos clave
Archivo secuencial Mezcla
Archivo secuencial indexado Ordenacin externa
Colisin Registro
Direccin dispersa Secuencia
Hash Transformacin de claves
Registros
Un archivo o fichero es un conjunto de datos estructurados en una coleccin de registros, que son de
igual tipo y constan a su vez de diferentes entidades de nivel ms bajo denominadas campos.
Un registro es una coleccin de campos lgicamente relacionados, que pueden ser tratados como
una unidad por algn programa. Un ejemplo de un registro puede ser la informacin de un determina-
do libro que contiene los campos de ttulo, autor, editorial, fecha de edicin, nmero de pginas,
ISBN, etc. Los regis tros organizados en campos se denominan registros lgicos.
El concepto de registro es similar al concepto de estructura (struct) de C. Una posible representa-
cin en C del registro libro es la siguiente:
struct libro
{
char titulo [46];
char autor [80];
char editorial [35];
struct fecha fechaEdicion;
int numPags;
long isbn;
};
Clave
Una clave es un campo de datos que identifica el registro y lo diferencia
de otros registros. Normalmente los registros de un archivo se organizan
segn un campo clave. Claves tpicas son nmeros de identifica cin,
nombres; en general puede ser una clave de cualquier campo que admi-
ta relaciones de comparacin. Por ejemplo, un archivo de libros puede
estar organizado por autor, por editorial, etctera.
Registro fsico (bloque)
Un registro fsico o bloque es la cantidad de datos que se transfieren en
una operacin de entrada/salida entre la memoria central y los disposi-
tivos perifricos.
Un bloque puede contener uno o ms registros lgicos. Tambin pue-
de ser que un registro lgico ocu pe ms de un registro fsico o bloque.
Organizacin de archivos
La organizacin de un archivo define la forma en que los registros se disponen sobre el dispositivo de
alma cenamiento. La organizacin determina cmo estructurar los registros en un archivo. Se conside-
ran tres organizaciones fundamentales:
Organizacin secuencial.
Organizacin directa.
Organizacin secuencial indexada.

A recordar
El nmero de registros lgicos que puede conte-
ner un registro fsico se denomina factor de
blo queo. Las operaciones de entrada/salida que
se realizan en programas C se hacen por blo-
ques a travs de un rea de memoria principal
denominada buffer; esto hace que mejore el
rendimiento de los programas.
La constante predefinida BUFSIZ
(stdio.h) contiene el tamao del buffer.
935 Organizacin de archivos
Organizacin secuencial
Un archivo con organizacin secuencial (archivo secuencial) es una sucesin de registros almacena-
dos consecutivamente, uno detrs de otro, de tal modo que para acceder a un registro dado es necesa-
rio pasar por todos los registros que le preceden.
En un archivo secuencial los registros se insertan en orden de llegada, es decir, un registro se
almace na justo a continuacin del registro anterior. Una vez insertado el ltimo registro y cerrado el
archivo, el sistema aade la marca fin de archivo.
Las operaciones bsicas que se realizan en un archivo secuencial son: escribir los registros, con-
sultar los registros y aadir un registro al final del archivo.
Un archivo con organizacin secuencial se puede procesar tanto en modo texto como en modo
bina rio. En C para crear estos archivos se abren (fopen ( )) especificando en el argumento modo:
"w", "a", "wb" o "ab"; a continuacin se escriben los registros utilizando, normalmente, las funcio-
nes fwrite ( ), fprintf ( ) y fputs ( ).
La consulta del archivo se realiza abriendo ste con el modo "r" y, generalmente, leyendo todo
el archivo hasta el indicador o marca fin de archivo. La funcin feof ( ) es muy til para detectar el
fin de archivo, devuelve 1 si se ha alcanzado el final del archivo. El siguiente bucle permite leer to-
dos los regis tros del archivo:
while (!feof(pf))
{
< leer registro >
}
Ejercicio 31.1
Se realizan votaciones para elegir al presidente de la federacin de Petanca. Cada distrito enva a la
ofi cina central un sobre con los votos de cada uno de los tres candidatos. En un archivo con organi-
zacin secuencial se graban registros con la estructura de datos correspondiente a cada distrito, es
decir: nom bre del distrito y votos de cada candidato. Una vez terminado el proceso de grabacin se
han de obtener los votos de cada candidato.
Los campos de datos que tiene cada uno de los registros estn descritos en el propio enunciado:
Nom bre del distrito, Candidato1, votos, Candidato2, votos y Candidato3, votos. El archivo es de tipo
binario; de esa forma se ahorra convertir los datos de tipo entero (votos) a dgitos ASCII, y cuando se
lea el archi vo para contar votos hacer la conversin inversa.
La creacin del archivo se hace abriendo ste en modo aadir al final ("a"); de esta forma pueden
aa dirse nuevos registros en varias sesiones.
Para realizar la operacin de cuenta de votos es necesario, en primer lugar, abrir el archivo en modo
lectura ("rb"), y en segundo lugar leer todos los registros hasta llegar a la marca de fin de archivo.
Con cada registro ledo se incrementa la cuenta de votos de cada candidato.
La declaracin del registro, en este ejercicio, se denomina Distrito, el nombre del archivo y la
defi nicin del puntero a FILE se realiza en el archivo Petanca.h:
typedef struct
{
char candidato1[41];
long vot1;
char candidato2 [41];
long vot2;
char candidato3 [41];
long vot3;
} Distrito;
char* archivo = "Petanca.dat";
FILE *pf = NULL;
936 Captulo 31 Organizacin de datos en un archivo
/*
Cdigo fuente del programa, petanca.c, que escribe secuencialmente los
registros en el archivo Petanca.dat.
*/
void main ( )
{
Distrito d;
int termina;
pf = fopen (archivo, "ab");
if (pf == NULL)
{
puts ("No se puede crear el archivo.");
exit(1);
}
strcpy(d.candidato1, "Lis Alebuche");
strcpy(d.candidato2, "Pasionis Cabitorihe");
strcpy(d.candidato3, "Gulius Martaria");
termina = 0;
puts ("Introducir los votos de cada candidato, termina con 0 0 0");
do {
leeRegistro (&d);
if ( (d.vot1 == 0) && (d.vot2 == 0) && (d.vot3 == 0))
{
termina = 1;
puts ("Fin del proceso. Se cierra el archivo");
}
else
fwrite(&d, sizeof(Distrito), 1, pf);
} while (!termina);
fclose(pf);
}
void leeRegistro(Distrito* d)
{
printf ("Votos para %s : ", d > candidato1);
scanf("%ld", &(d > vot1));
printf ("Votos para %s : ", d > candidato2);
scanf("%ld", &(d > vot2));
printf ("Votos para %s : ", d > candidato3);
scanf("%ld", &(d > vot3));
}
/*
Cdigo fuente del programa, cntavoto.c, que lee secuencialmente los registros
del archivo Petanca.dat y cuenta los votos.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string .h>
#include "petanca. h"
void main ( )
{
Distrito d;
int votos[3] = {0,0,0};
pf = fopen(archivo, "rb");
if (pf == NULL)
{
puts("No se puede leer el archivo.");
exit(1);
}
fread (&d, sizeof(Distrito),1, pf);
937 Organizacin de archivos
while (!feof(pf))
{
votos[0] += d.vot1;
votos[1] += d.vot2;
votos[2] += d.vot3;
fread(&d, sizeof(Distrito),1, pf);
}
fclose(pf);
puts (" \n\tVOTOS DE CADA CANDIDATO\n");
printf (" %s %ld: \n", d.candidato1, votos [0] );
printf (" %s %ld: \n", d.candidato2, votos [1] );
printf (" %s %ld: \n", d.candidato3, votos [2] );
}
Organizacin directa
Un archivo con organizacin directa (aleatoria), o sencillamente archivo directo, se caracteriza por-
que el acceso a cualquier registro es directo mediante la especificacin de un ndice, que da la posi-
cin ocupada por el registro respecto al origen del archivo.
Los archivos directos tienen una gran rapidez para el acceso a los registros comparados con los
secuen ciales. La lectura/escritura de un registro es rpida, ya que se accede directamente al registro y
no se nece sita recorrer los anteriores, como ocurre en los archivos secuenciales.
En C estos archivos pueden ser de texto o binarios. Normalmente se crean de tipo binario para que
las operaciones de entrada/salida sean ms eficientes. C dispone de funciones para situarse, o cono-
cer la posicin en el archivo: fseek ( ), fsetpos ( ), ftell ( ) y fgetpos ( ).
Los registros de los archivos directos disponen de un campo de tipo entero (por ejemplo, indice)
con el que se obtiene la posicin en el archivo. Esta sencilla expresin permite obtener el desplaza-
miento de un registro dado respecto al origen del archivo:
desplazamiento (indice) = (indice 1) * tamao (registro)
En los registros de un archivo directo se suele incluir un campo adicional, ocupado, que permite
dis tinguir un registro dado de baja o de alta. Entonces, dentro del proceso de creacin se puede consi-
derar una inicializacin de cada uno de los posibles registros del archivo con el campo ocupado = 0,
para indicar que no se han dado de alta:
registro.ocupado = 0;
for (i = 1; i <= numRegs; i++)
fwrite(&registro, sizeof (tipoRegistro), 1, pf);
Tanto para dar de alta como para dar de baja, modificar o consultar un registro se accede a la po-
sicin que ocupa, calculando el desplazamiento respecto al origen del archivo. La funcin C de posi-
cin (fse ek ( ) fsetpos ( )) sita el apuntador del archivo directamente sobre el byte a partir del
cual se encuentra el registro.
Ejercicio 31.2
Las reservas de un hotel de n habitaciones se van a gestionar con un archivo directo. Cada reserva
tiene los campos: nombre del cliente, NIF (nmero de identificacin fiscal) y nmero de habitacin
asignada. Los nmeros de habitacin son consecutivos, desde 1 hasta el nmero total de habitaciones
(por ello, se uti liza como ndice de registro el nmero de habitacin).
Las operaciones que se podrn realizar son: inaguracin, entrada de una reserva, finalizacin de es-
tancia, consulta de habitaciones.
938 Captulo 31 Organizacin de datos en un archivo
Cada registro del archivo va a corresponder a una reserva y a la vez al estado de una habita cin. Si
la habitacin n est ocupada, el registro de ndice n contendr el nombre del cliente y su NIF. Si est
vaca (libre) el campo NIF va a tener un asterisco ('*'). Por consiguiente, se utiliza como indicador
de habitacin libre que nif == *.
La operacin inauguracin inicializa el archivo, escribe tantos registros como habitaciones; cada
regis tro con el campo NIF igual a la clave *, para indicar habitacin libre y su nmero de habita-
cin.
La operacin entrada busca en el archivo la primera habitacin libre y en su registro escribe uno
nue vo con los datos de la reserva.
La finalizacin de una reserva consiste en asignar al campo NIF la clave (*) que indica habitacin
libre.
Tambin se aade la operacin ocupadas para listar todas las habitaciones ocupadas en ese momento.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#define numhab 55
FILE *fb = NULL;
const char fich [ ] = "fichero.dat";
typedef struct
{
int num;
char nif [13];
char nombre[45];
} Habitacion;
#define desplazamiento(n) ((n 1) * sizeof (Habitacion))
void inauguracion (void);
void entrada (void);
void salida (void);
void ocupadas (void);
void leerRes (Habitacion * ph);
void escribirRes (Habitacion h);
void main ( )
{
char opcion;
do {
puts ("1. Inauguracion");
puts ("2. Entrada ");
puts ("3. Salida ");
puts ("4. Ocupadas ");
puts ("5. Salir ");
do {
printf ("Elige una opcin ");
scanf ("%c%*c", &opcion);
} while (opcion < '1' || opcion > '5');
switch (opcion)
{
case '1':
inauguracion ( );
break;
case '2':
entrada ( );
break;
case '3':
salida ( );
break;
case '4':
ocupadas ( );
system ("pause");
939 Organizacin de archivos
break;
}
}
while (opcion != '5');
if (fb != NULL) fclose (fb);
}
void inauguracion (void)
{
Habitacion h;
int i;
if (fb != NULL)
{
char opcion;
printf ("Archivo ya creado, desea continuar(S/N) ?: ");
scanf ("%c%*c", &opcion);
if (toupper(opcion) != 'S') return;
}
fb = fopen (fich, "wb+");
for (i = 1; i <= numhab; i++)
{
h.num = i;
strcpy (h.nif, "*");
fwrite (&h, sizeof (h), 1, fb);
}
fflush (fb);
}
void entrada (void)
{
Habitacion h;
int encontrado, nh;
/* Bsqueda secuencial de primera habitacin libre */
encontrado = 0;
nh = 0;
if (fb == NULL) fb = fopen (fich, "rb+");
fseek (fb, 0L, SEEK_SET);
while ((nh < numhab) && !encontrado)
{
fread (&h, sizeof (h), 1, fb);
nh++;
if (strcmp (h.nif ,"*") == 0) /* Habitacin libre */
{
encontrado = 1;
leerRes (&h);
fseek (fb, desplazamiento (h.num), SEEK_SET);
fwrite (&h, sizeof (h), 1, fb);
puts ("Datos grabados");
}
}
if (!encontrado) puts ("Hotel completo ");
fflush (fb);
}
void salida (void)
{
Habitacion h;
int n;
char r;
if (fb == NULL) fb = fopen (fich, "rb+");
printf ("Numero de habitacion: ");
scanf ("%d%*c",&n);
fseek (fb, desplazamiento (n), SEEK_SET);
fread (&h, sizeof (h), 1, fb);
940 Captulo 31 Organizacin de datos en un archivo
Archivos con funcin de direccionamiento hash
La organizacin directa tiene el inconveniente de que no hay un campo del registro que permita obte-
ner posiciones consecutivas y, como consecuencia, quedan muchos huecos libres entre registros. En-
if (strcmp (h.nif,"*") != 0)
{
escribirRes (h);
printf ("Son correctos los datos?(S/N) ");
scanf ("%c%*c" ,&r);
if (toupper (r) == 'S')
{
strcpy (h.nif, "*");
fseek (fb, sizeof (h), SEEK_CUR);/* se posiciona de nuevo */
fwrite (&h, sizeof (h), 1, fb);
}
}
else
puts ("La habitacion figura como libre");
fflush (fb);
}
void ocupadas (void) {
Habitacion h;
int n;
if (fb == NULL) fb = fopen (fich, "rb+");
fseek (fb, 0L, SEEK_SET);
puts (" Numero \t NIF \t\t Nombre");
puts (" habitacion ");
for (n = 1 ; n <= numhab; n++)
{
fread (&h, sizeof (h), 1, fb);
if (strcmp (h.nif ,"*") != 0)
escribirRes (h);
}
}
void leerRes (Habitacion *ph)
{
printf ("Nif: ");
gets (ph > nif);
printf ("Nombre ");
gets (ph > nombre);
}
void escribirRes (Habitacion h)
{
printf ("\t %d", h.num);
printf ("\t%s\t", h.nif);
printf ("\t%s\n", h.nombre);
}

A recordar
La frmula o algoritmo que transforma una clave en una direccin se denomina funcin hash. Las posiciones de los
registro en un archivo se obtienen transformando la clave del registro en un ndice entero. A los archivos con esta organiza-
cin se les conoce como archivos hash o archivos con transformacin de claves.
941 Archivos con funcin de direccionamiento hash
tonces la organi zacin directa necesita programar una relacin entre un campo clave del registro y la
posicin que ocupa.
Funciones hash
Una funcin hash, o de dispersin, convierte un campo clave (un entero o una cadena) en un valor en-
tero dentro del rango de posiciones que puede ocupar un registro de un archivo.
Si x es una clave, entonces h(x) se denomina direccionamiento hash de la clave x, adems es el
ndice de la posicin donde se debe guardar el registro con esa clave. Por ejemplo, si estn previstos
un mxi mo de tamIndex = 199 registros, la funcin hash debe generar ndices en el rango
0 .. tamIndex1.
Por ejemplo, la clave puede ser el nmero de serie de un artculo (hasta 6 dgitos) y si estn pre-
vistos un mximo de tamIndex registros, la funcin de direccionamiento tiene que ser capaz de
transformar valo res pertenecientes al rango 0 .. 999999, en un conjunto de rango 0 .. tamIn-
dex1. La clave tambin puede ser una cadena de caracteres, en cuyo caso se hace una transforma-
cin previa a valor entero.
La funcin hash ms utilizada por su sencillez se denomina aritmtica modular. Genera valores
disper sos calculando el resto de la divisin entera entre la clave x y el nmero mximo de registros
previstos.
x % tamIndex = genera un nmero entero de 0 a tamIndex1
La funcin hash o funcin de transformacin debe reducir al mximo las colisiones. Se produce
una colisin cuando dos registros de claves distintas, c1 y c2, producen la misma direccin, h (c1)
= h(c2). Nunca existir una garanta plena de que no haya colisiones, y ms sin conocer de antema-
no las claves y las direcciones. La experiencia ensea que siempre habr que preparar la resolucin
de colisiones para cuando se produzca alguna.
Para que la funcin aritmtica modular disperse lo ms uniformemente posible se recomienda ele-
gir como tamao mximo, tamIndex, un nmero primo que supere el nmero previsto de registros.
Ejemplo 31.1
Considerar una aplicacin en la que se deben almacenar n = 900 registros. El campo clave ele-
gido para dispersar los registros en el archivo es el nmero de identificacin. Elegir el tamao
de la tabla de dis persin y calcular la posicin que ocupan los registros cuyo nmero de iden-
tificacin es: 245643, 245981 y 257135.
En este supuesto, una buena eleccin de tamIndex es 997 al ser un nmero primo mayor que
el nme ro de registros que se van a grabar, 900. Se aplica la funcin hash de aritmtica modu-
lar y se obtienen estas direcciones:
h(245643)
=
245643 % 997 = 381
h(245981)
=
245981 % 997 = 719
h(257135)
=
257135 % 997 = 906
Caractersticas de un archivo con direccionamiento hash
Para el diseo del archivo se deben considerar dos reas de memoria externa: el rea principal y el
rea de sinnimos o colisiones. Aproximadamente el archivo se crea con 25% ms que el nmero de
registros necesarios.
Un archivo hash se caracteriza por:
Acceder a las posiciones del archivo a travs del valor que devuelve una funcin hash.
La funcin hash aplica un algoritmo para transformar uno de los campos llamado campo clave
en una posicin del archivo.
942 Captulo 31 Organizacin de datos en un archivo
El campo elegido para la funcin debe ser nico (no repetirse) y conocido fcilmente por el
usuario, porque a travs de ese campo va a acceder al programa.
Todas las funciones hash provocan colisiones o sinnimos. Para solucionar estas repeticiones se
defi nen dos zonas:
Zona de datos o principal en la que el acceso es directo al aplicarle la funcin hash.
Zona de colisiones o sinnimos en la que el acceso es secuencial. En esta zona se guardan las
estructuras o registros en los que su campo clave ha producido una posicin repetida. Y se
van colocando secuencialmente, es decir, en la siguiente posicin que est libre.
En el ejercicio 31.3 se disea un archivo con direccionamiento hash. Implementa las operaciones
ms importantes que se pueden realizar con archivos: creacin, consulta, alta y baja. El campo clave
elegido es una cadena (formada por caracteres alfanumricos); para obtener la posicin del registro,
primero se transforma la cadena (segn el cdigo ASCII) a un valor entero; a continuacin se aplica
la aritmtica modu lar para obtener la posicin del registro.
Ejercicio 31.3
Los libros de una pequea librera van a guardarse en un archivo para poder realizar accesos tan r-
pido como sea posible. Cada registro (libro) tiene los campos cdigo (cadena de 6 caracteres), autor
y ttulo. El archivo debe estar organizado como acceso directo con transformacin de claves (archivo
hash), la posicin de un registro se obtendr aplicando aritmtica modular al campo clave: cdigo.
La librera tie ne capacidad para 190 libros.
Para el diseo del archivo se crearn 240 registros que se distribuirn de la siguiente forma:
1. Posiciones 0 198 constituyen el rea principal del archivo.
2. Posiciones 199 239 constituyen el rea de desbordamiento o de colisiones.
El campo clave, cadena de 6 caracteres, se transforma considerando su secuencia de valores numri-
cos (ordinal ASCII de cada carcter) en base 27. Por ejemplo, el cdigo 2R545 se transforma en:
'2'*27
4
+ 'R'*27
3
+ '5'*27
2
+ '4'*27
1
+ '5'*27
0
En C, un carcter se representa como un valor entero que es, precisamente, su ordinal. La transfor-
macin da lugar a valores que sobrepasan el mximo entero (incluso con enteros largos), generando
nme ros negativos. No es problema, simplemente se cambia de signo.
La creacin del archivo escribe 240 registros, con el campo cdigo = '*', para indicar que estn
disponibles (de baja).
Para dar de alta un registro, se obtiene primero la posicin (funcin hash); si se encuentra dicha
posi cin ocupada, el nuevo registro deber ir al rea de colisiones (sinnimos).
El proceso de consulta de un registro debe comenzar con la entrada del cdigo; la transformacin
de la clave permite obtener la posicin del registro. A continuacin se lee el registro; la comparacin
del cdi go de entrada con el cdigo del registro determina si se ha encontrado. Si son distintos, se ex-
plora secuen cialmente el rea de colisiones.
La baja de un registro tambin comienza con la entrada del cdigo, se realiza la bsqueda, de igual
for ma que en la consulta, y se escribe la marca '*' en el campo cdigo (se puede elegir otro campo)
para indicar que ese hueco (registro) est libre.
La funcin hash devuelve un nmero entero n de 0 a (199-1); por esa razn el desplazamiento
desde el origen del archivo se obtiene multiplicando n por el tamao de un registro.
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#define principal 199
#define total 240
943 Archivos con funcin de direccionamiento hash
const char fich [12] = "fichash.dat";
FILE *fh = NULL;
typedef struct
{
char codigo [7];
char autor [41];
char titulo [41];
} Libro;
#define desplazamiento (n) ((n) * sizeof (Libro))
/* prototipo de las funciones */
void creacin (void);
void compra (void); /* operacin dar de Alta */
void venta (void); /* operacin dar de Baja */
void consulta (void);
void colisiones (Libro lib);
int indexSinonimo (const char c [ ]);
int hash(char c [ ]);
long transformaClave (const char c [ ]);
void escribir (Libro lib);
void main ( )
{
char opcion;
/* comprueba si el archivo ya ha sido creado */
fh = fopen (fich, "rb");
if (fh == NULL)
{
puts ("EL ARCHIVO VA A SER CREADO");
creacion ( );
}
else
fh = NULL;
do
{
puts ("1. Compra ");
puts ("2. Venta ");
puts ("3. Consulta ");
puts ("5. Salir ");
do {
printf ("Elige una opcin ");
scanf ("%c%*c", &opcion);
} while (opcion < '1' || opcion > '5' || opcion == '4 ');
switch (opcion)
{
case '1':
compra ( ); break;
case '2':
venta ( ); break;
case '3':
consulta ( ); break;
}
} while (opcion != '5');
if (fh != NULL) fclose (fh);
}
/*
Creacin: escribe consecutivamente total registros, todos con el campo cdigo
igual a '*' para indicar que estn libres.
*/
void creacin (void)
944 Captulo 31 Organizacin de datos en un archivo
{
Libro lib;
int i;
fh = fopen (fich, "wb+");
strcpy (lib.codigo, "*");
for (i = 1; i <= total; i++)
fwrite (&lib, sizeof (lib), 1, fh);
fclose (fh);
fh = NULL;
}
/*
Alta de un registro: pide al usuario los campos cdigo, ttulo y autor. Llama a
la funcin hash ( ) para obtener la posicin en la cual leer el registro, si
est libre graba el nuevo registro. Si est ocupado busca en el rea de
colisiones la primera posicin libre que ser donde escribe el registro.
*/
void compra (void)
{
Libro lib, libar;
long posicion;
if (fh == NULL) fh = fopen (fich, "rb+");
printf ("Cdigo: ");gets (lib.codigo);
printf ("Autor: ");gets (lib.autor);
printf ("Ttulo: ");gets (lib.titulo);
posicion = hash (lib.codigo);
posicion = desplazamiento(posicion);
fseek(fh, posicion, SEEK_SET);
fread(&libar, sizeof(Libro), 1, fh);
if (strcmp(libar.codigo, "*") == 0) /* registro libre */
{
fseek(fh, sizeof(Libro), SEEK_CUR);
fwrite(&lib, sizeof(Libro), 1, fh);
printf("Registro grabado en la direccin: %ld\n",posicion);
}
else if (strcmp(lib.codigo, libar.codigo) == 0) /* duplicado */
{
puts("Cdigo repetido, revisar los datos.");
return;
}
else
colisiones(lib);
fflush(fh);
}
/*
Baja de un registro: pide el cdigo del registro. Se lee el registro cuya
posicin est determinada por la funcin hash ( ). Si los cdigos son iguales,
se da de baja escribiendo '*' en el campo cdigo. En caso contrario se busca en
el rea de colisiones y se procede igual.
*/
void venta( )
{
Libro libar;
char codigo [7], r;
long posicion;
if (fh == NULL) fh = fopen(fich, "rb+");
printf("Cdigo: ");gets(codigo);
posicion = hash(codigo);
posicion = desplazamiento(posicion);
945 Archivos con funcin de direccionamiento hash
fseek(fh, posicion, SEEK_SET);
fread(&libar, sizeof(Libro), 1, fh);
if (strcmp(libar.codigo, codigo) != 0)
posicion = indexSinonimo (codigo);
if (posicion != 1)
{
escribir(libar);
printf("Son correctos los datos? (S/N): ");
scanf("%c%*c" ,&r);
if (toupper(r) == 'S')
{
strcpy(libar. codigo, " *");
fseek(fh, sizeof(Libro), SEEK_CUR);
fwrite(&libar, sizeof(Libro), 1, fh);
}
}
else
puts("No se encuentra un registro con ese cdigo.");
fflush(fh);
}
/*
Consulta de un registro: pide el cdigo del registro. Se lee el registro cuya
posicin est determinada por la funcin hash ( ). Si los cdigos son iguales
se muestra por pantalla. En caso contrario se busca en el rea de colisiones.
*/
void consulta( )
{
Libro lib;
char codigo[7];
long posicion;
if (fh == NULL) fh = fopen(fich, "rb+");
printf("Cdigo: ");gets(codigo);
posicion = hash(codigo);
posicion = desplazamiento(posicion);
fseek(fh, posicion, SEEK_SET);
fread(&lib, sizeof(Libro), 1, fh);
if (strcmp(lib.codigo, codigo) == 0)
escribir(lib);
else
{
int posicion;
posicion = indexSinonimo (codigo);
if (posicion != 1)
{
fseek(fh, sizeof(Libro), SEEK_CUR);
fread(&lib, sizeof(Libro), 1, fh);
escribir(lib);
}
else
puts("No se encuentra un ejemplar con ese cdigo.");
}
}
/*
Inserta en rea de sinnimos: busca secuencialmente el primer registro libre
(codigo=='*') para grabar el registro lib.
*/
946 Captulo 31 Organizacin de datos en un archivo
void colisiones(Libro lib)
{
Libro libar;
int pos = desplazamiento(principal);
int j = principal;
int encontrado;
fseek(fh, pos, SEEK_SET); /* se sita en rea de sinnimos */
encontrado = 0;
while ((j < total) && !encontrado)
{
fread(&libar, sizeof(Libro), 1, fh);
j++;
if (strcmp(libar.codigo ,"*") == 0) /* libre */
{
encontrado = 1;
fseek(fh, sizeof(Libro), SEEK_CUR);
fwrite(&lib, sizeof(Libro), 1, fh);
puts("Datos grabados en el rea de sinnimos.");
}
}
if (!encontrado) puts("rea de sinnimos completa. ");
fflush(fh);
}
/*
Bsqueda en rea de sinnimos: bsqueda secuencial, por cdigo, de un registro.
Devuelve la posicin que ocupa, o bien 1 si no se encuentra.
*/
int indexSinonimo (const char c [ ])
{
Libro libar;
int pos = desplazamiento(principal);
int j = principal;
int encontrado;
fseek(fh, pos, SEEK_SET); /* se sita en rea de sinnimos */
encontrado = 0;
while ((j < total) && !encontrado)
{
fread(&libar, sizeof(Libro), 1, fh);
j++;
if (strcmp(libar.codigo, c) == 0)
encontrado = 1;
}
if (!encontrado) j = 1;
return j;
}
/*
Aritmtica modular: transforma cadena a un entero en el rango [0, principal).
En primer lugar pasa los caracteres del cdigo a maysculas. A continuacin, llama
a la funcin que convierte la cadena a entero largo. Por ltimo, aplica el mdulo
respecto a principal. El mdulo produce un entero de 0 a principal1.
*/
int hash(char c [ ])
{
int i, suma = 0;
for (i = 0; i < strlen(c); i++)
c[i] = toupper(c[i]);
return transformaClave(c) % principal;
}
947 Archivos secuenciales indexados
long transformaClave(const char* clave)
{
int j;
long d;
d = 0;
for (j = 0; j < strlen(clave); j+
{
d = d * 27 + clave[j];
}
/*
Si d supera el mximo entero largo, genera nmero negativo
*/
if (d < 0) d = d;
return d;
}
void escribir(Libro lib)
{
printf("Cdigo: %s\t", lib.codigo);
printf("Autor: %s\t", lib.autor);
printf("Ttulo: %s\n" ,lib.titulo);
}
Archivos secuenciales indexados
Para buscar el telfono de una persona en la gua no se busca secuencialmente desde los nombres cuya
ini cial es "a" hasta la "z", sino que se abre la gua por la letra inicial del nombre. Si se desea buscar
"zala bastro", se abre la gua por la letra "z" y se busca la cabecera de pgina hasta encontrar la
pgina ms prxima al nombre, buscando a continuacin nombre a nombre hasta encontrar "zala-
bastro". La gua es un ejemplo tpico de archivo secuencial indexado con dos niveles de ndices, el
nivel superior para las letras iniciales y el nivel menor para las cabeceras de pgina. Por consiguiente,
cada archivo secuencial indexa do consta de un archivo de ndices y un archivo de datos. La figura 31.1
muestra un archivo con organi zacin secuencial indexada.
Figura 31.1 Organizacin secuencial indexada.
rea de
ndices
clave direccin
15 010
24 020
36 030
54 040
240 070
rea
principal
clave datos
010
011
019 15
020
029 24
030
070
079 240
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
948 Captulo 31 Organizacin de datos en un archivo
Partes de un archivo secuencial indexado
Para que un archivo pueda organizarse en forma secuencial indexada el tipo de los registros debe con-
tener un campo clave identificador. La clave se asocia con la direccin (posicin) del registro de datos
en el archi vo principal.
Un archivo con organizacin secuencial indexada consta de las siguientes partes:
rea de datos. Contiene los registros de datos en forma secuencial, sin dejar huecos intercalados.
rea de ndices. Es una tabla que contiene la clave identificativa y la direccin de almacena-
miento. Puede haber ndices enlazados.
El rea de ndices normalmente est en memoria principal para aumentar la eficiencia en los tiem-
pos de acceso. Ahora bien, debe existir un archivo donde guardar los ndices para posteriores explo-
taciones del archivo de datos. Entonces, al disear un archivo indexado hay que pensar que se
manejarn dos tipos de archivos, el de datos y el de ndices, con sus respectivos registros.
Por ejemplo, si se quieren grabar los atletas federados en un archivo secuencial indexado, el cam-
po ndi ce que se puede elegir es el nombre del atleta (tambin se puede elegir el nmero de carnet de
federado). Habra que declarar dos tipos de registros:
typedef struct
{
int edad;
char carnet[15];
char club[29];
char nombre[41];
char sexo;
char categoria[21];
char direccion[71];
} Atleta;
typedef struct
{
char nombre[41];
long posicion;
} Indice;
Proceso de un archivo secuencial indexado
Al disear un archivo secuencial indexado, lo primero que hay que decidir es cul va a ser el campo
clave. Los registros han de ser grabados en orden secuencial, y simultneamente a la grabacin de los
registros, el sistema crea los ndices en orden secuencial ascendente del contenido del campo clave.
A continuacin se desarrollan las operaciones (altas, bajas, consultas...) para un archivo con esta
orga nizacin. Tambin es necesario considerar el inicio y la salida de la aplicacin que procesa un ar-
chivo inde xado, para cargar y descargar, respectivamente, la tabla de ndices.
Los registros tratados se corresponden con artculos de un supermercado. Los campos de cada
regis tro son: nombre del artculo, identificador, precio, unidades. Un campo clave adecuado para este
tipo de registro es el nombre del artculo.
Tipos de datos
Se declaran dos tipos de estructura para representar el registro de datos y el de ndice respectivamente:
typedef struct
{
char nombre[21];
char identificador[14];
double precio;
int unidades;
int estado;
} Articulo;
typedef struct
{

A recordar
Los archivos secuenciales indexados presentan
dos ventajas importantes: rpido acceso, y que
la gestin de archivos se encarga de relacionar
la posicin de cada registro con su contenido
mediante la tabla de ndices. El principal incon-
veniente es el espacio adicional para guardar el
rea de ndices.
949 Archivos secuenciales indexados
char nombre[21];
long posicion;
} Indice;
Por razones de eficiencia, la tabla de ndices est en la memoria principal. Se pueden seguir dos
alterna tivas: definir un array de tipo Indice con un nmero de elementos que cubra el mximo pre-
visto, o crear una tabla dinmica que se ample, cada vez que se llene, en N elementos. La tabla din-
mica tiene la ventaja de ajustarse al nmero de artculos y la desventaja de que aumenta el tiempo de
proceso ya que cuando la tabla se llena hay que pedir memoria extra para N nuevos elementos.
La definicin de la tabla con el mximo de elementos:
#define MXAR 200
Indice tabla[MXAR];
La definicin de la tabla dinmica:
#define N 15
Indice *tab = (Indice*)malloc(N*sizeof(Indice));
Creacin
El archivo de datos y el archivo de ndices, inicialmente, se abren en modo wb. La cuenta de registros
se pone a cero. Es importante verificar que se est inicializando la aplicacin del archivo indexado;
los datos de un archivo que se abre en modo w se pierden.
FILE *fix;
FILE *findices;
int numReg = 0;
fix = fopen("mitienda.dat", "wb+");
findice = fopen("inxtienda.dat", "wb");
Altas
Para aadir registros al archivo de datos, ste se abre (puede ser que previamente se haya abierto) en
modo lec tura/escritura, se realizarn operaciones de lectura para comprobar datos. El proceso sigue
estos pasos:
1. Leer el campo clave y el resto de campos del artculo.
2. Comprobar si existe, o no, en la tabla de ndices. Se hace una bsqueda binaria de la clave en
la tabla.
3. Si existe en la tabla, se lee el registro del archivo de datos segn la direccin que se obtiene de
la tabla. Puede ocurrir que el artculo, previamente, se hubiera dado de baja, o bien que se quie-
ra reescribir; en cualquier caso se deja elegir al usuario la accin que desee.
4. Si no existe, se graba en el siguiente registro vaco del archivo de datos. A continuacin, se in-
serta ordenadamente en la tabla de ndices el nuevo campo clave junto a su direccin en el ar-
chivo de datos.
A continuacin se escriben las funciones necesarias para realizar esta operacin.
/*
Bsqueda binaria de clave. Devuelve la posicin; 1 si no est.
*/
int posicionTabla(Indice *tab, int n, const char* cl)
{
int bajo, alto, central;
bajo = 0;
alto = n 1; /* Nmero de claves en la tabla */
while (bajo <= alto)
{
central = (bajo + alto)/2;
if (strcmp(cl, tab[central].nombre) == 0)
return central; /* encontrado, devuelve posicin */
else if (strcmp(cl, tab[central].nombre) < 0)
950 Captulo 31 Organizacin de datos en un archivo
alto = central 1; /* ir a sublista inferior */
else
bajo = central + 1; /* ir a sublista superior */
}
return 1;
}
/*
Insercin ordenada. Inserta, manteniendo el orden ascendente, una nueva clave y
la direccin del registro.
*/
void inserOrden(Indice *tab, int *totreg, Indice nv)
{
int i, j;
int parada;
parada = 0;
/* busqueda de posicin de insercin, es decir,
el elemento mayor ms inmediato */
i = 0;
while (!parada && (i < *totreg))
{
if (strcmp(nv.nombre , tab[i].nombre) < 0)
parada = 1;
else
i++;
}
(*totreg)++;
if (parada)
{
/* mueve elementos desde posicin de insercin hasta el final*/
for (j = *totreg 1; j >= i+1; j)
{
strcpy(tab[j].nombre, tab[ j1].nombre);
tab[j].posicion = tab[ j1].posicion;
}
}
/* se inserta el ndice en el "hueco" */
strcpy(tab[i].nombre, nv.nombre);
tab[i].posicion = nv.posicion;
}
void leeReg(Articulo* at)
{
printf(" Nombre del artculo: ");
gets (at > nombre);
printf(" Identificador: ");
gets(at > identificador);
printf(" Precio: ");
scanf("%lf", &(at > precio));
printf(" Unidades: ");
scanf("%d%*c", &(at > unidades));
}
#define desplazamiento(n) ((n 1) * sizeof(Articulo))
void Altas(Indice* tab, int* nreg)
{
Articulo nuevo;
int p;
if (fix == NULL) fix = fopen("mitienda.dat", "rb+");
leeReg(&nuevo);
nuevo.estado = 1; /* estado del registro: alta */
/* busca si est en la tabla de ndices */
p = posicionTabla(tab, *nreg, nuevo.nombre);
if (p == 1) /* registro nuevo, se graba en el archivo */
{
int dsp;
951 Archivos secuenciales indexados
Indice nv;
nv.posicion = dsp = desplazamiento(*nreg +1);
strcpy(nv.nombre, nuevo.nombre);
inserOrden(tab, nreg, nv); /* inserta e incrementa el nmero
de registros */
fseek(fix, dsp, SEEK_SET);
fwrite(&nuevo, sizeof(Articulo), 1, fix);
}
else
puts("Registro ya dado de alta");
}
Bajas
Para dar de baja un registro (en el ejemplo, un artculo) del archivo de datos, simplemente se marca el
cam po estado a cero que indica borrado, y se elimina la entrada de la tabla de ndices. El archivo de
datos esta r abierto en modo lectura/escritura. El proceso sigue estos pasos:
1. Leer el campo clave del registro a dar de baja.
2. Comprobar si existe, o no, en la tabla de ndices (bsqueda binaria).
3. Si existe en la tabla, se lee el registro del archivo de datos segn la direccin que se obtiene de
la tabla para confirmar la accin.
4. Si el usuario confirma la accin, se escribe el registro en el archivo con la marca estado a cero.
Ade ms, en la tabla de ndices se elimina la entrada del campo clave.
La funcin de bsqueda ya est escrita, a continuacin se escriben el resto de funciones que per-
miten dar de baja a un registro.
/*
Elimina entrada. Borra una entrada de la tabla de ndices moviendo a la
izquierda los ndices, a partir del ndice p que se da de baja.
*/
void quitaTabla(Indice *tab, int *totreg, int p)
{
int j;
for (j = p; j < *totreg 1; j++)
{
strcpy(tab[j].nombre, tab[j+1].nombre);
tab[j].posicion = tab[j+1].posicion;
}
(*totreg) ;
}
void escribeReg(Articulo a)
{
printf("Artculo: ");
printf("%s, %s, precio: %.1f\n", a.nombre, a.identificador, a.precio);
}
void Bajas(Indice* tab, int* nreg)
{
Articulo q;
char nomArt[21] ;
int p;
if (fix == NULL) fix = fopen("mitienda.dat", "rb+");
printf("Nombre del artculo: ");
gets(nomArt);
p = posicionTabla(tab, *nreg, nomArt);
if (p != 1) /* encontrado en la tabla */
{
char r;
fseek(fix, tab[p].posicion, SEEK_SET);
fread(&q, sizeof(Articulo), 1, fix);
escribeReg(q);
952 Captulo 31 Organizacin de datos en un archivo
printf("Pulsa S para confirmar la baja: ");
scanf("%c%*c", &r);
if (toupper(r) == 'S')
{
q.estado = 0;
fseek(fix, sizeof(Articulo), SEEK_CUR);
fwrite(&q, sizeof(Articulo), 1, fix);
quitaTabla(tab, nreg, p);
}
else
puts("Accin cancelada. ");
}
else
puts("No se encuentra un registro con ese cdigo.");
}
Consultas
La consulta de un registro (un artculo) sigue los pasos:
1. Leer el campo clave (en el desarrollo, el nombre del artculo) del registro que se desea con-
sultar.
2. Buscar en la tabla de ndices si existe o no (bsqueda binaria).
3. Si existe, se lee el registro del archivo de datos segn la direccin que se obtiene de la tabla
para mostrar el registro en pantalla.
La operacin modificar, tpica de archivos, sigue los mismos pasos que los expuestos anterior-
mente. Se debe aadir el paso de escribir el registro que se ha ledo, con el campo modificado. A con-
tinuacin se escribe la funcin consulta ( ) que implementa la operacin:
void Consulta(Indice* tab, int nreg)
{
Articulo q;
char nomArt[21];
int p;
if (fix == NULL) fix = fopen("mitienda.dat", "rb+");
printf("Nombre del artculo: ");
gets(nomArt);
p = posicionTabla(tab, nreg, nomArt);
if (p != 1) /* encontrado en la tabla */
{
fseek(fix, tab[p].posicion, SEEK_SET);
fread(&q, sizeof(Articulo), 1, fix);
escribeReg(q);
}
else
puts("No se encuentra un registro con ese cdigo.");
}
Salida del proceso
Una vez realizadas las operaciones con el archivo de datos, se da por terminada la aplicacin cerrando
el archi vo de datos y, muy importante, guardando la tabla de ndices en su archivo. El primer registro
de este archi vo contiene el nmero de elementos de la tabla (nreg), los siguientes registros son los nreg
elementos de la tabla. La funcin grabaIndice ( ) implementa estas acciones:
void grabaIndice(Indice *tab, int nreg)
{
int i;
findices = fopen("inxtienda.dat", "wb");
fwrite(&nreg, sizeof(int), 1, findices);
for (i = 0; i < nreg; i++)
953 Archivos secuenciales indexados
fwrite(tab++, sizeof(Indice), 1, findices);
fclose (findices);
}
Inicio del proceso
La primera vez que se ejecuta la aplicacin se crea el archivo de datos y el de ndices. Cada vez que
se pro duce un alta se graba un registro y a la vez se inserta una entrada en la tabla. Cuando se d por
terminada la ejecucin se grabar la tabla en el archivo de ndices llamando a grabaIndice ( ).
Nuevas ejecuciones han de leer el archivo de ndices y escribirlos en la tabla (memoria principal). El
primer registro del archi vo contiene el nmero de entradas, el resto del archivo son los ndices. Como
se grabaron en orden del cam po clave, tambin se leen en orden y entonces la tabla de ndices queda
ordenada.
void recuperaIndice(Indice *tab, int *nreg) {
int i;
findices = fopen("inxtienda.dat", "rb");
fread(nreg, sizeof (int), 1, f indices);
for (i = 0; i < *nreg; i++)
fread(tab++, sizeof (Indice), 1, findices);
fclose(findices);
}
Nota de programacin
No es necesario que el primer registro del archivo de ndices sea el nmero de entradas de la
tabla (nreg) si se cambia el bucle for por un bucle mientras no fin de fichero y se cuentan
los registros ledos. Se ha optado por grabar el nmero de registros porque de utilizar una ta-
bla de ndices din mica se conoce el nmero de entradas y se puede reservar memoria para
ese nmero de ndices.
Ejercicio 31.4
Escribir la funcin principal para gestionar el archivo secuencial indexado de artculos de un super
mer cado. Los campos que describen cada artculo son: nombre del artculo, identificador, precio,
unidades.
nicamente se escribe la codificacin de la funcin main ( ) con un sencillo men para que el
usuario elija la operacin que quiere realizar. El archivo articulo.h contiene la declaracin de las
estructuras Articulo e Indice, la macro desplazamiento y la declaracin de los punteros a
FILE, fix y findices. Los prototipos de las funciones desarrolladas en el anterior apartado se en-
cuentran en el archivo indexado.h; la implentacin de las funciones est en el archivo
indexado.c
#include <stdlib.h>
#include <stdio.h>
#include "articulo.h"
#include "indexado.h"
#define MXAR 200
Indice tabla[MXAR];
void escribeTabla(Indice *tab, int nreg);
void main ( )
{
int numReg;
char opcion;
954 Captulo 31 Organizacin de datos en un archivo
Ordenacin de archivos: ordenacin externa
Los algoritmos de ordenacin estudiados hasta ahora utilizan arrays para contener los elementos a
orde nar, por lo que es necesario que la memoria interna tenga capacidad suficiente. Para ordenar se-
cuencias grandes de elementos que se encuentran en soporte externo (posiblemente no pueden alma-
cenarse en memo ria interna) se aplican los algoritmos de ordenacin externa.
El tratamiento de archivos secuenciales exige que stos se encuentren ordenados respecto a un
campo del registro, denominado campo clave K. El archivo est ordenado respecto a la clave si:
i < j K( i ) < K( j )
Los distintos algoritmos de ordenacin externa utilizan el esquema general de separacin y fusin
o mezcla. Por separacin se entiende la distribucin de secuencias de registros ordenados en varios
if ((f ix = fopen("mitienda.dat", "rb+")) == NULL)
{
fix = fopen("mitienda.dat", "wb+");
findices = fopen("inxtienda.dat", "wb");
numReg = 0;
}
else /* archivos ya existen. Se vuelcan los ndices a tabla */
recuperaIndice (tabla, &numReg);
escribeTabla(tabla, numReg);
do
{
puts("1. Alta ");
puts("2. Baja ");
puts("3. Consulta ");
puts("5. Salir ");
do {
printf("Elige una opcin ");
scanf("%c%*c", &opcion);
} while (opcion < '1' || opcion > '5' || opcion == '4 ');
switch (opcion)
{
case '1':
Altas(tabla, &numReg); break;
case '2':
Bajas(tabla, &numReg); break;
case '3':
Consulta(tabla, numReg); break;
case '5':
grabaIndice (tabla, numReg); break;
}
} while (opcion != '5');
if (fix != NULL) fclose(fix);
}
void escribeTabla(Indice *tab, int nreg)
{
int i = 0;
if (nreg > 0)
{
puts("Tabla de ndices actual");
for (;i < nreg; i++)
printf ("%s %d\n",tabla[i].nombre,tabla[i].posicion);
system("Pause");
system("cls");
}
}
955 Ordenacin de archivos: ordenacin externa
archivos; por fusin la mezcla de dos o ms secuencias ordenadas en
una nica secuencia ordenada. Variaciones de este esquema general dan
lugar a diferentes algoritmos de ordenacin externa.
Fusin de archivos
La fusin (mezcla) consiste en juntar en un archivo los registros de dos
o ms archivos ordenados respecto a un campo clave. El archivo resul-
tante tambin estar ordenado. F1 y F2, archivos ordenados; F3, archivo
generado por mezcla.
F1 12 24 36 37 40 52
F2 3 8 9 20
Para realizar la fusin es preciso acceder a los archivos F1 y F2, en cada operacin slo se lee un
ele mento del archivo dado. Es necesario una variable de trabajo por cada archivo (actual1, ac-
tual2) para representar el elemento actual de cada archivo.
Se comparan las claves actual1 y actual2 y se sita la ms pequea 3 (actual2) en el ar-
chivo de salida (F3). A continuacin, se avanza un elemento el archivo F2 y se realiza una nueva com-
paracin de los elementos situados en las variables actual.
actual1
F1 12 24 36 40 52
F2 3 8 20

actual2
F3 3
La nueva comparacin sita la clave ms pequea 8 (actual2) en F3. Se avanza un elemento
(20) el archivo F2 y se realiza una nueva comparacin. Ahora la clave ms pequea es 12 (ac-
tual1) que se sita en F3. A continuacin, se avanza un elemento el archivo F1 y se vuelven a com-
parar las claves actuales.
actual1
F1 12 24 36 40 52
F2 3 8 20
actual2
F3 3 8 12
Cuando uno u otro archivo de entrada ha terminado, se copia el resto del archivo sobre el archivo
de salida. El resultado final ser:
F3 3 8 12 24 36 40 52
La codificacin correspondiente de fusin de archivos (se supone que el campo clave es de tipo
int) es:
void fusion(FILE* f1, FILE* f2, FILE* f3)
{
Registro actual1, actual2;
fread(&actual1, sizeof(Registro), 1, f1);
fread(&actual2, sizeof(Registro), 1, f2);
while (!feof(f1) && !feof(f2))
{

A recordar
El tiempo de un algoritmo de ordenacin de
registros de un archivo, ordenacin externa,
depen de notablemente del dispositivo de
almacenamiento. Los algoritmos utilizan el
esquema general de separacin y mezcla, y
consideran slo el tratamiento secuencial.
956 Captulo 31 Organizacin de datos en un archivo
Registro d;
if (actual1.clave < actual2.clave)
{
d = actual1;
fread(&actual1, sizeof(Registro), 1, f1);
}
else
{
d = actual2;
fread(&actual2, sizeof(Registro), 1, f2);
}
fwrite(&d, sizeof(Registro), 1, f3);
}
/*
Lectura terminada de f1 o f2. Se escriben los registros no procesados
*/
while (!feof(f 1))
{
fwrite(&actual1, sizeof(Registro), 1, f3);
fread(&actual1, sizeof(Registro), 1, f1);
}
while (!feof(f2))
{
fwrite(&actual2, sizeof(Registro), 1, f3);
fread(&actual2, sizeof(Registro), 1, f1);
}
fclose (f3);fclose(f1);fclose(f2);
}
Antes de llamar a la funcin se han de abrir los archivos:
f3 = fopen("fileDestino", "wb");
f1 = fopen("fileOrigen1", "rb");
f2 = fopen("fileOrigen2", "rb");
if (f1 == NULL || f2 == NULL || f3 == NULL)
{
puts("Error en los archivos.");
exit (1);
}
Por ltimo, la llamada a la funcin:
fusion(f1, f2, f3);
Clasificacin por mezcla directa
El mtodo de ordenacin externa ms fcil de comprender es el denominado mezcla directa. Utiliza
el esquema iterativo de separacin y mezcla. Se manejan tres archivos: el archivo original y dos ar-
chivos auxiliares.
El proceso consiste en:
1. Separar los registros individuales del archivo original O en dos archivos F1 y F2.
2. Mezclar los archivos F1 y F2 combinando registros individuales (segn sus claves) y forman-
do pares ordenados que son escritos en el archivo O.
3. Separar pares de registros del archivo original O en dos archivos F1 y F2.
4. Mezclar F1 y F2 combinando pares de registros y formando cudruplos ordenados que son
escri tos en el archivo O.
Cada separacin (particin) y mezcla duplica la longitud de las secuencias ordenadas. La primera
pasa da (separacin + mezcla) se hace con secuencias de longitud 1 y la mezcla produce secuencias
de longi tud 2; la segunda pasada produce secuencias de longitud 4. Cada pasada duplica la longitud
de las secuen cias; en la pasada n la longitud ser 2
n
. El algoritmo termina cuando la longitud de la se-
cuencia supera el nmero de registros del archivo a ordenar.
957 Codificacin del algoritmo mezcla directa
Codificacin del algoritmo mezcla directa
La implementacin del mtodo se basa, fundamentalmente, en dos rutinas: distribuir ( ) y mez-
clar ( ). La primera separa secuencias de registros del archivo original en los dos archivos auxilia-
res. La segunda mezcla secuencias de los dos archivos auxiliares y la escribe en el archivo original.
Las pasadas que da el algoritmo son iteraciones de un bucle mientras longitud_secuencia menor nu-
mero_registros; cada itera cin consiste en llamar a distribuir ( ) y mezclar ( ).
El nmero de registros del archivo se determina dividiendo posicin_ fin _archivo por tamao_re-
gistro:
int numeroReg(FILE* pf)
{
Ejemplo 31.2
Un archivo est formado por registros que tienen un campo clave de tipo entero. Suponiendo
que las cla ves del archivo son:
34 23 12 59 73 44 8 19 28 51
Se van a realizar los pasos que sigue el algoritmo de mezcla directa para ordenar el archivo.
Se con sidera el archivo O como el original, F1 y F2 archivos auxiliares.
Pasada 1
Separacin:
F1: 34 12 73 8 28
F2: 23 59 44 19 51
Mezcla formando duplos ordenados:
O: 23 34 12 59 44 73 8 19 28 51
Pasada 2
Separacin:
F1: 23 34 44 73 28 51
F2: 12 59 8 19
Mezcla formando cudruplos ordenados:
O: 12 23 34 59 8 19 44 73 28 51
Pasada 3
Separacin:
F1: 12 23 34 59 28 51
F2: 8 19 44 73
Mezcla formando ctuplos ordenados:
O: 8 12 19 23 34 44 59 73 28 51
Pasada 4
Separacin:
F1: 8 12 19 23 34 44 59 73
F2: 28 51
Mezcla con la que ya se obtiene el archivo ordenado:
O: 8 12 19 23 28 34 44 51 59 73
958 Captulo 31 Organizacin de datos en un archivo
if (pf != NULL)
{
fpos_t fin;
fseek(pf, 0L, SEEK_END);
fgetpos(pf,&fin);
return fin/sizeof (Registro);
}
else
return 0;
}
La implementacin que se escribe a continuacin supone que los registros se ordenan de acuerdo
con un campo clave de tipo int:
typedef int TipoClave;
typedef struct
{
TipoClave clave;
} Registro;
void mezclaDirecta(FILE *f)
{
int longSec;
int numReg;
FILE *f1 = NULL, *f2 = NULL;
f = fopen("fileorg","rb");
numReg = numeroReg(f);
longSec = 1;
while (longSec < numReg)
{
distribuir(f, f1, f2, longSec, numReg);
mezclar(f1, f2, f, &longSec, numReg);
}
}
void distribuir(FILE* f, FILE* f1, FILE* f2, int lonSec, int numReg)
{
int numSec, resto, i;
numSec = numReg/(2*lonSec);
resto = numReg%(2*lonSec);
f = fopen("fileorg","rb");
f1 = fopen("fileAux1","wb");
f2 = fopen("fileAux2","wb");
for (i = 1; i <= numSec; i++)
{
subSecuencia(f, f1, lonSec);
subSecuencia(f, f2, lonSec);
}
/*
Se procesa el resto de registros del archivo
*/
if (resto > lonSec)
resto = lonSec;
else
{
lonSec = resto;
resto = 0;
}
subSecuencia(f, f1, lonSec);
subSecuencia(f, f2, resto);
fclose(f1); fclose(f2); fclose(f);
}
void subSecuencia(FILE* f, FILE* t, int longSec)
{
Registro r;
int j;
959 Codificacin del algoritmo mezcla directa
for (j = 1; j <= longSec; j++)
{
fread(&r, sizeof(Registro), 1, f);
fwrite(&r, sizeof(Registro), 1, t);
}
}
void mezclar(FILE* f1, FILE* f2, FILE* f, int* lonSec, int numReg)
{
int numSec, resto, s, i, j, k, n1, n2;
Registro r1, r2;
numSec = numReg/(2*(*lonSec)); /* nmero de subsecuencias */
resto = numReg%(2*(*lonSec));
f = fopen("fileorg","wb");
f1 = fopen("fileAux1","rb");
f2 = fopen("fileAux2","rb");
fread(&r1, sizeof(Registro), 1, f1);
fread(&r2, sizeof(Registro), 1, f2);
for (s = 1; s <= numSec+1; s++)
{
n1 = n2 = (*lonSec);
if (s == numSec+1)
{ /* proceso de los registros de la subsecuencia incompleta */
if (resto > (*lonSec))
n2 = resto (*lonSec);
else
{
n1 = resto;
n2 = 0;
}
}
i = j = 1;
while (i <= n1 && j <= n2)
{
Registro d;
if (r1.clave < r2.clave)
{
d = r1;
fread(&r1, sizeof(Registro), 1, f1);
i++;
}
else
{
d = r2;
fread(&r2, sizeof(Registro), 1, f2);
j++;
}
fwrite(&d, sizeof(Registro), 1, f);
}
/*
Los registros no procesados se escriben directamente
*/
for (k = i; k <= n1; kII)
{
fwrite(&r1, sizeof(Registro), 1, f);
fread(&r1, sizeof(Registro), 1, f1);
}
for (k = j; k <= n2; kII)
{
fwrite(&r2, sizeof(Registro), 1, f);
fread(&r2, sizeof(Registro), 1, f2);
}
}
(*lonSec) *= 2;
fclose (f);fclose(f1);fclose(f2);
}
960 Captulo 31 Organizacin de datos en un archivo
Resumen
Un archivo de datos es un conjunto de datos relacionados entre s y almacenados en memoria ex-
terna. Estos datos se encuentran estructurados en una coleccin de entida des denominadas regis-
tros. La organizacin de archivos define la forma en la que los registros se disponen sobre el
soporte de almacenamiento y puede ser secuencial, directa o secuencial indexada.
La organizacin secuencial sita los registros unos al lado de otros en el orden en el que van sien-
do introdu cidos. Para efectuar el acceso a un determinado registro es necesario pasar por los que
le preceden.
En la organizacin directa el orden fsico de los regis tros puede no corresponderse con aquel en
el que han sido introducidos y el acceso a un determinado registro no obli ga a pasar por los que
le preceden. La funcin de librera fseek ( ) permite situarse directamente en un registro de-
terminado y es la ms utilizada para procesar este tipo de archivos.
Los archivos hash son archivos de organizacin direc ta, con la particularidad de que el ndice de
un registro se obtiene transformando un campo del registro (campo cla ve) en un entero pertene-
ciente a un rango de valores pre determinados. Una funcin hash realiza la transforma cin. Es
necesario establecer una estrategia para tratar colisiones.
La organizacin secuencial indexada requiere la exis tencia de un rea de datos y un rea de n-
dices. El rea de datos est siempre en memoria externa, es el archivo con los registros. Cada re-
gistro se corresponde unvocamen te con un ndice; ste consta de dos elementos: clave del
registro y posicin del registro en el dispositivo externo. El rea de ndices, normalmente, est en
memoria inter na formando una tabla de ndices.
La ordenacin de archivos se denomina ordenacin externa y requiere algoritmos apropiados.
Una manera tri vial de realizar la ordenacin de un archivo secuencial consiste en copiar los re-
gistros a otro archivo de acceso directo, o bien secuencial indexado, usando como clave el campo
por el que se desea ordenar.
Si se desea realizar la ordenacin de archivos, utili zando solamente como estructura de almace-
namiento auxiliar otros archivos secuenciales de formato similar al que se desea ordenar, hay que
trabajar usando el esque ma de separacin y mezcla.
En el caso del algoritmo de mezcla simple se opera con tres archivos anlogos: el original y dos
archivos auxi liares. El proceso consiste en recorrer el archivo original y copiar secuencias de su-
cesivos registros en, alternati vamente, cada uno de los archivos auxiliares. A conti nuacin se
mezclan las secuencias de los archivos y se copia la secuencia resultante en el archivo original.
El proceso contina, de tal forma que en cada pasada la lon gitud de la secuencia es el doble de
la longitud de la pasa da anterior. Todo empieza con secuencias de longitud 1, y termina cuando
se alcanza una secuencia de longitud igual al nmero de registros.
Ejercicios
31.1. Un archivo secuencial contiene registros con un campo clave de tipo entero en el rango de 0 a
777. Escribir la funcin volcado( ), que genere un archi vo directo de tal forma que el nmero
de registro coincida con el campo clave.
31.2. El archivo secuencial F almacena registros con un campo clave de tipo entero. Supngase que
la secuencia de claves que se encuentra en el archi vo es la siguiente:
14 27 33 5 8 11 23 44 22 31 46 7 8 11 1 99 23 40 6 11 14 17
Aplicando el algoritmo de mezcla directa, rea lizar la ordenacin del archivo y determinar el
nmero de pasadas necesarias.
961 Problemas
31.3. Los registros de un archivo secuencial indexado tienen un campo, estado, para conocer si el
regis tro est dado de baja. Escribir una funcin para compactar el archivo de datos, de tal for-
ma que se eliminen fsicamente los registros dados de baja.
31.4. Realizar los cambios necesarios en la funcin fusin ( ) que mezcla dos archivo secuen-
ciales ordenados ascendentemente respecto al campo fecha de nacimiento. La fecha de naci-
miento est formada por los campos de tipo int: mes, da y ao.
31.5. Escribir una funcin que distribuya los registros de un archivo no ordenado, F, en otros dos F1
y F2, con la siguiente estrategia: leer M (por ejem plo, 31) registros a la vez del archivo, orde-
narlos utilizando un mtodo de ordenacin interna y a continuacin escribirlos, alternativa-
mente, en F1 y F2.
31.6. Modificar la implementacin de la mezcla direc ta de tal forma que inicialmente se distribuya
el fichero origen en secuencias de M registros ordenados, segn se explica en el ejercicio 31.5.
Y a partir de esa distribucin, repetir los pasos del algo ritmo mezcla directa: fusin de M-
uplas para dar lugar a 2M registros ordenados, separacin...
31.7. Un archivo secuencial F contiene registros y quiere ser ordenado utilizando 4 archivos auxi-
liares. Suponiendo que la ordenacin se desea hacer respecto a un campo de tipo entero, con
estos valores:
22 11 3 4 11 55 2 98 11 21 4 3 8 12 41 21 42 58 26 19 11 59 37 28 61 72 47
aplicar el esquema seguido en el algoritmo de mezcla directa (teniendo en cuenta que se uti-
lizan 4 archivos en vez de 2) y obtener el nmero de pasadas necesarias para su ordenacin.
31.8. Un archivo est ordenado alfabticamente respecto de un campo clave que es una cadena de
caracte res. Disear un algoritmo e implementarlo para que la ordenacin sea en sentido in-
verso.
31.9. Un archivo secuencial no ordenado se quiere dis tribuir en dos ficheros F1 y F2 siguiendo estos
pasos:
1. Leer n registros del archivo origen y ponerlos en una lista secuencial. Marcar cada registro
de la lista con un estatus, por ejemplo activo = 1.
2. Obtener el registro t con clave ms pequea de los que tienen el estatus activo y escribirlo
en el archivo destino F1.
3. Sustituir el registro t por el siguiente registro del archivo origen. Si el nuevo registro es me-
nor que t, se marca como inactivo, es decir activo = 0; en caso contrario se marca activo.
Si hay registros en la lista activos, volver al paso 2.
4. Cambiar el fichero destino. Si el anterior es F1, ahora ser F2 y viceversa. Activar todos
los registros de la lista y volver al paso 2.
Problemas
31.1. Los registros que representan a los objetos de una perfumera se van a guardar en un archivo
hash. Se prevn como mximo 1024 registros. El cam po clave es una cadena de caracteres,
cuya mxi ma longitud es 10. Con este supuesto codificar la funcin de dispersin y mostrar
10 direcciones dispersas.
31.2. Disear un algoritmo e implementar un programa que permita crear un archivo secuencial
PERFU MES cuyos registros constan de los siguientes cam pos:
962 Captulo 31 Organizacin de datos en un archivo
Nombre
Descripcin
Precio
Cdigo
Creador
31.3. Realizar un programa que copie el archivo secuen cial del ejercicio anterior en un archivo hash
PERME_DIR; el campo clave es el cdigo del per fume que tiene como mximo 10 caracteres
alfa numricos.
31.4. Disear un algoritmo e implementar un programa para crear un archivo secuencial indexado
denominado DIRECTORIO, que contenga los datos de los habitantes de una poblacin que ac-
tualmente est formada por 5590 personas. El campo clave es el nmero de DNI.
31.5. Escribir un programa que liste todas las personas del archivo indexado DIRECTORIO que pue-
den votar.
31.6. Realizar un programa que copie los registros de personas con edad ente 18 y 31 aos, del ar-
chivo DIRECTORIO del ejercicio 31.4, en un archivo secuencial JOVENES.
31.7. Se desea ordenar alfabticamente el archivo JOVENES (ejercicio 31.6). Aplicar el mtodo mez
cla directa.
31.8. Dado un archivo hash, disear un algoritmo e implementar el cdigo para compactar el archi-
vo despus de dar de baja un registro. Es decir, un registro del rea de sinnimos se mueve al
rea principal si el registro del rea principal, con el que colision el registro del rea de sin-
nimos, fue dado de baja.
Listas, pilas
y colas en C
Captulo
Introduccin
En este captulo se estudian estructuras de datos dinmicas. Al contrario que las estructuras de datos
estticas (arrays listas, vectores y tablas y estructuras) en las que el tamao en memoria se esta-
blece durante la compilacin y permanece inalterable durante la ejecucin del programa, las estructu-
ras de datos dinmicas crecen y se contraen a medida que se ejecuta el programa.
Una lista enlazada (ligada o encadenada, linked list) es una coleccin de elementos (denominados
nodos) dispuestos uno a continuacin de otro, cada uno de ellos conectado al siguiente elemento por
un enlace o referencia. En el captulo se desarrollan algoritmos para insertar, buscar y borrar ele-
mentos en las listas enlazadas.
32
Contenido
Listas enlazadas
Clasificacin de listas enlazadas
Operaciones en listas enlazadas
Insercin de un elemento en una lista
Bsqueda de un elemento de una lista
Borrado de un nodo en una lista
Concepto de pila
El tipo pila implementado con arreglos
El tipo pila implementado como una lista enlazada
Concepto de cola
El tipo cola implementado con arreglos ( arrays) circulares
El tipo cola implementado con una lista enlazada
Resumen
Ejercicios
Problemas
964 Captulo 32 Listas, pilas y colas en C
Una pila es una estructura de datos que almacena y recupera sus elementos atendiendo a un estric-
to orden. Las pilas se conocen tambin como estructuras LIFO (Last-in, first-out, ltimo en entrar-
primero en salir), todas las inserciones y retirada de elementos se realizan por un mismo extremo
denominado cima de la pila. Las pilas se utilizan frecuentemente en programas y en la vida diaria.
Las colas se conocen como estructuras FIFO (First-in, First-out, primero en entrar-primero en sa-
lir), debido a la forma y orden de insercin y de extraccin de elementos de la cola. Las colas tienen
numerosas aplicaciones en el mundo de la computacin: colas de mensajes, colas de tareas a realizar
por una impresora, colas de prioridades.
Conceptos clave
Asignacin de memoria Puntero nulo
Eliminar un nodo en una lista enlazada Punteros
Estructura FIFO Recorrido de una lista
Lista enlazada Referencia
Memoria libre Variables puntero
Listas enlazadas
Una lista enlazada es una coleccin o secuencia de elementos dispuestos uno detrs de otro, en la que
cada elemento se conecta al siguiente por un enlace o puntero. La idea bsica consiste en cons-
truir una lista cuyos elementos, llamados nodos, se componen de dos partes o campos: la primera
contiene la informacin y es, por consiguiente, un valor de un tipo genrico (denominado Dato, Tipo-
Elemento, Info, etc.) y la segunda parte o campo es un puntero (denominado enlace o sgte) que apun-
ta al siguiente elemento de la lista.
Figura 32.1 Lista enlazada (representacin simple).
Nodo Nodo Nodo
puntero puntero
La representacin grfica ms extendida es aquella que utiliza una caja (un rectngulo) con dos
secciones en su interior. En la primera seccin se escribe el elemento o valor del dato, y en la segun-
da, el enlace o puntero mediante una flecha que sale de la caja y apunta al nodo siguiente.
...
e
1
e
2
e
3
e
n
e
1
, e
2
, ... e
n
son valores del tipo TipoElemento
Figura 32.2 Lista enlazada (representacin grfica tpica).
Estructura de una lista
Una lista enlazada consta de un nmero de elementos y cada elemento tiene dos componen-
tes (campos), un puntero al siguiente elemento de la lista y un valor, que puede ser de cual-
quier tipo.
Los enlaces se representan por flechas para facilitar la comprensin de la conexin entre dos
nodos; ello indica que el enlace tiene la direccin en memoria del siguiente nodo. En la figura 32.2
965 Clasificacin de las listas enlazadas
los nodos forman una secuencia desde el primer elemento (e
1
) al ltimo elemento (e
n
). El primer nodo
se enlaza al segundo nodo; ste se enlaza al tercero y as sucesivamente hasta llegar al ltimo nodo.
El ltimo nodo se debe representar de forma diferente para significar que ste no se enlaza a ningn
otro. La figura 32.3 muestra distintas representaciones grficas que se utilizan para dibujar el campo
enlace del ltimo nodo.
Clasificacin de las listas enlazadas
Las listas se pueden dividir en cuatro categoras:
Listas simplemente enlazadas . Cada nodo (elemento) contiene un nico enlace que conecta ese
nodo al nodo siguiente o nodo sucesor. La lista es eficiente en recorridos directos (adelante).
Listas doblemente enlazadas . Cada nodo contiene dos enlaces, uno a su nodo predecesor y el
otro a su nodo sucesor. La lista es eficiente tanto en recorrido directo (adelante) como en re-
corrido inverso (atrs).
Lista circular simplemente enlazada . Una lista enlazada simplemente en la que el ltimo ele-
mento (cola) se enlaza al primer elemento (cabeza) de tal modo que la lista puede ser recorrida
de modo circular (en anillo).
Lista circular doblemente enlazada . Una lista doblemente enlazada en la que el ltimo elemento
se enlaza al primer elemento y viceversa. Esta lista se puede recorrer de modo circular (en ani-
llo) tanto en direccin directa (adelante) como inversa (atrs).
Por cada uno de estos cuatro tipos de estructuras de listas, se puede elegir una implementacin
basada en arreglos o una implementacin basada en punteros. Estas implementaciones difieren en el
modo en que asigna la memoria para los datos de los elementos, cmo se enlazan juntos los elemen-
tos y cmo se accede a dichos elementos. De forma ms especfica, segn la forma de reservar memo-
ria las implementaciones pueden hacerse con:
Asignacin fija, o esttica, de memoria mediante arreglos.
Asignacin dinmica de memoria mediante punteros.
Dado que la asignacin fija de memoria mediante arrays es ms ineficiente, se utilizar en este
captulo la asignacin de memoria mediante punteros, dejando como ejercicio al lector la implemen-
tacin mediante arreglos.
Conceptos bsicos sobre listas
Una lista enlazada consta de un conjunto de nodos. Un nodo consta de un campo dato y un
puntero que apunta al siguiente elemento de la lista.
Figura 32.3 Diferentes representaciones grficas del nodo ltimo.
e
n
e
n
e
n
NULL
Figura 32.4 Representacin grfica de una lista enlazada.
...
dato siguiente
...
dato siguiente dato siguiente dato
cabeza ptr actual cola
966 Captulo 32 Listas, pilas y colas en C
El primer nodo, frente, es el nodo apuntado por cabeza. La lista encadena nodos juntos desde el
frente al final (cola) de la lista. El final se identifica como el nodo cuyo campo puntero tiene el valor
NULL = 0. La lista se recorre desde el primero al ltimo nodo; en cualquier punto del recorrido la
posicin actual se referencia por el puntero ptrActual. En el caso en que la lista no contiene ningn
nodo (est vaca), el puntero cabeza es nulo.
Operaciones en listas enlazadas
Una lista enlazada requiere realizar la gestin de los elementos contenidos en ella. Estos controles se
manifiestan en forma de las operaciones bsicas, especificadas al definir el tipo abstracto lista, que
tendrn las siguientes funciones:
Declarar los tipos nodo y puntero a nodo .
Inicializar o crear .
Insertar elementos en una lista
Eliminar elementos de una lista
Buscar elementos de una lista (comprobar la existencia de elementos en una lista)
Recorrer una lista enlazada (visitar cada nodo de la lista)
Comprobar si la lista est vaca
Declaracin de un nodo
Cada nodo de una lista enlazada combina dos partes: un tipo de dato (entero, real, doble, carcter o tipo
predefinido) y un enlace (puntero) al siguiente nodo. En C se puede declarar un nuevo tipo de dato,
correspondiente a un nodo, mediante la especificacin de struct que agrupa las dos partes citadas.
struct Nodo typedef struct Nodo
{ {
int dato; int dato;
struct Nodo* enlace; struct Nodo *enlace;
}; } NODO;
La declaracin utiliza el tipo struct que permite agrupar campos de diferentes tipos, el campo
dato y el campo enlace. Con typedef se puede declarar a la vez un nuevo identificador de struct
Nodo; en el caso anterior se ha elegido NODO.
Dado que los tipos de datos que se puede incluir en una lista pueden ser de cualquier tipo (enteros,
dobles, caracteres o incluso cadenas), con el objeto de que el tipo de dato de cada nodo se pueda cam-
biar con facilidad se suele utilizar la sentencia typedef para declarar el nombre de elemento como
un sinnimo del tipo de dato de cada campo. A continuacin se muestra la declaracin:
typedef double elemento;
struct Nodo
{
elemento dato;
struct nodo *enlace;
};
Entonces, si se necesita cambiar el tipo de elemento en los nodos, slo se tendr que cambiar la
sentencia de declaracin de tipos que afecta a elemento. Siempre que una funcin necesite referirse
al tipo de dato del nodo, puede utilizar el nombre elemento.
Figura 32.5 Representacin de lista vaca con cabeza = null.
cabeza
NULL
967 Operaciones en listas enlazadas
Apuntador (puntero) de cabecera y cola
A una lista enlazada se accede a travs de uno o ms punteros a los nodos. El acceso ms frecuente a
una lista enlazada es a travs del primer nodo de la lista que se llama cabeza o cabecera de la lista, o
simplemente L. En ocasiones, se mantiene tambin un puntero al ltimo nodo de una lista enlazada,
es el apuntador (puntero) cola.
Cada puntero a un nodo debe ser declarado como una variable puntero. Por ejemplo, si se mantiene
una lista enlazada con un puntero de cabecera y otro de cola, se deben declarar dos variables puntero:
struct nodo *ptr _cabeza;
struct nodo *ptr_cola;
El tipo struct nodo a veces se simplifica utilizando la declaracin typedef. As se puede es-
cribir:
typedef struct nodo Nodo;
typedef struct nodo* ptrNodo;
ptrNodo ptr_cabeza;
ptrNodo ptr_cola;
Ejemplo 32.1
En este ejemplo se declara un tipo denominado Punto; representa un punto en el plano con su
coordenada x y y. Tambin se declara el tipo Nodo con el campo dato del tipo Punto. Por lti-
mo, se define un puntero a Nodo.
#include <stdlib.h>
typedef struct punto
{
float x, y;
} Punto;
typedef struct nodo
{
PUNTO dato;
struct nodo* enlace;
} Nodo;
Nodo* cabecera;
cabecera = NULL;
23.5 40.7 21.7
Declaracin del nodo Definicin de punteros
typedef double elemento; struct nodo *ptr_cabeza;
struct nodo
{
elemento dato; struct nodo *ptr_cola;
struct nodo *enlace;
};
Figura 32.6 Declaraciones de tipos en lista enlazada.
ptr_cabeza
ptr_cola
968 Captulo 32 Listas, pilas y colas en C
Acceso a una lista
La construccin y manipulacin de una lista enlazada requiere el acceso a los nodos de la lis-
ta a travs de uno o ms punteros a nodos. Normalmente, un programa incluye un puntero al
primer nodo (cabeza) y un puntero al ltimo nodo (cola).
El apuntador nulo
La figura 32.7 muestra una lista con un apuntador cabeza y un apuntador nulo al final de la lista so-
bre el que se ha escrito la palabra NULL. La palabra NULL representa el apuntador nulo, que es una
constante especial de C. Se puede utilizar el apuntador nulo para cualquier valor de apuntador que no
apunte a ningn sitio. El apuntador nulo se utiliza, normalmente, en dos situaciones:
Usar el apuntador nulo en el campo enlace o siguiente del nodo final de una lista enlazada.
Cuando una lista enlazada no tiene ningn nodo, se utiliza el apuntador NULL como apuntador
de cabeza y de cola. Tal lista se denomina lista vaca.
En un programa, el apuntador nulo se puede escribir como NULL, que es una constante de la bi-
blioteca estndar stdlib.h.
1
El apuntador nulo se puede asignar a una variable apuntador con una
sentencia de asignacin ordinaria. Por ejemplo:
ptrNodo *ptr_cabeza;
ptr_cabeza = NULL ; /* incluir archivo stdlib.h */
Construccin de una lista
Una primera operacin que se realiza con una lista es asignar un valor inicial, que es el de lista vaca.
Sencillamente consiste en asignar nulo al puntero de acceso a la lista:
Nodo* cabeza;
cabeza = NULL;
Para aadir elementos a la lista se utiliza la operacin insertar ( ), con sus diferentes versiones.
Tambin, para la creacin de una lista enlazada, se utiliza el siguiente algoritmo:
Paso 1. Declarar el tipo de dato y el puntero de cabeza o primero.
Paso 2. Asignar memoria para un elemento del tipo definido anteriormente utilizando alguna de
las funciones de asignacin de memoria (malloc ( ), calloc ( ), realloc ( ) )
y un cast para la conversin de void* al tipo puntero a nodo.
1
A veces algunos programadores escriben el puntero nulo como 0, pero pensamos que es un estilo ms claro escribirlo como
NULL.
Figura 32.7 Apuntador NULL.
4.15 5.25 71.5
10.5 NULL
ptr_cabeza

A recordar
El apuntador de cabeza y de cola en una lista enlazada puede ser NULL, lo que indicar que la lista est vaca (no tiene
nodos). ste suele ser un mtodo usual para construir una lista. Cualquier funcin que se escribe para manipular listas enla-
zadas debe poder manejar un apuntador de cabeza y un apuntador de cola nulos.
969 Operaciones en listas enlazadas
Paso 3. Crear iterativamente el primer elemento (cabeza) y los elementos sucesivos de la lista
enlazada simplemente.
Paso 4. Repetir hasta que no haya ms entrada para el elemento.
Ejemplo 32.2
Crear una lista enlazada de elementos que almacenen datos de tipo entero.
Un elemento de la lista se puede definir con la ayuda de la estructura siguiente:
typedef elemento int;
struct nodo
{
elemento dato;
struct nodo * siguiente;
};
typedef struct nodo Nodo;
En la estructura nodo hay dos miembros, dato y siguiente. Este ltimo es un puntero al
siguiente nodo, y dato contiene el valor del elemento de la lista. Tambin se declara un nuevo
tipo: Nodo que es sinnimo de struct nodo.
El siguiente paso para construir la lista es declarar la variable primero que apuntar al pri-
mer elemento de la lista:
Nodo *primero = NULL
A continuacin, se crea un elemento de la lista, para lo cual hay que reservar memoria, tanta co-
mo tamao tenga cada nodo, y asignar la direccin de la memoria reservada al puntero primero:
primero = (Nodo*)malloc (sizeof (Nodo) );
Con el operador sizeof se obtiene el tamao de cada nodo de la lista, la funcin malloc
( ) devuelve un puntero genrico (void*), por lo que se convierte a Nodo*. Ahora se puede
asignar un valor al campo dato:
primero>dato = 11;
primero>siguiente = NULL;
11 NULL
primero
La operacin de crear un nodo se puede hacer en una funcin a la que se pasa el valor del
campo dato y del campo siguiente. La funcin devuelve un puntero al nodo creado:
Nodo* crearNodo (elemento x, Nodo* enlace)
{
Nodo *p;
p = (Nodo*)malloc (sizeof (Nodo) );
p>dato = x;
p>siguiente = enlace;
return p;
}
La llamada a la funcin crearNodo ( ) para crear el primer nodo de la lista:
primero = crearNodo (11, NULL);
Si ahora se desea aadir un nuevo elemento con un valor 6, y situarlo en el primer lugar de la
lista, se escribe simplemente:
970 Captulo 32 Listas, pilas y colas en C
Insercin de un elemento en una lista
El algoritmo empleado para aadir o insertar un elemento en una lista enlazada vara dependiendo de
la posicin en que se desea insertar el elemento. La posicin de insercin puede ser:
En la cabeza (elemento primero) de la lista.
En el final de la lista (elemento ltimo).
Antes de un elemento especificado, o bien
Despus de un elemento especificado.
Insertar un nuevo elemento en la cabeza de una lista
El proceso de insercin se puede resumir en este algoritmo:
1. Asignar un nuevo nodo apuntado por nuevo que es una variable puntero local que apunta al
nuevo nodo que se va a insertar en la lista.
2. Situar el nuevo elemento en el campo dato del nuevo nodo.
3. Hacer que el campo enlace siguiente del nuevo nodo apunte a la cabeza (primer nodo) de la
lista original.
4. Hacer que cabeza (puntero cabeza) apunte al nuevo nodo que se ha creado.
primero = crearNodo (6,primero);
Por ltimo, para obtener una lista compuesta de 4, 6, 11, se habra de ejecutar
primero = crearNodo (4,primero);
11 4 6
11 NULL 6
Ejemplo 32.3
Una lista enlazada contiene tres elementos: 10, 25 y 40. Insertar un nuevo elemento, 4, en cabe-
za de la lista
Pasos 1 y 2
nuevo
cabeza
40 NULL 10 25
4
40 NULL 10 25
971 Insercin de un elemento en una lista
Cdigo C
Nodo* nuevo;
nuevo = (Nodo*)malloc (sizeof (Nodo) ); /* asigna un nuevo nodo */
nuevo > dato = entrada;
Paso 3
El campo enlace (siguiente) del nuevo nodo apunta a la cabeza actual de la lista.
Cdigo C
nuevo > siguiente = cabeza;
Paso 4
Se cambia el puntero de cabeza para apuntar al nuevo nodo creado; es decir, el puntero de ca-
beza apunta al mismo sitio que apunte nuevo.
Cdigo C
cabeza = nuevo;
En este momento, la funcin de insertar un elemento en la lista termina su ejecucin, la va-
riable local nuevo desaparece y slo permanece el puntero de cabeza cabeza que apunta a la
nueva lista enlazada.
El cdigo fuente de la funcin inserPrimero ( ):
void inserPrimero (Nodo** cabeza, Item entrada)
{
Nodo *nuevo ;
nuevo = (Nodo*)malloc (sizeof (Nodo) );
nuevo > dato = entrada; /* pone elemento en nuevo */
nuevo > siguiente = *cabeza; /* enlaza con el primer nodo */
*cabeza = nuevo;
}
40 NULL 10 25
nuevo
cabeza
40 NULL 10 25
nuevo
4
40 NULL 10 25 4
cabeza
4
cabeza
972 Captulo 32 Listas, pilas y colas en C
Ejercicio 32.1
Se va a formar una lista enlazada de nmeros aleatorios. El programa que realiza esta tarea inserta
los nuevos nodos por la cabeza de la lista. Una vez creada la lista, se recorren los nodos para mostrar
los nmeros pares.
La funcin inserPrimero ( ) aade un nodo a la lista, siempre como nodo cabeza. El primer ar-
gumento es un puntero a puntero porque tiene que modificar la variable cabeza, que es a su vez un
puntero a Nodo. La funcin crearNodo ( ) reserva memoria para un nodo, asigna el campo dato y
devuelve la direccin del nodo creado.
La funcin main ( ) primero inicializa el puntero cabeza a NULL, que es la forma de indicar lista
vaca. En un bucle se generan nmeros aleatorios; cada nmero generado se inserta como nuevo nodo
en la lista, invocando a la funcin inserPrimero ( ); el bucle termina cuando se genera el 0. Una
vez terminado el bucle de creacin de la lista, otro bucle realiza tantas iteraciones como nodos tiene
la lista, y en cada pasada escribe el campo dato si es par.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MX 3131
typedef int Item;
typedef struct Elemento
{
Item dato;
struct Elemento* siguiente;
} Nodo;
void inserPrimero (Nodo** cabeza, Item entrada);
Nodo* crearNodo (Item x);
void main ( )
{
Item d;
Nodo *cabeza, *ptr;
int k;
cabeza = NULL; /* lista vaca */
randomize ( );
/* El bucle termina cuando se genera el nmero aleatorio 0 */
for (d = random (MX); d; )
{
inserPrimero (&cabeza, d);
d = random (MX);
} /* recorre la lista para escribir los pares */
for (k = 0, ptr = cabeza; ptr; )
{
if (ptr > dato%2 == 0)
{
printf ("%d ", ptr > dato);
k++;
printf ("%c", (k%12 ? ' ' : '\n') ); /* 12 datos por lnea */
}
ptr = ptr > siguiente;
}
printf ("\n\n");
}
void inserPrimero (Nodo** cabeza, Item entrada)
{
Nodo *nuevo ;
nuevo = crearNodo (entrada);
nuevo > siguiente = *cabeza;
973 Insercin de un elemento en una lista
Insercin de un nodo al final de la lista
La insercin al final de la lista es menos eficiente debido a que, normalmente, no se tiene un puntero
al ltimo elemento de la lista y entonces se ha de seguir la traza desde la cabeza de la lista hasta el l-
timo nodo de la lista y a continuacin realizar la insercin. Cuando ultimo es puntero que apunta al
ltimo nodo de la lista, las sentencias siguientes insertan un nodo al final de la lista:
ultimo > siguiente = crearNodo (x);
ultimo > siguiente > siguiente = NULL;
ultimo = ultimo > siguiente;
La primera sentencia crea un nuevo nodo, llamada a crearNodo ( ), y se asigna al campo si-
guiente del ltimo nodo de la lista (antes de la insercin) de modo que el nuevo nodo ahora es el
ltimo. La segunda sentencia establece el campo siguiente del nuevo ltimo nodo a NULL. Y la l-
tima sentencia pone la variable ultimo al nuevo ltimo nodo de la lista.
La funcin inserFinal ( ) tiene como entrada el puntero cabeza, recorre la lista hasta situar-
se al final y realiza la insercin. Se tiene en cuenta la circunstancia de que la lista est vaca, en cuyo
caso inserta el nuevo nodo como nodo primero y nico.
void inserFinal (Nodo** cabeza, Item entrada)
{
Nodo *ultimo;
ultimo = *cabeza;
if (ultimo == NULL) /* lista vaca */
{
*cabeza = crearNodo (entrada);
}
else
{
for (; ultimo > siguiente; ) /* termina con ultimo
referenciando al nodo final */
ultimo = ultimo > siguiente;
ultimo > siguiente = crearNodo (entrada);
}
}
Insercin de un nodo entre dos nodos de la lista
La insercin de un nuevo nodo no siempre se realiza al principio (en cabeza) de la lista o al final; se
puede insertar entre dos nodos cualesquiera de la lista. Por ejemplo, se tiene una lista enlazada con los
nodos 10, 25, 40 en la que se quiere insertar un nuevo elemento 75 entre el elemento 25 y el elemen-
to 40. La figura 32.8 representa la lista y el nuevo nodo a insertar.
El algoritmo de la nueva operacin insertar requiere las siguientes etapas:
1. Asignar el nuevo nodo, con el campo dato, apuntado por el puntero nuevo.
2. Hacer que el campo enlace siguiente del nuevo nodo apunte al nodo que va despus de la
posicin que se desea para el nuevo nodo (o bien a NULL si no hay ningn nodo despus de
la nueva posicin).
*cabeza = nuevo;
}
Nodo* crearNodo (Item x)
{
Nodo *a ;
a = (Nodo*)malloc (sizeof (Nodo) ); /* asigna nuevo nodo */
a > dato = x;
a > siguiente = NULL;
return a;
}
974 Captulo 32 Listas, pilas y colas en C
3. En la variable puntero anterior tener la direccin del nodo que est antes de la posicin de-
seada para el nuevo nodo. Hacer que anterior > siguiente apunte al nuevo nodo que se
acaba de crear.
Etapa 1
Se crea un nuevo nodo que contiene a 75
Cdigo C
nuevo = crearNodo (entrada);
Etapa 2
Cdigo C
nuevo > siguiente = anterior > siguiente
Etapa 3
40 NULL 10 25
Figura 32.8 Insercin entre dos nodos.
75
40 NULL 10 25
cabeza
anterior
no se utiliza
40 NULL 10 25
nuevo
anterior
75
40 NULL 10 25
nuevo
anterior
75
nuevo
75
975 Bsqueda de un elemento de una lista
Despus de ejecutar todas las sentencias de las sucesivas etapas, la nueva lista comenzara en el
nodo 10, seguira 25, 75 y por ltimo 40.
Cdigo C
void insertar (Nodo* anterior, Item entrada)
{
Nodo *nuevo;
nuevo = crearNodo (entrada);
nuevo > siguiente = anterior > siguiente;
anterior > siguiente = nuevo;
}
Para llamar a la funcin insertar ( ), previamente se ha de buscar la direccin del nodo ante-
rior y asegurarse de que la lista no est vaca ni que se quiera insertar como primer nodo.
Una versin de la funcin tiene como argumentos la direccin de inicio de la lista, cabeza, el
campo dato a partir del cual se inserta y el dato del nuevo nodo. El algoritmo de esta versin de la ope-
racin insertar requiere las siguientes etapas:
1. Asignar el nuevo nodo, con el campo dato, apuntado por el puntero nuevo.
2. Buscar la direccin del nodo, despues, que contiene el campo dato a partir del cual se ha de
insertar.
3. Hacer que el campo enlace siguiente del nuevo nodo apunte al nodo que va a continuacin
de la posicin que se desea para el nuevo nodo (o bien a NULL si no hay ningn nodo).
4. Hacer que despues > siguiente apunte al nuevo nodo que se acaba de crear.
La primera comprobacin, previa a las etapas a seguir, es que la lista est vaca. Si es as, se inser-
ta como primer nodo. Tambin se tiene que tener precaucin en la bsqueda que se realiza en la etapa
2; sta puede ser negativa, no se encuentra el dato; entonces no se inserta el nuevo nodo.
Cdigo C
void insertar (Nodo** cabeza, Item testigo, Item entrada)
{
Nodo *nuevo, *despues;
nuevo = crearNodo (entrada);
if (*cabeza == NULL)
*cabeza = nuevo;
else
{
int esta = 0;
despues = *cabeza; /* etapa de bsqueda */
while ( (despues != NULL) && !esta)
{
if (despues > dato != testigo)
despues = despues > siguiente;
else
esta = 1;
}
if (esta)
{
nuevo > siguiente = despues > siguiente;
despues > siguiente = nuevo;
}
}
}
Bsqueda de un elemento de una lista
El algoritmo que sirva para localizar un elemento en una lista enlazada puede devolver un puntero a
ese elemento, o bien, un valor lgico que indique su existencia.
976 Captulo 32 Listas, pilas y colas en C
La funcin localizar ( ) utiliza una variable puntero denominada indice que va recorriendo
la lista nodo a nodo. Mediante un bucle, indice apunta a los nodos de la lista de modo que si se en-
cuentra el nodo buscado devuelve un puntero al nodo con la sentencia de retorno (return); en el caso
de no encontrarse el nodo buscado, la funcin debe devolver NULL (return NULL). El nodo que se
busca es el que tiene un campo dato que coincide con una clave.
Cdigo C
Nodo* localizar (Nodo* cabeza, Item destino)
/* cabeza: puntero de cabeza de una lista enlazada.
destino: dato que se busca en la lista.
*/
{
Nodo *indice;
for (indice = cabeza; indice != NULL; indice = indice > siguiente)
if (destino == indice > dato)
return indice;
return NULL;
}
A tener en cuenta
La operacin de bsqueda puede tener diferentes enfoques: bsqueda de la posicin del pri-
mer nodo que contiene un dato, bsqueda de la posicin de un nodo segn el nmero de or-
den, determinacin si existe o no un nodo con un campo dato determinado.
Ejemplo 32.4
En este ejemplo se escribe una funcin para encontrar la direccin de un nodo dado el orden
que ocupa en una lista enlazada.
El nodo o elemento se especifica por su nmero de orden en la lista; para ello se considera
posicin 1 la correspondiente al nodo de cabeza; posicin 2 la correspondiente al siguiente
nodo, y as sucesivamente.
El algoritmo de bsqueda del elemento comienza con el recorrido de la lista mediante un pun-
tero indice que comienza apuntando al nodo cabeza de la lista. Un bucle mueve el indice ha-
cia adelante el nmero correcto de sitios (lugares). A cada iteracin del bucle se mueve el
puntero indice un nodo hacia adelante. El bucle termina cuando se alcanza la posicin deseada
e indice apunta al nodo correcto. El bucle tambin puede terminar si indice apunta a NULL, lo
que indicar que la posicin solicitada era ms grande que el nmero de nodos de la lista.
Nodo* buscarPosicion (Nodo *cabeza, size_t posicion)
/* El programa que llame a esta funcin ha de incluir
biblioteca stdlib.h (para implementar tipo size_t)
*/
{
Nodo *indice;
size_t i;
if (posicion < 1) /* posicin ha de ser mayor que 1 */
return NULL;
indice = cabeza;
for (i = 1 ; (i < posicin) && (indice != NULL) ; i++)
indice = indice > siguiente;
return indice;
}
977 Borrado de un nodo en una lista
Borrado de un nodo en una lista
La operacin de eliminar un nodo de una lista enlazada implica enlazar el nodo anterior con el nodo
siguiente al que se desea eliminar y liberar la memoria que ocupa.
El algoritmo para eliminar un nodo que contiene un dato se puede expresar en estos pasos o etapas:
1. Bsqueda del nodo que contiene el dato. Se ha de tener la direccin del nodo a eliminar y la
direccin del anterior.
2. El puntero siguiente del nodo anterior ha de apuntar al siguiente del nodo a eliminar.
3. En caso de que el nodo a eliminar sea el primero, cabeza, se modifica cabeza para que tenga
la direccin del nodo siguiente.
4. Por ltimo, se libera la memoria ocupada por el nodo.
A continuacin se escribe una funcin que recibe la direccin de inicio, cabeza, de la lista y el
dato del nodo que se quiere borrar.
Cdigo C
void eliminar (Nodo** cabeza, Item entrada)
{
Nodo* actual, *anterior;
int encontrado = 0;
actual = *cabeza; anterior = NULL;
/* bsqueda del nodo y del anterior */
while ( (actual!=NULL) && (!encontrado) )
{
encontrado = (actual > dato == entrada);
if (!encontrado)
{
anterior = actual;
actual = actual > siguiente;
}
}
/* enlace de nodo anterior con siguiente */
if (actual != NULL)
{
/* distingue entre que el nodo sea el cabecera o del
resto de la lista */
if (actual == *cabeza)
*cabeza = actual > siguiente;
else
anterior > siguiente = actual > siguiente;
free (actual);
}
}
Ejercicio 32.2
Se crea una lista enlazada de nmeros enteros ordenada. La lista va estar organizada de tal forma que
el nodo cabecera tenga el menor elemento, y as en orden creciente los dems nodos. Una vez creada
la lista, se puede recorrer para escribir los datos por pantalla; tambin se ha de poder eliminar un
nodo con un dato determinado.
Inicialmente la lista se crea con el primer valor. El segundo elemento se ha de insertar antes del pri-
mero o despus, dependiendo de que sea menor o mayor. As en general, para insertar un nuevo ele-
mento, primero se busca la posicin de insercin en la lista actual, que en todo momento est
ordenada, del nodo a partir del cual se ha de enlazar el nuevo nodo para que la lista siga ordenada. Las
978 Captulo 32 Listas, pilas y colas en C
diversas formas de realizar la insercin de un nodo en una lista estudiadas en apartados anteriores no
se ajustan exactamente a la funcionalidad requerida; por ello se escribe la funcin insertaOrden (
) para aadir los nuevos elementos en el orden requerido. Los datos con los que se va a formar la lis-
ta se generan aleatoriamente.
La funcin recorrer ( ) avanza por cada uno de los nodos de la lista con la finalidad de escribir
el campo dato. Para eliminar nodos, se invoca a la funcin eliminar ( ) escrita antes con una pe-
quea modificacin, escribir un literal en el caso de que el nodo no est en la lista.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MX 101
typedef int Item;
typedef struct Elemento
{
Item dato;
struct Elemento* siguiente;
} Nodo;
void insertaOrden (Nodo** cabeza, Item entrada);
Nodo* crearNodo (Item x);
void recorrer (Nodo* cabeza);
void eliminar (Nodo** cabeza, Item entrada);
void main ( )
{
Item d;
Nodo* cabeza;
cabeza = NULL; /* lista vaca */
randomize ( );
/* bucle termina cuando se genera el nmero aleatorio 0 */
for (d = random (MX); d; )
{
insertaOrden (&cabeza,d);
d = random (MX);
}
recorrer (cabeza);
printf ("\n Elemento a eliminar: ");
scanf ("%d", &d);
eliminar (&cabeza,d);
recorrer (cabeza);
}
void insertaOrden (Nodo** cabeza, Item entrada)
{
Nodo *nuevo;
nuevo = crearNodo (entrada);
if (*cabeza == NULL)
*cabeza = nuevo;
else if (entrada < (*cabeza) > dato) /* primer nodo */
{
nuevo > siguiente = *cabeza;
*cabeza = nuevo;
}
else /* bsqueda del nodo anterior a partir del que se debe insertar */
{
Nodo* anterior, *p;
anterior = p = *cabeza;
979 Borrado de un nodo en una lista
while ( (p > siguiente != NULL) && (entrada > p > dato) )
{
anterior = p;
p = p > siguiente;
}
if (entrada > p > dato) /* inserta por el final */
anterior = p;
/* Se procede al enlace del nuevo nodo */
nuevo > siguiente = anterior > siguiente;
anterior > siguiente = nuevo;
}
}
Nodo* crearNodo (Item x)
{
Nodo *a ;
a = (Nodo*)malloc (sizeof(Nodo) );
a > dato = x;
a > siguiente = NULL;
return a;
}
void recorrer (Nodo* cabeza)
{
int k;
printf ("\n\t\t Lista Ordenada \n");
for (k = 0; cabeza; cabeza = cabeza > siguiente)
{
printf ("%d ",cabeza > dato);
k++;
printf ("%c",(k%15 ?' ':'\n') );
}
printf ("\n\n");
}
void eliminar (Nodo** cabeza, Item entrada)
{
Nodo* actual, *anterior;
int encontrado = 0;
actual = *cabeza; anterior = NULL;
while ( (actual!=NULL) && (!encontrado) )
{
encontrado = (actual > dato == entrada);
if (!encontrado)
{
anterior = actual;
actual = actual > siguiente;
}
}
if (actual != NULL)
{
if (actual == *cabeza)
*cabeza = actual > siguiente;
else
anterior > siguiente = actual > siguiente;
free (actual);
}
else
puts ("Nodo no eliminado, elemento no esta en la lista ");
}
980 Captulo 32 Listas, pilas y colas en C
Concepto de pila
Una pila (stack) es una coleccin ordenada de elementos a los que slo se puede acceder por un nico
lugar o extremo de la pila. Los elementos de la pila se aaden o quitan (borran) de la misma slo por
la parte superior (cima) de la pila. ste es el caso de una pila de platos,
una pila de libros, la organizacin de la memoria libre, etctera.
Cuando se dice que la pila est ordenada, lo que se quiere decir es
que hay un elemento al que se puede acceder primero (el que est enci-
ma de la pila), otro elemento al que se puede acceder en segundo lugar
(justo el elemento que est debajo de la cima), un tercero, etc. No se re-
quiere que las entradas se puedan comparar utilizando el operador me-
nor que (<) y pueden ser de cualquier tipo.
Las entradas de la pila deben ser eliminadas en el orden inverso al
que se situaron en la misma. Por ejemplo, se puede crear una pila de libros, situando primero un dic-
cionario, encima de l una enciclopedia y encima de ambos una novela de modo que la pila tendr la
novela en la parte superior.
Cuando se quitan los libros de la pila, primero debe quitarse la
novela, luego la enciclopedia y por ltimo el diccionario.
Debido a su propiedad especfica ltimo en entrar, primero en
salir se conoce a las pilas como estructura de datos LIFO (last-in,
first-out)
Las operaciones usuales en la pila son insertar y quitar. La ope-
racin insertar o poner (push) aade un elemento en la cima de la
pila y la operacin quitar (pop) elimina o saca un elemento de la
pila. La figura 32.10 muestra una secuencia de operaciones insertar
y quitar.

A recordar
Una pila es una estructura de datos de entradas
ordenadas tales que slo se pueden introducir y
eliminar por un extremo, llamado cima.
Figura 32.9 Pila de libros.
Novela
Enciclopedia
Diccionario
Entrada : MAC Salida : CAM
Figura 32.10 Poner y quitar elementos de la pila.
Poner M Poner A Poner C Quitar C Quitar A Quitar M
C
A
M
A
M
A
M M M
Figura 32.11 Operaciones bsicas de una pila.
Insertar
Cima
Quitar
Fondo
981 El tipo pila implementado con arreglos (arrays)
Una pila puede estar vaca (no tiene elementos) o llena (en el caso de tener tamao fijo, si no ca-
ben ms elementos en la pila). Si un programa intenta sacar un elemento de una pila vaca, se produ-
cir un error, debido a que esa operacin es imposible; esta situacin se denomina desbordamiento
negativo (underflow). Por el contrario, si un programa intenta poner un elemento en una pila llena se
produce un error llamado desbordamiento (overflow) o rebosamiento. Para evitar estas situaciones
se disean funciones que comprueban si la pila est llena o vaca.
Operaciones en una pila
Las operaciones que sirven para definir una pila y poder manipular su contenido son las siguientes:
CrearPila Inicia
Insertar (push) Pone un dato en la pila
Quitar (pop) Retira (saca) un dato de la pila
Pila vaca Comprueba si la pila no tiene elementos
Pila llena Comprueba si la pila est llena de elementos
Limpiar pila Quita todos sus elementos y deja la pila vaca
Cima Obtiene el elemento cima de la pila
Tamao de la pila Nmero de elementos mximo que puede contener la pila
El tipo pila implementado con arreglos (arrays)
Una pila se puede implementar mediante arreglos (arrays) o mediante listas enlazadas. Una imple-
mentacin esttica se realiza utilizando un array de tamao fijo y una implementacin dinmica me-
diante una lista enlazada.
El tipo pila implementado con arreglos incluye una lista (arreglo) y un ndice a la cima de la pila;
adems una constante con el mximo nmero de elementos. Al utilizar un arreglo para contener los
elementos de la pila es necesario tener en cuenta que el tamao de la pila no puede exceder el nmero
de elementos del arreglo, y la condicin pila llena ser significativa para el diseo.
El mtodo usual de introducir elementos en una pila es definir el fondo de la pila en la posicin
1 y sin ningn elemento en su interior, es decir, definir una pila vaca; a continuacin, se van intro-
duciendo elementos en la pila (el arreglo) de modo que el primer elemento aadido se introduce en
una pila vaca y en la posicin 0, el segundo elemento en la posicin 1, el siguiente en la posicin 2 y
as sucesivamente. Con estas operaciones el ndice que apunta a la cima de la pila se va incrementan-
do en 1 cada vez que se aade un nuevo elemento. Los algoritmos de poner insertar (push) y quitar
sacar (pop) datos de la pila:
Insertar (push)
1. Verificar si la pila no est llena.
2. Incrementar en 1 el puntero ndice de la pila.
3. Almacenar elemento en la posicin del puntero de la pila.
Quitar (pop)
1. Si la pila no est vaca.
2. Leer el elemento de la posicin del puntero de la pila.
3. Decrementar en 1 el puntero de la pila.
En el caso de que el arreglo que define la pila tenga TAMPILA elementos, las posiciones del arre-
glo, es decir el ndice o puntero de la pila, estarn comprendidas en el rango 0 a TAMPILA1 elemen-
tos, de modo que en una pila llena el puntero de la pila apunta a TAMPILA1 y en una pila vaca el
puntero de la pila apunta a 1, ya que 0 ser el ndice del primer elemento.
Ejemplo 32.5
Una pila de 7 elementos se puede representar grficamente as:
982 Captulo 32 Listas, pilas y colas en C
Declaracin del tipo pila
Para que la declaracin sea lo ms abstracta posible, en un archivo .h se declara el tipo de los datos
que contiene la pila y los prototipos de las funciones que representan las operaciones del tipo abstrac-
to pila:
/* archivo pilaarray.h */
#include <stdio.h>
#include <stdlib.h>
typedef int TipoDato; /* Tipo de los elementos de la pila */
#define TAMPILA 100
typedef struct
{
TipoDato listaPila[TAMPILA];
int cima;
} Pila;
/* Operaciones sobre la pila */
void crearPila (Pila* pila);
void insertar (Pila* pila, TipoDato elemento);
TipoDato quitar (Pila* pila);
void limpiarPila (Pila* pila);
/* Operacin de acceso a pila */
TipoDato cima (Pila pila);
/* Verificacin estado de la pila */
int pilaVacia (Pila pila);
int pilaLlena (Pila pila);
El tipo de los datos que contiene la pila deben declararse como TipoDato. As, si se quiere una
pila de nmeros enteros:
cima
0 1 2 3 4 5 6
Pila vaca Pila llena
puntero de la pila = 1 puntero de la pila = 6
puntero de la pila
Si se almacenan los datos A, B, C,... en la pila se puede representar grficamente de alguna de
estas formas
ndice cima
A B C ...
C
B
A
cima = 2
983 El tipo pila implementado con arreglos (arrays)
typedef int TipoDato;
Si la pila fuera de nmeros complejos:
typedef struct
{
float x, y;
} complejo
typedef complejo TipoDato;
Implementacin de las operaciones sobre pilas
Las operaciones del tipo Pila definidas en la especificacin se implementan en el archivo pilaarray.c,
para despus formar un proyecto con otros mdulos y la funcin principal. La codificacin de las fun-
ciones exige incluir el archivo donde se encuentra la representacin de los elementos y los prototipos.
Archivo pilaarray.c
typedef int TipoDato; /* tipo de los elementos de la pila */
#include "pilaarray.h"
crearPila inicializa una pila sin elementos, vaca, pone el ndice cima a 1.
void crearPila (Pila* pila)
{
pila > cima = 1;
}
La operacin insertar un elemento incrementa el puntero de la pila (cima) en 1 y asigna el nue-
vo elemento a la lista de la pila. Cualquier intento de aadir un elemento en una pila llena produce un
mensaje de error Desbordamiento pila.
/* poner un elemento en la pila */
void insertar (Pila* pila, TipoDato elemento)
{
/* si la pila est llena, termina el programa */
if (pilaLlena (*pila) )
{
puts ("Desbordamiento pila");
exit (1);
}
pila > cima++;
pila > listaPila[pila>cima] = elemento;
}
La operacin quitar elimina un elemento de la pila; copia el primer valor de la cima de la pila
en una variable auxiliar, tem, y a continuacin decrementa el puntero de la pila en 1. El objetivo de la
operacin es retirar el elemento cima; no obstante, la funcin devuelve la variable tem para que pue-
da ser utilizado. Al eliminar un elemento en una pila vaca se debe producir un mensaje de error y ter-
minar.
Figura 32.12 Operacin de extraer un elemento de la pila.
Antes de quitar
Despus de quitar
elemento
se devuelve
cima cima = cima1
elemento
984 Captulo 32 Listas, pilas y colas en C
/* Quitar un elemento de la pila */
TipoDato quitar (Pila* pila)
{
TipoDato tem;
/* si la pila est vaca, termina el programa */
if (pilaVacia (*pila) )
{
puts (" Se intenta sacar un elemento en pila vaca");
exit (1);
}
tem = pila > listaPila[pila>cima];
pila > cima;
return tem;
}
La operacin cima devuelve el elemento que se encuentra en la cima de la pila, no se modifica
la pila.
TipoDato cima (Pila pila)
{
if (pilaVacia (pila) )
{
puts (" Error de ejecucin, pila vaca");
exit (1);
}
return pila.listaPila[pila.cima];
}
La funcin pilaVacia comprueba si la pila no tiene elementos; para que eso ocurra, la cima de
la pila debe ser 1.
int pilaVacia (Pila pila)
{
return pila.cima == 1;
}
La funcin pilaLlena comprueba si la pila est llena, segn el tamao del arreglo donde se
guardan sus elementos.
int pilaLlena (Pila pila)
{
return pila.cima == TAMPILA 1;
}
Por ltimo, la operacin limpiarPila vaca ntegramente la pila, para lo que pone cima al valor
que se corresponde con pila vaca (1)
void limpiarPila (Pila* pila)
{
pila > cima = 1;
}
El tipo pila implementado como una lista enlazada
La realizacin dinmica de una pila se hace almacenando los elementos como nodos de una lista en-
lazada, con la particularidad de que siempre que se quiera insertar o poner (empujar) un elemento se
hace por el mismo extremo que se extrae.
A tener en cuenta
Una pila realizada con una lista enlazada crece y decrece dinmicamente. En tiempo de eje-
cucin se reserva memoria segn se ponen elementos en la pila y se libera memoria segn se
extraen elementos de la pila.
985 El tipo pila implementado como una lista enlazada
Tipos de datos para una pila con listas enlazadas
Los elementos de la pila van a ser los nodos de la lista, con un campo para guardar el elemento y otro
de enlace; la base para todo ello es la utilizacin de variables puntero.
Las operaciones del tipo pila implementada con listas son las mismas que si la pila se implemen-
ta con arrays, salvo la operacin que controla si la pila est llena, pilaLlena, que con listas enlazadas
no se produce al poder crecer indefinidamente, con el nico lmite de la memoria. Tambin se aade
la operacin que elimina el elemento cabeza, suprimir, que se diferencia de quitar en que no devuelve
el elemento extrado.
En el archivo pila.h se declaran los tipos de datos y los prototipos de las funciones que repre-
sentan las operaciones.
/* archivo pila.h */
#include <stdlib.h>
typedef struct nodo
{
TipoDato elemento;
struct nodo* siguiente;
} Nodo;
/* prototipo de las operaciones */
void crearPila (Nodo** pila);
void insertar (Nodo** pila, TipoDato elemento);
void suprimir (Nodo** pila);
TipoDato quitar (Nodo** pila);
void limpiarPila (Nodo** pila);
TipoDato cima (Nodo* pila);
int pilaVacia (Nodo* pila);
Antes de incluir el archivo pila.h debe declararse el TipoDato segn el tipo de los elementos
que va a almacenar la pila. As, si se quiere una pila cuyos elementos sean el par <palabra, nmero
de lnea>:
typedef struct
{
char palabra[81];
int numLinea;
} elemento
typedef elemento TipoDato;
Figura 32.13 Representacin de una pila con una lista enlazada.
Pila
986 Captulo 32 Listas, pilas y colas en C
Implementacin de las operaciones de pilas con listas enlazadas
Las operaciones de la pila cuya declaracin se encuentran en el archivo pila.h, se implementan en
el archivo pila.c para despus formar un proyecto con otros mdulos y la funcin principal.
La codificacin que a continuacin se escribe es para una pila de nmeros reales; al estar la pila
representada por una lista enlazada, esta codificacin es muy similar a la de funciones de listas enla-
zadas.
Archivo pila.c
typedef double TipoDato; /* tipo de los elementos de la pila */
#include "pila.h"
Creacin de una pila sin elementos, vaca:
void crearPila (Nodo** pila)
{
*pila = NULL;
}
Verificacin del estado de la pila:
int pilaVacia (Nodo* pila)
{
return pila == NULL;
}
Poner un elemento en la pila. Se crea un nodo con el elemento que se pone en la pila y se enlaza
por la cima de la pila.
void insertar (Nodo** pila, TipoDato elemento)
{
Nodo* nuevo;
nuevo = (Nodo*)malloc (sizeof (Nodo) ); /* crea un nuevo nodo */
nuevo > elemento = elemento;
nuevo > siguiente = *pila;
(*pila) = nuevo;
}
Figura 32.14 Operacin de aadir
un elemento a la pila.
Pila
Eliminacin del elemento cima. Con esta operacin es eliminado y liberado el elemento cabeza,
modificando la pila.
void suprimir (Nodo** pila)
{
if (!pilaVacia (*pila) )
{
Nodo* f;
f = *pila;
(*pila) = (*pila) > siguiente;
free (f);
}
}
987 El tipo pila implementado como una lista enlazada
Obtencin del elemento cabeza o cima de la pila, sin modificar la pila:
TipoDato cima (Nodo* pila)
{
if (pilaVacia (pila) )
{
puts (" Error de ejecucin, pila vaca");
exit (1);
}
return pila > elemento;
}
Extraccin y obtencin del elemento cima. Esta operacin no slo elimina y libera el elemento
cima sino que adems devuelve dicho elemento. Se considera un error invocar esta operacin estando
la pila vaca.
TipoDato quitar (Nodo** pila)
{
TipoDato tem;
Nodo* q;
if (pilaVacia (*pila) )
{
puts (" Se intenta sacar un elemento en pila vaca");
exit (1);
}
tem = (*pila) > elemento;
q = *pila;
(*pila) = (*pila) > siguiente;
free (q);
return tem;
}
Vaciado de la pila. Libera todos los nodos de que consta la pila. Se basa en la operacin suprimir.
void limpiarPila (Nodo** pila)
{
while (!pilaVacia (*pila) )
{
suprimir (pila);
}
}
Figura 32.15 Operacin de suprimir la cima de la pila.
Pila
988 Captulo 32 Listas, pilas y colas en C
Concepto de cola
Una cola es una estructura de datos que almacena elementos en una lis-
ta y permite acceder a los datos por uno de los dos extremos de la lista
(figura 32.16). Un elemento se inserta en la cola (parte final) de la lis-
ta y se suprime o elimina por la frente (parte inicial, frente) de la lista.
Las aplicaciones utilizan una cola para almacenar elementos en su or-
den de aparicin o concurrencia.
Los elementos se eliminan de la cola en el mismo orden en que se
almacenan; por esa razn una cola es una estructura de tipo FIFO (first-
in/first-out, primero en entrar/primero en salir o bien primero en llegar/
primero en ser servido). El servicio de atencin a clientes en un alma-
cn es un ejemplo tpico de cola.
Las operaciones tpicas usuales que se utilizan en las colas son las siguientes:
Crearcola Inicia la cola como vaca
Insertar Aade un dato por el nal de la cola
Quitar Retira (extrae) el elemento frente de la cola
Cola vaca Comprobar si la cola no tiene elementos
Cola llena Comprobar si la cola est llena de elementos
Frente Obtiene el elemento frente o primero de la cola
Tamao de la cola Nmero de elementos mximo que puede contener la cola
La forma que los lenguajes tienen para representar el tipo cola depende de dnde se almacenen
los elementos, en un arreglo o en una lista dinmica. La utilizacin de arreglos tiene el problema de
que la cola no puede crecer indefinidamente, est limitada por el tamao del arreglo, como contrapar-
tida, el acceso a los extremos es muy eficiente. Utilizar una lista dinmica permite que el nmero de
nodos se ajuste al de elementos de la cola; por el contrario, cada nodo necesita memoria extra para el
enlace y tambin est el lmite de memoria de la pila de la computadora.
Tipo cola implementado con arreglos (arrays)
Se utiliza un arreglo para almacenar los elementos de la cola, y dos marcadores o apuntadores para
mantener las posiciones frente y final de la cola; es decir, un marcador apuntando a la posicin
de la cabeza de la cola y el otro al primer espacio vaco que sigue al final de la cola. Cuando un ele-
mento se aade a la cola, se verifica si el marcador final apunta a una posicin vlida, entonces se
aade el elemento a la cola y se incrementa el marcador final en 1. Cuando un elemento se elimina
de la cola, se hace una prueba para ver si la cola est vaca y, si no es as, se recupera el elemento de
la posicin apuntada por el marcador (puntero) de cabeza y ste se incrementa en 1.
La operacin de aadir un elemento a la cola comienza a partir de la posicin final 0; cada que
se aade un nuevo elemento se incrementa final en 1. La retirada de un elemento se hace por el
frente; cada que sale un elemento avanza frente una posicin. En la figura 32.17 se puede obser-
var cmo avanza frente al retirar un elemento.
El avance lineal de frente y final tiene un grave problema, deja huecos por la izquierda del
array. Puede ocurrir que final alcance el ndice ms alto del arreglo, sin que puedan insertar nuevos
elementos y sin embargo haber posiciones libres a la izquierda de frente.
Figura 32.16 Una cola.

A recordar
Una cola es una estructura de datos cuyos ele-
mentos mantienen un cierto orden, tal que slo
se pueden aadir elementos por un extremo,
final de la cola, y eliminar o extraer por el otro
extremo, llamado frente.
1 2 3 4 ltimo
Frente Final
989 El tipo cola implementado con arreglos (arrays) circulares
Una alternativa a esto es mantener fijo el frente de la cola al comienzo del arreglo. Esto supone
mover todos los elementos de la cola una posicin cada vez que queramos retirar un elemento. Estos
problemas quedan resueltos considerando el arreglo como circular.
El tipo cola implementado con arreglos (arrays)
circulares
La forma ms eficiente de almacenar una cola en un arreglo es considerar que el arreglo tiene unido
el extremo final de la cola con su extremo cabeza. Tal arreglo se denomina arreglo circular y permite
utilizar el arreglo completo para guardar los elementos de la cola, sin dejar posiciones libres a las que
no se puede acceder; el arreglo sigue siendo una estructura lineal; es a nivel lgico como el arreglo se
considera circular. Un arreglo circular con n elementos se visualiza en la figura 32.18.
Figura 32.17 Una cola representada en un arreglo (array).
1 2 3 4
A G H K
frente final
1 2 3 4
G H K
posicin de frente y final despus de extraer.
frente final
Figura 32.18 Un arreglo (array) circular.
n 1 0
1
El arreglo se almacena de modo natural en la memoria tal como un bloque lineal de n elementos.
Se necesitan dos marcadores (apuntadores) frente y final para indicar la posicin del elemento cabeza
y la posicin del final, donde se almacen el ltimo elemento aadido.
La variable frente es siempre la posicin del elemento primero de la cola y avanza en el sentido
de las agujas del reloj. La variable final es la posicin de la ltima insercin; una nueva insercin
supone mover final circularmente a la derecha y asignar el nuevo elemento.
La simulacin del movimiento circular de los ndices se realiza utilizando la teora de los restos
de tal forma que se generen ndices de 0 a MAXTAMQ1:
990 Captulo 32 Listas, pilas y colas en C
Los algoritmos que formalizan la gestin de colas en un arreglo circular han de incluir las opera-
ciones bsicas del tipo cola, en concreto, al menos en las siguientes tareas:
Creacin de una cola vaca, de tal forma que final apunte a una posicin inmediatamente an-
terior a frente: frente = 0; final = MAXTAMQ1.
Comprobar si una cola est vaca:
es frente = siguiente (final) ?
Comprobar si una cola est llena. Para distinguir entre la condicin de cola llena y cola vaca se
sacrifica una posicin del arreglo, de tal forma que la capacidad de la cola va a ser MAXTAMQ1.
La condicin de cola llena:
es frente = siguiente (siguiente (final) ) ?
Aadir un elemento a la cola: si la cola no est llena, establecer final a la siguiente posicin y
aadir el elemento en esa posicin:
final = (final + 1) % MAXTAMQ
Eliminacin de un elemento de una cola: si la cola no est vaca, suprimirlo de la posicin fren-
te y establecer frente a la siguiente posicin:
(frente + 1) % MAXTAMQ
Obtener el elemento primero de la cola, si la cola no est vaca, sin suprimirlo de la cola.
Figura 32.19 Una cola vaca.
0
1
frente
final
2
n 1
...
...
Figura 32.20 Una cola que contiene un elemento
0
1
2
n 1
frente
final
...
...
Mover final adelante = (final + 1) % MAXTAMQ
Mover frente adelante = (frente + 1) % MAXTAMQ
991 El tipo cola implementado con arreglos (arrays) circulares
Codificacin del tipo cola con un array (arreglo) circular
A continuacin se escribe el cdigo de cada una de las funciones del tipo cola que implementan las
operaciones sobre colas.
Siguiente circular
int siguiente (int n)
{
return (n + 1) % MAXTAMQ;
}
Crear Cola
Inicializa una cola vaca.
void crearCola (Cola* cola)
{
cola > frente = 0;
cola > final = MAXTAMQ1;
}
Insertar
El ndice final apunta al ltimo elemento insertado; ste avanza circularmente.
void insertar (Cola* cola, TipoDato entrada)
{
if (colaLlena (*cola) )
{
puts (" desbordamiento cola");
exit (1);
}
/* avance circular al siguiente del final */
cola > final = siguiente (cola > final);
cola > listaCola[cola > final] = entrada;
}
Quitar
El ndice frente avanza circularmente.
TipoDato quitar (Cola* cola)
{
TipoDato tmp;
if (colaVacia (*cola) )
{
puts (" Extraccin en cola vaca ");
exit (1);
}
tmp = cola > listaCola[cola>frente];
/* avanza circularmente frente */
cola > frente = siguiente (cola > frente);
return tmp;
}
Frente
Obtiene el elemento del frente o primero de la cola, sin modificar la cola.
TipoDato frente (Cola cola)
{
if (colaVacia (cola) )
{
puts (" Se requiere frente de una cola vaca ");
exit (1);
}
return cola.listaCola[cola.frente];
}
992 Captulo 32 Listas, pilas y colas en C
Cola Vaca
int colaVacia (Cola cola)
{
return cola.frente == siguiente (cola.final);
}
Cola Llena
Prueba si la cola no puede contener ms elementos. En el array queda una posicin no ocupada,
y as se distingue de la condicin de cola vaca.
int colaLlena (Cola cola)
{
return cola.frente == siguiente (siguiente (cola.final) );
}
Ejemplo 32.6
Encontrar un nmero capica ledo del dispositivo estndar de entrada.
Para encontrar el capica buscado se utiliza una cola y una pila. Los dgitos se leen carcter
a carcter (un dgito es un carcter del 0 al 9), almacenndose en una cola y a la vez en una
pila. Una vez ledo el nmero se extraen consecutivamente elementos de la cola y de la pila y se
comparan por igualdad. De producirse alguna no coincidencia es que el nmero no es capica
y en ese caso se vacan las estructuras para solicitar a continuacin, otra entrada. El nmero es
capica si el proceso de verificacin termina al coincidir todos los dgitos en orden inverso; por
consiguiente, con la pila y la cola vaca.
Por qu utilizar una pila y una cola? Sencillamente por el orden inverso en procesar los ele-
mentos; en la pila el ltimo en entrar es el primero en salir, en la cola el primero en entrar es el
primero en salir.
Para que no haya colisin con los nombres de las operaciones, en el tipo pila se aade la letra
p a insertar y quitar: insertarp ( ), quitarp ( ).
typedef char TipoDato;
#include "colacircular.h"
#include "pila.h"
#include <stdio.h>
void main ( )
{
char d;
Cola cola;
Pila pila
int capicua = 0;
crearCola (&q);
crearPila (&pila);
while (!capicua)
{
printf (" Nmero a investigar: ");
while ( (d = getchar ( ) )!= '\n')
{
if ( d < '0' || d > '9')
{
puts (" \n Error en el nmero introducido ");
}
else
{
insertar (&cola, d);
insertarp (&pila, d);
}
993 El tipo cola implementado con una lista enlazada
El tipo cola implementado con una lista enlazada
La realizacin de una cola con arreglos exige reservar memoria contigua para el mximo de elemen-
tos previstos. En muchas ocasiones esto da lugar a que se desaproveche memoria; tambin puede ocu-
rrir lo contrario, que se llene la cola y/o se pueda seguir con la ejecucin del programa. La alternativa
a esto est en utilizar memoria que se ajusta en todo momento al nmero de elementos de la cola, uti-
lizar memoria dinmica mediante una lista enlazada, an a pesar de tener el inconveniente de la me-
moria extra utilizada para realizar los encadenamientos entre nodos.
Esta implementacin utiliza memoria dinmica, mediante una lista enlazada, que se ajusta en todo
momento al nmero de elementos de la cola. Son necesarios dos punteros para acceder a la lista,
frente y final, que, respectivamente, son los extremos por donde salen los elementos y por donde
se insertan.
}
capicua = 0;
do {
capicua = quitar (&cola) == quitarp (&pila);
} while (capicua && !colaVacia (cola) );
if (capicua)
puts (" \n El numero introducido es capica ");
else
{
puts (" \n El numero no es capica, intente con otro " );
crearCola (&cola); /* se inicializan las estructuras */
crearPila (&pila);
}
}
}
Figura 32.21 Cola con lista enlazada.
q
n
q
2
q
3
q
1
frente final
...
La variable puntero frente referencia al primer elemento de la cola, el primero en ser retirado
de la cola. La otra variable puntero, final, referencia al ltimo elemento en ser aadido, que ser el
ltimo en ser retirado.
Nota de programacin
Una estructura dinmica puede crecer y decrecer segn las necesidades, segn el nmero de
nodos (el lmite est en la memoria libre de la computadora). En una estructura dinmica no
tiene sentido la operacin que verifica un posible desbordamiento, esta prueba s es necesaria
en las estructuras de tamao fijo.
Declaracin de tipos de datos y operaciones
La representacin de una cola con listas enlazadas maneja dos tipos de datos: el tipo nodo de la lista,
y otro tipo para agrupar a las variables frente y final; este tipo lo denominamos Cola.
994 Captulo 32 Listas, pilas y colas en C
El archivo coladinamica.h contiene la declaracin de estos tipos y los prototipos de las funcio-
nes que representan a las operaciones bsicas de una cola.
/* archivo coladinamica.h */
#include <stdlib.h>
struct nodo
{
TipoDato elemento;
struct nodo* siguiente;
};
typedef struct nodo Nodo;
typedef struct
{
Nodo* frente;
Nodo* final;
}Cola;
/* prototipos de las operaciones */
void crearCola (Cola* cola);
void insertar (Cola* cola,TipoDato entrada);
TipoDato quitar (Cola* cola);
void borrarCola (Cola* cola); /* libera todos los nodos */
/* acceso a la cola */
TipoDato frente (Cola cola);
/* mtodos de verificacin del estado de la cola */
int colaVacia (Cola cola);
Codificacin de las funciones de una cola con listas
La codificacin se encuentra en el archivo fuente coladinamica.c. En primer lugar hay que decla-
rar el tipo concreto de los elementos de la cola e incluir el archivo coladinamica.h.
A continuacin se escribe la codificacin completa, el tipo de los datos de los elementos de la cola
se supone cadena de caracteres, representado por char*.
/* archivo coladinamica.c */
typedef char* TipoDato;
#include "coladinamica.h"
void crearCola (Cola* cola)
{
cola > frente = cola > final = NULL;
}
Nodo* crearNodo (TipoDato elemento)
{
Nodo* t;
t = (Nodo*)malloc (sizeof (Nodo) );
t > elemento = elemento;
t > siguiente = NULL;
return t;
}
int colaVacia (Cola cola)
{
return (cola.frente == NULL);
}
void insertar (Cola* cola, TipoDato entrada)
{
Nodo* a;
a = crearNodo (entrada);
if (colaVacia (*cola) )
{
cola > frente = a;
}
995 Resumen
else
{
cola > final > siguiente = a;
}
cola > final = a;
}
TipoDato quitar (Cola* cola)
{
TipoDato tmp;
if (!colaVacia (*cola) )
{
Nodo* a;
a = cola > frente;
tmp = cola > frente > elemento;
cola > frente = cola > frente > siguiente;
free (a);
}
else
{
puts ("Error cometido al eliminar de una cola vaca");
exit (1);
}
return tmp;
}
TipoDato frente (Cola cola)
{
if (colaVacia (cola) )
{
puts ("Error: cola vaca ");
exit (1);
}
return (cola.frente > elemento);
}
void borrarCola (Cola* cola)
{
/* Elimina y libera todos los nodos de la cola */
for (; cola>frente != NULL;)
{
Nodo* n;
n = cola > frente;
cola > frente = cola > frente > siguiente;
free (n);
}
}
Resumen
Una lista enlazada es una estructura de datos dinmica en la que sus componentes estn orde-
nados lgicamente por sus campos de enlace en vez de ordenados fsicamente como estn en un
arreglo. El final de la lista se seala mediante una constante o puntero especial llamado NULL.
La gran ventaja de una lista enlazada sobre un array es que la lista enlazada puede crecer y de-
crecer en tamao, ajustndose al nmero de elementos.
Una lista simplemente enlazada contiene slo un enlace a un sucesor nico, a menos que sea el
ltimo, en cuyo caso no se enlaza con ningn otro nodo.
Cuando se inserta un elemento en una lista enlazada, se deben considerar cuatro casos: aadir a
una lista vaca, aadir al principio de la lista, aadir en el interior y aadir al final de la lista.
996 Captulo 32 Listas, pilas y colas en C
Para borrar un elemento, primero hay que buscar el nodo que lo contiene y considerar dos casos:
borrar el primer nodo y borrar cualquier otro de la lista.
El recorrido de una lista enlazada significa pasar por cada nodo (visitar) y procesarlo. El proce-
so puede ser escribir su contenido o modificar el campo de datos.
Una pila es una estructura de datos tipo LIFO (last-in/first-out, ltimo en entrar primero en salir)
en la que los datos (todos del mismo tipo) se aaden y eliminan por el mismo extremo, denomi-
nado cima de la pila.
Se definen las siguientes operaciones bsicas sobre pilas: crearPila, insertar, cima, su-
primir, quitar, pilaVacia, pilaLlena y liberarPila.
crearPila inicializa la pila como pila vaca. sta es la primera operacin a ejecutar.
insertar aade un elemento en la cima de la pila. Debe haber espacio en la pila.
cima devuelve el elemento que est en la cima, sin extraerlo.
suprimir extrae de la pila el elemento cima.
quitar extrae de la pila el elemento cima de la pila que es el resultado de la operacin.
pilaVacia determina si el estado de la pila es vaca, en su caso devuelve el valor que represen-
ta a true.
pilaLlena determina si existe espacio en la pila para aadir un nuevo elemento. De no haber
espacio devuelve false(0). Esta operacin se aplica en la representacin de la pila mediante un
arreglo.
liberarPila el espacio asignado a la pila se libera, queda disponible.
Una cola es una lista lineal en la que los datos se insertan por un extremo ( final) y se extraen por
el otro extremo (frente). Es una estructura FIFO (first-in/first-out, primero en entrar primero en
salir).
Las operaciones bsicas que se aplican sobre colas: crearCola, colaVacia, colaLlena,
insertar, frente, retirar.
crearCola inicializa una cola sin elementos. Es la primera operacin a realizar con una cola.
colaVacia determina si una cola tiene o no tiene elementos. Devuelve 1 (true) si no tiene
elementos.
colaLlena determina si no se pueden almacenar ms elementos en una cola. Se aplica esta
operacin cuando se utiliza un arreglo para guardar los elementos de la cola.
insertar aade un nuevo elemento a la cola, siempre por el extremo final.
frente devuelve el elemento que est en el extremo frente de la cola, sin extraerlo.
retirar extrae el elemento frente de la cola.
El tipo Cola se puede implementar con arrays y con listas enlazadas. La implementacin con un
arreglo lineal es muy ineficiente; se ha de considerar el arreglo como una estructura circular y
aplicar la teora de los restos para avanzar el frente y el final de la cola.
Ejercicios
32.1. Considerar una cola de nombres representada por un arreglo circular con 6 posiciones, el cam-
po frente con el valor: frente = 2. Y los elementos de la cola: Mar, Sella, Centurin.
Escribir los elementos de la cola y los campos frente y final segn se realizan estas ope-
raciones:
Aadir Gloria y Generosa a la cola.
Eliminar de la cola.
Aadir Positivo.
Aadir Horche a la cola.
Eliminar todos los elementos de la cola.
997 Problemas
32.2. Escribir una funcin entera que devuelva el nmero de nodos de una lista enlazada.
32.3. Escribir una funcin que elimine el nodo que ocupa la posicin i, siendo el nodo cabecera el
que ocupa la posicin 0.
32.4. Cul es la salida de este segmento de cdigo, teniendo en cuenta que el tipo de dato de la pila
es int?
Nodo* pila:
int x=4, y;
crearPila (&pila);
insertar (&pila, x);
printf ("\n %d ", cima (pila) );
y = quitar (&pila);
insertar (&pila, 32);
insertar (&pila, quitar (&pila) );
do {
printf ("\n %d ", quitar (&pila) );
}while (!pilaVacia (pila) );
32.5. Utilizando una pila de caracteres, transformar la siguiente expresin a su equivalente expre-
sin en postfija.
(xy)/(z+w) (z+y)^x
32.6. Escribir una funcin entera que tenga como argumento una lista enlazada de nmeros enteros
y devuelva el dato del nodo con mayor valor.
32.7. Se tiene una lista de simple enlace, el campo dato es un registro (estructura) con los campos
de un alumno: nombre, edad, sexo. Escribir una funcin para transformar la lista de tal forma
que si el primer nodo es de un alumno de sexo masculino el siguiente sea de sexo femenino.
32.8. Supngase que se tienen ya codificadas las declaraciones para definir el tipo cola. Escribir una
funcin para crear una copia de una cola determinada. La funcin tendr dos argumentos, el
primero es la cola origen y el segundo la cola que va a ser la copia. Las operaciones que se han
de utilizar sern nicamente las definidas para el tipo cola.
32.9. Escribir la funcin mostrarPila ( ) para escribir los elementos de una pila de cadenas de
caracteres, utilizando slo las operaciones bsicas y una pila auxiliar.
32.10. Se ha estudiado en el captulo la realizacin de una cola mediante un arreglo circular; una va-
riacin a esta representacin es aquella en la que, adems de tener una variable con la posicin
del elemento frente, dispone de otra variable con la longitud de la cola (nmero de elementos),
longCola, y el arreglo considerado circular. Dibujar una cola vaca; aadir a la cola 6 ele-
mentos; extraer de la cola tres elementos; aadir elementos hasta que haya overflow. En todas
las representaciones escribir los valores de frente y longCola.
32.11. Escribir la implementacin de las operaciones del tipo cola para la realizacin del ejercicio
32.10.
32.12. Se tiene una lista enlazada a la que se accede por el puntero lista que referencia al primer
nodo. Escribir una funcin que imprima los nodos de la lista en orden inverso, desde el ltimo
nodo al primero; como estructura auxiliar utilizar una pila y sus operaciones.
Problemas
32.1. Escribir un programa o funciones individuales que realicen las siguientes tareas:
998 Captulo 32 Listas, pilas y colas en C
Crear una lista enlazada de nmeros enteros positivos al azar; la insercin se realiza por el
ltimo nodo.
Recorrer la lista para mostrar los elementos por pantalla.
Eliminar todos los nodos que superen un valor dado.
32.2. Se tiene un archivo de texto de palabras separadas por un blanco o el carcter de fin de lnea.
Escribir un programa para formar una lista enlazada con las palabras del archivo. Una vez for-
mada la lista se pueden aadir nuevas palabras o borrar alguna de ellas. Al finalizar el
programa escribir las palabras de la lista en el archivo.
32.3. Un polinomio se puede representar como una lista enlazada. El primer nodo de la lista repre-
senta el primer trmino del polinomio; el segundo nodo al segundo trmino del polinomio, y
as sucesivamente. Cada nodo tiene como campo dato el coeficiente del trmino y el exponen-
te. Por ejemplo, el polinomio 3x
4
4x
2
+ 11 se representa
Escribir un programa que permita dar entrada a polinomios en x, representndolos con una lis-
ta enlazada simple. A continuacin obtener una tabla de valores del polinomio para valores de
x = 0.0, 0.5, 1.0, 1.5, , 5.0
32.4. Con un archivo de texto se quieren realizar las siguientes acciones: se formar una lista de co-
las, de tal forma que en cada nodo de la lista est la direccin de una cola que tiene todas las
palabras del archivo que empiezan por una misma letra. Visualizar las palabras del archivo,
empezando por la cola que contiene las palabras que comienzan por a, a continuacin las de
la letra b y as sucesivamente.
32.5. Segn la representacin de un polinomio propuesta en el problema 32.3, escribir un programa
para realizar las siguientes operaciones:
Obtener la lista suma de dos polinomios.
Obtener el polinomio derivado.
Obtener una lista que sea el producto de dos polinomios.
32.6. En un archivo F estn almacenados nmeros enteros arbitrariamente grandes. La disposicin
es tal que hay un nmero entero por cada lnea de F. Escribir un programa que muestre por
pantalla la suma de todos los nmeros enteros. Al resolver el problema habr que tener en
cuenta que al ser enteros grandes no pueden almacenarse en variables numricas.
Utilizar dos pilas para guardar los dos primeros nmeros enteros, almacenndose dgito a
dgito. Al extraer los elementos de la pila salen en orden inverso y por tanto de menos peso
a mayor peso, se suman dgito con dgito y el resultado se guarda en una cola, tambin dgito a
dgito. A partir de este primer paso, se obtiene el siguiente nmero del archivo, se guarda en
una pila y a continuacin se suma dgito a dgito con el nmero que se encuentra en la cola; el
resultado se guarda en otra cola. El proceso se repite, el nuevo nmero del archivo se mete en
la pila, que se suma con el nmero actual de la cola.
32.7. Un conjunto es una secuencia de elementos todos del mismo sin duplicidades. Escribir un pro-
grama para representar un conjunto de enteros mediante una lista enlazada. El programa debe
contemplar las operaciones:
Cardinal del conjunto.
Pertenencia de un elemento al conjunto.
Aadir un elemento al conjunto.
Escribir en pantalla los elementos del conjunto.
3 4 11 4 2 0
999 Problemas
32.8. Con la representacin propuesta en el problema 32.7, aadir las operaciones bsicas de con-
juntos:
Unin de dos conjuntos.
Interseccin de dos conjuntos.
Diferencia de dos conjuntos.
Inclusin de un conjunto en otro.
32.9. Escribir un programa que haciendo uso de una pila de caracteres, procese cada uno de los ca-
racteres de una expresin que viene dada en una lnea. La finalidad es verificar el equilibrio de
parntesis, llaves y corchetes.
Por ejemplo, la siguiente expresin tiene un nmero de parntesis equilibrado:
( (a + b) *5) 7
A esta otra expresin le falta un corchete:
2* [ (a + b) / 2.5 + x 7 *y
32.10. Escribir un programa en el que dados dos archivos F1, F2 formados por palabras separadas por
un blanco o fin de lnea, se creen dos conjuntos con las palabras de F1 y F2 respectivamente.
Despus encontrar las palabras comunes y mostrarlas por pantalla. Para resolver el problema,
utilizar la representacin y operaciones sobre conjuntos definidas en los problemas 32.7 y
32.8.
32.11. Escribir un programa en el que se manejen un total de n = 5 pilas: P
1
, P
2
, P
3
, P
4
y P
5
. La entra-
da de datos ser pares de enteros (i,j) tal que 1 abs(i) n. De tal forma que el criterio
de seleccin de pila:
Si i es positivo, debe insertarse el elemento j en la pila P
i
.
Si i es negativo, debe eliminarse el elemento j de la pila P
i
.
Si i es cero, fin del proceso de entrada.
Los datos de entrada se introducen por teclado. Cuando termina el proceso, el programa
debe escribir el contenido de las n pilas en pantalla.
32.12. Un vector disperso es aquel que tiene muchos elementos que son cero. Escribir un programa
que permita representar mediante listas enlazadas un vector disperso. Los nodos de la lista son
los elementos de la lista distintos de cero; en cada nodo se representa el valor del elemento y
el ndice (posicin del vector). El programa ha de realizar las operaciones: sumar dos vectores
de igual dimensin y hallar el producto escalar.
Flujos y archivos
en C++
Captulo
Contenido
Flujos ( streams)
La biblioteca de clases iostream
Clases istream y ostream
La clase ostream
Salida a la pantalla y a la impresora
Lectura del teclado
Formateado de salida
Indicadores de formato
Archivos C++
Apertura de archivos
E/S en archivos
Lectura y escritura de archivos de texto
E/S binaria
Acceso aleatorio
Resumen
Ejercicios
Problemas
Introduccin
Hasta este momento se han realizado las operaciones bsicas de entrada y salida. La operacin de in-
troducir (leer) datos en el sistema se denomina lectura y la gene racin de datos del sistema se deno-
mina escritura. La lectura de datos se realiza desde el teclado e incluso des de la unidad de disco, y la
escritura de datos se realiza en el monitor y en la impresora del sistema.
Al igual que sucede en C ANSI, las funciones de entrada/salida no estn definidas en el propio
lenguaje C++, sino que estn incorporadas en cada compilador de C++ bajo la forma de biblioteca de
ejecucin. En C existe la biblioteca stdio.h estandarizada por ANSI; en C++ la biblioteca corres-
33
1001 Flujos (streams)
pondiente es iostream, aunque en este caso todava no est estandarizada. En conse cuencia, el lector
puede recurrir en las operaciones de E/S (entrada/salida) a cualquiera de las dos bibliotecas, aunque
la biblioteca iostream se distingue positivamente de stdio.h en que la entrada/salida se realiza por flu-
jos (streams). Este mtodo adems de proporcionar la fun cionalidad de C, es flexible y eficiente me-
diante la gestin de clases, que permite sobrecargar funciones y operadores, lo que har que sus clases
pue dan ser manipuladas como si fueran tipos predefinidos.
La gran ventaja de la biblioteca iostream es que se pueden manipular las operaciones E/S sin ne-
cesidad de conocer los conceptos tpicos de orientacin a objetos, tales como clases, herencia, funcio-
nes virtuales, etctera.
En este captulo aprender a utilizar las caractersticas tpicas de E/S de C++ y obtener el mximo
rendimiento de las mismas.
La biblioteca de flujos iostream de C++ contiene la clase ios. Esta clase declara los identifica-
dores que esta blecen el modo de flujos de archivos. La biblioteca tiene las clases ifstream, ofs-
tream y fstream, que admiten flujos de archivos de entrada, de salida y de entrada/salida. Este
captulo examina las funciones de la biblioteca de flujos que admite E/S de archivos de texto, E/S de
archivos binarios y E/S de archivos de acceso aleatorio.
Los tipos de clases proporcionan el siguiente soporte de archivos:
ifstream , derivada de istream, conecta un archivo al programa para entrada.
of stream , derivada de ostream, conecta un archivo al programa para salida.
fstream , derivada de iostream, conecta un archivo al programa para entrada y salida.
Para usar el componente de flujos de archivos de la biblioteca iostream se debe incluir su ar-
chivo asociado: #include <fstream>.
Conceptos clave
Acceso aleatorio Final del archivo
Apertura de un archivo Flujo ( stream)
Archivo de cabecera Flujo binario
Archivo de texto Flujo de texto
Archivos binarios Indicador de estado
Biblioteca Manipulador
Biblioteca de clases Operador de extraccin
Bit de estado Operador de insercin
Entrada/Salida
Flujos (streams)
Un flujo (stream) es una abstraccin que se refiere a un flujo o corriente de datos entre un origen o
fuente (productor) y un destino o sumidero (consumidor). Entre el origen y el destino debe existir una
conexin o tubera (pipe) por la que circulen los datos. Estas conexiones se realizan me diante opera-
dores (<< y >>) sobrecargados y funciones de E/S.
Un flujo es una abstraccin desarrollada por Bjarne Stroustrup, el diseador de C++, sobre la idea
original de C y UNIX. Stroustrup se imagin un flujo (corriente) de caracteres fluyendo desde el te-
clado a un programa, al igual que un flujo de agua fluye de un sitio a otro. Schwarz utiliz esta idea
para crear la clase istream, una clase que representa un flujo de caracteres desde un dispositivo de
entrada arbi trario a un programa en ejecucin.
En esencia, un flujo es una abstraccin que se refiere a una interfaz comn a diferentes dispositi-
vos de entrada y salida de una computadora. Existen dos formas de flujo: texto y binario. Los flujos
de texto se utilizan con caracteres ASCII, mientras que los flujos binarios se pueden utilizar con cual-
quier tipo de dato. Los sinnimos extraer u obtener se utilizan generalmente para referirse a la entrada
de datos de un dispositivo e insercin o colocacin (poner) cuando se refieren a la salida de datos a
un dispositivo.
1002 Captulo 33 Flujos y archivos en C++
Flujos de texto
Un flujo de texto es una secuencia de caracteres. En un flujo de texto, pueden ocurrir ciertas conver-
siones de caracteres si son requeridas por el entorno del sistema. Por ejemplo, un carcter de nueva
lnea puede convertirse en un par de caracteres retorno de carro/salto de lnea. Por esta razn, pue-
de suceder que no se establezca una relacin de uno a uno entre los caracteres que se escriben o se
leen y los que apa recen en el dispositivo externo. De igual forma, como consecuencia de las posibles
conversiones puede suceder que el nmero de caracteres escritos o ledos no coincida con el del dis-
positivo externo.
Flujos binarios
Un flujo binario es una secuencia de bytes que tiene una correspondencia uno a uno con los del dispo-
sitivo externo. Es decir, no se producen conversiones de caracteres. En
este caso, la cantidad de bytes escritos o ledos coincide con la del dis-
positivo externo. Sin embargo, se puede aadir un nmero de bytes nu-
los definidos en la implementacin, a un flujo binario. Estos bytes nulos
se suelen utilizar, por ejemplo, para ajustar la informacin de tal forma
que se complete un sector de un disco.
Hasta este momento del libro, la entrada y la salida en C se ha implementado utilizando el flujo
de entra da estndar (denominado cin) y el flujo de salida estndar (denominado cout). Todo progra-
ma C++ tiene estos flujos disponibles automticamente siempre que se incluya el archivo de cabece-
ra ios tream.h; cin y cout son objetos de tipos istream y ostream respectivamente. Los tipos
definidos por el usuario istream y ostream estn definidos en las clases de la biblioteca iostream
y cin/cout se declaran como objetos de esos tipos. Normalmente, cin se conecta al teclado. La lec-
tura de caracte res desde el objeto cin del flujo de entrada estndar es equivalente a la lectura del te-
clado; la escritura de caracteres al cout del flujo de salida estndar es equivalente a visualizar estos
caracteres en la pan talla.
Todas las caractersticas de flujos entrada/salida se relacionan con conversiones de representacio-
nes internas de datos (variables u objetos) a un flujo de caracteres (para salida) y conversin de flujos
de caracteres a un formato interno correcto (para entrada). La figura 33.2 muestra esta conversin para
los flujos cin y cout. Mediante el operador de salida, <<, se convierten los datos que aparecen en el
ope rando a un flujo de caracteres y se insertan en el flujo de salida (cout). Al contrario, el opera-
dor de entrada, >>, especifica la extraccin del flujo de entrada de un flujo de caracteres. Estos ca-
racteres se convierten entonces al formato interno apropiado y se almacenan en las posiciones de
almacenamiento especificados.
Las clases de flujo de E/S
El archivo de cabecera <iostream> declara tres clases para los flujos de entradas y salida estndar.
La clase istream es para entrada de datos desde un flujo de entrada, la clase ostream es para sa-
lida de datos a un flujo de salida, y la clase iostream es para entrada de datos dentro de un flujo.
Por otra par te, estas clases declaran tambin los cuatro objetos ya conocidos (tabla 33.1).
Figura 33.1 Simulacin de un flujo.
Programa
en ejecucin
a
b
c
d
e
f
g
h
i
j
k
l

A recordar
Un flujo (stream) es una secuencia de caracteres.
1003 Flujos (streams)
Figura 33.2 Conversin de datos a/desde flujos.
Conversin de salida (de representacin interna a caracteres)
cout << " Introduzca " << setw (3) << x << " valores enteros "
121 x
(entero x, representacin interna)
...

I

n

t

r

o

d

u

z

c

a

1

2

1

v

a

l

o

r

e

s

...
cout (flujo de salida de caracteres a la pantalla)
Conversin de entrada
cin >> dia >> mes >> anyo
cin (flujo de caracteres ledos del teclado)
... 21 06 1999 ...
da mes ao
21 06 1999
~~~~~~~ ~~~~~~~~~~~~~~~~
~~~ ~~~~ ~~~~~
representacin
interna de los
enteros ledos)
Tabla 33.1
Objetos de <iostream.h>.
Objeto
de flujo
Funcin
cin
Un objeto de la clase istream conectado a la entrada estndar.
cout
Un objeto de la clase ostream conectado a la salida estndar.
cerr
Un objeto de la clase ostream conectado al error estndar, para salida sin bfer.
clog
Un objeto de la clase ostream conectado al error estndar, con salida a travs de bfer.
ios
istream ostream
of stream istrstream istringstream iostream ostringstream
ifstream ostrstream
fstream strtream
streambuf
filebuf stdiobuf
Las clases que se derivan de la clase base ios se utilizan para procesamiento de flujos de alto ni-
vel, mientras que las clases que se derivan de la clase base streambuf se utilizan para procesamien-
to de bajo nivel.
La clase iostream es la que se utiliza normalmente en operaciones ordinarias de E/S. Esta clase
es una subclase de las clases istream y ostream, que a su vez son clases derivadas (subclases) de
la cla se base ios. Las tres clases que incluyen la palabra fstream en su nombre se utilizan para
~~~~~~~~~~~~~~~~~~~~~~
Figura 33.3 Biblioteca de clases de E/S.
1004 Captulo 33 Flujos y archivos en C++
tratamien to de archivos. Por ltimo, la clase stdiobuf se utiliza para combinar E/S de flujos C++
con las funciones antiguas de E/S estilo C.
Archivos de cabecera
Existen tres archivos de cabecera importantes para clases de flujos de E/S. El archivo de cabecera
<ios tream> declara las clases istream, ostream e iostream para las operaciones de E/S de flu-
jos de entrada y salida estndar. Declara tambin los objetos cout, cin, cerr y clog que se utilizan
en la mayora de los programas C++.
El archivo de cabecera <fstream> declara las clases ifstream, of stream y fstream para
ope raciones de E/S a archivos de disco. Por ltimo, el archivo de cabecera <strstream> declara las
clases istrstream, ostrstream y strstream para formateado de datos con bfers de caracteres.
#include<iostream>
en cada programa que utilice E/S se ha de utilizar la directiva. El archivo de cabecera iostream in-
cluye las definiciones de la bi blioteca de E/S.
La biblioteca de clases iostream
La biblioteca iostream se basa en el concepto de flujos; incorpora la ventaja de las potentes caracte-
rsticas orientadas a objetos de C++.
La biblioteca de E/S de flujos se construye a base de una jerarqua de clases que se declaran en
di versos archivos de cabecera. La biblioteca de clases tiene dos familias paralelas de clases: las deri-
vadas de streambuf y las derivadas de ios.
La clase streambuf
La clase streambuf proporciona una interfaz a dispositivos fsicos; proporciona mtodos fundamen-
tales para realizar operaciones con buffer y manejo de flujos cuando las condiciones de formateado
no son muy exigentes.
Jerarqua de clases ios
La jerarqua de clases ios gestiona todas las operaciones de E/S y proporciona la interfaz de bajo ni-
vel al programador. La clase ios contiene un puntero a streambuf.
Para acceder a la biblioteca iostream se deben incluir archivos de cabecera especficos; uno de
ellos ya ha sido utilizado por el lector, iostream , pero existen otros archivos de cabecera, como se
ver en esta misma seccin.
La clase istream (input stream) proporciona las operaciones de lectura de datos, mientras
que la clase ostream (output stream) implementa las operaciones de escritura de datos. La clase
ios tream (input-output stream) se deriva simultneamente de istream y ostream, y propor-
cio na operaciones bidireccionales de entrada/salida (es un ejemplo de herencia mltiple).
Las clases istrstream y ostrstream se utilizan cuando se desea manejar arrays de caracte-
res y flujos, mientras que el otro conjunto de clases istringstream y ostringstream, se utilizan
cuando se conectan flujos con objetos de la clase estndar string. La figura 33.4 muestra el modo
en el que se relacionan unas clases con otras.
ifstream
istream istrstream
ios
istringstream
of stream
ostream ostrstream
ostringstream
Figura 33.4 Clases derivadas de ios.
1005 La biblioteca de clases iostream
Las clases ios, istream, ostream y los objetos de flujos predeclarados (cin, cout, cerr y
clog) se definen en el archivo iostream, el cual debe, por consiguiente, ser incluido en el programa.
Las clases ifstream y of stream se definen en el archivo de cabecera fstream. El archivo de cabe-
cera strstream contiene las definiciones de las clases istrstream y ostrstream y por ltimo las
clases istringstream y ostringstream estn declaradas en el archivo de cabecera sstream.
Flujos estndar
La biblioteca iostream define cuatro flujos estndar (objetos de flujo predefinidos): cin, cout,
cerr y clog (tabla 33.1). Estos tipos se declaran siempre automticamente, de modo que no precisan
declaracin previa.
El flujo cin, definido por la clase istream, est conectado al perifrico de entrada estndar (el
teclado, representado por el archivo stdin), aunque en algunos sistemas operativos podra ser
redirigido (MS-DOS, Windows, Linux y UNIX).
El flujo cout, definido por la clase ostream, est conectado al perifrico de salida estndar (la
pantalla, representado por el archivo stdout).
El flujo cerr, definido por la clase ostream, est conectado al pe-
rifrico de error estndar (la pantalla, representado por el archivo
stdout). Este flujo no es a travs de buffer.
El flujo clog, definido por la clase ostream, est conectado igual-
mente al perifrico de error estndar (la pantalla, representado por
el archivo stdout). Al contrario que cerr, el flujo clog se realiza
a travs de buffer.
La ventaja de cerr sobre clog es que los buffers de salida se limpian (vacan) cada vez que cerr
se utiliza, de modo que la salida est disponible ms rpidamente en el dispositivo externo (que de
manera predeterminada es la pantalla de video). Sin embargo, en grandes cantidades de mensajes, la
versin clog a travs de buffer es ms eficiente.
Entradas/salidas en archivos
Las tres clases siguientes permiten efectuar entradas/salidas en archivos:
ifstream , clase derivada de istream; se utiliza para gestionar la lectura de un archivo. Cuan-
do se crea un objeto ifstream y se especifican parmetros, se abre un archivo.
ofstream , clase derivada de ostream; gestiona la escritura en un archivo. Los objetos ofstream
se utilizan para hacer operaciones de salida de archivos. Se declara un objeto ofstream si se
piensa escribir un archivo de disco. Si se proporciona un nombre de archivo cuando se declara
un objeto ofstream, se abre el archivo. Se puede especificar que el archivo se cree en modo bi-
nario o en modo texto. Si un objeto de of stream est ya declarado, se puede utilizar la fun-
cin miembro open ( ) para abrir el archivo. Por otra parte, se dispone de la funcin miembro
close ( ), que sirve para cerrar el archivo.
fstream , clase derivada de iostream; permite leer y escribir en un archivo. Los objetos
fstream se utilizan cuando se desea realizar en forma simultnea operaciones de lectura y es-
critura en el mismo archivo.
Las definiciones de estas clases se encuentran en el archivo de cabecera fstream.
Entradas/salidas en un bfer de memoria
Existen dos clases especficas destinadas a las entradas/salidas en un bfer en memoria:
istrstream , clase derivada de istream, permite leer caracteres a partir de una zona de memo-
ria, que sirve de flujo de entrada.
ostrstream , clase derivada de ostream, permite escribir caracteres en una zona de memoria,
que sirve de flujo de salida.

A recordar
Cualquier objeto creado de la clase ios o cual-
quiera de sus clases derivadas se conoce gene-
ralmente como un objeto flujo.
1006 Captulo 33 Flujos y archivos en C++
El archivo de cabecera strstream contiene las definiciones de las clases istrstream y
ostrstream.
Archivos de cabecera
Cualquier programa que utilice la biblioteca iostream debe incluir el archivo de cabecera y, even-
tualmente, otros archivos de cabecera suplementarios: <ios>, <istream>, <ostream>, <ifstream>,
<ofstream>, <fstream>, <strstream>, <iomanip>.
El archivo de cabecera i declara clases de bajo nivel e identificadores. Los archivos istream.h
y ostream.h admiten las entradas y salidas bsicas de los flujos. El archivo iostream. h combina
las operaciones de las clases en los dos archivos de cabecera anteriores. Para realizar entradas/salidas
en archivos, se deben incluir los archivos de cabecera fstream e iostream. El archivo iomanip
permi tir formatear y organizar la salida de datos. La inclusin del archivo de cabecera strstream
permite el acceso a las funciones de la biblioteca iostream, que permiten efectuar las entradas/sali-
das en me moria.
Entrada/salida de caracteres y flujos
C++ visualiza todas las entradas y salidas como flujos de caracteres. Si su programa obtiene la entra-
da del teclado, un archivo de disco, un mdem o un ratn, C++ ve slo un flujo de caracteres. C++ no
co noce cul es el tipo de dispositivo que le proporciona la entrada.
Estas operaciones de E/S de flujos significan que se utilizan las mismas funciones para obtener la
entrada del teclado como del mdem. Se pueden utilizar las mismas funciones para escribir en un
archi vo de disco, una impresora o una pantalla. Naturalmente, se necesita algn medio para encami-
nar el flujo de entrada o salida al dispositivo adecuado.
El flujo de datos ir de un dispositivo de entrada (teclado) al programa C++ y de un programa
C++ al dispositivo de salida (la pantalla o la impresora).
Clases istream y ostream
Las clases istream y ostream se derivan de la clase base ios.
class istream: virtual public ios { // ... };
class ostream: virtual public ios { // ... };
Clase istream
La clase istream permite definir un flujo de entrada y admite mtodos para entrada formateada y
no formateada. El operador de extraccin >> est sobrecargado para todos los tipos de datos integra-
les de C++, haciendo posible operaciones de entrada de alto nivel.
Declaracin
class istream: virtual public ios // iostream
{
// ...
};
Un objeto de flujo es una instancia de una subclase de la clase ios. El operador de entrada >> se
aplica a un objeto istream y a un objeto variable.
objeto_istream >> objeto_variable
y trata de extraer una secuencia de caracteres correspondientes a un valor del tipo de objeto_va-
riable de objeto_istream. Si no hay caracteres, se bloquea la ejecucin precedente hasta que se
introducen caracteres. Como ejemplo, supongamos que cin est inicialmente vaco y se ejecutan las
sentencias siguientes:
int edad;
cin >> edad;
1007 Clases istream y ostream
Si el usuario introduce 25, los caracteres 2 y 5 se introducen en el objeto cin de istream. Se
leen los caracteres 2 y 5 de iostream, los convierte al valor entero 25 y almacena este valor en su
operando derecho edad.
Indicadores de estado
Qu suceder si el usuario introduce valores no apropiados? Por ejem-
plo, en el caso anterior, en lugar de escribir 25 se comete un error y se
teclea t5. Entonces el flujo cin contendr los caracteres t y 5. Es de-
cir, el operador >> trata de extraer un entero y encuentra el carcter t.
La clase istream tiene un atributo que es su estado o condicin.
El estado se representa mediante indicadores o banderas (flags) que re-
presentan la condicin o estado de un flujo (tabla 32.2). Las tres condi-
ciones bsicas son: good (el estado es bueno); bad (hay algo incorrecto
en el flujo); fail (la l tima operacin del flujo no tuvo xito). La clase istream contiene una va-
riable booleana llamada ban dera para cada uno de estos estados, con el indicador good inicializado
a true (verdadero) y los indi cadores bad y fail inicializados a falso.
En el ejemplo anterior, al encontrar la letra t cuando se intentaba leer un entero, se pone el indi-
cador good del flujo a falso, el indicador bad a true (verdadero) y fail tambin verdadera, y vi-
ceversa. Para cada uno de estos indicadores, la clase istream proporciona una funcin miembro
boolean que tiene el mismo nombre que su indicador, que informa del valor de ese indicador.
Variables de estado de ios
La clase ios tiene unas variables de estado que se especifican en la definicin enum.
class ios {
public:
enum { // valor del indicador de estado de error
goodbit = 0, // todos correctos
eofbit = 01, // fin de archivo
failbit = 02, // ltima operacin fallada
badbit = 04 // operacin no vlida
};
// otros miembros incluidos aqu
};
Los indicadores de formato de flujo slo se pueden cambiar explcitamente y slo mediante las
fun ciones de acceso de ios. En contraste, variables de estado de flujo se cambian implcitamente,
como resultado de operaciones de E/S. Por ejemplo, cuando un usuario introduce Control-D (o
Control-Z en computadoras DOS y VAX) para indicar el final del archivo; el indicador de estado
eof de cin se pone a 1, y se dice que el flujo est en un estado eof.
Programa
en ejecucin
25
cin

A recordar
El operador >> se suele denominar operador de
extraccin debido a que su comportamien to es
extraer valores de una clase istream.
Llamada a funcin Devuelve true si y slo si
cin.good ( )
Todo est correcto en istream
cin.bad ( )
Algo est mal en el istream
cin.fail ( )
No se puede completar la ltima operacin
Tabla 33.2
Indicadores de estado.
1008 Captulo 33 Flujos y archivos en C++
Se puede acceder individualmente a las cuatro variables de estado (goodbit, failbit, eofbit
y badbit) por sus funciones de acceso [good ( ), fail( ) y bad ( )]. Puede accederse tambin
a todas ellas colectivamente mediante la funcin rdstate ( ).
Ejemplo 33.1
main( )
{
cout << "cin.rdstate( ) = " << cin.rdstate << endl;
int n;
cin >> n;
cout << "cin.rdstate( ) = " << cin.rdstate() << endl;
}
Ejecucin
cin.rdstate( ) = 0
22
cin.rdstate( ) = 0
Segunda ejecucin
cin.rdstate( ) = 0
^D control-D
cin.rdstate( ) = 3
En la segunda ejecucin, el usuario puls Control-D para sealar el final del archivo. Esta
accin fija a eofbit y failbit de cin, que tienen valores numricos 1 y 2, toman el valor
total 3 de la variable de estado _state.
La funcin clear( )
El uso de Control-D (o Control-Z) para terminar la entrada es sencillo y adecuado. Pulsando
esta se cuencia de teclas, se fija el valor de eofbit en el flujo de entrada. Pero a continuacin,
si se desea utilizarlo de nuevo en el mismo programa, se ha de borrar (limpiar) primero. Esta
accin se realiza con la funcin clear( ).
Ejemplo 33.2
int main( )
{
int n, suma = 0;
while (cin >> n)
suma += n;
cout << "La suma parcial es " << suma << endl;
cin.clear();
while (cin >> n)
suma += n;
cout << "La suma total es" << suma << endl;
return 0;
}
Ejecucin
30 20 50 D
La suma parcial es 100
40 80
La suma total es 220
1009 Clases istream y ostream
Clase ostream
La clase ostream permite a un usuario definir un flujo de salida y admite mtodos para salidas
forma teadas y no formateadas. El operador de insercin se sobrecarga para todos los tipos de datos
integrales. Las clases of stream, ostrstream, constream, iostream y ostream_withassign
se derivan todas de ostream.
Al igual que istream, la clase ostream se deriva virtualmente de la clase ios para evitar
declara ciones mltiples cuando se declara iostream.
class ostream: virtual public ios // iostream
{
// ...
};
La nocin abstracta de un flujo se puede utilizar para ocultar estos detalles de bajo nivel del
progra mador, y de este modo la biblioteca iostream proporciona la clase ostream para representar
el flujo de caracteres de un programa en ejecucin a un dispositivo de salida arbitrario (vase la fi-
gura 33.5).
Despus de crear una clase ostream, se definen dos objetos ostream de la biblioteca iostream
de modo que cualquier programa que incluye el archivo de cabecera de la biblioteca tiene dos flu-
jos de salida del programa a cualquier dispositivo que el usuario est utilizando para salida: una ven-
tana, una terminal, etctera.
1. cout, un ostream para visualizar salida normal.
2. cerr, un ostream para visualizar mensajes de error o de diagnstico.
El mecanismo assert( ) normalmente escribe sus mensajes de diagnstico a cerr, no a
cout.
Figura 33.5 Simulacin de flujo ostream.
Programa
en ejecucin I nt r
o
d
u
z
c
a
u
n ent ero
El operador <<
Cuando el operador << se aplica a un objeto ostream y una expresin
objeto_ostream << expresin
se evaluar la expresin y se inserta la secuencia de caracteres correspondientes a ese valor en el ob-
jeto ostream. Por consiguiente, en el caso de:
const double PI = 3.1416;
cout << PI;
la funcin << convierte el valor double, 3.1416, en los caracteres correspondientes 3, ., 1, 4, 1 y
6 y los inserta uno a uno en cout:
Programa
en ejecucin
3.1416
1010 Captulo 33 Flujos y archivos en C++
Los caracteres permanecen realmente en el ostream, sin aparecer en la pantalla, hasta que se lim-
pia (fluye) el ostream, que, como su nombre sugiere, vaca el flujo en la pantalla.
Ejemplo 33.3
double f(double x, double y)
{
cout << "Introduccin de f";
.
.
.
cout << "salida de f";
return z;
}
La salida "Introduccin de f" puede no aparecer en nuestra pantalla cuando se espera ya
que el ostream no se ha limpiado.
Manipulador de salida
Un medio comn para limpiar un ostream es utilizar un manipulador de salida, un identificador
que afecta al propio ostream cuando se utiliza en una sentencia de salida, en lugar de generar sim-
plemente un valor que aparece en la pantalla. El manipulador ms utilizado para limpiar un flujo de
salida es el manipulador endl:
double f(double x, double y)
{
cout << "Introduccin de f" << endl;
...
return z;
}
Este manipulador inserta un carcter de nueva lnea ('\n') en el flujo ostream y lo limpia, ter-
minando una lnea de salida. Otro manipulador es flush que limpia el flujo ostream sin insertar
nada:
cout << "Salida de f" << flush;
Salida a la pantalla y a la impresora
Cuando se ejecutan los programas C++ normalmente se desear generar informacin en uno de los
dos dispositivos hardware tpicos: un monitor o una impresora. De hecho, habr ocasiones en que se
necesi tar generar informacin en ambos dispositivos durante la ejecucin de un programa.
Se puede escribir informacin en el dispositivo de salida utilizando el objeto cout, que est defi-
nido en el archivo de cabecera iostream. Los objetos son el ncleo de la programacin orientada a
objetos y su uso correcto en C++ potenciar los programas.
El mtodo ms comn de dirigir la salida a la pantalla es utilizar el objeto cout. El flujo de salida
que se pasa al objeto cout se dirige al flujo de salida estndar. Mediante el operador de insercin (<<)
se ponen datos en el flujo de salida.
cout << "Hola mundo, C++";
El operador de insercin se define para todos los tipos de datos bsicos: char, unsigned char,
signed char, short, unsigned short, int, unsigned int, long, unsigned long, float,
double, long double, void* y char* (un puntero a una cadena). El operador de insercin convier-
1011 Salida a la pantalla y a la impresora
te los datos a la derecha de << a una cadena de caracteres (char), tipo esperado por el objeto cout.
Por ejemplo, el siguiente valor entero, val_ent, se convierte a la cadena 47 y se pasa al objeto
cout:
int val_ent = 47;
cout << val _ent;
En el caso anterior se visualiza 47. La variable val_ent es una expresin, por lo que podra tam-
bin ser vlida la sentencia:
int i, j;
cout << i+j;
Operadores de insercin en cascada
El operador de insercin se puede poner en cascada, de modo que pueden aparecer varios elementos
en una sola sentencia C++. As, la sentencia
cout << 1 << 2 << 3 << 4;
generar una salida tal como
1 2 3 4
Si se desea escribir informacin de caracteres, se debe encerrar la informacin de salida entre
comi llas. La sentencia
cout << "Hola, programador de C++";
genera la salida
Hola, programador de C++
Los operadores en cascada pueden mezclar valores de caracteres y numricos. As, por ejemplo,
cout << "Total =" << Suma << endl;
visualizar
Total = 450
Suponiendo que el valor de la variable Suma es 450. El smbolo endl, como ya conoce el lector,
hace que el flujo de salida avance a la siguiente lnea. Se puede situar endl en cualquier parte del flu-
jo, aunque suele situarse al final de la lnea. La sentencia siguiente:
cout << "Los resultados son los siguientes:" << endl;
cout << "Enero " << Total_Ene << endl;
cout << "Febrero " << Total _Feb << endl;
cout << "Marzo " << Total_Mar << endl;
produce la salida
Los resultados son los siguientes:
Enero 300
Febrero 425
Marzo 106
Desde el punto de vista prctico, cada operador de insercin enva un dato (una constante, una ex-
presin o una variable) al flujo de salida, y se pueden concatenar datos de tipos diferentes en una ni-
ca expresin cout.
cout << Voltaje << Corriente << Resistencia;
La sentencia precedente escribir los valores almacenados en memoria en el mismo orden en que
estn escritos. El orden de salida ser el mismo que el orden listado en la sentencia cout, pero sin nin-
gn espacio entre los valores. Si desea espaciado entre los valores deber insertar caracteres en blan-
co dentro de la sentencia cout.
1012 Captulo 33 Flujos y archivos en C++
Las funciones miembro put ( ) y write ( )
Las clases definen datos y funciones miembro. Una funcin de una clase se llama mtodo o funcin
miembro. La clase ostream proporciona la funcin put ( ) para insertar un nico carcter en el flu-
jo de salida y la funcin write ( ), para insertar una cadena en el flujo de salida. Ambas funciones
devuelven un objeto cout.
Se puede escribir en un flujo de salida llamando a las funciones miembro put ( ) o write ( ).
Los formatos de estas funciones son:
dispositivo. put (valor_caracter);
dispositivo.write (valor _cadena, num);
El punto que separa dispositivo de la funcin put( ) es el operador de miembro (.). valor_
carac ter puede ser una constante, expresin o variable carcter (char) y valor_cadena una ca-
dena; num es un valor int utilizado para especificar el nmero de caracteres de la cadena a visualizar.
El dispo sitivo puede ser cualquier dispositivo de salida estndar. Para escribir, por ejemplo, un ca-
rcter en su impresora, se abrir PRN con of stream.
As, las sentencias siguientes visualizan dos caracteres ('Z' y 'l') en el flujo de salida:
cout.put('Z');
char letra = 'l';
cout.put(letra);
Si se desea escribir un bloque de caracteres, se utiliza la funcin miembro write, como en estos
ejemplos:
cout.write("Biblioteca", 3); // Se visualiza Bib
cout.write("Da Nacional", 12) << "\n";
cout.put(65) << "Antonio Molina \n";
cout.put('H').write("ola", 4) << " mundo C++ \n";
Al ejecutarse las tres sentencias anteriores se visualiza
Da Nacional
Antonio Molina
Hola mundo C++
Obsrvese que la primera funcin put ( ) contiene una constante de valor entero; el entero se
inter preta como un cdigo ASCII de la letra A que se visualiza.
La funcin write ( ) visualiza tantos caracteres como se especifique en el segundo argumento.
Si se especifica un nmero mayor que el nmero de caracteres de la cadena, la funcin visualiza cual-
quier cosa que resida en memoria a continuacin de la cadena.
Impresin de la salida en una impresora
El envo de la salida de un programa a la impresora es fcil con la clase ofstream. El formato es:
ofstream dispositivo (nombre_dispositivo)
y su uso requiere el archivo de cabecera fstream.
Ejemplo 33.4
El siguiente programa solicita al usuario el nombre y los apellidos. A continuacin, imprime el
nombre completo y el apellido en la impresora.
// SALIMPRE.CPP
// Imprime un nombre en la impresora
1013 Lectura del teclado
Lectura del teclado
Obtener informacin en un programa para su procesamiento se denomina lectura. En la mayora de
los sistemas actuales, la informacin se lee de una de las dos fuentes: de un teclado o de un archivo
de dis co. En esta seccin, aprender cmo se lee la informacin procedente del teclado.
La sentencia C++ que se utiliza para la lectura de datos del teclado es cin. Al igual que cout,
cin es un objeto predefinido en C++ y es parte del archivo de cabecera iostream.
Cuando se teclea, se genera un flujo de entrada. Al igual que con la salida, C++ utiliza un enfoque
orientado a objetos para la entrada. El objeto cin extrae caracteres del flujo de entrada, lo convierte
a cualquier tipo de dato diseado en la sentencia de entrada y lo almacena en la posicin de memoria
de seada.
Se utiliza el operador de extraccin >> para manejar la entrada de un flujo. El operador obtiene
datos del flujo y lo sita en una variable.
Un ejemplo de una sentencia de entrada en C++ es:
int valor;
cin >> valor;
El operador de extraccin se utiliza con el objeto cin para introducir datos desde el teclado. El
ope rador >> es fcil de recordar, ya que sugiere un flujo de datos desde la izquierda a la derecha.
El opera dor se denomina operador de extraccin, ya que extrae datos desde el flujo de entrada.
Al igual que el operador de insercin, el operador de extraccin se define para todos los tipos de
datos bsicos: char, unsigned char, signed char, short, unsigned short, int, un signed
int, long, unsigned long, float, double, long double, cadenas y punteros. El operador de
extraccin convierte los datos desde el flujo de entrada al tipo de datos esperado por la va riable que
recibe el dato.
El operador de extraccin se suele utilizar en unin con un operador de insercin y un mensaje de
peticin de datos o salutacin:
int Edad;
cout << "Introduzca edad del alumno:";
cin >> Edad;
Al igual que el operador de insercin, el operador de extraccin se puede poner en cascada, con
un formato similar a:
cin >> variable1 >> variable2 >> ... >> variablen
#include <fstream>
using namespace std;
int main( )
{
char nombre[20];
char apellidos[30];
cout << "Cul es su nombre?";
cin >> Nombre;
cout << "Cules son sus apellidos?";
cin >> Apellidos;
// Enviar nombre y apellidos a la impresora
ofstream impresora ("PRN");
impresora << "Su nombre completo es: \n";
impresora << apellidos << ", " << nombre << endl;
return 0;
}
1014 Captulo 33 Flujos y archivos en C++
Al ejecutarse el programa anterior, se podran seguir estas acciones:
Introduzca un nmero: 4321<Intro>
Ha introducido el nmero 4321
Lectura de datos carcter
Los caracteres se leen uno a uno, de acuerdo con las reglas siguientes:
1. Las tabulaciones, nuevas lneas y avances de pgina son ignorados por cin cuando se utili-
za el operador >>. Sin embargo, los espacios en blanco se pueden leer utilizando el operador
cin.
2. Los valores numricos se pueden leer como caracteres, pero cada dgito es un carcter indepen-
diente.
// Este programa lee datos carcter
#include <iostream> using namespace std;
void main( )
{
char Letra1;
char Letra2;
char Letra3;
cin >> Letra1 >> Letra2 >> Letra3;
cout << Letra1 << Letra2 << Letra3;
}
Algunas ejecuciones del programa anterior son:
Letras ABC 7547 5 47.543
Let ABC 754 47.
Ejemplo 33.5
int Edad;
float Altura;
cout << "Introduzca Edad y Altura:";
cin >> Edad >> Altura;
Cuando se procesan elementos mltiples en la entrada, se debe teclear al menos un espacio
en blan co entre los elementos de entrada. La entrada a las sentencias anteriores podra ser:
Introduzca Edad y Altura: 47 75
Ejemplo 33.6
Introduzca un valor entero desde el teclado y, a continuacin, visualcelo.
// ENDATOS1.CPP - Introducir un nmero utilizando el objeto cin
#include <iostream>
using namespace std;
void main( )
{
int val_e;
cout << "Introduzca un nmero:";
cin >> val_e;
cout << "\n Ha introducido un nmero" << val_e << endl;
}

A recordar
La notacin <Intro> se utiliza para representar
la pulsacin de la tecla INTRO (ENTER o
RETURN).
1015 Lectura del teclado
Obsrvese que la lectura de un carcter cada vez impone una restriccin en la introduccin de da-
tos carcter. Se requiere una variable independiente para cada carcter individual introducido. Por esta
cau sa, se necesita disponer de un medio para leer datos de cadena.
Lectura de datos cadena
Cuando se utiliza el operador de extraccin para lectura de datos tipo cadena, se producirn anomalas
si las cadenas constan de ms de una palabra separada por blancos.
Ejemplo 33.7
// Listado LEERCAD1.CPP
#include <iostream>
using namespace std;
int main( )
{
float salario;
cout << "\n Introduzca nombre, edad y salario: \n";
cin >> nombre >> edad >> salario;
cout << "\n Nombre: " << nombre << "\n Edad: "
<< edad << "\n Salario: " << salario << endl;
return 0;
}
Si la cadena de entrada contiene ms de una palabra, el objeto cin no leer ms que la primera
palabra, truncando el resto de la cadena.
#include <iostream>
using namespace std;
void main( )
{
char Nombre[30];
cin >> Nombre;
cout << '\n' << Nombre;
}
Cuando el usuario teclea
Pepe Mackoy
el sistema visualiza
Pepe
La razn de los caracteres truncados (Mackoy) es que cuando se leen datos cadena, el ope-
rador >> hace que el objeto cin termine la operacin de lectura, siempre que se encuentre cual-
quier espacio en blanco, de modo que la variable Nombre contiene slo Pepe.
El sistema para resolver esta anomala puede ser definir una cadena para cada palabra com-
pleta a intro ducir. Sin embargo, el mtodo ms eficiente consistir en utilizar funciones miem-
bro get ( ) y getline ( ).
Funciones miembro get ( ) y getline ( )
El objeto flujo cin contiene varias funciones miembro que permiten procesar entrada de cadenas y
ca racteres sin utilizar el operador de extraccin. La funcin miembro get ( ) lee un nico carcter
o una lnea de datos del teclado (cadenas).
La funcin miembro get ( ) tiene varios formatos y est definida en la clase istream. Desde
un punto de vista prctico, get ( ) se puede utilizar de dos modos: get ( ), se utiliza sin parme-
tros en cuyo caso el valor devuelto se puede utilizar con una referencia a un carcter; otro formato es
get con par metros (una referencia a un carcter).
1016 Captulo 33 Flujos y archivos en C++
El primer formato de get ( ) es sin parmetros. Este formato devuelve el valor del carcter
encon trado y devolver EOF (fin de archivo, end of file) si se alcanza el final del archivo. Su prototipo
de funcin es:
int get( )
Esta versin suele utilizarse normalmente en bucles de entrada.
Ejemplo 33.8
Lectura de caracteres con la funcin cin.get ( ).
#include <iostream>
using namespace std;
int main( )
{
char c;
while ((c = cin.get( ))!= EOF)
{
cout << "c:" << c << endl;
}
cout << "\n Terminado!\n";
return 0;
}
Al ejecutar el programa se produce esta salida.
Hola
c : H
c : o
c : l
c : a
Mundo
c : M
c : u
c : n
c : d
c : o
(Ctrl+Z) // o bien ^D, Ctrl+D
Terminado!
Explicacin
Cada llamada de la funcin cin.get ( ) lee un carcter ms de cin y lo devuelve a la varia-
ble c. A con tinuacin, la sentencia del interior del bucle inserta c en el flujo de salida. Estos ca-
racteres se acumulan en un bfer hasta que se inserta el carcter fin de lnea. Entonces se limpia
el bfer. Cuando se encuentra EOF (Ctrl+Z o Ctrl+D), se sale del bucle. En la mayora de las
computadoras EOF toma el valor 1.
Nota
Para salir de este programa, debe enviar un final de archivo desde el teclado. En compu -
tadoras DOS/Windows utilice Ctrl+Z; en sistemas UNIX utilice Ctrl+D.
Ejemplo 33.9
La constante entera EOF
void main( )
{
cout << "EOF =" << endl;
}
1017 Lectura del teclado
Al ejecutarse
EOF = 1
Otro formato de la funcin get ( ) lee el siguiente carcter del flujo de entrada en su parme-
tro que se pasa por referencia
istream& get(char& c);
Esta versin devuelve nul cuando se detecta el final del archivo, de modo que se puede utilizar
para controlar un bucle de entrada tal como ste:
while (cin.get(car))
Ejemplo 33.10
Lectura de caracteres con la funcin cmn.get ( ).
main( )
{
char c;
while (cin.get(c))
cout << c;
cout << endl;
}
En un lugar de Sierra Magina
En un lugar de Sierra Magina
Conocido por Carchelejo
Conocido por Carchelejo
^D
Ejemplo 33.11
Otra aplicacin de get ( ) con parmetro referencia.
// Listado get() << c << endl;
#include <iostream>
using namespace std;
int main( )
{
char a, b, c;
cout << "Introduzca tres letras:";
cin.get(a).get(b).get(c);
cout << "a:" << a << "\nb:" << b << "\nc:"
return 0;
}
Ejecucin
Introduzca tres letras: ono
a: o
b: n
c: o
1018 Captulo 33 Flujos y archivos en C++
Ejemplos
char cadena[80]; // Array de entrada
cin.get(cadena, 80); // Lectura de caracteres hasta que
// se encuentra un carcter nueva
// lnea o se
// han ledo 79 caracteres
Explicacin
Se crean tres variables de carcter. Se realizan tres llamadas a cin.get ( ) ... y se pone la
primera letra en a y vuelve a cin, de modo que se llama a cin.get(b) y se pone la siguiente
letra en b. El resultado final es que se llama a cin.get(c) y se pone la tercera letra en c.
Existe un tercer formato de la funcin get ( ), similar a la funcin getline ( ) que se ver
poste riormente. Su prototipo es
istream& get (char* bufer, int n, char sep = '\n');
Este formato lee caracteres del bfer hasta que se lean, o bien n 1 caracteres, o bien hasta
que se encuentre el carcter separador sep.
Regla del prototipo de get ( )
El primer parmetro es un puntero a un arreglo de caracteres; el segundo parmetro es el nme-
ro mximo de caracteres a leer ms uno y el tercer parmetro es el carcter de terminacin.
Ejemplo 33.12
Lectura de caracteres con cin.get ( )
main( )
{
char bufer[80];
cin.get(bufer, 8); // lee 7 caracteres del bufer
cout << "[" << bfer << "]\n";
cin.get(bufer, sizeof(bufer));
cout << "[" << bfer << "]\n";
}
Si el bfer almacena ABCDE|FGHIJ|KLMNO|PQRST|UVWXY|Z
la salida ser:
[ABCDE|F]
[GHIJ|KLMNO|PQRST|UVWXY|Z]
La funcin getline
Otra funcin miembro que se puede utilizar para la lectura de datos es getline ( ). La funcin get-
line ( ) permite a cin leer cadenas completas, incluyendo espacios en blanco; es muy similar a la
fun cin miembro get ( ) de dos o tres argumentos, excepto que el carcter de terminacin en get-
line ( ) se extrae del flujo de entrada y se considera. El formato de getline ( ) es
cin.getline(var_cad, long_max_cad+2, car_separador);
La funcin getline ( ) utiliza tres argumentos. El primer argumento, var_cad, es el identifi-
cador de la variable cadena. El segundo argumento es la mxima longitud de la cadena (nmero mxi-
mo de caracteres que se leen); la longitud ha de ser mayor que la cadena real al menos en dos
1019 Lectura del teclado
caracteres, para permitir los caracteres '\n' (CRLF) y '\ 0' (carcter nulo). El carcter de separa-
cin se lee y almacena como el siguiente al ltimo carcter de la cadena. La funcin getline ( )
inserta automticamente el carcter de terminacin nulo como el ltimo carcter de la cadena. Si no
se especifica ningn carcter de terminacin, se toma de modo determinado el carcter '\ n'.
Veamos un programa ejemplo:
// Uso de cin y getline para leer datos de cadena
#include <iostream>
using namespace std;
void main( )
{
char Nombre[40];
cin.getline(Nombre, 40);
cout << Nombre;
}
Al ejecutar el programa con la entrada Sierra de Cazorla la variable Nombre acepta toda la
cadena, es decir, todos los caracteres incluyendo blanco.
Ejemplo 33.13
// Este programa lee y escribe
// Nombre, Direccin y Telfono del usuario
#include <iostream>
using namespace std;
void main( )
{
//Definicin de arrays de caracteres
char Nombre[40];
char Calle[30];
char Ciudad[30];
char Provincia[30];
char CodigoPostal[5];
char Telefono[10];
//Lectura de datos
cin.getline(Nombre, 40);
cin.getline(Calle, 30);
cin.getline(Ciudad, 30);
cin.getline(Provincia, 30);
cin.getline(CodigoPostal, 5);
cin.getline(Telefono, 10);
//Visualizar datos
cout << Nombre;
cout << Calle;
cout << Ciudad;
cout << Provincia;
cout << CodigoPostal;
cout << Telefono;
}
Una entrada de datos en una ejecucin del programa:
Luis Enrique
Santiago Bernabu 45
Madrid
Madrid
28230
91-7151515
1020 Captulo 33 Flujos y archivos en C++
producir una salida tal como:
Luis Enrique
Santiago Bernabu 45
Madrid
Madrid
28230
91-7151515

Problemas en la utilizacin de getline( )
Aunque getline ( ) funciona cuando se leen datos tipo de cadena de modo consecutivo, se presen-
tarn problemas cuando se intenta utilizar una variable de cadena, despus de que se ha utilizado cin
para leer una variable carcter o numrica. Por ejemplo, supongamos un programa como el siguiente:
// PERGETL.CPP
// Programa que muestra el problema de utilizar cin.getline( )
// para leer una cadena despus de haber ledo una
// variable numrica o carcter
#include <iostream>
using namespace std;
void main( )
{
char Nombre[30];
int Edad;
cout << "Introduzca edad:";
cin << Edad;
cout << Edad;
cout << "Introduzca el nombre:";
cin.getline(Nombre, 30);
cout << Nombre;
}
Si se introducen en Edad y Nombre los valores 525 y Mortimer; es decir, suprimiendo la salida
del programa siguiente:
Introduzca edad: 525
Introduzca el nombre: Mortimer
Los valores que toman las variables citadas son:
Edad 525
Nombre '\n''\0'
La razn de los valores anteriores se debe a que al introducir 525, se debe pulsar la tecla
<INTRO>(ENTER) a la terminacin. Esta accin inserta un carcter CRLF (retorno de carro/avance de
lnea) y permanece en el bfer (memoria intermedia). Cuando la sentencia cin.getline (Nombre,
30) se ejecuta, se lee la memoria intermedia del teclado y se encuentra el carcter CRLF, dado que
ste es, en forma determinada, el carcter de separacin, se detiene la lectura y se inserta el carcter
de terminacin nulo en el arreglo. Por consiguiente, no se puede introducir el nombre.
Existen tres mtodos para resolver el problema:
1. Especificar un carcter de separacin diferente en la funcin getline ( ). El usuario debe
in troducir este carcter y se almacenar como ltimo carcter, antes del carcter nulo del
arreglo.
2. Limpiar la memoria intermedia (bfer) del teclado leyendo el carcter sobrante CRLF en una
variable basura, despus de leer cualquier dato numrico o carcter y antes de leer cualquier
dato cadena. De este modo en la variable basura (auxiliar) se almacenaran los datos de la me-
moria intermedia y ya se podran leer los caracteres tiles para la variable cadena.

A recordar
La diferencia entre get ( ) y getline ( ) es
que esta ltima almacena el carcter separador
o delimitador en la cadena antes de aadir el
carcter nulo.
1021 Manipuladores
// LIMPIARB.CPP
// Limpieza de la memoria intermedia con una
// variable auxiliar o basura
#include <iostream.h>
using namespace std;
void main( )
{
char Auxiliar[2];
char Nombre[30];
int Edad;
cout << "Introduzca edad:";
cin >> Edad; // Lectura de datos numricos
cout << Edad;
cin.getline(Auxiliar, 2); // Limpiar bfer de teclado
cout << "Introduzca el nombre:";
cin.getline(Nombre, 30); // Leer datos de cadena
cout << Nombre;
}
3. Utilizar una sentencia de lectura diferente; para ello se recurre a funciones de cadena de E/S
definidas en la biblioteca estndar de C y C++: gets ( ) y fgets ( ). Estas funciones se
encuen tran dentro del archivo de cabecera stdio.h.
Formateado
1
de salida
Si no se instruye al operador de insercin para realizar operaciones de formateado especfico, la sali-
da se formatea cuando se convierte un dato a un flujo de caracteres para la salida. La tabla 33.3 des-
cribe cmo formatea la salida el operador de insercin para diversos tipos de datos.
Ejemplos
cout << "CAZORLA#";
char letra = 'J';
cout << letra;
float f = 123.456789101, g = 1234567890.456;
cout << f << '#\n';
cout << g << '#\n';
int e = 123, en = 525;
cout << e << en << '\n';
Si se ejecutan las sentencias cout precedentes se produce una salida tal como sta:
CAZORLA#J123.456789#
1.234567E+009#
1235 25
Manipuladores
La entrada y salida de datos realizada mediante los operadores de insercin y extraccin puede for-
matearse ajustndola a izquierda o derecha, proporcionando una longitud mnima o mxima, preci-
sin, etc.
La solucin al problema son los manipuladores que son funciones especiales diseadas especfica-
mente para modificar el funcionamiento de un flujo. Los manipuladores manipulan el formato de un
objeto. La biblioteca iostream viene con un nmero de manipuladores incorporados, aunque pue-
den aadirse otros fcilmente. La mayora de las veces, los manipuladores se utilizan para indicar for-
1
La 22a. edicin (2001) del Diccionario de la Lengua Espaola de la Real Academia Espaola incorpora el verbo formatear
como acepcin informtica.
1022 Captulo 33 Flujos y archivos en C++
mateado, tal como la anchura de un campo, la precisin en nmeros de coma flotante, etc. Sin
embargo, los ma nipuladores no slo pueden realizar formateado, sino otras tareas. Normalmente los
manipuladores se utilizan en el centro de una secuencia de insercin o extraccin de flujos. La mayo-
ra de los manipulado res no tienen argumentos y estn diseados para realizar la tarea de formateado
lo ms sencilla posible.
Los manipuladores de flujos estn incluidos, fundamentalmente, en el archivo de cabecera ioma
nip. Los manipuladores se pueden utilizar tanto para flujos de entrada como de salida. Consulte el
manual de referencia de su compilador C++ para cualquier ampliacin de la informacin de este ca-
ptulo.
La tabla 33.4 recoge los manipuladores de flujo de E/S. Cualquiera de los manipuladores se pue-
de insertar en la sentencia cout como cualquier otro elemento.
Tabla 33.3
Conversin para salida de tipos de datos.
Tipo Tipo de conversin de salida
char
Los caracteres imprimibles se visualizan con una anchura de una columna. Los
caracteres de control, tales como nueva lnea, tabulacin, etc., pueden producir ms
caracteres de salida.
int
Cualquier tipo entero (int, short o long) se visualizan como nmeros decimales
con anchura suficiente para contener el nmero y un signo menos si el entero es
negativo.
cadena
La anchura en pantalla es igual a la longitud de la cadena.
float
Los nmeros reales en coma flotante se visualizan con seis dgitos decimales de
precisin. Los ceros no significativos no se visualizan. Si el nmero es muy grande o
muy pequeo, se visualiza el nmero con un exponente prefijado por la letra E y dos
dgitos (o tres dgitos si el tipo es double). La anchura siempre es lo suficiente para
mantener un signo menos y/o un exponente.
Tabla 33.4
Manipuladores de flujos.
Manipulador Accin
dec
Utiliza conversin decimal (de modo predeterminado).
hex
Utiliza conversin hexadecimal.
oct
Utiliza conversin octal.
ws
Extrae caracteres espacios en blanco.
endl
Inserta nueva lnea (se puede utilizar en lugar de '\n').
ends
Aade un carcter terminal nulo al flujo de salida ('\0').
flush
Limpia (fluye) un flujo de salida.
setbase (n)
Establece la base de conversin a n (0, 8, 10 o bien 16). 0 significa
decimal por defecto.
setprecision (n)
Establece la precisin de coma flotante a n.
setw (n)
Establece la anchura del campo a n.
setf ill (c)
setiosflags (f)
Establece el carcter de relleno a c.
Establece los bits de formato especificado por el argumento f de tipo long.
resetiosflags (f)
Pone a cero los bits de formato especificados por el argumento f de tipo
long.
1023 Manipuladores
La sintaxis tpica para utilizar los manipuladores es:
cout << setw (anchura del campo) << elemento de salida;
requiriendo la inclusin del archivo de cabecera iomanip
#include <iostream>
#include <iosmanip> ...
cout << setw(3) << i << setw(5) << i*i*i;
Bases de numeracin
Normalmente, los enteros se visualizan como decimales (nmeros escritos en base 10); es posible, sin
embargo, seleccionar una base de numeracin distinta (octal, 8, hexadecimal, 16) llamando a las fun-
ciones (manipuladores) dec, hex o bien oct. As, por ejemplo,
cout << dec << Total << endl;
visualiza el valor de Total en base 10. dec es importante para volver a seleccionar la base 10 (deci-
mal) despus de haber trabajado con otras bases. Por ejemplo, si Total toma el valor decimal 255, la
sen tencia
cout << hex << Total << endl;
produce la salida
ff valor hexadecimal correspondiente a 255 decimal
Se pueden entremezclar en una misma sentencia diversos manipuladores:
cout << "Base 10 =" << dec << Total
<< "Base 16 =" << hex << Total << endl;
Ejemplo 33.14
// Uso de los manipuladores
#include <iostream>
using namespace std;
#include <iomanip>
using namespace std;
void main( )
{
cout << "Valor hex de" << 40 << "en decimal es:"
<< hex << 40 << endl;
cout << "Valor octal de" << hex << 34 << "en hexadecimal es:"
<< oct << 34 << endl;
cout << dec; // Se restablece la numeracin decimal
}
La salida del programa es:
Valor hex de 40 en decimal es: 28
Valor octal de 22 en hexadecimal es: 42
ya que 40 decimal equivale a 28 (2 * 16 + 8 = 40) en hexadecimal, y 42 en base octal
(4 * 8 + 2 = 34) equivale a 22 en hexadecimal (2 * 16 + 2 = 34).
El manipulador setbase (int n) establece la base numrica a 8, 10 o 16. Este manipula-
dor para metrizado funciona igual que los manipuladores 8, 10 o 16. Un ejemplo puede ser
cout << setbase(10);
cin >> setbase(10);
1024 Captulo 33 Flujos y archivos en C++
Anchura de los campos
El manipulador setw ( ) proporciona un medio para fijar la anchura del formato de salida. En forma
predeterminada, los datos que entran y salen se corresponden con el espacio necesario para almacenar
ese dato, es decir, si se escribe una cadena de seis caracteres, la anchura de salida es 6. El prototipo de
setw es
setw(int n)
Un ejemplo del uso de setw( ) para visualizar un campo de ocho caracteres de ancho es
cout << "12345678901234567890" << endl;
cout << setw(8) << "Hola";
cout << "Mundo";
que produce la salida siguiente:
12345678901234567890
Hola mundo
Es decir, el ancho del campo Hola ha sido ocho caracteres y se alinea a derechas.
Ejemplo 33.15
#include <iostream>
#include <iomanip>
using namespace std;
void main( )
{
const int n = 100;
// visualizar los resultados en distintas bases
cout << "\n" << n << " "
<< oct << n << " "
<< hex << n << endl;
}
Ejemplo 33.16
// ANCHURA.CPP
#include <iostream>
using namespace std;
#include <iomanip>
void main( )
{
cout << setw(10) << "M" <<setw(10) << "N" << endl;
cout << setw(10) << 1 <<setw(10) << 7.77 << endl;
cout << setw(10) << 10 <<setw(10) << 77.77 << endl;
cout << setw(10) << 100 <<setw(10) << 777.77 << endl;
}
La salida ser
M N
1 7.77
10 77.77
100 777.77
1025 Manipuladores
Rellenado de caracteres
Siempre que se establece la anchura de un campo ms grande que la anchura de los datos, los espacios
adicionales se rellenan con caracteres blancos.
Se puede cambiar el carcter de relleno utilizando la funcin miembro fill ( ). As, por ejem-
plo, supongamos que se desea que el carcter de relleno sea un asterisco (*); un cdigo fuente que
realiza esa tarea puede ser:
cout << "12345678901234567890" << endl;
cout.width(15);
cout.fill('*');
cout << "Hola Mackoy" << endl;
float Z = 99.99;
cout.width(15);
cout << Z;
cuya salida ser:
12345678901234567890
****Hola Mackoy
******99.989998
Obsrvese que el carcter de relleno permanece hasta que se vuelve a cambiar.
char Relleno;
Relleno = cout.fill('*');
cout << "Hola Mackoy" << endl;
cout.fill(Relleno); // Se recupera el antiguo carcter de relleno
Precisin de nmeros reales
Si se visualiza un nmero en coma flotante, se visualizan hasta seis dgitos de precisin, en forma pre-
determinada. Los ceros a la derecha del punto decimal se suprimen. Se puede cambiar el nmero de
dgitos de precisin de seis a otro valor con el manipulador setprecision( ). Su argumento ente-
ro (n) especifica el n mero de dgitos significativos que se visualizarn. El formato es:
cout << setprecision(i);
Se puede utilizar la funcin miembro width ( ) para modificar la anchura del campo. El va-
lor del parmetro pasado ser la anchura en caracteres. Si se especifica una anchura en la entra-
da, lo que se hace es limitar la entrada a esa longitud. As,
cout.width(5);
cout << "ABC" << "DEF" << "GHI"
visualiza
ABC DEF GHI
y
cin.width(20);
limita la entrada a 20 caracteres.
Ejemplo 33.17
// Archivo PRECISIO.CPP
// Fija el nmero de posiciones decimales
1026 Captulo 33 Flujos y archivos en C++
Indicadores de formato
Cada flujo, es decir, cada objeto de la clase istream y ostream contiene un conjunto de informacio-
nes (indicadores) que especifican cal es en un momento dado su estatuto de formato. Este modo de
proceder es muy diferente del empleado por las funciones de C, tales como printf o scanf, en las
que a cada operacin de entrada/salida se le proporcionan los indicadores de formateado apropiado.
El mtodo empleado por C++ es ms eficiente que el empleado por C, ya que permite al usuario,
eventualmen te, ignorar por completo el mtodo empleado por C, un tanto complejo por otra parte.
Cada uno de estos indicadores (flags) pueden establecerse o reinicializarse utilizando un manipu-
lador incorporado. Los manipuladores setiosflags (long) y resetiosflags (long) reali-
zan fundamen talmente estas tareas.
Uso de setiosflags( ) y resetiosflags( )
El manipulador setiosflags ( ) se define como
setiosflags (long f)
y sirve para especificar, por ejemplo, si un dato se alinea a izquierda o derecha en un campo. En for-
ma predeterminada, los valores se alinean a derecha en un campo. La siguiente sentencia activa la op-
cin de alineacin a izquierda:
cout << setiosflags(ios::left);
Los argumentos de los manipuladores (bits indicadores de ios) realizan diversas tareas que se
reco gen en la tabla 33.5.
Se debe hacer preceder a cada argumento o bit de estado de la clusula ios::, que significar su
asociacin con la clase ios. Por ejemplo,
float pi = 3.14159;
cout << setiosflags(ios::fixed) << pi << endl;
#include <iostream>
using namespace std;
#include <iomanip>
void main( )
{
float prueba = 814.159265;
cout << setprecision(2) //2 dgitos significativos
<< prueba << endl;
cout << setprecision(3) //3 dgitos significativos
<< prueba << endl;
cout << setprecision(4) //4 dgitos significativos
<< prueba << endl;
cout << setprecision(5) //5 dgitos significativos
<< prueba << endl;
}
La salida del programa es:
814.16
814.159
814.1592
814.15924
Se puede utilizar tambin la funcin miembro precision ( ) para establecer la precisin.
Por ejem plo, la sentencia fija la precisin a 3, para la salida correspondiente:
cout.precision(3);
1027 Indicadores de formato
ha seleccionado un valor de coma flotante con notacin fija y la salida ser:
3.14159
Se selecciona la notacin cientfica utilizando la sentencia siguiente:
cout << setiosflags(ios::scientific) << pi << endl;
y se visualiza la salida siguiente:
3.14159e+00
Se pueden establecer mltiples indicadores en una operacin mediante operadores OR. Por
ejemplo,
cout << setiosflags(ios::dec|ios::showbase) << Total << endl;
Para limpiar o reponer los indicadores de estado, se deben utilizar resetiosflags ( ). Por
ejemplo, para limpiar o borrar el parmetro showbase, escribir
cout << resetiosflags(ios::showbase) << Total << endl;
As por ejemplo, si se activa la opcin de alineacin a la izquierda
cout << setiosflags(ios::left);
Tabla 33.5
Bits de estado (palabra de estado del formateado).
Bit de estado
(argumento)

Propsito
skipws
Salta espacios en blanco en operaciones de entrada.
left
Justifica la salida a la izquierda del campo.
right
Justifica la salida a la derecha del campo.
internal
Rellena el campo despus del signo o el indicador base.
dec
Activa conversin decimal.
oct
Activa conversin octal.
hex
Activa conversin hexadecimal.
showbase
Visualiza el indicador de base numrica.
Ejemplo:
044 (nmero octal)
0x2ea7 (nmero hex)
showpoint
Visualiza punto decimal en valores de coma flotante.
Ejemplo: 456.00
uppercase
Visualiza valores hexadecimales en maysculas.
Ejemplo: 4BFE
showpos
Visualiza nmeros enteros positivos precedidos del signo +.
scientific
Notacin cientfica en los nmeros en coma flotante.
Ejemplo: 3.1416e+00
fixed
Utiliza notacin en coma fija para nmeros en coma flotante.
Ejemplo: 1234.5
unitbuf
Vaca (limpia) las memorias (bfers) despus de cada escritura.
stdio
Vaca (limpia) las memorias intermedias despus de cada escritura
sobre stdout o stderr.
1028 Captulo 33 Flujos y archivos en C++
se desactivar con la sentencia
cout << resetiosflags(ios::left);
Otro ejemplo para visualizar resultados en diferentes bases de numeracin son las siguientes l-
neas de cdigo:
cout << setiosflags(ios::showbase)
<< "\n" << v << " "
<< oct << v << " "
<< hex << v << endl;
que produce la salida:
100 0144 0x64
Ejemplo 33.18
El programa FORMATO1.CPP muestra un sistema para formatear datos de salida de diversas
maneras.
//Archivo FORMATO1.CPP
#include <iostream>
using namespace std;
#include <iomanip>
void main( )
{
float v1 = 4500.25;
float v2 = 325.99;
float v3 = 54225.00;
cout << setiosflags(ios::showpoint|ios::fixed)
<< setprecision( 2)
<< setfill('*')
<< setiosflags(ios::right);
cout << "\n Saldo Final: $" << setw(10) << v1 << endl;
cout << "\n Saldo Final: $" << setw(10) << v2 << endl;
cout << "\n Saldo Final: $" << setw(10) << v3 << endl;
}
Al ejecutar el programa se produce:
Saldo Final: $***4500.25
Saldo Final: $****325.99
Saldo Final: $**54225.00
Ejemplo 33.19
// Archivo FORMATOS2.CPP
#include <iostream>
using namespace std;
#include <iomanip>
void main( )
{
const float p = 3.14159;
// Visualizar nmeros reales con diversos manipuladores
cout << setiosflags(ios::showpos|ios::scientific)
<< "\n El valor de PI es"
1029 Apertura de archivos
Las funciones miembro setf ( ) y unsetf ( )
Existe un segundo mtodo para establecer los indicadores de flujo: llamar a las funciones miembro
setf ( ) y unsetf ( ). Estas funciones son similares a los manipuladores setiosflags( ) y
resetio sflags( ). La diferencia reside en que setf ( ) y unsetf ( ) son verdaderas funciones
miembro. Se accede a las funciones miembro directamente:
cout.setf(ios:: scientific);
cout.unsetf(ios::scientific);
Archivos C++
C++ utiliza flujos (streams) para gestionar flujos de datos, incluyendo el flujo de entrada y de salida.
Un archivo es una secuencia de bits almacenados en algn dispositivo externo tal como un disco o
una cin ta magntica. Los bits se interpretan de acuerdo con el protocolo de algn sistema software. Si
estos bits se agrupan en bytes de 8 bits interpretados por el cdigo ASCII, entonces el archivo se de-
nomina archivo de texto y puede ser procesado por editores estndar. Si los bits se agrupan en palabras
de 32 bits repre sentando a pixeles de colores, entonces el archivo es un archivo grfico y se procesa
por un software de grfico especializado. Si el archivo es un programa ejecutable, entonces sus bits se
interpretan como instrucciones al procesador de la computadora.
En C++, un archivo es simplemente un flujo externo: una secuencia de bytes almacenados en dis-
co. Si el archivo se abre para salida, es un flujo de archivo de salida. Si el archivo se abre para entrada,
es un flujo de archivo de entrada.
La biblioteca de flujos contiene tres clases, ifstream, ofstream y fstream, y mtodos aso-
ciados para crear archivos y manejo de entrada y salida de archivos.
Las tres clases, ifstream, ofstream y fstream, se declaran en el archivo de cabecera fstream,
que, incidentalmente, incluye el archivo de cabecera iostream de modo que no necesita definir expl-
citamente #include <iostream si se incluye el archivo de cabecera fstream (#include <fs-
tream>).
C++ admite dos tipos de archivos: texto y binario. Los archivos de texto almacenan datos como
c digos ASCII. Los valores simples, tales como nmeros y caracteres nicos, estn separados por es-
pacios. Los archivos de texto se pueden utilizar para almacenamiento de datos o crear imgenes de
salida im presa que se pueden enviar ms tarde a una impresora.
Los archivos binarios almacenan flujos de bits, sin prestar atencin a los cdigos ASCII o a la
sepa racin de espacios. Son adecuados para almacenar objetos. Sin embargo, el uso de los archivos
binarios requiere usar la direccin de una posicin de almacenamiento.
Apertura de archivos
Antes de que un programa pueda leer o escribir de un disco, se debe abrir el archivo. El proceso de la
apertura de un archivo identifica la posicin del archivo en el programa. Para abrir un archivo de tex-
to C++ para lectura, se crea un objeto (un flujo) de la clase ifstream; para abrir un archivo para
<< setprecision( 3)
<< setw(15) << setfill('*')
<< setiosflags(ios::right)
<< p;
}
La salida de este programa es
El valor de PI es*****+3.142e+00
1030 Captulo 33 Flujos y archivos en C++
escritu ra, se crea un objeto de la clase of stream. Se puede entonces utilizar los nombres de los
flujos que se crean con los operadores de insercin y extraccin.
Al igual que los flujos, en forma predeterminada, cin y cout, los flujos de E/S de archivos ANSI
pueden trans ferir datos slo en una direccin. Esto significa que se deben abrir y manipular flujos in-
dependientes para lectura y escritura de archivos de texto. La biblioteca de flujos C++ ofrece un con-
junto de funciones miembro comunes a todas las operaciones de E/S de flujo de archivo.
Para abrir el archivo para lectura al comienzo de cada ejecucin de programa, el programa inclu-
ye la sentencia
ifstream aen("demo");
El objeto ifstream se llama aen. Est asociado con un archivo cuyo nombre del camino est
den tro de la lista de parmetros. Este parmetro se pasa al constructor de clase, que se utiliza para lo-
calizar y abrir el archivo.
La apertura de un archivo para escritura o salida se realiza con la sentencia ofstream definien-
do un objeto de la clase ofstream (output file stream).
ofstream archsal ("copy.out", ios_base::out);
Un archivo ofstream se puede abrir de dos maneras: 1) en modo salida (ios_base::out); 2)
en modo aadir (ios_base::app). En forma predeterminada, un archivo ofstream se abre en
modo salida. La definicin de archsa1 es equivalente a la del archivo de salida.
// abierta en modo salida por defecto
ofstream archsalida("copy.sal");
Si un archivo existe se abre en modo salida, todos los datos almacenados en ese archivo se des-
cartan. Si se desea aadir en lugar de reemplazar los datos dentro de un archivo existente, se debe
abrir el ar chivo en modo append (aadir). Los datos adicionales escritos en el archivo se aaden a
continuacin de su extremo final. De cualquier manera, si el archivo no existe, se crear.
Precaucin
Antes de intentar leer o escribir en un archivo, es siempre una buena idea verificar que se ha
abierto con xito. Se puede comprobar archsal del modo siguiente:
if (!archsal) { // apertura fallida
cerr << "no se puede abrir copy.sal para salida\n";
exit (1);
}
Ejercicio 33.1
El siguiente programa obtiene caracteres de la entrada estndar y los pone en el archivo copy.sal.
#include <fstream>
int main( )
{
// abrir un archivo copi.sal para salida;
of stream archsal ("copi.sal");
if (!archsal) {
cerr << "No se puede abrir copi.sal para salida:" << endl;
return 1;
}
char car;
while (cin.get (car))
archsal.put (car);
return 0;
}
1031 E/S en archivos
Apertura de un archivo slo para entrada
Leer un archivo especificado por el usuario y escribir su contenido en la salida.
Para esta operacin se utiliza un objeto de ifstream. La clase ifstream se deriva de istream.
Ejercicio 33.2
cerr << "incapaz abrir archivo de entrada:"
<<nombre_arch << "precaucin de salida\n";
#include <fstream>
using namespace std;
int main( )
{
char nombre_arch[80];
cout << "Nombre archivo: "; cin >> nombre_arch;
ifstream ArchEn(nombre_arch);
if (!ArchEn) {
cerr << "No se puede abrir: " << nombre_arch;
return -1;
}
char car;
while (ArchEn.get(car))
cout.put (car);
return 0;
}
Un archivo se puede desconectar del programa invocando la funcin miembro close ( ). Por
ejemplo,
ArchivoAct.close();
E/S en archivos
El sistema de E/S de C++ se puede utilizar para hacer E/S a archivos. Para poder realizar las opera-
ciones de E/S, se necesita incluir el archivo de cabecera fstream en el que se definen varias clases con
diferen tes funciones miembro. El archivo de cabecera <fstream> declara las clases ifstream, of
stream y fstream.
Especficamente la clase ifstream se deriva de la clase istream y permite a los usuarios acce-
der a archivos y leer datos de ellos. La clase ofstream se deriva de la clase ostream y permite a los
usua rios acceder a archivos y escribir datos en ellos. Por ltimo, la clase fstream se deriva de las cla-
ses ifstream y ofstream y permite a los usuarios acceder a los archivos para entrada y salida de
datos. Para abrir y gestionar adecuadamente las clases ifstream y ofstream relacionadas con un
sistema de archivos, se debe declarar con un constructor apropiado.
ifstream( );
ifstream (const char*, int = ios::in, int prot = filebuf::openprot);
ofstream( );
ofstream (const char*, int = ios::out, int prot = filebuf::openprot);
El constructor sin argumentos crea una variable que se asociar posteriormente con un archivo de
entrada. El constructor de tres argumentos toma como su primer argumento el archivo con nombre. El
segundo argumento especifica el modo archivo. El tercer argumento es para proteccin de archivos.
La apertura de un flujo de entrada se debe declarar en la clase ifstream.
La apertura de un flujo de salida se debe declarar en la clase ofstream.
Los flujos que realicen operaciones de entrada y salida deben declararse en la clase fstream.
1032 Captulo 33 Flujos y archivos en C++
Ejemplo
ifstream ent; // crea un flujo de entrada
ofstream sal; // crea un flujo de salida
fstream ambos; // crea un flujo de entrada y salida
La funcin open
Una vez creado el flujo, se puede utilizar la funcin open ( ) para asociarlo con un archivo. Esta fun-
cin es miembro de las tres clases de flujo. La declaracin de la funcin open ( ) (su prototipo) tie-
ne por prototipos:
void open (const char*, int = ios::in, int prot = filebuf::openprot);
// abre archivos ifstream
void open (const char*, int = ios::out, int prot = filebuf::openprot);
// abre archivo ofstream
Estas funciones se pueden utilizar para abrir y cerrar archivos apropiados. El prototipo estndar es:
void open(const char*nomarch, int modo, int acceso = (filebuf::openprot);
nombre del archivo (puede incluir determina cmo proteccin del archivo
un especificador de va de acceso) se abrir el archivo
Los posibles valores del argumento modo se definen como enumeradores de la clase ios (tabla
33.6).
El argumento acceso especifica el permiso de acceso en forma predeterminada que se asigna a un
archivo si se crea por la funcin constructor. El valor en forma predeterminada es filebuf::
openprot.
Ejemplos
// Ejemplo 1. Abre el archivo AUTOEXEC.BAT para entrada de texto
char* cAutoExec = "\\AUTOEXEC.BAT";
fstream f;
// abrir para entrada
f.open (cAutoExec, ios::in);
// Ejemplo 2. Abre el archivo DEMO.DAT para salida de texto
fstream f;
// abrir para salida
f.open ("DEMO.DAT", ios::out);
// Ejemplo 3. Abre el archivo PRUEBAS.DAT para entrada y
// salida binaria
fstream f;
// abrir para E/S de acceso aleatorio
f.open ("PRUEBAS.DAT", ios::in | ios::out | ios::binary);
Tabla 33.6
Valores del argumento modo de archivo.
Argumento Modo
ios::in
Modo entrada
ios::app
Modo aadir
ios::out
Modo salida
ios::ate
Abrir y buscar el fin del archivo
ios::nocreate
Genera un error si no existe el archivo
ios::trunc
Trunca el archivo a 0 si existe ya
ios::noreplace
Genera un error si el archivo existe ya
ios::binary
El archivo se abre en modo binario
1033 E/S en archivos
Consejo
A fin de abrir un flujo para entrada y salida, se deben especificar los dos valores del argumento modo,
ios::in e ios::out, como se muestra en el siguiente ejemplo:
fstream miflujo;
miflujo.open ("prueba", ios::in |ios::out);
Si open ( ) falla, el flujo ser nulo. A pesar de que es sintcticamente vlido abrir un archivo
me diante la funcin open ( ), no suele hacerse, ya que las clases ifstream, of stream y fstream
dispo nen de funciones constructor que abren automticamente el archivo. Las funciones construc tor
tienen los mismos parmetros y valores por omisin que la funcin open( ). En consecuencia, la
forma habitual de abrir un archivo ser la siguiente:
ifstream miflujo ("miarch"); // abre el archivo para entrada
Si no se puede abrir el archivo por algn motivo, el valor de la variable de flujo asociada miflujo
ser cero. Se puede utilizar el siguiente cdigo para confirmar que el archivo se ha abierto correcta-
mente:
ifstream miflujo ("archdemo");
if (!miflujo) {
cout << "No se puede abrir el archivo\n"; // error
}
Ejercicio 33.3
El siguiente ejemplo muestra el uso de las clases ifstream y ofstream. En el ejemplo, un archivo
denominado fuente se abre para lectura y otro archivo que se denomina destino se abre para escritura.
Si ambos archivos se abren con xito, el contenido del archivo fuente se copia en el archivo destino.
Si un archivo no se puede abrir con xito, se seala un error:
ifstream fuenteF( "fuente");
ofstream destinoF( "destino");
if(!fuenteF || !destinoF)
cerr << "Error fuente o destino open failed\n";
else
for(char c = 0; destinoF && fuenteF.get(c);)
destinoF.put(c);
La funcin close
La funcin miembro close cierra el archivo al que se conecta el objeto de flujo. La sintaxis de
clo se es:
void close( );
Ejemplo
char cAutoExec = "AUTOEXEC.BAT";
fstream f;
// abrir para entrada
f.open(cAutoExec, ios::in);
// sentencias de E/S
f.close() // cerrar bufer del flujo del archivo
Este ejemplo abre el archivo AUTOEXEC.BAT para entrada, realiza operaciones de entrada y, a
con tinuacin, cierra el bfer del flujo del archivo.

A recordar
La funcin close ( ) no utiliza parmetros ni
devuelve ningn valor.
1034 Captulo 33 Flujos y archivos en C++
Otras funciones miembro
Adems de las funciones miembro open y close, la biblioteca de flujos C++ ofrece las siguientes
fun ciones miembro y operadores:
La funcin miembro good, que devuelve un valor distinto de cero si no existe ningn error en
una operacin de flujo. La declaracin de esta funcin miembro es:
int good( );
La funcin miembro fail, que devuelve un valor distinto de cero si existe un error en una ope-
racin de flujo. La declaracin de esta funcin miembro es:
int fail( );
La funcin miembro eof, que devuelve un valor distinto de cero si el flujo ha alcanzado el final
del archivo. La declaracin de esta funcin miembro es:
int eof( );
El operador sobrecargado !, que determina el estado del error. Este operador toma un objeto de
flujo como un argumento.
Adems de las funciones miembro heredadas de las clases iostream. Las clases ifstream, of
s tream y fstream definen tambin sus propias funciones especficas:
void attach (int fd) Conecta el objeto flujo al flujo referenciado por el descriptor del
archivo fd.
filebuf* rdbuf ( ) Devuelve un arreglo filebuf asociado con el objeto flujo.
Lectura y escritura de archivos de texto
La lectura y escritura en un archivo de texto se puede realizar con los operadores << y >> con el flu-
jo abierto.
Ejercicio 33.4
El siguiente programa escribe un entero y un valor en coma flotante y una cadena en un archivo
llama do DEMO.
#include <iostream>
using namespace std;
#include <fstream>
int main( )
{
ofstream sal ("demo");
if (!sal) {
cerr << "No se puede abrir el archivo" << endl;
return -1;
}
sal << 10 << " " << 325.45 << endl;
sal << "Ejemplo de archivo de texto" << endl;
sal. close ( );
return 0;
}
1035 Lectura y escritura de archivos de texto
Ejercicio 33.5
El programa siguiente lee un nmero entero, un nmero en coma flotante, un carcter y una cadena
del archivo DEMO.
#include <iostream>
#include <fstream>
int main( )
{
char c;
int i;
float f;
char cad[40];
ifstream ent ("demo");
if (!ent)
{
cerr << "No se puede abrir el archivo" << endl;
return -1;
}
ent >> i;
ent >> f;
ent >> c;
ent >> cad;
ent.close( );
return 0;
}

A recordar
El operador >> produce en la lectura de archivos de texto una conversin de ciertos caracteres. Por ejemplo, se omiten los
caracteres en blanco.
Ejercicio 33.6
Escribir datos en un archivo de datos externo.
El archivo de cabecera <fstream> define la clase of stream que debe ser instanciada para es-
cribir en un archivo externo.
Es preciso abrir un archivo notas.dat como archivo de salida, construir el flujo de salida archsal
y conectar ese flujo al archivo. Estas operaciones se realizan invocando al constructor de la clase
ofstream.
El algoritmo de escritura en un archivo de datos externo prosigue asegurando que el archivo se ha
abierto adecuadamente. Para ello se invoca al operador de negacin sobrecargado (!archsal) y en
caso de que se produzca un error, se visualiza un mensaje de error y se termina el programa.
cerr << "Error: no se puede abrir archivo de Salida" << endl;
exit(1)
Si el proceso es correcto, el programa utiliza un bucle de entrada para leer nombres, identificar los
nmeros de expediente del alumno (exp) y las calificaciones (notas) de la entrada estndar y escribir-
las en un archivo externo.
1036 Captulo 33 Flujos y archivos en C++
#include <iostream> // define el flujo cout
#include <fstream> // define la clase ofstream
using namespace std;
#include <iostream>
#include <stdlib.h> // define la funcin exit()
using namespace std;
main( )
{
ofstream archsal( "notas.dat", ios::out);
if (!archsal) {
cerr << "Error: no se puede abrir archivo de salida" << endl;
exit(1);
}
char exp[7], nombre[20];
int nota;
cout << "\t1:";
int n = 1;
while (cin >> nombre >> exp >> nota) {
archsal << nombre << " " << exp << " " << nota << endl;
cout << "\t" << ++n << ":";
}
archsal.close( );
}
Al ejecutar el programa se introducen los datos de alumnos siguientes:
1:Mackoy 951234 7
2:Carrigan 962146 8
3:Mortimer 991045 9
4:Mackena 981146 9
5:Garca 982045 5
6:Rodrguez 991122 1
7:Lpez 961333 6
El archivo creado notas.dat es
Mackoy 951234 7
Carrigan 962146 8
Mortimer 991045 9
Mackena 981146 9
Garca 982045 5
Rodrguez 991122 1
Lpez 961333 6
Ejercicio 33.7
Leer datos de un archivo de datos externo (notas.dat).
La clase iostream se define en el archivo de cabecera <fstream>. Esta clase debe ser instancia-
da para leer de un archivo externo.
#include <iostream> // define el flujo cout
#include <fstream> // define la clase ofstream
#include <stdlib> // define la funcin exit( )
main( )
{
ifstream archen ("notas.dat, ios::in);
if (!archen) { // archivo de entrada, archen
cerr << "Error: no se puede abrir archivo de entrada" << endl;
exit(1);
1037 E/S binaria
E/S binaria
Existen varios modos de escribir y leer datos en binario desde un archivo. Un mtodo es utilizar las
fun ciones miembro put ( ) y get ( ). Otro mtodo es utilizar las funciones miembro read ( ) y
write ( ).
Funciones miembro get y put
La funcin miembro get ( ) de la clase istream lee el flujo de entrada byte a byte (carcter). Exis-
ten tres formatos de la funcin get( ):
istream & get (char& car);
Extrae un nico carcter del flujo de entrada, incluyendo espacio en blanco y lo almacena en car.
Devuelve el objeto istream al que se aplica.
}
char exp[7], nombre[20];
int nota, suma = 0, cuenta = 0;
while (archen >> nombre >> exp >> nota) {
suma += nota;
++cuenta;
}
archen.close( );
cout << "La nota media es" << float(suma)/cuenta << endl;
}
Si se ejecuta el programa anterior la salida ser:
La nota media es 6.42857
Ejemplo 33.20
El siguiente programa muestra en pantalla el contenido de un archivo.
#include <iostream>
#include <fstream>
int main(int argc, char *argv[ ])
{
char c;
if (argc != 2) {
cout << "error en nmero de archivos\n";
return 1;
}
ifstream ent(argv[1], ios::in | ios::binary);
if (!ent)
{
cerr << "No se puede abrir el archivo" << endl;
return -1;
}
while (ent) // ent ser 0 cuando se alcance eof
{
ent.get(c);
cout << c;
}
ent.close( );
return 0;
}
1038 Captulo 33 Flujos y archivos en C++
El bucle while se termina cuando ent alcanza el final del archivo (eof, end of file) cuyo valor
ser nulo.
Consejo
Existe un modo ms abreviado de implementar el bucle de lectura, aunque menos legible.
while (ent.get(c))
cout << c;
Funcin put ( )
La funcin miembro put ( ) proporciona un mtodo alternativo de sacar (escribir) un carcter en el
flu jo de salida. put ( ) acepta un argumento de tipo char y devuelve el objeto de la clase ostream
a la cual se invoca.
Sintaxis
ostream& put(char&c);
Ejemplo 33.21
Escribir una cadena de caracteres no ASCII en el archivo.
#include <iostream>
#include <fstream>
using namespace std;
int main ( )
{
char *p = "Hola colegas \n\r\xff";
of stream sal( "prueba", ios: :out | ios: :binary);
if (!sal)
{
cerr << "No se puede abrir el archivo" << endl;
return -1;
}
while (*p) sal.put(*p++);
sal.close( );
return 0;
}
Formato 2 de get ( )
El segundo formato de get ( ) lee tambin un nico carcter del flujo de entrada. La diferencia es
que devuelve el valor en lugar del objeto istream al que se aplica. Devuelve un tipo int en lugar de
char debido a que devuelve tambin la representacin final de archivo (eof), que suele representar-
se como 1 para diferencia de los valores del conjunto de caracteres.
Se ha de comprobar si el valor devuelto es final de archivo, para lo cual se compara con la
constan te EOF definida en el archivo de cabecera iostream. La variable asignada para contener el
valor devuel to por get ( ) se debe declarar del tipo int, de modo que pueda contener valores carc-
ter y EOF.
1039 E/S binaria
Funciones read y write
El segundo mtodo de lectura y escritura de datos binarios consiste en utilizar las funciones miembro
read( ) y write( ).
Sintaxis de write ( )
ostream& write(const char* buf, int num);
ostream& write(const unsigned char* buf, int num);
ostream& write(const signed char* buf, int num);
La funcin write ( ) inserta (escribe) el nmero especificado de bytes (num) en el flujo asocia-
do desde el bfer al que apunta buf (buf es un puntero a un arreglo de caracteres y num es el n-
mero de caracteres a escribir).
Sintaxis de read ( )
istream &read(char* buf, int num);
istream &read(unsigned char* buf, int num);
istream &read(signed char* buf, int num);
La funcin read ( ) lee num bytes del flujo asociado y los coloca en el bfer al que apunta buf.
El parmetro es un puntero a un arreglo de caracteres y num especifica el mximo nmero de caracte-
res a leer. La funcin miembro extrae caracteres hasta que se alcanza el final del archivo.
Ejemplo
#include <iostream>
using namespace std;
int main( )
{
int car;
while ((car = cin.get( )) != EOF)
cout.put(car);
return 0;
}
Ejemplo 33.22
Escritura de un arreglo de enteros y lectura posterior.
#include <iostream>
#include <fstream>
using namespace std;
int main( )
{
int n[6] = {15, 25, 35, 45, 50, 60};
register int i;
of stream salida ("prueba", ios::out | ios::binary);
if( !salida)
{
cerr << "No se puede abrir el archivo" << endl;
1040 Captulo 33 Flujos y archivos en C++
Caracteres ledos
Si se alcanza el final del archivo antes de que se haya ledo el nmero de caracteres especificado, la
funcin read ( ) se interrumpe, y el bfer contiene tantos caracteres como estn disponibles. Si se
desea conocer el nmero de caracteres que se han ledo, se puede utilizar la funcin miembro gcount
( ), que devuelve el nmero de caracteres ledos en la ltima operacin de entrada binaria.
Sintaxis
int gcount( );
Deteccin de EOF
El final del archivo se puede detectar mediante la funcin miembro eof(), que devuelve un valor dis-
tinto de cero cuando se alcanza el final de archivo; en caso distinto devuelve 0.
Sintaxis
int eof( );
Acceso aleatorio
En el sistema de E/S de C++ se pueden realizar accesos aleatorios mediante las funciones seekg ( )
y seekp( ). Este tipo de acceso es usual en trabajos de bases de datos que permiten seleccionar y
elegir registros especificados en archivos. Estas tareas se pueden realizar con las dos funciones miem-
bro so brecargadas heredadas de la clase istream.
El acceso a archivos aleatorios se describe generalmente en trminos de un puntero de archivo (no
confundir con punteros C++). Un puntero de archivo es un ndice o apuntador que apunta a una posi-
cin dada o a la posicin actual de un archivo entre el principio y el final de ste. La posicin actual
es el punto en el que comienza el siguiente acceso al archivo. Dado que el acceso al archivo se puede
clasificar en trminos de entrada y salida, la posicin actual se puede referenciar como posicin actual
obtener (current get position) y una posicin actual de poner (current put position). Las posicio-
nes actuales get y put ayudan a introducir las funciones miembro asociadas de las clases istream y
ostream para realizar acceso aleatorio o no secuencial.
El sistema de E/S de C++ maneja dos punteros asociados con el archivo. Uno es el puntero get,
que especifica la posicin del archivo en el que se producir la siguiente operacin de entrada. El otro
es el puntero put, que especifica la posicin del archivo en el que se producir la siguiente operacin
return -1;
}
salida.write ((unsigned char*) n, sizeof) (n));
salida.close();
// inicializa el array
for (i = 0; i < 6; i++)
n[i] = 0;
ifstream entrada ("prueba", ios::in | ios::binary);
entrada.read ((unsigned char*) n, sizeof) (n));
// presenta valores ledos del archivo
for (i = 0; i < 6; i++)
cout << n[i] << " ";
entrada.close( );
return 0;
}
1041 Acceso aleatorio
de sali da. Cada vez que se realiza una operacin de entrada o salida, el puntero correspondiente se
avanza automticamente. Sin embargo, cuando se utilizan las funciones seekg ( ) y seekp ( ) se
puede acceder al archivo de modo aleatorio, no secuencial.
Las funciones de acceso aleatorio a archivos seekg ( ) y tellg ( ) tienen la sintaxis siguiente:
istream& seekg (streampos pos);
istream& seekg (streamoff desp, seek_dir dir);
streampos tellg( );
La versin de un argumento de seekg ( ) mueve la posicin actual get a una posicin absoluta
pos, en el flujo de entrada. El argumento pos es de tipo streampos, que es, simplemente, un long
typedef definido en el archivo de cabecera iostream.
typedef long streampos;
La versin de dos argumentos de seekg ( ) mueve desp bytes relativos a la posicin actual get
en la direccin especificada por dir. Al igual que streampos, streamoff est definido en el archivo
ios tream.
typedef long streamoff;
El tipo de dir es un tipo enumerado seek_dir y est declarado como miembro de la clase ios:
enum seek_dir
{
beg, // principio del archivo
cur, // posicin actual
end // final del archivo
};
La funcin seekg ( ) desplaza el puntero get del archivo asociado un nmero de bytes especifica-
do en el desplazamiento desp, desde el origen dir que tomar uno de los tres valores del tipo enum
seek_dir.
La funcin miembro tellg( ) devuelve la posicin actual del flujo de entrada. Su prototipo es:
streampos tellg( ):
Figura 33.6 Posiciones de desplazamiento.
principio fin
archivo
desplazamiento
principio fin
archivo
desplazamiento
principio fin
archivo
desplazamiento
ios::beg
ios::cur
ios:: end
1042 Captulo 33 Flujos y archivos en C++
De igual modo, la clase ostream tiene dos funciones miembro: seekp ( ) y tellp( ). Su
sin taxis es:
ostream& seekp(streampos pos);
ostream& seekp(streamoff desp, seek_dir dir);
streampos tellp( );
La versin de un argumento de seekp ( ) mueve la posicin actual put a una posicin absoluta,
pos, en el flujo de salida. La versin de dos argumentos de seekp ( ) mueve desp bytes relativos a
la posicin put actual en la direccin especificada por dir. La funcin miembro tellp ( ) devuelve
la posicin actual del flujo de salida y devuelve un valor de 1 si ocurre un error.
Ejemplo
// uso de las funciones miembro seekg( ) y tellg( )
#include <fstream>
using namespace std;
void main( )
{
char nombre _arch[ ] = "CGP.CPP";
ifstream arch_en (nombre _arch);
if (!arch_en)
cerr << "no se puede abrir el archivo\""
<< nombre _arch << "\" " << endl;
// determinar la longitud del archivo
streampos inicio = arch_en.seekg(0, ios::beg).tellg( );
streampos fin = arch_en.seekg(0, ios::end) .tellg( );
// obtener un desplazamiento
streamoff desplazamiento;
cout << "introducir un desplazamiento (+) desde el principio
del archivo |""
<< nombre _arch << " \"" << endl
<< "cuya longitud tenga una longitud de archivo"
<< "(fin-inicio) << "caracteres:";
cin >> desplazamiento;
// ir a desplazamiento
arch_en.seekg (desplazamiento, ios::beg);
char car;
while (arch_en)
{
arch_en.get(car);
cout << car;
}
// cerrar
arch_en.close( );
}
Ejercicio 33.8
El siguiente programa permite especificar un nombre de archivo en la lnea de rdenes, seguido del
nme ro del byte que se desea modificar en el archivo. Luego se escribe una 'T' en la posicin espe-
cificada.
1043 Resumen
#include <iostream>
#include <fstream>
#include <stdlib>
using namespace std;
int main(int argc, char *argv[ ])
{
if(argc != 3) {
cout << "Usar: CAMBIA <nombre_archivo> <byte>\n";
return -1;
}
fstream sal(argv[1], ios::in | ios::out | ios::binary);
if (!sal) {
cerr << "No se puede abrir el archivo\n";
return -1;
}
sal.seekp(atoi(argv[2]), ios::beg);
sal.put('T');
sal.close( );
return 0;
}
Resumen
En este captulo se ha aprendido a manejar las operacio nes bsicas de entrada y salida. Asimismo, se
ha aprendi do a utilizar los manipuladores para ajustar opciones de formatos para seleccionar entradas
y salidas. Las carac tersticas tpicas de E/S son:
Cuatro objetos de flujos se crean en cada programa: cout, cin, cerr y clog.
La salida a pantalla utiliza el identificador de flu jo cout y el operador de insercin.
cout << valor;
La entrada del teclado se realiza mediante el iden tificador de flujo cin y el operador de inser-
cin >>.
cin >> variable;
La funcin miembro get ( ) se utiliza para leer caracteres individuales desde el teclado y se
pue de utilizar put ( ) para sacar caracteres.
Cuando se necesita leer una lnea completa de entrada, se debe utilizar la funcin miembro
getline( ).
Los manipuladores proporcionan un medio para modificar el flujo de entrada o salida. Un ejem-
plo tpico, muy utilizado, es endl que sirve para in sertar una nueva lnea o retorno de carro.
cout << setw(i)
cout << hex; cout << oct
cout << setprecision(i)
cout << setiosflags(indicador)
cout << resetiosflags(indicador)
Algunas funciones miembro utilizadas en el ca ptulo son:
cout.write(cadena, int i)
cout.put(char car);
cout.get( [char c]);
cin.get(cadena, int i[, char term]);
cin.getline(cadena, int [, char term]);
Un archivo de datos es una secuencia de bits alma cenados en algn dispositivo de almacena-
miento exter no (cinta, disco, disquete, CD-ROM). Excepto en cir cunstancias extremas, los archivos
1044 Captulo 33 Flujos y archivos en C++
externos son permanentes: se pueden crear y guardar un da y recuperarlos posteriormente del disco
en su computadora.
Se puede crear un archivo de datos externo utilizan do un editor de texto de igual forma que se crea
un archivo de programa y tambin crearse durante la eje cucin de un programa. Un archivo de datos
se puede leer muchas veces. De igual forma se puede instruir a un programa para que ejecute su salida
en un archivo de disco en vez de visualizarlo en la pantalla de su mo nitor.
La escritura de programas que manipulan archivos de disco externos se complica con el hecho de
que es tn implicados dos nombres diferentes: el nombre del archivo externo, que aparece en el direc-
torio del disco en el que reside el archivo (nombre por el que cono ce el sistema operativo al archivo),
y el nombre del objeto flujo, que es el nombre interno por el que el programa accede al archivo. En
C++, la conexin entre estos dos nombres se realiza mediante el uso de una funcin especial, denomi-
nada open.
Para abrir y cerrar archivos se crean objetos if s tream y of stream. Para comenzar a escribir
en un archivo, se debe crear primero un objeto of stream y, a continuacin, asociar ese objeto con
un archivo espe cfico en su disco. Para utilizar objetos of stream, debe asegurarse de incluir el ar-
chivo fstream en su pro grama.
Nota: fstream incluye iostream, por ello no es necesario incluir explcitamente iostream.
Los archivos necesitan ser abiertos y cerrados. Es tas operaciones se realizan con las funciones
open ( ) y close ( ). Las funciones implicadas en las operaciones de lectura y escritura son: get/
put y read/write as como seekp y seekg.
Ejercicios
33.1. Cules son los operadores de insercin y ex traccin? Cul es su misin?
33.2. Explicar las diferencias de los formatos de cin.get ( ) y cin.getline( ).
33.3. Escribir un programa que escriba en los cuatro objetos iostream estndar: cin, cout, cerr
y clog.
33.4. Escribir un programa que solicite al usuario in troducir su nombre completo y, a continuacin,
que se visualice en pantalla.
33.5. Escribir un programa que utilice las funciones setf( ), fill( ) y width( ) que produz-
ca la siguiente salida formateada:
Captulo 5 Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
Captulo 6 Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
Captulo 7 Flujos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
33.6. Escribir el cdigo para cada uno de los siguien tes casos:
a) Imprimir el entero 12345 en un campo de 12 dgitos justificados a izquierda.
b) Imprimir 3.14159 en un campo de 12 dgitos con ceros precedentes.
c) Leer un entero en decimal e imprimirlo en octal.
d) Leer un entero en hexadecimal e imprimirlo en decimal.
33.7. Escribir una funcin set_width (int w) que establezca el campo con cout a w colum-
nas.
33.8. Escribir un cdigo C++ que lea una lnea de texto y haga eco de la lnea con todas las letras
maysculas suprimidas.
1045 Problemas
33.9. Abrir un archivo de texto, leerlo lnea a lnea en un arreglo de caracteres y escribirlo de nuevo
en otro archivo.
33.10. Copiar un archivo, en modo binario, carcter a carcter.
33.11. Escribir un programa que lea un nmero arbi trario de enteros de un archivo que tiene el si-
guiente formato:
8
7
6
5
4
3
2
1
33.12. Escribir un programa que lea un texto de la ter minal y almacene el texto en un nuevo archivo
de texto con el nombre miarch.txt. El nue vo archivo debe tener la misma estructura de lnea
que el archivo de texto escrito desde la terminal. Adems, todas las letras minsculas deben
ser traducidas a letras maysculas.
33.13. Se dispone de un archivo telefono.txt, con nombres y nmeros de telfonos en orden al-
fabtico. Escribir un programa que aa da una nueva persona. El programa debe leer un nom-
bre y nmero de telfono de la nueva persona desde la terminal y, a continuacin, inser tar esta
informacin en el lugar correcto del archivo de modo que permanezca ordenado. Sugerencia:
utilice un archivo temporal.
33.14. La informacin acerca de un conjunto de perso nas se ha almacenado en un archivo binario
para propsitos estadsticos. La informacin almacenada de cada persona es el nombre, altu ra,
tamao del calzado, edad y estado civil. Con el objeto de procesar los datos, el sexo de cada
persona tiene que conocerse, aunque esta infor macin no se incluye en el archivo. Escribir un
programa que lea el archivo y cree dos archivos nuevos, uno que contenga slo mujeres y otro
slo hombres. Por cada persona del archi vo, el programa debe pedir al operador si una perso-
na es un hombre o una mujer.
33.15. Escribir un programa que permita crear un ar chivo inventario de los libros de una librera, as
como calcular e imprimir el valor total del in ventario. Los campos de cada libro deben ser,
como mnimo, ttulo, autor, ISBN, precio, can tidad, editorial.
Problemas
33.1. Se trata de declarar una cadena de caracteres para leer el nombre del archivo, y abrir el archivo
en modo entrada. En caso de que no pueda abrirse se debe crear de escritura. Una observacin
importante es que siempre se ha de comprobar si la apertura del archivo ha sido rea lizada con
xito, puesto que es una operacin que realiza el sistema operativo para el progra ma y queda
fuera de nuestro control; esta opera cin se realiza comprobando el valor de good.
33.2. Una vez que se conoce el funcionamiento del operador de extraccin >> para el objeto cin, es
muy sencillo su uso para cualquier objeto de la clase fstream, ya que su funcionamiento es
el mismo. El archivo abierto contiene nmeros enteros, pero al ser un archivo de texto esos
nmeros estn almacenados no de forma bina ria sino como una cadena de caracteres que re-
presentan los dgitos y el signo del nmero en forma de secuencia de sus cdigos ASCII bina-
1046 Captulo 33 Flujos y archivos en C++
rios. Esto no quiere decir que haya que leer l nea a lnea y en cada una de ellas convertir las
secuencias de cdigos de caracteres a los nme ros enteros en binario correspondiente, para
almacenarlos as en la memoria. Este trabajo es el que realiza el operador de extraccin >>
cuando tiene una variable de tipo entero en su parte derecha. Es la misma operacin de con-
versin que realiza el operador de extraccin cuando lee secuencias de cdigos de teclas des-
de la entrada estndar. Escribir un programa que utilice dos contadores para contar los
positivos y negativos y una variable para leer el dato del archivo. El programa visualizar ade-
ms el archivo.
Listas, pilas
y colas en C++
Captulo
Contenido
Tipo abstracto de datos lista
Operaciones de listas enlazadas, clase lista
Insercin en una lista
Buscar un elemento y recorrer una lista enlazada
Borrado de un nodo
Lista doblemente enlazada
Clase ListaDoble
Listas enlazadas genricas
Tipo abstracto de datos pila
Clase pila con arreglo (array)
Pila genrica con listas enlazadas
Tipo abstracto de datos cola
Cola con un arreglo ( array) circular
Cola genrica con una lista enlazada
Bicolas: colas de doble entrada
Resumen
Ejercicios
Problemas
Introduccin
Este captulo estudia tres tipos abstractos de datos fundamentales, las listas enlazadas, las pilas y las
colas. Cada una de ellas se implementa en C++ utilizando clases. La estructura lista enlazada (ligada o
encadenada, linked list) es una coleccin de elementos (denominados nodos) dispuestos uno a con-
tinuacin de otro, cada uno de ellos conectado al siguiente elemento por un enlace o referencia. En
el captulo se define una clase que agrupa la representacin de una lista y las operaciones que se apli-
34
1048 Captulo 34 Listas, pilas y colas en C++
can: insertar, buscar, borrar elementos y recorrer la lista. De igual modo se muestra el tipo abstracto
de datos (TAD) que representa a las listas enlazadas.
La Pila es una estructura de datos que almacena y recupera sus elementos atendiendo a un estricto
orden; las pilas se conocen tambin como estructuras LIFO (last-in/first-out, ltimo en entrar/primero
en salir). Las pilas se utilizan en compiladores, sistemas operativos y programas de aplicaciones.
El tipo abstracto de datos cola almacena y recupera sus elementos atendiendo a un estricto orden.
Las colas se conocen como estructuras FIFO (first-in/first-out, primero en entrar/primero en salir),
debido a la forma y orden de insercin y de extraccin de elementos de la cola. Las colas tienen nu-
merosas aplicaciones en el mundo de la computacin: colas de mensajes, colas de tareas a realizar por
una impresora, colas de prioridades.
Conceptos clave
Cola Nodo
Enlace Pila
Estructura enlazada template
Lista Tipo abstracto de datos
Lista doblemente enlazada
Tipo abstracto de datos lista
Una lista almacena informacin del mismo tipo, con la caracterstica de que puede contener un nme-
ro indeterminado de elementos, y que estos elementos mantienen un orden explcito. Este ordena-
miento explcito se manifiesta en que, en s mismo, cada elemento contiene la direccin del siguiente
elemento. Cada elemento es un nodo de la lista.
Figura 34.1 Representacin grfica de una lista enlazada.
dato siguiente dato dato dato siguiente siguiente
cabeza actual cola
Una lista es una estructura de datos dinmica. El nmero de nodos puede variar rpidamente en
un proceso, aumentando los nodos por inserciones, o bien, disminuyendo por eliminacin de nodos.
Las inserciones se pueden realizar por cualquier punto de la lista. Por la cabeza (inicio), por el fi-
nal (cola), a partir o antes de un nodo determinado de la lista. Las eliminaciones tambin se pueden
realizar en cualquier punto de la lista; adems, se eliminan nodos dependiendo del campo de informa-
cin o dato que se desea suprimir de la lista.
Especificacin formal del TAD lista
Matemticamente, una lista es una secuencia de cero o ms elementos de un determinado tipo.
(a
1
, a
2
, a
3
, ... , a
n
) siendo n >= 0,
si n = 0 la lista es vaca.
Los elementos de la lista tienen la propiedad de que estn ordenados de forma lineal, segn las
posiciones que ocupan en la misma. Se dice que a
i
precede a a
i+1
para i = 1 ..., n1; y que a
i

sucede a a
i1
para i = 2 ... n.
Para formalizar el tipo de dato abstracto lista a partir de la nocin matemtica, se define un con-
junto de operaciones bsicas con objetos de tipo Lista. Las operaciones:
1049 Operaciones de listas enlazadas, clase lista
L Lista, x Dato, p puntero
Listavacia(L) Inicializa la lista L como lista vaca.
Esvacia(L) Determina si la lista L est vaca.
Insertar(L,x,p) Inserta en la lista L un nodo con el campo dato x, delante del
nodo de direccin p.
Localizar(L,x) Devuelve la posicin/direccin donde est el dato x.
Suprimir(L,x) Elimina de la lista el nodo que contiene el dato x.
Anterior(L,p) Devuelve la posicin/direccin del nodo anterior a p.
Primero(L) Retorna la posicin/direccin del primer nodo de la lista L.
Anula(L) Vaca la lista L.
Estas operaciones son las bsicas para manejar listas. En realidad, la decisin de qu operaciones
son las bsicas depende de las caractersticas de la aplicacin que se va a realizar con los datos de la
lista. Tambin depender del tipo de representacin elegido para las listas. As, para aadir nuevos
nodos a una lista se implementan, adems de insertar( ), versiones de sta como:
insertarPrimero(L,x) Inserta un nodo con el dato x como primer nodo de la lista L.
insertarFinal(L,x) Inserta un nodo con el dato x como ltimo nodo de la lista L.
Otra operacin tpica de toda estructura enlazada de datos es recorrer. Consiste en visitar cada
uno de los datos o nodos de que consta. En las listas enlazadas, normalmente se realiza desde el nodo
cabeza al ltimo nodo o cola de la lista.
Operaciones de listas enlazadas, clase lista
La implementacin del TAD lista requiere, en primer lugar, declarar la clase Nodo en la cual se encie-
rra el dato (entero, real, doble, carcter, o referencias a objetos) y el enlace. Adems, la clase Lista
con las operaciones (creacin, insercin,...) y el atributo cabeza de la lista.
Clase Nodo
Una lista enlazada se compone de una serie de nodos enlazados mediante punteros. La clase Nodo de-
clara las dos partes en que se divide: dato y enlace. Adems, el constructor y la interfaz bsica; por
ejemplo, para una lista enlazada de nmeros enteros:
typedef int Dato;
// archivo de cabecera Nodo.h
#ifndef _NODO_H
#define _NODO_H
class Nodo
{
protected:
Dato dato;
Nodo* enlace;
public:
Nodo(Dato t)
{
dato = t;
enlace = 0; // 0 es el puntero NULL en C++
}
Nodo(Dato p, Nodo* n) // crea el nodo y lo enlaza a n
{
dato = p;
enlace = n;
}
Dato datoNodo( ) const
{
return dato;
}
1050 Captulo 34 Listas, pilas y colas en C++
Nodo* enlaceNodo( ) const
{
return enlace;
}
void ponerEnlace(Nodo* sgte)
{
enlace = sgte;
}
};
#endif
Ejemplo 34.1
Se declara la clase Punto para representar un punto en el plano, con su coordenada x y y. Tam-
bin, se declara la clase Nodo con el campo dato de tipo Punto.
// archivo de cabecera punto.h
class Punto
{
double x, y;
public:
Punto(double x = 0.0, double y = 0.0)
{
this.x = x;
this.y = y;
}
};
La declaracin de la clase Nodo consiste, sencillamente, en asociar el nuevo tipo de dato; el
resto no cambia.
typedef Punto Dato;
#include "Nodo.h"
Figura 34.2 Acceso a una lista con apuntador cabeza denominado primero.
primero
4.15 5.25 71.5 10.5 NULL
Clase Lista, construccin de una lista
La creacin de una lista enlazada implica la definicin de, al menos, las clases Nodo y Lista. La cla-
se Lista contiene el puntero de acceso a la lista enlazada, de nombre primero, que apunta al nodo
cabeza; tambin se puede declarar un puntero al nodo cola, como no se necesita para implementar las
operaciones, no se ha declarado.
Las funciones miembro de la clase Lista implementan las operaciones de una lista enlazada: in-
sercin, bsqueda... El constructor inicializa primero a null, (lista vaca). Adems, crearLista( )
construye iterativamente el primer elemento (primero) y los elementos sucesivos de una lista enla-
zada. La declaracin de la clase es:
// archivo Lista.h
class Lista
{
protected:
Nodo* primero;
1051 Operaciones de listas enlazadas, clase lista
public:
Lista( ) { primero = NULL;}
void crearLista( );
void insertarCabezaLista(Dato entrada);
void insertarUltimo(Dato entrada);
void insertarLista(Dato entrada);
Nodo* buscarLista(Dato destino);
void eliminar(Dato entrada)
Nodo* ultimo( );
void visualizar( );
};
El ejemplo 34.2 declara una lista para un tipo particular de dato: int. Lo ms interesante del
ejemplo es la codificacin, paso a paso, de la funcin crearLista( ).
Ejemplo 34.2
Crear una lista enlazada de elementos que almacenen datos de tipo entero.
Antes de escribir su cdigo, se muestra el comportamiento de la funcin crearLista( )
de la clase Lista. En primer lugar, se crea un nodo con un valor y su direccin se asigna a pri-
mero:
primero = new Nodo(19);
Ahora se desea aadir un nuevo elemento con el valor 61, y situarlo en el primer lugar de la
lista. Se utiliza el constructor de Nodo que enlaza con otro nodo ya creado:
primero = new Nodo(61,primero);
Por ltimo, para obtener una lista compuesta de 4, 61, 19 se debe ejecutar:
primero = new Nodo(4,primero);
A continuacin se escribe la funcin crearLista( )que codifica las acciones descritas an-
teriormente. Los valores se leen del teclado; termina con el valor clave 1.
void Lista::crearLista( )
{
int x;
primero = NULL;
cout << "Termina con 1" << endl;
do {
cin >> x;
if (x != 1)
{
primero = new Nodo(x,primero);
}
}while (x != 1);
}
primero
19 NULL
primero
19 NULL 61 19 NULL 19 NULL
primero
4 19 61
1052 Captulo 34 Listas, pilas y colas en C++
Insercin en una lista
Hay distintas formas de aadir un elemento, segn la posicin o punto de insercin. ste puede ser:
En la cabeza (elemento primero) de la lista.
En el final o cola de la lista (elemento ltimo).
Antes de un elemento especificado, o bien,
Despus de un elemento especificado.
Insertar en la cabeza de la lista
La posicin ms fcil y, a la vez, ms eficiente donde insertar un nuevo elemento en una lista es por
la cabeza. El proceso de insercin se resume en este algoritmo:
1. Crear un nodo e inicializar el campo dato al nuevo elemento. La direccin del nodo creado se
asigna a nuevo.
2. Hacer que el campo enlace del nodo creado apunte a la cabeza (primero) de la lista.
3. Hacer que primero apunte al nodo que se ha creado.
El cdigo fuente de insertarCabezaLista:
void Lista::insertarCabezaLista(Dato entrada)
{
Nodo* nuevo ;
nuevo = new Nodo(entrada);
nuevo > ponerEnlace(primero); // enlaza nuevo con primero
primero = nuevo; // mueve primero y apunta al nuevo nodo
}
Caso particular
insertarCabezaLista tambin acta correctamente si se trata de aadir un primer nodo a una lista
vaca. En este caso, primero apunta a NULL y termina apuntando al nuevo nodo de la lista enlazada.
Insercin al final de la lista
La insercin al final de la lista es menos eficiente debido a que, normalmente, no se tiene un puntero
al ltimo nodo y entonces se ha de seguir la traza desde la cabeza de la lista hasta el ltimo nodo y, a
continuacin, realizar la insercin. Una vez que la variable ultimo apunta al final de la lista, el enla-
ce con el nuevo nodo es sencillo:
ultimo > ponerEnlace(new Nodo(entrada));
El campo enlace del ltimo nodo queda apuntando al nodo creado y as se enlaza, como nodo
final, a la lista.
A continuacin, se codifica la funcin insertarUltimo( ) que implementa esta forma de in-
sertar. Tambin la funcin que recorre la lista y devuelve el puntero al ltimo nodo.
void Lista::insertarUltimo(Dato entrada)
{
Nodo* ultimo = this > ultimo( );
ultimo > ponerEnlace(new Nodo(entrada));
}
Nodo* Lista::ultimo( )
{
Nodo* p = primero;
if (p == NULL ) throw "Error, lista vaca";
while (p > enlaceNodo( )!= NULL) p = p > enlaceNodo( );
return p;
}
1053 Buscar un elemento y recorrer una lista enlazada
Insertar entre dos nodos de la lista
La insercin de un nodo no siempre se realiza al principio o al final de la lista; puede hacerse entre
dos nodos cualesquiera. Por ejemplo, en la lista de la figura 34.3 se quiere insertar el elemento 35 en-
tre los nodos con los datos 20 y 40.
Figura 34.3 Insercin entre dos nodos.
primero
9 20
35
40 NULL
El algoritmo para la operacin de insertar entre dos nodos (n1, n2) requiere las siguientes eta-
pas:
1. Crear un nodo con el elemento y el campo enlace a NULL.
2. Poner campo enlace del nuevo nodo apuntando a n2, ya que el nodo creado se ubicar justo
antes de n2.
3. Si el puntero anterior tiene la direccin del nodo n1, entonces poner su campo enlace
apuntando al nodo creado.
La funcin insertarLista( ), miembro de la clase Lista, implementa la operacin:
void Lista::insertarLista(Nodo* anterior, Dato entrada)
{
Nodo* nuevo;
nuevo = new Nodo(entrada);
nuevo > ponerEnlace(anterior > enlaceNodo( ));
anterior > ponerEnlace(nuevo);
}
Antes de llamar a insertarLista( ) es necesario buscar la direccin del nodo n1, esto es, del
nodo a partir del cual se enlazar el nodo que se va a crear.
Otra versin de la funcin tiene como argumentos el dato a partir del cual se realiza el enlace, y
el dato del nuevo nodo: insertarLista(Dato testigo, Dato entrada). El algoritmo de esta
versin, primero busca el nodo con el dato testigo a partir del cual se inserta, y a continuacin realiza
los mismos enlaces que en la anterior funcin.
Buscar un elemento y recorrer una lista enlazada
La bsqueda de un elemento en una lista enlazada recorre la lista hasta encontrar el nodo con el ele-
mento. La funcin de bsqueda devuelve el puntero al nodo (en caso negativo, devuelva NULL). Otro
planteamiento consiste en devolver true si encuentra el nodo y false si no est en la lista.
Figura 34.4 Bsqueda en una lista.
primero
5.75 41.25 101.43 0.25 NULL
ndice
La funcin buscarLista de la clase Lista, utiliza el puntero indice para recorrer la lista,
nodo a nodo. Primero, se inicializa indice al nodo cabeza (primero), a continuacin se compara
el dato del nodo apuntado por indice con el elemento buscado; si coincide, la bsqueda termina; en
caso contrario, indice avanza al siguiente nodo. La bsqueda termina cuando se encuentra el nodo,
o bien cuando se ha terminado de recorrer la lista y entonces indice toma el valor NULL.
1054 Captulo 34 Listas, pilas y colas en C++
Nodo* Lista:: buscarLista(Dato destino)
{
Nodo* indice;
for (indice = primero; indice!= NULL; indice=indice>enlaceNodo( ))
if (destino == indice > datoNodo( ))
return indice;
return NULL;
}
Ejemplo 34.3
Se escribe una funcin alternativa a la bsqueda del nodo que contiene un campo dato. Ahora
tambin se devuelve un puntero a nodo, pero con el criterio de que ocupa una posicin, pasada
como argumento, en una lista enlazada.
La funcin es un miembro pblico de la clase Lista, por consiguiente tiene acceso a sus
miembros. Como se busca por posicin en la lista, se considera posicin 1 la del nodo cabeza
(primero); posicin 2 al siguiente nodo, y as sucesivamente.
El algoritmo de bsqueda comienza inicializando indice al nodo cabeza de la lista. En cada ite-
racin del bucle se mueve indice un nodo hacia adelante. El bucle termina cuando se alcanza la
posicin deseada, o bien cuando se ha recorrido toda la lista, es decir, si indice apunta a NULL.
Nodo* Lista::buscarPosicion(int posicion)
{
Nodo* indice;
int i;
if (0 > posicion) // posicin ha de ser mayor que 0
return NULL;
indice = primero;
for (i = 1 ;(i < posicion) && (indice != NULL); i++)
indice = indice > enlaceNodo( );
return indice;
}
Recorrer una estructura enlazada implica visitar cada uno de sus nodos. Para una lista, el recorri-
do se hace desde el primer nodo hasta el ltimo. El fin del bucle diseado para recorrer la lista ocurre
cuando el campo enlace es NULL. La codificacin que se escribe a continuacin, escribe el dato de
cada nodo de la lista.
void Lista::visualizar( )
{
Nodo* n;
int k = 0;
n = primero;
while (n != NULL)
{
char c;
k++; c = (k%15 != 0 ? ' ' : '\n');
cout << n > datoNodo( ) << c;
n = n > enlaceNodo( );
}
}
Borrado de un nodo
Eliminar un nodo de una lista enlazada supone enlazar el nodo anterior con el nodo siguiente al que
se desea eliminar y liberar la memoria que ocupa. El algoritmo que se enfoca para eliminar un nodo
que contiene un dato, sigue estos pasos:
1055 Lista doblemente enlazada
1. Bsqueda del nodo que contiene el dato. Se ha de obtener la direccin del nodo a eliminar y la
direccin del anterior.
2. El enlace del nodo anterior que apunte al nodo siguiente del que se elimina.
3. Si el nodo a eliminar es el cabeza de la lista (primero), se modifica primero para que tenga
la direccin del siguiente nodo.
4. Por ltimo, la memoria ocupada por el nodo se libera.
La funcin miembro, de la clase Lista, eliminar( ) implementa el algoritmo; recibe el dato
del nodo que se quiere borrar, desarrolla su propio bucle de bsqueda con el fin de disponer de la di-
reccin del nodo anterior.
void Lista::eliminar(Dato entrada)
{
Nodo *actual, *anterior;
bool encontrado;
actual = primero;
anterior = NULL;
encontrado = false;
// bsqueda del nodo y del anterior
while ((actual != NULL) && !encontrado)
{
encontrado = (actual > datoNodo( ) == entrada);
if (!encontrado)
{
anterior = actual;
actual = actual > enlaceNodo( );
}
}
// enlace del nodo anterior con el siguiente
if (actual != NULL)
{
// distingue entre cabecera y resto de la lista
if (actual == primero)
{
primero = actual > enlaceNodo( );
}
else
{
anterior > ponerEnlace(actual > enlaceNodo( ));
}
delete actual;
}
}
Lista doblemente enlazada
Una lista doblemente enlazada se caracteriza porque se puede avanzar hacia adelante, o bien hacia atrs.
Cada nodo de una lista doble tiene tres campos, el dato y dos punteros; uno apunta al siguiente nodo de
la lista y el otro al nodo anterior. La figura 34.5 muestra una lista doblemente enlazada y un nodo.
Figura 34.5 Lista doblemente enlazada. a) Lista con tres nodos; b) nodo.
cabeza
a)
atrs Dato adelante
b)
1056 Captulo 34 Listas, pilas y colas en C++
Las operaciones de una lista doble son similares a las de una Lista: insertar, eliminar, buscar, re-
correr... Tanto para insertar, como para borrar un nodo, se deben realizar ajustes de los dos punteros
del nodo. La figura 34.6 muestra los movimientos de punteros para insertar un nodo; como se obser-
va, intervienen cuatro enlaces.
Figura 34.6 Insercin de un nodo en una lista doblemente enlazada.
Nodo actual
La operacin de eliminar un nodo de la lista doble necesita enlazar, mutuamente, el nodo anterior
y el nodo siguiente del que se borra, como se observa en la figura 34.7.
Figura 34.7 Eliminacin de un nodo en una lista doblemente enlazada.
Nodo de una lista doblemente enlazada
La clase NodoDoble agrupa los componentes del nodo de una lista doble y las operaciones de la in-
terfaz.
// archivo de cabecera NodoDoble.h
class NodoDoble
{
protected:
Dato dato;
NodoDoble* adelante;
NodoDoble* atras;
public:
NodoDoble(Dato t)
{
dato = t;
adelante = atras = NULL;
}
Dato datoNodo( ) const { return dato; }
NodoDoble* adelanteNodo( ) const { return adelante; }
NodoDoble* atrasNodo( ) const { return atras; }
void ponerAdelante(NodoDoble* a) { adelante = a; }
void ponerAtras(NodoDoble* a) { atras = a; }
};
Clase ListaDoble
La clase ListaDoble encapsula las operaciones bsicas de las listas doblemente enlazadas. La clase
dispone del puntero cabeza para acceder a la lista, apunta al primer nodo. El constructor de la clase
inicializa la lista vaca. La declaracin de la clase es:
1057 Clase ListaDoble
// archivo ListaDoble.h
class ListaDoble
{
protected:
Nodo* cabeza;
public:
ListaDoble( ) { cabeza = NULL;}
void insertarCabezaLista(Dato entrada);
void insertarUltimo(Dato entrada);
void insertarLista(Dato entrada);
void insertaDespues(NodoDoble* anterior, Dato entrada)
NodoDoble* buscarLista(Dato destino);
void eliminar(Dato entrada);
Nodo* ultimo( );
void visualizar( );
};
Insertar un nodo en una lista doblemente enlazada
Se pueden aadir nodos a la lista de distintas formas, segn la posicin donde se inserte. La posicin
de insercin puede ser:
En la cabeza de la lista.
Al final de la lista.
Antes de un elemento especificado.
Despus de un elemento especificado.
Se describe e implementa la operacin que inserta un nodo despus de otro. Las otras formas de
insertar siguen los mismos pasos que los descritos en una lista enlazada, teniendo en cuenta el doble
enlace de los nodos.
Insertar despus de un nodo
El algoritmo de la operacin que inserta un nodo despus de otro, n, requiere las siguientes etapas:
1. Crear un nodo, nuevo, con el elemento.
2. Poner el enlace adelante del nodo creado apuntando al nodo siguiente de n. El enlace atras
del nodo siguiente a n (si n no es el ltimo nodo) tiene que apuntar a nuevo.
3. Hacer que el enlace adelante del nodo n apunte al nuevo nodo. A su vez, el enlace atras del
nuevo nodo debe apuntar a n.
void ListaDoble::insertaDespues(NodoDoble* anterior, Dato entrada)
{
NodoDoble* nuevo;
nuevo = new NodoDoble(entrada);
nuevo > ponerAdelante(anterior > adelanteNodo( ));
if (anterior > adelanteNodo( ) != NULL)
anterior > adelanteNodo( ) > ponerAtras(nuevo);
anterior> ponerAdelante(nuevo);
nuevo > ponerAtras(anterior);
}
Eliminar un nodo de una lista doblemente enlazada
Quitar un nodo de una lista doble supone ajustar los enlaces de dos nodos, el nodo anterior con el
nodo siguiente al que se desea eliminar. El puntero adelante del nodo anterior debe apuntar al nodo
siguiente, y el puntero atras del nodo siguiente debe apuntar al nodo anterior.
El algoritmo es similar al del borrado para una lista simple, ms simple ya que ahora la direccin
del nodo anterior se encuentra en el campo atras del nodo a borrar. Los pasos a seguir:
1. Bsqueda del nodo que contiene el dato.
2. El puntero adelante del nodo anterior tiene que apuntar al puntero adelante del nodo a eli-
minar (si no es el nodo cabecera).
1058 Captulo 34 Listas, pilas y colas en C++
3. El puntero atras del nodo siguiente a borrar tiene que apuntar a donde apunta el puntero
atras del nodo a eliminar (si no es el ltimo nodo).
4. Si el nodo que se elimina es el primero se modifica cabeza para que tenga la direccin del
nodo siguiente.
5. La memoria ocupada por el nodo es liberada.
La implementacin del algoritmo es:
void ListaDoble::eliminar(Dato entrada)
{
NodoDoble* actual;
bool encontrado = false;
actual = cabeza;
// Bucle de bsqueda
while ((actual != NULL) && (!encontrado))
{
encontrado = (actual > datoNodo( ) == entrada);
if (!encontrado)
actual = actual > adelanteNodo( );
}
// Enlace de nodo anterior con el siguiente
if (actual != NULL)
{
//distingue entre nodo cabecera o resto de la lista
if (actual == cabeza)
{
cabeza = actual > adelanteNodo( );
if (actual > adelanteNodo( ) != NULL)
actual > adelanteNodo( ) > ponerAtras(NULL);
}
else if (actual > adelanteNodo( ) != NULL) // No es el ltimo
{
actual>atrasNodo( ) > ponerAdelante(actual>adelanteNodo( ));
actual>adelanteNodo( ) > ponerAtras(actual>atrasNodo( ));
}
else // ltimo nodo
actual>atrasNodo( ) >ponerAdelante(NULL);
delete actual;
}
}
Listas enlazadas genricas
La definicin de una lista est muy ligada al tipo de datos de sus elementos; as se han puesto ejem-
plos en los que el tipo es int, otros en los que el tipo es double. C++ dispone del mecanismo tem-
plate para declarar clases y funciones con independencia del tipo de dato de al menos un elemento.
Entonces, el tipo de los elementos de una lista genrica ser un tipo genrico T, que ser conocido en
el momento de crear la lista. La declaracin es:
template <class T> class ListaGenerica
template <class T> class NodoGenerico
ListaGenerica<double> lista1; // lista de nmero reales
ListaGenerica<string> lista2; // lista de cadenas
Declaracin de la clase ListaGenrica
Las operaciones del tipo lista genrica son las especificadas anteriormente en la clase lista. La cla-
se que representa un nodo tambin es una clase genrica; se declara a continuacin:
// archivo NodoGenerico.h
template <class T> class NodoGenerico
{
1059 Listas enlazadas genricas
protected:
T dato;
NodoGenerico <T>* enlace;
public:
NodoGenerico (T t)
{
dato = t;
enlace = 0;
}
NodoGenerico (T p, NodoGenerico<T>* n)
{
dato = p;
enlace = n;
}
T datoNodo( ) const
{
return dato;
}
NodoGenerico<T>* enlaceNodo( ) const
{
return enlace;
}
void ponerEnlace(NodoGenerico<T>* sgte)
{
enlace = sgte;
}
};
La declaracin de la ListaGenerica es:
// archivo ListaGenerica.h
template <class T> class ListaGenerica
{
protected:
NodoGenerico<T>* primero;
public:
ListaGenerica( ){ primero = NULL;}
NodoGenerico<T>* leerPrimero( ) const { return primero;}
void insertarCabezaLista(T entrada);
void insertarUltimo(T entrada);
void insertarLista(NodoGenerico<T>* ant, T entrada);
NodoGenerico<T>* ultimo( );
void eliminar(T entrada);
NodoGenerico<T>* buscarLista(T destino);
};
La implementacin de las funciones miembro, tambin son funciones genricas, se realiza en el
mismo archivo que declara la clase, en ListaGenerica.h. Esta implementacin no difiere de la rea-
lizada con una lista, salvo que hay que especificar la genericidad. Se escriben algunas de las funciones.
insercin entre dos nodos de la lista
template <class T> void
ListaGenerica<T>::insertarLista(NodoGenerico<T>* ant, T entrada)
{
NodoGenerico<T>* nuevo = new NodoGenerico<T>(entrada);
nuevo > ponerEnlace(ant > enlaceNodo( ));
ant > ponerEnlace(nuevo);
}
borrado del primer nodo encontrado con el dato entrada
template <class T>
void ListaGenerica<T>:: eliminar(T entrada)
{
1060 Captulo 34 Listas, pilas y colas en C++
NodoGenerico<T> *actual, *anterior;
bool encontrado;
actual = primero;
anterior = NULL;
encontrado = false;
// bsqueda del nodo y del anterior
while ((actual != NULL) && !encontrado)
{
encontrado = (actual > datoNodo( ) == entrada);
if (!encontrado)
{
anterior = actual;
actual = actual > enlaceNodo( );
}
}
// enlace del nodo anterior con el siguiente
if (actual != NULL)
{
// Distingue entre cabecera y resto de la lista
if (acual == primero)
{
primero = actual > enlaceNodo( );
}
else
anterior > ponerEnlace(actual > enlaceNodo( ));
delete actual;
}
}
Iterador de ListaGenerica
Un objeto iterador se disea para recorrer los elementos de un contenedor. Un iterador de una lista
enlazada accede a cada uno de sus nodos, hasta alcanzar el ltimo elemento. El constructor del objeto
iterador inicializa el puntero actual al primer elemento de la estructura; la funcin siguiente( )
devuelve el elemento actual y hace que ste quede apuntando al siguiente elemento. Si no hay si-
guiente, devuelve NULL.
La clase ListaIterador implementa el iterador de una lista enlazada genrica, y en consecuen-
cia tambin ser clase genrica.
template <class T> class ListaIterador
{
private:
NodoGenerico<T> *prm, *actual;
public:
ListaIterador(const ListaGenerica<T>& list)
{
prm = actual = list.leerPrimero( );
}
NodoGenerico<T> *siguiente( )
{
NodoGenerico<T> * s;
if (actual != NULL)
{
s = actual;
actual = actual > enlaceNodo( );
}
return s;
}
void iniciaIterador( ) // pone en actual la cabeza de la lista
{
actual = prm;
}
}
1061 Clase Pila con arreglo (array)
Tipo abstracto de datos pila
Una pila (stack) es una coleccin ordenada de elementos a los que slo se puede acceder por un nico
lugar o extremo. Los elementos de la pila se aaden o quitan slo por su parte superior, la cima de la
pila. Las pilas son estructura de datos LIFO (last-in, first-out/ltimo en entrar, primero en salir).
La operacin insertar (push) sita un elemento en la cima de la pila y quitar (pop) elimina o
extrae el elemento cima de la pila.
Figura 34.8 Operaciones bsicas de una pila.
Insertar
Cima
Quitar
Fondo
Especificaciones del tipo pila
Las operaciones que sirven para definir una pila y poder manipular su contenido son las siguientes.
Tipo de dato Elemento que se almacena en la pila
Operaciones
CrearPila Inicia
Insertar (push) Pone un dato en la pila
Quitar (pop) Retira (saca) un dato de la pila
Pila vaca Comprueba si la pila no tiene elementos
Pila llena Comprueba si la pila est llena de elementos
Limpiar pila Quitar todos sus elementos y deja la pila vaca
CimaPila Obtiene el elemento cima de la pila
Tamao de la pila Nmero de elementos mximo que puede contener la pila
La pila se puede implementar guardando los elementos en un array, y por consiguiente su dimen-
sin es fija, la que tiene el array. Otra forma de implementacin consiste en construir una lista enla-
zada, cada elemento de la pila forma un nodo de la lista; la lista crece o decrece segn se aaden o se
extraen, respectivamente, elementos de la pila; sta es una representacin dinmica y no existe limi-
tacin en su tamao excepto la memoria de la computadora.
Clase Pila con arreglo (array)
Guardar los elementos de una pila tiene el problema del crecimiento, el mximo de elementos est
prefijado por la longitud del arreglo. La operacin Pila llena se implementa cuando se utiliza un arre-
glo para almacenar los elementos; cuando se utiliza una estructura dinmica no tiene sentido ya que
crece indefinidamente.
La declaracin de la clase se realiza en el archivo PilaLineal.h:
typedef tipo TipoDeDato; // tipo de los elementos de la pila
const int TAMPILA = 49;
class PilaLineal
{
1062 Captulo 34 Listas, pilas y colas en C++
private:
int cima;
TipoDeDato listaPila[TAMPILA];
public:
PilaLineal( )
{
cima = 1; // condicin de pila vaca
}
// operaciones que modifican la pila
void insertar(TipoDeDato elemento);
TipoDeDato quitar( );
void limpiarPila( );
// operacin de acceso a la pila
TipoDeDato cimaPila( );
// verificacin estado de la pila
bool pilaVacia( );
bool pilaLlena( );
};
Las funciones son sencillas de implementar, teniendo en cuenta la caracterstica principal de esta
estructura: inserciones y borrados se realizan por el mismo extremo, la cima de la pila.
La operacin insertar un elemento en la pila incrementa el apuntador cima y asigna el nuevo
elemento a listaPila. Cualquier intento de aadir un elemento en una pila llena genera una excep-
cin debido al Desbordamiento de la pila.
void PilaLineal::insertar(TipoDeDato elemento)
{
if (pilaLlena( ))
{
throw "Desbordamiento pila";
}
//incrementar puntero cima y copia elemento
cima++;
listaPila[cima] = elemento;
}
La operacin quitar elimina un elemento de la pila. Copia, en primer lugar, el valor de la cima
de la pila en una variable local, aux, y, a continuacin, decrementa el ndice cima de la pila. Devuel-
ve el elemento eliminado por la operacin. Si se intenta eliminar o borrar un elemento en una pila va-
ca se produce error, se lanza una excepcin.
TipoDeDato PilaLineal::quitar( )
{
TipoDeDato aux;
if (pilaVacia( ))
{
throw "Pila vaca, no se puede extraer";
}
// guarda elemento de la cima
aux = listaPila[cima];
// decrementar cima y devolver elemento
cima;
return aux;
}
Antes de quitar
Despus de quitar
elemento
devuelve
cima cima = cima1
elemento
Figura 34.9 Quitar el elemento cima.
1063 Pila genrica con listas enlazadas
Pila genrica con listas enlazadas
Utilizar una lista enlazada para implementar una pila hace que sta pueda crecer o decrecer dinmi-
camente. Como las operaciones de insertar y extraer en el TAD Pila se realizan por el mismo extremo
(cima de la pila), las acciones correspondientes con la lista se realizarn siempre por el mismo extre-
mo de la lista.
Nota
Una pila realizada con una lista enlazada crece y decrece dinmicamente. En tiempo de eje-
cucin, se reserva memoria segn se ponen elementos en la pila, y se libera memoria segn
se extraen elementos de la pila.
Clase PilaGenerica y NodoPila
Los elementos de la pila son los nodos de la lista, con un campo para guardar el elemento y otro de
enlace. Las operaciones a implementar son, naturalmente, las mismas que si la pila se implementara
Ejemplo 34.4
Escribir un programa que cree una pila de enteros. Se realizan operaciones de aadir datos a
la pila, quitar ... .
Se supone implementada la clase pila con el tipo primitivo int. El programa crea una pila de
nmeros enteros, inserta en la pila elementos ledos del teclado (hasta leer la clave 1), a con-
tinuacin extrae los elementos de la pila hasta que se vaca. En pantalla deben escribirse los n-
meros ledos en orden inverso por la naturaleza de la pila. El bloque de sentencias se encierra
en un bloque try para tratar errores de desbordamiento de la pila.
#include <iostream>
using namespace std;
typedef int TipoDeDato;
#include "pilalineal.h"
int main( )
{
PilaLineal pila; // crea pila vaca
TipoDeDato x;
const TipoDeDato CLAVE = 1;
cout << "Teclea elemento de la pila(termina con 1)" << endl;
try {
do {
cin >> x;
pila.insertar(x);
}while (x != CLAVE);
cout << "Elementos de la Pila: " ;
while (!pila.pilaVacia( ))
{
x = pila.quitar( );
cout << x << " ";
}
}
catch (const char * error)
{
cout << "Excepcion: " << error;
}
return 0;
}
1064 Captulo 34 Listas, pilas y colas en C++
con arreglos, salvo la operacin que controla si la pila est llena, pilaLlena, que ahora no tiene sig-
nificado ya que las listas enlazadas crecen indefinidamente, con el nico lmite de la memoria.
El tipo de dato de elemento se corresponde con el tipo de los elementos de la pila, para que no
dependa de un tipo concreto; para que sea genrico, se disea una pila genrica utilizando las planti-
llas (template) de C++. La clase NodoPila representa un nodo de la lista enlazada y tiene dos atri-
butos: elemento, guarda el elemento de la pila y siguiente, contiene la direccin del siguiente
nodo de la lista. En esta implementacin, NodoPila es una clase anidada de PilaGenerica.
// archivo PilaGenerica.h
template <class T>
class PilaGenerica
{
class NodoPila
{
public:
NodoPila* siguiente;
T elemento;
NodoPila(T x)
{
elemento = x;
siguiente = NULL;
}
};
NodoPila* cima;
public:
PilaGenerica ( )
{
cima = NULL;
}
void insertar(T elemento);
T quitar( );
T cimaPila( );
bool pilaVacia( );
void limpiarPila( );
~PilaGenerica( )
{
limpiarPila( );
}
};
Figura 34.10 Representacin de una pila con una lista enlazada.
Pila
1065 Pila genrica con listas enlazadas
Implementacin de las operaciones
El constructor de PilaGenerica inicializa a sta como pila vaca (cima = NULL), realmente, a la
condicin de lista vaca. Las operaciones insertar, quitar y cimaPila acceden a la lista direc-
tamente con el puntero cima (apunta al ltimo nodo apilado). Entonces, como no necesitan recorrer
los nodos de la lista, no dependen del nmero de nodos; la eficiencia de cada operacin es constante,
O(1).
A continuacin se escribe la codificacin de las operaciones que modifican el estado de la pila.

Poner un elemento en la pila
Crea un nuevo nodo con el elemento que se pone en la pila y se enlaza por la cima.
template <class T>
void PilaGenerica<T>::insertar(T elemento)
{
NodoPila* nuevo;
nuevo = new NodoPila(elemento);
nuevo > siguiente = cima;
cima = nuevo;
}
Pila
Figura 34.11 Apilar un elemento.

Eliminacin del elemento cima
Retorna el elemento cima y lo quita de la pila, disminuye el tamao de la pila.
template <class T>
T PilaGenerica<T>::quitar( )
{
if (pilaVacia( ))
throw "Pila vaca, no se puede extraer.";
T aux = cima > elemento;
Nodofila *a = cima -> siguiente;
cima = cima > siguiente;
delete a;
return aux;
}
Figura 34.12 Quita la cima de la pila.
Pila
1066 Captulo 34 Listas, pilas y colas en C++

Vaciado de la pila
Libera todos los nodos de que consta la pila. Recorre los n nodos de la lista enlazada, es una opera-
cin de complejidad lineal, O(n).
template <class T>
void PilaGenerica<T>::limpiarPila( )
{
NodoPila* n;
while(!pilaVacia( ))
{
n = cima;
cima = cima > siguiente;
delete n;
}
}
Tipo abstacto de datos cola
Una cola es una estructura de datos lineal, a la que se accede por uno de los dos extremos de la lista.
Los elementos se insertan por el extremo final, y se suprimen por el otro extremo, llamado frente. Las
aplicaciones utilizan una cola para almacenar elementos en su orden de aparicin o concurrencia.
Figura 34.13 Una cola.
Los elementos se quitan de la cola en el mismo orden en que se almacenan y, por consiguiente,
una cola es una estructura de tipo FIFO (first-in/first-out, primero en entrar/primero en salir o bien
primero en llegar/primero en ser servido).
Definicin
Una cola es una estructura de datos cuyos elementos mantienen un orden tal que slo se pue-
den aadir elementos por un extremo, final de la cola, y eliminar o extraer por el otro extre-
mo, llamado frente.
Especificaciones del tipo abstracto de datos cola
Las operaciones que definen la estructura de una cola son las siguientes:
Tipo de dato Elemento que se almacena en la cola
Operaciones
CrearCola Inicia la cola como vaca
Insertar Aade un elemento por el fnal de la cola
Quitar Retira (extrae) el elemento frente
Cola vaca Comprueba si la cola no tiene elementos
Cola llena Comprueba si la cola est llena de elementos
Frente Obtiene el elemento frente o primero de la cola
Tamao de la cola Nmero de elementos mximo que puede contener la cola
1 2 3 4 ltimo
Frente Final
1067 Tipo abstacto de datos cola
La forma que los lenguajes tienen para representar el TAD cola depende de dnde se almacenen
los elementos, en un arreglo, o en una estructura dinmica como puede ser una lista enlazada. La uti-
lizacin de arreglo tiene el problema de que la cola no puede crecer indefinidamente, est limitada por
el tamao del arreglo. Como contrapartida, el acceso a los extremos es muy eficiente. Utilizar una es-
tructura dinmica permite que el nmero de nodos se ajuste al de elementos de la cola.
Colas implementadas con arrays
La implementacin esttica de una cola se realiza declarando un array para almacenar los elementos,
y dos marcadores o apuntadores para mantener las posiciones frente y final de la cola. Cuando un
elemento se aade a la cola, se verifica si el marcador final apunta a una posicin vlida, entonces
se asigna el elemento en esa posicin y se incrementa el marcador final en 1. Cuando un elemento
se elimina de la cola, se hace una prueba para ver si la cola est vaca y, si no es as, se recupera el
elemento de la posicin apuntada por el marcador frente y ste se incrementa en 1.
El avance lineal de frente y final tiene un grave problema, deja huecos por la izquierda del
array. Llega a ocurrir que final alcanza el ndice ms alto del arreglo, no pudindose aadir nuevos
elementos y que, sin embargo existan posiciones libres a la izquierda de frente.
Figura 34.14 Una cola representada en un array.
1 2 3 4
A G H K
frente final
1 2 3 4
G H K
posicin de frente y final despus de extraer.
frente final
La clase ColaLineal declara un arreglo (listaCola) de tamao MAXTAMQ. Las variables fren-
te y final son los apuntadores a cabecera y cola, respectivamente. El constructor de la clase inicia-
liza la estructura, de tal forma que se parte de una cola vaca.
Las operaciones bsicas del tipo abstracto de datos cola: insertar, quitar, colaVacia,
colaLlena, y frente se implementan en la clase. insertar toma un elemento y lo aade por el fi-
nal. quitar suprime y devuelve el elemento cabeza de la cola. La operacin frente devuelve el ele-
mento que est en la primera posicin (frente) de la cola, sin eliminar el elemento. colaVacia
comprueba si la cola tiene elementos; colaLlena comprueba si se pueden aadir nuevos elementos.
// archivo ColaLineal.h
typedef tipo TipoDeDato; // tipo ha de ser conocido
const int MAXTAMQ = 39;
class ColaLineal
{
protected:
int frente;
int final;
TipoDeDato listaCola[MAXTAMQ];
public:
ColaLineal( )
{
1068 Captulo 34 Listas, pilas y colas en C++
frente = 0;
final = 1;
}
// operaciones de modificacin de la cola
void insertar(const TipoDeDato& elemento)
{
if (!colaLlena( ))
{
listaCola[++final] = elemento;
}
else
throw "Overflow en la cola";
}
TipoDeDato quitar( )
{
if (!colaVacia( ))
{
return listaCola[frente++];
}
else
throw "Cola vacia ";
}
void borrarCola( )
{
frente = 0;
final = 1;
}
// acceso a la cola
TipoDeDato frenteCola( )
{
if (!colaVacia( ))
{
return listaCola[frente];
}
else
throw "Cola vacia ";
}
// mtodos de verificacin del estado de la cola
bool colaVacia( )
{
return frente > final;
}
bool colaLlena( )
{
return final == MAXTAMQ1;
}
};
Cola con un arreglo (array)
circular
La forma ms eficiente de almacenar una cola en un arreglo es modelar
ste de tal forma que se una el extremo final con el extremo cabeza. Tal
arreglo se denomina arreglo circular y permite que la totalidad de sus
posiciones se utilicen para almacenar elementos de la cola sin necesi-
dad de desplazar elementos.
El apuntador frente siempre contiene la posicin del primer elemento de la cola y avanza en el
sentido de las agujas del reloj; final contiene la posicin donde se puso el ltimo elemento, tambin
avanza en el sentido del reloj (circularmente a la derecha). La implementacin del movimiento circu-
lar se realiza segn la teora de los restos, de tal forma que se generen ndices de 0 a MAXTAMQ1:

A recordar
La realizacin de una cola con un arreglo lineal
es notablemente ineficiente; se puede alcanzar
la condicin de cola llena habiendo elementos
del arreglo sin ocupar.
1069 Cola con un arreglo (array) circular
Mover final adelante = (final + 1) % MAXTAMQ
Mover frente adelante = (frente + 1) % MAXTAMQ
La implementacin de la gestin de colas con un array circular han de incluir las operaciones b-
sicas del TAD cola, es decir, las siguientes tareas bsicas:
Creacin de una cola vaca, de tal forma que final apunte a una posicin inmediatamente an-
terior a frente:
frente = 0; final = MAXTAMQ1.
Comprobar si una cola est vaca:
frente == siguiente(final)
Comprobar si una cola est llena. Para diferenciar la condicin cola llena de cola vaca se sacri-
fica una posicin del arreglo: entonces la capacidad real de la cola ser MAXTAMQ1. La condi-
cin de cola llena:
frente == siguiente(siguiente(final))
Poner un elemento en la cola: si la cola no est llena, fijar final a la siguiente posicin: final
= (final + 1) % MAXTAMQ y asignar el elemento.
Retirar un elemento de la cola: si la cola no est vaca, quitarlo de la posicin frente y estable-
cer frente a la siguiente posicin: (frente + 1) % MAXTAMQ.
Obtener el elemento primero de la cola, si la cola no est vaca, sin suprimirlo de la cola.
Clase cola con un arreglo circular
La clase ColaCircular implementa la representacin de una cola en un arreglo circular. Para apro-
vechar la potencia de la orientacin a objetos, y de C++, la clase deriva de ColaLineal. Es necesario
redefinir las funciones de la interfaz. Adems, se escribe la funcin auxiliar siguiente( ) para ob-
tener la siguiente posicin de una dada, aplicando la teora de los restos.
// archivo ColaCircular.h
#include "ColaLineal.h"
class ColaCircular : public ColaLineal
{
protected:
int siguiente(int r)
{
return (r+1) % MAXTAMQ;
}
public:
ColaCircular( )
Figura 34.15 Cola con un elemento en un arreglo circular.
0
1
frente
final
2
n 1
...
...
1070 Captulo 34 Listas, pilas y colas en C++
{
frente = 0;
final = MAXTAMQ1;
}
// operaciones de modificacin de la cola
void insertar(const TipoDeDato& elemento);
TipoDeDato quitar( );
void borrarCola( );
// acceso a la cola
TipoDeDato frenteCola( );
// mtodos de verificacin del estado de la cola
bool colaVacia( );
bool colaLlena( );
};
Implementacin:
void ColaCircular :: insertar(const TipoDeDato& elemento)
{
if (!colaLlena( ))
{
final = siguiente(final);
listaCola[final] = elemento;
}
else
throw "Overflow en la cola";
}
TipoDeDato ColaCircular :: quitar( )
{
if (!colaVacia( ))
{
TipoDeDato tm = listaCola[frente];
frente = siguiente(frente);
return tm;
}
else
throw "Cola vacia ";
}
void ColaCircular :: borrarCola( )
{
frente = 0;
final = MAXTAMQ1;
}
TipoDeDato ColaCircular :: frenteCola( )
{
if (!colaVacia( ))
{
return listaCola[frente];
}
else
throw "Cola vacia ";
}
bool ColaCircular :: colaVacia( )
{
return frente == siguiente(final);
}
bool ColaCircular :: colaLlena( )
{
return frente == siguiente(siguiente(final));
}
1071 Cola con un arreglo (array) circular
Ejemplo 34.5
Se utiliza una Cola y una Pila para encontrar un nmero capica ledo del dispositivo estndar
de entrada.
El nmero se lee del teclado en forma de cadena de dgitos. La cadena se procesa carcter a
carcter, es decir dgito a dgito (un dgito es un carcter del 0 al 9). Cada dgito se pone en
la cola y a la vez en la pila. Una vez que se terminan de leer los dgitos y de ponerlos en la cola
y en la pila, comienza la comprobacin: se extraen consecutivamente elementos de la cola y de
la pila, y se comparan por igualdad; de producirse alguna no coincidencia entre dgitos es que
el nmero no es capica y entonces se vacan las estructuras. El nmero es capica si el proceso
de comprobacin termina habiendo coincidido todos los dgitos en orden inverso, lo cual equi-
vale a que la pila y la cola terminen vacas.
Por qu utilizar una pila y una cola? Sencillamente para asegurar que se procesan los dgitos
en orden inverso; en la pila el ltimo en entrar es el primero en salir, en la cola el primero en
entrar es el primero en salir.
La pila es una instancia de la clase PilaGenerica y la cola de la clase ColaCircular. El
cdigo principal:
#include <iostream>
using namespace std;
#include "PilaGenerica.h"
typedef char TipoDeDato;
#include "ColaCircular.h"
bool capicua;
char numero[81];
PilaGenerica<char> pila;
ColaCircular q;
capicua = false;
while (!capicua)
{
do {
cout << "\nTeclea el nmero: ";
cin.getline(numero,80);
}while (!valido(numero)); // todos los caracteres dgitos
// pone en la cola y en la pila cada dgito
for (int i = 0; i < strlen(numero); i++)
{
char c;
c = numero[i];
q.insertar(c);
pila.insertar(c);
}
// se retira de la cola y la pila para comparar
do {
char d;
d = q.quitar( );
capicua = d == pila.quitar( ); //compara por igualdad
} while (capicua && !q.colaVacia( ));
if (capicua)
cout << numero << " es capica " << endl;
else
{
cout << numero << " no es capica, ";
cout << " intente con otro. ";
// se vaca la cola y la pila
q.borrarCola( );
pila.limpiarPila( );
}
}
1072 Captulo 34 Listas, pilas y colas en C++
Cola genrica con una lista enlazada
La implementacin del TAD cola con independencia del tipo de dato de los elementos se consigue uti-
lizando las plantillas (template) de C++. Para que la cola pueda crecer o decrecer, se utiliza la es-
tructura dinmica lista enlazada. Los extremos de la cola son los punteros de acceso a la lista:
frente y final. El puntero frente apunta al primer elemento de la lista; el puntero final, al l-
timo elemento de la lista.
Clase ColaGenerica
La implementacin de la Cola Genrica se realiza con dos clases: clase ColaGenerica y clase
NodoCola que ser una clase anidada. El Nodo representa al elemento y al enlace con el siguiente
nodo; al crear un Nodo se asigna el elemento y el enlace se pone null.
La declaracin de la clase y la implementacin se encuentran en el archivo ColaGenerica.h:
// archivo ColaGenerica.h
template <class T>
class ColaGenerica
{
protected:
class NodoCola
{
public:
NodoCola* siguiente;
T elemento;
NodoCola (T x)
{
elemento = x;
siguiente = NULL;
}
};
NodoCola* frente;
NodoCola* final;
public:
ColaGenerica( )
{
frente = final = NULL;
}
void insertar(T elemento);
T quitar( );
void borrarCola( );
T frenteCola( ) const;
bool colaVacia( ) const;
~ColaGenerica( )
{
borrarCola ( );
}
};
Las funciones acceden directamente a la lista, son tambin funciones genricas. A continuacin
se escribe la codificacin de las funciones que modifican la cola.

Aadir un elemento a la cola
template <class T>
void ColaGenerica<T> :: insertar(T elemento)
{
NodoCola* nuevo;
nuevo = new NodoCola (elemento);
if (colaVacia( ))
{
frente = nuevo;
}
1073 Bicolas: colas de doble entrada
else
{
final > siguiente = nuevo;
}
final = nuevo;
}

Sacar elemento frente de la cola
template <class T>
T ColaGenerica<T> :: quitar( )
{
if (colaVacia( ))
throw "Cola vaca, no se puede extraer.";
T aux = frente > elemento;
NodoCola* a = frente;
frente = frente > siguiente;
delete a;
return aux;
}

Vaciado de la cola
Elimina todos los elementos de la cola. Recorre la lista desde frente a final; es una operacin de com-
plejidad lineal, O(n).
template <class T>
void ColaGenerica<T> :: borrarCola( )
{
for (;frente != NULL;)
{
NodoCola* a;
a = frente;
frente = frente > siguiente;
delete a;
}
final = NULL;
}
Bicolas: colas de doble entrada
Una bicola o cola de doble entrada es un conjunto ordenado de elementos al que se pueden aadir o
quitar elementos desde cualquier extremo del mismo. El acceso a la bicola est permitido desde cual-
quier extremo, por lo que se considera que es una cola bidireccional. La estructura bicola es una ex-
tensin del TAD cola.
Los dos extremos de una bicola se identifican con los apuntadores frente y final (mismos
nombres que en una cola). Las operaciones bsicas que definen una bicola son una ampliacin de las
operaciones de una cola:
CrearBicola : inicializa una bicola sin elementos.
BicolaVacia : devuelve true si la bicola no tiene elementos.
PonerFrente : aade un elemento por el extremo frente.
PonerFinal : aade un elemento por el extremo final.
QuitarFrente : devuelve el elemento frente y lo retira de la bicola.
QuitarFinal : devuelve el elemento final y lo retira de la bicola.
Frente : devuelve el elemento frente de la bicola.
Final : devuelve el elemento final de la bicola.
Al tipo de datos bicola se pueden poner restricciones respecto a la entrada o a la salida de elemen-
tos. Una bicola con restriccin de entrada es aquella que slo permite inserciones por uno de los dos
extremos, pero que permite retirar elementos por los dos extremos. Una bicola con restriccin de sa-
1074 Captulo 34 Listas, pilas y colas en C++
lida es aquella que permite inserciones por los dos extremos, pero slo permite retirar elementos por
un extremo.
La representacin de una bicola puede ser con un arreglo, con un arreglo circular, o bien con lis-
tas enlazadas. Siempre se debe disponer de dos marcadores o variables ndice (apuntadores) que se
correspondan con los extremos, frente y final, de la estructura.
BicolaGenerica con listas enlazadas
La implementacin del TAD bicola con una lista enlazada se caracteriza por ajustarse al nmero de
elementos; es una implementacin dinmica, crece o decrece segn lo requiera la ejecucin del pro-
grama que utiliza la bicola. Como los elementos de una bicola, y en general de cualquier estructura
contenedora, pueden ser de cualquier tipo, se declara la clase genrica bicola. Adems, la clase va a
heredar de ColaGenerica ya que es una extensin de sta.
template <class T> class BicolaGenerica : public ColaGenerica<T>
De esta forma, BicolaGenerica dispone de todas las funciones y variables de la clase ColaGe-
nerica. Entonces, slo es necesario codificar las operaciones de Bicola que no estn implementa-
das en ColaGenerica. La declaracin de la clase:
// archivo BicolaGenerica.h
#include "ColaGenerica.h"
template <class T>
class BicolaGenerica : public ColaGenerica<T>
{
public:
void ponerFinal(T elemento);
void ponerFrente(T elemento);
T quitarFrente( );
T quitarFinal( );
T frenteBicola( )const;
T finalBicola( )const;
bool bicolaVacia( )const;
void borrarBicola( );
int numElemsBicola( )const;// cuenta los elementos de la bicola
};
Implementacin de las operaciones de BicolaGenerica
Las funciones: ponerFinal( ), quitarFrente( ), bicolaVacia( ), frenteBicola( ) son
idnticas a las funciones de la clase ColaGenerica insertar( ), quitar( ), colaVacia( ) y
frenteCola( ) respectivamente; se han heredado por el mecanismo de derivacin de clases; su im-
plementacin consiste en una simple llamada a la correspondiente funcin heredada. La implementa-
cin de alguna de estas funciones:

Aadir un elemento a la bicola
Aadir por el extremo final de la bicola.
template <class T>
void BicolaGenerica<T> :: ponerFinal(T elemento)
{
insertar(elemento); // heredado de ColaGenerica
}
Aadir por el extremo frente de la bicola.
template <class T>
void BicolaGenerica<T> :: ponerFrente(T elemento)
{
NodoCola* nuevo;
nuevo = new NodoCola(elemento);
1075 Bicolas: colas de doble entrada
if (bicolaVacia( ))
{
final = nuevo;
}
nuevo > siguiente = frente;
frente = nuevo;
}

Sacar un elemento de la bicola
Devuelve el elemento frente y lo quita de Bicola, disminuye su tamao.
template <class T>
T BicolaGenerica<T> :: quitarFrente( )
{
return quitar( ); // mtodo heredado de ColaLista
}
Devuelve el elemento final y lo quita de la bicola, disminuye su tamao. Es necesario recorrer
la lista para situarse en el nodo anterior a final, y despus enlazar.
template <class T>
T BicolaGenerica<T> :: quitarFinal( )
{
T aux;
if (! bicolaVacia( ))
{
if (frente == final) // Bicola dispone de un solo nodo
{
aux = quitar( );
}
else
{
NodoCola* a = frente;
while (a > siguiente != final)
a = a > siguiente;
aux = final > elemento;
final = a;
delete (a > siguiente);
}
}
else
throw "Eliminar de una bicola vaca";
return aux;
}

Nmero de elementos de la bicola
Recorre la estructura, de frente a final, para contar el nmero de elementos de que consta.
template <class T>
int BicolaGenerica<T> :: numElemsBicola( ) const
{
int n = 0;
NodoCola* a = frente;
if (!bicolaVacia( ))
{
n = 1;
while (a != final)
{
n++;
a = a > siguiente;
}
}
return n;
}
1076 Captulo 34 Listas, pilas y colas en C++
Resumen
Una lista enlazada es una estructura de datos dinmica, que se crea vaca y crece o decrece en
tiempo de ejecucin. Los componentes de una lista estn ordenados lgicamente por sus campos
de enlace en vez de estar ordenados fsicamente como estn en un arreglo.
El final de la lista se seala mediante una constante o puntero especial llamado NULL.
La principal ventaja de una lista enlazada sobre un arreglo radica en el tamao dinmico de la
lista, ajustndose al nmero de elementos.
La desventaja de la lista frente a un arreglo est en el acceso a los elementos: para un arreglo el
acceso es directo, a partir del ndice; para la lista el acceso a un elemento se realiza mediante
el campo enlace entre nodos.
Una lista simplemente enlazada contiene slo un enlace a un sucesor nico, a menos que sea el
ltimo, en cuyo caso no se enlaza con ningn otro nodo.
Una lista doblemente enlazada es aquella en la que cada nodo tiene una referencia a su sucesor
y otro a su predecesor. Las listas doblemente enlazadas se pueden recorrer en ambos sentidos.
Las operaciones bsicas son insercin, borrado y recorrer la lista; similares a las listas simples.
Una lista enlazada genrica tiene como tipo de dato el tipo genrico T, es decir, el tipo concreto se
especificar en el momento de crear el objeto lista. La construccin de una lista genrica se realiza con
las plantillas (template), mediante dos clases genricas: clase NodoGenerico y ListaGenerica.
En C++ cada tipo abstracto de datos se implementa con una clase. En el captulo se ha imple-
mentado la clase lista, la clase ListaDoble y la clase ListaGenerica.
Una pila es una estructura de datos tipo LIFO ( last-in/first-out, ltimo en entrar/primero en salir)
en la que los datos (todos del mismo tipo) se aaden y eliminan por el mismo extremo, denomi-
nado cima de la pila.
Se definen las siguientes operaciones bsicas sobre pilas: crear, insertar, cima, quitar, pilaVa-
cia, pilaLlena y limpiarPila.
El tipo abstracto pila se implementa mediante una clase en la que se agrupan las operaciones y
el acceso a los datos de la pila.
Una cola es una lista lineal en la que los datos se insertan por un extremo (final) y se retiran por el otro
(frente). Es una estructura FIFO (first-in/first-out, primero en entrar/primero en salir).
Las operaciones bsicas que se aplican sobre colas: crearCola, colaVacia, colaLlena,
insertar, frente y quitar.
La implementacin del TAD cola, en C++, se realiza con arrays, o bien con listas enlazadas. La
implementacin con un array lineal es muy ineficiente; se ha de considerar el array como una
estructura circular.
La realizacin de una cola con listas enlazadas permite que el tamao de la estructura se ajuste
al nmero de elementos.
Las bicolas son colas dobles en el sentido de que las operaciones bsicas insertar y retirar ele-
mentos se realizan por los dos extremos. Una bicola es, realmente, una extensin de una cola. La
implementacin natural del TAD bicola es con una clase derivada de la clase cola.
Ejercicios
34.1. Escribir una funcin, en la clase Lista, que devuelva cierto si la lista est vaca.
34.2. Aadir a la clase ListaDoble una funcin que devuelva el nmero de nodos de una lista doble.
34.3. Cul es la salida de este segmento de cdigo?, teniendo en cuenta que es una pila de tipo int:
Pila p;
int x = 4, y;
1077 Problemas
p.insertar(x);
cout << "\n " << cimaPila( );
y = p.quitar( );
p.insertar(32);
p.insertar(p.quitar( ));
do {
cout << "\n " << p.quitar( );
}while (!p.pilaVacia( ));
34.4. A la clase Lista aadir la funcin eliminaraPosicion( )que retire el nodo que ocupa la
posicin i, siendo 0 la posicin del nodo cabecera.
34.5. Escribir un funcin que tenga como argumento el puntero al primer nodo de una lista enlaza-
da, y cree una lista doblemente enlazada con los mismos campos dato pero en orden inverso.
34.6. Supngase que se tiene la clase Cola que implementa las operaciones del TAD cola. Escribir
una funcin para crear un clon (una copia) de una cola determinada. Las operaciones que se
han de utilizar sern nicamente las del TAD cola.
34.7. Escribir una funcin para crear una lista doblemente enlazada de palabras introducidas por te-
clado. El acceso a la lista debe ser el nodo que est en la posicin intermedia.
34.8. Se tiene una pila de enteros positivos. Con las operaciones bsicas de pilas y colas escribir un
fragmento de cdigo para poner todos los elementos de la pila que son par en la cola.
34.9. Con las operaciones implementadas en la clase PilaGenerica escribir la funcin mostrar-
Pila( ) que muestre en pantalla los elementos de una pila de cadenas.
34.10. Suponer una lista doblemente enlazada de cadenas ordenada alfabticamente. Escribir una
funcin para aadir una nueva palabra, en el orden que le corresponda, a la lista.
34.11. Dada la lista del ejercicio 34.10 escribir una funcin que elimine una palabra dada.
34.12. Considere una bicola de caracteres, representada en un array circular. El array consta de 9 po-
siciones. Los extremos actuales y los elementos de la bicola:
frente = 5 final = 7 bicola: A, C, E
Escribir los extremos y los elementos de la bicola segn se realizan estas operaciones:
Aadir los elementos F y K por el final de la bicola.
Aadir los elementos R, W y V por el frente de la bicola.
Aadir el elemento M por el final de la bicola.
Eliminar dos caracteres por el frente.
Aadir los elementos K y L por el final de la bicola.
Aadir el elemento S por el frente de la bicola.
Problemas
34.1. Escribir un programa que realice las siguientes tareas:
Crear una lista enlazada de nmeros enteros positivos al azar. Insertar por el ltimo nodo.
Recorrer la lista para mostrar los elementos por pantalla.
Eliminar todos los nodos que superen un valor dado.
34.2. Se tiene un archivo de texto de palabras separadas por un blanco o el carcter fin de lnea. Es-
cribir un programa para formar una lista enlazada con las palabras del archivo. Una vez for-
mada la lista, aadir nuevas palabras o borrar alguna de ellas. Al finalizar el programa, escribir
las palabras de la lista en el archivo. Utilizar la implementacin de una lista genrica.
1078 Captulo 34 Listas, pilas y colas en C++
34.3. Un polinomio se puede representar como una lista enlazada. El primer nodo representa el pri-
mer trmino del polinomio, el segundo nodo, el segundo trmino del polinomio y as sucesi-
vamente. Cada nodo tiene como campo dato el coeficiente del trmino y su exponente. Por
ejemplo, 3x
4
4x
2
+ 11 se representa:
3 4 11 4 2 0
Desarrollar la clase Polinomio con los atributos grado del polinomio, y una lista para re-
presentar los trminos del polinomio. Dotar a la clase de funciones para dar entrada a un poli-
nomio, visualizar el polinomio, evaluarlo para un valor numrico de la variable x.
34.4. Con la clase del ejercicio 34.3, escribir un programa que d entrada a polinomios en x. A conti-
nuacin, obtener valores del polinomio para valores de x = 0.0, 0.5, 1.0, 1.5, ..., 5.0.
34.5. Para determinar frases que son palndromo, se ha de seguir la siguiente estrategia: considerar
cada lnea una frase; aadir cada carcter de la frase a una pila y, a la vez, a una lista enlazada
doble por el final de la lista; extraer carcter a carcter, simultneamente de la pila y de la lis-
ta doble por el extremo cabeza; su comparacin determina si es palndromo o no. Escribir un
programa que lea lneas y determine si son palndromo.
34.6. Segn la representacin de un polinomio propuesta en el problema 34.3, aadir las siguientes
operaciones:
Obtener la suma de dos polinomios.
Obtener el polinomio derivado.
34.7. Un pequeo supermercado dispone en la salida de tres cajas de pago. En el local hay 25 carritos
de compra. Escribir un programa que simule el funcionamiento, siguiendo las siguientes reglas:
Si cuando llega un cliente no hay ningn carrito disponible, espera a que lo haya.
Ningn cliente se impacienta y abandona el supermercado sin pasar por alguna de las colas
de las cajas.
Cuando un cliente finaliza su compra, se coloca en la cola de la caja donde hay menos gen-
te, y no se cambia de cola.
En el momento en que un cliente paga en la caja, el carro de la compra que tiene queda dis-
ponible.
Representar la lista de carritos de la compra y las cajas de salida mediante colas.
34.8. Un conjunto es una secuencia de elementos todos del mismo sin duplicados. Representar el
tipo conjunto con una clase, de tal forma que los elementos del conjunto se almacenen en un
objeto Lista. Las operaciones a desarrollar:
Cardinal del conjunto.
Pertenencia de un elemento al conjunto.
Aadir un elemento al conjunto.
Escribir en pantalla los elementos del conjunto.
34.9. Con la representacin propuesta en el problema 34.8, aadir las operaciones bsicas de con-
juntos:
Unin de dos conjuntos.
Interseccin de dos conjuntos.
Diferencia de dos conjuntos.
Inclusin de un conjunto en otro.
34.10. Un vector disperso es aquel que tiene muchos elementos que son cero. Escribir una clase que
represente un vector disperso con listas enlazadas. Los nodos son los elementos del vector dis-
tintos de cero. Cada nodo contendr el valor del elemento y su ndice (posicin del vector). Las
operaciones a considerar: sumar dos vectores de igual dimensin y hallar el producto escalar.
Archivos
y flujos
en Java
Captulo
Contenido
Flujos y archivos
Clase File
Flujos y jerarqua de clases
Archivos de caracteres: flujos de tipo Reader y Writer
Archivos de objetos
Archivos de acceso directo
Resumen
Ejercicios
Problemas
Introduccin
Los archivos tienen como finalidad guardar datos de forma permanente; una vez que acaba la aplica-
cin, los datos siguen disponibles para que otra aplicacin pueda recuperarlos, para su consulta o mo-
dificacin.
El proceso de archivos en Java se hace mediante el concepto de flujo (stream) o canal, o tambin
denominado secuencia. Los flujos pueden estar abiertos o cerrados, conducen los datos entre el progra-
ma y los dispositivos externos. Con las clases y sus mtodos proporcionados por el paquete java.io
se pueden tratar archivos secuenciales, de acceso directo, archivos indexados, etctera.
Conceptos clave
Acceso secuencial Flujos
Archivos de texto
35
1080 Captulo 35 Archivos y flujos en Java
Flujos y archivos
Un fichero (archivo) de datos, o simplemente un archivo, es una coleccin de registros relacionados
entre s con aspectos en comn y organizados para un propsito especfico. Por ejemplo, un fichero
de una clase escolar contiene un conjunto de registros de los estudiantes de esa clase.
Un archivo en una computadora es una estructura diseada para contener datos, los cuales estn
organizados de tal modo que puedan ser recuperados fcilmente, actualizados o borrados y almacena-
dos de nuevo en el archivo con todos los cambios realizados.
Segn las caractersticas del soporte empleado y el modo en que se han organizado los registros,
se consideran dos tipos de acceso a los registros de un archivo:
acceso secuencial,
acceso directo.
El acceso secuencial implica el acceso a un archivo segn el orden de almacenamiento de sus regis-
tros, uno tras otro.
El acceso directo implica el acceso a un registro determinado, sin que ello implique la consulta de
los registros precedentes. Este tipo de acceso slo es posible con soportes direccionales.
En Java un archivo es, sencillamente, una secuencia de bytes, que son la representacin de los datos
almacenados. Java dispone de clases para trabajar las secuencias de bytes como datos de tipos bsicos
(int, double, String ...); incluso, para escribir o leer del archivo objetos. El diseo del archivo es el
que establece la forma de manejar las secuencias de bytes, con una organizacin secuencial, o bien de
acceso directo.
Un flujo (stream) es una abstraccin que se refiere a una corriente de datos que fluyen entre un
origen o fuente (productor) y un destino o sumidero (consumidor). Entre el origen y el destino debe
existir una conexin o canal ( pipe) por la que circulen los datos. La apertura de un archivo supone es-
tablecer la conexin del programa con el dispositivo que contiene el archivo; por el canal que comu-
nica el archivo con el programa van a fluir las secuencias de datos.
Abrir un archivo supone crear un objeto que queda asociado con un flujo. Al comenzar la ejecu-
cin de un programa Java se crean automticamente tres objetos de flujo, son tres canales por los que
pueden fluir datos, de entrada o de salida. stos son objetos definidos en la clase System:
System.in; entrada estndar; permite la entrada al programa de flujos de bytes desde el te
clado.
System.out; salida estndar; permite al programa la salida de datos por pantalla.
System.err; salida estndar de errores; permite al programa salida de errores por pantalla.
En Java, un archivo es simplemente un flujo externo, una secuencia de bytes almacenados en un
dispositivo externo (normalmente en disco). Si el archivo se abre para salida, es un flujo de archivo de
salida. Si el archivo se abre para entrada, es un flujo de archivo de entrada. Los programas leen o
escriben en el flujo, que puede estar conectado a un dispositivo o a otro. El flujo es por tanto una abs-
traccin de tal forma que las operaciones que realizan los programas son sobre el flujo independiente
mente del dispositivo al que est asociado.
A tener en cuenta
El paquete java.io agrupa al conjunto de clases e interfaces necesarios para procesar archi-
vos. Es necesario utilizar clases de este paquete; por consiguiente, se debe incorporar al pro-
grama con la sentencia import java.io.*.
Clase File
Un archivo consta de un nombre, adems de la ruta que indica dnde est ubicado, por ejemplo "C:\
pasaje.dat". Este identificador del archivo (cadena de caracteres) se transmite al constructor del
flujo de entrada o de salida que procesa el fichero:
1081 Clase File
FileOutputStream f = new FileOutputStream("C:\pasaje.dat");
Los constructores de flujos estn sobrecargados para, adems de recibir el archivo como cadena,
recibir un objeto de la clase File. Este tipo de objeto contiene el nombre del archivo, la ruta y ms
propiedades relativas al archivo.
La clase File define mtodos para conocer propiedades del archivo (ltima modificacin, permi-
sos de acceso, tamao, ...); tambin mtodos para modificar alguna caracterstica del archivo.
Los constructores de File permiten inicializar el objeto con el nombre de un archivo y la ruta
donde se encuentra. Tambin, inicializar el objeto con otro objeto File como ruta y el nombre del
archivo.
public File(String nombreCompleto)
Crea un objeto File con el nombre y ruta del archivo pasado como argumento.
public File(String ruta, String nombre)
Crea un objeto File con la ruta y el nombre del archivo pasado como argumentos.
public File(File ruta, String nombre)
Crea un objeto File con un primer argumento que a su vez es un objeto File con la ruta y el
nombre del archivo como segundo argumento.
Por ejemplo:
File miFichero = new File("C:\LIBRO\Almacen.dat");
crea un objeto FILE con el archivo Almacen.dat que est en la ruta C\LIBRO.
File otro = new File("COCINA", "Enseres.dat");
Nota
Es una buena prctica crear objetos File con el archivo que se va a procesar, para pasar el
objeto al constructor del flujo en vez de pasar directamente el nombre del archivo. De esta
forma se pueden hacer controles previos sobre el archivo.
Informacin de un archivo
Con los mtodos de la clase File se obtiene informacin relativa al archivo o ruta con que se ha ini-
cializado el objeto. As, antes de crear un flujo para leer de un archivo es conveniente determinar si el
archivo existe; en caso contrario no se puede crear el flujo. A continuacin se exponen los mtodos
ms tiles para conocer los atributos de un archivo o un directorio.
public boolean exists( )
Devuelve true si existe el archivo (o el directorio).
public boolean canWrite( )
Devuelve true si se puede escribir en el archivo, si no es de slo lectura.
public boolean canRead( )
Devuelve true si es de slo lectura.
public boolean isFile( )
Devuelve true si es un archivo.
public boolean isDirectory( )
Devuelve true si el objeto representa a un directorio.
public boolean isAbsolute( )
Devuelve true si el directorio es la ruta completa.
public long length( )
Devuelve el nmero de bytes que ocupa el archivo. Si el objeto es un directorio devuelve cero.
1082 Captulo 35 Archivos y flujos en Java
public long lastModified( )
Devuelve la hora de la ltima modificacin. El nmero devuelto es una representacin inter-
na de la hora, minutos y segundos de la ltima modificacin, slo es til para establecer
comparaciones con otros valores devueltos por el mismo mtodo.
Tambin dispone de mtodos que modifican el archivo: cambiar de nombre, marcar de slo lec-
tura. Para los directorios, tiene mtodos para crear uno nuevo, obtener una lista de todos los elemen-
tos (archivos o subdirectorios) del directorio. Algunos de estos mtodos se escriben a continuacin.
public String getName( )
Devuelve una cadena con el nombre del archivo o del directorio con que se ha inicializado
el objeto.
public String getPath( )
Devuelve una cadena con la ruta relativa al directorio actual.
public String getAbsolutePath( )
Devuelve una cadena con la ruta completa del archivo o directorio.
public boolean setReadOnly( )
Marca al archivo para que no se pueda escribir, de slo lectura.
public boolean delete( )
Elimina el archivo o directorio (debe estar vaco).
public boolean renameTo(File nuevo)
Cambia el nombre del archivo con que ha sido inicializado el objeto por el nombre que con-
tiene el objeto pasado como argumento.
public boolean mkdir( )
Crea el directorio con el que se ha creado el objeto.
public String[ ] list( )
Devuelve un array de cadenas, cada una contiene un elemento (archivo o directorio) del di-
rectorio con el que se ha inicializado el objeto.
Ejemplo 35.1
Se muestra por pantalla cada uno de los archivos y subdirectorios de que consta un directorio
que se transmite en la lnea de rdenes.
Se crea un objeto File inicializado con el nombre del directorio o ruta procedente de la lnea
de rdenes. El programa comprueba que es un directorio y llamando al mtodo list( ) se ob-
tiene un arreglo de cadenas con todos los elementos del directorio. Con un bucle, tantas itera-
ciones como longitud tiene el arreglo de cadenas, se escribe cada cadena en la pantalla.
import java.io.*;
class Directorio
{
public static void main(String[ ] a)
{
File dir;
String[ ] cd;
// para la ejecucin es necesario especificar el directorio
if (a.length > 0)
{
dir = new File(a[0]);
// debe ser un directorio
if (dir.exists( ) && dir.isDirectory( ))
{
1083 Flujos y jerarqua de clases
Flujos y jerarqua de clases
En los programas hay que crear objetos stream y en muchas ocasiones hacer uso de los objetos in,
out de la clase System. Los flujos de datos, de caracteres, de bytes se pueden clasificar en flujos de
entrada y flujos de salida. En consonancia, Java declara dos clases (derivan directamente de la clase
Object): InputStream y OutputStream. Ambas son clases abstractas que declaran mtodos que
deben redefinirse en sus clases derivadas. InputStream es la clase base de todas las clases definidas
para flujos de entrada, y OutputStream es la clase base de todas las clases definidas para flujos de
salida. La tabla 35.1 muestra las clases derivadas ms importantes de stas.
Archivos de bajo nivel: FileInputStream y FileOutputStream
Todo archivo, para entrada o salida, se puede considerar como una secuencia de bytes. A partir de
estas secuencias de bytes, flujos de bajo nivel, se construyen flujos de ms alto nivel para proce-
so de datos complejos, desde tipos bsicos hasta objetos. Las clases FileInputStream y
FileOutputStream se utilizan para leer o escribir bytes en un archivo; objetos de estas dos clases
son los flujos de entrada y salida, respectivamente, a nivel de bytes. Los constructores de ambas cla-
ses permiten crear flujos (objetos) asociados a un archivo que se encuentra en cualquier dispositivo;
el archivo queda abierto. Por ejemplo, el flujo mf se asocia al archivo Temperatura.dat del direc-
torio en forma predeterminada:
FileOutputStream mf = new FileOutputStream("Temperatura.dat");
Las operaciones que a continuacin se realicen con mf escriben secuencias de bytes en el archivo
Temperatura.dat.
La clase FileInputStream dispone de mtodos para leer un byte o una secuencia de bytes. A
continuacin se escriben los mtodos ms importantes de esta clase, todos con visibilidad public. Es
importante tener en cuenta la excepcin que pueden lanzar para que cuando se invoquen se haga un
tratamiento de la excepcin.
// se obtiene la lista de elementos
cd = dir.list( );
System.out.println("Elementos del directorio " + a[0]);
for (int i = 0; i < cd.length; i++)
System.out.println(cd[i]);
}
else
System.out.println("Directorio vaco");
}
else
System.out.println("No se ha especificado directorio ");
}
}
Tabla 35.1
Primer nivel de la jerarqua de clases de Entrada/Salida.
InputStream OutputStream
FileInputStream FileOutputStream
PipedInputStream PipedOutputStream
ObjectInputStream ObjectOutputStream
StringBufferInputStream FilterOutputStream
FilterInputStream
1084 Captulo 35 Archivos y flujos en Java
FileInputStream(String nombre) throws FileNotFoundException;
Crea un objeto inicializado con el nombre de archivo que se pasa como argumento.
FileInputStream(File nombre) throws FileNotFoundException;
Crea un objeto inicializado con el objeto archivo pasado como argumento.
int read( ) throws IOException;
Lee un byte del flujo asociado. Devuelve 1 si alcanza el fin del fichero.
int read(byte[ ] s) throws IOException;
Lee una secuencia de bytes del flujo y se almacena en el arreglo s. Devuelve 1 si alcanza el
fin del fichero, o bien el nmero de bytes ledos.
int read(byte[ ] s, int org, int len) throws IOException;
Lee una secuencia de bytes del flujo y se almacena en arreglo s desde la posicin org y un
mximo de len bytes. Devuelve 1 si alcanza el fin del fichero, o bien el nmero de bytes
ledos.
void close( )throws IOException;
Cierra el flujo, el archivo queda disponible para posterior uso.
La clase FileOutputStream dispone de mtodos para escribir bytes en el flujo de salida asocia-
do a un archivo. Los constructores inicializan objetos con el nombre del archivo, o bien con el archivo
como un objeto File; el archivo queda abierto. A continuacin se escriben los constructores y mto-
dos ms importantes; todos tienen visibilidad public.
FileOutputStream(String nombre) throws IOException;
Crea un objeto inicializado con el nombre de archivo que se pasa como argumento.
FileOutputStream(String nombre, boolean sw) throws IOException;
Crea un objeto inicializado con el nombre de archivo que se pasa como argumento. En el
caso de que sw = true los bytes escritos se aaden al final.
FileOutputStream(File nombre) throws IOException;
Crea un objeto inicializado con el objeto archivo pasado como argumento.
void write(byte a) throws IOException;
Escribe el byte a en el flujo asociado.
void write(byte[ ] s) throws IOException;
Escribe el arreglo de bytes en el flujo.
void write(byte[ ] s, int org, int len) throws IOException;
Escribe el arreglo s desde la posicin org y un mximo de len bytes en el flujo.
void close( )throws IOException;
Cierra el flujo, el archivo queda disponible para posterior uso.
Nota
Una vez creado un flujo pueden realizarse operaciones tpicas de archivos, leer (flujos de en-
trada), escribir (flujos de salida). Es el constructor el encargado de abrir el flujo; en definitiva,
de abrir el archivo. Si el constructor no puede crear el flujo (archivo de lectura no existe),
lanza la excepcin FileNotFoundException.
Recomendacin
Siempre que finaliza la ejecucin de un programa Java se cierran los flujos abiertos. Sin em-
bargo, se aconseja ejecutar el mtodo close( ) cuando deje de utilizarse un flujo; de esa
manera se liberan recursos asignados y el archivo queda disponible.
1085 Flujos y jerarqua de clases
Archivos de datos: DataInputStream y DataOutputStream
Resulta poco prctico trabajar directamente con flujos de bytes; los datos que se escriben o se leen en
los archivos son ms elaborados, de ms alto nivel, como enteros, reales, cadenas de caracteres, etc.
Las clases DataInputStream y DataOutputStream (derivan de FilterInputStream y Filter-
OutputStream, respectivamente) filtran una secuencia de bytes, organizan los bytes para formar da-
tos de tipo primitivo. De esta forma, se pueden escribir o leer directamente datos de tipo: char, byte,
short, int, long, float, double, boolean, String.
La clase DataInputStream declara el comportamiento de los flujos de entrada, con mtodos
para leer cualquier tipo bsico: readInt( ), readDouble( ), readUTF( ), etc. Estos flujos leen
los bytes de otro flujo de bajo nivel (flujo de bytes) para formar nmeros enteros, doble precisin,
etc.; por consiguiente, deben asociarse a un flujo de bytes, de tipo InputStream, generalmente un
Ejemplo 35.2
Dado el archivo jardines.txt, se desea escribir toda su informacin en el archivo jardin-
Old.txt.
El primer archivo se asocia con un flujo de entrada; el segundo fichero, con un flujo de salida.
Entonces, se instancia un objeto flujo de entrada y otro de salida del tipo FileInput Stream
y FileOutputStream, respectivamente. La lectura se realiza byte a byte con el mtodo read( );
cada byte se escribe en el flujo de salida invocando al mtodo write( ). El proceso termina
cuando read( ) devuelve 1, seal de haber alcanzado el fin del archivo.
import java.io.*;
public class CopiaArchivo
{
public static void main(String [ ] a)
{
FileInputStream origen = null;
FileOutputStream destino = null;
File f1 = new File("jardines.txt");
File f2 = new File("jardinOld.txt");
try
{
origen = new FileInputStream(f1);
destino = new FileOutputStream(f2);
int c;
while ((c = origen.read( )) != 1)
destino.write((byte)c);
}
catch (IOException er)
{
System.out.println("Excepcin en los flujos "
+ er.getMessage( ));
}
finally {
try
{
origen.close( );
destino.close( );
}
catch (IOException er)
{
er.printStackTrace( );
}
}
}
}
1086 Captulo 35 Archivos y flujos en Java
flujo FileInputStream. La asociacin se realiza al crear el flujo, el constructor recibe como argu-
mento el objeto flujo de bajo nivel, de bytes, del que realmente se lee. Por ejemplo, para leer datos del
archivo nube.dat, se crea un flujo gs de tipo FileInputStream asociado con el archivo, a con-
tinuacin un objeto DataInputStream que envuelve al flujo gs:
FileInputStream gs = new FileInputStream("nube.dat");
DataInputStream ent = new DataInputStream(gs);
Se puede escribir en una sola sentencia:
DataInputStream ent = new DataInputStream(
new FileInputStream("nube.dat"));
Al crear el flujo ent queda asociado con el flujo de bajo nivel FileInputStream, que a su vez
abre, en modo lectura, el archivo nube.dat.
A continuacin se escriben los mtodos ms importantes de la clase DataInputStream. Todos
tienen visibilidad public, y adems no se pueden redefinir, ya que estn declarados como final.
public DataInputStream(InputStream entrada) throws IOException
Crea un objeto asociado con cualquier objeto de entrada pasado como argumento.
public final boolean readBoolean( ) throws IOException
Devuelve el valor de tipo boolean ledo.
public final byte readByte( ) throws IOException
Devuelve el valor de tipo byte ledo.
public final short readShort( ) throws IOException
Devuelve el valor de tipo short ledo.
public final char readChar( ) throws IOException
Devuelve el valor de tipo char ledo.
public final int readInt( ) throws IOException
Devuelve el valor de tipo int ledo.
public final long readLong( ) throws IOException
Devuelve el valor de tipo long ledo.
public final float readFloat( ) throws IOException
Devuelve el valor de tipo float ledo.
public final double readDouble( ) throws IOException
Devuelve el valor de tipo double ledo.
public final String readUTF( ) throws IOException
Devuelve una cadena que se escribi en formato UTF.
El flujo de entrada lee un archivo que previamente ha sido escrito con un flujo de salida del tipo
DataOutputStream. Los flujos de salida se asocian con otro flujo de salida de bajo nivel, de bytes.
Los mtodos de este tipo de flujos permiten escribir cualquier valor de tipo de dato primitivo, int
double ... y String. Al constructor de DataOutputStream se pasa como argumento el flujo de
bajo nivel al cual queda asociado. De esta forma, el mtodo, por ejemplo, writeInt( ) que escribe
un nmero entero, en realidad escribe 4 bytes en el flujo de salida.
La aplicacin que vaya a leer un archivo creado con los mtodos de un flujo DataOutputStream
debe tener en cuenta que los mtodos de lectura se han de corresponder. Por ejemplo, si se han escri-
to secuencias de datos de tipo entero, char y double con los mtodos writeInt( ), writeChar( )
y writeDouble( ), el flujo de entrada para leer el archivo utilizar los mtodos readInt( ),
read Char( ) y readDouble( ), respectivamente.
El flujo de salida que se crea para escribir el archivo nube.dat:
FileOutputStream fn = new FileOutputStream("nube.dat");
DataOutputStream snb = new DataOutputStream(fn);
1087 Flujos y jerarqua de clases
O bien, en una sola sentencia:
DataOutputStream snb = new DataOutputStream(
new FileOuputStream("nube.dat"));
A continuacin se escriben los mtodos ms importantes de DataOutputStream:
public DataOutputStream(OutputStream destino) throws IOException
Crea un objeto asociado con cualquier objeto de salida pasado como argumento.
public final void writeBoolean(boolean v) throws IOException
Escribe el dato de tipo boolean v.
public final void writeByte(int v) throws IOException
Escribe el dato v como un byte.
public final void writeShort(int v) throws IOException
Escribe el dato v como un short.
public final void writeChar(int v) throws IOException
Escribe el dato v como un carcter.
public final void writeChars(String v) throws IOException
Escribe la secuencia de caracteres de la cadena v.
public final void writeInt(int v) throws IOException
Escribe el dato de tipo int v.
public final void writeLong(long v) throws IOException
Escribe el dato de tipo long v.
public final void writeFloat(float v) throws IOException
Escribe el dato de tipo float v.
public final void writeDouble(double v) throws IOException
Escribe el valor de tipo double v.
public final void writeUTF(String cad) throws IOException
Escribe la cadena cad en formato UTF. Escribe los caracteres de la cadena y dos bytes adi-
cionales con longitud de la cadena.
public final int close( )throws IOException
Cierra el flujo de salida.
Nota
La composicin de flujos es la forma habitual de filtrar secuencias de bytes para tratar datos
a ms alto nivel, desde datos de tipos bsicos como int, char, double hasta objetos como
pueden ser cadenas, arreglos y cualquier objeto creado por el usuario. As, para leer un archi-
vo datos a travs de un bfer:
DataInputStream entrada = new DataInputStream(
new BufferedInputStream(
new FileInputStream(miFichero)));
Ejercicio 35.1
Se dispone de los datos registrados en la estacin meteorolgica situada en el cerro Garabitas, co-
rrespondientes a un da del mes de septiembre. La estructura de los datos: un primer registro con el
da (p. ej., 1 septiembre); y para cada hora: hora, presin y temperatura. Los datos se han de grabar
en el archivo SeptGara.tmp.
1088 Captulo 35 Archivos y flujos en Java
Para resolver el supuesto enunciado se define un flujo del tipo DataOutputStream asociado a otro
flujo de salida a bajo nivel o simplemente flujo de bytes. Con el fin de simular la situacin real, los
datos se obtienen aleatoriamente, llamando al mtodo Math.random( ) y transformando el nmero
aleatorio en otro dentro de un rango preestablecido. Se utilizan los mtodos writeUTF( ), write-
Double( ) y writeInt( ) para escribir en el objeto flujo. En cuanto al tratamiento de excepciones,
simplemente se captura y se escribe el mensaje asociado.
import java.io.*;
public class Garabitas
{
public static void main(String[ ] a)
{
String dia = "1 Septiembre 2001";
DataOutputStream obfl = null;
try {
obfl = new DataOutputStream (
new FileOutputStream("septGara.tmp"));
obfl.writeUTF(dia); // escribe registro inicial
for (int hora = 0; hora < 24; hora++)
{
double presion, temp;
presion = presHora( );
temp = tempHora( );
// escribe segn la estructura de cada registro
obfl.writeInt(hora);
obfl.writeDouble(presion);
obfl.writeDouble(temp);
}
}
catch (IOException e)
{
System.out.println(" Anomala en el flujo de salida " +
e.getMessage( ));
}
finally {
try
{
obfl.close( );
}
catch (IOException er)
{
er.printStackTrace( );
}
}
}
// mtodos auxiliares para generar temperatura y presin
static private double presHora( )
{
final double PREINF = 680.0;
final double PRESUP = 790.0;
return (Math.random( )*(PRESUP PREINF) + PREINF);
}
static private double tempHora( )
{
final double TEMINF = 5.0;
final double TEMSUP = 40.0;
return (Math.random( )*(TEMSUP TEMINF) + TEMINF);
}
}
1089 Flujos y jerarqua de clases
Ejercicio 35.2
El archivo SeptGara.tmp ha sido creado con un flujo DataOutputStream, tiene la estructura de
datos: el primer registro es una cadena escrita con formato UTF; los dems registros tienen los datos:
hora (tipo int), presin (tipo double) y temperatura (tipo double). Se quiere escribir un programa
para leer cada uno de los datos, calcular la temperatura mxima y mnima y mostrar los resultados
por pantalla.
El archivo que se va a leer se cre con un flujo DataOutputStream; entonces para leer los datos
del archivo se necesita un flujo DataInputStream y conocer la estructura, en cuanto a tipos de da-
tos, de los tems escritos. Como esto se conoce, se procede a la lectura: primero se lee la cadena inicial
con la fecha (readUTF( )); a continuacin se leen los campos correspondientes a la hora, tempera-
tura y presin con los mtodos readInt( ), readDouble( ) y readDouble( ). Hay que leer todo
el archivo mediante el tpico bucle mientras no fin de fichero; sin embargo, la clase DataInput-
Stream no dispone del mtodo eof( ). Por ello el bucle est diseado como un bucle infinito; la sa-
lida del bucle se produce cuando un mtodo intente leer despus de fin de fichero, entonces lanza la
excepcin EOFException y termina el bucle.
Los datos ledos se muestran en pantalla; con llamadas al mtodo Math.max( ) se calcula el valor
mximo pedido.
import java.io.*;
class LeeGarabitas
{
public static void main(String[ ] a)
{
String dia;
double mxt = 11.0; // valor mnimo para encontrar mximo
FileInputStream f;
DataInputStream obfl = null;
try {
f = new FileInputStream("septGara.tmp");
obfl = new DataInputStream(f);
}
catch (IOException io)
{
System.out.println("Anomala al crear flujo de entrada, " +
io.getMessage( ));
return; // termina la ejecucin
}
// proceso del flujo
try {
int hora;
boolean mas = true;
double p, temp;
dia = obfl.readUTF( );
System.out.println(dia);
while (mas)
{
hora = obfl.readInt( );
p = obfl.readDouble( );
temp = obfl.readDouble( );
System.out.println("Hora: " + hora + "\t Presin: " + p
+ "\t Temperatura: " + temp);
mxt = Math.max(mxt,temp);
}
}
catch (EOFException eof)
{
System.out.println("Fin de lectura del archivo.\n");
}
1090 Captulo 35 Archivos y flujos en Java
Flujos PrintStream
La clase PrintStream deriva directamente de FilterOutputStream. La caracterstica ms impor-
tante es que dispone de mtodos que aaden la marca de fin de lnea. Los flujos de tipo PrintStream
son de salida, se asocian con otro flujo de bajo nivel, de bytes, que a su vez se crea asociado a un ar-
chivo externo. Por ejemplo, mediante el flujo fjp se puede escribir cualquier tipo de dato; los bytes
que ocupa cada dato se vuelcan secuencialmente en Complex.dat
fjp = new PrintStream(new FileOutputStream("Complex.dat"));
Los mtodos de esta clase, print( ) y println( ) estn sobrecargados para poder escribir
desde cadenas hasta cualquiera de los datos primitivos; println( ) escribe un dato y a continuacin
aade la marca de fin de lnea.
A continuacin estn los mtodos ms importantes:
public PrintStream(OutputStream destino)
Crea un objeto asociado con cualquier objeto de salida pasado como argumento.
public PrintStream(OutputStream destino, boolean flag)
Crea un objeto asociado con objeto de salida pasado como argumento y si el segundo argu-
mento es true se produce un automtico volcado al escribir el fin de lnea.
public void flush( )
Vuelca el flujo actual.
public void print(Object obj)
Escribe la representacin del objeto obj en el flujo.
public void print(String cad)
Escribe la cadena en el flujo.
public void print(char c)
Escribe el carcter c en el flujo.
mtodo print( ) para cada tipo de dato primitivo.
public void println(Object obj)
Escribe la representacin del objeto obj en el flujo y el fin de lnea.
public void println(String cad)
Escribe la cadena en el flujo y el fin de lnea.
catch (IOException io)
{
System.out.println("Anomala al leer flujo de entrada, "
+ io.getMessage( ));
return; // termina la ejecucin
}
finally {
try
{
obfl.close( );
}
catch (IOException er)
{
er.printStackTrace( );
}
}
// termina el proceso, escribe la temperatura mxima
System.out.println("\n La temperatura mxima: " + (float)mxt);
}
}
1091 Archivos de caracteres: flujos de tipo Reader y Writer
public void println(char c)
Escribe el carcter c en el flujo y el fin de lnea.
mtodo println( ) para cada tipo de dato primitivo
Nota de programacin
El objeto definido en la clase System: System.out es de tipo PrintStream, asociado,
normalmente, con la pantalla. Por ello se han utilizado los mtodos:
System.out.print( );
System.out.println( );
Archivos de caracteres: flujos de tipo
Reader y Writer
Los flujos de tipo InputStream y OutputStream estn orientados a bytes, para el trabajo con flu-
jos orientados a caracteres Java dispone de las clases de tipo Reader y Writer.
Reader es la clase base, abstracta, de la jerarqua de clases para leer un carcter o una secuencia
de caracteres. Writer es la clase base de la jerarqua de clases diseadas para escribir caracteres.
Leer archivos de caracteres: InputStreamReader,
FileReader y BufferedReader
Para leer archivos de caracteres se utilizan flujos derivados de la clase Reader. sta declara mtodos
para lectura de caracteres que son heredados, en algn caso redefinido, por las clases derivadas. Los
mtodos ms interesantes:
public int read( )
Lee un carcter; devuelve el carcter ledo (como un entero). Devuelve 1 si lee el final del
archivo.
public int read(char [ ] b);
Lee una secuencia de caracteres, hasta completar el array b, o bien leer el carcter fin de
archivo. Devuelve el nmero de caracteres ledos, o bien 1 si alcanz el final del archivo.
InputStreamReader
Los flujos de la clase InputStreamReader envuelven a un flujo de bytes; convierte la secuencia de
bytes en secuencia de caracteres y de esa forma lee caracteres en lugar de bytes. La clase deriva direc-
tamente de la clase Reader, por lo que tiene disponibles los mtodos read( ) para lectura de carac-
teres. Generalmente se utilizan estos flujos como entrada en la construccin de flujos con bfer.
El mtodo ms importante es el constructor que tiene como argumento cualquier flujo de entrada:
public InputStreamReader(InputStream ent);
En el siguiente ejemplo se crea el flujo entradaChar que puede leer caracteres del flujo de bytes
System.in (asociado con el teclado):
InputStremReader entradaChar = new InputStreamReader(System.in);
FileReader
Para leer archivos de texto, archivos de caracteres, se puede crear un flujo del tipo FileReader. Esta
clase deriva de InputStreamReader, hereda los mtodos read( ) para lectura de caracteres; el
constructor tiene como entrada una cadena con el nombre del archivo.
public FileReader(String miFichero) throws FileNotFoundException;
Por ejemplo,
1092 Captulo 35 Archivos y flujos en Java
FileReader fr = new FileReader("C:\cartas.dat");
No resulta eficiente, en general, leer directamente de un flujo de este tipo; se utilizar un flujo
BufferedReader envolviendo al flujo FileReader.
BufferedReader
La lectura de archivos de texto se realiza con un flujo que almacena los caracteres en un bfer inter-
medio. Los caracteres no se leen directamente del archivo sino del bfer; con esto se consigue ms
eficiencia en las operaciones de entrada. La clase BufferReader permite crear flujos de caracteres
con bfer, es una forma de organizar el flujo bsico de caracteres del que procede el texto; esto se
manifiesta en que al crear el flujo BufferReader se inicializa con un flujo de caracteres de tipo
InputStreamReader.
El constructor de la clase tiene un argumento de tipo Reader, un FileReader o un Input-
StreamReader. El flujo creado dispone de un bfer de un tamao, normalmente suficiente; el tama-
o del bfer se puede especificar en el constructor con un segundo argumento aunque no resulta
necesario. Ejemplos de flujos con bfer:
File mf = new File("C:\listados.txt");
FileReader fr = new FileReader(mf);
BufferedReader bra = new BufferedReader(fr);
File mfz = new File("Complejo.dat");
BufferedReader brz = new BufferedReader(
new InputStreamReader(
new FileInputStream(mfz)));
La clase BufferedReader deriva directamente de la clase Reader. Entonces, dispone de los
mtodos read( ) para leer un carcter, o bien un arreglo de caracteres. El mtodo ms importante es
readLine( ):
public String readLine( ) throws IOException;
El mtodo lee una lnea de caracteres, termina con el carcter de fin de lnea, y devuelve una ca-
dena con la lnea leda (no incluye el carcter fin de lnea). Puede devolver null si lee la marca de fin
de archivo.
Otro mtodo interesante es close( ), cierra el flujo y libera los recursos asignados. El fin de la
aplicacin Java tambin cierra los flujos abiertos, aunque es recomendable cerrar un flujo que no se
va a utilizar.
public void close( ) throws IOException;
Ejemplo 35.3
Para ver el contenido de un archivo de texto se van a leer lnea a lnea hasta leer la marca de
fin de fichero. El nombre completo del archivo se ha de transmitir en la lnea de rdenes de la
aplicacin.
Para realizar la lectura del archivo de texto, se va a crear un flujo BufferReader asociado
con el flujo de caracteres del archivo. Se crea un objeto File con argumento, la cadena trans-
mitida en la lnea de rdenes. Una vez creado el flujo, el bucle mientras cadena leda distinto
de null procesa todo el archivo.
import java.io.*;
class LeerTexto
{
public static void main(String[ ] a)
{
File mf;
BufferedReader br = null;
1093 Archivos de caracteres: flujos de tipo Reader y Writer
String cd;
// se comprueba que hay una cadena
if (a.length > 0)
{
mf = new File(a[0]);
if (mf.exists( ))
{
int k = 0;
try {
br = new BufferedReader(new FileReader(mf));
while ((cd = br.readLine( )) != null)
{
System.out.println(cd);
if ((++k)%21 == 0)
{
System.out.print("Pulse una tecla ...");
System.in.read( );
}
}
br.close( );
}
catch (IOException e)
{
System.out.println(e.getMessage( ));
}
}
else
System.out.println("Directorio vaco");
}
}
}
Flujos que escriben caracteres: Writer, PrintWriter
Los archivos de texto son archivos de caracteres; se pueden crear con flujos de bytes, o bien con flu-
jos de caracteres derivados de la clase abstracta Writer.
La clase Writer define mtodos write( ) para escribir arreglos de caracteres o cadenas
(String). De Writer deriva OutputStreamWriter que permite escribir caracteres en un flujo de
bytes al cual se asocia en la creacin del objeto o flujo. Por ejemplo:
OutputStreamWriter ot = new OutputStreamWriter(
new FileOutputStream(archivo));
No es frecuente utilizar directamente flujos OutputStreamWriter. Ahora bien, resulta de inte-
rs porque la clase FileWriter es una extensin de ella, diseada para escribir en un archivo de ca-
racteres. Los flujos de tipo FileWriter escriben caracteres, mtodo write( ), en el archivo al que
se asocia el flujo cuando se crea el objeto.
FileWriter nr = new FileWriter("cartas.dat");
nr.write("Estimado compaero de fatigas");
PrintWriter
Los flujos ms utilizados para salida de caracteres son de tipo PrintWriter. Esta clase declara cons-
tructores para asociar un flujo PrintWriter con cualquier otro flujo de tipo Writer, o bien
OutputStream.
public PrintWriter(OutputStream destino)
Crea un flujo asociado con cualquier flujo de salida a nivel de byte.
public PrintWriter(Writer destino)
Crea un flujo asociado con otro flujo de salida de caracteres de tipo Writer.
1094 Captulo 35 Archivos y flujos en Java
La importancia de esta clase radica en que define los mtodos print( ) y println( ) para
cada uno de los tipos de datos simples, para String y para Object. La diferencia entre los mtodos
print( ) y println( ) est en que println( ) aade, a continuacin de los caracteres escritos
para el argumento, los caracteres de fin de lnea.
public void print(Object obj)
Escribe la representacin del objeto obj en el flujo.
public void print(String cad)
Escribe la cadena en el flujo.
public void print(char c)
Escribe el carcter c en el flujo.
Sobrecarga de print( ) para cada tipo de dato primitivo.
public void println(Object obj)
Escribe la representacin del objeto obj en el flujo y el fin de lnea.
public void println(String cad)
Escribe la cadena en el flujo y el fin de lnea.
public void println(char c)
Escribe el carcter c en el flujo y el fin de lnea.
Sobrecarga de println( ) para cada tipo de dato primitivo.
Ejemplo 35.4
En un archivo de texto se van a escribir los caminos directos entre los pueblos de una comarca al-
carrea. Cada lnea contiene el nombre de dos pueblos y la distancia del camino que los une (slo
si hay camino directo) separados por un blanco. La entrada de los datos es a partir del teclado.
El archivo de texto, argumento de la lnea de rdenes, se maneja con un objeto File de tal
forma que si ya existe se crea un flujo de bytes FileOutputStream con la opcin de aadir al
final. Este flujo se utiliza para componer un flujo de ms alto nivel, DataOutputStream, que
a su vez se le asocia con el flujo PrintWriter para escribir datos con los mtodos print y
println. La entrada es a partir del teclado; se repiten las entradas hasta que el mtodo read-
Line( ) devuelve null (cadena vaca) debido a que el usuario ha tecleado ^Z; o bien termina
al pulsar una lnea vaca. La cadena leda inicializa un objeto de la clase StringTokenizer
para comprobar que se han introducido correctamente los datos pedidos.
import java.io.*;
import java.util.StringTokenizer;
class EscribeCamino
{
static boolean datosValidos(String cad) throws Exception
{
StringTokenizer cd;
String dist;
boolean sw;
cd = new StringTokenizer(cad);
sw = cd.countTokens( ) == 3;
cd.nextToken( );
sw = sw && (Integer.parseInt(cd.nextToken( )) > 0);
return sw;
}
public static void main(String[ ] a)
{
File mf;
BufferedReader entrada = new BufferedReader(
new InputStreamReader(System.in));
DataOutputStream d = null;
1095 Archivos de objetos
PrintWriter pw = null;
String cad;
boolean modo;
// se comprueba que hay una cadena
if (a.length > 0)
{
mf = new File(a[0]);
if (mf.exists( ))
modo = true;
else
modo = false;
try {
pw = new PrintWriter( new DataOutputStream (
new FileOutputStream(mf,modo)));
System.out.println("Pueblo_A distancia Pueblo_B");
while (((cad = entrada.readLine( ))!= null)&&
(cad.length( ) > 0))
{
if (datosValidos(cad))
pw.println(cad);
}
pw.close( );
}
catch (Exception e)
{
System.out.println(e.getMessage( ));
e.printStackTrace( );
}
}
else
System.out.println("Archivo no existente ");
}
}
Archivos de objetos
Para que un objeto persista una vez que termina la ejecucin de una aplicacin se ha de guardar en un
archivo de objetos. Por cada objeto que se almacena en un archivo se graban caractersticas de la cla-
se y los atributos del objeto. Las clases ObjectInputStream y ObjectOutputStream estn dise-
adas para crear flujos de entrada y salida de objetos persistentes.
Clase de objeto persistente
La declaracin de la clase cuyos objetos van a persistir debe implementar la interfaz Serializable
del paquete java.io. Es una interfaz vaca, no declara mtodos; simplemente indica a la JVM que
las instancias de estas clases podrn grabarse en un archivo. La clase declarada a continuacin tiene
esta propiedad:
import java.io.*;
class Racional implements Serializable {...}
Nota de programacin
Los atributos de los objetos declarados transient no se escriben al grabar un objeto en un
archivo.
class TipoObjeto implements Serializable
{
transient tipo dato; // no se escribe en el flujo de objetos
}
1096 Captulo 35 Archivos y flujos en Java
Flujos ObjectOutputStream
Los flujos de la clase ObjectOutputStream se utilizan para grabar objetos persistentes. El mtodo
writeObject( ) escribe cualquier objeto de una clase serializable en el flujo de bytes asociado;
puede elevar excepciones del tipo IOException que es necesario procesar.
public void writeObject(Object obj) throws IOException;
El constructor de la clase espera un argumento de tipo OutputStream, que es la clase base de los
flujos de salida a nivel de bytes. Entonces, para crear este tipo de flujos, primero se crea un flujo de
salida a nivel de bytes (asociado a un archivo externo) y a continuacin se pasa este flujo como argu-
mento al constructor de ObjectOutputStream. Por ejemplo:
FileOutputStream bn = new FileOutputStream("datosRac.dat");
ObjectOutputStream fobj = new ObjectOutputStream(bn);
O bien en una sola sentencia:
ObjectOutputStream fobj = new ObjectOutputStream(
new FileOutputStream("datosRac.dat"));
A continuacin se puede escribir cualquier tipo de objeto en el flujo:
Racional rd = new Racional(1,7);
fobj.writeObject(rd);
String sr = new String("Cadena de favores");
fpbj.writeObj(sr);
Flujos ObjectInputStream
Los objetos guardados en archivos con flujos de la clase ObjectOutputStream se recuperan, se
leen, con flujos de entrada del tipo ObjectInputStream. Esta clase es una extensin de Input-
Stream, adems implementa la interfaz DataInput; por ello dispone de los diversos mtodos de en-
trada (read) para cada uno de los tipos de datos, readInt( ) El mtodo ms interesante definido
por la clase ObjectInputStream es readObject( ); lee un objeto del flujo de entrada (del archi-
vo asociado al flujo de bajo nivel). Este objeto se escribi en su momento con el mtodo write-
Object( ).
public Object readObject( ) throws IOException;
El constructor de flujos ObjectInputStream tiene como entrada otro flujo, de bajo nivel, de
cualquier tipo derivado de InputStream, por ejemplo FileInputStream, asociado con el archivo
de objetos. A continuacin se crea un flujo de entrada para leer los objetos del archivo "archivoOb-
jets.dat":
ObjectInputStream obje = new ObjectInputStream(
new FileInputStream("archivoObjets.dat "));
El constructor levanta una excepcin si, por ejemplo, el archivo no existe,..., del tipo ClassNo-
tFoundException, o bien IOException; es necesario poder capturar estas excepciones.
Nota
El mtodo readObject( ) lee cualquier objeto del flujo de entrada, devuelve el objeto
como tipo Object. Entonces, es necesario convertir Object al tipo del objeto que se espera
leer. Por ejemplo, si el archivo es de objetos Racional:
rac = (Racional) flujo.readObject( );
La lectura de archivos con diversos tipos de objetos necesita una estructura de seleccin
para conocer el tipo de objeto ledo. El operador instanceof es til para esta seleccin.
1097 Archivos de objetos
Ejercicio 35.3
Se desea guardar en un archivo los libros y discos de que disponemos. Los datos de inters para un
libro son: ttulo, autor, editorial, nmero de pginas; para un disco: cantante, ttulo, duracin en mi-
nutos, nmero de canciones y precio.
Se podra disear una jerarqua de clases que modelase los objetos libro, disco, Sin embargo, el
ejercicio slo pretende mostrar cmo crear objetos persistentes; declara directamente la clase Libro
y Disco con la propiedad de ser serializables (implementan la interfaz java.io.Serializable).
La clase principal crea un flujo de salida para objetos; segn los datos que introduce el usuario se ins-
tancia un tipo de objeto Disco o Libro y con el mtodo writeObject( ) se escribe en el flujo.
import java.io.*;
class Libro implements Serializable
{
private String titulo;
private String autor;
private String editorial;
private int pagina;
public Libro( )
{
titulo = autor = editorial = null;
}
public Libro(String t, String a, String e, int pg)
{
titulo = t;
autor = a;
editorial = e;
pagina = pg;
}
public void entrada(BufferedReader ent) throws IOException
{
System.out.print("Titulo: "); titulo = ent.readLine( );
System.out.print("Autor: "); autor = ent.readLine( );
System.out.print("Editorial: "); editorial = ent.readLine( );
System.out.print("Pginas: ");
pagina = Integer.parseInt(ent.readLine( ));
}
}
class Disco implements Serializable
{
private String artista;
private String titulo;
private int numCancion, duracion;
private transient double precio;
public Disco( )
{
artista = titulo = null;
}
public Disco(String a, String t, int n, int d, double p)
{
titulo = t;
artista = a;
numCancion = n;
duracion = d;
precio = p;
}
public void entrada(BufferedReader ent) throws IOException
{
System.out.print("Cantante: "); artista = ent.readLine( );
System.out.print("Titulo: "); titulo = ent.readLine( );
1098 Captulo 35 Archivos y flujos en Java
System.out.print("Canciones: ");
numCancion = Integer.parseInt(ent.readLine( ));
System.out.print("Duracin(minutos): ");
duracion = Integer.parseInt(ent.readLine( ));
System.out.print("Precio: ");
precio = Double.valueOf(ent.readLine( )).doubleValue( );
}
}
public class Libreria
{
public static void main (String [ ] a)
{
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
File mf = new File("libreria.dat");
ObjectOutputStream fobj = null;
Libro libro = new Libro( );
Disco disco = new Disco( );
int tipo;
boolean mas = true;
try {
fobj = new ObjectOutputStream(new FileOutputStream(mf));
do {
System.out.println
("Pulsa L(libro), D(disco), F(finalizar)");
tipo = System.in.read( );
System.in.skip(2); // salta caracteres fin de lnea
switch (tipo) {
case 'L':
case 'l': libro = new Libro( );
libro.entrada(br);
fobj.writeObject(libro);
break;
case 'D':
case 'd': disco = new Disco( );
disco.entrada(br);
fobj.writeObject(disco);
break;
case 'F':
case 'f': fobj.close( );
mas = false;
}
} while (mas);
}
catch (IOException e)
{
e.printStackTrace( );
}
}
}
Archivos de acceso directo
Un archivo es de acceso aleatorio o directo cuando cualquier registro es directamente accesible me-
diante la especificacin de un ndice, que da la posicin del registro con respecto al origen del archivo.
La principal caracterstica de los archivos de acceso aleatorio radica en la rapidez de acceso a un de-
terminado registro; conociendo el ndice del registro se puede situar el puntero de acceso en la posi-
cin donde comienza el registro. Las operaciones que se realizan con los archivos de acceso directo
son las usuales: creacin, consulta, dar altas, dar bajas, modificar.
1099 Archivos de acceso directo
Java considera un archivo como secuencias de bytes. A la hora de realizar una aplicacin es cuan-
do se establece la forma de acceso al archivo. Es importante, para el proceso de archivos aleatorios,
establecer el tipo de datos de cada campo del registro lgico y el tamao de cada registro. En cuanto
al tamao, se establece teniendo en cuenta la mxima longitud de cada campo; para los campos de
tipo String se debe prever el mximo nmero de caracteres. Adems, si se escribe en formato UTF
se aaden 2 bytes ms (en los dos bytes se guarda la longitud de la cadena). Los campos de tipo pri-
mitivo tienen longitud fija: char 2 bytes, int 4 bytes, double 8 bytes
RandomAccessFile
La clase RandomAccesFile define mtodos para facilitar el proceso de archivos de acceso directo.
Esta clase deriva directamente de la clase Object e implementa los mtodos de las interfaces Da-
taInput y DataOutput, al igual que las clases DataInputStream y DataOutputStream, respec-
tivamente. Por consiguiente, RandomAccesFile tiene tanto mtodos de lectura como de escritura,
que coinciden con los de las clases DataInputStream y DataOutputStream, adems de mtodos
especficos para el tratamiento de este tipo de archivos.
Constructor
public RandomAccessFile(String nomArchivo, String modo)
El objeto queda ligado al archivo que se pasa como cadena en el primer argumento. El se-
gundo argumento es el modo de apertura.
public RandomAccesFile(File archivo, String modo)
El primer argumento es un objeto File que se cre con la ruta y el nombre del archivo, el
segundo argumento es el modo de apertura.
Java simplifica el modo de apertura al mximo; puede ser de dos formas:
"r" Modo de slo lectura. nicamente se pueden realizar operaciones de entrada, de lectura
de registros. No se pueden modificar o escribir registros.
"rw" Modo lectura/escritura. Permite hacer operaciones tanto de entrada (lectura) como de sa-
lida (escritura).
Por ejemplo, los flujos a crear para acceder al archivo Libros.dat son:
File f = new File("libros.dat");
try
{
RandomAccessFile dlib = new RandomAccessFile(f, "r");
}
catch (IOException e)
{
System.out.println("Flujo no creado, " +
"el proceso no puede continuar");
System.exit(1);
}
Ambos constructores lanzan una excepcin del tipo IOException si hay algn problema al crear
el flujo. Por ejemplo, si se abre para lectura y el archivo no existe. Es habitual comprobar el atributo
de existencia del archivo con el mtodo exists( ) de la clase File antes de empezar el proceso.
Por ejemplo:
try
{
RandomAccessFile dlib = new RandomAccessFile("Provins.dat","rw");
}
catch (IOException e)
{
System.out.println("Flujo no creado, " +
"el proceso no puede continuar");
System.exit(1);
}
1100 Captulo 35 Archivos y flujos en Java
Mtodos de posicionamiento
En los archivos cuando se hace mencin al puntero del archivo se refiere a la posicin (en nmero de
bytes) a partir de la que se va a realizar la siguiente operacin de lectura o de escritura. Una vez rea-
lizada la operacin, el puntero queda situado justo despus del ltimo byte ledo o escrito. Por ejem-
plo, si el puntero se encuentra en la posicin n y se lee un campo entero (4 bytes) y un campo double
(8 bytes), la posicin del puntero del archivo ser n+12.
getFilePointer( )
Este mtodo de la clase RandomAccessFile devuelve la posicin, en nmero de bytes, actual del
puntero del archivo. Su declaracin:
public long getFilePointer( ) throws IOException
Por ejemplo, en la siguiente llamada al mtodo, ste devuelve cero debido a que la posicin inme-
diatamente despus de abrir el flujo es cero.
RandomAccessFile acc = new RandomAccessFile("Atletas.dat","rw");
System.out.println("Posicin del puntero: " + acc.getFilePointer( ));
seek ( )
Este mtodo desplaza el puntero del archivo n bytes, tomando como origen el del archivo (byte 0). Su
declaracin:
public void seek(long n) throws IOException
Con el mtodo seek( ) se puede tratar un archivo como un array que es una estructura de datos
de acceso aleatorio. seek( ) sita el puntero del archivo en una posicin aleatoria, dependiendo del
desplazamiento que se pasa como argumento.
Precaucin
El desplazamiento pasado al mtodo seek( ) es relativo a la posicin 0 del archivo. Es err-
neo pasar desplazamientos negativos. Si el desplazamiento pasado es mayor que el tamao
actual del archivo, se ampla ste si a continuacin se realiza una escritura.
length( )
Con este mtodo se obtiene el tamao actual del archivo, el nmero de bytes que ocupa o longitud que
tiene. La declaracin:
public long length( ) throws IOException
Por ejemplo, para conocer la longitud del archivo Atletas:
RandomAccessFile acc = new RandomAccessFile("Atletas.dat","rw");
System.out.println("Tamao del archivo" + acc.length( ));
Para situar el puntero al final del archivo:
acc.seek(acc.length( ));
Proceso que crea y aade registros en un archivo directo
Para crear un archivo directo, lo primero a determinar es la longitud que va a tener cada registro. Esto
depende de los campos y del nmero de bytes que tenga cada uno. Por ejemplo, suponer que se crea
un archivo de acceso directo o aleatorio, con estas caractersticas:
Se dispone de una muestra de las coordenadas de puntos en el plano representada por pares de
nmeros reales (x,y), tales que 1.0 x 100.0 y 1.0 y 100.0. Cada punto es la ubicacin
1101 Archivos de acceso directo
en el plano de una casa o refugio rural identificado por su nombre popular (Casa Mariano, Re-
fugio De los Ros ...). El nombre ms largo es de 40 caracteres. Adems, cada Casa y el punto en el
plano est identificado por un nmero de orden. Esta muestra se va a guardar en un archivo con ac-
ceso directo, de tal forma que cada registro represente un punto en el plano con los campos: coorde-
nadas del punto (x,y), nombre de la casa y nmero de orden. A su vez, el nmero de orden va a ser el
nmero de registro.
El primer campo a considerar es numOrden, de tipo int, que ocupa 4 bytes; el campo nombre-
Casa de 40 caracteres como mximo, se va a escribir en formato UTF que aade 2 bytes ms, por lo
que ocupa un mximo de 82 bytes. Las coordenadas son dos nmeros reales de tipo double, el tama-
o es 8+8 bytes. En total cada registro tiene una longitud de 4+82+8+8= 102.
El archivo va a tener el nombre de Rurales.dat. Se ha de crear un objeto File con el nombre
del archivo y la ruta. El flujo (objeto RandomAccessFile) se abre en modo lectura/escritura, "rw"
para as poder escribir cada registro.
El nmero de orden de cada registro se establece en el rango de 1 a 99. Al comenzar la aplicacin
se crean los flujos y se llama al mtodo archVacio( ) para escribir 99 registros vacos, con el cam-
po numOrden a 1, el nombre de la casa a cadena vaca y las coordenadas (0,0). Posiblemente que-
den huecos, o registros vacos; posteriormente se pueden aadir nuevos registros.
La clase CasaReg tiene como variables instancia o atributos los campos de cada registro. El m-
todo annadeReg( ) escribe en el flujo los campos del registro llamando a writeInt( ), write-
UTF( ) y writeDouble( ), respectivamente.
La entrada de los registros va a ser interactiva; la condicin para acabar el bucle de entrada es leer
como NumOrden el 0.
import java.io.*;
import java.util.*;
class CasaReg
{
int numOrden;
String nombreCasa;
double x, y;
final int TAM = 102;
public CasaReg( )
{
nombreCasa = new String( );
numOrden = 0;
x = y = 0.0;
}
public boolean annadeReg( RandomAccessFile fl)
{
boolean flag ;
flag = true;
try
{
fl.writeInt(numOrden);
fl.writeUTF(nombreCasa);
fl.writeDouble(x);
fl.writeDouble(y);
}
catch (IOException e)
{
System.out.println("Excepcin al escribir el registro.");
flag = false;
}
return flag;
}
public int tamanno( ) { return TAM;}
// desplazamiento en bytes de registro n
public long desplazamiento(int n) { return TAM*(n-1);}
}
// clase principal
class CreaCasas
1102 Captulo 35 Archivos y flujos en Java
{
File af;
RandomAccessFile arch;
CasaReg reg;
final int MAXREG = 99;
public CreaCasas( )
{
try
{
af = new File("Rurales.dat");
arch = new RandomAccessFile(af, "rw");
reg = new CasaReg( );
}
catch(IOException e)
{
System.out.println("No se puede abrir el flujo."
+ e.getMessage( ));
System.exit(1);
}
}
void archVacio( )
{
try
{
reg = new CasaReg( ); // crea registro vaco
for (int k = 1; k <= MAXREG; ++k)
{
arch.seek(reg.desplazamiento(k));
reg.annadeReg(arch);
}
}
catch(IOException e)
{
System.out.println("Excepcin al mover puntero del archivo");
System.exit(1);
}
}
void procesoEntrada( )
{
Scanner entrada = new Scanner(System.in);
System.out.println("Datos de la muestra en el orden: " +
" Nmero de orden. Nombre. Coordenadas\n" +
" Para terminar, Nmero de orden = 0");
try {
int nr;
double x, y;
String cd;
boolean masRegistros = true;
// bucle para la entrada
while (masRegistros)
{
do {
System.out.println(" Nmero de orden: ");
nr = entrada.nextInt( );
} while (nr<0 || nr>MAXREG);
if (nr > 0) // no es clave de salida
{
reg.numOrden = nr;
System.out.println(" Nombre: ");
cd = entrada.nextLine( );
// se asegura un mximo de 40 caracteres
reg.nombreCasa = cd.substring(0,Math.min(40,cd.length( )));
do {
System.out.println(" Coordenadas (x,y): ");
x = entrada.nextDouble( );
y = entrada.nextDouble( );
1103 Archivos de acceso directo
} while ((x<1.0 || x > 100.0) || (y<1.0 || y > 100.0));
// escribe el registro en la posicin que le corresponde
reg.x = x;
reg.y = y;
arch.seek(reg.desplazamiento(nr));
reg.annadeReg(arch);
}
masRegistros =( nr > 0);
} // fin del bucle de entrada
}
catch(IOException e)
{
System.out.println("Excepcin, termina el proceso ");
System.exit(1);
}
finally
{
System.out.println("Se cierra el flujo ");
try {
arch.close( );
}
catch(IOException e)
{
System.out.println("Error al cerrar el flujo ");
}
}
}
// driver del proceso
public static void main(String [ ]a)
{
CreaCasas pr = new CreaCasas( );
pr.archVacio( );
pr.procesoEntrada( );
}
}
Consultas de registros en un archivo directo
En el apartado anterior, se ha creado un archivo aleatorio con las ubicaciones de casas en el plano con
ciertas condiciones. Ahora se quiere realizar el proceso para hacer consultas y modificaciones. Nor-
malmente hay diversos tipos de consultas, algunas de ellas:
Listado de todos los registros.
Mostrar un registro.
Mostrar registros que cumplen cierta condicin.
Para listar todos los registros hay que hacer una lectura de todo el archivo. Es un proceso completa-
mente secuencial. Sin embargo, no todos los registros son altas realizadas, o registros activos. As, si en
nuestro archivo creado se ha previsto un mximo de 99 registros, no todos estarn dados de alta y por
tanto habr huecos. Los registros no activos son los que tienen como numOrden 0. sa ser la clave para
discernir si el registro ledo se muestra. La lectura del archivo se hace hasta el final del archivo.
La segunda forma de consulta, por el nmero de registro, es ms simple. Directamente se sita el
puntero del archivo en la posicin que determina el nmero de registro y a continuacin se lee. El re-
gistro pedido puede ser que no est activo, numOrden = 0. Si es as no se muestra.
La tercera forma de consulta supone el mismo proceso que la consulta total. Hay que leer todos
los registros del archivo y seleccionar los que cumplen la condicin.
Codificacin
La clase CasaReg, adems de tener un mtodo para escribir los campos en un flujo, debe tener un
mtodo para leer los campos del registro de un flujo, por lo que se aade dicho mtodo a la clase (los
mtodos anteriores no se vuelven a escribir).
1104 Captulo 35 Archivos y flujos en Java
Ahora se crea una nueva clase, ConsultaCasa, en la que se definen los mtodos de consulta de
todo el archivo o de un registro. El constructor recibe como argumento el objeto File con el archivo
que se va a leer, abre el flujo en modo de slo lectura. La clase principal crea el objeto Consulta-
Casa y ofrece al usuario los diversos tipos de consulta.
import java.io.*;
import java.util.*;
class CasaReg
{
int numOrden;
String nombreCasa;
double x, y;
final int TAM = 102;
// mtodos ya escritos
public CasaReg( ) {}
public boolean annadeReg( ){}
public int tamanno( ) { return TAM;}
public long desplazamiento(int n) { return TAM*(n-1);}
//
public boolean leerReg( RandomAccessFile flo) throws EOFException
{
boolean flag ;
flag = true;
try
{
numOrden = flo.readInt( );
nombreCasa = flo.readUTF( );
x = flo.readDouble( );
y = flo.readDouble( );
}
catch (EOFException e)
{
throw e;
}
catch (IOException e)
{
System.out.println("Excepcin al leer el registro.");
flag = false;
}
return flag;
}
}
// clase para realizar consultas
class ConsultaCasa
{
private RandomAccessFile archivo;
private void mostrar(CasaReg reg)
{
System.out.println(reg.nombreCasa +
" ubicada en las coordenadas \t (" +
(float)reg.x + "," + (float)reg.y +")");
}
public ConsultaCasa(File f) throws IOException
{
if (f.exists( ))
{
archivo = new RandomAccessFile(f,"r");
System.out.println("Flujo abierto en modo lectura, tamao: "
+ archivo.length( ));
}
else
{
archivo = null;
throw new IOException("Archivo no existe. ");
}
}
1105 Archivos de acceso directo
public void consultaGeneral( ) throws IOException
{
if (archivo != null)
{
try {
int n;
CasaReg reg = new CasaReg( );
n = 0;
// se lee hasta fin de archivo, termina con la
// excepcion EOFException
while (true)
{
archivo.seek(reg.desplazamiento(++n));
if (reg.leerReg(archivo)) // lectura sin error
if (reg.numOrden > 0) mostrar(reg); // registro activo
}
}
catch(EOFException eof)
{
System.out.println("\n Se alcanza el fin del archivo" +
" en la consulta ");
}
}
}
public void consultaReg(int nreg) throws IOException
{
if (archivo != null)
{
CasaReg reg = new CasaReg( );
if (nreg>0 && reg.desplazamiento(nreg)< archivo.length( ))
{
archivo.seek(reg.desplazamiento(nreg));
if (reg.leerReg(archivo))
if (reg.numOrden > 0)
mostrar(reg);
else
System.out.println("Registro " + nreg +
" no dado de alta.");
}
else throw new IOException(" Registro fuera de rango.");
}
}
}
//
// clase principal
public class Consultas
{
public static void main(String [ ]a)
{
File f = new File("Rurales.dat");
ConsultaCasa clta = null;
int opc = 1;
Scanner entrada = new Scanner(System.in);
try {
System.out.println("\n Consultas al archivo " +
f.getName( ));
clta = new ConsultaCasa(f);
while (opc != 5)
{
System.out.println("1. Muestra todos los registros " +
"activos.");
System.out.println("2. Muestra un registro determinado.");
System.out.println("5. Se sale de la aplicacin.");
do {
1106 Captulo 35 Archivos y flujos en Java
opc = entrada.nextInt( );
} while ((opc<1) || (opc>2 && opc!=5));
if (opc == 1)
clta.consultaGeneral( );
else if (opc == 2)
{
int nreg;
System.out.print("Nmero de registro: ");
nreg = entrada.nextInt( );
clta.consultaReg (nreg);
}
}
}
catch(IOException e)
{
System.out.println("\n Excepcin durante " +
"el proceso de consulta: " +
e.getMessage( ) + " Termina la aplicacin.");
}
}
}
Resumen
Java dispone de un paquete especializado en la entrada y salida de datos, es el paquete java.io.
Contiene un conjunto de clases organizadas jerrquicamente para tratar cualquier tipo de flujo de
entrada o de salida de datos.
Es necesaria la sentencia import java.io.* en los programas que utilicen objetos de alguna
de estas clases.
Todo se basa en la abstraccin de flujo: corriente de bytes que entran a un dispositivo o que sa-
len de l. Para Java un archivo, cualquier archivo, es un flujo de bytes; incorpora clases que pro-
cesan los flujos a bajo nivel, como secuencias de bytes.
Para estructurar los bytes y formar datos a ms alto nivel hay clases de ms alto nivel; con estas
clases se pueden escribir o leer directamente datos de cualquier tipo simple (entero, char...). Es-
tas clases se enlazan con las clases de bajo nivel que a su vez se asocian a los archivos.
Los objetos creados en Java pueden ser persistentes. Las clases de objetos persistentes imple-
mentan la interfaz Serializable. Los flujos que escriben y leen estos objetos son Object-
OutputStream y ObjectInputStream, respectivamente.
Java dispone de la clase RandomAccessFile para procesar un archivo con acceso aleatorio. La
clase RandomAccessFile contiene mtodos de posicionamiento (seek( )), de lectura (read-
Int( ), ...) y de grabacin de campos (writeInt( ),...).
Ejercicios
35.1. Escribir las sentencias necesarias para abrir un archivo de caracteres, cuyo nombre y acceso
se introduce por teclado, en modo lectura; en el caso de que el resultado de la operacin sea
errneo, abrir el archivo en modo escritura.
35.2. Un archivo contiene enteros positivos y negativos. Escribir un mtodo para leer el archivo y
determinar el nmero de enteros negativos.
1107 Problemas
35.3. Escribir un mtodo para copiar un archivo. El mtodo tendr dos argumentos de tipo cadena,
el primero es el archivo original y el segundo el archivo destino. Utilizar flujos FileInput-
Stream y FileOutputStream.
35.4. Una aplicacin instancia objetos de las clases NumeroComplejo y NumeroRacional. La
primera tiene dos variables instancia de tipo float, parteReal y parteImaginaria.
La segunda clase tiene definidas tres variables, numerador y denominador de tipo int y
frac de tipo double. Escribir la aplicacin de tal forma que los objetos sean persistentes.
Problemas
35.1. Escribir un programa que compare dos archivos de texto (caracteres). El programa ha de mos-
trar las diferencias entre el primer archivo y el segundo, precedidas del nmero de lnea y de
columna.
35.2. Un atleta utiliza un pulsmetro para sus entrenamientos. El pulsmetro almacena las pulsacio-
nes cada 15 segundos, durante un tiempo mximo de 2 horas. Escribir un programa para alma-
cenar en un archivo los datos del pulsmetro del atleta, de tal forma que el primer registro
contenga la fecha, hora y tiempo en minutos de entrenamiento, a continuacin los datos del
pulsmetro por parejas: tiempo, pulsaciones.
35.31 Las pruebas de acceso a la Universidad Pontificia de Guadalajara, UPGA, constan de 4 aparta-
dos, cada uno de los cuales se punta de 1 a 25 puntos. Escribir un programa para almacenar en
un archivo los resultados de las pruebas realizadas, de tal forma que se escriban objetos con los
siguientes datos: nombre del alumno, puntuaciones de cada apartado y la puntuacin total.
35.4. Una farmacia quiere mantener su stock de medicamentos en un archivo. De cada producto
interesa guardar el cdigo, precio y descripcin. Escribir un programa que genere el archivo
pedido, almacenndose los objetos de manera secuencial.
35.5. Se quiere crear un archivo binario formado por registros que representan productos de perfu-
mera. Los campos de cada registro son cdigo de producto, descripcin, precio y nmero de
unidades. La direccin de cada registro viene dada por una funcin hash que toma como cam-
po clave el cdigo del producto (tres dgitos):
hash(clave) = (clave modulo 97) + 1
El nmero mximo de productos distintos es 100. Las colisiones, de producirse, se situarn
secuencialmente a partir del registro nmero 120.
Listas, pilas
y colas
en Java
Captulo
Contenido
Tipo abstracto de datos (TAD) lista
Clase Lista
Lista ordenada
Lista doblemente enlazada
Lista circular
Tipo abstracto de datos pila
Pila dinmica implementada con vector
Pila implementada con una lista enlazada
Tipo abstracto de datos cola
Cola implementada con una lista enlazada
Resumen
Ejercicios
Problemas
Introduccin
La estructura de datos que se estudia en este captulo es la lista enlazada (ligada o encadenada, linked
list), que es una coleccin de elementos (denominados nodos) dispuestos uno a continuacin de otro,
cada uno de ellos conectado al siguiente elemento por un enlace o referencia. En el captulo se
desarrollan mtodos para insertar, buscar y borrar elementos en las listas enlazadas. De igual modo
se muestra el tipo abstracto de datos (TAD) que representa a las listas enlazadas.
Las pilas, como tipo abstracto de datos, son objeto de estudio en este captulo; son estructuras de
datos LIFO (last-in/first-out, ltimo en entrar/primero en salir). Las pilas se utilizan en compiladores,
sistemas operativos y programas de aplicaciones.
Tambin en este captulo se estudia el tipo abstracto cola. Las colas se conocen como estructuras
FIFO (first-in/first-out, primero en entrar/primero en salir), debido al orden de insercin y de extrac-
36
1109 Tipo abstracto de datos (TAD) lista
cin de elementos de la cola. Las colas tienen numerosas aplicaciones en el mundo de la computa-
cin: colas de mensajes, colas de tareas a realizar por una impresora, colas de prioridades.
Conceptos clave
Arreglo circular Lista doble
Estructura de datos LIFO Lista enlazada
Estructura FIFO Nodos
Lista circular
Tipo abstracto de datos (TAD) lista
Una lista enlazada es una coleccin o secuencia de elementos dispuestos uno detrs de otro, en la que
cada elemento se conecta al siguiente por un enlace o referencia. La idea bsica consiste en cons-
truir una lista cuyos elementos llamados nodos se componen de dos partes (campos): la primera par-
te contiene la informacin y es, por consiguiente, un valor de un tipo genrico (denominado Dato,
TipoElemento, Info, etc.) y la segunda parte es una referencia (denominada enlace) que apunta,
enlaza, al siguiente elemento de la lista.
Figura 36.1 Lista enlazada (representacin grfica tpica).
...
e
1
e
2
e
3
e
n
Los enlaces se representan por flechas para facilitar la comprensin
de la conexin entre dos nodos; ello indica que el enlace tiene la direc-
cin en memoria del siguiente nodo. Los enlaces tambin sitan los
nodos en una secuencia. El primer nodo se enlaza al segundo nodo, el
segundo se enlaza al tercero y as sucesivamente hasta llegar al ltimo.
El nodo ltimo ha de ser representado de forma diferente para significar
que ste no se enlaza a ningn otro. Las listas se pueden dividir en cua-
tro categoras.
Clasificacin de las listas enlazadas
Los diferentes tipos de listas dependen de la forma de enlazar los nodos, son:
Listas simplemente enlazadas . Cada nodo (elemento) contiene un nico enlace que conecta ese
nodo al nodo siguiente o nodo sucesor. La lista es eficiente en recorridos directos (adelante).
Listas doblemente enlazadas . Cada nodo contiene dos enlaces, uno a su nodo predecesor y el
otro a su nodo sucesor. La lista es eficiente tanto en recorrido directo (adelante) como en re-
corrido inverso (atrs).
Lista circular simplemente enlazada . Una lista enlazada simplemente en la que el ltimo ele-
mento (cola) se enlaza al primer elemento (cabeza) de tal modo que la lista puede ser recorrida
de modo circular (en anillo).
Lista circular doblemente enlazada . Una lista doblemente enlazada en la que el ltimo elemento
se enlaza al primer elemento y viceversa. Esta lista se puede recorrer de modo circular (en ani-
llo) tanto en direccin directa (adelante) como inversa (atrs).
La implementacin de cada uno de los cuatro tipos de estructuras de listas se puede desarrollar
utilizando referencias.
El primer nodo, frente, de una lista es el nodo apuntado por cabeza. La lista encadena nodos jun-
tos desde el frente hasta el final (cola) de la lista. El final se identifica como el nodo cuyo campo re-
ferencia tiene el valor null.

A recordar
Una lista enlazada consta de un nmero de
elementos y cada elemento tiene dos compo-
nentes (campos), una referencia al siguiente
elemento de la lista y un valor, que puede ser
de cualquier tipo.
1110 Captulo 36 Listas, pilas y colas en Java
TAD Lista
Una lista almacena informacin del mismo tipo, con la caracterstica de que puede contener un nme-
ro indeterminado de elementos, y que estos elementos mantienen un orden explcito. Este ordena-
miento explcito se manifiesta en que, en s mismo, cada elemento contiene la direccin del siguiente
elemento. Una lista es una estructura de datos dinmica, el nmero de nodos puede variar rpidamen-
te en un proceso, aumentando los nodos por inserciones, o bien, disminuyendo por eliminacin de
nodos.
Matemticamente, una lista es una secuencia de cero o ms elementos de un determinado tipo.
(a1, a2, a3, ... , an) siendo n >= 0,
si n = 0 la lista es vaca.
Los elementos de la lista tienen la propiedad de estar ordenados de forma lineal, segn las posiciones
que ocupan en la misma. Se dice que a
i
precede a a
i+1
para i = 1 ... , n1; y que a
i
sucede a a
i1
para
i = 2 ... n.
Para formalizar el tipo de dato abstracto Lista a partir de la nocin matemtica, se define un
conjunto de operaciones bsicas con objetos de tipo Lista. Las operaciones:
L Lista , x Dato, p puntero
Listavacia(L) Inicializa la lista L como lista vaca.
Esvacia(L) Determina si la lista L est vaca.
Insertar(L, x, p) Inserta en la lista L un nodo con el campo dato x, delante del nodo de di-
reccin p.
Localizar(L, x) Devuelve la posicin/direccin donde est el campo de informacin x.
Suprimir(L, x) Elimina de la lista el nodo que contiene el dato x.
Anterior(L, p) Devuelve la posicin/direccin del nodo anterior a p.
Primero(L) Devuelve la posicin/direccin del primer nodo de la lista L.
Anula(L) Vaca la lista L.
Estas operaciones son las que pueden considerarse bsicas para manejar listas. En realidad, la deci-
sin de qu operaciones son las bsicas depende de las caractersticas de la aplicacin que se va a realizar
con los datos de la lista.
Clase lista
Una lista enlazada se compone de una serie de nodos enlazados mediante referencias. En Java, se de-
clara una clase para contener las dos partes del nodo: dato y enlace. Por ejemplo, para una lista enla-
zada de nmeros enteros la clase Nodo:
class Nodo
{
protected int dato;
protected Nodo enlace;
public Nodo(int t)
{
dato = t;
enlace = null;
}
}
Figura 36.2 Representacin grfica de una lista enlazada.
dato siguiente
...
dato siguiente dato siguiente dato
cabeza actual cola
1111 Clase lista
Dado que los tipos de datos que se puede incluir en una lista pueden ser de cualquier tipo (enteros,
dobles, caracteres o cualquier objeto), con el fin de que el tipo de dato de cada nodo se pueda cambiar
con facilidad, a veces se define la clase Elemento como una generalizacin del tipo de dato de cada
campo. En ese caso se utiliza una referencia a Elemento dentro del nodo, como se muestra a conti-
nuacin:
class Elemento
{
// ...
}
class Nodo
{
protected Elemento dato;
protected Nodo enlace;
}
Ejemplo 36.1
Se declara la clase Punto, representa un punto en el plano con su coordenada x y y. Tambin,
la clase Nodo con el campo dato referencia a objetos de la clase Punto. Estas clases formarn
parte del paquete ListaPuntos.
package ListaPuntos;
public class Punto
{
double x, y;
public Punto(double x, double y)
{
this.x = x;
this.y = y;
}
public Punto( ) // constructor por defecto
{
x = y = 0.0;
}
}
La clase Nodo que se escribe a continuacin, tiene como campo dato una referencia a Pun-
to, y el campo enlace una referencia a otro nodo. Se definen dos constructores, el primero
inicializa dato a un objeto Punto y enlace a null. El segundo, inicializa enlace de tal for-
ma que referencia a un nodo.
package ListaPuntos;
public class Nodo
{
protected Punto dato;
protected Nodo enlace;
public Nodo(Punto p)
{
dato = p;
enlace = null;
}
public Nodo(Punto p, Nodo n)
{
dato = p;
enlace = n;
}
}
1112 Captulo 36 Listas, pilas y colas en Java
Definicin de la clase lista
El acceso a una lista se hace mediante una, o ms, referencias a los nodos. Normalmente, se accede a
partir del primer nodo de la lista, llamado cabeza o cabecera de la lista. En ocasiones, se mantiene
tambin una referencia al ltimo nodo de la lista enlazada, llamado cola de la lista.
4.15 5.25 71.5
10.5 NULL
Cabeza
Definicin nodo Definicin de referencias

class Nodo Nodo cabeza;
{
double dato; Nodo cola;
Nodo enlace;
public Nodo( ){;}
}
Figura 36.3 Declaraciones de tipos en lista enlazada.
Cola
Nota de programacin
La referencia cabeza (y cola) de una lista enlazada, normal-
mente se inicializa a null, indica lista vaca (no tiene nodos),
cuando se inicia la construccin de una lista. Cualquier mtodo
que se escriba para implementar listas enlazadas debe poder
manejar una referencia de cabeza (y de cola) null.
Error
Uno de los errores tpicos en el tratamiento de referencias con-
siste en escribir la expresin p.miembro cuando el valor de la
referencia p es null.
La clase Lista define el atributo cabeza o primero para acceder a los elementos de la lista. Nor-
malmente, no es necesario definir el atributo referencia cola. El constructor de lista inicializa
primero a null (lista vaca).
Los mtodos de la clase lista implementan las operaciones de una lista enlazada: insercin,
bsqueda... Adems, el mtodo crearLista( ) construye iterativamente el primer elemento (pri-
mero) y los elementos sucesivos de una lista enlazada.
A continuacin se declara la clase lista para representar una lista enlazada de nmeros enteros.
La declaracin se realiza paso a paso con el fin de describir detalladamente las operaciones. En primer
lugar, la declaracin del nodo de la lista con el dato de la lista, dos constructores bsicos y mtodos
de acceso:
package ListaEnteros;
public class Nodo
{
protected int dato;
protected Nodo enlace;
public Nodo(int x)
{
dato = x;
enlace = null;

A recordar
La construccin y manipulacin de una lista
enlazada requiere el acceso a los nodos de la
lista a travs de una o ms referencias a nodos.
Normalmente, se incluye una referencia al pri-
mer nodo (cabeza) y adems, en algunas aplica-
ciones, una referencia al ltimo nodo (cola).
1113 Clase lista
}
public Nodo(int x, Nodo n)
{
dato = x;
enlace = n;
}
public int getDato( )
{
return dato;
}
public Nodo getEnlace( )
{
return enlace;
}
public void setEnlace(Nodo enlace)
{
this.enlace = enlace;
}
}
A continuacin la clase lista:
package ListaEnteros;
public class Lista
{
private Nodo primero;
public Lista( )
{
primero = null;
}
//...
La referencia primero (a veces se denomina cabeza) se ha inicializado en el constructor a un
valor nulo, es decir, a lista vaca.
El mtodo crearLista( ), en primer lugar crea un nodo con un valor y su referencia se asigna
a primero:
primero = new Nodo(19);
19 null
primero
La operacin de crear un nodo se puede realizar en un mtodo al que se pasa el valor del campo
dato y del campo enlace. Si ahora se desea aadir un nuevo elemento con el 61, y situarlo en el pri-
mer lugar de la lista:
primero = new Nodo(61,primero);
primero
19 null 61
Por ltimo, para obtener una lista compuesta de 4, 61, 19 se habr de ejecutar
primero = new Nodo(4,primero);
primero
19 4 61
El mtodo crearLista( )construye una lista y devuelve una referencia al objeto lista creado
(this).
private int leerEntero( ) {;}
public Lista crearLista( )
{
int x;
1114 Captulo 36 Listas, pilas y colas en Java
primero = null;
do {
x = leerEntero( );
if (x != 1)
{
primero = new Nodo(x,primero);
}
} while (x != 1);
return this;
}
Insercin de un elemento en una lista
El nuevo elemento que se desea incorporar a una lista se puede insertar de distintas formas, segn la
posicin de insercin. sta puede ser:
En la cabeza (elemento primero) de la lista.
En el final de la lista (elemento ltimo).
Antes de un elemento especificado, o bien,
Despus de un elemento especificado.
Insertar un nuevo elemento en la cabeza de la lista
La posicin ms eficiente para insertar un nuevo elemento es la cabeza, es decir, por el primer nodo
de la lista. El proceso de insercin se resume en este algoritmo:
1. Crear un nodo e inicializar el campo dato al nuevo elemento. La referencia del nodo creado
se asigna a nuevo, variable local del mtodo.
2. Hacer que el campo enlace del nuevo nodo apunte a la cabeza (primero) de la lista ori-
ginal.
3. Hacer que primero apunte al nodo que se ha creado.
El cdigo fuente del mtodo insertarCabezaLista:
public Lista insertarCabezaLista(Elemento entrada)
{
Nodo nuevo ;
nuevo = new Nodo(entrada);
nuevo.enlace = primero; // enlaza nuevo nodo al frente de la lista
primero = nuevo; // mueve primero y apunta al nuevo nodo
return this; // devuelve referencia del objeto Lista
}
Insertar entre dos nodos de la lista
Por ejemplo, en la lista de la figura 36.4 insertar el elemento 75 entre los nodos con los datos 25 y 40.
El algoritmo para la operacin de insertar entre dos nodos (n1, n2) requiere las siguientes etapas:
1. Crear un nodo con el nuevo elemento y el campo enlace a null. La referencia al nodo se
asigna a nuevo.
2. Hacer que el campo enlace del nuevo nodo apunte al nodo n2, ya que el nodo creado se ubi-
car justo antes de n2 (en el ejemplo de la figura 36.4, el nodo 40).
3. La variable referencia anterior tiene la direccin del nodo n1 (en el ejemplo de la figura
36.4, el nodo 25), entonces hacer que anterior.enlace apunte al nodo creado.
Figura 36.4 Insercin entre dos nodos.
10 25
75
40 NULL
1115 Clase lista
Grficamente las etapas del algoritmo y el cdigo que implementa la operacin.
Etapa 1
Se crea un nodo con el dato 75. La variable anterior apunta al nodo n1, en la figura 25.
40 NULL 10 25
primero
anterior
no se utiliza

75
Cdigo Java
nuevo = new Nodo(entrada);
Etapa 2
El campo enlace del nodo creado que apunte a n2, en la figura 40. La direccin de n2 se consigue con
anterior.enlace:
40 null 10 25
nuevo
anterior
75
Cdigo Java
nuevo.enlace = anterior.enlace
Etapa 3
Por ltimo, el campo enlace del nodo n1 (anterior) que apunte al nodo creado:
40 null 10 25
nuevo
anterior
75
nuevo
1116 Captulo 36 Listas, pilas y colas en Java
La operacin es un mtodo de la clase lista:
public Lista insertarLista(Nodo anterior, Elemento entrada)
{
Nodo nuevo;
nuevo = new Nodo(entrada);
nuevo.enlace = anterior.enlace;
anterior.enlace = nuevo;
return this;
}
Antes de llamar al mtodo insertarLista( ), es necesario buscar la direccin del nodo n1,
esto es, del nodo a partir del cual se enlazar el nodo que se va a crear.
Bsqueda en listas enlazadas
La operacin bsqueda de un elemento en una lista enlazada recorre la lista hasta encontrar el nodo
con el elemento. El algoritmo, una vez encontrado el nodo, devuelve la referencia a ese nodo (en caso
negativo, devuelve null). Otro planteamiento es que el mtodo devuelva true si encuentra el nodo
con el elemento, y false si no est en la lista.
primero
5.75 41.25 101.43 0.25 NULL
ndice
Figura 36.5 Bsqueda en una lista.
El mtodo buscarLista, de la clase Lista, utiliza la referencia indice para recorrer la lista,
nodo a nodo. El bucle de bsqueda inicializa indice al nodo primero, compara el nodo referencia-
do por indice con el elemento buscado. Si coincide, la bsqueda termina; en caso contrario, indice
avanza al siguiente nodo. La bsqueda termina cuando se encuentra el nodo, o bien cuando se ha re-
corrido la lista y entonces indice toma el valor null. La comparacin entre el dato buscado y el dato
del nodo, que realiza el mtodo buscarLista( ), utiliza el operador == por claridad; realmente slo
se utiliza dicho operador si los datos son de tipo simple (int, double, ...). Normalmente, los datos
de los nodos son objetos y entonces se utiliza el mtodo equals( ) que compara dos objetos.
Cdigo Java
public Nodo buscarLista(Elemento destino)
{
Nodo indice;
for (indice = primero; indice!= null; indice = indice.enlace)
if (destino == indice.dato) // (destino.equals(indice.dato))
return indice;
return null;
}
Borrado de un nodo de una lista
La operacin de eliminar un nodo de una lista enlazada supone enlazar el nodo anterior con el nodo
siguiente al que se desea eliminar y liberar la memoria que ocupa. El algoritmo sigue estos pasos:
1. Bsqueda del nodo que contiene el dato. Se ha de obtener la direccin del nodo a eliminar y la
direccin del anterior.
2. El enlace del nodo anterior que apunte al nodo siguiente del que se elimina.
3. Si el nodo a eliminar es el cabeza de la lista (primero), se modifica primero para que tenga
la direccin del siguiente nodo.
4. Por ltimo, la memoria ocupada por el nodo se libera. Es el propio sistema el que libera el
nodo, al dejar de estar referenciado.
1117 Lista ordenada
A continuacin se escribe el mtodo eliminar( ), miembro de la clase Lista, recibe el dato
del nodo que se quiere borrar. Construye su bucle de bsqueda con el fin de disponer de la direc-
cin del nodo anterior.
Cdigo Java
public void eliminar (Elemento entrada)
{
Nodo actual, anterior;
boolean encontrado;
actual = primero;
anterior = null;
encontrado = false;
// bsqueda del nodo y del anterior
while ((actual!=null) && (!encontrado))
{
encontrado = (actual.dato == entrada);
//con objetos: actual.dato.equals(entrada)
if (!encontrado)
{
anterior = actual;
actual = actual.enlace;
}
}
// Enlace del nodo anterior con el siguiente
if (actual != null)
{
// Distingue entre que el nodo sea el cabecera,
// o del resto de la lista
if (actual == primero)
{
primero = actual.enlace;
}
else
{
anterior.enlace = actual.enlace;
}
actual = null; // no es necesario al ser una variable local
}
}
Lista ordenada
Los elementos de una lista tienen la propiedad de estar ordenados de forma lineal, segn las posiciones
que ocupan en la misma. Ahora bien, tambin es posible mantener una lista enlazada ordenada segn
el dato asociado a cada nodo. La figura 36.6 muestra una lista enlazada de nmeros reales, ordenada
de forma creciente.
La forma de insertar un elemento en una lista ordenada siempre realiza la operacin de tal forma
que la lista resultante mantiene la propiedad de ordenacin. Para esto, en primer lugar determina la
posicin de insercin, y a continuacin ajusta los enlaces.
Por ejemplo, para insertar el dato 104 en la lista de la figura 36.7 es necesario recorrer la lista has-
ta el nodo con 110.0, que es el inmediatamente mayor. El pointer indice se queda con la direccin
del nodo anterior, a partir del cual se enlaza el nuevo nodo.
primero
5.5 51.0 101.5 110.0 NULL
Figura 36.6 Lista ordenada.
1118 Captulo 36 Listas, pilas y colas en Java
primero
5.5 51.0 101.5 110.0 NULL
104
nuevo
ndice
Figura 36.7 Insercin en una lista ordenada.
El mtodo insertaOrden( ) crea una lista ordenada; el punto de partida es una lista vaca, a la
que se aaden nuevos elementos, de tal forma que en todo momento el orden de los elementos es cre-
ciente. La insercin del primer nodo de la lista consiste, sencillamente, en crear el nodo y asignar su
referencia a la cabeza de la lista. El segundo elemento se ha de insertar antes o despus del primero,
dependiendo de que sea menor o mayor. En general, para insertar un nuevo elemento a la lista orde-
nada, primero se busca la posicin de insercin en la lista actual, es decir, el nodo a partir del cual se
ha de enlazar el nuevo nodo para que la lista mantenga la ordenacin.
Los datos de una lista ordenada han de ser de tipo ordinal (tipo al que se pueda aplicar los opera-
dores ==, <, >); o bien objetos de clases que tengan definidos mtodos de comparacin (equals( ),
compareTo( ), ...). A continuacin se escribe el cdigo Java que implementa el mtodo para una lis-
ta de enteros.
public ListaOrdenada insertaOrden(int entrada)
{
Nodo nuevo ;
nuevo = new Nodo(entrada);
if (primero == null) // lista vaca
primero = nuevo;
else if (entrada < primero.getDato( ))
{
nuevo. setEnlace(primero);
primero = nuevo;
}
else /* bsqueda del nodo anterior a partir del que
se debe insertar */
{
Nodo anterior, p;
anterior = p = primero;
while ((p.getEnlace( ) != null) && (entrada > p.getDato( )))
{
anterior = p;
p = p.getEnlace( );
}
if (entrada > p.getDato( )) //se inserta despus del ltimo nodo
anterior = p;
// Se procede al enlace del nuevo nodo
nuevo.setEnlace(anterior.getEnlace( ));
anterior.setEnlace(nuevo);
}
return this;
}
Clase ListaOrdenada
Se declara la clase ListaEnlazada como extensin (derivada) de la clase lista. Por consiguiente,
hereda las propiedades de lista. Los mtodos eliminar( ) y buscarLista( ) se deben redefi-
nir para que la bsqueda del elemento aproveche el hecho de que stos estn ordenados.
La declaracin de la clase:
public class ListaOrdenada extends Lista
{
public ListaOrdenada( )
1119 Lista doblemente enlazada
{
super( );
}
public ListaOrdenada insertaOrden(int entrada) // ya definido
// mtodos a codificar:
public void eliminar (int entrada){ ; }
public Nodo buscarLista(int destino){ ; }
}
Lista doblemente enlazada
Existen numerosas aplicaciones en las que es conveniente poder acceder a los elementos o nodos de
una lista en cualquier orden, tanto hacia adelante como hacia atrs. La estructura lista doblemente
enlazada hace posible el acceso bidireccional a los elementos. Cada elemento de la lista dispone de
dos pointers (referencias), adems del valor almacenado. Una referencia apunta al siguiente elemento
de la lista y la otra referencia apunta al elemento anterior.
cabeza
a)
izquierdo Dato derecho
b)
Figura 36.8 Lista doblemente enlazada. a) Lista con tres nodos; b) nodo.
Las operaciones de una Lista doble son similares a las de una Lista: insertar, eliminar, buscar,
recorrer... Las operaciones que modifican la lista, insertar y eliminar, deben realizar ajustes de los dos
pointers de cada nodo. La figura 36.9 muestra los ajustes de los enlaces que se deben realizar al inser-
tar un nodo a la derecha del nodo actual.
Figura 36.9 Insercin de un nodo en una lista doblemente enlazada.
D
Nodo actual
I
D I
Para eliminar un nodo de la lista doble se necesita enlazar el nodo anterior con el nodo siguiente
del que se borra, como se observa en la figura 36.10.
Figura 36.10 Eliminacin de un nodo en una lista doblemente enlazada.
D I D
I
D I D I
I D
1120 Captulo 36 Listas, pilas y colas en Java
Clase Lista Doble
Un nodo de una lista doblemente enlazada tiene dos pointer (referencias) para enlazar con nodo iz-
quierdo y derecho, adems la parte correspondiente al campo dato. La declaracin de la clase Nodo:
package listaDobleEnlace;
public class Nodo
{
protected TipoDato dato;
protected Nodo adelante;
protected Nodo atras;
public Nodo(int entrada)
{
dato = entrada;
adelante = atras = null;
}
// ...
}
La clase ListaDoble implementa la estructura lista doblemente enlazada. La clase dispone de
la variable cabeza que referencia al primer nodo de la lista, y la variable cola para referenciar al l-
timo nodo.
package listaDobleEnlace;
public class ListaDoble
{
protected Nodo cabeza;
protected Nodo cola;
// mtodos de la clase (implementacin)
public ListaDoble( ){ cabeza = cola = null;}
public ListaDoble insertarCabezaLista(TipoDato entrada){;}
public ListaDoble insertaDespues(Nodo anterior, TipoDato entrada)
public void eliminar (TipoDato entrada)
public void visualizar( )
public void buscarLista(TipoDato destino)
}
Insertar un elemento en una lista doblemente enlazada
Se puede aadir un nuevo nodo a la lista de distintas formas, segn la posicin donde se inserte el
nodo. La posicin de insercin puede ser:
En la cabeza (elemento primero) de la lista.
Al final de la lista (elemento ltimo).
Antes de un elemento especificado.
Despus de un elemento especificado.
El algoritmo de insercin depende de la posicin donde se inserte. Los pasos que se siguen para
insertar despus de un nodo n son:
1. Crear un nodo con el nuevo elemento y asignar su referencia a la variable nuevo.
2. Hacer que el enlace adelante del nuevo nodo apunte al nodo siguiente de n (o bien a null si
n es el ltimo nodo). El enlace atras del nodo siguiente a n (si n no es el ltimo nodo) tiene
que apuntar a nuevo.
3. Hacer que el enlace adelante del nodo n apunte al nuevo nodo. A su vez, el enlace atras
del nuevo nodo debe apuntar a n.
El mtodo de la clase ListaDoble, insertaDespues( ), implementa el algoritmo. El primer
argumento, anterior, representa el nodo n a partir del cual se enlaza el nuevo nodo. El segundo ar-
gumento, entrada, es el dato que se aade a la lista.
1121 Lista doblemente enlazada
Cdigo Java
public ListaDoble insertaDespues(Nodo anterior, TipoDato entrada)
{
Nodo nuevo;
nuevo = new Nodo(entrada);
nuevo.adelante = anterior.adelante;
if (anterior.adelante !=null)
anterior.adelante.atras = nuevo;
anterior.adelante = nuevo;
nuevo.atras = anterior;
if (anterior == cola) cola = nuevo; // es el ltimo elemento
return this;
}
Eliminar un elemento de una lista doblemente enlazada
Quitar un nodo de una lista doble supone realizar el enlace de dos nodos, el nodo anterior con el nodo
siguiente al que se desea eliminar. La referencia adelante del nodo anterior debe apuntar al nodo
siguiente, y la referencia atras del nodo siguiente debe apuntar al nodo anterior.
El algoritmo es similar al del borrado para una lista simple. Ahora, la direccin del nodo anterior
se encuentra en la referencia atras del nodo a borrar. Los pasos a seguir son:
1. Bsqueda del nodo que contiene el dato.
2. La referencia adelante del nodo anterior tiene que apuntar a la referencia adelante del
nodo a eliminar (si no es el nodo cabecera).
3. La referencia atras del nodo siguiente a borrar tiene que apuntar a la referencia atras del
nodo a eliminar (si no es el ltimo nodo).
4. Si el nodo que se elimina es el primero, cabeza, se modifica cabeza para que tenga la direc-
cin del nodo siguiente.
5. La memoria ocupada por el nodo es liberada automticamente.
El mtodo eliminar, de la clase ListaDoble, implementa el algoritmo:
public void eliminar (TipoDato entrada)
{
Nodo actual;
boolean encontrado = false;
actual = cabeza;
// Bucle de bsqueda
while ((actual != null) && (!encontrado))
{
/* La comparacin se podr realizar con el mtodo equals( )...,
depende del tipo que tenga entrada */
encontrado = (actual.dato == entrada);
if (!encontrado)actual = actual.adelante;
}
// Enlace de nodo anterior con el siguiente
if (actual != null)
{
//distingue entre nodo cabecera o resto de la lista
if (actual == cabeza)
{
cabeza = actual.adelante;
if (actual.adelante != null)
actual.adelante.atras = null;
}
else if (actual.adelante != null) // No es el ltimo nodo
{
actual.atras.adelante = actual.adelante;
actual.adelante.atras = actual.atras;
}
else // ltimo nodo
actual.atras.adelante = null;
1122 Captulo 36 Listas, pilas y colas en Java
if (actual == cola) cola = actual.atras;
actual = null;
}
}
Lista circular
En las listas lineales simples o dobles siempre hay un primer nodo (cabeza) y un ltimo nodo (cola).
Una lista circular, por propia naturaleza, no tiene ni principio ni fin. Sin embargo, resulta til estable-
cer un nodo a partir del cual se acceda a la lista y as poder acceder a sus nodos.
La figura 36.11 muestra una lista circular con enlace simple; podra considerarse que es una lista
lineal cuyo ltimo nodo apunte al primero.
Figura 36.11 Lista circular.
Lc
4.0 5.0 7.5 1.5
Las operaciones que se realizan sobre una lista circular son similares a las operaciones sobre listas
lineales, teniendo en cuenta que no hay primero ni ltimo nodo, aunque s un nodo de acceso a la
lista. Estas operaciones permiten construir el TAD lista circular y su funcionalidad es la siguiente:
Inicializacin o creacin.
Insercin de elementos en una lista circular.
Eliminacin de elementos de una lista circular.
Bsqueda de elementos de una lista circular.
Recorrido de cada uno de los nodos de una lista circular.
Verificacin de lista vaca.
La construccin de una lista circular se puede hacer con enlace simple o enlace doble. La imple-
mentacin que se desarrolla, en este apartado, enlaza dos nodos con un enlace simple.
Se declara la clase Nodo, con el campo dato y enlace; y la clase ListaCircular con el pointer
de acceso a la lista, junto a los mtodos que implementan las operaciones. Los elementos de la lista
pueden ser de cualquier tipo, se puede abstraer el tipo de stos en otra clase, por ejemplo Elemento;
con el fin de simplificar se supone un tipo conocido.
El constructor de la clase Nodo vara respecto al de las listas no circulares; el campo referencia
enlace, en vez de quedar a null, se inicializa para que apunte al mismo nodo, de tal forma que que-
da como lista circular de un solo nodo.
package listaCircular;
public class Nodo
{
Elemento dato;
Nodo enlace;
public Nodo (Elemento entrada)
{
dato = entrada;
enlace = this; // se apunta a s mismo
}
}
A tener en cuenta
El pointer de acceso a una lista circular, lc, normalmente apunta al ltimo nodo aadido a la
estructura. Esta convencin puede cambiar ya que en una estructura circular no hay primero
ni ltimo.
Figura 36.12
Creacin de un
nodo de lista
circular.
nuevo 2.5
1123 Lista circular
Clase Lista Circular
La clase ListaCircular declara la variable lc a partir de la cual se puede acceder a cualquier nodo
de la lista. El constructor de la clase inicializa la lista vaca.
package listaCircular;
public class ListaCircular
{
private Nodo lc;
public ListaCircular( )
{
lc = null;
}
public ListaCircular insertar(Elemento entrada)
{
Nodo nuevo;
nuevo = new Nodo(entrada);
if (lc != null) // lista circular no vaca
{
nuevo.enlace = lc.enlace;
lc.enlace = nuevo;
}
lc = nuevo;
return this;
}
El algoritmo empleado por el mtodo insertar, realiza la insercin en el nodo anterior al de ac-
ceso a la lista lc y considera que lc tiene la direccin del ltimo nodo insertado.
Eliminar un elemento de una lista circular
Para eliminar un nodo hay que enlazar el nodo anterior con el nodo siguiente al que se desea eliminar
y que el sistema libere la memoria que ocupa. El algoritmo es:
1. Bsqueda del nodo que contiene el dato.
2. Enlace del nodo anterior con el siguiente.
3. En caso de que el nodo a eliminar sea por el que se accede a la lista, lc, se modifica lc para
que tenga la direccin del nodo anterior.
4. Por ltimo, el sistema libera la memoria ocupada por el nodo al anular la referencia.
La implementacin del mtodo debe tener en cuenta que la lista circular tendr un solo nodo, ya
que al eliminarlo la lista se queda vaca (lc = null). La condicin de lista con un nodo se corres-
ponde con la forma de inicializar un nodo: lc = lc.enlace.
El mtodo recorre la lista buscando el nodo con el dato a eliminar, utiliza un pointer al nodo an-
terior para que cuando se encuentre el nodo se enlace con el siguiente. Se accede al dato con la sen-
tencia actual.enlace.dato, con el fin de que si coincide con el dato a eliminar, se disponga en
actual el nodo anterior.
Cdigo Java
public void eliminar(Elemento entrada)
{
Nodo actual;
boolean encontrado = false;
actual = lc;
while ((actual.enlace != lc) && (!encontrado))
{
encontrado = (actual.enlace.dato == entrada);
if (!encontrado)
{
actual = actual.enlace;
}
}
1124 Captulo 36 Listas, pilas y colas en Java
encontrado = (actual.enlace.dato == entrada);
// Enlace de nodo anterior con el siguiente
if (encontrado)
{
Nodo p;
p = actual.enlace; // Nodo a eliminar
if (lc == lc.enlace) // Lista con un solo nodo
lc = null;
else
{
if (p == lc)
{
lc = actual; // Se borra el elemento referenciado por lc,
// el nuevo acceso a la lista es el anterior
}
actual.enlace = p.enlace;
}
p = null;
}
}
Recorrer una lista circular
Una operacin comn a todas las estructuras enlazadas es recorrer o visitar todos los nodos de la es-
tructura. En una lista circular el recorrido puede empezar en cualquier nodo e iterativamente ir proce-
sando cada nodo hasta alcanzar el nodo de partida. El mtodo recorrer, miembro de la clase
ListaCircular, inicia el recorrido en el nodo siguiente al de acceso a la lista, lc, y termina cuando
alcanza el nodo lc.
public void recorrer( )
{
Nodo p;
if (lc != null)
{
p = lc.enlace; // siguiente nodo al de acceso
do {
System.out.println("\t" + p.dato);
p = p.enlace;
} while(p != lc.enlace);
}
else
System.out.println("\t Lista Circular vaca.");
}
Tipo abstracto de datos pila
Una pila (stack) es una coleccin ordenada de elementos a los que slo se puede acceder por un nico
lugar o extremo de la pila. Los elementos de la pila se aaden o quitan (borran) de la misma slo por
su parte superior (cima) de la pila.
Las entradas de la pila deben ser eliminadas en el orden inverso al que se situaron en la misma.
Por ejemplo, se puede crear una pila de libros, situando primero un diccionario, encima de l una en-
ciclopedia y encima de ambos una novela de modo que la pila tendr la novela en la parte superior.
Figura 36.13 Pila de libros.
Novela
Enciclopedia
Diccionario
1125 Tipo abstracto de datos pila
Debido a su propiedad especfica ltimo en entrar, primero en salir se conoce a las pilas como
estructura de datos LIFO (last-in/first-out).
La pila se puede implementar guardando los elementos en un arreglo en cuyo caso su dimensin
o longitud es fija. Tambin se puede utilizar un Vector para almacenar los elementos. Otra forma de
implementacin consiste en construir una lista enlazada, cada elemento de la pila forma un nodo de la
lista; la lista crece o decrece segn se aaden o se extraen, respectivamente, elementos de la pila; sta
es una representacin dinmica y no existe limitacin en su tamao excepto la memoria de la compu-
tadora.
Una pila puede estar vaca (no tiene elementos) o llena (en la representacin con un arreglo, si se
ha llegado al ltimo elemento). Si un programa intenta sacar un elemento de una pila vaca, se produ-
cir un error, una excepcin, debido a que esa operacin es imposible; esta situacin se denomina des-
bordamiento negativo (underflow). Por el contrario, si un programa intenta poner un elemento en
una pila llena se produce un error, o excepcin, de desbordamiento (overflow) o rebosamiento.
Especificaciones de una pila
Las operaciones que sirven para definir una pila y poder manipular su contenido son las siguientes.
Tipo de dato Elemento que se almacena en la pila
Operaciones
CrearPila Inicia
Insertar (push) Pone un dato en la pila
Quitar (pop) Retira (saca) un dato de la pila
Pila vaca Comprueba si la pila no tiene elementos
Pila llena Comprueba si la pila est llena de elementos
Limpiar pila Quita todos sus elementos y deja la pila vaca
CimaPila Obtiene el elemento cima de la pila
Tamao de la pila Nmero de elementos mximo que puede contener la pila
El ejemplo 36.2 muestra cmo declarar una pila con un arreglo lineal. El tipo de los elementos de
la pila es Object, lo que permite que pueda contener cualquier tipo de objeto.
Figura 36.14 Operaciones bsicas de una pila.
Insertar
Cima
Quitar
Fondo
Ejemplo 36.2
Declarar la clase Pila con elementos de tipo Object. Insertar y extraer de la pila datos de
tipo entero.
La declaracin de la clase:
package TipoPila;
1126 Captulo 36 Listas, pilas y colas en Java
Pila dinmica implementada con Vector
La clase Vector es un contenedor de objetos que puede ser manejado para que crezca y decrezca di-
nmicamente. Los elementos de Vector son de tipo Object. Dispone de mtodos para asignar un
elemento en una posicin (insertElementAt), para aadir un elemento a continuacin del ltimo
(addElement), para obtener el elemento que se encuentra en una posicin determinada (elemen-
tAt), para eliminar un elemento (removeElementAt) ...
La posicin del ltimo elemento aadido a la pila se mantiene con la variable cima. Inicialmente
cima == 1, que es la condicin de pila vaca.
Insertar un nuevo elemento supone aumentar cima y asignar el elemento a la posicin cima del
Vector. La operacin se realiza llamando al mtodo addElement. No resulta necesario implementar
el mtodo pilaLlena ya que la capacidad del Vector crece dinmicamente.
El mtodo quitar( ) devuelve el elemento cima de la pila y lo elimina. Al utilizar un Vector,
llamando a removeElementAt(cima) se elimina el elemento cima; a continuacin se decrementa
cima.
La implementacin es:
package TipoPila;
import java.util.Vector;
public class PilaVector
{
private static final int INICIAL = 19;
private int cima;
private Vector listaPila;
public PilaVector( )
{
cima = 1;
listaPila = new Vector(INICIAL);
}
public void insertar(Object elemento)throws Exception
{
cima++;
listaPila.addElement(elemento);
}
public Object quitar( ) throws Exception
{
Object aux;
public class PilaLineal
{
private static final int TAMPILA = 49;
private int cima;
private Object [ ]listaPila;
// operaciones que aaden o extraen elementos
public void insertar(Object elemento)
public Object quitar( )throws Exception
public Object cimaPila( )throws Exception
// resto de operaciones
}
El siguiente segmento de cdigo inserta los datos 11, 50 y 22:
PilaLineal pila = new PilaLineal( );
pila.insertar(new Integer(11));
pila.insertar(new Integer(50));
pila.insertar(new Integer(22));
Para extraer de la pila y asignar el dato a una variable:
Integer dato;
dato = (Integer) pila.quitar( );
1127 Pila implementada con una lista enlazada
if (pilaVacia( ))
{
throw new Exception ("Pila vaca, no se puede extraer.");
}
aux = listaPila.elementAt(cima);
listaPila.removeElementAt(cima);
cima;
return aux;
}
public Object cimaPila( ) throws Exception
{
if (pilaVacia( ))
{
throw new Exception ("Pila vaca, no se puede extraer.");
}
return listaPila.elementAt(cima);
}
public boolean pilaVacia( )
{
return cima == 1;
}
public void limpiarPila( )throws Exception
{
while (! pilaVacia( ))
quitar( );
}
}
Nota de programacin
Para utilizar una pila de elementos de tipo primitivo (int, char, long, float, double ... )
es necesario, para insertar, crear un objeto de la correspondiente clase envolvente (Integer,
Character, Long, Float, Double...) y pasar dicho objeto como argumento del mtodo in-
sertar( ).
Pila implementada con una lista enlazada
Guardar los elementos de la pila en una lista enlazada permite que la pila crezca o decrezca de mane-
ra indefinida. El tamao se ajusta exactamente a su nmero de elementos.
Figura 36. 15 Representacin de una pila con una lista enlazada.
Pila
Cima
1128 Captulo 36 Listas, pilas y colas en Java
Clase Pila y NodoPila
Los elementos de la pila son los nodos de la lista, con un campo para guardar el elemento y otro de en-
lace. La clase NodoPila representa un nodo de la lista enlazada. Tiene dos atributos: elemento guarda
el elemento de la pila y siguiente contiene la direccin del siguiente nodo de la lista. El constructor
pone el dato en elemento e inicializa siguiente a null. El tipo de dato de elemento se corresponde
con el tipo de los elementos de la pila, para que no dependa de un tipo concreto; para que sea ms gen-
rico se utiliza el tipo Object y de esa forma puede almacenar cualquier tipo de referencia.
package TipoPila;
public class NodoPila
{
Object elemento;
NodoPila siguiente;
NodoPila(Object x)
{
elemento = x;
siguiente = null;
}
}
La clase PilaLista implementa las operaciones del TAD pila. Adems, dispone del atributo cima
que es la direccin del primer nodo de la lista. El constructor inicializa la pila vaca (cima == null),
realmente, a la condicin de lista vaca.
package TipoPila;
public class PilaLista
{
private NodoPila cima;
public PilaLista( )
{
cima = null;
}
// operaciones
}
Implementacin de las operaciones
Las operaciones insertar, quitar, cima acceden a la lista directamente con la referencia cima
(apunta al ltimo nodo apilado). Entonces, como no necesitan recorrer los nodos de la lista, no depen-
den del nmero de nodos, la eficiencia de cada operacin es constante, O(1).
La clase PilaLista forma parte del mismo paquete que NodoLista, por ello tiene acceso a to-
dos sus miembros.
Verificacin del estado de la pila:
public boolean pilaVacia( )
{
return cima == null;
}
Poner un elemento en la pila. Crea un nuevo nodo con el elemento que se pone en la pila y se enlaza
por la cima.
public void insertar(Object elemento)
{
NodoPila nuevo;
nuevo = new NodoPila(elemento);
nuevo.siguiente = cima;
cima = nuevo;
}
1129 Pila implementada con una lista enlazada
Eliminacin del elemento cima. Retorna el elemento cima y lo quita de la pila.
public Object quitar( ) throws Exception
{
if (pilaVacia( ))
throw new Exception ("Pila vaca, no se puede extraer.");
Object aux = cima.elemento;
cima = cima.siguiente;
return aux;
}
Figura 36.16 Apilar un elemento.
Figura 36.17 Quita la cima de la pila.
pila
Obtencin del elemento cabeza o cima de la pila, sin modificar la pila:
public Object cimaPila( ) throws Exception
{
if (pilaVacia( ))
throw new Exception ("Pila vaca, no se puede leer cima.");
return cima.elemento;
}
Vaciado de la pila. Libera todos los nodos de que consta la pila. Recorre los n nodos de la lista enlazada,
entonces es una operacin lineal, O(n).
public void limpiarPila( )
{
NodoPila t;
while(!pilaVacia( ))
{
t = cima;
cima = cima.siguiente;
t.siguiente = null;
}
}
pila
cima
cima
1130 Captulo 36 Listas, pilas y colas en Java
Tipo abstracto de datos Cola
Una cola es una estructura de datos que almacena elementos en una lista y permite acceder a los datos
por uno de los dos extremos de la lista (figura 36.18). Un elemento se inserta en la cola (parte final)
de la lista y se suprime o elimina por el frente (parte inicial, frente) de la lista. Las aplicaciones utili-
zan una cola para almacenar elementos en su orden de aparicin o concurrencia.
Figura 36.18 Una cola.
Los elementos se eliminan (se quitan) de la cola en el mismo orden
en que se almacena, por ello una cola es una estructura de tipo FIFO
(first-in/first-out, primero en entrar-primero en salir o bien primero en
llegar-primero en ser servido).
Especificaciones del tipo abstracto de datos Cola
Las operaciones que sirven para definir una cola y poder manipular su
contenido son las siguientes:
Tipo de dato Elemento que se almacena en la cola
Operaciones
CrearCola Inicia la cola como vaca
Insertar Aade un elemento por el nal de la cola
Quitar Retira (extrae) el elemento frente de la cola
Cola vaca Comprobar si la cola no tiene elementos
Cola llena Comprobar si la cola est llena de elementos
Frente Obtiene el elemento frente o primero de la cola
Tamao de la cola Nmero de elementos mximo que puede contener la cola
Las colas se implementan utilizando una estructura esttica (arreglos), o una estructura dinmica
(listas enlazadas, Vector...). Utilizar un arreglo tiene el problema del avance lineal de frente y fin;
este avance deja huecos por la izquierda del arreglo. Llega a ocurrir que fin alcanza el ndice ms alto
del arreglo, sin poder aadir nuevos elementos y sin embargo haber posiciones libres a la izquierda de
frente.

A recordar
Una cola es una estructura de datos cuyos ele-
mentos mantienen un cierto orden, tal que slo
se pueden aadir elementos por un extremo,
final de la cola, y eliminar o extraer por el otro
extremo, llamado frente.
Figura 36.19 Una cola representada en un arreglo.
1 2 3 4
A G H K
frente fin
1 2 3 4
G H K
(posicin de frente y fin despus de extraer)
frente fin
1 2 3 4 ltimo
Frente Fin
1131 Tipo abstracto de datos Cola
Cola con un arreglo circular
La forma ms eficiente de almacenar una cola en un array es modelar a ste de tal forma que se una
el extremo final con el extremo cabeza. Tal arreglo se denomina arreglo circular y permite que la to-
talidad de sus posiciones se utilicen para almacenar elementos de la cola sin necesidad de desplazar
elementos. La figura 36.20 muestra un arreglo circular de n elementos.
Figura 36.20 Un arreglo circular.
n 1 0
1
El arreglo se almacena de modo natural en la memoria, como un bloque lineal de n elementos. Se
necesitan dos marcadores (apuntadores) frente y fin para indicar, respectivamente, la posicin del ele-
mento cabeza y del ltimo elemento puesto en la cola.
Figura 36.21 Una cola vaca.
0
1
frente
final
2
n 1
El frente siempre contiene la posicin del primer elemento de la cola y avanza en el sentido de
las agujas del reloj; fin contiene la posicin donde se puso el ltimo elemento, tambin avanza en el
sentido del reloj (circularmente a la derecha). La implementacin del movimiento circular se realiza
segn la teora de los restos, de tal forma que se generen ndices de 0 a MAXTAMQ1:
Mover fin adelante = (fin + 1) % MAXTAMQ
Mover frente adelante = (frente + 1) % MAXTAMQ
Los algoritmos que formalizan la gestin de colas en un arreglo circular han de incluir las opera-
ciones bsicas del TAD cola; en concreto, las siguientes tareas bsicas:
Creacin de una cola vaca, de tal forma que fin apunte a una posicin inmediatamente ante-
rior a frente:
frente = 0; fin = MAXTAMQ1.
Comprobar si una cola est vaca:
frente == siguiente(fin)
Comprobar si una cola est llena. Para diferenciar la condicin entre cola llena y cola vaca se
sacrifica una posicin del arreglo, de tal forma que la capacidad de la cola va a ser MAXTAMQ1.
La condicin de cola llena:
frente == siguiente(siguiente(fin))

1132 Captulo 36 Listas, pilas y colas en Java


Poner un elemento a la cola, si la cola no est llena, avanzar fin a la siguiente posicin: fin =
(fin + 1) % MAXTAMQ y asignar el elemento.
Retirar un elemento de la cola, si la cola no est vaca, quitarlo de la posicin frente y avanzar
frente a la siguiente posicin:(frente + 1) % MAXTAMQ.
Obtener el elemento primero de la cola, si la cola no est vaca, sin suprimirlo de la cola.
Clase cola con array circular
La clase declara los apuntadores frente, fin y el arreglo listaCola[ ]. El mtodo siguiente( )
obtiene la posicin siguiente de una dada, aplicando la teora de los restos.
A continuacin se codifica los mtodos que implementan las operaciones del TAD cola. Ahora el
tipo de los elementos es Object, de tal forma que se pueda guardar cualquier tipo de elementos.
package TipoCola;
public class ColaCircular
{
private static fin int MAXTAMQ = 99;
protected int frente;
protected int fin;
protected Object [ ]listaCola ;
private int siguiente(int r)
{
return (r+1) % MAXTAMQ;
}
public ColaCircular( )
{
frente = 0;
fin = MAXTAMQ1;
listaCola = new Object [MAXTAMQ];
}
// operaciones de modificacin de la cola
public void insertar(Object elemento) throws Exception
{
if (!colaLlena( ))
{
fin = siguiente(fin);
listaCola[fin] = elemento;
}
else
throw new Exception("Overflow en la cola");
}
public Object quitar( ) throws Exception
{
if (!colaVacia( ))
{
Object tm = listaCola[frente];
frente = siguiente(frente);
return tm;
}
else
throw new Exception("Cola vacia ");
}
public void borrarCola( )
{
frente = 0;
fin = MAXTAMQ1;
}
// acceso a la cola
public Object frenteCola( ) throws Exception
{
if (!colaVacia( ))
{
1133 Cola implementada con una lista enlazada
return listaCola[frente];
}
else
throw new Exception("Cola vacia ");
}
// mtodos de verificacin del estado de la cola
public boolean colaVacia( )
{
return frente == siguiente(fin);
}
public boolean colaLlena( )
{
return frente == siguiente(siguiente(fin));
}
}
Cola implementada con una lista enlazada
La implementacin del TAD Cola con una lista enlazada permite ajustar el tamao exactamente al n-
mero de elementos de la cola; la lista enlazada crece y decrece segn las necesidades, segn se incor-
poren elementos o se retiren. Utiliza dos apuntadores (referencias) para acceder a la lista, frente y
fin, que son los extremos por donde salen y por donde se ponen, respectivamente, los elementos de
la cola.
Figura 36.22 Cola con lista enlazada.
e
n
e
2
e
3
e
1
frente fin
...
La referencia frente apunta al primer elemento de la lista y por tanto de la cola (el primero en
ser retirado). La referencia fin apunta al ltimo elemento de la lista y tambin de la cola.
Declaracin de nodo y cola
Se utilizan las clases Nodo y ColaLista. El Nodo representa al elemento y al enlace con el siguien-
te nodo; al crear un Nodo se asigna el elemento y el enlace se pone null. Con el objetivo de genera-
lizar, el elemento se declara de tipo Object.
La clase ColaLista define las variables (atributos) de acceso: frente y fin, y las operaciones
bsicas del TAD cola. El constructor de ColaLista inicializa frente y fin a null, es decir, a la
condicin cola vaca.
package TipoCola;
// declaracin de Nodo (slo visible en este paquete)
class Nodo
{
Object elemento;
Nodo siguiente;
public Nodo(Object x)
{
elemento = x;
siguiente = null;
}
}
1134 Captulo 36 Listas, pilas y colas en Java
// declaracin de la clase ColaLista
public class ColaLista
{
protected Nodo frente;
protected Nodo fin;
// constructor: crea cola vaca
public ColaLista( )
{
frente = fin = null;
}
// insertar: pone elemento por el final
public void insertar(Object elemento)
{
Nodo a;
a = new Nodo(elemento);
if (colaVacia( ))
{
frente = a;
}
else
{
fin.siguiente = a;
}
fin = a;
}
// quitar: sale el elemento frente
public Object quitar( )throws Exception
{
Object aux;
if (!colaVacia( ))
{
aux = frente.elemento;
frente = frente.siguiente;
}
else
throw new Exception("Eliminar de una cola vaca");
return aux;
}
// libera todos los nodos de la cola
public void borrarCola( )
{
Nodo T;
for (; frente != null;)
{
T = frente;
frente = frente.siguiente; t.siguiente = null;
}
System.gc( );
}
// acceso al primero de la cola
public Object frenteCola( ) throws Exception
{
if (colaVacia( ))
throw new Exception("Error: cola vaca");
return (frente.elemento);
}
// verificacin del estado de la cola
public boolean colaVacia( )
{
return (frente == null);
}
}
1135 Resumen
Resumen
Una lista enlazada es una estructura de datos dinmica cuyos componentes estn ordenados l-
gicamente por sus campos de enlace en vez de ordenados fsicamente como estn en un arreglo.
El final de la lista se seala mediante una constante o referencia especial llamada null.
Una lista simplemente enlazada contiene slo un enlace a un sucesor nico, a menos que sea el
ltimo, en cuyo caso no se enlaza con ningn otro nodo.
Cuando se inserta un elemento en una lista enlazada, se deben considerar cuatro casos: aadir a
una lista vaca, aadir al principio de la lista, aadir en el interior y aadir al final de la lista.
Para borrar un elemento, primero hay que buscar el nodo que lo contiene y considerar dos casos:
borrar el primer nodo y borrar cualquier otro nodo de la lista.
El recorrido de una lista enlazada significa pasar por cada nodo (visitar) y procesarlo. El proceso
de cada nodo puede consistir en escribir su contenido, modificar el campo dato, etctera.
Una lista doblemente enlazada es aquella en la que cada nodo tiene una referencia a su sucesor
y otra a su predecesor. Las listas doblemente enlazadas se pueden recorrer en ambos sentidos.
Las operaciones bsicas son insercin, borrado y recorrer la lista; similares a las listas simples.
Una lista enlazada circularmente por propia naturaleza no tiene primero ni ltimo nodo. Las
listas circulares pueden ser de enlace simple o doble.
Una pila es una estructura de datos tipo LIFO (last-in/first-out, ltimo en entrar/primero en sa-
lir) en la que los datos (todos del mismo tipo) se aaden y eliminan por el mismo extremo, deno-
minado cima de la pila.
Se definen las siguientes operaciones bsicas sobre pilas: crear, insertar, cima, quitar,
pilaVacia, pilaLlena y limpiarPila.
crear inicializa la pila como pila vaca. Esta operacin la implementa el constructor.
insertar aade un elemento en la cima de la pila. Debe haber espacio en la pila.
cima devuelve el elemento que est en la cima, sin extraerlo.
quitar extrae de la pila el elemento cima de la pila.
pilaVacia determina si el estado de la pila es vaca; en su caso devuelve el valor lgico true.
pilaLlena determina si existe espacio en la pila para aadir un nuevo elemento. De no haber es-
pacio devuelve true. Esta operacin se aplica en la representacin de la pila mediante arreglo.
limpiarPila , el espacio asignado a la pila se libera, queda disponible.
El TAD Pila se puede implementar con arreglos y con listas enlazadas.
La realizacin de una pila con listas enlazadas ajusta el tamao de la estructura al nmero de ele-
mentos de la pila, es una estructura dinmica.
Una cola es una lista lineal en la que los datos se insertan por un extremo ( final) y se extraen por
el otro ( frente). Es una estructura FIFO (first-in/first-out, primero en entrar/primero en salir).
Las operaciones bsicas que se aplican sobre colas son: crearCola, colaVacia, colaLlena,
insertar, frente, retirar.
crearCola inicializa a una cola sin elementos. Es la primera operacin a realizar con una cola.
colaVacia determina si una cola tiene o no elementos. Devuelve true si no tiene elementos.
colaLlena determina si no se pueden almacenar ms elementos en una cola. Se aplica esta ope-
racin cuando se utiliza un arreglo para guardar los elementos de la cola.
insertar aade un nuevo elemento a la cola, siempre por el extremo final.
frente devuelve el elemento que est en el extremo frente de la cola, sin extraerlo.
retirar extrae el elemento frente de la cola.
El TAD cola se puede implementar con arreglos y con listas enlazadas. La implementacin con
un arreglo lineal es muy ineficiente; se ha de considerar el arreglo como una estructura circular
y aplicar la teora de los restos para avanzar el frente y el final de la cola.
La realizacin de una cola con listas enlazadas permite que el tamao de la estructura se ajuste
al nmero de elementos. La cola puede crecer indefinidamente, con el nico tope de la memoria
libre.
1136 Captulo 36 Listas, pilas y colas en Java
Numerosos modelos de sistemas del mundo real son de tipo cola: cola de impresin en un servi-
dor de impresoras, programas de simulacin, colas de prioridades en organizacin de viajes. Una
cola es la estructura tpica que se suele utilizar como almacenamiento de datos, cuando se envan
datos desde un componente rpido de una computadora a un componente lento (por ejemplo, una
impresora).
Ejercicios
36.1. Escribir un mtodo, en la clase Lista, que devuelva cierto si la lista est vaca.
36.2. Aadir a la clase ListaDoble un mtodo que devuelva el nmero de nodos de una lista
doble.
36.3. A la clase que representa una cola implementada con un arreglo circular y dos variables fren-
te y fin, se le aade una variable ms que guarda el nmero de elementos de la cola. Escribir
de nuevo los mtodos de manejo de colas considerando este campo contador.
36.4. A la clase Lista aadir el mtodo eliminaraPosicion( ) que elimine el nodo que ocupa
la posicin i, siendo el nodo cabecera el que ocupa la posicin 0.
36.5. Cul es la salida de este segmento de cdigo, teniendo en cuenta que el tipo de dato de la pila
es int:
Pila p = new Pila( );
int x = 4, y;
p.insertar(x);
System.out.println("\n " + p.cimaPila( ));
y = p.quitar( );
p.insertar(32);
p.insertar(p.quitar( ));
do {
System.out.println("\n " + p.quitar( ));
}while (!p.pilaVacia( ));
36.6. Se tiene una lista simplemente enlazada de nmeros reales. Escribir un mtodo para obtener
una lista doble ordenada respecto al campo dato, con los valores reales de la lista simple.
36.7. Se tiene una lista enlazada a la que se accede por el primer nodo. Escribir un mtodo que im-
prima los nodos de la lista en orden inverso, desde el ltimo nodo al primero; como estructura
auxiliar utilizar una pila y sus operaciones.
36.8. Supngase que se tienen ya codificados los mtodos que implementan las operaciones del
TAD Cola. Escribir un mtodo para crear una copia de una cola determinada. Las operaciones
que se han de utilizar sern nicamente las del TAD cola.
36.9. Dada una lista circular de nmeros enteros, escribir un mtodo que devuelva el mayor entero
de la lista.
36.10. Se tiene una lista de simple enlace, el campo dato es un objeto alumno con las variables: nom-
bre, edad, sexo. Escribir un mtodo para transformar la lista de tal forma que si el primer nodo
es de un alumno de sexo masculino, el siguiente sea de sexo femenino, as alternativamente,
siempre que sea posible, masculino y femenino.
36.11. Una lista circular de cadenas est ordenada alfabticamente. El pointer de acceso a la lista tie-
ne la direccin del nodo alfabticamente mayor. Escribir un mtodo para aadir una nueva pa-
labra, en el orden que le corresponda, a la lista.
1137 Problemas
36.12. Dada la lista del ejercicio 36.11 escribir un mtodo que elimine una palabra dada.
Problemas
36.1. Escribir un programa que realice las siguientes tareas:
Crear una lista enlazada de nmeros enteros positivos al azar, la insercin se realiza por el
ltimo nodo.
Recorrer la lista para mostrar los elementos por pantalla.
Eliminar todos los nodos que superen un valor dado.
36.2. Se tiene un archivo de texto de palabras separadas por un blanco o el carcter de fin de lnea.
Escribir un programa para formar una lista enlazada con las palabras del archivo. Una vez for-
mada la lista se pueden aadir nuevas palabras o borrar alguna de ellas. Al finalizar el
programa escribir las palabras de la lista en el archivo.
36.3. Un polinomio se puede representar como una lista enlazada. El primer nodo de la lista repre-
senta el primer trmino del polinomio; el segundo nodo al segundo trmino del polinomio y as
sucesivamente. Cada nodo tiene como campo dato el coeficiente del trmino y el exponente.
Por ejemplo, el polinomio 3x
4
4x
2
+ 11 se representa
3 4 11 4 2 0
Escribir un programa que permita dar entrada a polinomios en x, representndolos con una
lista enlazada simple. A continuacin, obtener una tabla de valores del polinomio para valores
de x = 0.0, 0.5, 1.0, 1.5, , 5.0.
36.4. Teniendo en cuenta la representacin de un polinomio propuesta en el problema 36.3, hacer
los cambios necesarios para que la lista enlazada sea circular. La referencia de acceso debe te-
ner la direccin del ltimo trmino del polinomio, el cual apuntar al primer trmino.
36.5. Segn la representacin de un polinomio propuesta en el problema 36.4, escribir un programa
para realizar las siguientes operaciones:
Obtener la lista circular suma de dos polinomios.
Obtener el polinomio derivada.
Obtener una lista circular que sea el producto de dos polinomios.
36.6. Se tiene un archivo de texto del cual se quieren determinar las frases que son palndromo, para
lo cual se ha de seguir la siguiente estrategia:
Considerar cada lnea del texto una frase.
Aadir cada carcter de la frase a una pila y a la vez a una cola.
Extraer carcter a carcter, y simultneamente de la pila y de la cola. Su comparacin de-
termina si es palndromo o no.
Escribir un programa que lea cada lnea del archivo y determine si es palndromo.
36.7. Un conjunto es una secuencia de elementos todos del mismo tipo sin duplicidades. Escribir un
programa para representar un conjunto de enteros mediante una lista enlazada. El programa
debe contemplar las operaciones:
Cardinal del conjunto.
Pertenencia de un elemento al conjunto.
Aadir un elemento al conjunto.
Escribir en pantalla los elementos del conjunto.
1138 Captulo 36 Listas, pilas y colas en Java
36.8. Con la representacin propuesta en el problema 36.7, aadir las operaciones bsicas de con-
juntos:
Unin de dos conjuntos.
Interseccin de dos conjuntos.
Diferencia de dos conjuntos.
Inclusin de un conjunto en otro.
36.9. Escribir un mtodo para determinar si una secuencia de caracteres de entrada es de la forma:
X & Y
siendo X una cadena de caracteres y Y la cadena inversa. El carcter & es el separador.
36.10. Utilizar una lista doblemente enlazada para controlar una lista de pasajeros de una lnea area.
El programa principal debe ser controlado por mens y debe permitir al usuario visualizar los
datos de un pasajero determinado, insertar un nodo (siempre por el final), eliminar un pasaje-
ro de la lista. A la lista se accede por dos variables, una referencia al primer nodo y la otra al
ltimo nodo.
36.11. Escribir un programa que haciendo uso de una pila, procese cada uno de los caracteres de una
expresin que viene dada en una lnea. La finalidad es verificar el equilibrio de parntesis, llaves
y corchetes. Por ejemplo, la siguiente expresin tiene un nmero de parntesis equilibrado:
((a+b)*5) 7
A esta otra expresin le falta un corchete: 2*[(a+b)/2.5 + x 7*y
36.12. Un vector disperso es aquel que tiene muchos elementos que son cero. Escribir un programa
que permita representar mediante listas enlazadas un vector disperso. Los nodos de la lista son
los elementos de la lista distintos de cero; en cada nodo se representa el valor del elemento y
el ndice (posicin del vector). El programa ha de realizar las operaciones: sumar dos vectores
de igual dimensin y hallar el producto escalar.

You might also like