Está en la página 1de 12

TEMA 8.

Ficheros
1. Introducción
Durante la ejecución de un programa los datos que representan las variables están
almacenados en la memoria del ordenador. Pero como sabemos cuando el programa
termina todos los datos se pierden. Con lo que sabemos hasta este tema, el resultado de
la ejecución de un programa sólo puede mostrarse en la pantalla mediante una sentencia
printf. Evidentemente esta solución es poco operativa en un programa real, ya que
ningún programa sería reutilizable. Lo adecuado es que los resultados de un programa
se guarden en un dispositivo de memoria permanente para que puedan ser reutilizados
como entrada para sí mismo u otros programas.

El mecanismo habitual usado en programación para guardar datos de forma permanente


es la estructura denominada fichero o archivo. Mediante su uso el programador puede
introducir datos en su programa sin necesidad de recurrir a la habitualmente poca
operativa lectura desde teclado y escribir los resultados de forma que puedan ser
consultados después de la ejecución.

El fichero es una estructura lógica que se debe conectar en el programa con el nombre
de un archivo en el sistema operativo. De esta forma, cuando un programa lee los datos
de un fichero, está accediendo a la información contenida en un archivo de una carpeta
del sistema operativo que lógicamente debe existir previamente. Cuando se escriben
resultados en un fichero, el archivo resultante se crea en una carpeta del árbol de
directorio y podrá ser usado con posterioridad como entrada de datos de otro programa.

En el lenguaje C (y en otros lenguajes) se distingue entre dos tipos de ficheros: de texto


o de registros (también llamados binarios). Los ficheros de texto son ficheros editables
con editores de texto plano como el Bloc de Notas de Microsoft. En ellos se pueden
guardar o leer datos de tipos básico como números enteros o reales, caracteres y cadenas
de caracteres normalmente separados por blancos o tabuladores. Los ficheros binarios
guardan en disco secuencias de bytes codificados en un formato no visible para los
editores de texto. Se pueden definir sobre cualquier tipo aunque lo más común es sobre
el tipo struct por eso se les llama también ficheros de registros.

Los ficheros de texto son usados en numerosos programas de índole científica para
introducir información de datos numéricos o etiquetas de resultados experimentales.
También como contenedor de los resultados de la computación que pueden ser
fácilmente visualizados por editores de texto plano. Normalmente su uso es mediante
lectura o escritura secuencial de principio a fin.

Los ficheros de registros son usados en aplicaciones de gestión, donde se trabajan con
los datos de los pacientes de un hospital, los empleados de una empresa o los alumnos
de un centro docente. En este tipo de programas no se trata solo de hacer una lectura
secuencial, sino que es posible necesitar un acceso directo a una información del fichero
para su consulta y actualización. Por ejemplo, para modificar la fecha de alta de un
paciente concreto, no debe ser necesario recorrer todo el fichero de pacientes, sino que
lo lógico es poder acceder directamente al registro mediante una clave única, por
ejemplo su DNI. El C no es un lenguaje de programación pensado para el propósito de
realizar aplicaciones de gestión, por esa razón su capacidad para manejar este tipo de
ficheros es muy limitada.

En este manual, nos vamos a centrar sólo en los ficheros de texto.

2. Ficheros de texto en C

2.1 Declaración. En C los ficheros se declaran usando la palabra reservada FILE. Por
ejemplo, la variable fich será de tipo fichero declarada con una sentencia como:

FILE * fich;

Nótese que FILE debe llevar por delante el símbolo * porque realmente un fichero es
una variable de tipo puntero al tipo FILE. Una forma de obviar esta declaración es
redeclarando el tipo FILE * con otro nombre:

typedef FILE * Fichero;

de esta manera se pueden declarar variables del nuevo tipo Fichero:

Fichero fich;

2.2 Apertura y cierre de ficheros. La apertura de un fichero en C se realiza mediante


la función fopen. Esta función “conecta” una variable de tipo fichero con el archivo
correspondiente en el sistema operativo. Asimismo debe indicar si el fichero se abre en
modo lectura (para obtener datos de él) o en modo escritura (para guardar datos). Por
tanto, la función fopen recibe dos argumentos de tipo cadena de caracteres. El primero
es el nombre del archivo en el sistema operativo, incluyendo si fuera necesario el path.
El segundo argumento es el tipo de apertura. En este manual sólo vamos a estudiar los
tipos de lectura “r”, escritura “w” o añadir “a”1. La función fopen devuelve una variable
de tipo FILE * que es la variable que se conecta con el archivo cuyo nombre es el
primer argumento. Veamos algunos ejemplos:

1. fich = fopen("datos.txt","r");
2. fich = fopen("resultados.dat", "w");

1
Existen en C otras posibilidades: Los modos “r+”, ”w+” y “a+” son distintos modos para poder realizar
en un mismo fichero lectura y escritura. Además de que son muy engorrosos de utilización, tienen poca
utilidad en ficheros de texto.
3. fich = fopen(".\\Debug\\datos.dat","r");
4. fich = fopen("..\\..\\datos.dat","a");

En la sentencia 1, la variable fich representa al archivo datos.txt que es abierto en modo


lectura, es decir, para extraer información de él. Cuando se abre un fichero en modo “r”
el fichero debe existir previamente en el sistema de archivos, sin la variable fich tomará
el valor null. En este primer caso el argumento con el nombre del archivo no lleva path,
por tanto el archivo deberá estar en la misma carpeta (o directorio) que el fichero fuente
.c que contiene la sentencia2.

En la sentencia 2, el archivo es abierto para escritura, de forma que si no existe, se crea


y si existiera se borra y se crea nuevo. Igual que en el caso anterior al no contener path
la cadena del primer argumento, el archivo se crea en la misma carpeta que está el
archivo fuente.

En la sentencia 3, el archivo datos.dat debe estar en una carpeta de nombre Debug que
cuelga de la carpeta por defecto. Como la apertura es en modo lectura, el archivo debe
existir.

Finalmente en la sentencia 4, el archivo datos.dat se sitúa dos carpetas por arriba en el


árbol de directorios de dónde está el archivo con el código C. 3 El archivo se abre para
añadir, por tanto, se escribirá a continuación de la última línea. Si el archivo no existiera
se crearía en blanco.

Nótese que aunque el modo de apertura sólo es un carácter (a, w ó r), se escribe entre
dobles comillas porque el compilador espera una cadena de caracteres, no un tipo char.

Para cerrar un fichero la sentencia es única independientemente del modo de apertura.


La función fclose, recibe como argumento la variable de tipo fichero, desconectando el
archivo físico de la variable. Es importante cerrar el fichero sobre todo cuando se ha
abierto en modo escritura o añadir porque si no se hace, podría no guardarse en el
archivo correspondiente toda la información escrita. Por ejemplo:

fclose(fich);

2
Esta característica puede depender del compilador y sistema operativo que estemos usando. Con el
Microsoft Visual Studio, el compilador crea una carpeta para cada proyecto con el nombre del proyecto
debajo de un directorio Projects. Dentro de esa carpeta si nuestro fichero de código .c ó .cpp está en la
carpeta por defecto, esto es, “Archivos de código fuente” entonces el fichero de código fuente estará en
otra carpeta con el mismo nombre del proyecto. Por ejemplo si tenemos el proyecto pruebafichero en
Visual Studio tendríamos este path hasta el código fuente: “…..\Visual Studio
2010\Projects\pruebafichero\pruebafichero”, donde los puntos iniciales dependen de dónde hallamos
instalado Visual Studio. Dentro de la última carpeta estará el fichero fuente .c y es ahí dónde por defecto
deberemos poner nuestros ficheros de texto para lectura y dónde se crearan los de escritura.
3
En Microsoft Visual Studio estaría en la carpeta Projects
2.3 Uso de ficheros para lectura. Una vez invocada la función fopen con el argumento
“r” lo primero que se debe hacer es comprobar si la apertura se ha realizado
correctamente, es decir, si la variable fichero tiene valor null o no. Un valor de null en
una variable fichero después de la apertura para lectura indica que el fichero no existe
en la carpeta donde se le esperaba. Si es en modo escritura, el valor de null indicaría
disco lleno o protegido contra escritura.
Por tanto es necesario comprobar que la variable fichero tiene un valor válido, mediante
una sentencia if:

f=fopen(nomfich,"r"); //nomfich de tipo array de char o Cadena


if (f==NULL){
printf("El fichero %s no existe",nomfich);
}
else {
// tratamiento del fichero
}

2.3.1 Lectura de datos simples de un fichero. La forma más simple de leer datos de un
fichero de texto es mediante la función fscanf que tiene tres argumentos. El primero es
la variable de tipo fichero de la que vamos a leer, el segundo argumento es el formato de
lectura con el tipo de las variables a leer y el tercer argumento es la lista de variables a
leer. Por tanto la función fscanf es similar a scanf salvo que tiene un argumento más (el
primero) con la variable de tipo fichero.

Ejemplo 1. Supongamos que tenemos un fichero de texto con la siguiente estructura:

6
23.4 32.3 -8.7 65.3 -28.7 43.5

Es decir, en primer lugar hay un número entero y después tantos valores reales como
indique el primer número. Se desea construir una función que reciba como argumento
de entrada una cadena de caracteres con el nombre de un fichero y devuelva como
salida un array de reales con los valores contenidos en la segunda línea del fichero de
texto y en el nombre de la función debe devolver el tamaño real del array, esto es el
valor de la primera línea. Su código sería

int lecturaFichero(Cadena nomfich, TablaReales v){

Fichero f;
int numelem,i;

f=fopen(nomfich,"r");
if (f==NULL){
printf("El fichero %s no existe", nomfich);
}
else {
fscanf(f,"%d",&numelem);
for(i=0;i<numelem;i++){
fscanf(f,"%f",&v[i]);
}
fclose(f);
}
return numelem;
}

Nótese como las invocaciones a fscanf son similares a si quisiéramos leer desde teclado
con la función scanf. Esto es, el formato de las variables a leer es %d para int y %f para
float. Igualmente es necesario un & delante de las variables a leer y por eso se escribe
&numelem y &v[i]. En este código suponemos definidos los siguientes tipos y
constantes:

#define NUMCAR 256


#define NUMVAL 16

typedef FILE * Fichero;


typedef char Cadena[NUMCAR];
typedef float TablaReales[NUMVAL];

Existen también otras posibilidades para leer desde fichero de texto, como son las
funciones fgets para leer cadenas de caracteres que incluyan blancos y fgetc para leer
variables de tipo char. En general, hay que tener en cuenta que no es adecuado mezclar
en la lectura de un fichero de texto datos de distinto tipo y desde luego dificulta mucho
la operativa si los datos se separan por comas u otro tipo de caracteres especiales
distintos de blancos y tabuladores.

Ejemplo 2. Supongamos un fichero similar al anterior pero conteniendo cadenas de


caracteres en vez de números reales. Es decir algo similar a lo siguiente:

7
amarillo naranja rojo verde azul añil violeta

Si queremos leer las palabras y guardarlas en un array de cadenas de caracteres,


deberemos de construir una función como:

int lecturaFichero(Cadena nomfich, TablaCadenas v){

Fichero fich;
int numelem,i;

fich=fopen(nomfich,"r");
if(fich==NULL){
printf("El fichero no existe");
}
else {
fscanf(f,"%d",&numelem);
for(i=0;i<numelem;i++){
fscanf(f,"%s",v[i]);
}
fclose(f);
}
return numelem;
}

Nótese como en la lectura de las cadenas de caracteres, las componentes del array v
están sin & en el tercer argumento de la invocación a fscanf. Habría que añadir a
nuestro programa el tipo para el array de cadenas de caracteres:

typedef Cadena TablaCadenas[NUMVAL];

2.3.2 Marca de fin de fichero. En los ejemplos anteriores el número de valores a leer del
fichero está fijado en el propio fichero como el primer valor entero del mismo. Sin
embargo, esto no es obligatorio, es decir, el número de elementos a leer de un fichero de
texto no tiene por qué ser conocido a priori. Es decir, se podrían leer tantos elementos
como tenga el fichero y construir un array con todos los datos que tuviera el fichero.
Para resolver este problema los lenguajes de programación proporcionan una función
que detecte cuando se ha terminado el fichero o no hay más datos para leer. En C esa
función se llama feof y recibe como argumento una variable de tipo fichero. La función
feof devuelve falso mientras haya datos por leer en el fichero, de forma que feof
devuelve true cuando se intenta leer después del último dato4. Eso condiciona que la
lectura de datos desde fichero en C debe hacerse siguiendo lo que se llama un “esquema
de lectura adelantada”. Este esquema consiste en hacer una lectura previa antes del
bucle de lectura y la lectura dentro del bucle hacerla al final del mismo.

Ejemplo 3. Supongamos tenemos un fichero de texto como el del Ejemplo 1 pero sin el
primer número entero sino solamente con número indeterminado de valores reales.
Queremos construir una función que reciba una cadena de caracteres con el nombre de
un fichero y devuelva un array con todos los valores reales leídos del fichero. Como es
habitual la función deberá devolver el número de elementos que hay en el array.

int lectura (Cadena s, TablaReales v){

Fichero fich;
int i=0;
float x;

fich=fopen(s,"r");
if (fichero==NULL){

4
Hay otros lenguajes donde la marca de fin de fichero se activa al leer el último dato. En C la marca se
activa cuando se intenta leer después de haber leído el último.
printf("El fichero no existe");
}
else{
fscanf(fich,"%f",&x);
while (!feof(fich)){
v[i]=x;
i++;
fscanf(fich,"%f",&x);
}
fclose(fich);
}
return i;
}

Nótese como hay una lectura antes del bucle while. Si el fichero estuviera vacío, esta
lectura activaría la función feof que devolvería cierto y entonces el bucle no se
ejecutaría ni una sola vez, devolviendo i con un valor cero. Si el fichero contiene algún
elemento entonces la primera lectura guarda en x el valor leído, feof devuelva falso y
como la condición del while está puesta con el operador lógico No, entonces
!feof(fich) devolvería cierto y el flujo del programa entraría en el bucle. Dentro del
bucle el valor x leído fuera se asignaría a la primera posición del vector v (i está
inicializada a 0), se incrementaría el contador i en uno y se volvería a leer la misma
variable x, de forma que si el fichero tenía más de un valor, volvería a la condición del
while que seguiría siendo cierta, la guardaría en la siguiente posición del array y así
sucesivamente. Cuando se lee el último dato5, todo continua igual, esto es, la condición
sigue siendo cierta, se guarda x en v, se incrementa la i pero cuando se va a volver a
leer, se lee “la marca de fin de fichero”, feof devuelve true, con el ! se cambia a false y
por tanto el bucle finaliza y la función termina devolviendo el número de datos leídos.

Para la lectura no haría falta usar una variable x sino que directamente se podría leer la
posición correspondiente del array v como se hizo en los ejemplos 1 y 2:

int lectura (Cadena s, TablaReales v){


Fichero f;
int i=0;
float x;

f=fopen(s,"r");
if(f==NULL){
printf("El fichero no existe");
}
else{
fscanf(f,"%f",&v[i]);
while (!feof(f)){
i++;
fscanf(f,"%f",&v[i]);

5
Para estar seguros de leer todos los datos contenidos en el fichero, es conveniente dejar una línea en
blanco al final del mismo. Esto es, dejar un retorno de carro después del último número.
}
fclose(f);
}
return i;
}

Otra cuestión a tener en cuenta es si queremos comprobar que no se leen más datos de la
dimensión o tamaño máximo del array. Si la dimensión del tipo TablaReales es
NUMVAL (16 en nuestro ejemplo) lógicamente si el fichero de texto contiene más de
16 valores se produciría un error en tiempo de ejecución si intentáramos almacenar en v
más valores de los que permite NUMVAL. Para controlar esta cuestión bastaría con
incluir una condición más en el while de lectura que controlara que el contador no
sobrepase este valor umbral. De esta manera la condición podría quedar:

while (!feof(fich) && i<NUMVAL-1)

Como ya sabemos las condiciones de los bucles de los arrays deben ser menor estricto
que la dimensión (véase el Tema 5 de tratamientos secuenciales) pero en este caso,
dentro del bucle se hace i++ y después se lee v[i], por tanto, la condición previa debe ser
parar uno antes de lo habitual.

2.3.3 Lectura de un array de struct. En numerosas ocasiones la información en un


fichero de texto está estructurada de forma que cada línea contiene la información que
conforma un registro (struct). Para leer esa información y cargarla en un array de struct
lo más adecuado es dividir el problema en dos funciones. La primera función lee una
sola línea del fichero guardando la información en un struct. La segunda función
reutiliza la primera de forma que recibiendo un argumento de tipo cadena de caracteres
con el nombre de un fichero, devuelve la información del fichero en un array de struct o
en un struct que encapsule la información del array junto a su tamaño.

Ejemplo 4. Supongamos tenemos la información de un conjunto de ciudades con los


valores de precipitación, temperatura máxima y mínima de un día. Las líneas del fichero
tendrían una información como la siguiente:

2017 12 17
Bogota Colombia 9.7 10.8 4.2
Rio_de_Janeiro Brasil 10.3 10.8 6.4
Brasilia Brasil 0.2 12.4 0.6
Bruselas Belgica 0.0 17.4 9.8
Bucaramanga Colombia 0.0 15.6 8.4

Los tipos de datos necesarios para guardar esa información serían:

typedef struct{
int dia,mes,anyo;
}Fecha;
Tema 8. Ficheros

typedef struct{
Cadena ciudad, pais;
float precip, tempmax, tempmin;
} Registro;

typedef Registro TablaDatos[MAX_DATOS];

typedef struct{
Fecha fecha;
TablaDatos datos;
int numreg;
} ClimaDiario;

La primera función para leer una línea del fichero recibe un argumento de tipo Fichero6
y su código es el siguiente:

Registro leeRegistroFichero(Fichero f){ //FILE *f


// Lee una linea del fichero y contruye un Registro
Registro r;

fscanf(f, "%s%s%f%f%f", r.ciudad, r.pais,&r.precip,&r.tempmax,


&r.tempmin);
return r;
}

La segunda función lee el fichero completo usando la función anterior, recibiendo como
argumento su nombre, abriéndolo con la función fopen y comprobando que el fichero
está en el directorio que se espera.

ClimaDiario leerClimaDiarioFichero(Cadena nomfich){


// Lee un fichero y construye un 'ClimaDiario'
ClimaDiario c;
Registro r;
Fichero f;
int i = 0;

f=fopen(nomfich,"r");
if (f==NULL)
printf("El fichero %s no existe", nomfich);
else {
r = leeRegistroFichero(f);
while(!feof(f)) {
c.datos[i] = r;
i++;
r = leeRegistroFichero(f);
}

6
El argumento de tipo FILE * es un argumento de E/S como indica el hecho de que esté definido
mediante un puntero. Debe ser así porque no sólo recibe una variable fichero sino que recibe el “lugar”
del fichero por el que tiene que empezar a leer y debe devolver el “lugar” del fichero donde ha
terminado de leer, de forma que la siguiente vez que se invoque una función de lectura con la misma
variable fichero “sepa” por dónde debe seguir leyendo.
Autor: José C. Riquelme (Universidad de Sevilla)

c.numreg = i;
}
fclose(f);
return(c);
}

2.4 Uso de ficheros para escritura. Una vez invocada la función fopen con el
argumento “w” o “a”, es muy raro que se haya producido error. En modo escritura, el
valor de null indicaría disco lleno o protegido contra escritura, que no suele ser una
situación habitual. Por tanto normalmente no es necesario comprobar que la variable
fichero tiene un valor válido mediante una sentencia if.

La forma más simple de escribir en un fichero es mediante la sentencia fprintf que


funciona exactamente igual que printf salvo que tiene un argumento más (el primero)
con una variable de tipo fichero que es donde se va a escribir.

Ejemplo 5. Queremos construir una función tal que dada una cadena de caracteres con
el nombre de un fichero y un array bidimensional de números reales escriba en el
fichero el contenido del array.

void escribeMatriz(const Cadena nomfich, const MatrizReal m, int nf,


int nc){
int i,j;
Fichero f;

f=fopen(nomfich,"w");

for(i=0;i<nf;i++){
for(j=0;j<nc;j++){
fprintf(f,"%f ",m[i][j]);
}
fprintf(f,"\n");
}
fclose(f);
}

3. Ejercicios resueltos

3.1 Lectura de ficheros


http://laprogramacionnoesunarte.blogspot.com.es/p/7.html

3.2 Escritura de ficheros


http://laprogramacionnoesunarte.blogspot.com.es/p/blog-page_3.html

4. Problemas
Tema 8. Ficheros

1. Implemente una función tal que dada una cadena de caracteres con el nombre de
un fichero almacene en un array bidimensional los valores reales del mismo. El
fichero tendrá en la primera fila dos valores enteros con el número de filas y
columnas que tendrá el array. Por ejemplo:
53
23.4 -45.5 54.3
12.4 -12.3 12.3
32.1 23.4 -12.4
23.4 12.3 87.4
-34.5 65.6 73.2

2. Realice las modificaciones oportunas en las funciones de los ejemplos del tema
para implementar la solución al siguiente problema: leer desde un fichero una
serie de palabras hasta el final y después escribirlas en otro fichero en orden
inverso a como estaban en el primer fichero y por tanto a como fueron leídas.

Teniendo en cuenta la definición de tipos de los problemas del Tema 7, resuelva los
siguientes ejercicios:

3. Implemente una función tal que dado nomfich un argumento de tipo Cadena lea
de un fichero de texto con ese nombre (nomfich) los 720 datos de la presión
sanguínea de un Paciente devolviéndolos en un array. La función por tanto tiene
como argumento de entrada una Cadena y devuelve un array con los 720 valores
de presión.

4. Tenemos varios ficheros cuyos nombres son códigos de pacientes. Implemente


una función tal que se le dé una cadena de caracteres con el código de un
paciente y devuelva un tipo Paciente con ese código de identificación y “relleno”
el array con los valores de la presión sanguínea extraídos del fichero de texto
correspondiente (el array de expresión genética no será tratado, poniendo el
valor del campo número de genes a 0). Para leer los datos del array de presión
sanguínea use la función del ejercicio 3.

5. Implemente una función tal que dado nomfich un argumento de tipo Cadena, lea
de un fichero de texto los códigos y notas de un alumno, devolviendo un tipo
Alumno que tenga como DNI el nombre del fichero (nomfich). El primer valor
del fichero será el número de asignaturas y después cada código de asignatura
con su nota.

Ejemplo fichero 12345678A


6 234 7.4 321 4.3 456 7.5 342 6.3 213 3.2 348 2.3 // el alumno 12345678A se ha
matriculado de 6 asignaturas
Ejemplo fichero 87654321Z
8 233 3.4 311 6.2 454 9.2 342 8.1 232 4.2 349 5.3 754 7.5 456 3.4 // el alumno
87654321Z tiene 8 asignaturas

También podría gustarte