Manejo de Ficheros en C BIOHAZARD 1999
0. Introducción
1. Definición de fichero
2. Ficheros de acceso secuencial
2.1. fopen3. Gestión de un fichero secuencial de estructuras
2.2. fclose
2.3. fcloseall
2.4. putc
2.5. getc
2.6. fputs
2.7. fgets
2.8. fwrite
2.9. fread
2.10. feof
2.11. ferror
2.12. rewind
2.13. getw
2.14. putw
2.15. fscanf
2.16. fprintf
2.17. remove
2.18. rename
4.1. fseek
4.2. ftell
Bienvenidos al maravilloso mundo de los ficheros en C. He intentado meter en este cursillo todo lo relacionado con la creación y gestión de ficheros, tanto binarios como de texto. Los ficheros son una de las más importantes, por no decir la más importante, de las partes de un programa, ya que nos ayudan a almacenar datos necesarios sin que se pierdan al apagar el equipo.
Veremos muchas de las funciones de manejo de ficheros, aunque no todas, pero si las más importantes y relevantes, asi como las formas de recorrer un fichero de la cabeza a los pies.
Acabando la siempre aburrida introducción en los documentos,
Os saluda
Un fichero en sentido global puede ser desde un monitor hasta una impresora, pasando por un archivo en disco.
La idea más común del concepto de fichero es un conjunto de posiciones de memoria situadas en un disco de los dispositivos externos de almacenamiento del sistema, en las cuales podemos almacenar y recuperar información.
El lenguaje C nos proporciona un acceso secuencial y directo a los registros de un fichero, pero no soporta el acceso indexado a un registro dado.
Los ficheros en C los podemos clasificar, según la información que contengan, en dos grupos: ficheros de texto y ficheros binarios.
Los ficheros de texto se caracterizan por estar compuestos por una serie de caracteres organizados en líneas terminadas por un carácter de nueva línea (carácter '\n'). Esto nos hace pensar en la idea de usar la impresora como si fuese un fichero de texto.
Por otro lado, los ficheros binarios constan de una secuencia de bytes. Podemos decir que cualquier fichero que no sea de texto, será binario.
A la hora de trabajar con ficheros, tendremos que especificar antes de usarlos, si serán de texto o binarios.
Podemos establecer una segunda clasificación de los ficheros, atendiendo al modo de acceso a su información. De este modo, distinguiremos entre ficheros secuenciales y ficheros directos.
Los ficheros de acceso secuencial se basan en el hecho de que, para acceder a una determinada posición de los mismos, hemos de recorrer desde el principio todas las posiciones hasta llegar a la deseada. Las impresoras son un claro ejemplo de acceso secuencial, así como las cintas magnéticas.
Con el uso de ficheros de acceso directo podemos acceder de forma directa a la posición que queramos sin tener que pasar por las posiciones anteriores. El dispositivo de acceso directo por excelencia es el dico magnético.
El lenguaje C trata a los ficheros como punteros. En realidad un fichero es un puntero a una estructura de nombre predefinido FILE, cuyas componentes son las características de la variable fichero declarada. Cada fichero deberá tener una estructura FILE asociada.
La estructura FILE se encuentra definida en el archivo de cabecera stdio.h, con lo cual es necesario incluirla en todos los programas que trabajen con ficheros mediante la conocida directiva #include <stdio.h>
La forma de declarar variables de tipo FILE es la siguiente:
FILE *f, *f1, ...
Como podemos observar se trata de punteros que apuntan a estructuras de tipo
FILE. En realidad lo que ocurre con el tipo FILE es que redefine a una
estructura de los siguiente forma:
typedef struct
{
short level;
unsigned flags;
char fd;
unsigned char hold;
short bsize;
unsigned char *buffer, *curp;
unsigned istemp;
short token;
}FILE;
Esta estructura la podrás ver editando el fichero stdio.h. El programador no tendrá que definirla puesto que ya existe.
A continuación vamos a ver las operaciones que podemos realizar con ficheros
secuenciales y directos.
La primera oeración después de declarar un fichero y antes de cualquier otra
operación con él es abrirlo.
Significado
Decir que los ficheros de texto se pueden referenciar tambien como "rt", "wt",
"at", "rt+", "wt+" y "at+", pero para facilitar más las cosas, cuando no
especificamos si queremos abrir un fichero de texto o binario, por defecto se
supone que es de texto, es por ello que los modos "r", "w" y "a" se refieren a
ficheros de texto.
Hemos de tener cuidado con los modos de apertura de los ficheros por que al
abrir con cualquier modo "w", si ya existía el fichero, se borrará la información
que contuviese. Si no existe, lo creará. Por el contrario con el modo "a"
también lo creará si no existe el fichero, pero si ya existe, añadirá al final
del mismo los datos que escribamos.
Podemos saber si un fichero ha sido abierto correctamente o no. Debido a que los
ficheros son punteros a estructuras tipo FILE, podemos conocer su valor al hacer
que apunten a ellas. La forma de hacerlo es comparando el puntero con la constante
predefinida NULL. De esta forma, cuando el puntero valga NULL (no apunta a
ninguna variable o posicion de memoria), es porque ha habido algún problema al
abrir el fichero.
Este problema se puede deber a que no existe el fichero que intentamos abrir
para lectura, que no hay espacio suficiente en el disco al tratar de crear el
fichero, que la unidad de disco no está preparada, que el disco está dañado
fisicamente o que la impresora no está conectada. Veamos en ejemplo del testeo
de la existencia de un fichero de texto.
Cuando terminemos de trabajar con un fichero hemos de realizar la operación de
cierre del fichero. Si no lo hacemos podemos ocasionar la pérdida de todos los
datos del mismo. Si se necesitan cerrar varios ficheros a la vez, nos podemos
ahorrar varios fclose utilizando la funcion fcloseall.
Ahora veamos una serie de funciones para escribir y leer datos de un fichero de
texto.
Esta función escribe un carácter en un fichero de texto. Devuelve un entero
si la escritura es correcta. De otra forma devuelve el valor EOF que indica
que ha habido un error. Su formato es:
Ni que decir tiene que cuando usemos funciones para escribir en ficheros, estos
tendrán que haber sido abiertos en modo escritura o para añadir datos.
Esta función lee un carácter de un fichero de texto abierto en modo lectura.
Devuelve un entero si la lectura es correcta. De otra forma devuelve el valor
EOF que indica que hemos llegado al final del fichero. Su formato es:
Un ejemplo del uso de estas dos funciones sería leer todos los caracteres de un
fichero de texto y escribirlos en otro fichero de texto y por pantalla a la vez
para comprobar la correcta ejecución:
Aparece en este ejemplo una nueva función, la función exit(). Esta función
provoca que acabe el programa con lo que la función main() devuelve un valor, en
este caso el valor 1. Recordamos que toda función devuelve un valor.
Esta función escribe una cadena de caracteres en un fichero de texto. Su formato
es:
Esta función lee un número de caracteres de un fichero almacenándolos en una
cadena. Si se encuentra el carácter de nueva línea ya no almacenará más
caracteres. Su formato es:
Esta función permite escribir uno o más datos o bloques de datos binarios en
un fichero. Su formato es el siguiente:
Esta función permite leer uno o más datos o bloques de datos de un fichero
binario. Su formato es:
Vamos a ver un ejemplo que escriba un float en un fichero y después lo lea
y escriba en pantalla:
Vemos que hemos usado el mismo puntero de fichero. Lo podemos hacer siempre
que cerremos el primero antes de abrir el segundo fichero, o se trate del mismo
fichero, como es en este caso. Primero hemos abierto el fichero binario para
escritura, con lo cual lo creamos de nuevo, y después lo abrimos para lectura
para leer el float.
Este es un ejemplo muy sencillo pues en el fichero solo escribimos un float y
después lo leemos. Pero, ¿qué ocurre cuando escribimos más de un dato? ¿cómo
sabemos hasta donde debemos leer en el fichero?. La respuesta está en conocer
donde está el final del fichero. De esta forma leeremos hasta encontrar el final
del fichero dado. Existe una función que nos dirá si hemos llegado a este punto
o no: la función feof.
Esta función nos devuelve un número positivo si hemos llegado al final
de un fichero binario. De otro modo, nos devuelve un cero. Su sintaxis es:
2. Ficheros de acceso secuencial
<variable_fichero> = fopen (<nombre_fichero>, <modo acceso>);
Donde:
Los distintos modos de abrir un fichero son los siguientes:
Abre un archivo de texto para solo lectura.
Si el archivo no existe, devuelve un error
Abre el archivo de texto para solo escritura.
Si el fichero no existe, lo crea, y si existe lo machaca.
Abre el archivo de texto para añadir al final.
Si el fichero no existe, lo crea.
Abre el archivo de texto para lectura/escritura.
Si el fichero no existe, da un error
Abre el archivo de texto para lectura/escritura.
Si el fichero no existe, lo crea, y si existe lo machaca.
Abre el archivo de texto para añadir al final, con
opción de lectura. Si el fichero no existe, lo crea.
Abre un archivo binario para solo lectura.
Si el archivo no existe, devuelve un error
Abre el archivo binario para solo escritura.
Si el fichero no existe, lo crea, y si existe lo machaca.
Abre el archivo binario para añadir al final.
Si el fichero no existe, lo crea.
Abre el archivo binario para lectura/escritura.
Si el fichero no existe, da un error
Abre el archivo binario para lectura/escritura.
Si el fichero no existe, lo crea, y si existe lo machaca.
Abre el archivo binario para añadir al final, con
opción de lectura. Si el fichero no existe, lo crea.
FILE *f;
f = fopen ("texto.txt", "r");
if (f == NULL)
printf ("Error, el fichero no existe\n");
else
...
O de esta otra manera
FILE *f;
f = fopen ("texto.txt", "r");
if (!f)
printf ("Error, el fichero no existe\n");
else
...
O bien, de forma abreviada
FILE *f;
if ((f = fopen ("texto.txt", "r")) == NULL)
printf ("Error, el ficnero no existe\n");
else
...
<valor> = fclose (<variable_fichero>);
Donde:
<numero_ficheros> = fcloseall();
Donde:
putc (<carácter>, <var_fich>);
Donde:
Ejemplos:
<carácter> = getc (<var_fich>);
Donde:
Ejemplos:
#include <stdio.h>
main ()
{
FILE *f_in, *f_out;
char c;
clrscr();
if ((f_in = fopen ("prueba.c", "r")) == NULL)
{
printf ("Error de apertura del fichero\n");
exit (1);
}
if ((f_out = fopen ("salida.c", "w")) == NULL)
{
printf ("Error de creación del fichero\n");
exit (1);
}
do
{
c = getc(f_in);
putchar(c) /* escritura en pantalla */
putc(c, f_out); /* escritura en el fichero */
}
while (c != EOF);
fclose (f_in);
fclose (f_out);
}
fputs (<cadena>, <var_fich>);
Donde:
Ejemplos:
fgets (<cadena>, <num_caracteres>, <var_fich>);
Donde:
Ejemplos:
fwrite (<dato>, <num_bytes>, <cont>, <var_fich>);
Donde:
fread (<dato>, <num_bytes>, <cont>, <var_fich>);
Donde:
#include <stdio.h>
main ()
{
FILE *fich;
float f = 4.879;
/* Escritura del float en el fichero */
if ((fich = fopen ("floats.dat", "wb")) == NULL)
{
printf ("Error de creación del fichero\n");
exit (1);
}
fwrite (&f, sizeof(f), 1, fich);
fclose (fich);
/* Lectura del float del fichero */
if ((fich = fopen ("floats.dat", "rb")) == NULL)
{
printf ("Error de existencia del fichero\n");
exit (1);
}
fread (&f, sizeof(f), 1, fich);
fclose (fich);
printf ("Float = %.3f", f);
}
<valor> = feof (<var_fich>);
Donde:
Ejemplos:
{
fread (....);
...
}
Este bucle significa "mientras no sea final de fichero leer..."
Determina si una operación con archivos ha sido errónea o no. Si lo ha sido
devuelve un número positivo, y si no ha habido problema, devuelve un 0. Su
sintaxis es:
<valor> = ferror (<var_fich>);
Donde:
Esta función restablece el localizador de posición del archivo al comienzo del
mismo. Su sintaxis es:
rewind (<var_fich>);
Donde:
Por ejemplo, si estamos leyendo secuencialmente el fichero fich y
vamos por el cuarto dato, al hacer rewind(fich), volveríamos al
principio del fichero y podríamos volver a leer el primer dato, como si lo
abriésemos de nuevo para lectura.
Esta función devuelve un entero leído de un fichero binario. Su sintaxis es:
<num_entero> = getw (<var_fich>);
Donde:
Esta función escribe un entero en un fichero binario. Su sintaxis es:
putw (<num_entero>, <var_fich>);
Donde:
Esta función trabaja de la misma manera que scanf, pero leyendo los
datos formateados de un fichero. Su sintaxis es:
fscanf (<var_fich>, <cadena_de_control>, <lista_variables>);
Donde:
Esta función trabaja de la misma manera que printf, pero escribiendo los
datos formateados sobre un fichero. Su sintaxis es:
fprintf (<var_fich>, <cadena_de_control>, <lista_variables>);
Donde:
Esta función borra fisicamente el archivo que se especifique. Devuelve un 0 si
todo ha salido correctamente o un -1 si ha ocurrido un error. Su sintaxis es:
<valor> = remove (<nombre_archivo>);
Donde:
Esta función renombra un fichero especificado. Devuelve 0 si el cambio de nombre
ha sido correcto, y -1 si no lo ha sido. Su sintaxis es:
<valor> = rename (<nombre_actual>, <nuevo_nombre>);
Donde:
Los componentes o registros de un fichero pueden ser de cualquier tipo de datos, desde tipos básicos (enteros, float, char, etc...) hasta estructuras de datos.
Las operaciones en cualquiera de los casos son siempre iguales. Veamos un ejemplo de un fichero de estructuras:
... struct Tcli { char nombre[30]; char dirección[30]; ... }; void main() { FILE *f; struct Tcli cliente; char fichero[13]; strcpy (fichero, "cliente.dat\0"); if ((fopen(fichero, "ab") == NULL) { printf ("Error al abrir el fichero\n"); exit (1); } ... }
fwrite (&cliente, sizeof(cliente), 1, f);
fread (&cliente, sizeof(cliente), 1, f);
Las operaciones básicas en el mantenimiento o gestión de un fichero secuencial son las siguientes:
FILE *f, *fimp; f = fopen ("clientes.dat", "r"); fimp = fopen ("prn", "w");De tal forma que todo lo que escribamos en fimp saldrá por impresora. Por tal motivo, debemos pensar que se trata de un fichero de texto, por lo cual, usaremos funciones de escritura en ficheros de texto.
Ya hemos visto como acceder secuencialmente a un fichero, sin embargo también se puede hacer de forma directa.
Supongamos que tenemos definido un fichero con la siguiente estructura de
registro:
struct
{
int codigo;
char nomart[31];
float precio;
}articulo;
Es evidente que la longitud de cada registro es de 37 bytes (2+31+4 bytes). De
esta forma, la disposición de los registros dentro del fichero en disco se realiza
en las siguientes posiciones:
![]() |
El acceso directo consiste en indicar la posición a la que queremos acceder en
bytes. Por ejemplo, para acceder directamente al registro 2, indicaremos que
queremos ir al byte 37, contando desde el principio del registro. La orden que
posibilita este acceso es la siguiente.
Hay que tener muy en cuenta que no es lo mismo desplazarse 37 bytes desde el
principio del fichero (accederemos al segundo registro), que desde el lugar
donde nos encontremos (accederemos al siguiente registro)
Existe otra función que nos devuelve la posición en la que nos encontramos dentro
del fichero
Esta función se suele usar, a parte de para saber la situación exacta en el fichero,
para obtener la longitud del mismo. Veamos un ejemplo:
La forma de dar de alta registros es igual que para el acceso secuencial. Se
hará un recorrido hasta llegar al final del fichero para comprobar que no
existe el código del registro a dar de alta, y en ese caso, se procede a hacer
fwrite. Abriremos para ello el fichero en modo añadir.
Las bajas consisten en localizar el registro según un código, y escribir un
registro vacío en tal posición. Para ello tendremos que abrir el fichero en
modo lectura/escritura, o sea, "r+".
Las modificaciones, de forma similar a las bajas pero escribiendo en el lugar
correspondiente el registro modificado que reemplace al actual.
Las consultas no son otra cosa que un acceso directo al código indicado.
Y por último los listados, que consisten en un recorrido secuencial desde el
primer registro hasta el último.
<valor> = fseek (<var_fich>, <dirección>, <desde>);
Donde:
SEEK_SET
SEEK_CUR
SEEK_END
<posición> = ftell (<var_fich>);
Donde:
...
if ((f=fopen("articul.dat", "rb")) == NULL)
{
printf ("Imposible crear fichero\n");
exit (1);
}
fseek (f, 0, 2); /* tambien puede ser fseek (f, 2, SEEK_END); */
l = ftell(f);
printf ("El fichero tiene un tamaño de %ld bytes\n", l);
printf ("Y un total de %ld registros\n", l/sizeof(reg));
/* donde reg sería la estructura o el dato simple contenido en el fichero */
...
Hasta aquí el cursillo sobre ficheros en C. Recordaros que la forma de gestión
de los ficheros aquí expuesta es la más básica que yo conozco, pero que se puede
hacer de mil maneras, mejores o peores, pero que no es la única. Comentamos casi
al principio que C no soporta los ficheros indexados, pero podeis crearoslos
vosotros mismos, sólo debeis pensar en crearos un fichero de índices y otro maestro
y a partir de ahí, todo sale solo.