Operaciones con ficheros en C

La entrada y salida a ficheros es uno de los aspectos más delicados de cualquier lenguaje de programación, pues suelen estar estrechamente integradas con el sistema operativo. Los servicios ofrecidos por los sistemas operativos varían enormemente de un sistema a otro. Las librerías del C proporcionan un gran conjunto de funciones, muchas de ellas descritas en el libro de Kernighan y Ritchie y otras derivadas de los servicios que ofrece el Unix.

En C hay dos tipos de funciones de entrada/salida a ficheros. Las primeras son derivadas del SO Unix y trabajan sin buffer. Las segundas son las que fueron estandarizadas por ANSI y utilizan un buffer intermedio. Además, hacen distinciones si trabajan con ficheros binarios o de texto. Veremos las segundas, que son las más utilizadas.

Las funciones del C no hacen distinción si trabajancon un terminal, cinta o ficheros situados en un disco. Todas las operaciones se realizan a través de streams. Un stream está formado por una serie ordenada de bytes. Leer o escribir de un fichero implica leer o escribir del stream. Para realizar operaciones se debe asociar un stream con un fichero, mediante la declaración de un puntero a una estructura FILE. En esta estructura se almacena toda la información para interactuar con el SO. Este puntero es inicializado mediante la llamada a la función fopen(), para abrir un fichero.

Cuando se ejecuta todo programa desarrollado en C hay tres streams abiertos automáticamente. Estos son stdin, stdouty stderr. Normalmente estos streamstrabajan con el terminal, aunque el sistema operativo permite redireccionarlos a otros dispositivos. Las funciones printf() y scanf() que hemos visto, utilizan stdout y stdin respectivamente.

Los datos de los ficheros pueden ser accedidos en uno de los dos formatos: texto o binario. Un text stream consiste en una serie de líneas de texto acabadas con un carácter newline. En modo binario un fichero es una colección de bytes sin ninguna estructura especial.

Hay que tener muy en cuenta que respecto a la velocidad de la memoria y de la CPU, los dispositivos de entrada y salida con muy lentos. Puede haber cinco o seis órdenes de magnitud entre la velocidad de la CPU y la de un disco duro. Además una operación de entrada y salida puede consumir una cantidad importante de recursos del sistema. Por ello, conviene reducir en número de lecturas y escrituras a disco.

La mejor forma de realizar esto es mediante un buffer. Un buffer es una área de memoria en la cual los datos son almacenados temporalmente, antes de ser enviados a su destino. Por ejemplo, las operaciones de escritura de caracteres a un fichero se realizan sobre el buffer del stream. Unicamente cuando se llena el buffer se escriben todos los caracteres sobre el disco de una vez. Esto ahorra un buen número de operaciones sobre el disco. Las funciones del C nos permiten modificar el tamaño y comportamiento del buffer de un stream.

Para utilizar las funciones de ficheros se debe incluir el fichero stdio.h. Este define los prototipos de todas las funciones, la declaración de la estructura FILE y algunas macros y definiciones. Una definición importante es EOF, que es el valor devuelto por muchas funciones cuando se llega al final de fichero.

Los pasos a seguir para operar con un fichero son: abrir, realizar el tratamiento y cerrar. Para abrir un fichero se utiliza la función fopen. Esta toma dos strings como parámetros. El primero indica el nombre del fichero que deseamos abrir y el segundo indica el modo de acceso.

#include <stdio.h> FILE *fopen ( const char *filename, const char *mode );

Los modos de acceso para streamsde texto son los siguientes:

  • “r”  Abre un fichero que ya existe para lectura. La lectura se realiza al inicio del fichero.
  • “w”  Se crea un nuevo fichero para escribir. Si el fichero existe se inicializa y sobreescribe.
  • “a”  Abre un fichero que ya existe para añadir información por el final. Sólo se puede escribir a partir del final.
  • “r+”  Abre un fichero que ya existe para actualizarlo (tanto para lectura como para escritura).
  • “w+”  Crea un nuevo fichero para actualizarlo (lectura y escritura) si existe, lo sobreescribe.
  • “a+”  Abre un fichero para añadir información al final. Si no existe lo crea.

Si se desea especificar un fichero binario, se añade una bal modo: “wb+”. Si el fichero se abre correctamente, la función devuelve un puntero a una estructura FILE. Si no, devuelve NULL.

La función fprintf se comporta exactamente a printf, excepto en que toma una argumento más que indica el stream por el que se debe realizar la salida. De hecho, la llamada printf(“x”) es equivalente a fprintf ( stdout, “x”).

FILE *f; if ((f = fopen( “login.com”, “r” )) == NULL ) printf(“ERROR: no puedo abrir el fichero\n”);

Para cerrar un fichero se utiliza la función fclose. Esta toma como argumento el puntero que nos proporcionó la función fopen.

#include <stdio.h> int fclose ( FILE *stream );

Devuelve 0 si el fichero se cierra correctamente, EOF si se produce algún error.

Una vez conocemos como abrir y cerrar ficheros, vamos a ver cómo leer y escribir en ellos. Hay funciones para trabajar con caracteres, líneas y bloques.

Las funciones que trabajan con caracteres son fgetcy fputc. La primera lee un carácter de un stream y la segunda lo estribe.

#include <stdio.h> int fgetc ( FILE *stream ); int fputc ( int c, FILE *stream );

La primera lee el siguiente carácter del stream y los devuelve convertido a entero sin signo. Si no hay carácter, devuelve EOF. La segunda, fputc, devuelve el propio carácter si no hay error. Si lo hay, devuelve el carácter EOF. Hay una tercera función, feof que devuelve cero si no se ha llegado al final del stream. Veamos un ejemplo de cómo se copia un fichero carácter a carácter.

while ( !feof(infile)) fputc ( fgetc ( infile ), outfile );

Suponemos que infiley outfileson los streamsasociados al fichero de entrada y al de salida, que están abiertos. Luego debemos cerrarlos para vaciarlos buffersy completar los cambios realizados.

Otras funciones nos permiten realizar operaciones con ficheros de texto trabajando línea a línea.

#include <stdio.h> char *fgets ( char *s, int n, FILE *stream ); int fputs ( const char *s, FILE *stream );

La función fgets lee caracteres del stream hasta que encuentra un final de línea o se lee el carácter n-1. Mantiene el carácter \n en el string y añade el carácter . Devuelve la dirección del string o NULL si se produce algún error. La función fputs copia el string al stream, no añade ni elimina caracteres \n y no copia la marca de final de string .

Hay dos funciones que nos permiten trabajan en bloques. Podemos considerar un bloque como un array. Debemos especificar el tamaño de cada elemento y el número de elementos.

#include <stdio.h> size_t fread ( void *p, size_t s, size_t n, FILE *f); size_t fwrite ( void *p, size_t s, size_t n, FILE *f );

A las anteriores funciones se les pasa un puntero genérico pcon la dirección del área de datos que se desea leer o escribir, el tamaño de cada elemento s, y el número de elementos n, además del stream. Ambas devuelven el número de elementos leídos o escritos, que debe ser el mismo que le hemos indicado, en el caso en que no se haya producido ningún error.

Hasta ahora hemos visto funciones de tratamiento deficheros que nos van bien para realizar un tratamiento secuencial de éstos. Debemos tener en cuenta que cuando debemos realizar operaciones de entrada y de salida alternadas, se deben vaciar los buffers de los streams para que las operaciones se realicen correctamente sobre los ficheros. Esto se consigue con las funciones fflush y flushall. La primera es aplicable al stream que deseemos y la segunda vacía los buffers de todos los streams abiertos.

#include <stdio.h> int fflush ( FILE *stream ); int flushall ( void );

Si deseamos hacer un acceso aleatorio a un fichero disponemos de las funciones fseek y ftell. La primera mueve el cursor, indicador de posición, a un lugar determinado dentro del fichero. Este lugar viene determinado por el parámetro whence. Este puede ser SEEK_SET, si es desde el inicio del fichero, SEEK_CUR si es desde la posición actual y SEEK_END si es desde el final del fichero. Devuelve 0 si la operación se realiza correctamente.

#include <stdio.h> int fseek ( FILE *stream, long offset, int whence ); long ftell ( FILE *stream );

La segunda función devuelve la posición del cursor dentro del fichero. Este valor indica el número de bytes desde el inicio del fichero, si el fichero esbinario.

int escribir_planetas ( char *nombre ) { PLANETA *p; FILE *f; if ( ( f = fopen ( “w+”, nombre )) == NULL ) return ERROR; if (N_PLA != fwrite ( p, sizeof(PLANETA), N_PLA, f )) return ERROR; if ( fclose(f) != 0 ) return ERROR; else return OK; }

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *