Guión 5: Ficheros


Un fichero puede definirse como un almacenamiento de datos, generalmente en disco, con un nombre.

En el Lenguaje C, un fichero es gestionado mediante una estructura de datos denominada FILE, cuya definición se encuentra en el fichero de encabezamiento <stdio.h> .


Apertura de un fichero.

Antes de comenzar a trabajar con un fichero es necesario abrirlo. La función fopen() utiliza dos parámetros.
FILE *fopen(char *, char*);
El primer parámetro es el nombre externo del fichero a abrir, escrito en el formato soportado por el sistema operativo correspondiente. El segundo parámetro es una cadena de caracteres que describe el uso al que se va a destinar el fichero (modo de apertura):
"r"    Lectura (read).
"w"    Escritura (write).
"a"    Apédice (append).

Algunos sistemas ofrecen posibilidades adicionales, como añadir la letra t si el fichero es de texto ("rt", "wt", "at") o la letra b si el fichero es binario ("rb", "wb", "ab"). Si se usa la opción "w" para abrir en modo de escritura un fichero ya existente, este será inicializado y su contenido se borrará.

Al abrir un fichero, la funcion fopen() mediante una llamada al sistema operativo proporciona un puntero a una estructura de tipo FILE. Dicho puntero (nombre interno o descriptor) es la conexión lógica entre el área de datos del programa y el fichero localizado en el soporte de información correspondiente: Un punto de acceso al fichero que permite al programa acceder al mismo para operar con él. A partir de entonces, el descriptor obtenido se utilizará para todas las operaciones subsiguientes sobre el fichero, incluso el cierre del mismo.

Valor de retorno de fopen():
- Puntero a la estructura del fichero, que debe asignarse a una variable de este tipo para poder acceder al fichero a través de dicha variable.
- NULL si se ha producido un error al intentar abrir el fichero. Un error muy común consiste en intentar abrir en modo de lectura un fichero que no existe en la ruta de acceso indicada mediante el primer parámetro de la función.

Cierre de un fichero.

La función fclose() acepta como argumento el puntero a la estructura que gestiona el fichero. Esta función cierra la conexión lógica establecida por fopen() entre el área de datos del programa y el fichero localizado en el soporte de información correspondiente. Además, realiza el vacioado del buffer de transferencia de datos, que podría haber quedado paracialmente lleno en el momento de cerrar el fichero.
int fclose(FILE *);
Valor de retorno de fclose():
- NULL si la operación de cierre se ha realizado satisfactoriamente.
- EOF si se ha producido un error al intentar cerrar el fichero.

Entrada/salida básica.

Las funciones getc() y putc() constituyen las funciones de E/S básica de ficheros. Se comportan de un modo muy semejante a getchar() y putchar(), salvo que estas últimas utilizan los ficheros de E/S estándar, cuyos descriptores son respectivamente stdin y stdout.

La función getc() captura un carácter de un fichero, cuyo puntero se especifica como parámetro de la función.
int getc(FILE *);
La función putc() envía el carácter especificado en el primer parámetro al fichero cuyo puntero se especifica en el segundo parámetro.
int putc(int, FILE *);
En el fichero <stdio.h> la funciones getchar() y putchar() se hallan definidas como macros:
#define getchar() getc(stdin)
#define putchar(c) putc((c), stdout)

Los ficheros cuyos descriptores son stdin y stdout son abiertos por el sistema operativo y asociados a la entrada y a la salida estándar (teclado y pantalla) del ordenador respectivamente de forma automática.

Ejemplo 5.1. Escribir un programa que lea del directorio de trabajo un fichero denominado TEST.TXT y muestre su contenido a través de la pantalla.
#include <stdio.h>

int main() {

  FILE *in;
  int ch;


  if ((in = fopen("TEST.TXT", "r")) != NULL) {
     while ((ch = getc(in)) != EOF)
            putc(ch, stdout); /* equivale a putchar(ch); */
     fclose(in);
     }
  else
     puts("No se puede abrir \"test\".");

  putchar('\n');
  system("pause");
  return 0;
}
Si la llamada a la función fopen() se ha realizado con éxito, dicha función devuelve un puntero a la estructura del fichero, que actúa como descriptor del mismo y es asignado a la variable in. Así pues, esta variable apunta a una estructura de tipo FILE que gestiona el fichero TEST.TXT e implementa el buffer (memoria de almacenamiento temporal) de transferencia de datos que dicho fichero utiliza. A partir de entonces, el programa accede al fichero a través de la variable in y no por su nombre externo.

Ejercicio: Modificar el ejemplo 5.1 para que guarde una copia del contenido del fichero TEST.TXT en el fichero COPIA.TXT.


Entrada/salida con formato.

Las funciones fprintf() y fscanf() se comportan exactamente igual que printf() y scanf() respectivamente, excepto que requieren un argumento adicional para apuntar al fichero correspondiente. Este argumento es el primero de ambas funciones.
Ejemplo 5.2. Desarrollar un programa que lea un número entero y otro número real de simple precisión separados por espacios desde un fichero de texto cuyo nombre es ENTRADA.TXT, y a continuación los escriba en otro fichero de texto denominado SALIDA.TXT según el siguiente formato: "Num1: %d, Num2: %f.\n".

#include <stdio.h>

int main() {

  FILE *ent, *sal;
  int entero;
  float real;

  if ((ent = fopen("ENTRADA.TXT", "rt")) == NULL)
     puts("Error al abrir ENTRADA.TXT");

  else
     if ((sal = fopen("SALIDA.TXT", "at")) == NULL) {
        puts("Error al abrir SALIDA.TXT.");
        fclose(ent);
        /* los ficheros siempre deben cerrarse */
        }
     else {
        fscanf(ent, "%d %f", &entero, &real);
        fprintf(sal, "Num1: %d, Num2: %f.\n", entero, real);
        fclose(ent);
        fclose(sal);
        }

  system("pause");
  return 0;
}

Ejercicio: Modificar el ejemplo 5.2 para que lea del fichero ENTRADA.TXT dos números reales de doble precisión separados por espacios, un carácter que represente el signo de una operación (suma, resta, multiplicación o división), y a continuación escriba en el fichero SALIDA.TXT el resultado de la operación según el siguiente formato: "El resultado de a op b es: c.", donde a y b son los operandos, op el signo del operador correspondiente y c el resultado. En caso de que se haya intentado realizar una división entre cero, se escribirá la siguiente frase en el fichero: "Intento de realizar una división por cero.".


Acceso aleatorio.

La función fseek() permmite tratar los ficheros como arrays y acceder directamente aun byte determinado del fichero abierto previamente por fopen().

Argumentos de fseek():
- Puntero a la estructura FILE del fichero.
- Desplazamiento (offset) desde el punto de referencia. Podrá ser positivo o negativo.
- Modo que indica el punto de referencia.

int fseek(FILE *, long, int);

Modo
Número
Origen del desplazamiento
SEEK_SET
0
Comienzo del fichero.
SEEK_CUR
1
Posición actual.
SEEK_END
2
Fin del fichero.
Valor de retorno de fseek():
- Si no se han producido errores 0.
- En caso de error -1. Un eror muy común consiste en intentar avanzar más allá de los límites del fichero.
Ejemplo 5.3. Codificar un programa que utilice la función fseek() para desplazarse byte a byte a lo largo del fichero TEXTO.TXT desde el final del mismo, con objeto de mostrar en pantalla su contenido en orden inverso.

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

int main() { /* imprime un fichero de texto al revés */

  FILE *fp;
  long despl = 0L; /* cero "largo" */
  char ch;

  if ((fp = fopen("texto.txt", "r")) == NULL) {
     puts("El fichero no puede abrirse.");
     exit(1); /* aborta el programa con un código de error */
     }

  fseek(fp, --despl, SEEK_END);
  /* sitúa el puntero justo antes del fin de fichero (EOF) */

  while ((ch = getc(fp)) != EOF) {
        putchar(ch);
        fseek(fp, --despl, SEEK_END);
        /* mueve el puntero un byte hacia atrás */
        }

  fclose(fp); /* los ficheros siempre deben cerrarse */
  return 0;
}

Ejercicio: Modificar el ejemplo 5.3 para que muestre el contenido del fichero anterior en su orden correcto utilizando la función fseek().


Otras funciones para el uso de ficheros.

La función fread() lee de un fichero binario referenciado por f (cuarto parámetro) n elementos (tercer parámetro) de tamaño t (segundo parámetro) y los almacena en la zona de memoria referenciada por el puntero p (primer parámetro).

fread(void *, unsigned, unsigned, FILE *);
La función fwrite() escribe en un fichero binario referenciado por f (cuarto parámetro) n elementos (tercer parámetro) de tamaño t (segundo parámetro). Dichos elementos constituyen un bloque de datos que se encuentra en una zona de memoria referenciada por el puntero p (primer parámetro).
fwrite(void *, unsigned, unsigned, FILE *);
Ejemplo 5.4. Escribir un programa que almacene los elementos de una matriz de dimensión 3x3 en un fichero denominado MATRIZ.TXT, y posteriormente recupere los datos guardados en dicho fichero y los asigne a otra matriz. Finalmente, el programa deberá mostrar en pantalla el contenido de esta segunda matriz que contiene los datos leídos del fichero.

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

int main() {

  int i, j;

  int matrizsal[3][3
] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}},
      matrizent[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
 
  FILE *fich;

  if ((fich = fopen("matriz.bin", "w")) == NULL) {
     puts("Error en apertura del fichero.\n");
     exit(1);        
     }

  fwrite((void *) matrizsal, sizeof(int), 9, fich);
  fclose(fich);
 
  if ((fich = fopen("matriz.bin", "r")) == NULL) {
     puts("Error en apertura del fichero.\n");
     exit(2);
     }

  fread((void *) matrizent, sizeof(int), 9, fich);
  fclose(fich);

  printf("Elementos de la matriz recuperada del fichero.\n\n");
  for (i = 0; i < 3; i++) {
      printf("  [");
      for (j = 0; j < 3; j++)
          printf("%d ", matrizent[i][j]);
      printf("\b]\n");   
      }

  putchar('\n');
  system("pause");
  return 0;
}

Ejercicio: Modificar el ejemplo 5.4 para que las funciones fread() y fwrite() realicen sus respectivas operaciones sobre un único bloque de datos, en lugar de efectuarlas de forma separada sobre cada uno de los datos pertenecientes a dicho bloque.

La función feof()
indica si se ha alcanzado el final de un fichero al leer su contenido. Devuelve un valor distinto de cero si se ha llegado al final del fichero y cero en caso contrario.
int feof(FILE *);
La función rewind() reinicializa el indicador de posición del archivo y lo sitúa al principio del mismo.
void rewind(FILE *);

Ejercicios de afianzamiento.

Escribir un programa que acceda a un fichero de palabras denominado PALABRAS.TXT, recupere las palabras que contiene dicho fichero y las muestre en pantalla. Dicho programa utilizará la función feof() para determinar cuándo se ha llegado al final del fichero.
Nota: El uso de la función fscanf() con el especificador de formato "%s" permite leer de un fichero una cadena de caracteres: Un conjunto de caracteres que finaliza con uno especial denominado carácter de fin de cadena ('\0' o NULL) y se almacena en un array.

Desarrollar un programa para crear un fichero que almacene los resultados de aplicar la ley de Ohm para corriente continua (V = I * R). Dicho programa tomará valores de resistencia en incrementos de 10 Ω, a partir de un valor inicial 10 Ω y hasta un valor final de 150 Ω, y admitirá como entrada un valor de intensidad de corriente expresado en miliamperios. A continuación, pasará el valor anterior a amperios, calculará los correspondientes valores de tensión y los escribirá de forma consecutiva en un fichero denominado TENSIONES.TXT.

Codificar un programa que lea de un fichero, cuyo nombre deberá ser introducido por el usuario a través del teclado, una lista de veinte números reales de doble precisión, los almacene en un array y finalmente los escriba en orden inverso en otro fichero cuyo nombre también se solicitará a través del teclado.
Nota: El uso de la función scanf() con el especificador de formato "%s" permite leer del teclado una cadena de caracteres.


Escribir un programa que lea de un fichero, cuyo nombre deberá ser introducido por el usuario a través del teclado, una lista de veinte palabras, las almacene en un array y finalmente las escriba en orden inverso en otro fichero cuyo nombre también se solicitará a través del teclado.

Desarrollar un programa que indique mediante un mensaje en la pantalla si una matriz real de orden 3x3, leída desde un fichero cuyo nombre será introducido por el usuario a través del teclado, es o no simétrica. Una matriz es simétrica si coincide con su traspuesta (Aij = Aji). Dicho programa hará uso de las funciones auxiliares carga() y simetrica() para leer la matriz a partir del fichero indicado y determinar si ésta es simétrica respectivamente. La primera de éstas tendrá como parámetro de entrada un array de caracteres que contenga el nombre del fichero y como parámetro de salida una matriz 3x3. La segunda aceptará como parámetro de entrada una matriz 3x3 y devolverá como resultado a la función principal uno (1) si la matriz es simétrica y cero (0) si no lo es.

Codificar un programa que a partir de dos matrices reales de orden 5x5 muestre por pantalla las matrices suma y producto. La función principal utilizará cuatro funciones auxiliares: La primera de ellas leerá las dos matrices operandos desde un fichero cuyo nombre es MATRICES.TXT, la segunda función calculará la matriz suma, la tercera función calculará la matriz producto y la cuarta función escribirá las dos matrices resultado anteriores en un fichero de salida denominado RESULTADOS.TXT.