El documento describe cómo crear y procesar archivos en C++. Explica que los archivos se utilizan para almacenar datos de forma permanente, mientras que las variables solo almacenan datos de forma temporal. Luego, presenta un ejemplo de programa que crea un archivo secuencial para almacenar registros de clientes con deudas, con el número de cuenta como clave de cada registro. El programa abre el archivo, comprueba si la apertura fue exitosa, y luego recopila datos de cliente e introduce cada registro en el archivo de forma secuencial.
2. Procesamiento
de archivos
• El almacenamiento de datos en variables y arreglos es temporal. Los
archivos se utilizan para la persistencia de los datos: datos de retención
permanente. Las computadoras almacenan archivos en dispositivos de
almacenamiento secundario como discos duros, CDs, DVDs, unidades
flash y cintas magnéticas. Explicaremos cómo construir programas en
C++ para crear, actualizar y procesar archivos de datos.
3. Procesamiento
de archivos
• Consideraremos los archivos secuenciales y los archivos de acceso
aleatorio. Compararemos el procesamiento de archivos de datos con
formato y el procesamiento de archivos de datos puros. Examinaremos
las técnicas para introducir datos de, y enviar datos a f lujos string en
vez de archivos en el capítulo 18, La clase string y el procesamiento de f
lujos de cadena.
4. Procesamiento
de archivos
• Consideraremos los archivos secuenciales y los archivos de acceso
aleatorio. Compararemos el procesamiento de archivos de datos con
formato y el procesamiento de archivos de datos puros. Examinaremos
las técnicas para introducir datos de, y enviar datos a f lujos string en
vez de archivos en el capítulo 18, La clase string y el procesamiento de f
lujos de cadena.
5. Jerarquía de datos
• Finalmente, todos los elementos de datos que procesan las computadoras
digitales se reducen a combinaciones de ceros y unos. Esto ocurre debido a que
es simple y económico construir dispositivos electrónicos que puedan asumir
uno de dos estados estables: un estado representa 0 y el otro representa 1. Es
increíble que las impresionantes funciones realizadas por las computadoras
impliquen solamente las manipulaciones más fundamentales de 0s y 1s.
6. Jerarquía de datos
• El elemento más pequeño de datos que soportan las computadoras se
conoce como bit (abreviatura de “dígito binario”; un dígito que puede
suponer uno de dos valores).. Cada elemento de datos, o bit, puede
asumir el valor 0 o el valor 1Los circuitos de computadora realizan varias
manipulaciones simples de bits, como examinar o establecer el valor de
un bit, o invertir su valor (de 1 a 0 o de 0 a 1).
7. Jerarquía de datos
• Es muy difícil para los programadores trabajar con datos en el formato de bits
de bajo nivel. Es preferible trabajar con datos en formatos como dígitos
decimales (0-9), letras (A-Z y a-z) y símbolos especiales ( por ejemplo, $, @, %,
&, * y muchos otros). Los dígitos, letras y símbolos especiales se conocen como
caracteres. El conjunto de caracteres de una computadora es el conjunto de
todos los caracteres utilizados para escribir programas y representar elementos
de datos de esa computadora.
8. Jerarquía de datos
• Las computadoras pueden procesar solamente 1s y 0s, por lo que cada
carácter en el conjunto de caracteres de una computadora se representa
como un patrón de 1s y 0s. Los bytes están compuestos de ocho bits. Los
programadores crean programas y elementos de datos con caracteres;
las computadoras manipulan y procesan estos caracteres como patrones
de bits. Por ejemplo, C++ proporciona el tipo de datos char.
9. Jerarquía de datos
• Cada char por lo general ocupa un byte. C++ también proporciona
el tipo de datos wchar_t, que puede ocupar más de un byte ( para
soportar conjuntos de caracteres más grandes, como el conjunto
de caracteres Unicode®; para obtener más información acerca de
Unicode®, visite el sitio www.unicode.org).
10. Jerarquía de datos
• Así como los caracteres están compuestos de bits, los campos están compuestos de
caracteres. Un campo es un grupo de caracteres que transmiten cierto significado. Por
ejemplo, un campo que consiste de letras mayúsculas y minúsculas puede representar el
nombre de una persona.
• Los elementos de datos que son procesados por las computadoras forman una jerarquía de
datos (figura 1), en la cual los elementos de datos se hacen más grandes y complejos en
estructura, a medida que progresamos de bits a caracteres, de caracteres a campos, hacia
agregados de datos más grandes.
12. Jerarquía de datos
• Generalmente, un registro (que puede ser representado como una clase en C++)
está compuesto de varios campos (conocidos como miembros de datos en C++).
Por ejemplo, en un sistema de nóminas el registro para un empleado específico
podría incluir los siguientes campos:
• 1. Número de identificación del empleado
• 2. Nombre
• 3. Dirección
13. Jerarquía de datos
• 4. Sueldo por hora
• 5. Número de exenciones reclamadas
• 6. Ingresos desde inicio de año a la fecha
• 7. Monto de impuestos retenidos
14. Jerarquía de datos
• Por lo tanto, un registro es un grupo de campos relacionados. En el ejemplo
anterior, cada campo está asociado al mismo empleado. Un archivo es un grupo
de registros relacionados. El archivo de nómina de una compañía generalmente
contiene un registro para cada empleado. Por ejemplo, un archivo de nómina
para una pequeña compañía podría contener sólo 22 registros, mientras que un
archivo de nómina para una compañía grande podría contener 100,000 registros.
Es común para una compañía tener muchos archivos, algunos de ellos
conteniendo miles de millones, o incluso billones de caracteres de información
15. Jerarquía de datos
• Para facilitar la recuperación de registros específicos de un archivo, debe
seleccionarse cuando menos un campo en cada registro como clave de registro.
Una clave de registro sirve para identificar que un registro pertenece a una
persona o entidad específica, y es única en cada registro. En el registro de
nómina que describimos anteriormente, por lo general se elegiría el número de
identificación de empleado como clave de registro.
16. Jerarquía de datos
• Existen muchas formas de organizar los registros en un archivo. Un tipo común
de organización se conoce como archivo secuencial, en el cual los registros se
almacenan en orden, con base en el campo que es la clave de registro. En un
archivo de nómina, los registros generalmente se colocan en orden, con base en
el número de identificación de empleado. El registro del primer empleado en el
archivo contiene el número de identificación de empleado más pequeño, y los
registros subsiguientes contienen números cada vez mayores.
17. Jerarquía de datos
• La mayoría de las empresas utilizan muchos archivos distintos para almacenar datos. Por
ejemplo, una compañía podría tener archivos de nómina, de cuentas por cobrar (listas
del dinero que deben los clientes), de cuentas por pagar (listas del dinero que se debe a
los proveedores), archivos de inventarios (listas de información acerca de los artículos
que maneja la empresa) y muchos otros tipos de archivos. A menudo, un grupo de
archivos relacionados se almacena en una base de datos. A una colección de programas
diseñada para crear y administrar bases de datos se le conoce como sistema de
administración de bases de datos (DBMS).
18. Archivos y flujos
• C++ considera a cada archivo como una secuencia de bytes (figura 2). Cada archivo
termina con un marcador de fin de archivo o con un número de bytes específico que se
registra en una estructura de datos administrativa, mantenida por el sistema. Cuando se
abre un archivo, se crea un objeto y se asocia un flujo a ese objeto. Vimos que los
objetos cin, cout, cerr y clog se crean cuando se incluye <iostream>. Los flujos asociados
con estos objetos proporcionan canales de comunicación entre un programa y un archivo
o dispositivo específico.
19. Archivos y flujos
• Por ejemplo, el objeto cin (objeto flujo de entrada estándar) permite a un
programa introducir datos desde el teclado o desde otros dispositivos, el objeto
cout (objeto flujo de salida estándar) permite a un programa enviar datos a la
pantalla o a otros dispositivos, y los objetos cerr y clog (objetos flujo de error
estándar) permiten a un programa enviar mensajes de error a la pantalla o a
otros dispositivos.
21. Archivos y flujos
• Para llevar a cabo el procesamiento de archivos en C++, se deben incluir los
archivos de encabezado <iostream> y <fstream>. El encabezado <fstream>
incluye las definiciones para las plantillas de clases de flujos basic_ ifstream (
para las operaciones de entrada con archivos), basic_ofstream ( para las
operaciones de salida con archivos) y basic_fstream ( para las operaciones de
entrada y salida con archivos). Cada plantilla de clase tiene una especialización
de plantilla predefi nida que permite las operaciones de E/S con valores char.
22. Archivos y flujos
• Además, la biblioteca <fstream> proporciona alias typedef para estas
especializaciones de plantilla. Por ejemplo, la definición typedef istream
representa a una especialización de basic_ifstream que permite la entrada de
valores char desde un archivo. De manera similar, typedef ofstream representa
una especialización de basic_ofstream que permite enviar valores char a
archivos. Además, typedf fstream representa una especialización de
basic_fstream que permite introducir valores char desde (y enviarlos hacia)
archivos.
23. Archivos y flujos
• Para abrir los archivos, se crean objetos de estas especializaciones de plantillas
de flujo. Estas plantillas se “derivan” de las plantillas de clases basic_istream,
basic_ostream y basic_iostream, respectivamente. Por ende, todas las funciones
miembro, operadores y manipuladores que pertenecen a estas plantillas
también se pueden aplicar a los flujos de archivos. En la figura 3 se sintetizan
las relaciones de herencia de las clases de E/S que hemos visto hasta este
punto.
25. Creación de un archivo secuencial
• C++ no impone una estructura sobre un archivo. Por ende, en un archivo de C++
no existe un concepto tal como el de un “registro”. En consecuencia, el
programador debe estructurar los archivos de manera que cumplan con los
requerimientos de la aplicación. En el siguiente ejemplo veremos cómo se
puede imponer una estructura de registro simple sobre un archivo.
26. Creación de un archivo secuencial
• La figura 4 crea un archivo secuencial que podría utilizarse en un archivo de
cuentas por cobrar, para ayudar a administrar el dinero que deben los clientes
de crédito a una empresa. Para cada cliente, el programa obtiene su número de
cuenta, nombre y saldo (es decir, el monto que el cliente debe a la empresa por
los bienes y servicios recibidos en el pasado).
27. Creación de un archivo secuencial
• Los datos que se obtienen para cada cliente constituyen un registro para ese
cliente. El número de cuenta sirve como la clave de registro; es decir, el
programa crea y da mantenimiento al archivo en orden por número de cuenta.
28. Creación de un archivo secuencial
• Este programa supone que el usuario introduce los registros en orden por
número de cuenta. En un sistema de cuentas por cobrar completo, sería
conveniente contar con una herramienta para ordenar datos, para que el
usuario pueda introducir los registros en cualquier orden; después los registros
se ordenarían y escribirían en el archivo
30. Creación de un archivo secuencial
• Vamos a examinar este programa. Como se dijo antes, para abrir los archivos se
crean objetos ifstream, ofstream o fstream. En la figura 4, el archivo se va a
abrir para salida, por lo que se crea un objeto ofstream. Se pasan dos
argumentos al constructor del objeto: el nombre de archivo y el modo de
apertura de archivo (línea 19).
31. Creación de un archivo secuencial
• Para un objeto ofstream, el modo de apertura de archivo puede ser ios::out
para enviar datos a un archivo, o ios::app para adjuntar datos al final de un
archivo (sin modificar los datos que ya estén en el archivo). Los archivos
existentes que se abren con el modo ios::out se truncan: se descartan todos los
datos en el archivo. Si el archivo especificado no existe todavía, entonces el
objeto ofstream crea el archivo, usando ese nombre de archivo.
32. Creación de un archivo secuencial
• En la línea 19 se crea un objeto ofstream llamado archivoClientesSalida,
asociado con el archivo clientes.
33. Creación de un archivo secuencial
• dat que se abre en modo de salida. Los argumentos "clientes.dat" e ios::out se
pasan al constructor de ofstream, el cual abre el archivo (esto establece una
“línea de comunicación” con el archivo). De manera predeterminada, los
objetos ofstream se abren en modo de salida, por lo que en la línea 19 se podría
haber utilizado la instrucción alterna ofstream archivoClientesSalida(
"clientes.dat" ); para abrir clientes.dat en modo de salida.
35. Creación de un archivo secuencial
• Después de crear un objeto ofstream y tratar de abrirlo, el programa prueba si la
operación de apertura tuvo éxito.
• La instrucción if en las líneas 22 a 26 utiliza la función miembro operator! sobrecargada
de ios para determinar si la operación open tuvo éxito. La condición devuelve un valor
true si se establece el bit failbit o el bit badbit para el flujo en la operación open.
Ciertos posibles errores son: tratar de abrir un archivo no existente en modo de lectura,
tratar de abrir un archivo en modo de lectura o escritura sin permiso, y abrir un archivo
en modo de escritura cuando no hay espacio disponible en disco.
36. Creación de un archivo secuencial
• Si la condición indica un intento fallido de abrir el archivo, en la línea 24 se imprime el
mensaje de error "No se pudo abrir el archivo", y en la línea 25 se invoca a la función
exit para terminar el programa. El argumento para exit se devuelve al entorno desde el
que se invocó el programa. El argumento 0 indica que el programa terminó en forma
normal; cualquier otro valor indica que el programa terminó debido a un error. El
entorno de llamada ( probablemente el sistema operativo) utiliza el valor devuelto por
exit para responder al error en forma apropiada.
37. Creación de un archivo secuencial
• Otra función miembro de operador sobrecargada de ios (operator void*) convierte el f
lujo en un apuntador, para que se pueda evaluar como 0 (es decir, el apuntador nulo) o
un número distinto de cero (es decir, cualquier otro valor de apuntador). Cuando se usa
el valor de un apuntador como una condición, C++ convierte un apuntador nulo en el
valor bool false y un apuntador no nulo en el valor bool true. Si se establecieron los bits
failbit o badbit (vea el capítulo 15) para el f lujo, se devuelve 0 (false).
38. Creación de un archivo secuencial
• La condición en la instrucción while de las líneas 36 a 40 invoca a la función miembro
operator void* en cin de manera implícita. La condición permanece como true, siempre y
cuando no se haya establecido el bit failbit o el bit badbit para cin. Al introducir el
indicador de fi n de archivo se establece el bit failbit para cin. La función operator void *
se puede utilizar para probar un objeto de entrada para el fi n de archivo, en vez de
llamar a la función miembro eof de manera explícita en el objeto de entrada .
39. Creación de un archivo secuencial
• Si en la línea 19 se abrió el archivo con éxito, el programa empieza a procesar los datos.
En las líneas 28 y 29 se pide al usuario que introduzca varios campos para cada registro,
o el indicador de fi n de archivo cuando esté completa la entrada de datos. En la figura 6
se listan las combinaciones de teclado para introducir el fin de archivo en varios sistemas
computacionales.
41. Creación de un archivo secuencial
• En la línea 36 se extrae cada conjunto de datos y se determina si se introdujo el fi n de
archivo. Al encontrar el fi n de archivo, o cuando se introducen datos incorrectos,
operator void * devuelve el apuntador nulo (que se convierte en el valor bool false) y la
instrucción while termina. El usuario introduce el fin de archivo para informar al
programa que ya no procese más datos. El indicador de fin de archivo se establece
cuando el usuario introduce la combinación de teclas de fin de archivo. La instrucción
while itera hasta que se establece el indicador de fi n de archivo.
42. Creación de un archivo secuencial
• En la línea 38 se escribe un conjunto de datos en el archivo clientes.dat, usando el
operador de inserción de flujo << y el objeto archivoClientesSalida asociado con el
archivo al principio del programa. Los datos se pueden recuperar mediante un programa
diseñado para leer el archivo.
43. Creación de un archivo secuencial
• Observe que, debido a que el archivo creado en la figura 4 es simplemente un archivo de
texto, puede verse en cualquier editor de texto. Una vez que el usuario introduce el
indicador de fin de archivo, main termina. Esto invoca de manera implícita a la función
destructor del objeto archivoClientesSalida, que cierra el archivo clientes.dat. También
podemos cerrar el objeto ofstream de manera explícita, usando la función miembro
close en la instrucción archivoClientesSalida.close();
44. Funciones miembro get y getline
• La función miembro get sin argumentos recibe como entrada un
carácter del flujo designado (incluyendo caracteres de espacio en
blanco y otros caracteres no gráficos, como la secuencia de teclas
que representa el fin de archivo) y lo devuelve como el valor de la
llamada a la función.
45. Uso de las funciones miembro eof, get y put
• La figura 4 demuestra el uso de las funciones miembro eof y get en el f lujo de
entrada cin, y la función miembro put en el flujo de salida cout. El programa
primero imprime el valor de cin.eof() [es decir, false (0 en la salida)] para
mostrar que no ha ocurrido el fi n de archivo en cin. El usuario introduce una
línea de texto y oprime Intro seguido del fin de archivo (<Ctrl>-z en sistemas
Microsoft Windows, <Ctrl>-d en sistemas UNIX y Macintosh)
48. Comparación entre cin y cin.get
• La figura 5 compara la entrada mediante el uso de la extracción
de flujo con cin (que lee caracteres hasta que se encuentra un
carácter de espacio en blanco) y la entrada mediante el uso de
cin.get. Observe que la llamada a cin.get (línea 24) no especifica
un delimitador, por lo que se utiliza el carácter predeterminado
'n'.
49. Comparación entre cin y cin.get
• La figura 5 compara la entrada mediante el uso de la extracción
de flujo con cin (que lee caracteres hasta que se encuentra un
carácter de espacio en blanco) y la entrada mediante el uso de
cin.get. Observe que la llamada a cin.get (línea 24) no especifica
un delimitador, por lo que se utiliza el carácter predeterminado
'n'.
52. Uso de la función miembro getline
• La función miembro getline opera de manera similar a la tercera versión de la
función miembro get e inserta un carácter nulo después de la línea en el arreglo
de caracteres. La función getline elimina el delimitador del f lujo (es decir, lee
el carácter y lo descarta), pero no lo almacena en el arreglo de caracteres. El
programa de la fi gura 15.6 demuestra el uso de la función miembro getline
para introducir una línea de texto (línea 15).