ESCUELA DE INGENIERÍA DE SISTEMAS DEPARTAMENTO DE COMPUTACIÓN PROGRAMACIÓN 2 PRÁCTICA DE LABORATORIO 12 Archivos, Archivos de texto Contenido Introducción .................................................................................................... 1 Archivos y flujos en C++ ................................................................................. 1 Archivos de encabezado................................................................................. 2 Abrir Archivos.................................................................................................. 2 Modos de apertura de archivo ........................................................................ 4 Cerrar archivos ............................................................................................... 5 Lectura y Escritura de un Archivo de texto...................................................... 5 Estado de las operaciones de E/S sobre archivos.......................................... 9 Ejercicio .......................................................................................................... 9 Introducción Un archivo es un grupo de registros relacionados almacenados en memoria secundaria. Por ejemplo, un archivo de nómina de una compañía contiene normalmente un registro por cada empleado. De esta manera, el archivo de nómina de una compañía pequeña podría contener sólo 20 registros y, en cambio un archivo de nómina de una compañía grande podría contener hasta 50.000 registros. La ventaja es que la información almacenada en un archivo es persistente en el tiempo, no es susceptible a fallas eléctricas y puede reproducirse y transportarse a bajo costo. Archivos y flujos en C++ C++ ve a cada archivo simplemente como una secuencia de bytes que termina con un marcador especial de fin de archivo. Las bibliotecas estándar de C++ proporcionan un amplio conjunto de capacidades de entrada/salida. En general, la E/S en C++ se da en flujo de bytes. Un flujo es simplemente una secuencia de bytes. En las operaciones de entrada, los bytes fluyen desde un dispositivo (por ejemplo: teclado, unidad de disco, etc.) hacia la memoria principal. En operaciones de salida los bytes fluyen de la memoria principal hacia un dispositivo (por ejemplo: pantalla, impresora, unidad de disco, etc.). Ya se ha visto que existen cuatro objetos que se crean automáticamente cuando se utiliza la biblioteca iostream: cin, cout, cerr y clog. Estos objetos asocian canales de comunicación entre un programa y un archivo o dispositivo particular. Por ejemplo, el objeto cin (flujo de entrada estándar) permite que un programa reciba datos desde el teclado; el objeto cout (flujo de salida estándar) permite que un programa envíe datos a la pantalla, y los objetos cerr y clog (flujo de error estándar) permiten que un programa envíe mensajes de error a la pantalla. Archivos de encabezado Para realizar el procesamiento de archivos en C++ se deben incluir los archivos de encabezado <iostream.h> y <fstream.h>. El archivo <fstream.h> incluye las definiciones para las clases ifstream (para entrada desde un archivo), ofstream (para salida hacia un archivo) y fstream (para entrada y salida de un archivo). ios istream ifstream ostream iostream ofstream fstream Para crear un flujo para leer/escribir archivos a disco (archivos físicos), lo primero que debe hacer es definir un objeto (llamado también archivo lógico) para una de las clases de archivo convenientemente: • la clase ifstream, para definir los archivos que se usan exclusivamente para entrada de datos (leer). • la clase ofstream, para definir los archivos que se usan exclusivamente para la salida de datos (escribir). • la clase fstream, para definir archivos de entrada y/o salida de datos. El siguiente programa muestra la forma de crear estos objetos de la clase fstream: #include <fstream> using namespace std; int main(){ ifstream lectura; // para leer un archivo ofstream escritura; // para escribir un archivo fstream lecturaEscritura; // para leer y escribir un archivo return 0; } Abrir Archivos El método open(), heredado por todas las clases del flujo de archivos, permite asociar el objeto del flujo de archivos (archivo lógico) a un archivo físico en disco y luego abrirlo para su acceso. La forma de llamar a este método es: <objeto de flujo de archivo>.open (<nombre del archivo físico>, <modo de apertura de archivo>); El nombre del objeto es seguido por un punto y después por la función open() y luego sus argumentos. Los argumentos son: un nombre del archivo en disco y un designador del modo de apertura (opcional). El nombre del archivo en disco se debe ajustar a los requerimientos del sistema operativo. El nombre del archivo en el disco físico se puede especificar directamente dentro de dobles comillas (por ejemplo “ejemplo.txt”) o en forma indirecta como una variable tipo cadena. El argumento indicador del modo de apertura define que tipo de acceso se realizará dentro del archivo (detallado en la siguiente sección). Nota: El (los) modo(s) de lectura/escritura siempre debe(n) especificarse cuando un objeto de flujo de archivos se define con la clase fstream, pues por definición, esta clase se usa para ambos accesos a archivos de entrada y salida (lectura/escritura) Los modos lectura/escritura no necesitan especificarse cuando se abren archivos definidos por las clases ifstream u ofstream, porque los archivos son predeterminados como entrada y salida respectivamente. Compile y ejecute el siguiente programa y luego verifique los archivos creados en el directorio. ¿Por qué solo se crea el archivo ejemplo2.txt? #include <fstream> using namespace std; int main(){ ifstream lectura; // para leer un archivo físico ofstream escritura; // para escribir un archivo físico fstream lecturaEscritura; // para leer y escribir un archivo // Archivo logico lectura que lea del archivo de disco ejemplo1.txt. lectura.open("ejemplo1.txt"); // Archivo lógico escritura que escribe al archivo de disco ejemplo2.txt. escritura.open("ejemplo2.txt"); // Archivo logico lescturaEscritura que lea y escriba el archivo ejemplo3.txt. lecturaEscritura.open("ejemplo3.txt", ios::in | ios::out); return 0; } Después de crear los objetos y abrirlos, se recomienda verificar si la operación tuvo éxito a través del operador sobrecargado ! de ios, retornando true si la operación falla. Algunos posibles errores son: intentar abrir un archivo que no existe en modo lectura, abrir un archivo de lectura sin permiso y abrir un archivo para escritura si no hay espacio en disco. El siguiente código realiza esta verificación (compile y ejecute): #include <iostream> #include <fstream> using namespace std; int main(){ ifstream lectura; // para leer un archivo ofstream escritura; // para escribir un archivo fstream lecturaEscritura; // para leer y escribir un archivo lectura.open("ejemplo1.txt"); escritura.open("ejemplo2.txt"); lecturaEscritura.open("ejemplo3.txt", ios::in | ios::out); if (!lectura) cout << "No se puede abrir el acrhivo lectura -> ejemplo1.txt" << endl; if (!escritura) cout << "No se puede abrir el acrhivo escritura --< ejemplo2.txt" << endl; if (!lecturaEscritura) cout << "No se puede abrir el acrhivo lecturaEscritura --> ejemplo3.txt" << endl; return 0; } Los constructores paramétricos de las clases fstream, ifstream y ofstream además de crear el objeto del archivo físico correspondiente, permiten abrir un archivo al igual que el método open(). El siguiente código lo muestra (compile y ejecute): #include <iostream> #include <fstream> using namespace std; int main(){ ifstream lectura("ejemplo1.txt"); ofstream escritura("ejemplo2.txt"); fstream lecturaEscritura("ejemplo3.txt", ios::in | ios::out); if (!lectura) cout << "No se puede abrir el acrhivo lectura -> ejemplo1.txt" << endl; if (!escritura) cout << "No se puede abrir el acrhivo escritura --< ejemplo2.txt" << endl; if (!lecturaEscritura) cout << "No se puede abrir el acrhivo lecturaEscritura --> ejemplo3.txt" << endl; return 0; } Modos de apertura de archivo Dependiendo del propósito del programa debe indicar el modo de acceso, por ejemplo es posible que sea necesario agregar datos a los ya existentes en el archivo, o quizá que las operaciones del programa no se lleven a cabo en caso de que el archivo especificado exista en el disco, etc., Dependiendo del caso podemos especificar el modo de apertura según la siguiente tabla: Modo ios::app Descripción Anexa toda la salida al final del archivo ios::ate ios::in ios::out ios::nocreate ios::noreplace ios::trunc ios::binary Abre en modo salida y se coloca el apuntador del archivo al final del mismo. Los datos pueden escribirse en cualquier parte del archivo Operaciones de lectura. Esta es la opción por defecto para objetos de la clase ifstream. Operaciones de escritura. Esta es la opción por defecto para objetos de la clase ofstream. Si el archivo no existe se suspende la operación Crea un archivo, si existe uno con el mismo nombre la operación se suspende. Crea un archivo, si existe uno con el mismo nombre lo borra. Operaciones binarias (no es texto) Cerrar archivos Después de terminar el procesamiento del archivo, deberá cerrar el archivo para garantizar su contenido y permitir su posterior uso. Esto se realiza con la función close() Para cerrar un archivo, lo que necesita hacer es llamar al método close() con un objeto de flujo de archivo. Como resultado, los enunciados lecturaEscritura.close(), escritura.close() y lectura.close() deberán cerrar los archivos que se abrieron anteriormente. Lectura y Escritura de un Archivo de texto Se escribe un archivo en disco usando su objeto de flujo de archivo de salida y el operador de inserción <<, de la misma forma como se usó el objeto de flujo de archivo estándar cout y el operador <<. De esta manera, luego de abrir un objeto de archivo de salida las siguientes instrucciones permiten escribir: ofstream salida; salida.open(“ejemplo.txt”); salida << “Esto es un ejemplo” << endl; salida.close(); Verifique en su disco que aparece el archivo “ejemplo.txt” y que contiene el texto. Se pruebe escribir cualquier tipo de variable (string, arreglo de caracteres, entero, etc.) que tenga definido el operador <<. El siguiente programa genera un archivo de texto donde cada línea contiene un número de línea correspondiente al valor inicial del número de líneas solicitado al usuario (copie, compile, ejecute y verifique el archivo creado con su contenido): #include <iostream> #include <fstream> using namespace std; int main(){ int numl,i; ofstream salida; // flujo de salida // Apertura del archivo, crear y abrir un nuevo archivo salida.open("ejemplo-escribir.txt"); // Verificando la operacion -- si el archivo existe o no if (!salida) { cout<<"No se puede abrir el archivo\n"; exit(1); } else { cout<<"Suministre el total de lineas del archivo: "; cin>>numl; for (i=0;i<numl;i++) { //Escritura de un archivo de texto salida << i+1 <<"\n"; } } salida.close(); //Cierre del archivo cout<<"****** Archivo Procesado ******\n"; return 0; } Se lee un archivo en disco con el objeto de flujo de archivo de entrada y el operador de extracción >>, tal como se usa el objeto de flujo de archivo estándar cin y el operador >>. De esta manera, luego de abrir un objeto de archivo de entrada las siguientes instrucciones permiten leer la primera secuencia de caracteres delimitada por el carácter espacio en blanco: string cadena; ifstream entrada; entrada.open(“ejemplo.txt”); entrada >> cadena; cout << cadena << endl; // muestra lo leído del archivo entrada.close(); Ejecute el código anterior y verifique lo que muestra por pantalla, note que no es toda la línea que contiene el archivo ejemplo.tx. Note que la cadena se definió como un string por tanto debe incluir la clase string de C++. Si desea obtener toda una línea del archivo de entrada (independientemente del número de palabras) debe usarse la función getline, en lugar de “entrada >> cadena”, de la siguiente forma: getline(entrada, cadena, ‘\n’); Copie, compile y ejecute el siguiente código que muestra como leer el archivo ejemplo-escribir.txt línea a línea y mostrarlo por pantalla. #include <iostream> #include <fstream> #include <string> using namespace std; int main(){ string i; ifstream entrada; // flujo de entrada // Apertura del archivo: abrir un nuevo existente) entrada.open("ejemplo-escribir.txt"); // Verificando la operacion -- si el archivo existe o no if (!entrada) { cout<<"No se puede abrir el archivo\n"; exit(1); } else { /* do { //Lectura de un archivo de texto entrada >> i; cout << i << endl; // muestra lo pantalla } while (entrada); */ } entrada.close(); //Cierre del archivo cout<<"****** Archivo Procesado ******\n"; return 0; } Hay muchas formas de hacer un ciclo iterativo para leer un archivo de forma secuencial línea por línea hasta el final. Las siguientes son alternativas: while (entrada >> i) { cout << i << endl; // muestra lo pantalla } while (!entrada.eof()) { entrada >> i; cout << i << endl; // muestra lo pantalla } entrada >> i; while (!entrada.eof()) { cout << i << endl; // muestra lo pantalla entrada >> i; } Sin embargo, la última alternativa deja de procesar la última línea. Esto se debe a que cuando se abre un archivo de lectura, un cursor que forma parte del archivo lógico se ubica en la primera línea del archivo físico, luego una vez leída la línea el cursor se ubica en la siguiente. Cuando se ejecuta eof(), este método determina si dicho cursor esta parado en el carácter de fin de archivo y se da el valor de verdad en caso positivo, entonces se sale del ciclo y no se procesa la última línea. Se debe tener cuidado con la forma en que realiza el ciclo iterativo. Cuando se tienen archivos de textos estructurados, es decir con más de un valor por línea y de tamaño fijo el número de campos. Se puede escribir con el operador << y agregar el separador que se necesite. Para leer de debe usar el operador >> tantas veces como valores están separados por un espacio en blanco o fin de línea. El siguiente código ilustra como crear y leer un archivo de 2 enteros por líneas: #include <iostream> #include <fstream> using namespace std; int main(){ int numl,i,j; ofstream salida; // flujo de salida salida.open("ejemplo-escribir2.txt",ios::out); if (!salida) { cout<<"No se puede abrir el archivo\n"; exit(1); } else { cout<<"Suministre el total de lineas del archivo: "; cin>>numl; for (i=0;i<numl;i++) { //Escritura de un archivo de texto salida << i+1 << " " << numl+i <<"\n"; } } salida.close(); //Cierre del archivo cout<<"****** Archivo de salida Procesado ******\n"; ifstream entrada; // flujo de entrada entrada.open("ejemplo-escribir2.txt"); if (!entrada) { cout<<"No se puede abrir el archivo\n"; exit(1); } else { do { //Lectura de un archivo de texto entrada >> i >> j; cout << i << " " << j << endl; // muestra lo pantalla } while (entrada); } entrada.close(); //Cierre del archivo cout<<"****** Archivo de entrada Procesado ******\n"; return 0; } El archivo “ejemplo-escribir2.txt” para un valor numl = 3 es el siguiente: 1 3 2 4 3 5 De la misma forma puede escribirse y leer un registro (struct de C++) sobre un archivo de texto, de la siguiente forma: struct datos{ int ci; string nombre; }; . . . ofstream salida(“ejemplo3.txt”); datos D; salida << D.ci << "\t" << D.nombre <<"\n"; salida.close(); . . . ifstream entrada("ejemplo-escribir3.txt"); datos D1; entrada >> D1.ci >> D1.nombre; entrada.close(); Si el campo nombre puede esta formado por más de una palabra entonces debe usarse getline para leer toda la línea y procesarla con los métodos de la clase string, pues el operador >> y << no servirán en este caso. Estado de las operaciones de E/S sobre archivos Se recomienda utilizar los siguientes métodos para verificar el éxito de las operaciones de, apertura, lectura y escritura sobre archivos: • good(): produce un 1 si la operación previa fué exitosa. • eof(): produce un 1 si se encuentra el final del archivo. • fail(): produce un 1 si ocurre un error. • bad(): produce un 1 si se realiza una operación inválida. Ejercicio 1. El departamento de Robótica de una compañía tiene varios tipos de robots. Para cada robot se tiene un archivo de texto secuencial (Archivo 1) que contiene los registros de las órdenes válidas formadas por 2 campos: a. el campo parte (cabeza, manos, brazos, piernas, pies, etc.) del robot y b. el campo movimiento (derecha, izquierda, arriba, abajo, etc.) de la parte Este archivo contiene registros ordenados por el campo parte (puede estar duplicado!). Por otra parte, la interfaz del robot puede ingresar todas las combinaciones de partes y movimientos a través del Archivo 2, pero para cada tipo de robot el Archivo 1 determina sus movimientos válidos. El Archivo 2, entonces expresa los requerimientos del usuario para intentar mover el robot y contiene la misma estructura que el Archivo 1 con la diferencia que contiene movimientos válidos e inválidos desordenados del robot. Se quiere que ud., procese los archivos de texto secuencial para que: a) Implemente un validador: dado como parámetros el Archivo 1 y Archivo 2, diga si el Archivo 2 es válido o inválido según el Archivo 1 b) Implemente un filtro: dado como parámetros el Archivo 1 y Archivo 2, genere la partición del Archivo 2 en dos archivos de salidas: Ordenes Válidas y Ordenes Inválidas, según el Archivo 1 Por ejemplo: Archivo 2 PIED DER CABEZA ARR CABEZA DER BRAZOD DER MANOI ARR PIEI IZQ BRAZOI IZQ Archivo 1 BRAZOD ABA BRAZOD ARR CABEZA ARR CABEZA DER CABEZA IZQ MANOD IZQ Archivo1 Archivo2 Archivo 1 Archivo 2 Validador (a) Filtro (b) Inválido Ordenes Inválidas PIED DER BRAZOD DER MANOI ARR PIEI IZQ BRAZOI IZQ Ordenes Válidas CABEZA ARR CABEZA DER Elaborado: prof. Hilda Contreras Revisado: prof. Rafael Rivas y prof. Gilberto Díaz.