Entrada y Salida de Datos. Lectura de archivos.


Son innumerables los casos en los que son necesarios guardar datos entre ejecuciones de un programa para poder ser recuperados en futuras sesiones. Los archivos de datos permiten almacenar información de cualquier tipo de modo permanente para ser accedida o alterada cuando sea necesario.


En C existe un conjunto extenso de funciones de biblioteca para crear y procesar archivos de datos. Los archivos secuenciales de datos se pueden dividir en dos categorías. En la primera categoría están los archivos que contienen caracteres consecutivos. Estos caracteres se pueden interpretar como datos individuales, como componentes de una cadena o como números. La segunda categoría es a menudo denominada archivos sin formato y organiza los datos en bloques contiguos de información. Estos bloques representan estructuras de datos más complejas como arrays y estructuras ( struct ).


Cuando se trabaja con archivos secuenciales de datos, el primer paso es establecer un área de buffer, donde la información se almacena temporalmente mientras se está transfiriendo entre la memoria y el archivo de datos. Este área de buffer permite leer y escribir información del archivo más rápidamente de lo que sería posible de otra manera. El área de buffer se establece escribiendo:


FILE *ptvar;


donde FILE es un tipo de estructura que establece el área de buffer y ptvar la variable puntero ( puntero a archivo ) que indica el comienzo de este área. El tipo de estructura FILE está definido en stdio.h. Aquí, a ptvar se le conoce como un flujo lo cual es un dispositivo lógico resultado de la transformación de un archivo con buffer. Esto crea un nivel abstracción que nos permite trabajar de igual forma con cualquier dispositivos aunque éstos sean diferentes.


Al principio de la ejecución de una programa se abren cinco flujos de texto predefinidos. Se trata de stdin, stdout, stderr, stdaux y stdprn, y se refieren a los dispositivos de E/S estándar conectados al sistema para teclado, pantalla, errores por pantalla, primer puerto serie e Impresora respectivamente. Los dos últimos son específicos de Turbo C por lo que pueden no ser transportables a otros compiladores.


El sistema de archivos ANSI C se compone de varias funciones interrelacionadas. Las más comunes son:


fopen(): Abre un flujo.

fclose(): Cierra un flujo.

putc(): Escribe una carácter en un flujo.

getc(): Lee un carácter de un flujo.

fseek(): Busca un byte específico en un flujo.

fprintf(): Hace lo mismo en flujos que printf() en consola.

fscanf(): Hace lo mismo en flujos que scanf() en consola.

feof(): Devuelve cierto si ha llegado al final del archivo.

rewind(): Coloca el localizador de posición del archivo al principio del mismo.

remove(): Elimina un archivo.


Un archivo de datos debe ser abierto antes de ser creado o procesado. Esto asocia el nombre del archivo con el área de buffer. También se especifica cómo se va a usar el archivo.


Para abrir un archivo se usa la función fopen():


ptvar = fopen ( nombre_archivo, tipo_archivo);


donde nombre_archivo y tipo_archivo son cadenas. Los tipos de archivo y su significado son:


"r": Abrir un archivo existente sólo para lectura.

"w": Abrir un nuevo archivo sólo para escritura. Si ya existe, será destruido y creado uno nuevo en su lugar.

"a": Abrir un archivo para añadir. Si no existe, se crea uno nuevo.

"r+": Abrir un archivo existente para lectura y escritura.

"w+": Abrir un archivo nuevo para lectura y escritura. Si ya existe, será destruido y creado uno nuevo en su lugar.

"a+": Abrir un archivo existente para leer y añadir. Si no existe, se crea uno nuevo.


La función fopen() retorna un puntero al principio del área de buffer asociada con el archivo. Se retorna un valor NULL si no se puede abrir el archivo.


Un archivo de datos debe cerrarse al final del programa o cuando ya no sea necesario mantenerlo mas tiempo abierto. Esto se realiza a través de:


fclose(ptvar);


Un archivo secuencial de datos puede crearse de dos formas distintas. Una es crear el archivo directamente, usando un editor. La otra es escribir un programa que introduzca información en la computadora y la escriba en un archivo. Los archivos sin formato sólo pueden crearse mediante programas.


Cuando se crea un archivo con un programa, lo habitual es introducir la información desde el teclado y escribirla en el archivo.


Si el archivo consta de caracteres individuales, se pueden usar la funciones getchar() ( para obtener caracteres de teclado ) y putc() ( para escribir caracteres en un archivo ). El uso de putc() es el siguiente:


putc(variable_de_tipo_caracter,puntero_al_area_de_buffer);


Un archivo creado de esta manera puede ser visualizado de distintas formas: usando una orden del sistema operativo tal como type, usando un editor o escribiendo un programa que lea el contenido y lo muestre. Para ello se pueden utilizar las funciones getc() ( lee caracteres de un archivo ) y putchar() ( para escribir caracteres por pantalla ). El uso de getc() es:


variable_de_tipo_caracter=getc (puntero_al_area_de_buffer);


Los archivos de datos que contienen sólo cadenas de caracteres pueden crearse y leerse más fácilmente con programas que utilizan funciones de biblioteca especialmente orientadas para cadenas: fgets() y fputs().


Muchos archivos de datos contienen estructuras de datos más complicadas ( como las struct ) que incluyen combinaciones de información, caracteres y números. Tales archivos se pueden procesar usando las funciones fscanf() y fprintf():


fprintf(puntero_area_buffer,cadena_control,argto1,..,argton);


fscanf(puntero_area_buffer,cadena_control,&argto1,..,&argton);


Una vez creado un archivo de datos surge la pregunta de cómo detectar una condición de fin de archivo. La función feof() sirve para este propósito ( válida para un archivo secuencial con o sin formato ). Esta función devuelve un valor distinto de cero ( cierto ) si detecta una condición de fin de archivo y un valor cero ( falso ) si no se detecta.


Para actualizar los registros dentro de un archivo de datos hay varios enfoques. Uno de ellos consiste en leer cada registro del archivo, actualizarlo y escribirlo en el mismo archivo. Sin embargo es difícil leer y escribir datos con formato al mismo archivo sin alterar la ordenación de los elementos dentro del archivo. Otro enfoque es trabajar con dos archivos diferentes: un archivo antiguo ( la fuente ) y otro nuevo. Se lee cada registro del archivo antiguo, se actualiza y se escribe en el archivo nuevo.


Algunas aplicaciones implican el uso de archivos para almacenar bloques de datos, donde cada bloque consiste en un número fijo de bytes contiguos. Cada bloque representará generalmente una estructura de datos compleja, como una struct o un array. Para estas aplicaciones sería deseable leer o escribir el bloque entero del archivo de datos en vez de leer o escribir separadamente las componentes individuales de cada bloque. Las funciones fread() y fwrite() deben usarse en estas situaciones. A estas funciones se las conoce como funciones de lectura y escritura sin formato. Igualmente se hace referencia a estos archivos de datos como archivos de datos sin formato.


Cada una de estas funciones necesita cuatro argumentos:


- la dirección del bloque de datos

- el tamaño del bloque de datos

- el número de bloques a transferir

- el puntero a un archivo secuencial


Ejemplo:


fwrite(&cliente, sizeof(struct ejemplo), 1, ptvar);

fread(&cliente, sizeof(struct ejemplo), 1, ptvar);


donde cliente es una variable estructura de tipo “struct ejemplo” y ptvar un puntero a archivo secuencial.


Utilizando el sistema de E/S con buffer se pueden realizar operaciones de lectura y escritura directa con la ayuda de fseek(), que sitúa el indicador de posición del archivo. Su prototipo es:


int fseek(FILE *pa, long num_bytes, int origen );


donde pa es un puntero a archivo devuelto por una llamada fopen(); num_bytes es un entero largo que representa el número de bytes a partir del “origen” que supondrán la nueva posición y origen es una de las siguientes macros definidas en stdio.h:


SEEK_SET Principio del archivo ( valor de cero )

SEEK_CUR Posición actual ( valor de uno )

SEEK_END Final del archivo ( valor de dos )


Veamos un ejemplo donde pondremos en práctica varios de estos conceptos.



/* Programa de acceso a archivos; escritura, agregado y lectura de líneas */


#include <stdio.h>

#define MAXLINEA 100


int main() {

char n_arch[] = "Archivo.dat"; /* nombre archivo de datos */

char n_prog[] = "Archivo.c"; /* nombre del programa, para error */


char mensaje[] = "Esta es una linea de prueba.\n";

char linea[MAXLINEA]; /* para lectura */


FILE *pa; /* puntero al archivo */

int i = 0;


printf("\n\nAcceso a archivos.\n");


/* reescribe o crea el archivo, agrega una línea */


if ((pa = fopen(n_arch, "w")) == NULL) {

printf("%s: no se puede abrir archivo %s", n_prog, n_arch);

return 1; /* error, no pudo abrir el archivo */

} else {

fputs(mensaje, pa); /* graba línea en archivo */

fclose(pa);

printf("Grabó línea en archivo.\n");

}


/* lectura del archivo, una línea */

pa = fopen(n_arch, "r");

fgets(linea, MAXLINEA, pa);

fclose(pa);

printf("Leyó línea: %s", linea);


/* agrega una línea al archivo */

pa = fopen(n_arch, "a");

strcpy(linea, "Esta es una línea agregada al archivo.\n");

fputs(linea, pa);

fclose(pa);

printf("Agregó l¡nea al archivo.\n");


/* lee líneas del archivo hasta terminar */

printf("Lectura de varias líneas:\n");

pa = fopen(n_arch, "r");

while ( fgets(linea, MAXLINEA, pa) != NULL ) {

printf("Línea %i: %s", ++i, linea);

}

fclose(pa);


printf("Fin de pruebas de acceso a archivos.\n");

return 0;

}



Otro ejemplo:


#include<stdio.h>


void main(void) {

char ch;

FILE *fp;

// Entrada desde Archivo en DISCO

printf("Entrada desde archivo.\n");

if (!(fp = fopen ("Archivo.c", "r"))) {

printf("No se puede abrir Archivo.c\n");

} else {

fflush(fp);

while ((ch != EOF)) {

ch = getc(fp);

printf("%c", ch);

}

fclose(fp);

}

}