lunes, 10 de junio de 2013

Estructuras (Unidad 8)

INTRODUCCIÓN


Hasta el momento hemos visto cómo se pueden guardar datos en variables, arrays o cadenas de caracteres. En una variable se guarda un tipo de dato y en los arrays, por ejemplo, se guardan muchos datos del mismo tipo. De este tipo a menudo superamos los problemas que se nos puedan presentar a la hora de procesar distintos tipos de datos. Pero hay ocasionas en las cuales, utilizando el mismo nombre, deseamos almacenar información que contenga tipos de datos diferentes. En estos casos las formas de guardar información serian variables, arrays, cadenas, no son suficientes.
Para poder hacer frente a este problema el lenguaje C nos ofrece un tipo de dato especial: la estructura.
Una estructura es un tipo de dato especial utilizada para guardar información que contiene diferentes tipos de datos. La utilización de estructuras en el lenguaje C requiere el uso de la palabra clave struct.














ESTRUCTURAS


Una estructura es un tipo de dato compuesto que permite almacenar un conjunto de datos de diferente tipo. Los datos que contiene una estructura pueden ser de tipo simple (caracteres, números enteros o de coma flotante etc.) o a su vez de tipo compuesto (vectores, estructuras, listas, etc.).
 A cada uno de los datos o elementos almacenados dentro de una estructura se les denomina miembros de esa estructura y éstos pertenecerán a un tipo de dato determinado. 
·         struct: es una palabra reservada de C que indica que los elementos que vienen agrupados a continuación entre llaves componen una estructura.
·         nombre_estructura: identifica el tipo de dato que se describe y del cual se podrán declarar variables. Se especifica entre corchetes para indicar su opcionalidad.
·         miembro1, miembro2,...: Son los elementos que componen la estructura de datos, deben ser precedidos por el tipo_dato al cual pertenecen.
 Recordemos que una estructura define un tipo de dato, no una variable, lo que significa que no existe reserva de memoria cuando el compilador está analizando la estructura. Posteriormente habrá que declarar variables del tipo definido por la estructura para poder almacenar y manipular datos.
 La declaración de variables de un determinado tipo de estructura de datos se puede realizar de dos modos:
1.    Primera: Incluir en la propia definición de la estructura aquellas variables que se van a emplear en el programa. Esta declaración de variables  implica que el ámbito en el que éstas son reconocidas será el mismo que el de la declaración del tipo de dato estructura. En estos casos, y si no se van a declarar más variables de este tipo de dato en otros lugares del programa, el nombre_estructura es innecesario, por ello viene entre corchetes.
Ejemplo: estructura de una tarjeta bancaria, utilizando esta primera forma:



                        struct  {
          long_int  num_tarjeta;
  char tipo_cuenta;
   char nombre [80];
float  saldo;
} cliente1, cliente2;
2.    Segunda: Definir el tipo de dato estructura con un nombre determinado y declarar posteriormente las variables de ese tipo de dato. Para ello la estructura se identificará con un nombre de forma obligatoria.
Ejemplo: estructura de una tarjeta bancaria, utilizando la segunda forma:
                  struct  {
         long_int  num_tarjeta;
  char tipo_cuenta;
   char nombre [80];
float  saldo;
                  }
struct  tarjetas cli1, cli2;}
Se ha señalado que, al igual que las matrices, las estructuras pueden iniciarse, incluso en el mismo punto de su declaración, con una lista de iniciadores entre corchetes  separados por comas, uno para cada miembro de la estructura. Por ejemplo, la sentencia:
struct Cliente {int i; char str[20]; double d;} s = {33, "Pepe Lopez", 3.14 };
declara una estructura Cliente compuesta por un entero; un array de 20 caracteres, y un doble.También inicia una estructura s como perteneciente al tipo Cliente con valores concretos en cada campo.


En estructuras o uniones con duración automática, el iniciador debe ser alguno de los siguientes:
Una lista de inicializadores constantes (también se permiten expresiones con sizeof) Ejemplo:
struct punto {int x; int y;} p1 = {1, 2};
Una sola expresión con una estructura de tipo compatible.  En este caso, el valor inicial del objeto es el de la expresión.  Ejemplo:
struct punto p2 = p1;
en este caso, (suponiendo los valores anteriores) sería: p2.x == 1 y p2.y == 2
Los miembros complejos de listas de iniciadores. Por ejemplo matrices, pueden inicializarse con expresiones adecuadas incluidas en bloques de corchetes anidados. Por ejemplo:
struct Pesadas {
   int tiket;
   int pesos[5];
} s = { 150, {10, 45, 0, 78, 20}};
Si la lista de inicializadores contenidos entre los corchetes { } es menor que los miembros de la estructura, el resto de los miembros es inicializado implícitamente siguiendo las mismas reglas que los objetos con duración estática.
Las estructuras, al igual que las matrices, almacenan sus miembros de forma contigua. Por la misma razón, se pueden crear matrices de estructuras e incluso estructuras de matrices (sus miembros son matrices). Conceptualmente, estas últimas no se diferencian gran cosa de las matrices de matrices (a no ser en la notación de acceso a sus miembros). También se puede calcular su tamaño en bytes con la expresión sizeof, aunque a este respecto debe tenerse en cuenta algunas posibles discrepancias respecto a los valores "teóricos", debidas a las alineaciones internas realizadas por el compilador, que se comentan .
Se ha dicho que los miembros de estructuras pueden ser de cualquier tipo, incluso otras estructuras. Sin embargo, existen varias limitaciones, sobre todo las que se opongan al principio anterior. Por ejemplo, la declaración que sigue conduce a un error:
struct str {int x; char * psch = "Hola";};  // Error!
La razón ya expuesta, que en la definición de un tipo no pueden realizarse asignaciones, tiene su base en el comentario anterior. En efecto: en "Constantes de cadena" , se indicó que ante la expresión char* psch = "Hola";, el compilador debe crear una matriz de caracteres "Hola\0"; almacenarlo en algún sitio, y asignar a una variable tipo puntero a carácter psch (dirección del primer elemento de la matriz). Esto conduciría a que no se podría garantizar que los miembros estuvieran íntegramente en la estructura.
La expresión anterior sería admisible con una modificación del tipo:
char* psch = "Hola";
struct str {int x; char* psch;};
pero ¡Atención!, con este esquema, el miembro psch de str no tiene nada que ver con el puntero a carácter psch de la primera línea. Espacio de nombres de estructuras ). str.psch sigue siendo un puntero a carácter genérico.
Una alternativa es efectuar la asignación en una variable concreta:
char * psch = "Hola";
struct str {int x; char * psch;};   // define el tipo de estructura str
struct str strA;                    // declara strA estructura tipo str
strA.psch = psch;                nbsp;  // asigna valor
Podemos efectuar una comprobación de la asignación:
printf("%s\n", strA.psch);   // -> Hola
Hay que hacer hincapié en que el miembro strA.psch (puntero a carácter) apunta ahora a la dirección de una zona de memoria donde está almacenada la cadena "Hola\0", dirección que coincide con la indicada por el puntero [2] psch. Si cambiamos el valor de psch, el del miembro no se verá afectado, lo que podemos comprobar añadiendo estas sentencias:
char * psch = "Lola";
printf("%s\n", strA.psch);    // -> Hola
El resultado evidencia que la cadena "Hola" sigue existiendo en alguna posición de memoria y es accedida mediante strA.psch.
Se habría obtenido un resultado distinto si en vez de las dos sentencias anteriores modificamos directamente la posición de memoria donde está alojada la cadena "Hola". Lo hacemos mediante:
*psch = 'L';  *(psch+3) = 'o';
printf("%s\n", strA.psch);    // -> Lolo
Observe que el paréntesis *(psch+3) es necesario, ya que *psch+3 es interpretado como (*psch)+3 = 'K'. Con lo que el intento de asignación 'K' = 'o' produce un error del compilador: No es un Lvalue. Ya vimos que el miembro a la izquierda de una asignación debe ser un Lvalue.
Una variante de la declaración anterior, señalada es la siguiente:
struct str {int x; char * psch;}; // define el tipo de estructura str
struct str strA = {1, "Hola"};    // declara e inicia strA
printf("%s\n", strA.psch);        // salida:  Hola
La situación final es análoga a la allí expuesta; el resultado es una cadena "Hola\0" almacenada en algún sitio y un puntero a carácter strA.psch apuntando a ella, como miembro de la estructura strA. Podemos comprobarlo modificando el contenido de las posiciones de memoria que alojan la cadena mediante:
*strA.psch = 'L';  *(strA.psch+3) = 'o';
printf("%s\n", strA.psch);    // -> Lolo
Espacio de almacenamiento:
A continuación se expone un ejemplo de estructura con una composición de miembros variada. En el comentario de cada línea se indica el espacio de almacenamiento necesario en bytes [3] según las especificaciones declaradas para el compilador utilizado. Representación interna y rango):
struct general {
  int x;              // L2.  4 bytes
  char ch;            // L3.  1 byte
  double db;          // L4.  8 bytes
  char * sg;          // L5.  4 bytes
  char nom[30];       // L6. 30 bytes
  char * dir[];       // L7.  4 bytes
} str;                // L8.
printf("M1:%3.0d\n", sizeof(str.x));      // L9.  -> M1:  4
printf("M2:%3.0d\n", sizeof(str.ch));     // L10. -> M2:  1
printf("M3:%3.0d\n", sizeof(str.db));     // L11. -> M3:  8
printf("M4:%3.0d\n", sizeof(str.sg));     // L12. -> M4:  4
printf("M5:%3.0d\n", sizeof(str.nom));    // L13. -> M5: 30
printf("M6:%3.0d\n", sizeof(str.dir[0])); // L14. -> M6:  4
printf("Total:%3.0d\n", sizeof(str));     // L15. -> Total: 56
Las líneas 2, 3 y 4 no requieren comentario; L5 define un puntero a carácter, ocupará 4 bytes en un compilador de 32 bits . La 6 define una matriz de 30 caracteres, por lo que se reserva espacio para otros tantos. Finalmente, L7 define un puntero a matriz de caracteres; este miembro ocupa un espacio de 4 bytes en la estructura, aunque la matriz a la que apunta (exterior a la propia estructura) sea de tamaño indefinido.
Las líneas 9 a 14 son simplemente una confirmación de que las suposiciones teóricas son correctas, como efectivamente se comprueba. La línea 15 es una comprobación del tamaño total de la estructura, con el sorprendente resultado que se indica (la compilación se ha realizado con la opción de alineación por defecto).
La razón de la discrepancia hay que buscarla en el hecho de que, por razones de eficiencia (por ejemplo de velocidad de acceso a memoria), el compilador, que asigna espacio de memoria en múltiplos de un cierto número determinado de bits (8 en este caso), intenta no utilizar fracciones de palabra, con lo que realiza determinados redondeos o alineaciones internas. En este caso, la suma teórica: 51 x 8 = 408 bits habría supuesto el uso de 12.75 palabras de 32 bits, por lo que el compilador redondea a 14 palabras; 56 x 8 = 448 bits, 14 palabras. Hay que recordar que, aunque existan 5 bytes perdidos (no utilizados), el almacenamiento ocupado por la estructura str sigue siendo contiguo, y el acceso a sus miembros totalmente transparente para el programador. Del mismo modo, si más tarde se declara una matriz de estructuras de tipo general y se utilizan punteros, el compilador tiene automáticamente en cuenta el verdadero tamaño ocupado por cada elemento, a fin de efectuar correctamente los desplazamientos pertinentes.
Las variables y los arrays pueden pasarse como argumentos a las funciones y todo ello sin problema alguno. Por lo tanto, ¿puede ocurrir lo mismo con las estructuras? La respuesta es afirmativa.
Para pasar datos tipo struct a funciones en C++ es posible hacerlo de dos diferentes maneras:
1) Invocando la función con el nombre del tipo creado como struct. La función invocada recibe la dirección de la estructura y usa un alias para referirse a ella.
2) Invocando la función con el nombre del tipo creado como struct. La función invocada recibe como parámetro un dato del tipo creado como struct.
Pasar estructuras a funciones es muy parecido a pasar un arreglo.
En el siguiente ejemplo se usan los dos casos mencionados.
#include <iostream>
 using namespace::std;

 struct Datos
 {
 // Estos datos no se pueden
 // inicializar
 int anio;
 int mes;
 int dia;
 };

 // Prototipos de funcion
 void Recibe( Datos &s);
 void Imprime1( Datos &t);
 void Imprime2( Datos Nacimiento);

 /////////////////////////////////////////////////////////////
 // MAIN
 /////////////////////////////////////////////////////////////

 int main()
 {      // Abre main
 // Declaracion de Elisa como tipo Datos
 Datos Elisa;

 // Se reciben los datos de Elisa mediante la funcion Recibe
 Recibe(Elisa);

 // Se imprimen los datos de Elisa desde la funcion Imprime1
 cout <<"\nLa fecha de nacimiento de Elisa desde Imprime1: "<<endl;
 Imprime1(Elisa);
 // Se imprimen los datos de Elisa desde la funcion Imprime2
 cout <<"\nLa fecha de nacimiento de Elisa desde Imprime2:" <<endl;
 Imprime2(Elisa);
 // Se imprimen los datos de Elisa desde main
 cout <<"\nLa fecha de nacimiento de Elisa desde main." <<endl;
 cout <<Elisa.dia<<"/" <<Elisa.mes<< "/" << Elisa.anio << endl << endl;

 return 0;
 }      // Cierra main

 /////////////////////////////////////////////////////////////////
 // RECIBE
 ////////////////////////////////////////////////////////////////

 void Recibe( Datos &s)
 {      // Abre funcion Recibe
 cout << "\nIntroduzca el anio de nacimiento: " <<endl;
 cin >> s.anio;
 cout << "\nIntroduzca el mes de nacimiento: " <<endl;
 cin >> s.mes;
 cout <<"\nIntroduzca el dia de nacimiento: " <<endl;
 cin >> s.dia;
 }      // Cierra funcion Recibe

 ////////////////////////////////////////////////////////////////
 // IMPRIME1
 ////////////////////////////////////////////////////////////////

 void Imprime1( Datos &t)

 {    // Abre Imprime
 cout <<t.dia <<"/"<<t.mes<<"/"<<t.anio<<endl;
 return;
 }    // Cierra Imprime

 ////////////////////////////////////////////////////////////////
 // IMPRIME2
 ////////////////////////////////////////////////////////////////

 void Imprime2( Datos Nacimiento)
 { // Abre
 cout << Nacimiento.dia <<"/" <<Nacimiento.mes<<"/"<< Nacimiento.anio << endl;
 return;
 }


CONCLUSIÓN


Una estructura contiene varios datos. La forma de definir una estructura es haciendo uso de la palabra clave struct.

¿Para qué nos  sirve? Las estructuras nos ayudan a agrupar datos de una manera más ordenada y fácil para nosotros. Por ejemplo, supongamos que queremos guardar el nombre, la edad y el número de teléfono de un contacto en un programa en C/C++.

No hay comentarios:

Publicar un comentario