9 GESTIÓN BÁSICA DE FICHEROS

Anuncio
9
GESTIÓN BÁSICA
DE
FICHEROS
Contenido
___________________________________________________________________________
9.1.- Introducción .
9.2.- Archivos C++.
9.2.1.- Apertura de ficheros.
9.2.2.- Cierre de ficheros.
9.2.3.- Detección de fin de fichero y otras funciones.
9.2.4.- Comprobación de apertura correcta.
9.3.- Lectura/Escritura en ficheros de texto.
9.3.1.- Avance del cursor.
9.3.2.- Ficheros de texto.
9.4.- Ficheros binarios.
9.4.1.- Utilidad de los ficheros binarios.
9.4.2.- Lectura/Escritura byte a byte.
9.4.3.- Lectura/Escritura por bloques de bytes.
9.5.- Acceso aleatorio a ficheros.
9.6.- Ficheros pasados como argumentos.
9.7.- Ejemplos de utilización de ficheros binarios y de texto.
Ejercicios.
___________________________________________________________________________
En este capítulo vamos a tratar la gestión básica de ficheros (también llamados archivos) en
C++ y lo haremos a partir del concepto de flujo. Es importante hacer notar que no trataremos el
concepto de fichero como tradicionalmente se ha empleado en el lenguaje C pues esto requeriría
el conocimiento del concepto de puntero el cual se trata en temas posteriores.
Vamos a estudiar cómo declarar los identificadores que establecen el modo de flujos de los
ficheros (de entrada o salida básicamente) y procuraremos no entrar en el concepto de clase de
___________________________________________________________________________
manera que la gestión de ficheros resulte asimilable por el alumno con los conceptos aprendidos
hasta el momento.
9.1.- INTRODUCCION.
En este tema se verán las características básicas que nos permitirán trabajar con ficheros
(también llamados archivos).
• ¿ Por qué se necesitan los ficheros?
Todos los programas vistos hasta ahora trabajaban con información almacenada en
memoria principal, no obstante, hay situaciones en que esto no es apropiado. Algunas de esas
situaciones podrían ser:
* Los datos con los que necesita trabajar el programa son demasiado grandes (ocupan
mucha memoria) para que entren en la memoria principal.
* Nos interesa mantener la información después de cada ejecución, necesitamos
utilizar datos procedentes de otros programas (editores, etc.), o generar datos para
que puedan ser utilizados por otros programas.
En dichos casos se utilizarán ficheros para contener la información en memoria
secundaria (disco duro, disquetes, etc.).
• Definición de Fichero:
Es una colección de elementos lógicamente relacionados y almacenados en memoria
secundaria. A más bajo nivel, un fichero es una secuencia de bits almacenado en algún
dispositivo externo (como por ejemplo uno de memoria secundaria).
9.2.- ARCHIVOS EN C++.
En los temas anteriores hemos visto que los datos de entrada o salida en un programa
C++ son obtenidos o enviados directamente a través de lo que llamamos flujos de entrada o
salida. Hasta hora hemos manejado los flujos estándares cin y cout, y una serie de operadores
(<< y >>) y funciones (como get o getline por ejemplo) disponibles a partir de la biblioteca
iostream y que son útiles para la entrada/salida de la información.
En C++ un fichero es simplemente un flujo externo que se puede abrir para entrada
(dando lugar a un flujo de archivo de entrada que, para simplificar, llamaremos simplemente
archivo o fichero de entrada), para salida (dando lugar a un flujo de archivo de salida que,
para simplificar, llamaremos simplemente archivo o fichero de salida) o para entrada-salida
(archivo o fichero de entrada-salida o archivo de E/S).
___________________________________________________________________________
!
C++ soporta dos tipos de archivos: de texto y binarios. Los primeros almacenan datos
como códigos ASCII. Los valores simples, tales como números y caracteres están separados
por espacios. Los segundos almacenan bits de forma directa y se necesita usar la dirección de
una posición de almacenamiento.
Una biblioteca en C++ que proporciona “funciones” y operadores para el manejo de
ficheros es la biblioteca fstream. En general emplearemos ésta para la gestión básica de
ficheros por lo que deberemos incluir el archivo de cabecera correspondiente en nuestros
programas mediante la declaración adecuada, o sea, incluyendo la directiva de preprocesamiento siguiente
#include <fstream.h>
La biblioteca fstream ofrece un conjunto de funciones y operadores comunes a todas las
operaciones de Entrada/Salida (E/S) de ficheros.
9.2.1.- Apertura de ficheros.
Antes de que un programa pueda manipular un fichero para leer o escribir información se
debe abrir (o crear si es necesario) el fichero para identificar la posición del mismo en el
programa (o sea, la dirección de memoria a partir de la cual almacenaremos o leeremos el
contenido del fichero). Ya hemos indicado anteriormente que los archivos son tratados como
flujos de entrada/salida por lo que, al igual que sucede con los flujos estándares y por defecto
cin y cout, la información “debe” ser transferida en una sola dirección por lo que, salvo en
ciertos casos, los ficheros deben abrirse bien para entrada o bien para salida.
9.2.1.1.- Ficheros de entrada o salida.
Supongamos que tenemos un fichero cuyo nombre en el sistema operativo es
“nombre.extensión”. Entonces hay varias formas de abrirlo.
(A) Como fichero de entrada: Para ello empleamos la sentencia
ifstream descriptor (“nombre.extensión”);
(B) Como fichero de salida: Para ello empleamos la sentencia
___________________________________________________________________________
"
ofstream descriptor (“nombre.extensión”);
En ambos casos descriptor es una variable que se asocia al fichero cuyo nombre es
“nombre.extensión”.
Comentario:
Observe que a partir de la apertura de un fichero, el descriptor del fichero será utilizado en
todas las operaciones que se realicen sobre el fichero (OJO! no utilizamos el nombre
original del fichero).
Otra forma de abrir un fichero para operar sobre él consiste en crear primero el flujo de
entrada asociado al mismo, o sea, en asociar una variable descriptor a un fichero en una
forma similar a como se declara una variable en C++, es decir, mediante una sentencia tal
como
ifstream descriptor;
// Para ficheros de entrada
ofstream descriptor;
// Para ficheros de salida
y, posteriormente, abrir el fichero (en el modo deseado: de entrada o salida por ejemplo)
mediante el uso de la función open aplicada a ese flujo de entrada, o sea en la forma
descriptor.open(“nombre.extensión”,int modo);
donde la variable modo indica el modo de apertura del fichero y los modos de apertura,
posiblemente combinados mediante el símbolo ‘|’ como veremos a continuación, pueden ser:
ios:: in
ios:: out
ios:: app
// Modo entrada
// Modo salida
// Modo añadir, o sea, posicionar el cursor del fichero (ver abajo)
// al final del fichero antes de cada escritura
ios:: binary
// El archivo se abre en modo binario
ios:: ate
// El archivo se abre y el cursor se posiciona al final
ios:: nocreate
// Genera un error si el fichero no existe
ios:: noreplace // Genera un error si el fichero existe ya
Un archivo abierto con ofstream puede serlo en dos modos:
___________________________________________________________________________
#
1. Modo salida, usando ios::out. En este caso todos los datos en el fichero se
descartan (o sea, se borra el contenido del fichero si existe previamente).
Naturalmente si el fichero no existe lo crea. En ambos casos el cursor del fichero (o
sea, la cabeza de lectura/escritura del mismo) se posiciona al principio del fichero.
Este es el modo por defecto de apertura de un fichero de salida.
2. Modo añadir, usando ios::app. En este caso los datos adicionales se añaden a
partir del final del fichero (o sea, el cursor se sitúa al final del fichero y es ahí a
partir de donde se empieza a realizar la escritura).
También se pueden combinar las dos formas anteriormente vistas de forma que el descriptor
de fichero es declarado a la vez que el fichero es abierto. Para ello empleamos las siguientes
declaraciones (según el fichero se abra para entrada o para salida):
ifstream descriptor(“nombre.extensión”,int modo);
// para entrada
ofstream descriptor(“nombre.extensión”,int modo);
// para salida
donde modo es como se ha mostrado anteriormente.
Ejemplo 1:
Las dos líneas siguientes abren el fichero “mio.txt” como fichero de entrada (para lectura) y
lo asocian al descriptor in.
ifstream in;
in.open(“mio.txt”);
// descriptor del fichero a abrir
// Apertura del fichero;
Esas dos líneas equivalen a la siguiente:
ifstream in (“mio.txt”);
// Apertura del fichero;
Ejemplo 2:
Para abrir el fichero “salida.dat” en modo salida (si el fichero no existe lo crea, y si existe
borra su contenido) asociándolo al descriptor out podemos usar la siguiente sentencia;
ofstream out("salida.dat");
o la siguiente
___________________________________________________________________________
$
ofstream out("salida.dat", ios::out);
o también
ofstream out;
out.open("salida.dat");
9.2.1.2.- Ficheros de entrada/ salida.
Un fichero puede ser también abierto para entrada-salida. En este caso emplearemos una
declaración fstream combinada con la operación de apertura correspondiente, o sea:
fstream descriptor;
descriptor.open(“nombrefichero.ext”, ios::in | ios::out)
O alternativamente podemos combinar la declaración y la apertura en una sola sentencia. Por
ejemplo:
fstream descriptor(“nombre.extensión”,ios::in | ios:: out);
// para entrada-salida
La declaración fstream puede ser usada arbitrariamente tanto sobre archivos de escritura
como sobre archivos de lectura, aunque recomendamos que para archivos de solo-lectura se
use ifstream y para aquellos de solo-escritura se use ofstream.
Comentario:
Cuando un fichero de E/S se declara con fstream, abrirlo con open debemos especificar el
modo de apertura que queremos (entrada, salida o bien entrada.salida).
Ejemplo 3:
// Abrimos el fichero “F1.dat” como fichero de entrada
fstream inout;
inout.open("F1.dat", ios::in);
// Intentamos abrir el fichero “F1.dat” pero tenemos un error puesto que no
// hemos especificado el modo de apertura
fstream inout;
inout.open("F1.dat");
___________________________________________________________________________
%
// Abrimos el fichero “F2.txt” como fichero de salida en modo AÑADIR.
fstream inout;
inout.open("F2.txt", ios::app);
// Abrimos el fichero “binario.dat” para entrada-salida binaria y le asociamos el
// descriptor ejemplo.
fstream ejemplo ("binario.dat", ios::in | ios:: out | ios::binary);
9.2.2.- Cierre de ficheros.
Un fichero anteriormente abierto y con un descriptor asociado a él debe ser cerrado con
el fin de liberar los recursos asociado a él de la siguiente forma:
descriptor.close()
9.2.3.- Detección de fin de fichero y otras funciones.
Además de las funciones open y close, existen otras funciones disponibles en la
biblioteca fstream, que pueden ser aplicadas directamente al descriptor de un fichero en la
forma
Descriptor.función();
Donde función es una de las siguientes:
•
La función eof() que devuelve "true" si se ha alcanzado el final del fichero y falso
en cualquier otro caso.
LECTURA ADELANTADA: Para que la función eof() devuelva un valor de
verdad (actualizado) es necesario, en muchos casos, realizar una operación de
lectura previa. Esto permite que se actualice el valor a devolver por la función eof
comprobándose si tras realizar dicha operación se ha llegado al final del fichero.
A este proceso lo llamaremos lectura adelantada (ver el ejemplo 6).
•
La funciones fail() o bad() que devuelven "true" si existe un error en una
operación de flujo asociada al fichero identificado por la variable descriptor
___________________________________________________________________________
&
(bien sea en una apertura, un cierre, una escritura o una lectura entre otras
posibles operaciones) y "false" en caso contrario. La diferencia entre estas dos
funciones es muy sutil. En realidad un flujo puede estar en un estado de fallo (i.e.,
fail) pero no en un estado erróneo (i.e. bad) en el caso de que el flujo no se
considere corrupto ya que no se han perdido los caracteres que provienen del
flujo (en un estado erróneo, esto puede no ocurrir). Recomendamos un vistazo a
algún manual de C++ para aclarar las diferencias tan sutiles.
•
La función good() que devuelve "true" si no existe un error en una operación de
flujo y "false" en caso contrario.
9.2.4.- Comprobación de apertura correcta
Antes de empezar a leer o escribir en un fichero es siempre conveniente verificar que la
operación de apertura se realizó con éxito. La comprobación del "buen estado" de un
determinado flujo asociado a un fichero se puede realizar preguntando directamente sobre el
descriptor de fichero asociado (al igual que se hace con los flujos estándares cin y cout) en la
forma siguiente:
if (descriptor){
\\Buen estado. Continuamos
\\ operando sobre el fichero
.....
}
if (! descriptor){
\\ Mal estado. Escribimos un mensaje
\\ a la salida estándar
cout << “Error en la apertura “
.....
}
o mejor aun, utilizando las funciones good y bad (o fail) vistas anteriormente, o sea:
if (descriptor,good()){
\\Buen estado. Continuamos
\\ operando sobre el fichero
.....
}
if (descriptor.bad()){ // o descriptor.fail()
\\ Mal estado. Escribimos un mensaje
\\ a la salida estándar
cout << “Error en la apertura “
}
Ejemplo 4:
ifstream in("F1.dat");
if (!in)
// O bien,
//
if (in.bad() )
{
___________________________________________________________________________
'
cout << "\n Incapaz de crear o abrir el fichero "; // salida estándar
cout << " para salida " << endl;
}
else{
…. // Se opera sobre el fichero
}
9.3.- LECTURA-ESCRITURA EN FICHEROS (DE TEXTO).
9.3.1.- Avance del cursor
Tanto para los ficheros de texto como para los ficheros binarios existe lo que se
denomina el cursor o apuntador del fichero que es un índice que direcciona una posición
relativa del fichero. El cursor marca en todo momento la posición actual del fichero a partir
de la cual va a tener lugar la siguiente operación de entrada-salida a ejecutar sobre el mismo
(bien sea una operación de lectura o una de escritura). Cada vez que se realiza una operación
de entrada o salida, el cursor del fichero avanza automáticamente el número de bytes leídos o
escritos.
9.3.2.- Ficheros de texto
La lectura y la escritura en un archivo de texto se puede realizar directamente con los
operadores << y >> al igual que se realiza sobre los flujos estándares cin y cout.
Ejemplo 5:
El siguiente programa escribe tres líneas en un fichero llamado “EJEMPLO5.TXT” que se
crea en el programa (si ya existe borramos su contenido). Cada línea consta de un entero, un
real y una cadena de caracteres. Los datos en cada línea están separados por espacios en
blanco.
#include <fstream.h> // Biblioteca para el manejo de ficheros
#include <iostream.h> // Biblioteca para la entrada-salida estándar
int main(){
ofstream fichout("EJEMPLO5.TXT",ios::out);
if (!fichout)
cout << "\n Incapaz de crear este o abrir el fichero \n";
else {
fichout << 1 << " " << 5.0 << " APROBADO" << endl; // Escritura en el fichero
fichout << 2 << " " << 1.1 << " SUSPENSO" << endl;
fichout << 3 << " " << 8.0 << " NOTABLE " << endl;
fichout.close();
}
} // Fin del main
___________________________________________________________________________
(
Comentario:
El operador >> omite los espacios en blanco al igual que ocurría en la entrada estándar.
Ejemplo 6:
El siguiente programa lee el fichero de texto “EJEMPLO5.TXT”, creado en el ejemplo
anterior, y visualiza su contenido en el monitor.
#include <fstream.h> // Libreria para el manejo de ficheros
#include <iostream.h>
typedef char TCadena[30];
int main(){
int i;
float r;
TCadena cad;
ifstream fichin("EJEMPLO5.TXT"); // declaracion y apertura del fichero
if (!fichin)
cout << "\n Incapaz de crear o abrir el fichero ";
else{
fichin >> i;
// Observese la lectura adelantada!!!
while (!fichin.eof()){
cout << i << " ";
// Lectura de valores en el fichero
fichin >> r;
cout << r << " ";
// Lectura de valores en el fichero
fichin >> cad;
cout << cad << "\n";
// Lectura de valores en el fichero
fichin >> i;
}
fichin.close();
} // Fin del main
Comentarios:
(1) Observe en el ejemplo anterior que ha sido necesario realizar una lectura adelantada
previamente al chequeo del fin de fichero. Si no se realiza de esta forma podriamos tener
problemas.
(2) Observe también que no es necesario realizar una lectura para “saltarse” los espacios en
blanco que fueron introducidos en el fichero “EJEMPLO5.TXT” en el ejemplo 5. Esto es
debido a que, como ya se comentó anteriormente, el operador >> omite los espacios en
blanco
(3) No olvide cerrar los ficheros!!
9.4.- FICHEROS BINARIOS.
Hasta ahora hemos trabajado con ficheros de texto, éstos pueden ser vistos como una serie de
___________________________________________________________________________
)
caracteres que pertenecen a un determinado código de entrada/salida, en nuestro caso al
código ASCII. También hemos visto cómo se abre un fichero binario. En esta sección vamos
a ver cómo podemos leer y escribir datos en binario.
9.4.1.- Utilidad de los ficheros binarios.
En esta sección explicamos la diferencia de los ficheros binarios con los de texto y lo
hacemos a partir de un ejemplo sencillo..
Por ejemplo, si creamos un fichero con
ofstream fich("F1.dat",ios::out);
if (!fich) {
cout << "\n Incapaz de crear este o abrir el fichero ";
cout << " para salida " << endl;
}
else{
fich << "HOLA ADIOS";
fich.close();
}
// Escribe la cadena en el fichero "F1.dat"
“F1.dat” será un fichero que contiene 10 bytes, donde cada uno será el código ASCII
correspondiente a cada uno de los caracteres que hemos escrito en el fichero (un carácter
ocupa un byte). Esta característica hace que un fichero de texto pueda ser usado por otros
programas que supongan que el contenido del fichero es un conjunto de caracteres del código
ASCII.
Sabemos que los datos se almacenan en memoria según las características del tipo con
que han sido declarados. Así, para:
char C;
unsigned N;
La variable C usa un byte en memoria principal, y en el se almacena el código ASCII de
un carácter, mientras que la variable N, al ser de tipo entero sin signo, usa dos bytes, y en
ellos se almacena el código binario correspondiente a un número entero sin signo. Esta
característica sugiere que para almacenar en el fichero de texto el número contenido en la
variable N no basta con grabar directamente los dos bytes en el fichero (Ejemplo, si N
contiene el número 13864, para almacenarlo en el fichero se requieren 5 caracteres, mientras
___________________________________________________________________________
que en memoria la variable N solo ocupa dos bytes). Así pues, una instrucción
fich << N;
ocasionará dos pasos:
1) Convertir N a su configuración en caracteres ASCII.
2) Escribir en el fichero los caracteres ASCII obtenidos.
En el caso del tipo CHAR, no es necesario efectuar la conversión pues la variable en
memoria principal se corresponde con (es decir, contendrá como valor) el código ASCII
correspondiente al carácter almacenado en la misma.
Hay situaciones en las que en lugar de convertir los valores a escribir en el fichero en una
serie de caracteres, puede resultar útil efectuar la grabación en el fichero directamente a partir
contenido de la memoria. A los ficheros creados de esta forma se les llama ficheros binarios.
Ventajas del uso de ficheros binarios::
• El proceso de lectura ó escritura es más rápido, pues nos ahorramos el tiempo
necesario para la conversión.
• Normalmente un fichero binario ocupa menos memoria que su correspondiente
fichero de texto (Ejemplo, escribir 32534 en formato de texto ocuparía cinco
bytes, mientras que hacerlo en formato binario ocuparía dos bytes).
Inconvenientes del uso de ficheros binarios:
• No podrán ser usados por programas de carácter general. Por ejemplo, si intento
usar la orden TYPE (de MS-DOS) o la orden ls (de UNIX) con un fichero
binario, la información que obtengo por pantalla no es legible.
De todo lo dicho anteriormente se desprende que es conveniente usar ficheros de texto
cuando pueda interesarnos tratar la información contenida en éstos con programas de carácter
general (Editor, TYPE, PRINT, etc.), y en otro caso usar ficheros binarios.
Tratamiento de ficheros binarios.
Puesto que no hay que distinguir entre diferentes formatos para diferentes tipos de datos,
sólo se necesitan instrucciones que lean y escriban un cierto número de bytes por lo que en
realidad sólo necesitamos un tipo de instrucción para lectura y un tipo de instrucción para
escritura. Aún así veremos dos formas diferentes de leer y escribir desde y en ficheros
binarios.
___________________________________________________________________________
!
9.4.2.- Lectura/Escritura byte a byte.
9.4.2.1.- Lectura.
Para leer un carácter (es decir un byte) desde un fichero usamos la función get aplicada sobre
el descriptor del mismo de la siguiente manera
descriptor.get(ch);
Esta instrucción extrae un único carácter del flujo de entrada, incluyendo el espacio en blanco
y lo almacena en la variable ch (que debe ser declarada de tipo carácter). Esta función avanza
además un byte la posición del cursor del archivo identificado por la variable descriptor.
Ejemplo 7:
El siguiente programa lee (byte a byte) un fichero binario que contiene caracteres y visualiza
su contenido en el monitor.
#include <fstream.h> // Libreria para el manejo de ficheros
#include <iostream.h>
int main()
{
char c;
ifstream fichin("F1.dat",ios::in | ios::binary);
if (!fichin)
cout << "\n Incapaz de Abrir el fichero ";
else{
fichin.get(c);
// LECTURA ADELANTADA!!
while (fichin){
// Tambien vale 'while (!fichin.eof())'
cout << c;
fichin.get(c);
};
fichin.close();
} // Fin del main
9.4.2.2.- Escritura.
Para escribir o mandar un carácter (es decir un byte) a un archivo de de salida usamos la
función put aplicada al descriptor de fichero del archivo de la siguiente manera
descriptor.put(ch);
___________________________________________________________________________
"
Esta instrucción coloca el carácter contenido en ch en el archivo de salida identificado por la
variable descriptor (o sea, en su caso en el fichero asociado a dicho descriptor) y lo hace a
partir de la posición actual del cursor. A continuación avanza el cursor del fichero al
siguiente byte.
Ejemplo 8:
El siguiente programa escribe un texto (byte a byte) en el fichero “Ejemplo8.dat”.
#include <fstream.h>
#include <iostream.h>
int main(){
char cad[17]="TEXTO A ESCRIBIR";
ofstream fichout("Ejemplo8.dat", ios::out | ios::binary);
if (!fichout)
cout << "\n Incapaz de Crear o Abrir el fichero ";
else{
for (int i=0;i<=16;i++)
fichout.put(cad[i]);
// Escritura en el fichero
fichout.close();
}
} // Fin del main
9.4.3.- Lectura/Escritura por bloque de bytes.
Otra manera alternativa de leer y escribir en ficheros binarios consiste en utilizar las
funciones read() y write().
9.4.3.1.- Lectura.
La función read tiene varios formatos aunque a continuación explicamos el formato
básico
De forma intuitiva, una llamada de la forma
descriptor.read(&c, num);
donde c es una variable de un tipo arbitrario (por ejemplo de un tipo TElemento), pasada por
___________________________________________________________________________
#
referencia, y num es un entero o una variable de tipo entero, ejecuta una lectura (a partir de la
posición actual del cursor del fichero asociado a la variable descriptor) de num bytes del
fichero y los coloca en la variable c. En definitiva la llamada lee num bytes del fichero y los
coloca en la variable c. Esta función puede extraer caracteres hasta alcanzar el fin del fichero.
9.4.3.2.- Escritura.
Asumiendo que c es una variable de un tipo arbitrario (por ejemplo de un tipo TElemento)
pasada por referencia, que num es un entero o una variable conteniendo un entero que
descriptor esta asociado a un fichero, una llamada de la forma
Descriptor.write(&c, num);
Escribe, a partir de la posición del cursor, el contenido de c en el fichero asociado a
descriptor. En realidad sólo escribe num bytes que corresponden a los num bytes siguientes
que se encuentran en memoria a partir de la dirección en la que se encuentra almacenado el
contenido de c.
Esta instrucción escribe a partir de la posición indicada por el puntero de lectura/escritura
del fichero asociado a descriptor los num bytes contenidos en el parámetro c.
Se puede observar que en el segundo parámetro hay que indicar el número de bytes que
ocupa el valor que se desea escribir. Para conocer éste dato de una forma fácil, podremos usar
la función sizeof.
Ejemplo 9:
El siguiente programa declara el fichero “F.dat” para entrada-salida, graba en dicho fichero el
valor 1234.86 en binario y después los veinte primeros enteros. Posteriormente, lee el fichero
visualizando su información en la salida estándar (el monitor).
#include <fstream.h>
#include <iostream.h>
int main(){
float R=1234.86;
int i,N;
fstream fichbin("F.dat",ios::binary | ios::out); // Apertura como salida
fichbin.write(&R,sizeof(float));
for (i=1;i<=20;i++)
___________________________________________________________________________
$
fichbin.write(&i,sizeof(int));
fichbin.close();
fichbin.open("F.dat",ios::binary | ios::in);
fichbin.read(&R,sizeof(float));
cout <<endl << "R= " << R << endl;
for (i=1;i<=20;i++){
fichbin.read(&N,sizeof(int));
cout <<endl << i << "= " << N << endl;
}
} // Fin del main
// Apertura como entrada
Observa que la apertura y el cierre de ficheros binarios se puede efectuar con las mismas
operaciones estudiadas para ficheros de texto.
9.5. Acceso aleatorio a ficheros.
Sabemos que las operaciones de lectura y escritura siempre se realizan a partir de la
posición indicada por el puntero de lectura/escritura del fichero. Para poder acceder
correctamente al fichero, debemos saber como cambia dicho puntero de lectura/escritura.
Hasta ahora hemos visto, que tenía un comportamiento secuencial, es decir, siempre se
incrementa en el número de caracteres leídos ó escritos con cada instrucción, no obstante,
puede haber situaciones en las que éste comportamiento no se adapte a nuestras necesidades.
Supongamos que deseamos acceder a información situada cerca del final del fichero.
Según lo visto hasta ahora, debemos leer previamente toda la información que le precede con
objeto de ir avanzando el puntero de lectura/escritura progresivamente. Puesto que
conocemos la situación de la información que queremos leer ó escribir, parece más
conveniente poder situar el puntero de lectura/escritura directamente en dicha posición. Para
gestionar éste tipo de comportamiento disponemos de una serie de funciones.
Ya hemos visto que el acceso a un fichero se describe en términos de lo que
denominamos el cursor o apuntador del fichero que es un índice que apunta a una posición
relativa del fichero de manera que en todo momento la posición actual del cursor marca el
lugar a partir del cual tendrá lugar la operación (de lectura o escritura) sobre el fichero.
Además, cada vez que se realiza una operación de entrada o salida, el cursor del fichero
avanza automáticamente dependiendo del número de bytes leídos o escritos. Básicamente
existen dos instrucciones de acceso aleatorio para cada tipo de acceso a un fichero. Las
vemos a continuación.
___________________________________________________________________________
%
9.5.1.- Lectura.
Las funciones de acceso aleatorio para lectura son seekg y tellg.
•
seekg(pos) mueve la posición del cursor del fichero a la posición relativa del fichero
•
indicada por pos, donde pos es un entero (o variable conteniendo un entero).
tellg() devuelve la posición relativa actual del cursor asociado al fichero de entrada y
devuelve un –1 en caso de existir algún error.
9.5.2.- Escritura.
Las funciones de acceso aleatorio equivalentes para escritura on seekp y tellp.
•
seekp(pos) mueve la posición del cursor del fichero a la posición absoluta del fichero
•
indicada por pos.
tellp() devuelve la posición absoluta actual del cursor asociado al fichero de salida y
devuelve un –1 en caso de existir algún error.
Ejemplo 10:
El siguiente programa almacena en un fichero los 10 primeros enteros, luego muestra por
pantalla el quinto entero (o sea el 5), posteriormente lo reemplaza por el valor 100, y al final
visualiza en el monitor el contenido del fichero.
#include <fstream.h>
#include <iostream.h>
int main(){
int i,N;
fstream fichbin("ejemplo11.dat",ios::binary | ios::in | ios::out);
for (i=1;i<=10;i++)
fichbin.write(&i,sizeof(int));
// Almacena los 10 primeros enteros
fichbin.seekp(4*sizeof(int)); // se posiciona al principio del quinto/ entero
fichbin.read(&N,sizeof(int)); // Lee dicho entero
cout <<endl << "Quinto= " << N << endl; // visualiza el valor 5 en pantalla
fichbin.seekp(4*sizeof(int)); // se posiciona de nuevo al principio del quinto entero
// pues el cursor había avanzado.
i=100;
fichbin.write(&i,sizeof(int)); // Modifica el valor 5 por el valor 100;
fichbin.seekp(0*sizeof(int)); // se posiciona de nuevo al principio del fichero
for (i=1;i<=10;i++){
fichbin.read(&N,sizeof(int));
___________________________________________________________________________
&
cout <<endl << i << "= " << N << endl;
}
fichbin.close();
} // Fin del main
// Se visualiza el contenido en pantalla
9.6.- Ficheros pasados como argumentos.
En esta sección simplemente comentamos que cuando el descriptor de un fichero es
pasado como argumento, la mayoría de las veces debe hacerlo como parámetro por referencia
puesto que cualquier operación de lectura/escritura (y la mayoría de las de acceso directo)
modifican la posición del cursor asociado a ese fichero.
Ejemplo 11:
El siguiente programa simplemente añade la cadena "HOLA" al fichero "poesia.txt". Observe
que el argumento formal del procedimiento escribir_hola esta declarado por referencia.
#include <fstream.h>
void escribir_hola(fstream &canal_salida){
canal_salida << endl << "HOLA" << endl;
}
int main(){
fstream out("poesia.txt",ios::app);
escribir_hola(out);
out.close();
} // Fin del main
9.7.- Ejemplos de utilización de ficheros binarios y de texto.
En esta sección mostramos dos ejemplos de cómo podemos leer/escribir datos desde/a
ficheros binarios o de texto.
Comentario:
Observe que en la apertuira de ficheros binarios ES NECESARIO, en algunos compiladores
8como es el caso del DEV-C++) especificar el modo de apertura (in, out, app, ..., etc) a
pesar de que este sea el modo por defecto. Si no se especifica entonces podemos tener
problemas en la lectura de los datos.
___________________________________________________________________________
'
Ejemplo 12:
El siguiente programa lee, desde teclado una información relativa a una serie de empleados
de una empresa. Los datos a leer son el nombre, un código formado por cuatro enteros - ojo,
no por cuatro dígitos- y una cantidad correspondiente al sueldo mensual. Entonces, toda la
información recogida es grabada en dos ficheros (uno de texto y otro binario) para ser
posteriormente leída desde los mismos y visualizada en pantalla.
#include <iostream.h>
#include <stdlib.h>
#include <fstream.h>
//CONSTANTES
const int MAXCAD = 20;
const int MAXVECT = 4;
//TIPOS
typedef int TVector[MAXVECT];
typedef char TCadena[MAXCAD+1];
typedef struct TPersona{
TCadena nombre;
TVector codigo; // exactamente cuatro cifras
float sueldo; };
void VisualizaPersonasFichero(TCadena nombreFichero,int numempleados);
void EscribePersonaFichero(TCadena nombreFichero, TPersona p);
void VisualizaPersonasFicheroBin(TCadena nombreFichero,int numempleados);
void EscribePersonaFicheroBin(TCadena nombreFichero, Tpersona p);
void pintaVector(TVector v);
void pintaPersona(TPersona p);
void leePersona(TPersona &p);
int main(){
Tpersona per;
TVector cod;
int i,N;
cout << "introduzca el número de personas a incluir ";
cin >> N;
for (i=1; i <= N ; i ++){
leePersona(per);
// Escribo la información en texto y en binario
EscribePersonaFichero("empleados.txt",per);
EscribePersonaFicheroBin("empleados.dat",per);
}
cout << "Mostramos la información almacenada" << endl;
cout << "Visualizo desde el fichero de texto: " << endl;
VisualizaPersonasFichero("empleados.txt",N);
cout << endl;
cout << "Visualizo desde el fichero binario: " << endl;
___________________________________________________________________________
(
VisualizaPersonasFicheroBin("empleados.dat",N);
cout << endl;
system("PAUSE"); return 0;
} // Fin del main
// Este proc. visualiza la información almacenada en el fichero de texto
void VisualizaPersonasFichero(TCadena nombreFichero, int numempleados)
{ ifstream in;
int i,j;
TPersona p;
in.open(nombreFichero);
if (in.bad())
// Comprobamos el estado de la apertura
cout << "Error al abrir el fichero: " << nombreFichero << endl;
else{
for (i=1;i<=numempleados; i++){
in >> p.nombre;
for(j=0;j<MAXVECT;j++)
in >> p.codigo[j];
in >> p.sueldo;
pintaPersona(p);
}
in.close();
}
} // Fin VisualizaPersonasFichero
// El siguiente proc. Escribe la informacion de una persona en un fichero de texto
void EscribePersonaFichero(Tcadena nombreFichero, TPersona p)
{ ofstream out;
int i;
out.open(nombreFichero,ios::app);
if (out.bad())
cout << "Error al abrir el fichero: " << nombreFichero << endl;
else{
out << p.nombre << endl;
for(i=0;i<MAXVECT;++i)
out << p.codigo[i] << " "; // Ojo, incluimos un espacio en medio!!
out << p.sueldo << endl;
out.close();
}
} // Fin EscribePersonaFichero
// Este proc. visualiza la información almacenada en el fichero binario
void VisualizaPersonasFicheroBin(TCadena nombreFichero,int numempleados)
{ ifstream in;
___________________________________________________________________________
!
)
int i;
TPersona p;
in.open(nombreFichero, ios::in | ios::binary);
// Ojo, hay que poner 'in' forzosamente
if (in.bad())
cout << "Error al abrir el fichero: " << nombreFichero << endl;
else{
for (i=1;i<=numempleados;i++)
{
in.read(&p,sizeof(TPersona)); // Se lee toda la información !!
pintaPersona(p);
}
in.close();
}
} // Fin VisualizaPersonasFicheros
// El siguiente proc. Escribe la informacion de una persona en un fichero binario
void EscribePersonaFicheroBin(TCadena nombreFichero, TPersona p)
{ ofstream out;
out.open(nombreFichero, ios::binary | ios::app); // Ojo, especificamos el modo AÑADIR
if (out.bad())
cout << "Error al abrir el fichero: " << nombreFichero << endl;
else{
out.write(&p,sizeof(TPersona));
out.close();
}
} // Fin EscribePersonaFicheroBin
// El siguiente proc. visualiza un código de cuatro enteros (o sea, un vector) a la pantalla
void pintaVector(TVector v){
int i;
cout << "CODIGO : { ";
for(i=0;i<MAXVECT-1;++i)
cout << v[i] << ", " ;
cout << v[MAXVECT-1] << " } ";
}
// El siguiente proc. visualiza la información de una persona en la pantalla
void pintaPersona(TPersona p){
cout << "NOMBRE : " << p.nombre << " ";
pintaVector(p.codigo);
cout << "SUELDO : " << p.sueldo << endl;
}
// El siguiente proc, lee información de una persona desde el teclado
void leePersona(TPersona &p)
{
int i;
___________________________________________________________________________
!
cout << "Nombre ? ";
cin >> p.nombre;
cout << endl << "Codigo (4 enteros-NO cifras solo) ?";
for(i=0;i<MAXVECT;++i)
cin >> p.codigo[i];
cout << endl << "Sueldo (euros) ?";
cin >> p.sueldo;
}
Comentario:
El primer argumento de read y write no necesita el símbolo & cuando es de tipo array
como se ve en el siguiente ejemplo.
Ejemplo 13:
El siguiente programa escribe el vector que contiene la información {0,1,2,3,....,8,9} tanto en
un fichero binario como en uno de texto.
#include <iostream.h>
#include <stdlib.h>
#include <fstream.h>
//CONSTANTES
const int MAXCAD = 20;
const int MAXVECT = 10;
//TIPOS
typedef int TVector[MAXVECT];
typedef char TCadena[MAXCAD+1];
void LeeVectorFichero(TCadena nombreFichero, TVector &v);
void EscribeVectorFichero(TCadena nombreFichero, TVector v);
void LeeVectorFicheroBin(TCadena nombreFichero, TVector &v);
void EscribeVectorFicheroBin(TCadena nombreFichero, TVector v);
void pintaVector(TVector v);
int main()
{ TVector v;
int i;
for(i=0;i<MAXVECT;++i) // Inicializo el vector
{ v[i]=i;
}
cout << "Inicializo el vector y lo escribo "
<< "en ficheros de texto y binario: "
<< endl;
pintaVector(v);
___________________________________________________________________________
!
!
EscribeVectorFichero("vector.txt",v);
EscribeVectorFicheroBin("vector.dat",v);
for(i=0;i<MAXVECT;++i) // Borro el vector
{ v[i]=0;
}
cout << "Reseteo el contenido del vector:"
<< endl;
pintaVector(v);
cout << "Leo v desde el fichero de texto: " << endl;
LeeVectorFichero("vector.txt",v);
pintaVector(v);
cout << "Leo v desde el fichero binario: " << endl;
LeeVectorFicheroBin("vector.dat",v);
pintaVector(v);
system("PAUSE");
return 0;
}
// Lee un vector desde un fichero de texto
void LeeVectorFichero(TCadena nombreFichero, TVector &v)
{ ifstream in;
int i;
in.open(nombreFichero);
if (in.bad())
// Comprobamos el estado de la apertura
{ cout << "Error al abrir el fichero: "
<< nombreFichero << endl;
}
else
{ for(i=0;i<MAXVECT;++i)
in >> v[i];
in.close();
}
}
// Escribe un vector en un fichero de texto
void EscribeVectorFichero(TCadena nombreFichero, TVector v)
{ ofstream out;
int i;
out.open(nombreFichero);
if (out.bad())
{ cout << "Error al abrir el fichero: "
<< nombreFichero << endl;
}
else
___________________________________________________________________________
!
"
{ for(i=0;i<MAXVECT;++i)
out << v[i] << " ";
out.close();
}
}
// Lee un vector desde un fichero binario
void LeeVectorFicheroBin(TCadena nombreFichero, TVector &v)
{ ifstream in;
in.open(nombreFichero);
if (in.bad())
{ cout << "Error al abrir el fichero: "
<< nombreFichero << endl;
}
else
{ in.read(v,sizeof(TVector));
in.close();
}
}
// Escribe un vector a un fichero binario
void EscribeVectorFicheroBin(TCadena nombreFichero, TVector v)
{ ofstream out;
out.open(nombreFichero);
if (out.bad())
{ cout << "Error al abrir el fichero: "
<< nombreFichero << endl;
}
else
{ out.write(v,sizeof(TVector));
out.close();
}
}
// Visualiza un vector a la pantalla
void pintaVector(TVector v)
{ int i;
cout << " v = { ";
for(i=0;i<MAXVECT-1;++i)
{ cout << v[i] << ", " ;
}
cout << v[MAXVECT-1] << " } " << endl;
}
Una vez ejecutado el programa la salida es la siguiente:
___________________________________________________________________________
!
#
*
+
,
-
,
3
L
2
4
>
2
1
2
1
2
@
A
?
@
@
A
B
?
@
1
A
2
C
A
;
2
A
2
C
A
-
A
A
A
.
/
9
E
2
E
A
/
A
A
P
A
,
-
,
-
A
F
G
A
2
A
H
@
I
A
2
A
2
4
J
8
1
2
+
9
,
-
:
2
5
1
7
;
2
4
2
<
4
1
6
8
,
+
.
5
,
1
=
-
4
1
5
2
<
4
1
K
,
J
/
,
K
8
-
5
3
2
A
I
4
/
;
1
-
K
2
A
A
7
J
1
5
5
2
A
@
H
2
G
,
A
5
A
:
1
I
;
@
2
A
O
A
1
:
F
/
H
;
@
,
6
G
+
@
9
5
A
2
A
/
1
F
4
@
D
4
A
2
D
O
-
E
+
@
;
A
B
+
A
1
A
7
2
D
-
@
2
3
A
7
A
/
C
/
2
;
,
2
A
2
;
3
7
1
B
1
3
>
5
0
A
2
?
>
3
,
@
2
3
N
/
?
7
3
M
M
.
>
+
.
5
,
1
K
.
Q
.
5
.
-
1
+
4
,
+
O
.
5
R
R
R
R
R
R
R
R
R
R
R
___________________________________________________________________________
!
$
EJERCICIOS
1.Escribir un programa con la opción de encriptar y de desencriptar un fichero de texto.
La encriptación (codificación) consiste en que dado un fichero de texto de entrada genere otro
fichero de salida de extensión <nombre>.COD donde el texto estará codificado (encriptado).
Esta codificación consiste en reemplazar cada carácter por el tercero siguiente (ej. a -> d). Si
el carácter resultante es no imprimible éste no se cambia. La opción de desencriptado consiste
en leer un fichero <nombre>.COD y recuperar la información original.
2.Escriba un programa que cree un fichero de texto llamado "PERSONAS.DAT" en el
que se guarde la información de un número indeterminado de personas. La información que
se guardará por cada persona será:
Nombre:
De 1 a 30 caracteres.
Edad
CARDINAL.
Sexo
CHAR (M/F).
Arte
CHAR (S/N).
Deporte
CHAR (S/N).
Libros CHAR (S/N).
MúsicaCHAR (S/N).
La información correspondiente a cada persona se leerá del teclado. El proceso
finalizará cuando se teclee un campo "Nombre" que esté vacío.
3.Amplíe el programa que procesa clientes de una agencia matrimonial para que tome
los datos de todos los candidatos a estudiar del fichero PERSONAS.DAT del ejercicio
anterior, lea el cliente del teclado y finalmente genere como resultado un listado por pantalla
con los nombres de los candidatos aceptados y un fichero llamado ACEPTADOS.DAT con
toda la información correspondiente a los candidatos aceptados. Una persona del fichero
PERSONAS.DAT se considerará aceptable como candidato si tiene diferente sexo y por lo
menos tres aficiones comunes con respecto al aspirante introducido por pantalla. (El
programa debe ser capaz de trabajar con cualquier número de personas en el fichero
PERSONAS.DAT).
4.Codifique un programa que cree un fichero para contener los datos relativos a los
artículos de un almacén. Para cada artículo habrá de guardar la siguiente información:
Código del artículo
(Numérico)
Nombre del artículo (Cadena de caracteres)
Existencias actuales (Numérico)
Precio
(Numérico).
Se deberán pedir datos de cada artículo por teclado hasta que como código se teclee 0.
El fichero se habrá de crear ordenado por el código del articulo.
5.Escriba un programa que dados dos ficheros generados por el programa anterior y
ordenados genere un tercer fichero que sea el resultado de mezclar de formar ordenada los
dos primeros.
6.Escriba un programa que tome como entrada el fichero del ejercicio 4 y una condición
sobre los campos Existencias actuales o Precio. La condición podrá ser:
___________________________________________________________________________
!
%
<campo> [<,<=,>,>=, != ] <número>
Este programa debe generar como salida un fichero llamado "salida.dat" que contenga todos
aquellos artículos para los que se cumple la condición de entrada.
7.Escriba un programa que tenga como entrada un fichero que contenga un texto y dé
como resultado una estadística de las palabras que hay de cada longitud, así como de los
caracteres especiales (",", ".", ":", ";").
8.Escribir un programa que genere a partir de un fichero de entrada que contiene una
tabla de números reales otro fichero de salida <nombre>.BIN grabado en formato binario.
Ej:
1.23 3.45 12.5
4.8
3.9
0.83
........................
9.Dado una tabla grabada en un fichero en formato binario <nombre>.BIN hallar la
suma horizontal y vertical y grabar la tabla y los resultados en otro fichero de texto o binario
según se introduzca por pantalla. El resultado en texto sería el siguiente:
Ej:
1.23 3.45 12.5 17,18
4.8
3.9
0.83 9,53
6,03 7,35 13,33
___________________________________________________________________________
!
&
Descargar