martes, 25 de febrero de 2014

Archivos o ficheros en el Lenguaje C



Los archivos o ficheros brindan una forma de guardar permanentemente los datos y resultados de nuestros programas. 

Es importante indicar que los ficheros no son únicamente los archivos que guardamos en el disco duro. En C todos los dispositivos del ordenador se tratan como ficheros: la impresora, el teclado, la pantalla, etc.

Lectura de archivos. Operaciones y funciones básicas

Para comenzar, vamos a analizar un ejemplo que lee un fichero de texto (origen.txt) y muestra su contenido en la pantalla:

#include <stdio.h>
#include <stdlib.h>

main()
{
 FILE *fichero;
 char letra;

 fichero = fopen("origen.txt","r");
 if (fichero == NULL) {
  printf( "No se puede abrir el fichero.\n" );
  exit( 1 );
 }

 printf( "Contenido del fichero:\n" );
 letra = getc(fichero);
 while (feof(fichero) == 0) {
  printf( "%c",letra );
  letra = getc(fichero);
 }

 if (fclose(fichero)!= 0)
  printf( "Problemas al cerrar el fichero\n" );
}

Analizaremos este ejemplo poco a poco deteniéndonos en:

El puntero FILE
Apertura del fichero para lectura: fopen
Comprobación de fichero abierto
Lectura de caracteres del fichero: getc
Comprobación de fin de fichero: feof
Cierre del fichero: fclose

y, posteriormente, nos referiremos a:
Lectura de líneas del fichero: fgets
La función fread

El puntero FILE *

Todas las funciones de estrada/salida estándar usan este puntero para conseguir información sobre el fichero abierto. Este puntero no apunta al archivo sino a una estructura que contiene información sobre él. Esta estructura incluye entre otras cosas información sobre el nombre del archivo, la dirección de la zona de memoria donde se almacena el fichero, tamaño del buffer.

Apertura del fichero para lectura: fopen

Ahora es preciso abrir el fichero. Para ello usamos la función fopen. Esta función tiene el siguiente formato:

FILE *fopen(const char *nombre_fichero, const char *modo);

En el ejemplo usábamos: fichero = fopen("origen.txt","r");

El nombre de fichero se puede indicar directamente (como en el ejemplo) o usando una variable y se puede abrir de diversas formas. Esto se especifica con el parámetro modo. Los modos posibles son:

rAbre un fichero existente para lectura.
wCrea un fichero nuevo (o borra su contenido si existe) y lo abre para escritura.
aAbre un fichero (si no existe lo crea) para escritura. El puntero se sitúa al final del archivo, de forma que se puedan añadir datos si borrar los existentes.

Se pueden añadir una serie de modificadores siguiendo a los modos anteriores:

bAbre el fichero en modo binario.
tAbre el fichero en modo texto.
+Abre el fichero para lectura y escritura.

Ejemplos de combinaciones:
  • rb+ - Abre el fichero en modo binario para lectura y escritura.
  • w+ - Crea (o lo borra si existe) un fichero para lectura y escritura.
  • rt - Abre un archivo existente en modo texto para lectura.
Comprobación de fichero abierto

Un aspecto muy importante después de intentar abrir un fichero es comprobar si realmente está abierto. El sistema no es infalible y pueden producirse fallos: el fichero puede no existir, estar dañado o no tener permisos de lectura. Si intentamos realizar operaciones sobre un puntero tipoFILE cuando no se ha conseguido abrir el fichero puede haber problemas.

Si el fichero no se ha abierto, el puntero fichero (puntero a FILE) tendrá el valor NULL, si se ha abierto con éxito tendrá un valor distinto de NULL. Por lo tanto, para comprobar si ha habido errores, nos fijamos en el valor del puntero:

 if (fichero == NULL) {
  printf( "No se puede abrir el fichero.\n" );
  exit( 1 );
 }

Si fichero == NULL significa que no se ha podido abrir por alguna causa. Lo más conveniente es salir del programa. Para salir utilizamos la función exit(), donde el argumento 1 indica al sistema operativo que se han producido errores. Esta función precisa del archivo de cabecera stdlib.h.

Lectura de caracteres del fichero: getc

Ahora ya podemos empezar a leer el fichero. Para ello podemos utilizar la función getc, que lee los caracteres uno a uno. Se puede usar también la función getc (son equivalentes: la diferencia es quegetc está implementada como macro). Además de estas dos, existen otras funciones como fgets,fread que leen más de un carácter.

El formato de la función getc (y de fgetc) es:

int getc(FILE *fichero);

En este caso lo usamos como:

 letra = getc( fichero );

Tomamos un carácter de fichero, lo almacenamos en letra y el puntero del fichero se coloca en el siguiente carácter.

Comprobación de fin de fichero: feof

Cuando entramos en el bucle while, la lectura se realiza hasta que se encuentre el final del fichero. La detección del final del fichero se puede llevar a cabo de dos formas:
  • con la función feof
  • comprobando si el valor de letra es EOF (el último carácter de cualquier archivo).
En el ejemplo hemos usado la función feof. Esta función es de la forma:

int feof(FILE *fichero);

Esta función comprueba si se ha llegado al final de fichero, en cuyo caso devuelve un valor distinto de 0. Si no se ha llegado al final de fichero devuelve un cero. Por eso lo usamos del siguiente modo:

 while ( feof(fichero) == 0 )

o

 while ( !feof(fichero) )

La segunda forma que comentaba arriba consiste en comprobar si el carácter leído es el de fin de fichero EOF:

 while ( letra != EOF )



Sin embargo, cuando trabajamos con ficheros de texto no hay ningún problema, pero si estamos manejando un fichero binario podemos encontrarnos EOF antes del fin de fichero. Por eso es preferible usar feof.

Cierre del fichero: fclose

Una vez realizadas todas las operaciones deseadas sobre el fichero hay que cerrarlo. Es importante no olvidar este paso pues el fichero podría corromperse. Al cerrarlo se vacían los buffers y se guarda el fichero en disco. Un fichero se cierra mediante la función fclose. Esta función tiene el siguiente formato:

FILE *fclose(FILE * stream);

Aquí stream es el puntero a FILE devuelto por fopen cuando el archivo se abrió. Si todo va bien, devuelve un cero, si hay problemas, devuelve otro valor. Estos problemas se pueden producir si el disco está lleno, por ejemplo

 if (fclose(fichero)!= 0)
         printf( "Problemas al cerrar el fichero\n" );

Lectura de líneas del fichero: fgets

Esta función es muy útil para leer líneas completas desde un fichero. Su formato es:

char *fgets(char *buffer, int longitud_max, FILE *fichero);

Esta función lee desde el fichero hasta que encuentra un carácter '\n' o hasta que leelongitud_max-1 caracteres y añade '\0' al final de la cadena (y retiene el carácter '\n'). La cadena leída la almacena en buffer. Si se encuentra EOF antes de leer ningún carácter, o si se produce un error, la función devuelve NULL; en caso contrario devuelve la dirección de buffer.

#include <stdio.h>
#include <stdlib.h>

main()
{
 FILE *fichero;
 char texto[100];

 fichero = fopen("origen.txt","r");
 if (fichero == NULL) {
  printf( "No se puede abrir el fichero.\n" );
  exit( 1 );
 }

 printf( "Contenido del fichero:\n" );
 fgets(texto,100,fichero);
 while (feof(fichero) == 0) {
  printf( "%s",texto );
  fgets(texto,100,fichero);
 }

 if (fclose(fichero)!=0)
  printf( "Problemas al cerrar el fichero\n" );
}

La función fread

Esta función se tratará posteriormente, junto con la función fwrite. Ambas permiten guardar y leer, respectivamente, cualquier tipo de dato, incluso estructuras.

Escritura de archivos. Operaciones y funciones básicas

Vamos a completar ahora la parte que faltaba en la sección anterior: escribir en un fichero. Vamos a hacerlo de nuevo mediante un ejemplo. En éste, abrimos un fichero origen.txt y lo copiamos en otro destino.txt. Además, el fichero se muestra en pantalla (las partes nuevas se han destacado en negrita).

#include <stdio.h>

main()
{
 FILE *origen, *destino;
 char letra;

 origen = fopen("origen.txt","r");
 destino= fopen("destino.txt","w");
 if (origen == NULL || destino == NULL) {
  printf( "Problemas con la apertura de los ficheros.\n" );
  exit( 1 );
 }

 letra = getc(origen);
 while (feof(origen) == 0) {
  putc(letra, destino);
  printf( "%c",letra );
  letra = getc(origen);
 }

 if (fclose(origen)!= 0)
  printf( "Problemas al cerrar el fichero origen.txt\n" );
 if (fclose(destino)!= 0)
  printf( "Problemas al cerrar el fichero destino.txt\n" );
}

Como en el caso de la lectura de archivos, analizaremos este ejemplo poco a poco deteniéndonos en:
  • El puntero FILE
  • Apertura del fichero para escritura: fopen
  • Escritura de caracteres en el fichero: putc
  • Comprobación de fin de fichero
  • Cierre del fichero: fclose
y, posteriormente, nos referiremos a:
  • Escritura de líneas en el fichero: fputs
  • La función fwrite
El puntero FILE *

Como hemos visto en antes, el puntero FILE es la base de la escritura/lectura de archivos. Por eso definimos dos punteros FILE:
  • el puntero origen donde vamos a almacenar la información sobre el fichero origen.txt, y
  • destino donde guardamos la del fichero destino.txt (el nombre del puntero no tiene por qué coincidir con el de fichero).
Apertura del fichero para escritura: fopen

El siguiente paso, como antes, es abrir el fichero usando fopen. La diferencia es que ahora tenemos que abrirlo en modo escritura. Usamos el modo w (crea el fichero, o lo vacía si existe) porque queremos crear un fichero. Recordemos que después de abrir un fichero hay que comprobar si la operación se ha realizado con éxito. En este caso, como es un sencillo ejemplo, hemos comprobado ambos a la vez:
if (origen==NULL || destino==NULL)

pero es más correcto hacerlo por separado, de modo que separemos la posible causa del fallo.

Escritura de caracteres en el archivo: putc

Como se puede observar en el ejemplo, la lectura del fichero se hace igual que antes, con getc. Para la escritura usamos la función putc:, cuya sintaxis es:
int putc(int c, FILE *fichero);


donde c contiene el carácter que queremos escribir en el fichero y el puntero fichero es el fichero sobre el que trabajamos.

En este caso lo usamos como:

 putc(letra, destino);

De esta forma vamos escribiendo en el fichero destino.txt el contenido del fichero origen.txt.

Comprobación fin de fichero

Como siempre que leemos datos de un fichero debemos comprobar si hemos llegado al final. Sólo debemos comprobar si estamos al final del fichero que leemos. No tenemos que comprobar el final del fichero en el que escribimos puesto que lo estamos creando y aún no tiene final.

Cierre del fichero: fclose

Y, por fin, lo que nunca debemos olvidar al trabajar con ficheros: cerrarlos. Debemos cerrar tanto los ficheros que leemos como aquellos sobre los que escribimos.

Escritura de líneas en el fichero: fputs

La función fputs trabaja junto con la función fgets . La sintaxis es:

int fputs(const char *cadena, FILE *fichero);

Esta función escribe en el fichero una cadena y no le añade un carácter '\n' ni el terminador de cadena '\0'. Devuelve el último carácter escrito o EOF si se produce un error.

Veamos un ejemplo en el que se escriben tres líneas de texto al fichero y después se muestra su contenido.

#include <stdio.h>
#include <stdlib.h>

main()
{
 FILE *fichero;
 char texto[100];

 fichero = fopen("destino.txt","w");
 if (fichero == NULL) {
  printf( "No se puede crear el fichero.\n" );
  exit( 1 );
 }

 /* Escribe en el fichero */
 fputs("Esta es la primera línea del fichero\n", fichero);
 fputs("Esta es la segunda línea\n", fichero);
 fputs("Esta es la última línea del fichero\n", fichero);

 if (fclose(fichero)!=0)
  printf( "Problemas al cerrar el fichero\n" );

 fichero = fopen("destino.txt","r");
 if (fichero == NULL) {
  printf( "No se puede crear el fichero.\n" );
  exit( 1 );
 }
 printf( "Contenido del fichero:\n" );
 fgets(texto,100,fichero);
 while (feof(fichero) == 0) {
  printf( "%s",texto );
  fgets(texto,100,fichero);
 }

 if (fclose(fichero)!=0)
  printf( "Problemas al cerrar el fichero\n" );
}

fwrite

Esta función se tratará posteriormente, junto con la función fread. Ambas permiten leer y guardar, respectivamente, cualquier tipo de dato, incluso estructuras.

Otras funciones para el manejo de ficheros
  • Lectura y escritura genéricas: fread y fwrite
  • Posicionamiento en el fichero: fseek y ftell
  • fprintf y fscanf
Lectura y escritura genéricas: fread y fwrite

Las funciones que hemos visto anteriormente (getc, putc, fgets, fputs) son adecuadas para trabajar con caracteres (1 byte) y cadenas. Pero, ¿qué sucede cuando queremos trabajar con otros tipos de datos?. Supongamos que queremos almacenar variables de tipo int en un fichero. Como las funciones vistas anteriormente sólo pueden operar con cadenas, deberíamos convertir los valores a cadenas (función itoa, integer to string). Para recuperar luego estos valores deberíamos leerlos como cadenas y pasarlos a enteros (atoi, string to integer). Pero existe una solución mucho más fácil: las funciones fwrite y fread. Éstas nos permiten tratar con datos de cualquier tipo, incluso con estructuras.

fwrite

Ésta nos permite escribir en un fichero. Tiene el siguiente formato:

size_t fwrite(const void *p_buffer, size_t tamaño, size_t número, FILE *p_fichero);

donde:
  • p_buffer - puntero a la variable que contiene los datos que vamos a escribir en el fichero.
  • tamaño - tamaño del tipo de dato a escribir. Puede ser un int, un float, una estructura, etc. Para conocer su tamaño usamos el operador sizeof.
  • número - número de datos a escribir.
  • p_fichero - puntero al fichero sobre el que trabajamos.
Esta función devuelve el número de elementos de tamaño tamaño (no bytes) escritos realmente al fichero, si no ha ocurrido algún error.

Para que quede más claro, examinemos el siguiente ejemplo: un programa de agenda que guarda el nombre, apellido y teléfono de cada persona:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct {
 char nombre[20];
 char apellido[20];
 char telefono[15];
} registro;

main()
{
 FILE *fichero;

 if ((fichero = fopen( "nombres.txt", "a" )) == NULL) {
  printf( "No se puede abrir el fichero.\n" );
  exit( 1 );
 }

 do {
  printf( "Nombre: " ); gets(registro.nombre);
  if (strcmp(registro.nombre,"")) {
   printf( "Apellido: " ); gets(registro.apellido);
   printf( "Teléfono: " ); gets(registro.telefono);

   /* Guarda el registro en el fichero */
   fwrite( &registro, sizeof(registro), 1, fichero );
  }
 } while (strcmp(registro.nombre,"")!= 0);

 fclose( fichero );
}

Este programa guarda los datos personales mediante fwrite usando la estructura registro. Abrimos el fichero en modo "a" (append, añadir), para que los datos que introducimos se añadan al final del fichero.

Una vez abierto abrimos entramos en un bucle do-while mediante el cual introducimos los datos. Estos se van almacenando en la variable registro (que es una estructura). Cuando tenemos todos los datos de la persona, los introducimos en el fichero con fwrite:

 fwrite( &registro, sizeof(registro), 1, fichero );
  • &registro - es la dirección donde comienza la estructura que contiene la información a escribir en el fichero.
  • sizeof(registro) - es el número de bytes que se escribirán, el tamaño en bytes que ocupa la estructura.
  • 1 - indica que sólo se guardará un elemento cada vez.
  • fichero - el puntero FILE al fichero nombres.txt donde vamos a escribir.
fread

Esta función se utiliza para extraer información de un fichero. Su formato es:
size_t fread(void *p_buffer, size_t tamaño, size_t número, FILE *p_fichero);

donde p_buffer es la dirección de la variable donde se almacenarán los datos leídos del fichero designado por p_fichero.

El valor que devuelve la función indica el número de elementos de tamaño tamaño que ha conseguido leer (podemos pedirle a fread que lea 10 elementos, pero si en el fichero sólo hay 6 devolverá el número 6).

Siguiendo con el ejemplo anterior, vamos a leer los datos que habíamos introducido ennombres.txt .

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct {
 char nombre[20];
 char apellido[20];
 char telefono[15];
} registro;

main()
{
 FILE *fichero;

 if ((fichero = fopen( "nombres.txt", "r" )) == NULL) {
  printf( "No se puede abrir el fichero.\n" );
  exit( 1 );
 }

 while (!feof(fichero)) {
  if (fread( &registro, sizeof(registro), 1, fichero )) {
   printf( "Nombre: %s\n",   registro.nombre );
   printf( "Apellido: %s\n", registro.apellido);
   printf( "Teléfono: %s\n", registro.telefono);
  }
 }

 fclose( fichero );
}

Abrimos el fichero en modo lectura, "r". Con el bucle while aseguramos el recorrido del fichero hasta el final (y que no vamos más allá).

La función fread lee 1 registro del tamaño de la estructura registro. Si realmente ha conseguido leer un registro, la función devolverá 1, en cuyo caso la condición del if será verdadera y se imprimirá el registro en la pantalla. En caso de que no queden más registros en el fichero, freaddevolverá 0 y no se mostrará nada en la pantalla.

Posicionamiento en el fichero: fseek y ftell

fseek

Esta función permite situarnos en la posición deseada de un fichero abierto. Cuando leemos un fichero existe un puntero o cursor que indica en qué lugar del fichero nos encontramos. Cada vez que leemos datos del fichero este puntero se desplaza. Con la función fseek podemos situar este puntero en el lugar que se precise.

El formato de fseek es el siguiente:

int fseek(FILE *p_fichero, long desplazamiento, int modo);

Como siempre, p_fichero es un puntero de tipo FILE que apunta al fichero con el que queremos trabajar.

Desplazamiento es el número de posiciones (o bytes) que queremos desplazar el puntero. Este desplazamiento puede ser de tres tipos, dependiendo del valor de modo:

SEEK_SETEl puntero se desplaza desde el principio del fichero.
SEEK_CUREl puntero se desplaza desde la posición actual del fichero.
SEEK_ENDEl puntero se desplaza desde el final del fichero.

Estas tres constantes están definidas en el fichero <stdio.h>. Como curiosidad, se indican a continuación sus definiciones:

#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2

Si se produce algún error al intentar posicionar el puntero, la función devuelve un valor distinto de 0. Si todo ha ido correctamente el valor devuelto es 0.

En el siguiente ejemplo se muestra el funcionamiento de fseek. Se trata de un programa que lee la letra que hay en la posición que especifica el usuario.

#include <stdio.h>
#include <stdlib.h>

main()
{
 FILE *fichero;
 long posicion;
 int resultado;

 if ((fichero = fopen( "origen.txt", "r" )) == NULL) {
  printf( "No se puede abrir el fichero.\n" );
  exit( 1 );
 }

 printf( "¿Qué posición quieres leer? " );
 scanf( "%li", &posicion );
 resultado = fseek( fichero, posicion, SEEK_SET );
 if (!resultado)
  printf( "En la posición %li está la letra %c.\n", posicion, getc(fichero) );
 else
  printf( "Problemas al posicionar el cursor.\n" );

 fclose( fichero );
}

NOTA: la primera posición del fichero es la 0, no la 1.

ftell

Esta función es complementaria a fseek. Devuelve la posición actual dentro del fichero.

Su formato es el siguiente:

long ftell(FILE *p_fichero);

El valor que devuelve puede ser usado por fseek para volver a la posición actual.

fprintf y fscanf

Estas dos funciones trabajan igual que sus equivalentes printf y scanf. La única diferencia es que podemos especificar el fichero sobre el que operan (si se desea, puede ser la pantalla para fprintfo el teclado para fscanf).

Los formatos de estas dos funciones son:

int fprintf(FILE *p_fichero, const char *formato, ...);
int fscanf(FILE *p_fichero, const char *formato, ...);

0 comentarios:

Publicar un comentario

Aprende a Programar tus propias aplicaciones