Está en la página 1de 48

Almacenamiento Externo de Datos

Ficheros

Metodologa de la Programacin II

Tema 2 2.1 Definicin de fichero Un fichero en sentido global puede ser desde un monitor hasta una impresora, pasando por un archivo en disco. La idea ms comn del concepto de fichero es un conjunto de posiciones de memoria situadas en un disco de los dispositivos externos de almacenamiento del sistema, en las cuales podemos almacenar y recuperar informacin. El lenguaje C nos proporciona un acceso secuencial y directo a los registros de un fichero, pero no soporta el acceso indexado a un registro dado. Los ficheros en C los podemos clasificar, segn la informacin que contengan, en dos grupos: Tipo FILE: C define la estructura de datos FILE en el fichero de cabecera "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. 2.2 Ficheros de texto y ficheros binarios. Los ficheros de texto se caracterizan por estar compuestos por una serie de caracteres organizados en lneas terminadas por un carcter de nueva lnea (carcter '\n'). Esto nos hace pensar en la idea de usar la impresora como si fuese un fichero de texto. Por otro lado, los ficheros binarios constan de una secuencia de bytes. Podemos decir que cualquier fichero que no sea de texto, ser binario. A la hora de trabajar con ficheros, tendremos que especificar antes de usarlos, si sern de texto o binarios. Podemos establecer una segunda clasificacin de los ficheros, atendiendo al modo de acceso a su informacin. De este modo, distinguiremos entre ficheros secuenciales y ficheros directos. Los ficheros de acceso secuencial se basan en el hecho de que, para acceder a una determinada posicin de los mismos, hemos de recorrer desde el principio todas las posiciones hasta llegar a la deseada. Las impresoras son un claro ejemplo de acceso secuencial, as como las cintas magnticas. Con el uso de ficheros de acceso directo podemos acceder de forma directa a la posicin que queramos sin tener que pasar por las posiciones anteriores. El dispositivo de acceso directo por excelencia es el dico magntico. 90 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos El lenguaje C trata a los ficheros como punteros. En realidad un fichero es un puntero a una estructura de nombre predefinido FILE, cuyas componentes son las caractersticas de la variable fichero declarada. Cada fichero deber tener una estructura FILE asociada. La estructura FILE se encuentra definida en el archivo de cabecera stdio.h, con lo cual es necesario incluirla en todos los programas que trabajen con ficheros mediante la conocida directiva #include <stdio.h> La forma de declarar variables de tipo FILE es la siguiente: FILE *f, *f1, ... Como podemos observar se trata de punteros que apuntan a estructuras de tipo FILE. En realidad lo que ocurre con el tipo FILE es que redefine a una estructura de los siguiente forma: typedef struct { short level; unsigned flags; char fd; unsigned char hold; short bsize; unsigned char *buffer, *curp; unsigned istemp; short token; }FILE; Esta estructura la podrs ver editando el fichero stdio.h. El programador no tendr que definirla puesto que ya existe. A continuacin vamos a ver las operaciones que podemos realizar con ficheros secuenciales y directos. 2.3 Ficheros de acceso secuencial La primera oeracin despus de declarar un fichero y antes de cualquier otra operacin con l es abrirlo. fopen <variable_fichero> = fopen (<nombre_fichero>, <modo acceso>); Donde: variable_fichero es la variable declarada de la forma: FILE *<variable_fichero> nombre_fichero es el nombre que tendr el fichero en el dispositivo usado. Podr ser variable o constante. Metodologa de la Programacin II 91

Tema 2 Modo acceso es el modo de apertura del fichero, mediante el cual le diremos al compilador si se trata de un fichero de texto o binario, y si vamos a leer o escribir en el fichero. Los distintos modos de abrir un fichero son los siguientes:

Modo Significado r w a r+ w+ a+ rb wb ab rb + Abre un archivo de texto para solo lectura. Si el archivo no existe, devuelve un error Abre el archivo de texto para solo escritura. Si el fichero no existe, lo crea, y si existe lo machaca. Abre el archivo de texto para aadir al final. Si el fichero no existe, lo crea. Abre el archivo de texto para lectura/escritura. Si el fichero no existe, da un error Abre el archivo de texto para lectura/escritura. Si el fichero no existe, lo crea, y si existe lo machaca. Abre el archivo de texto para aadir al final, con opcin de lectura. Si el fichero no existe, lo crea. Abre un archivo binario para solo lectura. Si el archivo no existe, devuelve un error Abre el archivo binario para solo escritura. Si el fichero no existe, lo crea, y si existe lo machaca. Abre el archivo binario para aadir al final. Si el fichero no existe, lo crea. Abre el archivo binario para lectura/escritura. Si el fichero no existe, da un error

wb + Abre el archivo binario para lectura/escritura. Si el fichero no existe, lo crea, y si existe lo machaca. ab + Abre el archivo binario para aadir al final, con opcin de lectura. Si el fichero no existe, lo crea.

Decir que los ficheros de texto se pueden referenciar tambien como "rt", "wt", "at", "rt+", "wt+" y "at+", pero para facilitar ms las cosas, cuando no especificamos si queremos abrir un fichero de texto o binario, por defecto se supone que es de texto, es por ello que los modos "r", "w" y "a" se refieren a ficheros de texto. Hemos de tener cuidado con los modos de apertura de los ficheros por que al abrir con cualquier modo "w", si ya exista el fichero, se borrar la informacin que contuviese. Si no existe, lo crear. Por el contrario con el modo "a" tambin lo crear si no existe el fichero, pero si ya existe, aadir al final del mismo los datos que escribamos. Podemos saber si un fichero ha sido abierto correctamente o no. Debido a que los ficheros son punteros a estructuras tipo FILE, podemos conocer su valor al hacer que apunten a ellas. La forma de hacerlo es comparando el puntero con la constante predefinida NULL. De esta forma,

92

Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos cuando el puntero valga NULL (no apunta a ninguna variable o posicion de memoria), es porque ha habido algn problema al abrir el fichero. Este problema se puede deber a que no existe el fichero que intentamos abrir para lectura, que no hay espacio suficiente en el disco al tratar de crear el fichero, que la unidad de disco no est preparada, que el disco est daado fisicamente o que la impresora no est conectada. Veamos en ejemplo del testeo de la existencia de un fichero de texto. FILE *f; f = fopen ("texto.txt", "r"); if (f == NULL) printf ("Error, el fichero no existe\n"); else ... O de esta otra manera FILE *f; f = fopen ("texto.txt", "r"); if (!f) printf ("Error, el fichero no existe\n"); else ... O bien, de forma abreviada FILE *f; if ((f = fopen ("texto.txt", "r")) == NULL) printf ("Error, el ficnero no existe\n"); else ...
#include <stdio.h> int main() { FILE *fichero; char nombre[10] = "datos.dat"; fichero = fopen( nombre, "w" ); printf( "Fichero: %s -> ", nombre ); if( fichero ) printf( "creado (ABIERTO)\n" ); else { printf( "Error (NO ABIERTO)\n" ); return 1; } if( !fclose(fichero) ) printf( "Fichero cerrado\n" ); else

Metodologa de la Programacin II

93

Tema 2
{ printf( "Error: fichero NO CERRADO\n" ); return 1; } return 0; }

fclose <valor> = fclose (<variable_fichero>); Donde: valor es el valor que nos dir si ha ocurrido algn error cerrando el fichero. 0 indica que todo ha ido bien. variable_fichero es la variable declarada de la forma: FILE *<variable_fichero> Cuando terminemos de trabajar con un fichero hemos de realizar la operacin de cierre del fichero. Si no lo hacemos podemos ocasionar la prdida de todos los datos del mismo. Si se necesitan cerrar varios ficheros a la vez, nos podemos ahorrar varios fclose utilizando la funcion fcloseall.
#include <stdio.h> int main() { FILE *fichero; char nombre[10] = "datos.dat"; fichero = fopen( nombre, "w" ); printf( "Fichero: %s -> ", nombre ); if( fichero ) printf( "creado (ABIERTO)\n" ); else { printf( "Error (NO ABIERTO)\n" ); return 1; } if( !fclose(fichero) ) printf( "Fichero cerrado\n" ); else { printf( "Error: fichero NO CERRADO\n" ); return 1; } return 0; }

fcloseall <numero_ficheros> = fcloseall(); 94 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos Donde: numero_ficheros es una variable entera que indica el nmero de ficheros cerrados, o EOF si ha ocurrido un error. Ahora veamos una serie de funciones para escribir y leer datos de un fichero de texto. #include <stdio.h> int main(void) { int streams_closed; /* open two streams */ fopen("DUMMY.ONE", "w"); fopen("DUMMY.TWO", "w"); /* close the open streams */ streams_closed = fcloseall(); if (streams_closed == EOF) /* issue an error message */ perror("Error"); else /* print result of fcloseall() function */ printf("%d streams were closed.\n", streams_closed); return 0; } fputc Esta funcin escribe un carcter en un fichero de texto. Devuelve un entero si la escritura es correcta. De otra forma devuelve el valor EOF que indica que ha habido un error. Su formato es: fputc (<carcter>, <var_fich>); Donde: carcter es el carcter que se desea escribir en el fichero. var_fich es la variable declarada como FILE. Ejemplos: putc ('a', f1); c = 'G'; putc (c, f1); Metodologa de la Programacin II 95

Tema 2 Ni que decir tiene que cuando usemos funciones para escribir en ficheros, estos tendrn que haber sido abiertos en modo escritura o para aadir datos.
#include <stdio.h> int main() { char nombre[10]="datos.dat"; FILE *fichero; int i; fichero = fopen( nombre, "a" ); printf( "Fichero: %s -> ", nombre ); if( fichero ) printf( "existe o ha sido creado (ABIERTO)\n" ); else { printf( "Error (NO ABIERTO)\n" ); return 1; } printf( "Escribimos las 18 primeras letras del abecedario ingles en el fichero: %s\n\n", nombre ); for( i=0; i<18; i++) printf( "%c", fputc('a'+i, fichero) ); if( !fclose(fichero) ) printf( "\nFichero cerrado\n" ); else { printf( "\nError: fichero NO CERRADO\n" ); return 1; } return 0; }

fgetc Esta funcin lee un carcter de un fichero de texto abierto en modo lectura. Devuelve un entero si la lectura es correcta. De otra forma devuelve el valor EOF que indica que hemos llegado al final del fichero. Su formato es: <carcter> = fgetc (<var_fich>); Donde: carcter es la variable que contendr el carcter ledo del fichero. var_fich es la variable declarada como FILE. Ejemplos: c = fgetc (f1);

96

Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos Un ejemplo del uso de estas dos funciones sera leer todos los caracteres de un fichero de texto y escribirlos en otro fichero de texto y por pantalla a la vez para comprobar la correcta ejecucin: #include <stdio.h> main () { FILE *f_in, *f_out; char c; clrscr(); if ((f_in = fopen ("prueba.c", "r")) == NULL) { printf ("Error de apertura del fichero\n"); exit (1); } if ((f_out = fopen ("salida.c", "w")) == NULL) { printf ("Error de creacin del fichero\n"); exit (1); } do { c = fgetc(f_in); putchar(c) /* escritura en pantalla */ putc(c, f_out); /* escritura en el fichero */ } while (c != EOF); fclose (f_in); fclose (f_out); } Aparece en este ejemplo una nueva funcin, la funcin exit(). Esta funcin provoca que acabe el programa con lo que la funcin main() devuelve un valor, en este caso el valor 1. Recordamos que toda funcin devuelve un valor. fputs Esta funcin escribe una cadena de caracteres en un fichero de texto. Su formato es: fputs (<cadena>, <var_fich>); Donde: cadena es el cadena que se desea escribir en el fichero. var_fich es la variable declarada como FILE. Ejemplos: Metodologa de la Programacin II 97

Tema 2 fputs ("hola\n", f1); strcpy (cad, "hola\n"); fputs (cad, f1); fgets Esta funcin lee un nmero de caracteres de un fichero almacenndolos en una cadena. Si se encuentra el carcter de nueva lnea ya no almacenar ms caracteres. Su formato es: fgets (<cadena>, <num_caracteres>, <var_fich>); Donde: cadena es la variable que almacenar la cadena leda del fichero. num_caracteres es el nmero de caracteres que se desea leer. var_fich es la variable declarada como FILE. Ejemplos: fgets (cad, 5, f1); fwrite Esta funcin permite escribir uno o ms datos o bloques de datos binarios en un fichero. Su formato es el siguiente: fwrite (<dato>, <num_bytes>, <cont>, <var_fich>); Donde: dato es el dato o datos que se van a escribir en el fichero. Hemos de especificar su direccin de memoria mediante el operador "&", ya que realmente es un puntero que apunta a la zona de memoria intermedia donde se almacenar temporalmente la informacin antes de pasarla al fichero. num_bytes es el nmero de bytes que se van a escribir. Lo ms corriente es usar para este argumento el operador sizeof sobre el dato a escribir. contes el nmero de datos, de num_bytes cada uno que se van a escribir. var_fich es el puntero al fichero binario usado. #include <stdio.h> struct mystruct { int i; 98 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos char ch; }; int main(void) { FILE *stream; struct mystruct s; if ((stream = fopen("TEST.$$$", "wb")) == NULL) /* open file TEST.$$$ */ { fprintf(stderr, "Cannot open output file.\n"); return 1; } s.i = 0; s.ch = 'A'; fwrite(&s, sizeof(s), 1, stream); /* write struct s to file */ fclose(stream); /* close file */ return 0; } fread Esta funcin permite leer uno o ms datos o bloques de datos de un fichero binario. Su formato es: fread (<dato>, <num_bytes>, <cont>, <var_fich>); Donde: dato es la variable donde se guardarn los datos ledos del fichero. Hemos de indicar su direccin por tratarse de un puntero. num_bytes es el nmero de bytes que se van a leer del fichero. Lo ms corriente es usar para este argumento el operador sizeof sobre el dato a escribir. Cont es el nmero de datos, de num_bytes cada uno que se van a leer. var_fich es el puntero al fichero binario usado. Vamos a ver un ejemplo que escriba un float en un fichero y despus lo lea y escriba en pantalla: #include <stdio.h> main () { FILE *fich; float f = 4.879; /* Escritura del float en el fichero */ if ((fich = fopen ("floats.dat", "wb")) == NULL) Metodologa de la Programacin II 99

Tema 2 { printf ("Error de creacin del fichero\n"); exit (1); } fwrite (&f, sizeof(f), 1, fich); fclose (fich); /* Lectura del float del fichero */ if ((fich = fopen ("floats.dat", "rb")) == NULL) { printf ("Error de existencia del fichero\n"); exit (1); } fread (&f, sizeof(f), 1, fich); fclose (fich); printf ("Float = %.3f", f); } Vemos que hemos usado el mismo puntero de fichero. Lo podemos hacer siempre que cerremos el primero antes de abrir el segundo fichero, o se trate del mismo fichero, como es en este caso. Primero hemos abierto el fichero binario para escritura, con lo cual lo creamos de nuevo, y despus lo abrimos para lectura para leer el float. Este es un ejemplo muy sencillo pues en el fichero solo escribimos un float y despus lo leemos. Pero, qu ocurre cuando escribimos ms de un dato? cmo sabemos hasta donde debemos leer en el fichero?. La respuesta est en conocer donde est el final del fichero. De esta forma leeremos hasta encontrar el final del fichero dado. Existe una funcin que nos dir si hemos llegado a este punto o no: la funcin feof.
#include <stdio.h> int main() { FILE *fichero; char nombre[11] = "datos5.dat"; unsigned int dinero[10] = { 23, 12, 45, 345, 512, 345, 654, 287, 567, 124 }; unsigned int leer[10], i; fichero = fopen( nombre, "w+" ); printf( "Fichero: %s -> ", nombre ); if( fichero ) printf( "creado (ABIERTO)\n" ); else { printf( "Error (NO ABIERTO)\n" ); return 1; } printf( "Escribiendo cantidades:\n\n" ); for( i=0; i<10; i++ ) printf( "%d\t", dinero[i] );

100

Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos


fwrite( dinero, sizeof(unsigned int), 10, fichero ); printf( "\nLeyendo los datos del fichero \"%s\":\n", nombre ); rewind( fichero ); fread( leer, sizeof(unsigned int), 10, fichero ); for( i=0; i<10; i++ ) printf( "%d\t", leer[i] ); if( !fclose(fichero) ) printf( "\nFichero cerrado\n" ); else { printf( "\nError: fichero NO CERRADO\n" ); return 1; } return 0; }

feof Esta funcin nos devuelve un nmero positivo si hemos llegado al final de un fichero binario. De otro modo, nos devuelve un cero. Su sintaxis es: <valor> = feof (<var_fich>); Donde: valor es el valor que nos indica si ha llegado a final de fichero. var_fich es la variable declarada como FILE. Ejemplos: while (!feof(f1)) { fread (....); ... } Este bucle significa "mientras no sea final de fichero leer..." #include <stdio.h> int main(void) { FILE *stream; /* open a file for reading */ stream = fopen("DUMMY.FIL", "r"); /* read a character from the file */ Metodologa de la Programacin II 101

Tema 2 fgetc(stream); /* check for EOF */ if (feof(stream)) printf("We have reached end-of-file\n"); /* close the file */ fclose(stream); return 0; } ferror Determina si una operacin con archivos ha sido errnea o no. Si lo ha sido devuelve un nmero positivo, y si no ha habido problema, devuelve un 0. Su sintaxis es: <valor> = ferror (<var_fich>); Donde: valor es el valor que nos indica si ha ocurrido un error. var_fich es la variable declarada como FILE. Ejemplos: fwrite (&dato, sizeof(dato), 1, fich); if (ferror(fich)) printf ("Error de escritura\n"); #include <stdio.h> int main(void) { FILE *stream; /* open a file for writing */ stream = fopen("DUMMY.FIL", "w"); /* force an error condition by attempting to read */ (void) getc(stream); if (ferror(stream)) /* test for an error on the stream */ { /* display an error message */ printf("Error reading from DUMMY.FIL\n"); /* reset the error and EOF indicators */ clearerr(stream); } 102 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos fclose(stream); return 0; } rewind Esta funcin restablece el localizador de posicin del archivo al comienzo del mismo. Su sintaxis es: rewind (<var_fich>); Donde: var_fich es la variable declarada como FILE. Por ejemplo, si estamos leyendo secuencialmente el fichero fich y vamos por el cuarto dato, al hacer rewind(fich), volveramos al principio del fichero y podramos volver a leer el primer dato, como si lo abrisemos de nuevo para lectura. Ejemplos: // ejemplo1.cpp: Muestra un fichero dos veces. #include <stdio.h> #include <stdlib.h> int main() { FILE *fichero; fichero = fopen("ejemplo1.cpp", "r"); while(!feof(fichero)) fputc(fgetc(fichero), stdout); rewind(fichero); while(!feof(fichero)) fputc(fgetc(fichero), stdout); fclose(fichero); system("pause"); return 0; } getw Esta funcin devuelve un entero ledo de un fichero binario. Su sintaxis es: <num_entero> = getw (<var_fich>); Donde: num_entero es la variable que contendr el valor entero ledo del fichero. var_fich es la variable declarada como FILE. Metodologa de la Programacin II 103

Tema 2 Ejemplos: i = getw (fich); printf ("%d\n", getw (fich)); #include <stdio.h> #include <stdlib.h> #define FNAME "test.$$$" int main(void) { FILE *fp; int word; /* place the word in a file */ fp = fopen(FNAME, "wb"); if (fp == NULL) { printf("Error opening file %s\n", FNAME); exit(1); } word = 94; putw(word,fp); if (ferror(fp)) printf("Error writing to file\n"); else printf("Successful write\n"); fclose(fp); /* reopen the file */ fp = fopen(FNAME, "rb"); if (fp == NULL) { printf("Error opening file %s\n", FNAME); exit(1); } /* extract the word */ word = getw(fp); if (ferror(fp)) printf("Error reading file\n"); else printf("Successful read: word = %d\n", word); 104 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos /* clean up */ fclose(fp); unlink(FNAME); return 0; } putw Esta funcin escribe un entero en un fichero binario. Su sintaxis es: putw (<num_entero>, <var_fich>); Donde: num_entero es el valor entero que se escribir en el fichero. var_fich es la variable declarada como FILE. Ejemplos: putw (5, fich); i = 99; putw (i, fich); #include <stdio.h> #include <stdlib.h> #define FNAME "test.$$$" int main(void) { FILE *fp; int word; /* place the word in a file */ fp = fopen(FNAME, "wb"); if (fp == NULL) { printf("Error opening file %s\n", FNAME); exit(1); } word = 94; putw(word,fp); if (ferror(fp)) printf("Error writing to file\n"); else printf("Successful write\n"); fclose(fp); Metodologa de la Programacin II 105

Tema 2 /* reopen the file */ fp = fopen(FNAME, "rb"); if (fp == NULL) { printf("Error opening file %s\n", FNAME); exit(1); } /* extract the word */ word = getw(fp); if (ferror(fp)) printf("Error reading file\n"); else printf("Successful read: word = %d\n", word); /* clean up */ fclose(fp); unlink(FNAME); return 0; } fscanf Esta funcin trabaja de la misma manera que scanf, pero leyendo los datos formateados de un fichero. Su sintaxis es: fscanf (<var_fich>, <cadena_de_control>, <lista_variables>); Donde: var_fich es la variable declarada como FILE. cadena_de_control son las cadenas de control que se desean leer, tales como %d, %s, %f, %x, etc... lista_variables son las variables que contendrn los valores de la lectura del fichero que deben coincidir con sus respectivas cadenas de control. Ejemplos: fscanf (fich, "%s%d%f", nombre, &edad, &altura); #include <stdlib.h> #include <stdio.h> int main(void) 106 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos { int i; printf("Input an integer: "); /* read an integer from the standard input stream */ if (fscanf(stdin, "%d", &i)) printf("The integer read was: %i\n", i); else { fprintf(stderr, "Error reading an integer from stdin.\n"); exit(1); } return 0; } fprintf Esta funcin trabaja de la misma manera que printf, pero escribiendo los datos formateados sobre un fichero. Su sintaxis es: fprintf (<var_fich>, <cadena_de_control>, <lista_variables>); Donde: var_fich es la variable declarada como FILE. cadena_de_control son las cadenas de control que se desean escribir, tales como %d, %s, %f, %x, etc... lista_variables son las variables que contendrn los valores para la escritura en el fichero que deben coincidir con sus respectivas cadenas de control. Ejemplos: fprintf (fich, "%s%d%f", nombre, edad, altura);
#include <stdio.h> int main() { FILE *fichero; char nombre[10] = "datos.dat"; unsigned int i; fichero = fopen( nombre, "w" ); printf( "Fichero: %s (para escritura) -> ", nombre ); if( fichero ) printf( "creado (ABIERTO)\n" ); else

Metodologa de la Programacin II

107

Tema 2
{ printf( "Error (NO ABIERTO)\n" ); return 1; } fprintf( fichero, "Esto es un ejemplo de usar la funcion \'fprintf\'\n" ); fprintf( fichero, "\t 2\t 3\t 4\n" ); fprintf( fichero, "x\tx\tx\tx\n\n" ); for( i=1; i<=10; i++ ) fprintf( fichero, "%d\t%d\t%d\t%d\n", i, i*i, i*i*i, i*i*i*i ); fprintf( stdout, "Datos guardados en el fichero: %s\n", nombre ); if( !fclose(fichero) ) printf( "Fichero cerrado\n" ); else { printf( "Error: fichero NO CERRADO\n" ); return 1; } return 0; }

remove Esta funcin borra fisicamente el archivo que se especifique. Devuelve un 0 si todo ha salido correctamente o un -1 si ha ocurrido un error. Su sintaxis es: <valor> = remove (<nombre_archivo>); Donde: valor es el valor que nos dir si ha ocurrido un error al borrar el fichero. nombre_archivo es el nombre del fichero que se desea borrar, o sea, que se refiere a una cadena de caracteres y no al identificador del fichero. Ejemplos: remove ("datos.dat"); strcpy (nombre, "texto.txt"); remove (nombre); #include <stdio.h> int main(void) { char file[80]; /* prompt for file name to delete */ printf("File to delete: "); gets(file); /* delete the file */ 108 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos if (remove(file) == 0) printf("Removed %s.\n",file); else perror("remove"); return 0; } rename Esta funcin renombra un fichero especificado. Devuelve 0 si el cambio de nombre ha sido correcto, y -1 si no lo ha sido. Su sintaxis es: <valor> = rename (<nombre_actual>, <nuevo_nombre>); Donde: valor es el valor que nos dir si ha ocurrido un error al renombrar el fichero. nombre_actual es el nombre actual del fichero que se desea renombrar. Se refiere a una cadena, y no al identificador del fichero. nuevo_nombre es el nuevo nombre que se le desea asignar al fichero. Se refiere a una cadena, y no al identificador del fichero.
#include <stdio.h> int main() { char viejo[18] = "fichero_viejo.tmp", nuevo[18] = "fichero_nuevo.tmp"; printf( "fichero viejo: %s", viejo ); if( rename(viejo, nuevo) == 0 ) printf( ", renombrado: %s\n", nuevo ); else printf( "\nNo pudo ser renombrado\n" ); return 0; }

Ejemplos: strcpy (viejo, "clientes.xxx\0"); strcpy (nuevo, "clientes.dat\0); rename (viejo, nuevo); Ejemplo: // copia.cpp: Copia de ficheros // Uso: copia <fichero_origen> <fichero_destino> #include <iostream.h> #include <stdio.h> Metodologa de la Programacin II 109

Tema 2 int main(int argc, char **argv) { FILE *fe, *fs; unsigned char buffer[2048]; // Buffer de 2 Kbytes int bytesLeidos; if(argc != 3) { cout << "Usar: copia <fichero_origen> <fichero_destino>" << endl; return 1; } fe = fopen(argv[1], "rb"); // Abrir el fichero de entrada en lectura y binario if(!fe) { cout << "El fichero " << argv[1] << " no existe o no puede ser abierto." << endl; return 1; } fs = fopen(argv[2], "wb"); // Crear o sobreescribir el fichero de salida en binario if(!fs) { cout << "El fichero " << argv[2] << " no puede ser creado." << endl; 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; } 2.4 Gestin de un fichero secuencial de estructuras Los componentes o registros de un fichero pueden ser de cualquier tipo de datos, desde tipos bsicos (enteros, float, char, etc...) hasta estructuras de datos. Las operaciones en cualquiera de los casos son siempre iguales. Veamos un ejemplo de un fichero de estructuras: Apertura: ... struct Tcli { char nombre[30]; char direccin[30]; ... }; 110 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos void main() { FILE *f; struct Tcli cliente; char fichero[13]; strcpy (fichero, "cliente.dat\0"); if ((fopen(fichero, "ab") == NULL) { printf ("Error al abrir el fichero\n"); exit (1); } ... } Escritura: fwrite (&cliente, sizeof(cliente), 1, f); Lectura: fread (&cliente, sizeof(cliente), 1, f); Las operaciones bsicas en el mantenimiento o gestin de un fichero secuencial son las siguientes: Comprobacin de la existencia del fichero: Se abrir el fichero en modo lectura, si no existe, se permitir la creacin del mismo, volvindolo a abrir en modo escritura. Altas: Se abrir el fichero en modo aadir. Bajas: Utilizaremos dos ficheros: el maestro y otro auxiliar. El maestro lo abriremos para lectura, y el auxiliar para escritura. Iremos transfiriendo todos los registros del maestro al auxiliar excepto el que queramos borrar. A continuacin, borraremos el maestro, y asignaremos al auxiliar el nombre del maestro. Modificaciones: Procederemos de igual forma que para las bajas, salvo que transferiremos todos los registros al auxiliar, los no modificados y el modificado. Borraremos el maestro y renombraremos el auxiliar para darle el nombre del maestro. Consultas: Se abrir el fichero en modo lectura. Listado por pantalla: Se abrir el fichero en modo lectura. Se ir leyendo informacin mientras no se llegue al final del ficnero. Listado por impresora: Utilizaremos dos ficheros, el maestro y el de impresora. El primero lo abriremos para lectura, y el segundo para escritura, del siguiente modo:

FILE *f, *fimp; f = fopen ("clientes.dat", "r"); fimp = fopen ("prn", "w");

Metodologa de la Programacin II

111

Tema 2 De tal forma que todo lo que escribamos en fimp saldr por impresora. Por tal motivo, debemos pensar que se trata de un fichero de texto, por lo cual, usaremos funciones de escritura en ficheros de texto. 2.5 Acceso directo a ficheros Ya hemos visto como acceder secuencialmente a un fichero, sin embargo tambin se puede hacer de forma directa. Supongamos que tenemos definido un fichero con la siguiente estructura de registro: struct { int codigo; char nomart[31]; float precio; }articulo; Es evidente que la longitud de cada registro es de 37 bytes (2+31+4 bytes). De esta forma, la disposicin de los registros dentro del fichero en disco se realiza en las siguientes posiciones:

El acceso directo consiste en indicar la posicin a la que queremos acceder en bytes. Por ejemplo, para acceder directamente al registro 2, indicaremos que queremos ir al byte 37, contando desde el principio del registro. La orden que posibilita este acceso es la siguiente. fseek

112

Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos <valor> = fseek (<var_fich>, <direccin>, <desde>); Donde: valor es el valor que nos dir si ha ocurrido un error al desplazarnos por el fichero. 0 si todo ha ido bien. var_fich es el puntero al fichero que estamos utilizando. direccin es el nmero de bytes que vamos a desplazarnos. desde es el punto de partida del desplazamiento. Este punto admite tres posibles valores: Referencia SEEK_SET SEEK_CUR SEEK_END Valor Desde 0 1 2 Principio de fichero Posicin actual del fichero Final del fichero

Hay que tener muy en cuenta que no es lo mismo desplazarse 37 bytes desde el principio del fichero (accederemos al segundo registro), que desde el lugar donde nos encontremos (accederemos al siguiente registro) Existe otra funcin que nos devuelve la posicin en la que nos encontramos dentro del fichero #include <stdio.h> long filesize(FILE *stream); int main(void) { FILE *stream; stream = fopen("MYFILE.TXT", "w+"); fprintf(stream, "This is a test"); printf("Filesize of MYFILE.TXT is %ld bytes\n", filesize(stream)); fclose(stream); return 0; } long filesize(FILE *stream) { long curpos, length; curpos = ftell(stream); fseek(stream, 0L, SEEK_END); length = ftell(stream); fseek(stream, curpos, SEEK_SET); Metodologa de la Programacin II 113

Tema 2 return length; } ftell <posicin> = ftell (<var_fich>); Donde: posicin es la variable que contendr la posicin en bytes en la que nos encontramos en ese momento en el fichero. var_fich es la variable declarada como FILE. Esta funcin se suele usar, a parte de para saber la situacin exacta en el fichero, para obtener la longitud del mismo. Veamos un ejemplo: ... if ((f=fopen("articul.dat", "rb")) == NULL) { printf ("Imposible crear fichero\n"); exit (1); } fseek (f, 0, 2); /* tambien puede ser fseek (f, 2, SEEK_END); */ l = ftell(f); printf ("El fichero tiene un tamao de %ld bytes\n", l); printf ("Y un total de %ld registros\n", l/sizeof(reg)); /* donde reg sera la estructura o el dato simple contenido en el fichero */ ... La forma de dar de alta registros es igual que para el acceso secuencial. Se har un recorrido hasta llegar al final del fichero para comprobar que no existe el cdigo del registro a dar de alta, y en ese caso, se procede a hacer fwrite. Abriremos para ello el fichero en modo aadir.
#include <stdio.h> #include <string.h> int main() { char nombre[11] = "datos4.dat", mensaje[81]=""; FILE *fichero; long int comienzo, final; fichero = fopen( nombre, "r" ); printf( "Fichero: %s -> ", nombre ); if( fichero ) printf( "existe (ABIERTO)\n" ); else { printf( "Error (NO ABIERTO)\n" ); return 1; }

114

Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos


if( (comienzo=ftell( fichero )) < 0 ) printf( "ERROR: ftell no ha funcionado\n" ); else printf( "Posicion del fichero: %d\n\n", comienzo ); fseek( fichero, 0L, SEEK_END ); final = ftell( fichero ); fseek( fichero, 0L, SEEK_SET ); fgets( mensaje, 80, fichero ); printf( "Tamao del fichero \"%s\": %d bytes\n", nombre, final-comienzo+1 ); printf( "Mensaje del fichero:\n%s\n", mensaje ); printf( "\nTamao del mensaje (usando strlen): %d\n", strlen(mensaje) ); if( !fclose(fichero) ) printf( "Fichero cerrado\n" ); else { printf( "Error: fichero NO CERRADO\n" ); return 1; } return 0; }

Las bajas consisten en localizar el registro segn un cdigo, y escribir un registro vaco en tal posicin. Para ello tendremos que abrir el fichero en modo lectura/escritura, o sea, "r+". Las modificaciones, de forma similar a las bajas pero escribiendo en el lugar correspondiente el registro modificado que reemplace al actual. Las consultas no son otra cosa que un acceso directo al cdigo indicado. Y por ltimo los listados, que consisten en un recorrido secuencial desde el primer registro hasta el ltimo. Recordaros que la forma de gestin de los ficheros aqu expuesta es la ms bsica que yo conozco, pero que se puede hacer de mil maneras, mejores o peores, pero que no es la nica. Comentamos casi al principio que C no soporta los ficheros indexados, pero puedes crearlos, slo debes pensar en crearos un fichero de ndices y otro maestro y a partir de ah, todo sale solo. 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 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 del Metodologa de la Programacin II 115

Tema 2 principio y sin haber ledo toda la informacin desde el principio 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 apogeo. 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 ese byte, 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 116 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos 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 tama 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: 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 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. 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. 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.cpp: Ejemplo de ficheros de acceso aleatorio. #include <stdio.h> Metodologa de la Programacin II 117

Tema 2 #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"); // Este modo permite leer y escribir if(!fa) fa = fopen("alea.dat", "w+b"); // si el fichero no existe, lo crea. 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 118 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos 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); Metodologa de la Programacin II 119

Tema 2 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"); } 120 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos 2.6 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. Ejemplo: // mezcla.cpp : 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 Mostrar(FILE *fich); void Mezcla(FILE *fich); void Separar(FILE *fich, FILE **aux); bool 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); Metodologa de la Programacin II 121

Tema 2 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]); 122 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos // 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++; Metodologa de la Programacin II 123

Tema 2 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. 2.7 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); 124 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos 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"); // Este modo permite leer y escribir if(!fa) fa = fopen("alea.dat", "w+b"); // si el fichero no existe, lo crea. 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"); Metodologa de la Programacin II 125

Tema 2 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); 126 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos // 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; Metodologa de la Programacin II 127

Tema 2 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); 128 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos 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. 2.8 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; }

Metodologa de la Programacin II

129

Tema 2 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. 130 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos 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]; fa = fopen("indices.dat", "r+b); // Este modo permite leer y escribir if(!fa) fa = fopen("indices.dat", "w+b"); // si el fichero no existe, lo crea. do { Metodologa de la Programacin II 131

Tema 2 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); 132 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos } 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) Metodologa de la Programacin II 133

Tema 2 { 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)) { 134 Dpl. Ing. Carlos Balderrama Vsquez

Almacenamiento Externo de Datos 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]; Metodologa de la Programacin II 135

Tema 2 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.

136

Dpl. Ing. Carlos Balderrama Vsquez