#include <fstream.h> //librería para manejar la //lectura/escritura de ficheros int main (void) { int outval=3; ofstream WriteFile("myfile.txt"); //apertura fichero en modo escritura WriteFile << outval; //escribimos el valor de outval en el fichero WriteFile.close(); //cierre de fichero int inval; ifstream ReadFile("myfile.txt"); //declaración de un fichero de lectura ReadFile >> inval; //leemos el fichero y lo colocamos en inval ReadFile.close(); //cierre del fichero cout <<"el valor escrito es = "<< inval << endl; //el valor escrito es = 3 return 1; }
Conceptos
Ficheros. Abrir, cerrar, escribir y leer
Igual que la consola se asocia a unos streams llamados cin y cout, se pueden asociar funciones de lectura y escritura a un stream para ficheros. El fichero o archivo es un espacio de disco donde se almacena información de forma secuencial bajo un nombre. Para trabajar con ficheros es necesario el archivo de cabecera <fstream.h> el cual incluye a <iostream.h>.
Debe destacarse que en el disco duro aparecerá el nombre del fichero, pero el programa utiliza el archivo a través del stream que le hayamos asociado. Esto se observa claramente donde el fichero se llama myfle.txt, mientras que el stream se maneja bajo la variable WriteFile.
Las operaciones que se pueden hacer sobre un fichero son: creación, apertura, lectura, escritura y desplazamiento a lo largo de la información que contiene. El uso de ficheros es muy importante para facilitar tanto la lectura de datos como la escritura de resultados. Sin embargo, el acceso a los datos de grandes ficheros puede producir una notable disminución en la velocidad de ejecución del programa, por ello se recomienda usar los ficheros con inteligencia. Las operaciones básicas sobre un fichero son:
Abrir un fichero
Se abre un fichero de nombre mnfile.txt. El fichero se abre con un tipo ofstream (output file stream); por lo tanto, la intención es escribir datos en él. Por el contrario, más adelante se define un tipo ifstream (input file stream); por lo tanto, sólo se desea leer datos de él; en este último caso, el archivo se abre en esa línea bajo el nombre de myfile.txt que es el fichero que ya se había creado antes.
Cerrar un fichero
Si un fichero no se usa, es importante cerrarlo porque son recursos que ocupan al sistema y puede producir errores en tiempo de ejecución, si el programa pretende volver a abrirlo. Para cerrar un fichero basta con ejecutar un close.
Escritura y lectura de un fichero
Para leer y escribir basta con utilizar los operadores » y « que ya se presentaron en el apartado de la consola.
Ahora veamos como se realiza lo mismo en el ANSI C ó C estándar.
Ficheros.
Definiciones.
Como ya hemos dicho, los ficheros son unas estructuras de datos de tamaño indefinido, es decir, desconocido al comienzo del programa. Por esta razón, no todos los datos contenidos en este tipo de estructura- de datos están almacenados en la memoria principal del ordenador, sino que tan sólo uno de los elementos de la estructura es accesible en cada momento de la ejecución del algoritmo, mediante una sentencia especial que transfiere ese elemento a una variable convencional del mismo tipo, realizando lo que denominamos una lectura. Análogamente, cuando queremos almacenar un valor en uno de los elementos del fichero, debemos realizar una operación de escritura en el mismo, con lo cual transferimos el contenido de una variable convencional (memoria principal) al elemento del fichero (la memoria secundaria). Es como si tuviésemos una ventana a través de la cual podemos acceder (leer o escribir) a uno de los elementos del fichero. Esta ventana a través de la cual podemos ver y modificar el contenido del fichero se denomina buffer de entrada/salida. Los elementos que componen un fichero se suelen denominar registros, pues en general lo son, aunque también pueden ser variables elementales (carácter, real, etc.)
Según la forma en que se pueda desplazar esta ventana sobre los elementos del fichero, tendremos distintos tipos de ficheros:
secuenciales: cada vez que se realiza una operación de lectura o escritura de un elemento del fichero, la ventana avanza una posición para colocarse sobre el elemento siguiente de la estructura. De esta forma, todos los elementos del fichero se van leyendo o escribiendo automáticamente uno detrás de otro, en secuencia. Un caso particular de los ficheros secuenciales son los ficheros de texto, cuyos elementos son caracteres imprimibles y para los que la mayoría de compiladores ofrecen funciones de tratamiento especiales como, por ejemplo, la de detección de final de línea, que vale verdadero cuando el último carácter leído es el último de una línea de texto, es decir, cuando la ventana se encuentra sobre un carácter de «retorno de carro» (<CR>).
de acceso directo: son aquéllos en los que el programador realiza manualmente el desplazamiento de la ventana sobre los elementos del fichero, es decir, controlado directamente por el algoritmo. Este modo de acceso permite emplear los ficheros como los vectores, leyendo o escribiendo datos en sus elementos en cualquier orden, aleatoriamente.
Cuando declaramos un fichero, en realidad estamos declarando una variable estructurada, en la que se almacenan todos los datos necesarios para acceder al fichero (nombre de éste, situación de la ventana, operaciones permitidas, etc.). Entre éstos se encuentra un campo del tipo de los elementos del fichero, que el sistema de gestión de ficheros del ordenador asociará a la ventana de comunicación entre nuestro programa y el fichero. Esta asociación se realiza mediante una instrucción de apertura del fichero, tras la cual podremos operar con los elementos de éste, a través de la ventana, hasta que realicemos la operación complementaria de cierre del fichero, que disocia la variable y el fichero.
Como resumen podemos decir que con los ficheros podemos realizar las siguientes operaciones:
ABRIR: asocia una variable del algoritmo a un fichero del ordenador. Además de estos dos parámetros, con esta operación debemos indicar el tipo de acciones que vamos a realizar sobre los elementos del fichero:
Lectura: Abrir (< IdFichero >, l , ‘ < NombreFichero > » ),
< IdFichero >, identificador lógico del fichero, es el nombre de la variable que lo representa en el algoritmo, a través de la cual accedemos al buffer (ventana) del fichero.
l, indica que el fichero se abre para leer su contenido. En algunos casos, si el fichero no existe nos dará un error.
< NombreFichero >, es el nombre completo del fichero que queremos abrir, incluyendo el nombre del dispositivo (disco) en el que se encuentra, y el camino de acceso al subdirectorio en el que se encuentre, de no coincidir con los valores por defecto en el momento de ejecutarse el programa.
Escritura: Abrir ( < IdFichero >, e, » < NombreFichero >» ),
e, indica que el fichero se abre para escribir en él. Si existe un fichero <NombreFichero>, destruye su contenido, para empezar a escribirlo desde el primer elemento.
Añadido: Abrir ( < IdFichero >, a, “< NombreFichero>” >
a, indica que el fichero se abre para escribir en él, pero a partir del último dato que contenga, con lo cual, si ya existía, no se borra su contenido.
En C la traducción es directa:
< IdFichero > = fopen (“< NombreFichero >”,<modo> ) donde <modo> indica el modo de acceso:
rb = lectura.
r = lectura de texto.
wb = escritura.
w = escritura de texto. r+b = lectura/escritura.
r+ = lectura/escritura texto sin borrar el contenido previo.
w+ = lectura/escritura texto borrando el contenido previo.
a+b = añadido.
a = añadido de texto.
FILE *archi; //define el archivo
float var; //lo uso tanto para leer o escribir
int pos; //lo utilizo para ir a un registro determinado
CERRAR: disocia el <IdFichero> del fichero <NombreFichero>. Después de esta operación ya no se podrán realizar operaciones de lectura o escritura sobre <NombreFichero>. Esta operación tiene la forma:
Cerrar (<IdFichero> )
La traducción de esta acción a C es inmediata: close (<idFichero> )
Close(archi);
LEER: copia el contenido del registro del fichero sobre el que se encuentra la ventana (buffer) a la variable que le pasemos como parámetro.
leer ( <IdFichero>, <variable> )
La traducción a C es un poco más compleja, pues como en la declaración de la variable de tipo fichero no se ha indicado el tipo de sus elementos, hay que indicar su tamaño al acceder a ellos (sizeof(<var>)). Además, el procedimiento permite leer más de un elemento (< NúmeroElementos > ) de una vez:
fread (&< var >, sizeof ( <var> ), <NúmeroElementos>, <IdFichero>)
fread(&var,sizeof(float),1,archi);
ESCRIBIR: copia el contenido de la variable que le pasemos como parámetro al registro del fichero sobre el que se encuentra la ventana, a través del buffer.
escribir (< IdFichero > , < variable > )
Su traducción a C es dual a la de lectura:
fwrite ( &< var > , sizeof ( < var >) , < NúmeroElementos > , < IdFichero > )
fwrite(var,sizeof(float),1,archi)
BORRAR: borra del directorio del dispositivo de memoria secundaria (disco) toda referencia al fichero < NombreFichero >. Después de esta operación no se puede volver a abrir el fichero borrado ni, por tanto, acceder a su contenido. Para poder borrar un fichero, debe estar cerrado.
Borrar ( < NombreFichero > )
Su traducción a C es:
remove ( < NombreFichero > )
remove(archi)
FinalDeFichero ( FDF ): Es una función que devuelve el valor cierto si la ventana del fichero se encuentra sobre el registro siguiente al último lleno (escrito), y falso en caso contrario. En un fichero secuencial, después de que esta función se hace cierta, no podemos seguir leyendo del fichero.
En C tiene la forma: int feof ( < IdFichero > ). Devuelve cero ( FALSO ) si no detecta fin de fichero y otro valor (CIERTO) si lo detecta.
feof(archi);
Los ficheros de acceso directo siempre deben abrirse para lectura/escritura, pues se supone que la aleatoriedad del acceso tiene por finalidad ir leyendo y modificando los datos leídos. Para acceder a una posición concreta, desde el punto de vista algorítmico, utilizaremos una sola acción para aproximar al máximo la sintaxis a la de manejo de vectores (una sola acción para acceder a un elemento del fichero), pero en C hay que invocar dos sentencias complementarias para hacerlo: mover la ventana a la posición <pos>, y leer o escribir con las sentencias normales:
LEERPOS (< IdFichero > , < pos > , < var > ):
En C tiene la forma:
fseek (< IdFichero > , sizeof (< var >) * (< pos > – 1 ) , SEEK_SET) ;
fseek(archi,sizeof(float)*(pos-1));
fread (&< var > , sizeof ( < var >) , < NúmeroElementos > , < IdFlchero > )
ESCRIBIRPOS (< IdFichero > , < pos > , < var > )
En C tiene la forma:
fseek ( < IdFichero > , sizeof ( < var > ) * (< pos > – 1) , SEEK_SET) ;
fwrite ( &< var > , sizeof (< var >) , < NúmeroEiementos > , < IdFichero > )
VISUALIZACIÓN DE UN FICHERO
El siguiente programa es el primer ejemplo de como enviar datos a un fichero.
FORMOUT.C
#include "stdio.h" main() { FILE *fp; char stuff[25]; int index; fp = fopen("TENLINES.TXT","w"); /* abre para escritura */ strcpy(stuff,"Una línea de ejemplo."); for (index = 1;index <= 10;index++) fprintf(fp,"%s Línea número %d\n",stuff,index); fclose(fp); /* cierra el fichero antes de acabar el programa */ }
Empezamos, como siempre, con el «include» para «stdio.h» y, definimos algunas variables para el ejemplo, incluyendo un extraño tipo que desconocíamos hasta ahora.
El tipo «FILE» se utiliza para una variable tipo fichero, y está definida en «stdio.h». Se usa para definir un puntero de fichero, en operaciones de ficheros. La definición del C especifica la obligatoriedad de un puntero tipo «FILE», el cual puede tener cualquier nombre, ateniéndonos a las reglas para formación de nombres.
ABRIENDO UN FICHERO
Antes de que podamos escribir en un fichero, debemos abrirlo. Esto quiere decir que debemos comunicar al sistema nuestra intención de escribir en un fichero, además de indicar su nombre. Hacemos esto con la instrucción «fopen«, vista de forma práctica en la primera línea del programa. El puntero del fichero, «fp» en nuestro ejemplo, apunta al fichero, y son necesarios 2 argumentos en el paréntesis, primero el nombre del fichero, seguido por el tipo. El nombre del fichero puede ser cualquiera válido para el DOS, y puede expresarse en mayúsculas o en minúsculas, o en las dos. Va encerrado en dobles signos de “. Para el ejemplo, hemos elegido el nombre de fichero TENLINES.TXT. Crearemos el fichero y le meteremos algunos datos.
LEYENDO («r»)
El segundo parámetro es el atributo del fichero, y puede ser cualquiera de estas 3 letras: «r», «w» o «a», siempre en minúscula. Cuando usamos «r» estamos abriendo el fichero en modo lectura, y «w» lo pone en modo escritura. Si empleamos «a» el fichero aceptará datos a partir del último registro existente, es decir, los agregará a los que ya tiene. El modo «r» lleva la condición implícita de que el fichero a leer debe existir en el disco, de lo contrario se produciría un error, y el puntero de ese fichero tendría valor 0.
ESCRIBIENDO(«w»)
Cuando un fichero se abre para escritura, será creado en caso de no existir. Si ya existiera, ¡ojo!: SU CONTENIDO SE PERDERÁ, Y VOLVERÁ A INICIALIZARSE.
AÑADIENDO («a»)
Cuando un fichero es abierto en este modo, se creará en caso de no existir con anterioridad y, si ya existiera, los datos que se le introduzcan se añadirán a los ya existentes, empezando por el final del fichero.
VISUALIZANDO EL FICHERO
Visualizar un fichero es muy parecido a como lo hemos visto antes. Las únicas diferencias son el nombre de la nueva función, y la adición del puntero del fichero, como un argumento más de la función. En el programa ejemplo, «fprintf» sustituye al conocido «printf» y, el puntero del fichero definido anteriormente es el primer argumento entre paréntesis. El resto del mandato parece (y en efecto es) idéntico al «printf».
CERRANDO EL FICHERO
Para cerrar el fichero, simplemente use la función «fclose» con el puntero del fichero entre paréntesis. En el programa no es necesario cerrar el fichero porque el sistema cerrará todos los ficheros abiertos, antes de volver al DOS. Es un buen hábito acostumbrarse a cerrar cada fichero que ya no usemos, evitando así pérdidas de información.
Podemos abrir un fichero para escritura, cerrarlo, volverlo a abrir para lectura, cerrarlo y, abrirlo de nuevo para añadir datos, etc. Cada vez que lo abrimos podemos utilizar el mismo puntero de fichero u otro distinto. El puntero es sólo una herramienta que se usa para señalar a un fichero cuando decidimos que debe ser señalado.
Cuando se ejecute el programa no se verá nada por pantalla, porque el programa funciona así. Tras ejecutarlo, buscaremos en el directorio un fichero de nombre TENLINES.TXT, y lo veremos. Comprobamos el contenido del fichero desde MSDOS y desde el programa. Debería ser el mismo.
VISUALIZANDO CARACTER A CARACTER
Este programa mostrará la manera de visualizar un caracter cada vez.
CHAROUT.C
#include "stdio.h" main() { FILE *point; char others[35]; int indexer,count; strcpy(others,"Lineas adicionales."); point= fopen("tenlines.txt","a"); /* abre para añadir */ for (count = 1;count <= 10;count++) { for (indexer = 0;others[indexer]!=NULL;indexer++) putc(others[indexer],point); /* muestra un caracter simple */ putc('\n',point); /* muestra un linefeed */ } fclose(point); }
El programa empieza con el «include» habitual, pasando luego a definir algunas variables, incluyendo un puntero del fichero. Le hemos llamado «point» esta vez, pero podríamos haber usado cualquier nombre de variable válido. Hemos definido también una cadena de caracteres para usarla en la función de salida, usando una función «strcpy». Hemos abierto el fichero para añadir datos, y lo hemos hecho con «fopen», excepto esta vez, que usamos las minúsculas para el nombre del fichero. Esto sólo indica que al DOS no le importa si el nombre del fichero va en mayúsculas o en minúsculas.
El programa posee dos bucles «for» anidados. El bucle exterior es sólo un contador hasta 10. Por tanto, ejecutaremos el bucle interno 10 veces. Este bucle llama a la función «putc», y se repetirá mientras el caracter contenido en «others» no sea un cero. (Null: final de la cadena).
LA FUNCIÓN «putc»
La parte del programa que nos interesa en este momento, es la función «putc». Muestra un caracter cada vez, siendo el caracter el primer argumento entre paréntesis, y el puntero como segundo argumento. Por qué el creador de C puso el puntero en primer lugar del «printf» y, último en el «putc» es una buena pregunta, para la cual quizás no haya respuesta.
Cuando el texto de «others» está completo, se necesita una nueva línea, ya que no fue definida anteriormente. Un simple «putc» es ejecutado, el cual muestra el caracter «\n», caracter de retorno de carro y «line-feed».
Cuando el bucle externo ha sido ejecutado 10 veces, el programa cerrará los ficheros y terminará.
Siguiendo la ejecución del programa, se muestra el contenido del fichero TENLINES.TXT, y veremos que 10 nuevas líneas le fueron añadidas al final de las ya existentes. Si lo ejecutamos otra vez, crecerá 10 líneas. En ningún momento borramos el fichero, porque aún no hemos acabado con él.
LEYENDO UN FICHERO
READCHAR.C
#include "stdio.h" main() { FILE *funny; int c; funny = fopen("TENLINES.TXT","r"); if (funny == NULL) printf("Fichero inexistente\n"); else { do { c = getc(funny); /* toma un caracter del fichero */ putchar(c); /* lo muestra en el monitor */ } while (c != EOF); /* repite mientras no se produzca */ /* un EOF (fin de fichero) */ } fclose(funny); }
El programa comienza con el archiconocido «include», algunas definiciones de datos y la apertura del fichero, lo cual no requiere explicaciones, excepto el hecho de que abrimos el fichero en modo lectura. Con este programa comprobamos que el fichero exista , y si existe, ejecutamos el cuerpo principal del programa. Caso contrario, un mensaje indica el error y, finaliza el programa. Si el fichero no existe, el sistema dará valor NULL al puntero destinado a ese fichero.
El cuerpo principal del programa es un «do while» en el cual un caracter simple se lee del fichero y, aparece por pantalla mientras no sea un EOF (Fin de Fichero). En este caso, el fichero se cerrará y el programa pararía.
PELIGRO PELIGRO PELIGRO
Llegado este punto, corremos el riesgo potencial de cometer uno de los más comunes y complejos problemas que se dan en la programación en C. La variable devuelta por «getc» es un caracter, tipo «char», pero en algunos compiladores C (en muy pocos, afortunadamente), el caracter EOF no puede ser manejado por un «char». Este tipo maneja valores entre 0 y 255, por lo cual jamás devolverá un EOF. Frustrante, ¿verdad?. Sobre todo, cuando nos hace falta el EOF si manejamos un «char» que nos va a las mil maravillas en el programa. El programa no encontrará nunca el EOF, y el bucle jamás dejará de ejecutarse. Esto es fácil de evitar usando un tipo «int» para devolver EOF.
Aparece otro problema en el programa, pero nos lo plantearemos y resolveremos en el siguiente ejemplo.
Tras compilar y ejecutar el programa a plena satisfacción, sería un buen ejercicio cambiar el nombre TENLINES.TXT y ejecutar el programa otra vez para ver que NULL ha sido incializado. Luego debemos restaurar el nombre a TENLINES.TXT ya que aún no hemos terminado con él.
LEYENDO UNA PALABRA CADA VEZ
READTEXT.C
#include "stdio.h" main() { FILE *fp1; char oneword[100]; int c; fp1 = fopen("TENLINES.TXT","r"); do { c = fscanf(fp1,"%s",oneword); /* toma una palabra del fichero */ printf("%s\n",oneword); /* lo muestra en el monitor */ } while (c != EOF); /* repite mientras no aparezca */ /* un EOF (fin de fichero) */ fclose(fp1); }
Este programa se parece mucho al anterior, excepto en que aquí se utiliza la instrucción «fscanf» para leer una cadena de caracteres cada vez. Dado que «scanf» deja de leer cuando encuentra un espacio o un indicador de nueva línea, leerá una palabra cada vez, y, mostrará los resultados, una palabra por línea. Veamos un problema de programación.
ESTE ES EL PROBLEMA
Una mirada al programa nos revelará que si cuando leemos datos y nos topamos con un EOF, imprimimos algo antes de chequear el EOF, resultará una línea extra en pantalla. Lo que sacamos por pantalla es lo mismo que apareció en el paso principal del bucle, ya que todavía nos encontramos en el buffer que contiene «oneword».
TENLINES debe finalizar con «Additional» y «lines» en dos líneas separadas, y con un «lines» extra, aparecido porque el «printf» aún no estaba buscando EOF.
Debemos asegurarnos de la no existencia de EOF antes de ejecutar el «printf». Esto aparece en el siguiente programa:
READGOOD.C
#include "stdio.h" main() { FILE *fp1; char oneword[100]; int c; fp1 = fopen("TENLINES.TXT","r"); do { c = fscanf(fp1,"%s",oneword); /* toma una palabra del fichero */ <b>if (c != EOF)</b> printf("%s\n",oneword); /* la muestra en el monitor */ } while (c != EOF); /* repite hasta encontrar EOF */ fclose(fp1); }
Observamos que el «lines» extra no aparece por pantalla, ya que encontró el EOF.
FINALMENTE, LEEMOS UNA LÍNEA ENTERA
READLINE.C
#include "stdio.h" main() { FILE *fp1; char oneword[100]; char *c; fp1 = fopen("TENLINES.TXT","r"); do { c = fgets(oneword,100,fp1); /* toma una línea del fichero */ if (c != NULL) printf("%s",oneword); /* la visualiza en el monitor */ } while (c != NULL); /* repite hasta encontrar NULL */ fclose(fp1); }
Este programa es muy parecido a los que hemos estudiado, excepto en que añadimos un nuevo concepto, el NULL.
Estamos usando «fgets«, el cual lee una línea entera de texto, incluyendo el caracter de nueva línea del buffer. El buffer, es leído en el primer argumento de la llamada a la función, y el número máximo de caracteres a leer constituye el segundo argumento, seguido por el puntero del fichero. Esta función leerá caracteres del buffer de entrada mientras no encuentre un indicador de nueva línea, o lea el máximo de caracteres permitidos, menos uno. Deja un caracter para el fin de la cadena, el caracter NULL. Además, si encuentra un EOF, devolverá un valor NULL. En nuestro ejemplo, cuando aparece EOF, el puntero «c» será asignado al valor de NULL, que como ya hemos dicho, se definió como 0 en «stdio.h».
Cuando advertimos que a «c» se le ha asignado el valor de NULL, podemos parar, pero debemos comprobar, tal como hicimos en el anterior programa.
Y por último, cerramos el fichero.
COMO USAR EL NOMBRE DE UN FICHERO A MODO DE VARIABLE
ANYFILE.C
#include "stdio.h" main() { FILE *fp1; char oneword[100],filename[25]; char *c; printf("Nombre de fichero -> "); scanf("%s",filename); /* lee el fichero indicado */ fp1 = fopen(filename,"r"); do { c = fgets(oneword,100,fp1); /* toma una línea del fichero */ if (c != NULL) printf("%s",oneword); /* aparece por pantalla */ } while (c != NULL); /* sigue hasta que salga NULL */ fclose(fp1); }
Este es un ejemplo de lectura de un fichero cualquiera, sin conocerlo previamente. El programa pide el nombre de un fichero al usuario, lo lee y procede a abrir el fichero en modo lectura. El fichero se lee en su totalidad, y aparece por pantalla.
Cuando el programa pida el nombre de fichero a leer, deberemos indicar nombre y extensión. El fichero obviamente debe existir en disco.
¿COMO SACARLO POR IMPRESORA?
PRINTDAT.C
#include "stdio.h" main() { FILE *funny,*printer; int c; funny = fopen("TENLINES.TXT","r"); /* abre fichero de entrada */ printer = fopen("PRN","w"); /* abre fichero de impresora */ do { c = getc(funny); /* toma un caracter del fichero */ if (c != EOF) { putchar(c); /* lo muestra en el monitor */ putc(c,printer); /* lo saca por impresora */ } } while (c != EOF); /* repite hasta encontrar un EOF */ fclose(funny); fclose(printer); }
Otra vez abrimos TENLINES.TXT para lectura y abrimos PRN (dispositivo standard del DOS) para impresión. Imprimir es un proceso idéntico al de escribir en un fichero, excepto en que en vez de usar disco, usamos papel y un fichero standard. No hemos hablado de los ficheros standard del DOS, algunos de los cuales son: PRN, LPT, LPT1 y LPT2.
El programa es simplemente un bucle en el cual el caracter se lee y si no es EOF, se saca por la impresora y por pantalla. Cuando encuentra un EOF, los ficheros se cierran y el programa termina.