Manejo de archivos en C++ 1 de noviembre de 2002 1. Streams En C++, los archivos se manejan con un tipo particular de stream. Un stream es una estructura de datos que se utiliza para manejar un “flujo de caracteres” y permitir poner o sacar de él tipos de datos estándar o clases definidas por el usuario. Por ejemplo, cout es un stream que nos permite escribir caracteres en la consola. Repasemos un poco el funcionamiento de cout y cin: #include <iostream> using namespace std; void main() { int n; cout << "Ingrese un numero entero" << endl; cin >> n; cout << "El numero que usted ingreso es el " << n << endl; } En este ejemplo podemos ver los dos operadores principales que se usan para trabajar con streams: << para streams de salida y >> para streams de entrada. Para recordar mejor el significado de estos operadores, podemos pensar que << “pone” las cosas en el stream y que >> las “saca”. Además, podemos ver que ambos operadores “saben” convertir entre enteros y caracteres, en el sentido en que corresponda. Estos dos operadores se pueden definir para cualquier clase, pero nosotros no vamos a trabajar con entrada y salida redefiniéndolos. cin es un ejemplo de un istream y cout es un ejemplo de un ostream. 1.1. >> Este operador, también llamado “de extracción”, saca cosas de un stream y las coloca en la variable recibida como parámetro. En su comportamiento por defecto, este operador ignora los espacios en blanco. Para entender esto, supongamos que el stream contiene lo siguiente: 1 2 3 4 | 1 AyEDI — 2do cuatrimestre de 2002 2 (| indica dónde se va a realizar la próxima extracción). Si la siguiente instrucción de nuestro programa es cin >> i; donde i es una variable de tipo int, i va a pasar a valer 1 y el estado del stream va a ser 2 3 4 | Si nuevamente hacemos cin >> i; el programa va a saltear el espacio, va a poner el 2 en i y va a dejar el puntero en el siguiente espacio en blanco. Es decir, al leer de un stream, por defecto, se ignoran los espacios en blanco. 1.2. Manipuladores Para cambiar el comportamiento de un stream, se pueden utilizar ciertas funciones especiales, llamadas manipuladores. Los manipuladores están definidos en el archivo iomanip. Veamos algunos de los más útiles. 1.2.1. endl Para marcar el final de una lı́nea en un stream de salida, podemos usar el manipulador endl #include <iostream> #include <iomanip> using namespace std; void main() { cout << "Hola!" << endl; cout << "| este palito esta justo abajor de la H" << endl; } 1.2.2. skipws, noskipws Si estamos trabajando con un stream de entrada, y queremos que >> no ignore los espacios en blanco, el manipulador noskipws es justo lo que necesitamos. Veamos un ejemplo: #include <iostream> #include <iomanip> using namespace std; void main() AyEDI — 2do cuatrimestre de 2002 3 { char c; cout << "Ingrese dos digitos separados por un espacio" << endl; cin >> c; cout << c; cin >> c; cout << c << endl; cout << "Ingrese dos digitos separados por un espacio" << endl; cin >> noskipws >> c; cin >> c; cout << c; cin >> c; cout << c; cin >> c; cout << c; } Si el usuario ingresa 1 2 1 2 Obtiene la siguiente salida 1 2 12 1 2 1 2 Para deshacer el efecto de noskipws, simplemente utilizamos skipws. 1.2.3. boolalpha, noboolalpha A menudo utilizamos el stream de salida por consola para conocer el valor de las variables de nuestro programa en distintos momentos. Cuando pedimos mostrar una variable de tipo bool, obtenemos un 0 o un 1. Si queremos algo un poco más significativo, podemos utilizar el manipulador boolalpha. #include <iostream> #include <iomanip> using namespace std; void main() { cout << true << endl; cout << boolalpha << true << endl; } La salida de este programa es 1 true Nuevamente, para deshacer el efecto de boolalpha, utilizamos noboolalpha. AyEDI — 2do cuatrimestre de 2002 2. 4 Archivos En C++, los archivos se manejan mediante filestreams o fstreams. Estos son streams que además proveen funciones para manipular archivos. Es decir, si nuestro programa escribe cosas en la consola, con los mismos comandos podemos escribir cosas en un archivo. Simplemente hay que cambiar cout por el nombre del stream que maneje nuestro archivo. 2.1. Entrada Para abrir un archivo para lectura, utilizamos un ifstream. Para eso, tenemos que definir una variable de ese tipo y vincularla a algún archivo. Para realizar esta vinculacion, tenemos dos métodos: dar el nombre del archivo al declarar la variable o utilizar el método open. Ambos producen el mismo resultado. Veamos un ejemplo. #include <iostream> #include <fstream> #include <iomanip> using namespace std; void main() { ifstream entrada("Hola.txt"); ifstream input; char c; input.open("Hello.txt"); if (entrada.good()) cout << "El archivo Hola.txt fue abierto correctamente"; else cout << "El archivo Hola.txt no pudo ser abierto correctamente"; cout << endl; entrada >> c; entrada.close(); input.close() cout << c << endl; } En este ejemplo, nuestro programa abre dos archivos para entrada y lee un caracter de uno de ellos. Cuando los archivos ya no son necesarios, los cerramos utilizando el método close. Una vez que cerramos el archivo, podemos usar el mismo ifstream para leer de otro archivo distinto, o del mismo archivo una vez más. Además de la apertura de archivos de entrada, vemos el uso del método good. Este método está presente en todos los streams y nos indica si la próxima AyEDI — 2do cuatrimestre de 2002 5 operación de lectura/escritura será viable. En el caso de archivos, preguntar good después de abrir un archivo nos informa si se pudo abrir correctamente. Cuando se trabaja con streams de entrada, a menudo es necesario leer un caracter y volverlo a poner dentro del stream. Para eso, los istreams poseen el método putback(char), que coloca el caracter en el stream, pasando a ser el próximo caracter que se leerá. Otro método útil para manipular ifstreams es eof(). Este nos dice si ya llegamos al final del archivo. Eso quiere decir que ya no se podrá leer más. 2.2. Salida Para manejar un archivo de salida, utilizamos un ostream #include <iostream> #include <fstream> #include <iomanip> using namespace std; void main() { ofstream salida("Hola.txt"); ofstream output; char c; output.open("Hello.txt"); cin >> c; salida << c; salida.close(); output.close(); } En este ejemplo, no revisamos si el archivo fue abierto correctamente. Si el archivo no existe, se creará. Solo puede haber problemas si no se puede crear un archivo con ese nombre en el directorio o si alguien tiene el archivo abierto. C++ permite abrir un archivo para escribir y leer de él, pero no utilizaremos este tipo de fstream en la materia. 3. Enum enum es un mecanismo del lenguaje para definir constantes simbólicas. Por ejemplo, si queremos representar los palos de una baraja francesa en C++, crear una clase puede ser demasiado, ya que solo tenemos que tratar con cuatro valores sencillos. Entonces, recurrimos al enum. #include <iostream> #include <iomanip> AyEDI — 2do cuatrimestre de 2002 6 using namespace std; enum Palo {Corazones = ’C’, Treboles = ’T’, Diamantes = ’D’, Piques = ’P’}; void main() { Palo p; p = Corazones; cout << p << endl; cout << char(p); } En este ejemplo, asignamos a cada una de las constantes simbólicas (Corazones, etc.) un caracter. Ese caracter podrı́a usarse, por ejemplo, para almacenar el palo en un archivo. La salida de este ejemplo es 67 C Esto nos muestra que C++ trata a los enum como enteros. Si queremos ver (o guarda en un archivo) el caracter correspondiente al palo, debemos encerrar la variable en un char(). Una forma alternativa de leer y escribir enums en streams es usar los métodos put y get. Estos solo manejan un caracter. Entonces, si queremos escribir en patalla un Trebol, podemos hacer cout.put(Trebol); cout << endl; Esto nos dará T Leer un enumerado es más complicado. Debemos poner el contenido en un char y después asignarlo al enumerado. Si el usuario ingresa P char c; Palo p; cin >> c; p = Palo(c); dejará en p el valor Piques. 4. DJGPP El compilador que la cátedra utiliza no cumple en un 100 % con el estándar ANSI C++. En particular, no posee manipuladores (a excepción de endl y AyEDI — 2do cuatrimestre de 2002 7 algún otro no muy relevante). De todas formas, sı́ brinda una forma de cambiar el comportamiento de un istream respecto a los espacios en blanco. Para hacer que éstos no sean ignorados, debemos usar el método unsetf de la siguiente forma cin.unsetf(ios::skipws); Si queremos volver a ignorar los espacios en blanco, hacemos cin.setf(ios::skipws);