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 como entrada

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

Free Web Hosting