You are on page 1of 45

Ing.

AlexisJurez
Ing.EmirGirn
Ing.GiovanniRuano

1 Generalidades:
Muy a menudo necesitamos almacenar cierta cantidad de datos de forma ms o menos
permanente. La memoria del ordenador es volatil, y lo que es peor, escasa y cara. De
modo que cuando tenemos que guardar nuestros datos durante cierto tiempo tenemos que
recurrir a sistemas de almacenamiento ms econmicos, aunque sea a costa de que sean
ms lentos.
Durante la historia de los ordenadores se han usado varios mtodos distintos para el
almacenamiento de datos. Al principio se recurri a cintas de papel perforadas, despus a
tarjetas perforadas. A continuacin se pas al soporte magntico, empezando por grandes
rollos de cintas magnticas abiertas.
Hasta aqu, todos los sistemas de almacenamiento externo eran secuenciales, es decir, no
permitan acceder al punto exacto donde se guardaba la informacin sin antes haber
partido desde el principio y sin haber ledo toda la informacin, hasta el punto donde se
encontraba la que estabamos buscando.
Con las cintas magnticas empez lo que con el tiempo sera el acceso aleatorio a los
datos. Se poda reservar parte de la cinta para guardar cierta informacin sobre la
situacin de los datos, y aadir ciertas marcas que hicieran ms sencillo localizarla.
Pero no fu hasta la aparicin de los discos magnticos cuando sta tcnica lleg a su
sentido ms amplio. En los discos es ms sencillo acceder a cualquier punto de la
superficie en poco tiempo, ya que se accede al punto de lectura y escritura usando dos
coordenadas fsicas. Por una parte la cabeza de lectura/escritura se puede mover en el
sentido del radio del disco, y por otra el disco gira permanentemente, con lo que
cualquier punto del disco pasa por la cabeza en un tiempo relativamente corto. Esto no
pasa con las cintas, donde slo hay una coordenada fsica.
Con la invencin y proliferacin de los discos se desarrollaron los ficheros de acceso
aleatorio, que permiten acceder a cualquier dato almacenado en un fichero en
relativamente poco tiempo.
Actualmente, los discos duros tienen una enorme capacidad y son muy rpidos, aunque
an siguen siendo lentos, en comparacin con las memorias RAM. El caso de los CD es
algo intermedio. En realidad son secuenciales en cuanto al modo de guardar los datos,
cada disco slo tiene una pista de datos grabada en espiral. Sin embargo, este sistema,
combinado con algo de memoria RAM, proporciona un acceso muy prximo al de los
discos duros.
En cuanto al tipo de acceso, en C y C++ podemos clasificar los archivos segn varias
categoras:

1. Dependiendo de la direccin del flujo de datos:


De entrada: los datos se leen por el programa desde el archivo.
De salida: los datos se escriben por el programa hacia el archivo.
De entrada/salida: los datos pueden se escritos o ledos.
2. Dependiendo del tipo de valores permitidos a cada byte:
De texto: slo estn permitidos ciertos rangos de valores para cada byte.
Algunos bytes tienen un significado especial, por ejemplo, el valor
hexadecimal 0x1A marca el fin de fichero. Si abrimos un archivo en modo
texto, no ser posible leer ms all de un byte con ese valor, aunque el
fichero sea ms largo.
Binarios: estn permitidos todos lo valores para cada byte. En estos
archivos el final del fichero se detecta de otro modo, dependiendo del
soporte y del sistema operativo. La mayora de las veces se hace
guardando la longitud del fichero. Cuando queramos almacenar valores
enteros, o en coma flotante, o imgenes, etc, deberemos usar este tipo de
archivos.
3. Segn el tipo de acceso:
Archivos secuenciales: imitan el modo de acceso de los antiguos ficheros
secuenciales almacenados en cintas magnticas y
Archivos de acceso aleatorio: permiten acceder a cualquier punto de ellos
para realizar lecturas y/o escrituras.
4. Segn la longitud de registro:
Longitud variable: en realidad, en este tipo de archivos no tiene sentido
hablar de longitud de registro, podemos considerar cada byte como un
registro. Tambin puede suceder que nuestra aplicacin conozca el tipo y
longitud de cada dato almacenado en el archivo, y lea o escriba los bytes
necesarios en cada ocasin. Otro caso es cuando se usa una marca para el
final de registro, por ejemplo, en ficheros de texto se usa el carcter de
retorno de lnea para eso. En estos casos cada registro es de longitud
diferente.
Longitud constante: en estos archivos los datos se almacenan en forma de
registro de tamao contante. En C usaremos estructuras para definir los
registros. C dispone de funciones de librera adecuadas para manejar este
tipo de ficheros.
Mixtos: en ocasiones pueden crearse archivos que combinen los dos tipos
de registros, por ejemplo, dBASE usa registros de longitud constante, pero
aade un registro especial de cabecera al principio para definir, entre otras
cosas, el tamao y el tipo de los registros.
Es posible crear archivos combinando cada una de estas categoras, por ejemplo:
archivos secuenciales de texto de longitud de registro variable, que son los tpicos
archivos de texto. Archivos de acceso aleatorio binarios de longitud de registro
constante, normalmente usados en bases de datos. Y tambin cualquier combinacin
menos corriente, como archivos secuenciales binarios de longitud de registro constante,
etc.

En cuanto a cmo se definen estas propiedades, hay dos casos. Si son binarios o de texto
o de entrada, salida o entrada/salida, se define al abrir el fichero, mediante la funcin
fopen en C o mediante el mtodo open de fstream en C++.
La funcin open usa dos parmetros. El primero es el nombre del fichero que contiene el
archivo. El segundo es em modo que es una cadena que indica el modo en que se abrir
el archivo: lectura o escritura, y el tipo de datos que contiene: de texto o binarios.
En C, los ficheros admiten seis modos en cuanto a la direccin del flujo de datos:

r: slo lectura. El fichero debe existir.


w: se abre para escritura, se crea un fichero nuevo o se sobrescribe si ya existe.
a: aadir, se abre para escritura, el cursor se situa al final del fichero. Si el fichero
no existe, se crea.
r+: lectura y escritura. El fichero debe existir.
w+: lectura y escritura, se crea un fichero nuevo o se sobrescribe si ya existe.
a+: aadir, lectura y escritura, el cursor se situa al final del fichero. Si el fichero
no existe, se crea.

En cuanto a los valores permitidos para los bytes, se puede aadir otro carcter a la
cadena de modo:

t: modo texto. Normalmente es el modo por defecto. Se suele omitir.


b: modo binario.

En ciertos sistemas operativos no existe esta distincin, y todos los ficheros son binarios.
En C++ es algo diferente, el constructor de las clases ifstream, ofstream y fstream admite
los parmetros para abrir el fichero directamente, y tambin disponemos del mtodo
open, para poder crear el stream sin asociarlo con un fichero concreto y hacer esa
asociacin ms tarde.

2 Tipos, funciones y clases usados


frecuentemente con ficheros
Funciones y tipos C estndar:
Tipo FILE:
C define la estructura de datos FILE en el fichero de cabecesa "stdio.h" para el manejo de ficheros. Nosotros
siempre usaremos punteros a estas estructuras.
La definicin de sta estructura depende del compilador, pero en general mantienen un campo con la posicin
actual de lectura/escritura, un buffer para mejorar las prestaciones de acceso al fichero y algunos campos para uso
interno.

Funcin fopen:
Sintaxis:
FILE *fopen(char *nombre, char *modo);

sta funcin sirve para abrir y crear ficheros en disco. El valor de retorno es un puntero a una estructura FILE. Los
parmetros de entrada son:
1. nombre: una cadena que contiene un nombre de fichero vlido, esto depende del sistema operativo que
estemos usando. El nombre puede incluir el camino completo.
2. modo: especifica en tipo de fichero que se abrir o se crear y el tipo de datos que puede contener, de texto
o binarios:
r: slo lectura. El fichero debe existir.
w: se abre para escritura, se crea un fichero nuevo o se sobreescribe si ya existe.
a: aadir, se abre para escritura, el cursor se situa al final del fichero. Si el fichero no existe, se crea.
r+: lectura y escritura. El fichero debe existir.
w+: lectura y escritura, se crea un fichero nuevo o se sobreescribe si ya existe.
a+: aadir, lectura y escritura, el cursor se situa al final del fichero. Si el fichero no existe, se crea.
t: tipo texto, si no se especifica "t" ni "b", se asume por defecto que es "t"
b: tipo binario.

Funcin fclose:
Sintaxis:
int fclose(FILE *fichero);

Es importante cerrar los ficheros abiertos antes de abandonar la aplicacin. Esta funcin sirve para eso. Cerrar un
fichero almacena los datos que an estn en el buffer de memoria, y actualiza algunos datos de la cabecera del
fichero que mantiene el sistema operativo. Adems permite que otros programas puedan abrir el fichero para su
uso. Muy a menudo, los ficheros no pueden ser compartidos por varios programas.
Un valor de retorno cero indica que el fichero ha sido correctamente cerrado, si ha habido algn error, el valor de
retorno es la constante EOF. El parmetro es un puntero a la estructura FILE del fichero que queremos cerrar.

Funcin fgetc:
Sintaxis:
int fgetc(FILE *fichero);

Esta funcin lee un carcter desde un fichero.


El valor de retorno es el carcter ledo como un unsigned char convertido a int. Si no hay ningn carcter
disponible, el valor de retorno es EOF. El parmetro es un puntero a una estructura FILE del fichero del que se
har la lectura.

Funcin fputc:
Sintaxis:
int fputc(int caracter, FILE *fichero);

Esta funcin escribe un carcter a un fichero.


El valor de retorno es el carcter escrito, si la operacin fue completada con xito, en caso contrario ser EOF.
Los parmetros de entrada son el carcter a escribir, convertido a int y un puntero a una estructura FILE del
fichero en el que se har la escritura.

Funcin feof:
Sintaxis:
int feof(FILE *fichero);

Esta funcin sirve para comprobar si se ha alcanzado el final del fichero. Muy frecuentemente deberemos trabajar
con todos los valores almacenados en un archivo de forma secuencial, la forma que suelen tener los bucles para
leer todos los datos de un archivo es permanecer leyendo mientras no se detecte el fin de fichero. Esta funcin
suele usarse como prueba para verificar si se ha alcanzado o no ese punto.
El valor de retorno es distinto de cero slo si no se ha alcanzado el fin de fichero. El parmetro es un puntero a la
estructura FILE del fichero que queremos verificar.

Funcin rewind:
Sintaxis:
void rewind(FILE *fichero)

Es una funcin heredada de los tiempos de las cintas magnticas. Literalmente significa "rebobinar", y hace
referencia a que para volver al principio de un archivo almacenado en cinta, haba que rebobinarla. Eso es lo que
hace sta funcin, sita el cursor de lectura/escritura al principio del archivo.

El parmetro es un puntero a la estructura FILE del fichero que queremos rebobinar.


Ejemplos:
// ejemplo1.c: Muestra un fichero dos veces.
#include <stdio.h>
int main()
{
FILE *fichero;
fichero = fopen("ejemplo1.c", "r");
while(!feof(fichero)) fputc(fgetc(fichero), stdout);
rewind(fichero);
while(!feof(fichero)) fputc(fgetc(fichero), stdout);
fclose(fichero);
getchar();
return 0;
}

Funcin fgets:
Sintaxis:
char *fgets(char *cadena, int n, FILE *fichero);

Esta funcin est diseada para leer cadenas de caracteres. Leer hasta n-1 caracteres o hasta que lea un retorno de
lnea. En este ltimo caso, el carcter de retorno de lnea tambin es ledo.
El parmetro n nos permite limitar la lectura para evitar derbordar el espacio disponible en la cadena.
El valor de retorno es un puntero a la cadena leda, si se ley con xito, y es NULL si se detecta el final del
fichero o si hay un error. Los parmetros son: la cadena a leer, el nmero de caracteres mximo a leer y un
puntero a una estructura FILE del fichero del que se leer.

Funcin fputs:
Sintaxis:
int fputs(const char *cadena, FILE *stream);

La funcin fputs escribe una cadena en un fichero. No se aade el carcter de retorno de lnea ni el carcter nulo
final.
El valor de retorno es un nmero no negativo o EOF en caso de error. Los parmetros de entrada son la cadena a
escribir y un puntero a la estructura FILE del fichero donde se realizar la escritura.

Funcin fread:
Sintaxis:

size_t fread(void *puntero, size_t tamao, size_t nregistros, FILE


*fichero);

Esta funcin est pensada para trabajar con registros de longitud constante. Es capaz de leer desde un fichero uno
o varios registros de la misma longitud y a partir de una direccin de memoria determinada. El usuario es
responsable de asegurarse de que hay espacio suficiente para contener la informacin leda.
El valor de retorno es el nmero de registros ledos, no el nmero de bytes. Los parmetros son: un puntero a la
zona de memoria donde se almacenarn los datos ledos, el tamao de cada registro, el nmero de registros a leer
y un puntero a la estructura FILE del fichero del que se har la lectura.

Funcin fwrite:
Sintaxis:
size_t fwrite(void *puntero, size_t tamao, size_t nregistros, FILE
*fichero);

Esta funcin tambin est pensada para trabajar con registros de longitud constante y forma pareja con fread. Es
capaz de escribir hacia un fichero uno o varios registros de la misma longitud almacenados a partir de una
direccin de memoria determinada.
El valor de retorno es el nmero de registros escritos, no el nmero de bytes. Los parmetros son: un puntero a
la zona de memoria donde se almacenarn los datos ledos, el tamao de cada registro, el nmero de registros a
leer y un puntero a la estructura FILE del fichero del que se har la lectura.
Ejemplo:
// copia.c: Copia de ficheros
// Uso: copia <fichero_origen> <fichero_destino>
#include <stdio.h>
int main(int argc, char **argv) {
FILE *fe, *fs;
unsigned char buffer[2048]; // Buffer de 2 Kbytes
int bytesLeidos;
if(argc != 3) {
printf("Usar: copia <fichero_origen> <fichero_destino>\n");
return 1;
}
// Abrir el fichero de entrada en lectura y binario
fe = fopen(argv[1], "rb");
if(!fe) {
printf("El fichero %s no existe o no puede ser abierto.\n", argv[1]);
return 1;
}
// Crear o sobreescribir el fichero de salida en binario
fs = fopen(argv[2], "wb");
if(!fs) {
printf("El fichero %s no puede ser creado.\n", argv[2]);

fclose(fe);
return 1;
}
// Bucle de copia:
while((bytesLeidos = fread(buffer, 1, 2048, fe)))
fwrite(buffer, 1, bytesLeidos, fs);
// Cerrar ficheros:
fclose(fe);
fclose(fs);
return 0;
}

Funcin fprintf:
Sintaxis:
int fprintf(FILE *fichero, const char *formato, ...);

La funcin fprintf funciona igual que printf en cuanto a parmetros, pero la salida se dirige a un fichero en lugar
de a la pantalla.

Funcin fscanf:
Sintaxis:
int fscanf(FILE *fichero, const char *formato, ...);

La funcin fscanf funciona igual que scanf en cuanto a parmetros, pero la entrada se toma de un fichero en lugar
del teclado.

Funcin fflush:
Sintaxis:
int fflush(FILE *fichero);

Esta funcin fuerza la salida de los datos acumulados en el buffer de salida del fichero. Para mejorar las
prestaciones del manejo de ficheros se utilizan buffers, almacenes temporales de datos en memoria, las
operaciones de salida se hacen a travs del buffer, y slo cuando el buffer se llena se realiza la escritura en el
disco y se vaca el buffer. En ocasiones nos hace falta vaciar ese buffer de un modo manual, para eso sirve sta
funcin.
El valor de retorno es cero si la funcin se ejecut con xito, y EOF si hubo algn error. El parmetro de entrada
es un puntero a la estructura FILE del fichero del que se quiere vaciar el buffer. Si es NULL se har el vaciado de
todos los ficheros abiertos.

Funciones C especficas para ficheros de acceso aleatorio

Funcin fseek:
Sintaxis:
int fseek(FILE *fichero, long int desplazamiento, int origen);

Esta funcin sirve para situar el cursor del fichero para leer o escribir en el lugar deseado.
El valor de retorno es cero si la funcin tuvo xito, y un valor distinto de cero si hubo algn error.
Los parmetros de entrada son: un puntero a una estructura FILE del fichero en el que queremos cambiar el cursor
de lectura/escritura, el valor del desplazamiento y el punto de origen desde el que se calcular el desplazamiento.
El parmetro origen puede tener tres posibles valores:
1. SEEK_SET el desplazamiento se cuenta desde el principio del fichero. El primer byte del fichero tiene un
desplazamiento cero.
2. SEEK_CUR el desplazamiento se cuenta desde la posicin actual del cursor.
3. SEEK_END el desplazamiento se cuenta desde el final del fichero.

Funcin ftell:
Sintaxis:
long int ftell(FILE *fichero);

La funcin ftell sirve para averiguar la posicin actual del cursor de lectura/excritura de un fichero.
El valor de retorno ser esa posicin, o -1 si hay algn error.
El parmetro de entrada es un puntero a una estructura FILE del fichero del que queremos leer la posicin del
cursor de lectura/escritura.

Clases para manejar ficheros en C++


Existen tres clases para manejar ficheros: ifstream, ofstream y fstream. La primera est orientada a ficheros de
entrada, la segunda a ficheros de salida, y la tercera puede manejar cualquiera de los dos tipos o ficheros de
entrada y salida.

Clase ifstream:
El constructor est sobrecargado para poder crear streams de varias maneras:
ifstream();
ifstream(const char *name, int mode = ios::in,
int = filebuf::openprot);

El primero slo crea un stream de entrada pero no lo asocia a ningn fichero. El segundo lo crea, lo asocia al
fichero con el nombre "name" y lo abre.

Los parmetros son: el nombre del fichero, el modo, que para ifstream es ios::in por defecto. El tercer parmetro
se refiere al buffer, y no nos preocupa de momento.

Clase ofstream:
Lo mismo pasa con ofstream, salvo que los valores por defecto de los parmetros son diferentes:
ofstream();
ofstream(const char *name, int mode = ios::out,
int = filebuf::openprot);

Clase fstream:
fstream();
fstream(const char *name, int mode = ios::in,
int = filebuf::openprot);

Mtodo open:
Todas estas clases disponen adems del mtodo "open", para abrir el fichero a lo largo de la ejecucin del
programa.
void open(const char *name, int mode,
int prot=filebuf::openprot);

"name" es el nombre del fichero, mode es el modo en que se abrir, puede ser uno o una combinacin del tipo
enumerado open_mode, de la clase "ios":
enum open_mode { in, out, ate, app, trunc, nocreate,
noreplace, binary };

Cada uno de los valores se pueden combinar usando el operador de bits OR (|), y significan lo siguiente:

in: modo de entrada.


out: modo de salida.
ate: abre el fichero y sita el cursor al final.
app: modo append, parecido al anterior, pero las operaciones de escritura siempre se hacen al final del
fichero.
trunc: si se aplica a ficheros de salida, se crear el fichero si no existe previamente, o se truncar con un
tamao de 0 bytes, si existe.
nocreate: impide crear un fichero si no existe, en ese caso, la funcin falla.
noreplace: lo ignoro.
binary: abre el fichero en modo binario.

Los tres ltimos modos probablemente no son estndar, y es posible que no existan en muchos compiladores.

Mtodo close:

void close();

Sencillamente, cierra el fichero asociado a un stream.

Operador >>:
Igual que sucede con el stream estndar cout, el operador de flujo de salida >> se puede usar con streams de salida
cuando trabajemos con texto.

Operador <<:
Del mismo modo, al igual que sucede con el stream estndar cin, el operador de flujo de entrada << se puede usar
con streams de entrada cuando trabajemos con texto.

Mtodo de salida put:


ostream& put(char ch);

Sirve para cualquier stream de salida, e inserta un carcter en el stream.

Mtodo de entrada get:


int get();
istream& get(char*, int len, char = '\n');
istream& get(char&);
istream& get(streambuf&, char = '\n');

La primera forma no se recomienda y se considera obsoleta, lee un carcter desde el stream de entrada.
La segunda lee caracteres y los almacena en el buffer indicado en el primer parmetro hasta que se leen "len"
caracteres o hasta que se encuentra el carcter indicado en el tercer parmetro, que por defecto es el retorno de
lnea.
La tercera forma extrae un nico carcter en la referencia a char proporcionada.
La cuarta no nos interesa de momento.

Mtodo de entrada getline:


istream& getline(char*, int, char = '\n');

Extrae caracteres hasta que se encuentra el delimitador y los coloca en el buffer, elimina el delimitador del stream
de entrada y no lo aade al buffer.
Mtodo eof:
int eof();

Verifica si se ha alcanzado el final del fichero, devuelve un valor nulo si no es as.

Mtodo clear:
void clear(iostate state=0);

Cada vez que se produzca una condicin de error en un stream es necesario eliminarla, ya que en caso contrario
ninguna operacin que se realice sobre l tendr xisto. Por ejemplo, si llegamos hasta el final de fichero, el
stream quedar en estado "eof" hasta que se elimine explcitamente ese estado. Eso se hace mediante el mtodo
"clear", sin parmetros dejar el estado en 0, es decir, sin errores.
Los estados posibles se definen en un enumerado:
enum io_state { goodbit, eofbit, failbit, badbit };

goodbit: indica que el estado es correcto.


eofbit: indica que se ha detectado fin de fichero.
failbit: indica que una operacin sobre el stream ha fallado.
badbit: se activa si falla una operacin de escritura de buffers.

Mtodo bad:
int bad();

Devuelve el estado del bit "badbit".

Mtodo fail:
int fail();

Devuelve el estado del bit "failbit".

Mtodo good:
int good();

Devuelve el estado del bit "goodbit".


Ejemplo:
Veamos el ejemplo anterior de mostrar dos veces un fichero, pero esta vez escrito para C++ usando streams:
// ejemplo1.cpp: Muestra un fichero dos veces.
#include <iostream>
#include <fstream>
using namespace std;

int main() {
ifstream fichero("ejemplo1.cpp");
char c;
while(fichero.get(c)) cout.put(c);
fichero.clear(); // (1)
fichero.seekg(0);
while(fichero.get(c)) cout.put(c);
fichero.close();
cin.get();
return 0;
}

Como vemos en (1), es necesario eliminar el bit de eof, que se ha activado al leer hasta el final del fichero, cuando
el ltimo intento de llamar a "get" ha fallado, porque se ha terminado el fichero.

Mtodo is_open:
int is_open();

Devuelve un valor no nulo si el fichero est abierto.

Mtodo flush:
ostream& flush();

Realiza las operaciones de escritura pendientes que an se han realizado slo en el buffer.

Mtodos relacionados con acceso aleatorio.


Disponemos de otro tipo enumerado en ios para indicar movimientos relativos dentro de un stream de acceso
aleatorio:
enum seek_dir { beg, cur, end};

beg: relativo al principio del fichero.


cur: relativo a la posicin actual del cursor dentro del fichero.
end: relativo al final del fichero.

Mtodo seekg:
Cambia la posicin del cursor en streams de entrada.
istream& seekg(streampos pos);
istream& seekg(streamoff offset, seek_dir dir);

La primera forma es para cambiar la posicin de modo absoluto. La segunda para cambios relativos, en la que se

indica el salto en el primer parmetro y el punto de partida en el segundo, que puede ser cualquiera de los
indicados anteriormente: ios::beg, ios::cur o ios::end.

Mtodo seekp:
Cambia la posicin del cursor en streams de salida.
ostream& seekp(streampos pos);
ostream& seekp(streamoff offset, seek_dir);

Lo mismo que seekg, pero aplicado a estream de salida.

Mtodo tellg:
streampos tellg();

Devuelve la posicin actual del cursor dentro de un stream de entrada.

Mtodo tellp:
streampos tellp();

Devuelve la posicin actual del cursor dentro de un stream de salida.

Mtodo read:
istream& read(char*, int);

Lee el nmero de caracteres indicado en el segundo parmetro dendro del buffer suministrado por el primero.

Mtodo gcount:
int gcount();

Devuelve el nmero de caracteres sin formato de la ltima lectura. Las lecturas sin formato son las realizadas
mediante las funciones get, getline y read.

Mtodo write:
ostream& write(const char*, int);

Escribe el nmero de caracteres indicado en el segundo parmetro desde el buffer suministrado por el primero.
Ejemplo:

De nuevo haremos el ejemplo de copiar ficheros, pero esta vez usando streams.
// copia.cpp: Copia de ficheros
// Uso: copia <fichero_origen> <fichero_destino>
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char **argv) {
ifstream entrada;
ofstream salida;
char buffer[2048]; // Buffer de 2 Kbytes
int bytesLeidos;
if(argc != 3) {
printf("Usar: copia <fichero_origen> <fichero_destino>\n");
return 1;
}
// Abrir el fichero de entrada en lectura y binario
entrada.open(argv[1]);
if(!entrada.good()) {
printf("El fichero %s no existe o no puede ser abierto.\n", argv[1]);
return 1;
}
// Crear o sobreescribir el fichero de salida en binario
salida.open(argv[2]);
if(!salida.good()) {
printf("El fichero %s no puede ser creado.\n", argv[2]);
entrada.close();
return 1;
}
// Bucle de copia:
do {
entrada.read(buffer, 2048);
bytesLeidos = entrada.gcount();
salida.write(buffer, bytesLeidos);
} while(bytesLeidos > 0);
// Cerrar ficheros:
entrada.close();
salida.close();
return 0;
}

3 Archivos secuenciales
En estos archivos, la informacin slo puede leerse y escribirse empezando desde el
principio del archivo.
Los archivos secuenciales tienen algunas caractersticas que hay que tener en cuenta:
1. La escritura de nuevos datos siempre se hace al final del archivo.
2. Para leer una zona concreta del archivo hay que avanzar siempre, si la zona est
antes de la zona actual de lectura, ser necesario "rebobinar" el archivo.
3. Los ficheros slo se pueden abrir para lectura o para escritura, nunca de los dos
modos a la vez.
Esto es en teora, por supuesto, en realidad C no distingue si los archivos que usamos son
secuenciales o no, es el tratamiento que hagamos de ellos lo que los clasifica como de
uno u otro tipo.
Pero hay archivos que se comportan siempre como secuenciales, por ejemplo los
ficheros de entrada y salida estndar: stdin, stdout, stderr y stdaux.
Tomemos el caso de stdin, que suele ser el teclado. Nuestro programa slo podr abrir
ese fichero como de lectura, y slo podr leer los caracteres a medida que estn
disponibles, y en el mismo orden en que fueron tecleados.
Lo mismo se aplica para stdout y stderr, que es la pantalla, en estos casos slo se pueden
usar para escritura, y el orden en que se muestra la informacin es el mismo en que se
enva.
Un caso especial es stdaux, que suele ser el puerto serie. Tambin es un archivo
secuencial, con respecto al modo en que se leen y escriben los datos. Sin embargo se un
fichero de entrada y salida.
Trabajar con archivos secuenciales tiene algunos inconvenientes. Por ejemplo, imagina
que tienes un archivo de este tipo en una cinta magntica. Por las caractersticas fsicas
de este soporte, es eviente que slo podemos tener un fichero abierto en cada unidad de
cinta. Cada fichero puede ser ledo, y tambin sobrescrito, pero en general, los archivos
que haya a continuacin del que escribimos se perdern, o bien sern sobreescritos al
crecer el archivo, o quedar un espacio vaco entre el final del archivo y el principio del
siguiente.
Lo normal cuando se quera actualizar el contenido de un archivo de cinta aadiendo o
modificando datos, era abrir el archivo en modo lectura en una unidad de cinta, y crear
un nuevo fichero de escritura en una unidad de cinta distinta. Los datos ledos de una
cinta se editan o modifican, y se copian en la otra secuencialmente.

Cuando trabajemos con archivos secuenciales en disco haremos lo mismo, pero en ese
caso no necesitamos dos unidades de disco, ya que en los discos es posible abrir varios
archivos simultaneamente.
En cuanto a las ventajas, los archivos secuenciales son ms sencillos de manejar, ya que
requieren menos funciones, adems son ms rpidos, ya que no permiten moverse a lo
largo del archivo, el punto de lectura y escritura est siempre determinado.
En ocasiones pueden ser tiles, por ejemplo, cuando slo se quiere almacenar cierta
informacin a medida que se recibe, y no interesa analizarla en el momento.
Posteriormente, otro programa puede leer esa informacin desde el principio y
analizarla. Este es el caso de archivos "log" o "diarios" por ejemplo, los servidores de las
pginas WEB pueden generar una lnea de texto cada vez que alguien accede al una de
las pginas y las guardan en un fichero secuencial.

4 Archivos de acceso aleatorio


Los archivos de acceso aleatorio son ms verstiles, permiten acceder a cualquier parte del fichero en cualquier
momento, como si fueran arrays en memoria. Las operaciones de lectura y/o escritura pueden hacerse en
cualquier punto del archivo.
En general se suelen establecer ciertas normas para la creacin, aunque no todas son obligatorias:
1. Abrir el archivo en un modo que te permita leer y escribir. Esto no es imprescindible, es posible usar
archivos de acceso aleatorio slo de lectura o de escritura.
2. Abrirlo en modo binario, ya que algunos o todos los campos de la estructura pueden no ser caracteres.
3. Usar funciones como fread y fwrite, que permiten leer y escribir registros de longitud constante desde y
hacia un fichero.
4. Usar la funcin fseek para situar el puntero de lectura/escritura en el lugar apropiado de tu archivo.
Por ejemplo, supongamos que nuestros registros tienen la siguiente estructura:
struct stRegistro {
char Nombre[34];
int dato;
int matriz[23];
} reg;

Teniendo en cuenta que los registros empiezan a contarse desde el cero, para hacer una lectura del registro
nmero 6 usaremos:
fseek(fichero, 5*sizeof(stRegistro), SEEK_SET);
fread(&reg, sizeof(stRegistro), 1, fichero);

Anlogamente, para hacer una operacin de escritura, usaremos:


fseek(fichero, 5*sizeof(stRegistro), SEEK_SET);
fwrite(&reg, sizeof(stRegistro), 1, fichero);

Muy importante: despus de cada operacin de lectura o escritura, el cursor del fichero se actualiza
automticamente a la siguiente posicin, as que es buena idea hacer siempre un fseek antes de un fread o un
fwrite.
En el caso de streams, la forma de trabajar es anloga:
fichero.seekg(5*sizeof(stRegistro), ios::beg);
fichero.read(&reg, sizeof(stRegistro));

Y para hacer una operacin de escritura, usaremos:


fichero.seekp(5*sizeof(stRegistro), ios::beg);
fichero.write(&reg, sizeof(stRegistro));

Calcular la longitud de un fichero


Para calcular el tamao de un fichero, ya sea en bytes o en registros se suele usar el siguiente procedimiento:
long nRegistros;
long nBytes;
fseek(fichero, 0, SEEK_END); // Colocar el cursor al final del fichero
nBytes = ftell(fichero); // Tamao en bytes
nRegistros = ftell(fich)/sizeof(stRegistro); // Tamao en registros

En el caso de streams:
long nRegistros;
long nBytes;
fichero.seekg(0, ios::end); // Colocar el cursor al final del fichero
nBytes = fichero.tellg(); // Tamao en bytes
nRegistros = fichero.tellg()/sizeof(stRegistro); // Tamao en registros

Borrar registros
Borrar registros puede ser complicado, ya que no hay ninguna funcin de librera estndar que lo haga.
Es su lugar se suele usar uno de estos dos mtodos:
1. Marcar el registro como borrado o no vlido, para ello hay que aadir un campo extra en la estructura del
registro:
struct stRegistro {
char Valido; // Campo que indica si el registro es vlido
char Nombre[34];
int dato;
int matriz[23];
};

Si el campo Valido tiene un valor prefijado, por ejemplo 'S' o ' ', el registro es vlido. Si tiene un valor
prefijado, por ejemplo 'N' o '*', el registro ser invlido o se considerar borrado.
De este modo, para borrar un registro slo tienes que cambiar el valor de ese campo.
Pero hay que tener en cuenta que ser el programa el encargado de tratar los registros del modo adecuado
dependiendo del valor del campo Valido, el hecho de marcar un registro no lo borra fsicamente.
Si se quiere elaborar ms, se puede mantener un fichero auxiliar con la lista de los registros borrados.
Esto tiene un doble propsito:
Que se pueda disear una funcin para sustituir a fseek() de modo que se tengan en cuenta los
registros marcados.
Que al insertar nuevos registros, se puedan sobrescribir los anteriormente marcados como
borrados, si existe alguno.
2. Hacer una copia del fichero en otro fichero, pero sin copiar el registro que se quiere borrar. Este sistema

es ms tedioso y lento, y requiere cerrar el fichero y borrarlo o renombrarlo, antes de poder usar de nuevo
la versin con el registro eliminado.
Lo normal es hacer una combinacin de ambos, durante la ejecucin normal del programa se borran registros
con el mtodo de marcarlos, y cuando se cierra la aplicacin, o se detecta que el porcentaje de registros borrados
es alto o el usuario as lo decide, se "empaqueta" el fichero usando el segundo mtodo.

Ejemplo:
A continuacin se incluye un ejemplo de un programa que trabaja con registros de acceso aleatorio, es un poco
largo, pero bastante completo:
// alea.c: Ejemplo de ficheros de acceso aleatorio.
#include <stdio.h>
#include <stdlib.h>
struct stRegistro {
char valido; // Campo que indica si el registro es vlido S->Vlido, N>Invlido
char nombre[34];
int dato[4];
};
int Menu();
void Leer(stRegistro &reg);
void Mostrar(stRegistro &reg);
void Listar(long n, stRegistro &reg);
long LeeNumero();
void Empaquetar(FILE *fa);
int main()
{
stRegistro reg;
FILE *fa;
int opcion;
long numero;
fa = fopen("alea.dat", "r+b");
escribir
if(!fa) fa = fopen("alea.dat", "w+b");
crea.

// Este modo permite leer y


// si el fichero no existe, lo

do {
opcion = Menu();
switch(opcion) {
case '1': // Aadir registro
Leer(reg);
// Insertar al final:
fseek(fa, 0, SEEK_END);
fwrite(&reg, sizeof(stRegistro), 1, fa);
break;
case '2': // Mostrar registro
system("cls");
printf("Mostrar registro: ");
numero = LeeNumero();
fseek(fa, numero*sizeof(stRegistro), SEEK_SET);

fread(&reg, sizeof(stRegistro), 1, fa);


Mostrar(reg);
break;
case '3': // Eliminar registro
system("cls");
printf("Eliminar registro: ");
numero = LeeNumero();
fseek(fa, numero*sizeof(stRegistro), SEEK_SET);
fread(&reg, sizeof(stRegistro), 1, fa);
reg.valido = 'N';
fseek(fa, numero*sizeof(stRegistro), SEEK_SET);
fwrite(&reg, sizeof(stRegistro), 1, fa);
break;
case '4': // Mostrar todo
rewind(fa);
numero = 0;
system("cls");
printf("Nombre
Datos\n");
while(fread(&reg, sizeof(stRegistro), 1, fa)) Listar(numero++,
reg);
system("PAUSE");
break;
case '5': // Eliminar marcados
Empaquetar(fa);
break;
}
} while(opcion != '0');
fclose(fa);
return 0;
}
// Muestra un men con las opciones disponibles y captura una opcin del
usuario
int Menu()
{
char resp[20];
do {
system("cls");
printf("MENU PRINCIPAL\n");
printf("--------------\n\n");
printf("1- Insertar registro\n");
printf("2- Mostrar registro\n");
printf("3- Eliminar registro\n");
printf("4- Mostrar todo\n");
printf("5- Eliminar registros marcados\n");
printf("0- Salir\n");
fgets(resp, 20, stdin);
} while(resp[0] < '0' && resp[0] > '5');
return resp[0];
}
// Permite que el usuario introduzca un registro por pantalla
void Leer(stRegistro &reg)
{
int i;
char numero[6];

system("cls");
printf("Leer registro:\n\n");
reg.valido = 'S';
printf("Nombre: ");
fgets(reg.nombre, 34, stdin);
// la funcin fgets captura el retorno de lnea, hay que eliminarlo:
for(i = strlen(reg.nombre)-1; i && reg.nombre[i] < ' '; i--)
reg.nombre[i] = 0;
for(i = 0; i < 4; i++) {
printf("Dato[%1d]: ", i);
fgets(numero, 6, stdin);
reg.dato[i] = atoi(numero);
}
}
// Muestra un registro en pantalla, si no est marcado como borrado
void Mostrar(stRegistro &reg)
{
int i;
system("cls");
if(reg.valido == 'S') {
printf("Nombre: %s\n", reg.nombre);
for(i = 0; i < 4; i++) printf("Dato[%1d]: %d\n", i, reg.dato[i]);
}
system("PAUSE");
}
// Muestra un registro por pantalla en forma de listado,
// si no est marcado como borrado
void Listar(long n, stRegistro &reg)
{
int i;
if(reg.valido == 'S') {
printf("[%6ld] %-34s", n, reg.nombre);
for(i = 0; i < 4; i++) printf(", %4d", reg.dato[i]);
printf("\n");
}
}
// Lee un nmero suministrado por el usuario
long LeeNumero()
{
char numero[6];
fgets(numero, 6, stdin);
return atoi(numero);
}
// Elimina los registros marcados como borrados
void Empaquetar(FILE *fa)
{
FILE *ftemp;
stRegistro reg;
ftemp = fopen("alea.tmp", "wb");
rewind(fa);

while(fread(&reg, sizeof(stRegistro), 1, fa))


if(reg.valido == 'S') fwrite(&reg, sizeof(stRegistro), 1, ftemp);
fclose(ftemp);
fclose(fa);
remove("alea.bak");
rename("alea.dat", "alea.bak");
rename("alea.tmp", "alea.dat");
fa = fopen("alea.dat", "r+b");
}

Y esto es un ejemplo equivalente en C++:


// alea.cpp: Ejemplo de ficheros de acceso aleatorio.
#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include <cstring>
using namespace std;
// Funciones auxiliares:
int Menu();
long LeeNumero();
// Clase registro.
class Registro {
public:
Registro(char *n=NULL, int d1=0, int d2=0, int d3=0, int d4=0) :
valido('S') {
if(n) strcpy(nombre, n); else strcpy(nombre, "");
dato[0] = d1;
dato[1] = d2;
dato[2] = d3;
dato[3] = d4;
}
void Leer();
void Mostrar();
void Listar(long n);
const bool Valido() { return valido == 'S'; }
const char *Nombre() { return nombre; }
private:
char valido; // Campo que indica si el registro es vlido
// S->Vlido, N->Invlido
char nombre[34];
int dato[4];
};
// Implementaciones de clase Registro:
// Permite que el usuario introduzca un registro por pantalla
void Registro::Leer() {
system("cls");
cout << "Leer registro:" << endl << endl;
valido = 'S';
cout << "Nombre: ";
cin.getline(nombre, 34);

for(int i = 0; i < 4; i++) {


cout << "Dato[" << i << "]: ";
dato[i] = LeeNumero();
}
}
// Muestra un registro en pantalla, si no est marcado como borrado
void Registro::Mostrar()
{
system("cls");
if(Valido()) {
cout << "Nombre: " << nombre << endl;
for(int i = 0; i < 4; i++)
cout << "Dato[" << i << "]: " << dato[i] << endl;
}
cout << "Pulsa una tecla";
cin.get();
}
// Muestra un registro por pantalla en forma de listado,
// si no est marcado como borrado
void Registro::Listar(long n) {
int i;
if(Valido()) {
cout << "[" << setw(6) << n << "] ";
cout << setw(34) << nombre;
for(i = 0; i < 4; i++)
cout << ", " << setw(4) << dato[i];
cout << endl;
}
}
// Clase Datos, almacena y trata los datos.
class Datos :public fstream {
public:
Datos() : fstream("alea.dat", ios::in | ios::out | ios::binary) {
if(!good()) {
open("alea.dat", ios::in | ios::out | ios::trunc | ios::binary);
cout << "fichero creado" << endl;
cin.get();
}
}
~Datos() {
Empaquetar();
}
void Guardar(Registro &reg);
bool Recupera(long n, Registro &reg);
void Borrar(long n);
private:
void Empaquetar();
};
// Implementacin de la clase Datos.
void Datos::Guardar(Registro &reg) {
// Insertar al final:
clear();

seekg(0, ios::end);
write(reinterpret_cast<char *> (&reg), sizeof(Registro));
cout << reg.Nombre() << endl;
}
bool Datos::Recupera(long n, Registro &reg) {
clear();
seekg(n*sizeof(Registro), ios::beg);
read(reinterpret_cast<char *> (&reg), sizeof(Registro));
return gcount() > 0;
}
// Marca el registro como borrado:
void Datos::Borrar(long n) {
char marca;
clear();
marca = 'N';
seekg(n*sizeof(Registro), ios::beg);
write(&marca, 1);
}
// Elimina los registros marcados como borrados
void Datos::Empaquetar() {
ofstream ftemp("alea.tmp", ios::out);
Registro reg;
clear();
seekg(0, ios::beg);
do {
read(reinterpret_cast<char *> (&reg), sizeof(Registro));
cout << reg.Nombre() << endl;
if(gcount() > 0 && reg.Valido())
ftemp.write(reinterpret_cast<char *> (&reg), sizeof(Registro));
} while (gcount() > 0);
ftemp.close();
close();
remove("alea.bak");
rename("alea.dat", "alea.bak");
rename("alea.tmp", "alea.dat");
open("alea.dat", ios::in | ios::out | ios::binary);
}
int main()
{
Registro reg;
Datos datos;
int opcion;
long numero;
do {
opcion = Menu();
switch(opcion) {
case '1': // Aadir registro
reg.Leer();
datos.Guardar(reg);
break;
case '2': // Mostrar registro

system("cls");
cout << "Mostrar registro: ";
numero = LeeNumero();
if(datos.Recupera(numero, reg))
reg.Mostrar();
break;
case '3': // Eliminar registro
system("cls");
cout << "Eliminar registro: ";
numero = LeeNumero();
datos.Borrar(numero);
break;
case '4': // Mostrar todo
numero = 0;
system("cls");
cout << "Nombre
Datos" << endl;
while(datos.Recupera(numero, reg)) reg.Listar(numero++);
cout << "pulsa return";
cin.get();
break;
}
} while(opcion != '0');
return 0;
}
// Muestra un men con las opciones disponibles y captura una opcin del
usuario
int Menu()
{
char resp[20];
do {
system("cls");
cout << "MENU PRINCIPAL" << endl;
cout << "--------------" << endl << endl;
cout << "1- Insertar registro" << endl;
cout << "2- Mostrar registro" << endl;
cout << "3- Eliminar registro" << endl;
cout << "4- Mostrar todo" << endl;
cout << "0- Salir" << endl;
cin.getline(resp, 20);
} while(resp[0] < '0' && resp[0] > '4');
return resp[0];
}
// Lee un nmero suministrado por el usuario
long LeeNumero()
{
char numero[6];
fgets(numero, 6, stdin);
return atoi(numero);
}

5 Ordenar ficheros secuenciales


A veces necesitaremos ordenar el contenido de un fichero secuencial, ya sea de longitud de registro variable o
constante.
Debido a la naturaleza de estos archivos, en general no ser posible usar los mtodos de ordenamiento que
usaramos con tablas en memoria. En muchas ocasiones trabajaremos con archivos muy grandes, de modo que
ser imposible ordenarlos en memoria y despus reconstruirlos en disco.

Algoritmo de mezcla natural


En cuanto a los ficheros secuenciales, el mtodo ms usado es el de mezcla natural. Es vlido para ficheros de
tamao de registro variable.
Es un buen mtodo para ordenar barajas de naipes, por ejemplo.
Cada pasada se compone de dos fases. En la primera se separa el fichero original en dos auxiliares, los elementos
se dirigen a uno u otro fichero separando los tramos de registros que ya estn ordenados. En la segunda fase los
dos ficheros auxiliares se mezclan de nuevo de modo que de cada dos tramos se obtiene siempre uno ordenado. El
proceso se repite hasta que slo obtenemos un tramo.
Por ejemplo, supongamos los siguientes valores en un fichero de acceso secuencial, que ordenaremos de menor a
mayor:
3, 1, 2, 4, 6, 9, 5, 8, 10, 7

Separaremos todos los tramos ordenados de este fichero:


[3], [1, 2, 4, 6, 9], [5, 8, 10], [7]

La primera pasada separar los tramos alternndolos en dos ficheros auxiliares:


aux1: [3],

[5, 8, 10]

aux2: [1, 2, 4, 6, 9],

[7]

Ahora sigue una pasada de mezcla, mezclaremos un tramo de cada fichero auxiliar en un nico tramo:
mezcla: [1, 2, 3, 4, 6, 9], [5, 7, 8, 10]

Ahora repetimos el proceso, separando los tramos en los ficheros auxiliares:


aux1: [1, 2, 3, 4, 6, 9]

aux2: [5, 7, 8, 10]

Y de mezclndolos de nuevo:
mezcla: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

El fichero ya est ordenado, para verificarlo contaremos los tramos obtenidos despus de cada proceso de mezcla,
el fichero estar desordenado si nos encontramos ms de un tramo.

Ejemplo:
// mezcla.c : Ordenamiento de archivos secuenciales
// Ordena ficheros de texto por orden alfabtico de lneas
// Usando el algoritmo de mezcla natural
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void
void
void
bool

Mostrar(FILE *fich);
Mezcla(FILE *fich);
Separar(FILE *fich, FILE **aux);
Mezclar(FILE *fich, FILE **aux);

int main()
{
FILE *fichero;
fichero = fopen("mezcla.txt", "r+");
puts("Fichero desordenado\n");
Mostrar(fichero);
puts("Ordenando fichero\n");
Mezcla(fichero);
puts("Fichero ordenado\n");
Mostrar(fichero);
fclose(fichero);
system("PAUSE");
return 0;
}
// Muestra el contenido del fichero "fich"
void Mostrar(FILE *fich)
{
char linea[128];
rewind(fich);
fgets(linea, 128, fich);
while(!feof(fich)) {
puts(linea);
fgets(linea, 128, fich);
}
}
// Algoritmo de mezcla:
void Mezcla(FILE *fich)
{
bool ordenado;
FILE *aux[2];

// Bucle que se repite hasta que el fichero est ordenado:


do {
// Crea los dos ficheros auxiliares para separar los tramos:
aux[0] = fopen("aux1.txt", "w+");
aux[1] = fopen("aux2.txt", "w+");
rewind(fich);
Separar(fich, aux);
rewind(aux[0]);
rewind(aux[1]);
rewind(fich);
ordenado = Mezclar(fich, aux);
fclose(aux[0]);
fclose(aux[1]);
} while(!ordenado);
// Elimina los ficheros auxiliares:
remove(aux[0]);
remove(aux[1]);
}
// Separa los tramos ordenados alternando entre los ficheros auxiliares:
void Separar(FILE *fich, FILE **aux)
{
char linea[128], anterior[2][128];
int salida = 0;
// Volores iniciales para los ltimos valores
// almacenados en los ficheros auxiliares
strcpy(anterior[0], "");
strcpy(anterior[1], "");
// Captura la primero lnea:
fgets(linea, 128, fich);
while(!feof(fich)) {
// Decide a qu fichero de salida corresponde la lnea leda:
if(salida == 0 && strcmp(linea, anterior[0]) < 0) salida = 1;
else if(salida == 1 && strcmp(linea, anterior[1]) < 0) salida = 0;
// Almacena la lnea actual como la ltima aadida:
strcpy(anterior[salida], linea);
// Aade la lnea al fichero auxiliar:
fputs(linea, aux[salida]);
// Lee la siguiente lnea:
fgets(linea, 128, fich);
}
}
// Mezcla los ficheros auxiliares:
bool Mezclar(FILE *fich, FILE **aux)
{
char ultima[128], linea[2][128], anterior[2][128];
int entrada;
int tramos = 0;
// Lee la primera lnea de cada fichero auxiliar:
fgets(linea[0], 128, aux[0]);
fgets(linea[1], 128, aux[1]);
// Valores iniciales;
strcpy(ultima, "");
strcpy(anterior[0], "");
strcpy(anterior[1], "");

// Bucle, mientras no se acabe ninguno de los ficheros auxiliares (quedan


tramos por mezclar):
while(!feof(aux[0]) && !feof(aux[1])) {
// Selecciona la lnea que se aadir:
if(strcmp(linea[0], linea[1]) <= 0) entrada = 0; else entrada = 1;
// Almacena el valor como el ltimo aadido:
strcpy(anterior[entrada], linea[entrada]);
// Aade la lnea al fichero:
fputs(linea[entrada], fich);
// Lee la siguiente lnea del fichero auxiliar:
fgets(linea[entrada], 128, aux[entrada]);
// Verificar fin de tramo, si es as copiar el resto del otro tramo:
if(strcmp(anterior[entrada], linea[entrada]) >= 0) {
entrada == 0 ? entrada = 1 : entrada = 0;
tramos++;
// Copia lo que queda del tramo actual al fichero de salida:
do {
strcpy(anterior[entrada], linea[entrada]);
fputs(linea[entrada], fich);
fgets(linea[entrada], 128, aux[entrada]);
} while(!feof(aux[entrada]) && strcmp(anterior[entrada],
linea[entrada]) <= 0);
}
}
// Aadir tramos que queden sin mezclar:
if(!feof(aux[0])) tramos++;
while(!feof(aux[0])) {
fputs(linea[0], fich);
fgets(linea[0], 128, aux[0]);
}
if(!feof(aux[1])) tramos++;
while(!feof(aux[1])) {
fputs(linea[1], fich);
fgets(linea[1], 128, aux[1]);
}
return(tramos == 1);
}

Ordenar archivos es siempre una tarea muy lenta y requiere mucho tiempo. Este algoritmo, adems requiere el
doble de espacio en disco del que ocupa el fichero a ordenar, por ejemplo, para ordenar un fichero de 500 megas
se necesitan otros 500 megas de disco libres.
Sin embargo, un fichero como el mencionado, sera muy difcil de ordenar en memoria.

6 Ordenar ficheros de acceso aleatorio


Cuando trabajemos con ficheros de acceso secuencial con tamao de registro constante, podremos aplicar los
mismos algoritmos de ordenacin que con tablas en memoria, ya que es posible acceder a cada registro para
lectura y escritura.
En el caso de ficheros de acceso aleatorio con tamao de registro variable, los trataremos como si fueran
secuenciales.

Algoritmo Quicksort
Por supuesto, hay que elegir un algoritmo que impleque un mnimo de lecturas y escrituras en el fichero, y
preferentemente, que stas operaciones estn los ms prximas posible entre si. Resulta muy costoso, en
trminos de tiempo de ejecucin, hacer muchas lecturas y escrituras en disco, y ms si los puntos donde se
realizan estn muy separados entre ellos.
Como ejemplo, usaremos el algoritmo de ordenacin quicksort, adaptndolo para ordenar ficheros.
Usaremos el programa de ejemplo que usamos para los archivos de acceso aleatorio "alea.cpp". Y aadiremos
una nueva opcin para ordenar el archivo.

Ejemplo:
// alea2.cpp: Ejemplo de ficheros de acceso aleatorio.
// Incluye la opcin de ordenar el archivo.
#include <stdio.h>
#include <stdlib.h>
struct stRegistro {
char valido; // Campo que indica si el registro es valido S->Vlido, N>Invlido
char nombre[34];
int dato[4];
};
int Menu();
void Leer(stRegistro &reg);
void Mostrar(stRegistro &reg);
void Listar(long n, stRegistro &reg);
long LeeNumero();
void Empaquetar(FILE *fa);
void Ordenar(FILE *fa);
void Intercambia(FILE *fa, long iz, long de);
char *LeeCampo(FILE *fa, long n, char *buf);
void QuickSort(FILE *fa, long inicio, long final);
int main()
{
stRegistro reg;
FILE *fa;
int opcion;
long numero;
fa = fopen("alea.dat", "r+b");
escribir

// Este modo permite leer y

if(!fa) fa = fopen("alea.dat", "w+b");


crea.

// si el fichero no existe, lo

do {
opcion = Menu();
switch(opcion) {
case '1': // Aadir registro
Leer(reg);
// Insertar al final:
fseek(fa, 0, SEEK_END);
fwrite(&reg, sizeof(stRegistro), 1, fa);
break;
case '2': // Mostrar registro
system("cls");
printf("Mostrar registro: ");
numero = LeeNumero();
fseek(fa, numero*sizeof(stRegistro), SEEK_SET);
fread(&reg, sizeof(stRegistro), 1, fa);
Mostrar(reg);
break;
case '3': // Eliminar registro
system("cls");
printf("Eliminar registro: ");
numero = LeeNumero();
fseek(fa, numero*sizeof(stRegistro), SEEK_SET);
fread(&reg, sizeof(stRegistro), 1, fa);
reg.valido = 'N';
fseek(fa, numero*sizeof(stRegistro), SEEK_SET);
fwrite(&reg, sizeof(stRegistro), 1, fa);
break;
case '4': // Mostrar todo
rewind(fa);
numero = 0;
system("cls");
printf("Nombre
Datos\n");
while(fread(&reg, sizeof(stRegistro), 1, fa)) Listar(numero++,
reg);
system("PAUSE");
break;
case '5': // Eliminar marcados
Empaquetar(fa);
break;
case '6': // Ordenar
Empaquetar(fa);
Ordenar(fa);
break;
}
} while(opcion != '0');
fclose(fa);
return 0;
}
// Muestra un men con las opciones disponibles y captura una opcin del
usuario
int Menu()
{
char resp[20];

do {
system("cls");
printf("MENU PRINCIPAL\n");
printf("--------------\n\n");
printf("1- Insertar registro\n");
printf("2- Mostrar registro\n");
printf("3- Eliminar registro\n");
printf("4- Mostrar todo\n");
printf("5- Eliminar registros marcados\n");
printf("6- Ordenar fichero\n");
printf("0- Salir\n");
fgets(resp, 20, stdin);
} while(resp[0] < '0' && resp[0] > '6');
return resp[0];
}
// Permite que el usuario introduzca un registro por pantalla
void Leer(stRegistro &reg)
{
int i;
char numero[6];
system("cls");
printf("Leer registro:\n\n");
reg.valido = 'S';
printf("Nombre: ");
fgets(reg.nombre, 34, stdin);
// la funcin fgets captura el retorno de lnea, hay que eliminarlo:
for(i = strlen(reg.nombre)-1; i && reg.nombre[i] < ' '; i--)
reg.nombre[i] = 0;
for(i = 0; i < 4; i++) {
printf("Dato[%1d]: ", i);
fgets(numero, 6, stdin);
reg.dato[i] = atoi(numero);
}
}
// Muestra un registro en pantalla, si no est marcado como borrado
void Mostrar(stRegistro &reg)
{
int i;
system("cls");
if(reg.valido == 'S') {
printf("Nombre: %s\n", reg.nombre);
for(i = 0; i < 4; i++) printf("Dato[%1d]: %d\n", i, reg.dato[i]);
}
system("PAUSE");
}
// Muestra un registro por pantalla en forma de listado,
// si no est marcado como borrado
void Listar(long n, stRegistro &reg)
{
int i;
if(reg.valido == 'S') {

printf("[%6ld] %-34s", n, reg.nombre);


for(i = 0; i < 4; i++) printf(", %4d", reg.dato[i]);
printf("\n");
}
}
// Lee un nmero suministrado por el usuario
long LeeNumero()
{
char numero[6];
fgets(numero, 6, stdin);
return atoi(numero);
}
// Elimina los registros marcados como borrados
void Empaquetar(FILE *fa)
{
FILE *ftemp;
stRegistro reg;
ftemp = fopen("alea.tmp", "wb");
rewind(fa);
while(fread(&reg, sizeof(stRegistro), 1, fa))
if(reg.valido == 'S') fwrite(&reg, sizeof(stRegistro), 1, ftemp);
fclose(ftemp);
fclose(fa);
remove("alea.bak");
rename("alea.dat", "alea.bak");
rename("alea.tmp", "alea.dat");
fa = fopen("alea.dat", "r+b");
}
void Ordenar(FILE *fa)
{
long nRegs;
fseek(fa, 0, SEEK_END);
nRegs = ftell(fa)/sizeof(stRegistro);
QuickSort(fa, 0L, nRegs-1);
}
void QuickSort(FILE *fa, long inicio, long final)
{
long iz, de;
char mitad[34];
static char cad[34];
iz = inicio;
de = final;
strcpy(mitad, LeeCampo(fa, (iz+de)/2, cad));
do {
while(strcmp(LeeCampo(fa, iz, cad), mitad) < 0 && iz < final) iz++;
while(strcmp(mitad, LeeCampo(fa, de, cad)) < 0 && de > inicio) de--;
if(iz < de) Intercambia(fa, iz, de);
if(iz <= de) {
iz++;
de--;

}
} while(iz <= de);
if(inicio < de) QuickSort(fa, inicio, de);
if(iz < final) QuickSort(fa, iz, final);
}
char *LeeCampo(FILE *fa, long n, char *buf)
{
stRegistro reg;
fseek(fa, n*sizeof(stRegistro), SEEK_SET);
fread(&reg, sizeof(stRegistro), 1, fa);
strcpy(buf, reg.nombre);
return buf;
}
void Intercambia(FILE *fa, long iz, long de)
{
stRegistro reg1, reg2;
fseek(fa, iz*sizeof(stRegistro), SEEK_SET);
fread(&reg1, sizeof(stRegistro), 1, fa);
fseek(fa, de*sizeof(stRegistro), SEEK_SET);
fread(&reg2, sizeof(stRegistro), 1, fa);
fseek(fa, iz*sizeof(stRegistro), SEEK_SET);
fwrite(&reg2, sizeof(stRegistro), 1, fa);
fseek(fa, de*sizeof(stRegistro), SEEK_SET);
fwrite(&reg1, sizeof(stRegistro), 1, fa);
}

El algoritmo que hemos usado es bastante bueno para ordenar ficheros, ya que requiere muy pocos intercambios
de registros, pero de todos modos, con ficheros grandes puede ser un proceso muy lento. En general es preferible
no ordenar los ficheros, salvo que sea muy necesario.

7 Ficheros de ndices
Mantener grandes ficheros de datos ordenados es muy costoso, ya que requiere mucho tiempo de procesador.
Afortunadamente, existe una alternativa mucho mejor: indicarlos (o indexarlos).
Para indicar un archivo normalmente se suele generar un archivo auxiliar de ndices. Existen varios mtodos, de
los que veremos algunos. El ms sencillo es crear un archivo plano que slo contenga registros con dos campos: el
campo o la expresin por la que queremos ordenar el archivo, y un campo con un ndexe que almecene la posicin
del registro indicado en el archivo de datos.
Por ejemplo, supongamos que tenemos un archivo de datos con la siguiente estructura de registro:
struct stRegistro {
char nombre[32];
char apellido[2][32];
char telefono[12];
char calle[45];
int numero;
char ciudad[32];
char fechaNacimiento[9]; // formato AAAAMMDD: Ao, mes y da
char estadoCivil;
int hijos;
}

Imaginemos que necesitamos buscar un registro a partir del nmero de telfono. Si no tenemos el archivo
ordenado por ese campo, estaremos obligados a leer todos los registros del archivo hasta encontrar el que
buscamos, y si el nmero no est, tendremos que leer todos los registros que existan.
Si tenemos el archivo ordenado por nmeros de telfono podremos aplicar un algoritmo de bsqueda. Pero si
tambin queremos hacer bsquedas por otros campos, estaremos obligados a ordenar de nuevo el archivo.
La solucin es crear un fichero de ndices, cada registro de este archivo tendr la siguiente estructura:
struct stIndiceTelefono {
char telefono[12];
long indice;
}

Crearemos el fichero de ndices a partir del archivo de datos, asignando a cada registro el campo "telefono" y el
nmero de registro correspondiente. Veamos un ejemplo:
000: [Fulanito] [Prez] [Sanchez] [12345678] [Mayor] [15] [Lisboa]
[19540425] [S] [0]
001: [Fonforito] [Fernandez] [Lpez] [84565456] [Baja] [54] [Londres]
[19750924] [C] [3]
002: [Tantolito] [Jimenez] [Fernandez] [45684565] [Alta] [153] [Berlin]
[19840628] [S] [0]
003: [Menganito] [Sanchez] [Lpez] [23254532] [Diagonal] [145] [Barcelona]
[19650505] [C] [1]
004: [Tulanito] [Sanz] [Sanchez] [54556544] [Pez] [18] [Dubln] [19750111]
[S] [0]

Generamos un fichero de ndices:


[12345678][000]
[84565456][001]
[45684565][002]
[23254532][003]
[54556544][004]

Y lo ordenamos:
[12345678][000]
[23254532][003]
[45684565][002]
[54556544][004]
[84565456][001]

Ahora, cuando queramos buscar un nmero de telfono, lo haremos en el fichero de ndices, por ejemplo el
"54556544" ser el registro nmero 3, y le corresponde el ndice "004". Con ese ndice podemos acceder
directamente al archivo de datos, y veremos que el nmero corresponde a "Tulanito Sanz Sanchez".
Por supuesto, nada nos impide tener ms ficheros de ndices, para otros campos.
El mayor problema es mantener los ficheros de ndices ordenados a medida que aadimos, eliminamos o
modificamos registros. Pero al ser los registros de ndices ms pequeos, los ficheros son ms manejables,
pudiendo incluso almacenarse en memoria en muchos casos.

Ejemplo
Veramos un ejemplo de implementacin de ndices:
// indices.cpp: Ejemplo de ficheros de acceso aleatorio con ndices.
#include <stdio.h>
#include <stdlib.h>
struct stRegistro {
char valido; // Campo que indica si el registro es valido S->Vlido, N>Invlido
char nombre[34];
char apellido[2][34];
char telefono[10];
};
struct stIndice {
char telefono[10];
long indice;
};
int Menu();
void Capturar(stRegistro &reg);
void EliminarRetornoLinea(char *cad);
void Leer(FILE *fa, stRegistro &:reg, char *telefono);
void Insertar(FILE *fa, stRegistro &reg);
void Mostrar(stRegistro &reg);

void ListarPorTelefonos(FILE *fa);


void ListarNatural(FILE *fa);
void ReconstruirIndices(FILE *fa);
// Funciones para ordenar el fichero de ndices:
void Intercambia(FILE *fa, long iz, long de);
char *LeeCampo(FILE *fa, long n, char *buf);
void QuickSort(FILE *fa, long inicio, long final);
int main()
{
stRegistro reg;
FILE *fa;
int opcion;
char telefono[10];
// Este modo permite leer y
fa = fopen("indices.dat", "r+b);
escribir
if(!fa) fa = fopen("indices.dat", "w+b"); // si el fichero no existe, lo
crea.
do {
opcion = Menu();
switch(opcion) {
case '1': // Insertar registro
Capturar(reg);
Insertar(fa, reg);
break;
case '2': // Buscar registro
system("cls");
printf("Buscar registro: ");
do {
fgets(telefono, 10, stdin);
EliminarRetornoLinea(telefono);
} while(strlen(telefono) < 1);
Leer(fa, reg, telefono);
Mostrar(reg);
break;
case '3': // Indicar archivo
system("cls");
printf("Indicando archivo: ");
ReconstruirIndices(fa);
break;
case '4': // Mostrar todo por orden de telfonos
ListarPorTelefonos(fa);
break;
case '5': // Mostrar todo por orden natural
ListarNatural(fa);
break;
}
} while(opcion != '0');
fclose(fa);
return 0;
}
// Muestra un men con las opciones disponibles y captura una opcin del
usuario
int Menu()
{

char resp[20];
do {
system("cls");
printf("MENU PRINCIPAL\n");
printf("--------------\n\n");
printf("1- Insertar registro\n");
printf("2- Buscar registro\n");
printf("3- Reindicar archivo\n");
printf("4- Listar por orden de telfonos\n");
printf("5- Listar por orden natural\n");
printf("0- Salir\n");
fgets(resp, 20, stdin);
} while(resp[0] < '0' && resp[0] > '5');
return resp[0];
}
// Permite que el usuario introduzca un registro por pantalla
void Capturar(stRegistro &reg)
{
int i;
char numero[6];
system("cls");
printf("Leer registro:\n\n");
reg.valido = 'S';
printf("Nombre: ");
fgets(reg.nombre, 34, stdin);
EliminarRetornoLinea(reg.nombre);
printf("Primer apellido: ");
fgets(reg.apellido[0], 34, stdin);
EliminarRetornoLinea(reg.apellido[0]);
printf("Segundo apellido: ");
fgets(reg.apellido[1], 34, stdin);
EliminarRetornoLinea(reg.apellido[1]);
printf("Telfono: ");
fgets(reg.telefono, 10, stdin);
EliminarRetornoLinea(reg.telefono);
}
// Elimina los caracteres de retorno de lnea al final de cadena
void EliminarRetornoLinea(char *cad)
{
int i;
// la funcin fgets captura el retorno de lnea, hay que eliminarlo:
for(i = strlen(cad)-1; i >= 0 && cad[i] < ' '; i--) cad[i] = 0;
}
// Muestra un registro en pantalla, si no est marcado como borrado
void Mostrar(stRegistro &reg)
{
int i;
if(reg.valido == 'S') {
printf("Nombre: %s %s %s\n", reg.nombre, reg.apellido[0],
reg.apellido[1]);
printf("Nmero de telfono: %s\n", reg.telefono);
}
system("PAUSE");

}
// Lee el registro desde el fichero de datos con el telfono dado
void Leer(FILE *fa, stRegistro &reg, char *telefono)
{
FILE *fi;
stIndice ind;
long inf, sup, n, nRegs;
fi = fopen("indices.ind", "rb");
fseek(fi, 0, SEEK_END);
nRegs = ftell(fi)/sizeof(stIndice);
// Bsqueda binaria:
inf = 0;
sup = nRegs-1;
do {
n = inf+(sup-inf)/2;
fseek(fi, n*sizeof(stIndice), SEEK_SET);
fread(&ind, sizeof(stIndice), 1, fi);
if(strcmp(ind.telefono, telefono) < 0) inf = n+1;
else sup = n-1;
} while(inf <= sup && strcmp(ind.telefono, telefono));
// Si se encontr el telfono, lee el registro, si no muestra mensaje.
if(!strcmp(ind.telefono, telefono)) {
fseek(fa, ind.indice*sizeof(stRegistro), SEEK_SET);
fread(&reg, sizeof(stRegistro), 1, fa);
}
else {
reg.valido = 'N';
printf("Registro no encontrado\n");
}
fclose(fi);
}
// Aade un registro al archivo de datos y reconstruye los ndices
void Insertar(FILE *fa, stRegistro &reg)
{
// Insertar al final:
fseek(fa, 0, SEEK_END);
fwrite(&reg, sizeof(stRegistro), 1, fa);
ReconstruirIndices(fa);
}
// Lista todos los registros ordenados por el nmero de telfono
void ListarPorTelefonos(FILE *fa)
{
FILE *fi;
stIndice ind;
stRegistro reg;
system("cls");
fi = fopen("indices.ind", "rb");
while(fread(&ind, sizeof(stIndice), 1, fi)) {
fseek(fa, ind.indice*sizeof(stRegistro), SEEK_SET);
fread(&reg, sizeof(stRegistro), 1, fa);
printf("%s %s %s %s\n", reg.nombre, reg.apellido[0],
reg.apellido[1], reg.telefono);
}
fclose(fi);

system("PAUSE");
}
// Lista todos los registros del archivo de datos por el orden en que se
// insertaron.
void ListarNatural(FILE *fa)
{
stRegistro reg;
rewind(fa);
system("cls");
while(fread(&reg, sizeof(stRegistro), 1, fa))
printf("%s %s %s %s\n", reg.nombre, reg.apellido[0],
reg.apellido[1], reg.telefono);
system("PAUSE");
}
// Reconstruye el archivo de ndices
void ReconstruirIndices(FILE *fa)
{
long n=0;
FILE *fi;
stRegistro reg;
stIndice ind;
// Crea el fichero de ndices a partir del archivo de datos:
fi = fopen("indices.ind", "w+b");
rewind(fa);
while(fread(&reg, sizeof(stRegistro), 1, fa)) {
strcpy(ind.telefono, reg.telefono);
ind.indice = n++;
fwrite(&ind, sizeof(stIndice), 1, fi);
}
// Ordena usando el algoritmo Quicksort:
QuickSort(fi, 0, n-1);
fclose(fi);
}
// Implementacin del algoritmo Quicksort para fichero de ndices
void QuickSort(FILE *fi, long inicio, long final)
{
long iz, de;
char mitad[10];
static char cad[10];
iz = inicio;
de = final;
strcpy(mitad, LeeCampo(fi, (iz+de)/2, cad));
do {
while(strcmp(LeeCampo(fi, iz, cad), mitad) < 0 && iz < final) iz++;
while(strcmp(mitad, LeeCampo(fi, de, cad)) < 0 && de > inicio) de--;
if(iz < de) Intercambia(fi, iz, de);
if(iz <= de) {
iz++;
de--;
}
} while(iz <= de);
if(inicio < de) QuickSort(fi, inicio, de);
if(iz < final) QuickSort(fi, iz, final);

}
char *LeeCampo(FILE *fi, long n, char *buf)
{
stIndice ind;
fseek(fi, n*sizeof(stIndice), SEEK_SET);
fread(&ind, sizeof(stIndice), 1, fi);
strcpy(buf, ind.telefono);
return buf;
}
void Intercambia(FILE *fi, long iz, long de)
{
stIndice reg1, reg2;
fseek(fi, iz*sizeof(stIndice), SEEK_SET);
fread(&reg1, sizeof(stIndice), 1, fi);
fseek(fi, de*sizeof(stIndice), SEEK_SET);
fread(&reg2, sizeof(stIndice), 1, fi);
fseek(fi, iz*sizeof(stIndice), SEEK_SET);
fwrite(&reg2, sizeof(stIndice), 1, fi);
fseek(fi, de*sizeof(stIndice), SEEK_SET);
fwrite(&reg1, sizeof(stIndice), 1, fi);
}
An no hemos llegado al mayor nivel de optimizacin, nuestro ltimo ejemplo requiere reconstruir el fichero de
ndices cada vez que se aade o se elimina un registro.

8 Ficheros indicados no ordenados: rboles binarios


Para evitar tener que reconstruir el fichero de ndices cada vez que se actualiza el archivo de datos existen
varios mtodos. Veremos ahora cmo implementar rboles binarios.
Para ello construiremos una estructura en rbol mediante una tabla almacenada en un archivo de disco.

La estructura para cada nodo del rbol es una extensin de la que usamos en el captulo anterior, tan slo
aadiremos dos campos extra para apuntar a otros nodos:
struct stNodo {
char telefono[10];
long indice;
long menor, mayor;
};

Crearemos el fichero de ndices estructurados en rbol a partir del archivo de datos, asignando a cada registro
el campo "telefono" y el nmero de registro correspondiente y aadiendo los enlaces a otros nodos. Veamos un
ejemplo:
000: [Fulanito] [Prez] [Sanchez] [12345678] [Mayor] [15] [Lisboa]
[19540425] [S] [0]
001: [Fonforito] [Fernandez] [Lpez] [84565456] [Baja] [54] [Londres]
[19750924] [C] [3]
002: [Tantolito] [Jimenez] [Fernandez] [45684565] [Alta] [153] [Berlin]
[19840628] [S] [0]
003: [Menganito] [Sanchez] [Lpez] [23254532] [Diagonal] [145] [Barcelona]
[19650505] [C] [1]
004: [Tulanito] [Sanz] [Sanchez] [54556544] [Pez] [18] [Dubln] [19750111]
[S] [0]

Veremos cmo se actualiza el fichero de ndices a medida que insertamos registros en el archivo de datos:
Paso uno:
[12345678][000][---][---]

Paso dos:
[12345678][000][---][001] <-[84565456][001][---][---]

Paso tres:
[12345678][000][---][001]
[84565456][001][002][---] <-[45684565][002][---][---]

Paso cuatro:
[12345678][000][---][001]
[84565456][001][002][---]
[45684565][002][003][---] <-[23254532][003][---][---]

Paso cinco:
[12345678][000][---][001]
[84565456][001][002][---]
[45684565][002][003][004] <-[23254532][003][---][---]
[54556544][004][---][---]

Como puede observarse, cada vez que se inserta un registro de datos, tan slo hay que insertar un registro de
ndice y modificar otro.

Eliminar registros
Supongamos que queremos eliminar un registro de datos. En el archivo de datos simplemente lo marcamos
como borrado. En teora, mientras el registro no se elimine fsicamente, no ser necesario eliminar el registro
de ndice asociado. Simplemente estar apuntando a un registro marcado como borrado. Posteriormente,
cuando purguemos el archivo de datos ser necesario reconstruir el fichero de ndices.

Duplicacin de claves
No hay inconveniente en almacenar registros con claves duplicadas, tan slo habr que tener en cuenta que
tendremos que almacenar un nodo para cada uno de ellos. Tomaremos un criterio para el rbol, la rama 'menor',
y pasar a ser la rama 'menor o igual'.

Ventajas y desventajas
Este mtodo tiene la ventaja de que no es necesario ordenar el archivo de ndices, pero puede producir
resultados mediocres o francamente malos. Por ejemplo, si los registros se introducen ordenados, buscar por la
clave del ltimo registro insertado requerir leer todos los nodos del rbol.

You might also like