viernes, 8 de abril de 2011

Capitulo 12. Archivos

Fuente:
Programación en C++ / Principios y Aplicaciones (resumen)
Carlos Romero Shollande

1.        Introducción

Muchas veces necesitamos almacenar cierta cantidad de datos de forma permanente. La memoria del ordenador es volátil, escasa y cara. De modo que cuando necesitamos guardar nuestros datos durante cierto tiempo tenemos que recurrir a sistemas de almacenamiento externo.
Al principio de la historia de los ordenadores los sistemas de almacenamiento externo eran secuenciales (cintas de papel perforado, tarjetas perforadas y cintas magnéticas), es decir, no permitían acceder al punto exacto donde se guardaba la información requerida, se tenía que partir desde el principio y recorrer el dispositivo hasta llegar al punto donde se encontraba lo que buscábamos.
Con la aparición de los discos magnéticos y posterior empleo de discos duros empezó la técnica de los ficheros de acceso aleatorio, que permiten acceder a cualquier dato almacenado en un fichero en poco tiempo. Con estos discos es más sencillo acceder a cualquier punto de la superficie en poco tiempo, ya que se accede al punto de lectura y escritura usando dos coordenadas físicas. Por una parte la cabeza de lectura/escritura se puede mover en el sentido del radio del disco, y por otra el disco gira permanentemente, con lo que cualquier punto del disco pasa por la cabeza en un tiempo relativamente corto. Esto no pasa con las cintas, donde sólo hay una coordenada física.
En cuanto al tipo de acceso, en C++ podemos clasificar los archivos según varias categorías:

a)        Dependiendo de la dirección del flujo de datos:

-       De entrada: los datos se leen por el programa desde el archivo.
-       De salida: los datos se escriben por el programa hacia el archivo.
-       De entrada/salida: los datos pueden se escritos o leídos.

b)        Dependiendo del tipo de valores permitidos a cada byte:

-       De texto: están compuestos únicamente por textos, sólo caracteres.
-       Binarios: contienen información de cualquier tipo, codificada en forma binaria para el propósito de almacenamiento y procesamiento en ordenadores.

c)        Según el tipo de acceso:

-       Archivos secuenciales: imitan el modo de acceso de los antiguos ficheros secuenciales almacenados en cintas magnéticas y
-       Archivos de acceso aleatorio: permiten acceder a cualquier punto de ellos para realizar lecturas y/o escrituras.

d)        Según la longitud de registro:

-       Longitud variable: en estos archivos cada registro es de longitud diferente; en realidad, en este tipo de archivos no tiene sentido hablar de longitud de registro, podemos considerar cada byte como un registro.
-       Longitud constante: en estos archivos los datos se almacenan en forma de registro de tamaño constante.
-       Mixtos: en ocasiones pueden crearse archivos que combinen los dos tipos de registros, por ejemplo, dBASE usa registros de longitud constante, pero añade un registro especial de cabecera al principio para definir, entre otras cosas, el tamaño y el tipo de registros.

Es posible crear archivos combinando cada una de estas categorías, por ejemplo: archivos secuenciales binarios de longitud de registro constante.

2.        Corrientes y Archivos

El sistema de E/S del C++ proporciona una interfaz, que es independiente del dispositivo real al que se accede, es decir, el sistema de E/S de C++ proporciona un nivel de abstracción entre el programador y el dispositivo que se usa. Esta abstracción se llama corriente y el dispositivo real se llama archivo.

2.2.  Corrientes

El sistema de almacenamiento temporal en archivo esta diseñado para trabajar con una amplia variedad de dispositivos, incluyendo dispositivos de disco, USB, discos duros, etc. Incluso aunque cada dispositivo sea diferente, el sistema de almacenamiento temporal en archivo los transforma en un dispositivo lógico llamado corriente.
Todas las corrientes son similares en su comportamiento, debido a que las corrientes son muy independientes del dispositivo, las mismas funciones que escriben en un archivo de disco pueden hacerlo en consola. Hay dos tipos de corrientes:

a)        Corrientes de texto: es una secuencia de caracteres organizados en líneas terminadas por un carácter de nueva línea (en el estándar ANSI es opcional).
b)        Corrientes binarias: es una secuencia de bytes que tienen una correspondencia uno a uno con un dispositivo externo. Así que no tendrá lugar ninguna traducción de caracteres. Además, el número de bytes escritos (leídos) será el mismo que los encontrados en el dispositivo externo.

2.3.  Archivos

En C++, un archivo es un concepto lógico que puede aplicarse a muchas cosas desde archivos de disco hasta terminales. Una corriente esta asociada con un archivo específico realizando una operación de apertura. Una vez que esta abierto (open) el archivo, se pueden intercambiar la información entre él y el programa.
Si el archivo puede soportar acceso directo (peticiones de posición), entonces abriendo ese archivo también se inicializa el indicador de posición de archivo al comienzo del archivo. Como cada carácter se lee y escribe a un archivo, el indicador de posición se incrementa, asegurando así la progresión a través del archivo.
Se separa una corriente de un archivo específico a través de la operación close. En archivo abierto para salir, el cierre provoca que los contenidos, si hay, de su corriente asociada se escriba en el dispositivo externo.

3.        Sistema de Memoria Intermedia de E/S

El sistema de memoria intermedia de E/S se compone de diversas funciones relacionadas. Estas funciones requieren que se incluya el archivo de cabecera stdio.h en cualquier programa en el que se usen.

3.2.  Declaración de Variables Puntero de Archivo

Sintaxis      : FILE *corriente
Propósito   : Identifica un archivo especifico de disco y se usa por su corriente asociada para dirigir cada una de las funciones de memoria intermedia de E/S al lugar donde se realizan las operaciones.
Ejemplo      : FILE *fichero;

Un puntero de archivo es un puntero a la información que define varios aspectos del archivo, incluyendo su nombre, estado y posición actual. Un puntero de archivo es una variable de tipo FILE, que se define en stdio.h. Con objeto de leer o escribir archivos, el programa necesita utilizar punteros de archivo.

3.3.  Apertura de Archivos

Sintaxis      : corriente = fopen(char *nombre, char *modo);
Propósito   : Abrir una corriente para enlazar un archivo con una corriente y retornar el puntero de archivo asociado con un archivo
Ejemplo      : fichero = fopen(“d:ficha.dat”,”w”);

La función fopen() devuelve la dirección cero o NULL si la operación de apertura falla (por ejemplo un disco lleno o protegido contra escritura antes de comenzar a escribir en él), o un valor (dirección) distinto al correcto.
Si la operación es correcta, a partir de este instante el archivo queda referido por el apuntador corriente hasta el momento en que se cierra.
Los parámetros de entrada son:

a)    nombre: es una cadena de caracteres que contiene un nombre de archivo válido para el sistema operativo y también pueden incluir la especificación del camino completo.
b)    modo: es una cadena de caracteres que contiene el estado deseado de apertura y especifica el tipo de fichero que se abrirá o se creará uno nuevo y el tipo de dato que puede contener: texto (t) o binario (b).

Tabla 1. Valores permitidos de modo

Modo
Significado
“w”
“r”
“a”

“wb”
“rb”
“ab”
“w+”
“r+”
“a+”

“w+b”
“r+b”
“a+b”
“wt”
“rt”
“at”
“w+t”
“r+t”
“a+t”
Crear un nuevo archivo de texto para escritura (o se sobrescribe si ya existe)
Abrir un archivo de texto para lectura (el archivo debe existir)
Añadir a un archivo de texto (se abre para escritura y el cursor se sitúa al final del archivo. Si el fichero no existe, se crea)
Crear un archivo binario para escritura
Abrir un archivo binario para lectura
Añadir a un archivo binario
Crear un archivo nuevo para leer/escribir (o se sobrescribe si ya existe)
Abrir un archivo para leer/escribir (el archivo debe existir)
Añadir a un archivo para leer/escribir (el cursor se sitúa al final del fichero, Si el fichero no existe, se crea
Crear un archivo binario para leer/escribir
Abrir un archivo binario para leer/escribir
Añadir a un archivo binario para leer/escribir
Crear un archivo de texto para escribir
Abrir un archivo de texto para leer
Añadir a un archivo de texto
Crear un archivo de texto para leer/escribir
Abrir un archivo de texto para leer/escribir
Añadir o crear a un archivo de texto para leer/escribir

A la cadena de modo se puede añadir otro carácter como:

-       t: modo texto. Normalmente es el modo por defecto. Se suele omitir.
-       b: modo binario.

3.4.  Cierre de Archivos

Sintaxis      : int fclose(FILE *corriente);
Propósito   : Cerrar una corriente que fue abierta por una llamada a fopen() y antes de abandonar el programa, finalizar su vínculo con nombre
Ejemplo      : fclose(fichero);

Si fclose() devuelve un valor cero (0) significa que la operación de cierre se ha realizado con éxito. Si ha habido algún error, el valor de retorno es la constante EOF. Generalmente, el único momento en que fallará es, o cuando se quita un diskette prematuramente  de la unidad o cuando no hay más espacio en el diskette.
Al cerrar un archivo almacena los datos que aún están en el buffer de memoria, y actualiza algunos datos de la cabecera del archivo que mantiene el sistema operativo. Además permite que otros programas puedan abrir el archivo para su uso. Muy a menudo, los archivos no pueden ser compartidos por varios programas.

4.        Escritura de Archivos

Para escribir en un archivo (abierto en los modos “w” o “a”) se pueden utilizar las funciones:

4.2.  Escritura de Datos con Formato

Sintaxis      : int fprintf(FILE *corriente, const char *formato, lista_datos);
Propósito   : Escribir datos con formato especificado en una cadena de control, pero la salida es a través de una corriente. Devuelve un valor igual al número de datos almacenados en caso de éxito o el valor EOF en caso de error
Ejemplo      : fprintf(fichero, “%c”, caracter);

4.3.  Escritura de un Carácter

Sintaxis      : int fputc(int caracter, FILE *corriente);
Propósito   : Escribir un carácter en una corriente que se abrió para escritura. Si la operación fue completada con éxito devuelve un valor igual al carácter escrito. En caso contrario devuelve EOF..
Ejemplo      : fputc(carácter, fichero);

Los parámetros de entrada son el carácter a escribir, convertido a int y un puntero a una estructura FILE del fichero en el que se hará la escritura.

4.4.  Escritura de Cadena de Caracteres

Sintaxis      : int fputs(const char *cadena, FILE *corriente);
Propósito   : Escribir una cadena (o puntero a caracteres) en una corriente hasta encontrar su final (“\0”). Devuelve un valor igual al último carácter escrito o el valor EOF en caso de error.
Ejemplo      : fputs(cadena, fichero);

Los parámetros de entrada son la cadena a escribir y un puntero a la estructura FILE del fichero donde se realizará la escritura

4.5.  Escritura de Datos en un Archivo

Sintaxis      : int fwrite(void *puntero, int longitud, int registros, FILE *corriente);
Propósito   : Escribir hacia una corriente uno o más registros de una longitud constante almacenados a partir de una dirección de memoria determinada. Devuelve un valor igual al número de registros escritos (no el número de bytes) o EOF en caso de error.
Ejemplo     : fwrite(dirección, longitud, número, fichero);

Los parámetros son: un puntero a la zona de memoria donde se almacenarán los datos leídos, el tamaño de cada registro, el número de registros a leer y un puntero a la estructura FILE del fichero del que se hará la lectura.

5.        Lectura en Archivos

Las funciones de lectura (archivos abiertos en modo r) son:

5.2.  Lectura de Datos con Formato

Sintaxis      : int fscanf(FILE *corriente, const char *formato, lista_direcciones);
Propósito   : Leer datos con formato especificado a través de una corriente. Devuelve un valor igual al número de datos leídos o el valor EOF en caso de error
Ejemplo      : fscanf(fichero, “%c”, &caracter);

5.3.  Lectura de Caracteres

Sintaxis      : int fgetc(FILE *corriente);
Propósito   : Leer un carácter desde una corriente abierta en modo lectura. Devuelve un valor igual al carácter leído como un unsigned char convertido a int. Si no hay ningún carácter disponible o hay error, el valor de retorno es un EOF (fin del archivo)
Ejemplo      : fgetc(fichero);

5.4.  Lectura de Cadenas de Caracteres

Sintaxis      : char fgets(char cadena, int n, FILE *corriente);
Propósito   : Leer una cadena de caracteres hasta un máximo de n-1 caracteres o hasta encontrar un cambio de línea (\n) de una corriente. El cambio de línea también es leído y añade al final el carácter (\0). Devuelve un puntero a la cadena leída en caso de éxito o NULL si se detecta el final del archivo o si hay un error.
Ejemplo      : fgets(cadena, longitud, fichero);

Los parámetros son: la cadena a leer, el número de caracteres máximo a leer y un puntero a una estructura FILE del fichero del que se leerá. El parámetro n nos permite limitar la lectura para evitar desbordar el espacio disponible en la cadena.

5.5.  Lectura de Datos de un Archivo

Sintaxis      : int fread(void *puntero, int longitud, int registros, FILE *corriente);
Propósito   : Leer desde una corriente uno o más registros de una longitud constante, y los almacena en una dirección de memoria especificada. Devuelve un valor menor o igual al número de datos leídos (no el número de bytes) y si esta al final del archivo devuelve 0.
Ejemplo      : fread(dirección, tamaño, número, fichero);

Los parámetros son: un puntero a la zona de memoria donde se almacenarán los datos leídos, la longitud de cada registro, el número de registros a leer y un puntero a la estructura FILE del fichero del que se hará la lectura. El usuario es responsable de asegurarse de que haya espacio suficiente para contener la información leída.

6.        Otras Funciones de Alto Nivel

Las siguientes funciones también son utilizadas en C++:

6.2.  Final de una Corriente

Sintaxis      : int feof(FILE *corriente);
Propósito   : Comprobar si se ha llegado al final de una corriente, en una lectura que efectuamos en ésta. El valor de retorno es distinto de cero sólo si no se ha alcanzado el fin del archivo. Se puede aplicar este método tanto para los archivos de texto como a los binarios.
Ejemplo      : feof(fichero);

Muy frecuentemente deberemos trabajar con todos los valores almacenados en un archivo de forma secuencial, la forma que suelen tener los bucles para leer todos los datos de un archivo es permanecer leyendo mientras no se detecte el fin del archivo.

6.3.  Error en una Operación de Archivo

Sintaxis      : int ferror(FILE *corriente);
Propósito  : Determinar si una operación con archivos produce un error. Devuelve verdad si ha ocurrido un error durante la última operación de archivo y devuelve falso si tuvo éxito la operación.
Ejemplo      : ferror(fichero);

6.4.  Localizador de Posición de Archivo

Sintaxis      : void rewind(FILE *corriente);
Propósito   : Ubicar el cursor (puntero) de lectura/escritura al principio de la corriente especificada como argumento. Si no tiene éxito devuelve un valor falso (≠0) y en caso contrario 0.
Ejemplo      : rewind(fichero);

Es una función heredada de los tiempos de las cintas magnéticas. Literalmente significa "rebobinar", y hace referencia a que para volver al principio de un archivo almacenado en cinta, había que rebobinarla.

6.5.       Grabación de Datos de un Buffer

Sintaxis      : int fflush(FILE *corriente);
Propósito   : Forzar la grabación de los datos acumulados en el buffer de salida del archivo asociado a él. El valor de retorno es cero si la función se ejecutó con éxito, y EOF si hubo algún error. El parámetro de entrada es un puntero a la estructura FILE del fichero del que se quiere vaciar el buffer. Si es NULL se hará el vaciado de todos los ficheros abiertos
Ejemplo      : fflush(fichero);

Para mejorar las prestaciones del manejo de ficheros se utilizan buffers, almacenes temporales de datos en memoria, las operaciones de salida se hacen a través del buffer, y sólo cuando el buffer se llena se realiza la escritura en el disco y se vacía el buffer. En ocasiones nos hace falta vaciar ese buffer de un modo manual, para eso sirve ésta función.

6.6.       Longitud de un Archivo

Sintaxis      : long filelenght(int nombre_archivo);
Propósito    : Devolver la longitud en bytes del archivo referenciado en el argumento
Ejemplo      : filelenght(fichero);

6.7.       Crear Archivo Temporal

Sintaxis      : FILE *tmpfile(void);
Propósito    : Crear un archivo de índole temporal

6.8.       Referencia del Contenido de una Corriente

Sintaxis      : int fseek(FILE *corriente, long int desplazamiento, int origen);
Propósito  : Ubicar el cursor del archivo para leer o escribir, que referencia el contenido de la corriente especificada, en la posición marcada por los argumentos de llamada. El valor de retorno es cero si la función tuvo éxito, y un valor distinto de cero si hubo algún error Se usa en las E/S de acceso directo.
Ejemplo      : fseek(fichero, 128, 0);

Los parámetros de entrada son: un puntero a una estructura FILE del fichero en el que queremos cambiar el cursor de lectura/escritura, el valor del desplazamiento y el punto de origen desde el que se calculará el desplazamiento.
El parámetro origen puede tener tres posibles valores:

-       SEEK_SET o 0, el desplazamiento se cuenta desde el principio del fichero. El primer byte del archivo tiene un desplazamiento cero.
-       SEEK_CUR o 1, el desplazamiento se cuenta desde la posición actual del cursor.
-       SEEK_END o 2, el desplazamiento se cuenta desde el final del archivo.

El desplazamiento es el número de bytes desde el origen hasta alcanzar la nueva posición.

6.9.       Devolver el Valor del Puntero

Sintaxis      : long int ftell(FILE *corriente);
Propósito   : Averiguar la posición actual del cursor de lectura/escritura de un archivo. El valor de retorno será esa posición, o -1 si hay algún error. El parámetro de entrada es un puntero a una estructura FILE del fichero del que queremos leer la posición del cursor de lectura/escritura.

6.10.   Borrar el Directorio

Sintaxis      : long remove(char *nombre);
Propósito   : Borrar del directorio  el archivo especificado en el argumento de llamada. Devuelve 0 si tiene éxito en su misión y -1 en caso contrario

6.11.   Cambiar Nombre del Archivo

Sintaxis      : int rename(char *anterior_nombre, char *nuevo_nombre);
Propósito   : Cambiar el nombre del archivo pasado como primer argumento (anterior nombre), por el pasado como segundo argumento (nuevo nombre). Si rename() no puede cumplir su objetivo devuelve un valor -1, en caso contrario un 0.

6.12.   Relación de un Buffer con un Archivo

Sintaxis      : void setbuf(FILE *corriente, char *buffer);
Propósito  : Relacionar un buffer con un archivo, en orden a utilizarlo para las operaciones de lectura y escritura en el archivo.

Ejemplo:
Mostrar un menú que presente tres opciones: (1) crear, (2) leer y (3) agregar. Estas operaciones se realizaran sobre un archivo temporal usando caracteres.

Codificación:
#include <iostream.h>       // Opciones para manejo de archivos temporales de caracteres
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
main() {
int y;
char caracter, p;
FILE *fichero;
clrscr();
do  {
cout<<"MENU\n\n";
cout<<"1. CREAR\n";
cout<<"2. LEER\n";
cout<<"3. AGREGAR\n";
cout<<"4. FINALIZAR\n\n";
cout<<"ELEGIR OPCION:  "; cin>>y;
switch(y) {
case 1: {
fichero=fopen("d:archi-1.dat","w+t");
do  {
cout<<"\nIngrese un caracter: ";
caracter=getche();
fprintf(fichero,"%c",caracter);
cout<<"\nDesea continuar ingresando caracteres (S/N): ";
p=getche();
}
while(toupper(p)=='S');
fclose(fichero);
break;
}
case 2: {
fichero=fopen("d:archi-1.dat", "r+t");
while(!feof(fichero))  {
fscanf(fichero,"%c",&caracter);
cout<<caracter;             }
fclose(fichero);
break;
}
case 3:  {
fichero=fopen("d:archi-1.dat", "a+t");
do  {
cout<<"\nAgregar un caracter: ";
caracter=getche();
fprintf(fichero,"%c",caracter);
cout<<"\nDesea continuar agregando caracteres (S/N): ";
p=getche();
}
while(toupper(p)=='S');
fclose(fichero);
break;
}
default: goto bb;
}
while(toupper(p)=='S');
cout<<"\n\nDesea continuar con el programa (S/N): ";
p=getche();
clrscr();
}
while(toupper(p)=='S');
bb:
clrscr();
cout<<"\nFinal del programa";
getch();
}

Capitulo 11. Estructuras

Fuente:
Programación en C++ / Principios y Aplicaciones (resumen)
Carlos Romero Shollande

1.        Organización de datos

Los datos procesados por computadoras digitales se reducen a combinaciones de ceros y unos, debido a que estas son construidas con dispositivos electrónicos que pueden asumir dos estados estables, uno representa a 0 y el otro a 1 (ejemplo: 10011101).
Esto da origen al elemento de datos más pequeño en una computadora, al cual se le llama bit (“dígito binario”, un dígito que sólo puede asumir uno de dos valores).
En vez de ello, los programadores prefieren trabajar con los datos en formas tales como dígitos decimales (es decir, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), letras (es decir, de la “A” a la “Z” y de la “a” a la “z”) y símbolos especiales (es decir, $, @, %, &, *, # y otros). A los dígitos, letras y símbolos especiales se les conoce como caracteres.
Un computador sólo puede procesar unos y ceros, cada carácter del conjunto de caracteres de la computadora esta representado como una secuencia de unos y ceros, llamada byte. Los bytes se componen de 8 bits.
Los datos pueden organizarse jerárquicamente:

-       Los datos elementales vienen a ser los caracteres.
-       Un grupo de caracteres forma un campo.
-       Un agrupamiento de campos es un registro.
-       Una colección de registros es un archivo y
-       Un grupo de archivos es una base de datos.

Gráfica 1. Organización de datos.


2.        Definición de Estructuras

El C++ permite crear los siguientes tipos de datos:

-       Estructuras
-       Campos de bits
-       Uniones
-       Enumeraciones

Una estructura es una colección de campos de tipos de datos distintos (a diferencia de los arreglos que contienen solamente elementos del mismo tipo de datos), lógicamente relacionados y referenciados bajo el mismo nombre.
Las estructuras se utilizan comúnmente para definir registros de datos que se almacenarán en archivos.

2.1. Creación del Tipo Estructura

Sintaxis      : struct nombre_tipo_estructura   {
                        tipo1 campo1;
                        tipo2 campo2;
                        ….
                        tipon campon;
                        };
Propósito    : Crear una estructura bajo la denominación nombre_tipo_estructura y definir el formato de los campos. Los campos de una misma estructura deben tener nombres únicos, pero dos estructuras diferentes pueden contener campos con el mismo nombre sin que haya conflicto.
Ejemplo      : struct trabajador   {
                        int codigo;
                        char nombre[30];
                        char puesto[10];
                        float salario;    };

El C++ asigna automáticamente suficiente memoria para acomodar todas las variables que configuran una variable estructurada; en el ejemplo, cada campo tiene reservado en la memoria las siguientes capacidades.

Código  = 2 bytes
Nombre = 30 bytes
Puesto   = 10 bytes
Salario  =  4 bytes

2.2.  Declaración de Variables Tipo Estructura

Sintaxis     : struct nomb_tip_estruc nomb_var_estruc;
Propósito   : Declarar una variable de tipo estructura.
Ejemplo     : struct trabajador obrero

La declaración anterior puede escribirse también:

struct trabajador   {
int codigo;
char nombre[30];
char puesto[10];
float salario;    }   obrero;

En este caso se hacen dos operaciones al mismo tiempo, la primera define un tipo estructura llamado trabajador y la segunda declara a la variable obrero.

Gráfica 2. Organización de una estructura.


Gráfica 3. Almacenamiento de una variable tipo estructura:


Si solamente se requiere una variable estructurada, no se necesita incluir el nombre de la estructura, por ejemplo:

struct      {
int codigo
char nombre[35];
char dirección[18];
float compras;
}   cliente;

2.3.  Acceso a un Campo

Sintaxis     : nomb_variable_estructura.nomb_campo
Propósito   : Referenciar campos individuales de una estructura usando el operador de punto(.).
Ejemplo     : obrero.salario=85.37;
                        gets(cliente.direccion);
                        cout<<cliente.compras;

2.4.  Inicialización de Estructuras

Sintaxis      : struct nomb_tipo_estruc nomb_var_estruc = {lista variables};
Propósito    : Inicializar estructuras mediante una lista de inicialización separada por comas y encerrada entre llaves como con los arreglos.
Ejemplo      : struct ventas x = {“Rodríguez”, “Trujillo”};

Crea x de tipo struct ventas e inicializa el primer campo como “Rodríguez” y el segundo como “Trujillo”.
Si en la lista aparecen menos inicializadores que en la estructura, los miembros restantes quedaran inicializados como 0 (o NULL si el campo es un puntero).
Es un error de sintaxis comparar estructuras, debido a diferentes requisitos de alineación en los sistemas.

2.5.  Operaciones con Estructuras

Sobre las estructuras se pueden utilizar las mismas operaciones que se realizan sobre una variable individual, es decir lectura, escritura, asignación, comparaciones, etc.
Ejemplos:

cin>>obrero.salario;
obrero.pago=obrero.salario*obrero.horas*(1–obrero.afp);
cout<<obrero.pago;
if(obrero.pago > 1000) cout<<”Obrero calificado”;

2.6.  Estructuras Anidadas

Se denomina así cuando una estructura es un campo de otra estructura. Ejemplo:

struct tiempo      {
            long int hora, minuto, segundo;
            };
struct periodo     {
            char apellido[25];
            char nombre[12];
            struct tiempo reloj;
            };
struct periodo corredor;

Este ejemplo define a la variable corredor.

Gráfica 4. Estructuras anidadas.


Para tener acceso a un campo de una estructura anidada, se debe indicar el camino a seguir en orden jerárquico desde el nombre de la estructura raíz hasta el campo específico, por ejemplo:

corredor.reloj.minuto = 37;

2.7.  Arreglo de Estructuras

En un arreglo de estructuras o arreglo de registros los elementos son de tipo estructura.
Un ejemplo de declaración de un arreglo de estructuras es:

struct dato    {
            unsigned codigo;
            char nombre[10];
            int nota;    }  alumnos[40];

Para acceder a una estructura específica, se indexa el nombre de la estructura, por ejemplo:

cout<<alumnos[7].nota;

Esta sentencia imprime la nota de la estructura 8, ya que los arreglos de estructuras comienzan el índice en cero.

2.8.  Uso de Estructuras con Subprogramas

Hay dos formas para pasar la información en estructuras hacia los subprogramas:

a)        Pasar los campos individuales de una variable estructura a una función.

De manera predeterminada, los datos (a excepción de los campos individuales de arreglos) se pasan mediante una llamada por valor (función). Por ejemplo:

struct   {
int n;
char w[20];
}   grupo;

funcion1(grupo.n);                      // Pasa el valor entero de n
funcion2(grupo.w);                 // Pasa el valor de la cadena w
funcion3(grupo.w[5]);                 // Pasa el valor del quinto carácter de w

Los campos de estructuras también se pueden pasar mediante llamadas por referencia (procedimiento), pasando referencias o apuntadores. Para pasar una estructura mediante una llamada por referencia, se pasa la dirección de la variable de estructura o una referencia a la variable de estructura. Por ejemplo:

Procedimiento1(&grupo.n);         // Pasa la dirección del entero n
Procedimiento2(&grupo.w);        // Pasa la dirección de la cadena w
Procedimiento3(grupo.w[5];        // Pasa la dirección del quinto carácter w

El operador & siempre precede al nombre de la variable estructura y no al nombre del campo individual. Por otro lado, el quinto elemento de la cadena w ya significa una dirección de forma que el uso de & no se requiere en esa línea de código.
Los arreglos de estructuras (al igual que los demás arreglos) se pasan automáticamente mediante llamadas por referencia.

b)        Pasar la estructura completa a una función.

Cuando se usa una estructura como parámetro se debe tener en cuenta que el tipo de argumento debe coincidir con el tipo de parámetro. Por ejemplo:

struct dato  {
int x, y;
char w;   };

main()  {
struct dato numero
numero.x = 220;
procedimiento(numero);
}

procedimiento(struct dato valor)  {
cout<<valor.x;
}

En este caso se recomienda definir una estructura globalmente y después usar su nombre para declarar variables y parámetros de estructura que se necesite.

3.        Campos de Bits

Un campo de bit es un tipo especial de estructura que define la longitud en bits que tendrá cada elemento, esto permitirá el acceso a un único bit en un byte.

Sintaxis      : struct nombre_tipo_estructura   {
                        tipo1 nombre1 : longitud1;
                        tipo2 nombre2 : longitud2;
                        ……..
                        tipon nombren : longitudn;      };
Propósito    : Definir un campo de bit. Se debe declarar un campo de bit como int, unsigned o signed. Se debe declarar los campos de bit de longitud 1 como unsigned, por que un bit único no puede tener signo.

Este tipo es muy útil por las siguientes razones:

-       Si el almacenamiento es limitado, se pueden almacenar varias variables boolean (verdad o falso) en un byte. Es decir, permiten un mejor uso de la memoria, almacenando datos en la mínima cantidad de bits requeridos.
-       Ciertas interfaces de dispositivo transmiten información que se codifican en bits en un byte.
-       Ciertas rutinas de encriptación necesitan acceder a los bits de un byte.

Aunque se pueden realizar todas estas operaciones con operadores de modo bit, un campo de bit puede añadir más estructuración y eficacia al código, así mismo, puede hacer más transportable un programa.

Tabla 1. Operadores a nivel de bits:

 OPERADOR
 ACCION
&
|
Ù
-
>> 
<< 
Y Lógico (AND)
O lógico (OR)
Operador OR exclusivo
Operador complemento
Desplaza un lugar a la derecha
Desplaza un lugar a la izquierda

Los operadores AND a nivel de bits (&), OR inclusivo a nivel de bits (|) y OR exclusivo a nivel de bits (^) comparan sus dos operandos bit por bit.
El operador AND a nivel de bits establece a 1 cada bit del resultado si los dos bits correspondientes en ambos operandos son 1.
El operador OR inclusivo a nivel de bits establece a 1 cada bit del resultado si el bit correspondiente el alguno (o ambos) operandos es 1.
El operador OR exclusivo a nivel de bits establece a 1 cada bit del resultado si el bit correspondiente en solamente un operando es 1.
El operador de desplazamiento a la izquierda (<<) desplaza los bits de su operando izquierdo hacia la izquierda el número de bits especificado por su operando derecho.
El operador de desplazamiento a la derecha (>>) desplaza los bits de su operando izquierdo hacia la derecha el número de bits especificado por su operando derecho.
El operador de complementos a nivel de bits establece a 1 todos los bits 0 del operando del resultado, y establece a 0 todos los bits 1 en el resultado.

4.        Unión

Una unión es una posición de memoria que es compartida por varias variables similares, que pueden ser de diferentes tipos, denominados miembros.

4.1.  DECLARACION DE UNA UNION

Sintaxis      : union nombre_union     {
                        tipo1 miembro1;
                        tipo2 miembro2;
                        …….
                        tipok miembrok;
                        };
Propósito    : Definir a una unión.
Ejemplo      : union perfil    {
                        int x;
                        char carácter;     };

4.2. Declaración de Variables Tipo Unión

La definición de una unión tiene la misma forma que la definición de una estructura.
Por tanto, todo lo expuesto para las estructuras es aplicable a las uniones, excepto la forma de almacenamiento de sus miembros.

Sintaxis      : union nombre_union var1, var2, … ;
Propósito    : Declarar una o más variables de tipo unión.
Ejemplo      : union perfil proy;

4.3.  Acceso a un Miembro

Sintaxis      : variable.miembro
Propósito    : Referenciar a un determinado miembro de una unión.
Ejemplo      : proy.x = 25;

Para almacenar los miembros de una unión se requiere una zona igual a la que ocupa el miembro cuyo tipo ocupa el mayor número de bytes de la unión.
Todos los miembros son almacenados en el mismo espacio de memoria y comienza en la misma dirección. Las uniones ahorran almacenamiento.
El valor almacenado es sobreescrito cada vez que se asigna un valor al mismo miembro o a un miembro diferente, debido a que únicamente un miembro y, por lo tanto, únicamente un tipo de dato puede ser referenciado en un momento dado. Son errores de sintaxis:

-       Referenciar con el tipo equivocado, datos en una unión almacenados en un tipo distinto.
-       Comparar uniones, por las razones expresadas en las estructuras.
-       Inicializar una unión en una declaración con un valor cuyo tipo sea distinto al del primer miembro de la unión.

5.        Enumeración

Una enumeración es un conjunto de constantes enteras con nombre, y especifica todos los valores legales que puede tener una variable. 

5.1.  Creación de una Enumeración

Sintaxis      : enum nombre_tipo_enum   {
                     lista_enumeraciones    } lista_variables;
Propósito    : Definir enumeraciones. Aquí la lista de enumeraciones es una lista de nombres separados por comas, representando los valores que una variable de tipo enumeración puede tener.
Ejemplo      : enum dinero   { decimo, quinto, medio, sol };

5.2.  Declaración de Variables Tipo Enumeración

Sintaxis      : enum nombre_tipo_enum variables;
Propósito    : Declarar una o más variables de tipo enum.
Ejemplo      : enum dinero moneda;

5.3.  Acceso a una Variable

El acceso a una variable tipo unión se realiza con la siguiente sintaxis:

Sintaxis      : variable
Propósito    : Acceder a una variables.
Ejemplo      : moneda = quinto;
                    If(moneda==medio) cout<<”Es medio sol \n”);

6.        Tamaño de Tipo o Variable

Sintaxis       : sizeof(tipo o variable);
Propósito    : Calcular el tamaño de cualquier tipo o variable.
Ejemplo      :union datos    {
                        char caracter;
                        int entero;
                        float flotante;   }   informe;

Tabla 2. Tamaños para los tipos de datos en el C++.

Tipo
Tamaño (bytes)
char
int
long int
float
doublé
1
2
4
4
8

El sizeof(informe) será 4, ya que toma el tamaño más grande que puede guardar porque la unión debe ser tan grande como el elemento más grande.


Ejemplo: 
Crear un arreglo de estructuras para un zoológico, que permita realizar el control de los animales ahí instalados, y que contenga los campos: clase, orden, genero, raza, número de ejemplares, fecha de nacimiento y edad en días. Ordene el arreglo en forma descendente de acuerdo a la edad, ingresando previamente la fecha actual.

Codificación:
#include <iostream.h> // Control de zoológico

#include <conio.h>
#include <stdio.h>
#include <string.h>
struct fecha     {
int dia, mes, ano;     };
struct animal    {
char clase[15],orden[15],genero[15],raza[15];
int numero,edad;
struct fecha nacim;     };
main()     {
int i, j, n, r, t, q, k;
struct animal animales[10], aux;
struct fecha actual;
long int periodo(int, int, int);
clrscr();
cout<<"Fecha actual \n\n";
cout<<"Día : "; cin>>actual.dia;
cout<<"Mes : "; cin>>actual.mes;
cout<<"Año : "; cin>>actual.ano;
cout<<"\nIngrese el número de animales : "; cin>>n;
for(i=1;i<=n;i++)     {
clrscr();
cout<<"Animal N° "<<i<<endl<<endl;
cout<<"Clase  : "; gets(animales[i].clase);
cout<<"Orden  : "; gets(animales[i].orden);
cout<<"Genero : "; gets(animales[i].genero);
cout<<"Raza   : "; gets(animales[i].raza);
cout<<"Número : "; cin>>animales[i].numero;
cout<<"\nFecha de nacimiento\n\n";
cout<<"Día : "; cin>>animales[i].nacim.dia;
cout<<"Mes : "; cin>>animales[i].nacim.mes;
cout<<"Año : "; cin>>animales[i].nacim.ano;
r = periodo(animales[i].nacim.ano,animales[i].nacim.mes,animales[i].nacim.dia);
t = periodo(actual.ano,actual.mes,actual.dia);
if(animales[i].nacim.ano == actual.ano) animales[i].edad = t - r;
else     {
if(animales[i].nacim.ano+1==actual.ano)    {
if(animales[i].nacim.ano%4==0&&(animales[i].nacim.ano%100!=0 ||
           animales[i].nacim.ano%400==0))   animales[i].edad = t + (366 - r);
else animales[i].edad = t + (365 - r);     }
else     {
q = 0;
for(k=animales[i].nacim.ano+1;k<=actual.ano-1;k++)    {
if(k%4==0 && (k%100!=0 || k%400==0))   q = q + 366;
else q = q + 365;     }
if(animales[i].nacim.ano%4==0 && (animales[i].nacim.ano%100!=0 ||
       animales[i].nacim.ano%400==0))   animales[i].edad = t + q + (366 - r);
else animales[i].edad = t + q + (365 - r);      }
}
}
for(i=1;i<=n-1;i++)     {
for(j=i+1;j<=n;j++)     {
if(animales[i].edad<animales[j].edad)    {
strcpy(aux.clase,animales[i].clase);
strcpy(animales[i].clase,animales[j].clase);
strcpy(animales[j].clase,aux.clase);
strcpy(aux.orden,animales[i].orden);
strcpy(animales[i].orden,animales[j].orden);
strcpy(animales[j].orden,aux.orden);
strcpy(aux.genero,animales[i].genero);
strcpy(animales[i].genero,animales[j].genero);
strcpy(animales[j].genero,aux.genero);
strcpy(aux.raza,animales[i].raza);
strcpy(animales[i].raza,animales[j].raza);
strcpy(animales[j].raza,aux.raza);
aux.numero=animales[i].numero;
animales[i].numero=animales[j].numero;
animales[j].numero=aux.numero;
aux.nacim.dia=animales[i].nacim.dia;
animales[i].nacim.dia=animales[j].nacim.dia;
animales[j].nacim.dia=aux.nacim.dia;
aux.nacim.mes=animales[i].nacim.mes;
animales[i].nacim.mes=animales[j].nacim.mes;
animales[j].nacim.mes=aux.nacim.mes;
aux.nacim.ano=animales[i].nacim.ano;
animales[i].nacim.ano=animales[j].nacim.ano;
animales[j].nacim.ano=aux.nacim.ano;
aux.edad=animales[i].edad;
animales[i].edad=animales[j].edad;
animales[j].edad=aux.edad;     }
}
}
for(i=1;i<=n;i++)     {
clrscr();
cout<<"Animal N° "<<i<<endl<<endl;
cout<<"Clase  : "<<animales[i].clase<<endl;
cout<<"Orden  : "<<animales[i].orden<<endl;
cout<<"Genero : "<<animales[i].genero<<endl;
cout<<"Raza   : "<<animales[i].raza<<endl;
cout<<"Número : "<<animales[i].numero<<endl<<endl;
cout<<"Fecha de nacimiento \n\n";
cout<<"Día : "<<animales[i].nacim.dia<<endl;
cout<<"Mes : "<<animales[i].nacim.mes<<endl;
cout<<"Año : "<<animales[i].nacim.ano<<endl<<endl;
cout<<"Edad (en días) : "<<animales[i].edad<<endl;  getch();     }
getch();
}
long int periodo(int a, int m, int d)     {
long int s;
switch(m)     {
case 1: {   s=d;  break;    }
case 2: {   s=31+d;  break;    }
case 3: {   if(a%4==0 && (a%100!=0 || a%400==0)) s=60+d;
                 else s=59+d;  break;    }
case 4: {   if(a%4==0 && (a%100!=0 || a%400==0)) s=91+d;
                 else s=90+d;  break;    }
case 5: {   if(a%4==0 && (a%100!=0 || a%400==0)) s=121+d;
                 else s=120+d;  break;    }
case 6: {   if(a%4==0 && (a%100!=0 || a%400==0)) s=152+d;
                 else s=151+d;  break;    }
case 7: {   if(a%4==0 && (a%100!=0 || a%400==0)) s=182+d;
                 else s=181+d; break;    }
case 8: {   if(a%4==0 && (a%100!=0 || a%400==0)) s=213+d;
                 else s=212+d; break;    }
case 9: {    if(a%4==0 && (a%100!=0 || a%400==0)) s=244+d;
                  else s=243+d; break;    }
case 10: {   if(a%4==0 && (a%100!=0 || a%400==0)) s=274+d;
                   else s=273+d; break;    }
case 11: {   if(a%4==0 && (a%100!=0 || a%400==0)) s=305+d;
                   else s=304+d; break;    }
case 12: {   if(a%4==0 && (a%100!=0 || a%400==0)) s=335+d;
                   else s=334+d; break;    }
}
return(s);
}