viernes, 8 de abril de 2011

Capitulo 12. Archivos

Fuente:
Programación en C++ / Principios y Aplicaciones (resumen)
Carlos Romero Shollande

1.        Introducción

Muchas veces necesitamos almacenar cierta cantidad de datos de forma permanente. La memoria del ordenador es volátil, escasa y cara. De modo que cuando necesitamos guardar nuestros datos durante cierto tiempo tenemos que recurrir a sistemas de almacenamiento externo.
Al principio de la historia de los ordenadores los sistemas de almacenamiento externo eran secuenciales (cintas de papel perforado, tarjetas perforadas y cintas magnéticas), es decir, no permitían acceder al punto exacto donde se guardaba la información requerida, se tenía que partir desde el principio y recorrer el dispositivo hasta llegar al punto donde se encontraba lo que buscábamos.
Con la aparición de los discos magnéticos y posterior empleo de discos duros empezó la técnica de los ficheros de acceso aleatorio, que permiten acceder a cualquier dato almacenado en un fichero en poco tiempo. Con estos 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.
En cuanto al tipo de acceso, en C++ podemos clasificar los archivos según varias categorías:

a)        Dependiendo de la dirección 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 leídos.

b)        Dependiendo del tipo de valores permitidos a cada byte:

-       De texto: están compuestos únicamente por textos, sólo caracteres.
-       Binarios: contienen información de cualquier tipo, codificada en forma binaria para el propósito de almacenamiento y procesamiento en ordenadores.

c)        Según el tipo de acceso:

-       Archivos secuenciales: imitan el modo de acceso de los antiguos ficheros secuenciales almacenados en cintas magnéticas y
-       Archivos de acceso aleatorio: permiten acceder a cualquier punto de ellos para realizar lecturas y/o escrituras.

d)        Según la longitud de registro:

-       Longitud variable: en estos archivos cada registro es de longitud diferente; en realidad, en este tipo de archivos no tiene sentido hablar de longitud de registro, podemos considerar cada byte como un registro.
-       Longitud constante: en estos archivos los datos se almacenan en forma de registro de tamaño constante.
-       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 registros.

Es posible crear archivos combinando cada una de estas categorías, por ejemplo: archivos secuenciales binarios de longitud de registro constante.

2.        Corrientes y Archivos

El sistema de E/S del C++ proporciona una interfaz, que es independiente del dispositivo real al que se accede, es decir, el sistema de E/S de C++ proporciona un nivel de abstracción entre el programador y el dispositivo que se usa. Esta abstracción se llama corriente y el dispositivo real se llama archivo.

2.2.  Corrientes

El sistema de almacenamiento temporal en archivo esta diseñado para trabajar con una amplia variedad de dispositivos, incluyendo dispositivos de disco, USB, discos duros, etc. Incluso aunque cada dispositivo sea diferente, el sistema de almacenamiento temporal en archivo los transforma en un dispositivo lógico llamado corriente.
Todas las corrientes son similares en su comportamiento, debido a que las corrientes son muy independientes del dispositivo, las mismas funciones que escriben en un archivo de disco pueden hacerlo en consola. Hay dos tipos de corrientes:

a)        Corrientes de texto: es una secuencia de caracteres organizados en líneas terminadas por un carácter de nueva línea (en el estándar ANSI es opcional).
b)        Corrientes binarias: es una secuencia de bytes que tienen una correspondencia uno a uno con un dispositivo externo. Así que no tendrá lugar ninguna traducción de caracteres. Además, el número de bytes escritos (leídos) será el mismo que los encontrados en el dispositivo externo.

2.3.  Archivos

En C++, un archivo es un concepto lógico que puede aplicarse a muchas cosas desde archivos de disco hasta terminales. Una corriente esta asociada con un archivo específico realizando una operación de apertura. Una vez que esta abierto (open) el archivo, se pueden intercambiar la información entre él y el programa.
Si el archivo puede soportar acceso directo (peticiones de posición), entonces abriendo ese archivo también se inicializa el indicador de posición de archivo al comienzo del archivo. Como cada carácter se lee y escribe a un archivo, el indicador de posición se incrementa, asegurando así la progresión a través del archivo.
Se separa una corriente de un archivo específico a través de la operación close. En archivo abierto para salir, el cierre provoca que los contenidos, si hay, de su corriente asociada se escriba en el dispositivo externo.

3.        Sistema de Memoria Intermedia de E/S

El sistema de memoria intermedia de E/S se compone de diversas funciones relacionadas. Estas funciones requieren que se incluya el archivo de cabecera stdio.h en cualquier programa en el que se usen.

3.2.  Declaración de Variables Puntero de Archivo

Sintaxis      : FILE *corriente
Propósito   : Identifica un archivo especifico de disco y se usa por su corriente asociada para dirigir cada una de las funciones de memoria intermedia de E/S al lugar donde se realizan las operaciones.
Ejemplo      : FILE *fichero;

Un puntero de archivo es un puntero a la información que define varios aspectos del archivo, incluyendo su nombre, estado y posición actual. Un puntero de archivo es una variable de tipo FILE, que se define en stdio.h. Con objeto de leer o escribir archivos, el programa necesita utilizar punteros de archivo.

3.3.  Apertura de Archivos

Sintaxis      : corriente = fopen(char *nombre, char *modo);
Propósito   : Abrir una corriente para enlazar un archivo con una corriente y retornar el puntero de archivo asociado con un archivo
Ejemplo      : fichero = fopen(“d:ficha.dat”,”w”);

La función fopen() devuelve la dirección cero o NULL si la operación de apertura falla (por ejemplo un disco lleno o protegido contra escritura antes de comenzar a escribir en él), o un valor (dirección) distinto al correcto.
Si la operación es correcta, a partir de este instante el archivo queda referido por el apuntador corriente hasta el momento en que se cierra.
Los parámetros de entrada son:

a)    nombre: es una cadena de caracteres que contiene un nombre de archivo válido para el sistema operativo y también pueden incluir la especificación del camino completo.
b)    modo: es una cadena de caracteres que contiene el estado deseado de apertura y especifica el tipo de fichero que se abrirá o se creará uno nuevo y el tipo de dato que puede contener: texto (t) o binario (b).

Tabla 1. Valores permitidos de modo

Modo
Significado
“w”
“r”
“a”

“wb”
“rb”
“ab”
“w+”
“r+”
“a+”

“w+b”
“r+b”
“a+b”
“wt”
“rt”
“at”
“w+t”
“r+t”
“a+t”
Crear un nuevo archivo de texto para escritura (o se sobrescribe si ya existe)
Abrir un archivo de texto para lectura (el archivo debe existir)
Añadir a un archivo de texto (se abre para escritura y el cursor se sitúa al final del archivo. Si el fichero no existe, se crea)
Crear un archivo binario para escritura
Abrir un archivo binario para lectura
Añadir a un archivo binario
Crear un archivo nuevo para leer/escribir (o se sobrescribe si ya existe)
Abrir un archivo para leer/escribir (el archivo debe existir)
Añadir a un archivo para leer/escribir (el cursor se sitúa al final del fichero, Si el fichero no existe, se crea
Crear un archivo binario para leer/escribir
Abrir un archivo binario para leer/escribir
Añadir a un archivo binario para leer/escribir
Crear un archivo de texto para escribir
Abrir un archivo de texto para leer
Añadir a un archivo de texto
Crear un archivo de texto para leer/escribir
Abrir un archivo de texto para leer/escribir
Añadir o crear a un archivo de texto para leer/escribir

A la cadena de modo se puede añadir otro carácter como:

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

3.4.  Cierre de Archivos

Sintaxis      : int fclose(FILE *corriente);
Propósito   : Cerrar una corriente que fue abierta por una llamada a fopen() y antes de abandonar el programa, finalizar su vínculo con nombre
Ejemplo      : fclose(fichero);

Si fclose() devuelve un valor cero (0) significa que la operación de cierre se ha realizado con éxito. Si ha habido algún error, el valor de retorno es la constante EOF. Generalmente, el único momento en que fallará es, o cuando se quita un diskette prematuramente  de la unidad o cuando no hay más espacio en el diskette.
Al cerrar un archivo almacena los datos que aún están en el buffer de memoria, y actualiza algunos datos de la cabecera del archivo que mantiene el sistema operativo. Además permite que otros programas puedan abrir el archivo para su uso. Muy a menudo, los archivos no pueden ser compartidos por varios programas.

4.        Escritura de Archivos

Para escribir en un archivo (abierto en los modos “w” o “a”) se pueden utilizar las funciones:

4.2.  Escritura de Datos con Formato

Sintaxis      : int fprintf(FILE *corriente, const char *formato, lista_datos);
Propósito   : Escribir datos con formato especificado en una cadena de control, pero la salida es a través de una corriente. Devuelve un valor igual al número de datos almacenados en caso de éxito o el valor EOF en caso de error
Ejemplo      : fprintf(fichero, “%c”, caracter);

4.3.  Escritura de un Carácter

Sintaxis      : int fputc(int caracter, FILE *corriente);
Propósito   : Escribir un carácter en una corriente que se abrió para escritura. Si la operación fue completada con éxito devuelve un valor igual al carácter escrito. En caso contrario devuelve EOF..
Ejemplo      : fputc(carácter, fichero);

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.

4.4.  Escritura de Cadena de Caracteres

Sintaxis      : int fputs(const char *cadena, FILE *corriente);
Propósito   : Escribir una cadena (o puntero a caracteres) en una corriente hasta encontrar su final (“\0”). Devuelve un valor igual al último carácter escrito o el valor EOF en caso de error.
Ejemplo      : fputs(cadena, fichero);

Los parámetros de entrada son la cadena a escribir y un puntero a la estructura FILE del fichero donde se realizará la escritura

4.5.  Escritura de Datos en un Archivo

Sintaxis      : int fwrite(void *puntero, int longitud, int registros, FILE *corriente);
Propósito   : Escribir hacia una corriente uno o más registros de una longitud constante almacenados a partir de una dirección de memoria determinada. Devuelve un valor igual al número de registros escritos (no el número de bytes) o EOF en caso de error.
Ejemplo     : fwrite(dirección, longitud, número, fichero);

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.

5.        Lectura en Archivos

Las funciones de lectura (archivos abiertos en modo r) son:

5.2.  Lectura de Datos con Formato

Sintaxis      : int fscanf(FILE *corriente, const char *formato, lista_direcciones);
Propósito   : Leer datos con formato especificado a través de una corriente. Devuelve un valor igual al número de datos leídos o el valor EOF en caso de error
Ejemplo      : fscanf(fichero, “%c”, &caracter);

5.3.  Lectura de Caracteres

Sintaxis      : int fgetc(FILE *corriente);
Propósito   : Leer un carácter desde una corriente abierta en modo lectura. Devuelve un valor igual al carácter leído como un unsigned char convertido a int. Si no hay ningún carácter disponible o hay error, el valor de retorno es un EOF (fin del archivo)
Ejemplo      : fgetc(fichero);

5.4.  Lectura de Cadenas de Caracteres

Sintaxis      : char fgets(char cadena, int n, FILE *corriente);
Propósito   : Leer una cadena de caracteres hasta un máximo de n-1 caracteres o hasta encontrar un cambio de línea (\n) de una corriente. El cambio de línea también es leído y añade al final el carácter (\0). Devuelve un puntero a la cadena leída en caso de éxito o NULL si se detecta el final del archivo o si hay un error.
Ejemplo      : fgets(cadena, longitud, fichero);

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á. El parámetro n nos permite limitar la lectura para evitar desbordar el espacio disponible en la cadena.

5.5.  Lectura de Datos de un Archivo

Sintaxis      : int fread(void *puntero, int longitud, int registros, FILE *corriente);
Propósito   : Leer desde una corriente uno o más registros de una longitud constante, y los almacena en una dirección de memoria especificada. Devuelve un valor menor o igual al número de datos leídos (no el número de bytes) y si esta al final del archivo devuelve 0.
Ejemplo      : fread(dirección, tamaño, número, fichero);

Los parámetros son: un puntero a la zona de memoria donde se almacenarán los datos leídos, la longitud 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. El usuario es responsable de asegurarse de que haya espacio suficiente para contener la información leída.

6.        Otras Funciones de Alto Nivel

Las siguientes funciones también son utilizadas en C++:

6.2.  Final de una Corriente

Sintaxis      : int feof(FILE *corriente);
Propósito   : Comprobar si se ha llegado al final de una corriente, en una lectura que efectuamos en ésta. El valor de retorno es distinto de cero sólo si no se ha alcanzado el fin del archivo. Se puede aplicar este método tanto para los archivos de texto como a los binarios.
Ejemplo      : feof(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 del archivo.

6.3.  Error en una Operación de Archivo

Sintaxis      : int ferror(FILE *corriente);
Propósito  : Determinar si una operación con archivos produce un error. Devuelve verdad si ha ocurrido un error durante la última operación de archivo y devuelve falso si tuvo éxito la operación.
Ejemplo      : ferror(fichero);

6.4.  Localizador de Posición de Archivo

Sintaxis      : void rewind(FILE *corriente);
Propósito   : Ubicar el cursor (puntero) de lectura/escritura al principio de la corriente especificada como argumento. Si no tiene éxito devuelve un valor falso (≠0) y en caso contrario 0.
Ejemplo      : rewind(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.

6.5.       Grabación de Datos de un Buffer

Sintaxis      : int fflush(FILE *corriente);
Propósito   : Forzar la grabación de los datos acumulados en el buffer de salida del archivo asociado a él. 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
Ejemplo      : fflush(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.

6.6.       Longitud de un Archivo

Sintaxis      : long filelenght(int nombre_archivo);
Propósito    : Devolver la longitud en bytes del archivo referenciado en el argumento
Ejemplo      : filelenght(fichero);

6.7.       Crear Archivo Temporal

Sintaxis      : FILE *tmpfile(void);
Propósito    : Crear un archivo de índole temporal

6.8.       Referencia del Contenido de una Corriente

Sintaxis      : int fseek(FILE *corriente, long int desplazamiento, int origen);
Propósito  : Ubicar el cursor del archivo para leer o escribir, que referencia el contenido de la corriente especificada, en la posición marcada por los argumentos de llamada. El valor de retorno es cero si la función tuvo éxito, y un valor distinto de cero si hubo algún error Se usa en las E/S de acceso directo.
Ejemplo      : fseek(fichero, 128, 0);

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:

-       SEEK_SET o 0, el desplazamiento se cuenta desde el principio del fichero. El primer byte del archivo tiene un desplazamiento cero.
-       SEEK_CUR o 1, el desplazamiento se cuenta desde la posición actual del cursor.
-       SEEK_END o 2, el desplazamiento se cuenta desde el final del archivo.

El desplazamiento es el número de bytes desde el origen hasta alcanzar la nueva posición.

6.9.       Devolver el Valor del Puntero

Sintaxis      : long int ftell(FILE *corriente);
Propósito   : Averiguar la posición actual del cursor de lectura/escritura de un archivo. 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.

6.10.   Borrar el Directorio

Sintaxis      : long remove(char *nombre);
Propósito   : Borrar del directorio  el archivo especificado en el argumento de llamada. Devuelve 0 si tiene éxito en su misión y -1 en caso contrario

6.11.   Cambiar Nombre del Archivo

Sintaxis      : int rename(char *anterior_nombre, char *nuevo_nombre);
Propósito   : Cambiar el nombre del archivo pasado como primer argumento (anterior nombre), por el pasado como segundo argumento (nuevo nombre). Si rename() no puede cumplir su objetivo devuelve un valor -1, en caso contrario un 0.

6.12.   Relación de un Buffer con un Archivo

Sintaxis      : void setbuf(FILE *corriente, char *buffer);
Propósito  : Relacionar un buffer con un archivo, en orden a utilizarlo para las operaciones de lectura y escritura en el archivo.

Ejemplo:
Mostrar un menú que presente tres opciones: (1) crear, (2) leer y (3) agregar. Estas operaciones se realizaran sobre un archivo temporal usando caracteres.

Codificación:
#include <iostream.h>       // Opciones para manejo de archivos temporales de caracteres
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
main() {
int y;
char caracter, p;
FILE *fichero;
clrscr();
do  {
cout<<"MENU\n\n";
cout<<"1. CREAR\n";
cout<<"2. LEER\n";
cout<<"3. AGREGAR\n";
cout<<"4. FINALIZAR\n\n";
cout<<"ELEGIR OPCION:  "; cin>>y;
switch(y) {
case 1: {
fichero=fopen("d:archi-1.dat","w+t");
do  {
cout<<"\nIngrese un caracter: ";
caracter=getche();
fprintf(fichero,"%c",caracter);
cout<<"\nDesea continuar ingresando caracteres (S/N): ";
p=getche();
}
while(toupper(p)=='S');
fclose(fichero);
break;
}
case 2: {
fichero=fopen("d:archi-1.dat", "r+t");
while(!feof(fichero))  {
fscanf(fichero,"%c",&caracter);
cout<<caracter;             }
fclose(fichero);
break;
}
case 3:  {
fichero=fopen("d:archi-1.dat", "a+t");
do  {
cout<<"\nAgregar un caracter: ";
caracter=getche();
fprintf(fichero,"%c",caracter);
cout<<"\nDesea continuar agregando caracteres (S/N): ";
p=getche();
}
while(toupper(p)=='S');
fclose(fichero);
break;
}
default: goto bb;
}
while(toupper(p)=='S');
cout<<"\n\nDesea continuar con el programa (S/N): ";
p=getche();
clrscr();
}
while(toupper(p)=='S');
bb:
clrscr();
cout<<"\nFinal del programa";
getch();
}

No hay comentarios:

Publicar un comentario