Análisis
y Diseño de Algoritmos
Prof:
Ing. Victor Garro
Asistente:
Marco Elizondo Vargas
PROGRAMACION
EN C++
CAPITULO 10 Manejo de
Archivos
Muy a menudo
necesitamos almacenar cierta cantidad de datos de forma más 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 más económicos, aunque sea a costa de que
sean más lentos.
Durante la
historia de los ordenadores se han usado varios métodos distintos para el
almacenamiento de datos. Al principio se recurrió a cintas de papel perforadas,
después a tarjetas perforadas. A continuación se pasó al soporte magnético,
empezando por grandes rollos de cintas magnéticas abiertas.
Hasta aquí,
todos los sistemas de almacenamiento externo eran secuenciales, es decir, no
permitían acceder al punto exacto donde se guardaba la información sin antes
haber partido desde el principio y sin haber leído toda la información, hasta
el punto donde se encontraba la que estabamos buscando.
Con las cintas
magnéticas empezó lo que con el tiempo sería el acceso aleatorio a los datos.
Se podía reservar parte de la cinta para guardar cierta información sobre la
situación de los datos, y añadir ciertas marcas que hicieran más sencillo
localizarla.
Pero no fué
hasta la aparición de los discos magnéticos cuando ésta técnica llegó a su
sentido más amplio. En los discos es más sencillo acceder a cualquier punto de
la superficie en poco tiempo, ya que se accede al punto de lectura y escritura
usando dos coordenadas físicas. 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 sólo hay una
coordenada física.
Con la invención
y proliferación 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 rápidos, aunque aún siguen
siendo lentos, en comparación 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
sólo tiene una pista de datos grabada en espiral. Sin embargo, este sistema,
combinado con algo de memoria RAM, proporciona un acceso muy próximo al de los
discos duros.
En cuanto al
tipo de acceso, en C y C++ podemos clasificar los archivos según varias
categorías:
1. Dependiendo de la dirección del flujo de datos:
o De entrada: los datos se leen por el programa desde el archivo.
o De salida: los datos se escriben por el programa hacia el archivo.
o De entrada/salida: los datos pueden se escritos o leídos.
2. Dependiendo del tipo de valores permitidos a cada byte:
o De texto: sólo están 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 más allá de un byte con ese valor, aunque el fichero sea más largo.
o Binarios: están 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 mayoría de las veces se hace guardando la longitud del fichero. Cuando queramos almacenar valores enteros, o en coma flotante, o imágenes, etc, deberemos usar este tipo de archivos.
1. Según el tipo de acceso:
o Archivos secuenciales: imitan el modo de acceso de los antiguos ficheros secuenciales almacenados en cintas magnéticas y
o Archivos de acceso aleatorio: permiten acceder a cualquier punto de ellos para realizar lecturas y/o escrituras.
2. Según la longitud de registro:
o Longitud variable: en realidad, en este tipo de archivos no tiene sentido hablar de longitud de registro, podemos considerar cada byte como un registro. También puede suceder que nuestra aplicación conozca el tipo y longitud de cada dato almacenado en el archivo, y lea o escriba los bytes necesarios en cada ocasión. Otro caso es cuando se usa una marca para el final de registro, por ejemplo, en ficheros de texto se usa el carácter de retorno de línea para eso. En estos casos cada registro es de longitud diferente.
o Longitud constante: en estos archivos los datos se almacenan en forma de registro de tamaño contante. En C usaremos estructuras para definir los registros. C dispone de funciones de librería adecuadas para manejar este tipo de ficheros.
o Mixtos: en ocasiones pueden crearse archivos que combinen los dos tipos de registros, por ejemplo, dBASE usa registros de longitud constante, pero añade un registro especial de cabecera al principio para definir, entre otras cosas, el tamaño y el tipo de los registros.
Es posible crear
archivos combinando cada una de estas categorías, por ejemplo: archivos
secuenciales de texto de longitud de registro variable, que son los típicos
archivos de texto. Archivos de acceso aleatorio binarios de longitud de
registro constante, normalmente usados en bases de datos. Y también cualquier
combinación menos corriente, como archivos secuenciales binarios de longitud de
registro constante, etc.
En cuanto a cómo
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
función fopen en C o mediante el método open de fstream en C++.
La función open
usa dos parámetros. 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 dirección del flujo de datos:
· r: sólo lectura. El fichero debe existir.
· w: se abre para escritura, se crea un fichero nuevo o se sobrescribe si ya existe.
· a: añadir, 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+: añadir, 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 añadir otro carácter 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 distinción, y todos los ficheros son
binarios.
En C++ es algo
diferente, el constructor de las clases ifstream, ofstream y fstream admite los
parámetros para abrir el fichero directamente, y también disponemos del método
open, para poder crear el stream sin asociarlo con un fichero concreto y hacer
esa asociación más tarde.
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 definición de
ésta estructura depende del compilador, pero en general mantienen un campo con
la posición actual de lectura/escritura, un buffer para mejorar las
prestaciones de acceso al fichero y algunos campos para uso interno.
Función fopen:
Sintaxis:
FILE *fopen(char *nombre, char *modo);
ésta función
sirve para abrir y crear ficheros en disco. El valor de retorno es un puntero a
una estructura FILE. Los parámetros de entrada son:
1. nombre: una cadena que contiene un nombre de fichero válido, 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:
o r: sólo lectura. El fichero debe existir.
o w: se abre para escritura, se crea un fichero nuevo o se sobreescribe si ya existe.
o a: añadir, se abre para escritura, el cursor se situa al final del fichero. Si el fichero no existe, se crea.
o r+: lectura y escritura. El fichero debe existir.
o w+: lectura y escritura, se crea un fichero nuevo o se sobreescribe si ya existe.
o a+: añadir, lectura y escritura, el cursor se situa al final del fichero. Si el fichero no existe, se crea.
o t: tipo texto, si no se especifica "t" ni "b", se asume por defecto que es "t"
o b: tipo binario.
Función fclose:
Sintaxis:
int fclose(FILE *fichero);
Es importante
cerrar los ficheros abiertos antes de abandonar la aplicación. Esta función
sirve para eso. Cerrar un fichero almacena los datos que aún están en el buffer
de memoria, y actualiza algunos datos de la cabecera del fichero que mantiene
el sistema operativo. Además 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
algún error, el valor de retorno es la constante EOF. El parámetro es un
puntero a la estructura FILE del fichero que queremos cerrar.
Función fgetc:
Sintaxis:
int fgetc(FILE *fichero);
Esta función lee
un carácter desde un fichero.
El valor de
retorno es el carácter leído como un unsigned char convertido a int.
Si no hay ningún carácter disponible, el valor de retorno es EOF. El parámetro
es un puntero a una estructura FILE del fichero del que se hará la lectura.
Función fputc:
Sintaxis:
int fputc(int caracter, FILE
*fichero);
Esta función
escribe un carácter a un fichero.
El valor de
retorno es el carácter escrito, si la operación fue completada con éxito, en
caso contrario será EOF. Los parámetros de entrada son el carácter a escribir,
convertido a int y un puntero a una estructura FILE del fichero en el
que se hará la escritura.
Función feof:
Sintaxis:
int
feof(FILE *fichero);
Esta función 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
función suele usarse como prueba para verificar si se ha alcanzado o no ese
punto.
El valor de
retorno es distinto de cero sólo si no se ha alcanzado el fin de fichero. El
parámetro es un puntero a la estructura FILE del fichero que queremos
verificar.
Función rewind:
Sintaxis:
void
rewind(FILE *fichero)
Es una función heredada
de los tiempos de las cintas magnéticas. Literalmente significa
"rebobinar", y hace referencia a que para volver al principio de un
archivo almacenado en cinta, había que rebobinarla. Eso es lo que hace ésta
función, sitúa el cursor de lectura/escritura al principio del archivo.
El parámetro 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;
}
Función fgets:
Sintaxis:
char
*fgets(char *cadena, int n, FILE *fichero);
Esta función
está diseñada para leer cadenas de caracteres. Leerá hasta n-1 caracteres o
hasta que lea un retorno de línea. En este último caso, el carácter de retorno de
línea también es leído.
El parámetro 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 leída, si se leyó con éxito, y es NULL si se
detecta el final del fichero o si hay un error. Los parámetros son: la cadena a
leer, el número de caracteres máximo a leer y un puntero a una estructura FILE
del fichero del que se leerá.
Función fputs:
Sintaxis:
int fputs(const
char *cadena, FILE *stream);
La función fputs
escribe una cadena en un fichero. No se añade el carácter de retorno de línea
ni el carácter nulo final.
El valor de
retorno es un número no negativo o EOF en caso de error. Los parámetros de
entrada son la cadena a escribir y un puntero a la estructura FILE del fichero
donde se realizará la escritura.
Función fread:
Sintaxis:
size_t fread(void *puntero, size_t
tamaño, size_t nregistros, FILE *fichero);
Esta función
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 dirección de memoria determinada. El usuario es responsable de asegurarse
de que hay espacio suficiente para contener la información leída.
El valor de
retorno es el número de registros leídos, no el número de bytes. Los
parámetros son: un puntero a la zona de memoria donde se almacenarán los datos
leídos, el tamaño de cada registro, el número de registros a leer y un puntero
a la estructura FILE del fichero del que se hará la lectura.
Función fwrite:
Sintaxis:
size_t fwrite(void *puntero, size_t
tamaño, size_t nregistros, FILE *fichero);
Esta función
también 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 dirección de memoria determinada.
El valor de
retorno es el número de registros escritos, no el número de bytes. Los
parámetros son: un puntero a la zona de memoria donde se almacenarán los datos
leídos, el tamaño de cada registro, el número 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;
}
Función fprintf:
Sintaxis:
int fprintf(FILE *fichero, const char *formato,
...);
La función
fprintf funciona igual que printf en
cuanto a parámetros, pero la salida se dirige a un fichero en lugar de a la
pantalla.
Función fscanf:
Sintaxis:
int fscanf(FILE *fichero, const char
*formato, ...);
La función
fscanf funciona igual que scanf en
cuanto a parámetros, pero la entrada se toma de un fichero en lugar del
teclado.
Función fflush:
Sintaxis:
int
fflush(FILE *fichero);
Esta función
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
través del buffer, y sólo cuando el buffer se llena se realiza la escritura en
el disco y se vacía el buffer. En ocasiones nos hace falta vaciar ese buffer de
un modo manual, para eso sirve ésta función.
El valor de
retorno es cero si la función se ejecutó con éxito, y EOF si hubo algún error.
El parámetro 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.
Función fseek:
Sintaxis:
int fseek(FILE *fichero, long int
desplazamiento, int origen);
Esta función
sirve para situar el cursor del fichero para leer o escribir en el lugar
deseado.
El valor de
retorno es cero si la función tuvo éxito, y un valor distinto de cero si hubo
algún error.
Los parámetros
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 parámetro 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 posición actual del cursor.
3. SEEK_END el desplazamiento se cuenta desde el final del fichero.
Función ftell:
Sintaxis:
long
int ftell(FILE *fichero);
La función ftell
sirve para averiguar la posición actual del cursor de lectura/excritura de un
fichero.
El valor de
retorno será esa posición, o -1 si hay algún error.
El parámetro de
entrada es un puntero a una estructura FILE del fichero del que queremos leer
la posición del cursor de lectura/escritura.
Ejemplos para Guardar Archivos en C++
Por lo general
los métodos de varios ejemplos del disco son los métodos para guardar los datos
en C así que los siguientes ejemplos son forma en C++
El siguiente programa declara el fichero “F.dat” para entrada-salida, graba en
dicho fichero el valor 1234.86 en binario y después los veinte primeros enteros.
Posteriormente, lee el fichero visualizando su información en la salida estándar
(el monitor).
#include
<fstream.h>
#include
<iostream.h>
int
main(){
float
R=1234.86;
int i,N;
fstream
fichbin("F.dat",ios::binary | ios::out); // Apertura como salida
fichbin.write(&R,sizeof(float));
for
(i=1;i<=20;i++)
fichbin.write(&i,sizeof(int));
fichbin.close();
fichbin.open("F.dat",ios::binary
| ios::in); // Apertura
fichbin.read(&R,sizeof(float));
cout
<<endl << "R= " << R << endl;
for
(i=1;i<=20;i++){
fichbin.read(&N,sizeof(float));
cout
<<endl << i << "= " << N << endl;
}
}
// Fin del main
El siguiente programa almacena en un fichero los 10 primeros enteros, luego
muestra por pantalla el quinto entero (o sea el 5), posteriormente lo reemplaza
por el valor 100, y al final visualiza en el monitor el contenido del fichero.
#include
<fstream.h>
#include
<iostream.h>
int
main(){
int i,N;
fstream
fichbin("ejemplo11.dat",ios::binary | ios::in | ios::out);
for
(i=1;i<=10;i++)
fichbin.write(&i,sizeof(int));
// Almacena los 10 primeros enteros
fichbin.seekp(4*sizeof(int)); // se
posiciona al principio del quinto/ entero
fichbin.read(&N,sizeof(float)); // Lee
dicho entero
cout <<endl << "Quinto=
" << N << endl; // visualiza el valor 5
fichbin.seekp(4*sizeof(int)); // se
posiciona de nuevo en el quinto entero
// pues el cursor había avanzado.
i=100;
fichbin.write(&i,sizeof(int)); //
Modifica el valor 5 por el valor 100;
fichbin.seekp(0*sizeof(int)); // se
posiciona de nuevo al principio del fichero
for
(i=1;i<=10;i++){
fichbin.read(&N,sizeof(float));
cout <<endl << i <<
"= " << N << endl; // Se visualiza el contenido
}
fichbin.close();
} // Fin del main