16 de enero de 2017

Archivos de texto.

   En C los archivos están representados por flujos (streams) de bytes. Por esta razón, cuando un archivo se abre, se dice que le es asociado un flujo.

   C no impone o establece algún tipo de estructura específica para los archivos, la estructura asociada a cada archivo es responsabilidad del programador.

Archivos de acceso secuencial.
Un archivo de acceso secuencial es habitualmente un archivo de texto en el que los datos se van almacenando uno detrás de otro sin tomar en consideración su tipo de dato. El acceso a los datos en este tipo de archivos se realiza normalmente de manera secuencial, es decir, no se accede de manera directa a algún dato específico dentro del archivo sin antes haber pasado por todos los datos anteriores a él.

   Los Ejemplos 9.1 y 9.2 muestran el uso de archivos de acceso secuencial para almacenamiento y lectura de datos respectivamente, y se describen en las secciones siguientes.

Almacenamiento de datos.
   En la entrada referente a Arreglos de Estructuras se trabajó con un programa que administra un directorio de contactos, en esta entrada se retomará el planteamiento utilizado en el Ejemplo 8.4 para almacenar los datos del directorio de contactos en un archivo.

   El Ejemplo 9.1 muestra la forma de utilizar un archivo de acceso secuencial para el almacenamiento de datos. La explicación del ejemplo se centrará en la función guardaDirectorio (líneas 38 - 53).

   La línea 39 declara una variable de tipo apuntador a FILE de nombre archivoPtr. FILE es una estructura (struct) que contiene información indispensable para el control del archivo o flujo incluyendo, entre otras cosas, un apuntador al buffer donde se almacenarán los datos que serán enviados o leídos a o del archivo respectivamente, el indicador de posición del archivo, indicadores de estado del archivo, etc.

   La función fopen (línea 43) abre el archivo especificado en la cadena de su primer argumento (la cadena puede incluir la ruta completa de acceso al archivo; si no se especifica una ruta, se busca el archivo en el directorio de trabajo actual), lo asocia con un flujo, y regresa un apuntador a la estructura FILE que representa dicho flujo, mismo que será utilizado en accesos posteriores a través de la variable archivoPtr.

   Las operaciones que se habilitarán sobre el flujo así como la forma en que éstas se llevan a cabo, son especificadas en la cadena del segundo argumento: el modo de apertura. La siguiente lista  muestra los modos básicos de apertura utilizados por la función fopen.
  • "r"      leer (read) : abre el archivo para lectura de datos. El archivo debe existir. El indicador de posición del archivo se establece al inicio.
  • "w"     escribir (write) : crea un archivo vacío para el almacenamiento de datos. Si el archivo existe, su contenido se descarta y el archivo es tratado como si fuera un nuevo archivo. El indicador de posición del archivo se establece al inicio.
  • "a"     agregar (append) : abre un archivo para el almacenamiento de datos al final del mismo respetando los que ya existen (si hubiera). Si el archivo no existe, se crea. El indicador de posición del archivo se establece al final para poder agregar los datos.
  • "r+"    leer/actualizar (read/update) : abre el archivo para actualización (lectura y escritura) de datos. El archivo debe existir. El indicador de posición del archivo se establece al inicio.
   Con los modos de apertura especificados en la lista anterior, el archivo es abierto y tratado como un archivo de texto. Para que el archivo pueda ser tratado como un archivo binario, se debe agregar el símbolo b (binary) a la cadena de modo de apertura, tal y como se muestra en la línea 15 del Ejemplo 9.7, la línea 20 del Ejemplo 9.5, y la línea 25 del Ejemplo 9.6, de los cuales se hablará más adelante.

   Continuando con el Ejemplo 9.1, note que la apertura del archivo ha sido solicitada en modo agregar y no en modo escribir; éste modo de apertura debe utilizarse con mucha precaución, ya que se pueden perder archivos completos debido a que se eliminan todos los datos de un archivo existente, aunque estos no hayan sido creados por un programa en C. Si la función fopen pudo abrir el archivo regresará, como ya se mencionó con anterioridad, una referencia al flujo que representa dicho archivo; en caso contrario, regresará un apuntador nulo, y ésta situación debe ser siempre verificada, como se muestra en la sentencia if de la línea 43.

   Una vez que el archivo ha sido abierto, almacenar datos en él es tan simple como mandar datos a la salida estándar. La función fprintf (líneas 47 y 48) es análoga a la función printf por lo que su uso debe ser más que natural en este momento; la única diferencia radica en su primer argumento, el cual es un apuntador al flujo que se utilizará para enviar los datos (si el primer argumento de la función fprintf es stdout, fprintf se comporta exactamente igual que printf. Note que el flujo al que se están enviando los datos es precisamente el apuntador a FILE archivoPtr, el cual es el medio de acceso para el archivo "contactos.txt" (vea nuevamente la línea 43).

   Por último, la función fclose (línea 50) cierra el archivo asociado al flujo representado por archivoPtr y elimina también dicha asociación. Una posible salida para el programa del Ejemplo 9.1 se muestra en la siguiente figura.

Una posible salida para el Ejemplo 9.1.

Lectura de datos.
   En la sección anterior se utilizó un archivo de acceso secuencial para almacenar datos en él, en esta sección se hará uso de dicho archivo para leer los datos almacenados en el mismo y mostrarlos en la salida estándar. En este sentido, el Ejemplo 9.2 es la parte complementaria del Ejemplo 9.1.

   Al igual que antes, la explicación estará limitada a la función leeDirectorio (líneas 28 - 40), la cual gestiona los aspectos referentes al manejo del archivo para su lectura.

   La función fopen abre el archivo "contactos.txt" (línea 32) en modo de lectura debido a que no se requiere modificar los datos del mismo, sino sólo procesarlos para su almacenamiento en un arreglo.

   Por otro lado, la función leeDirectorio muestra el uso de la función fgets en las líneas 35 y 36. La función fgets trabaja de manera análoga a la función gets, pero a diferencia de ésta última, fgets recibe tres parámetros:
  1. La cadena donde se almacenarán los datos que se procesen (representada en el Ejemplo 9.2 por el argumento contactos[i].nombre y contactos[i].telefono respectivamente).
  2. El número máximo de caracteres a almacenar en la cadena (representada en el Ejemplo 9.2 por el argumento TAM_NOM y TAM_TEL respectivamente). Esta característica resulta sumamente conveniente debido a que la función fgets protege de desbordamientos: aunque la cantidad de datos que se procesen sea mayor que el tamaño de la cadena, dichos datos son descartados y sólo se almacena como máximo, el número de datos especificado en este argumento menos uno, dado que se preserva el espacio para almacenar el fin de cadena ('\0').
  3. El flujo del cual se procesarán los datos (representado en el Ejemplo 9.2 por el argumento archivoPtr).
   La función fgets lee caracteres de un flujo y los almacena en la cadena correspondiente hasta que se hayan leído a lo más el número de caracteres indicado menos uno (para almacenar el fin de cadena '\0'), se encuentre el símbolo de fin de archivo (el símbolo de fin de archivo está representado por la constante simbólica EOF), o se encuentre el símbolo de avance de línea y retorno de carro ('\n').

   Tome en cuenta que aunque el símbolo '\n' hace que la función fgets termine la lectura, éste es considerado un carácter válido y en consecuencia, es también almacenado en la cadena.

   Ahora bien, si la función fgets encuentra el símbolo EOF antes que cualquier otro carácter, la función regresa un apuntador NULL, situación que es verificada en la línea 35 del Ejemplo 9.2. Se recomienda revisar un manual de referencia para tener una mejor y más amplia comprensión de la función fgets.

   Por último, note que la función leeDirectorio regresa el número (i) de contactos leídos del archivo (línea 39). Una posible salida para el Ejemplo 9.2 se muestra en la siguiente figura:

Una posible salida del Ejemplo 9.2.