Anda di halaman 1dari 36

8.

2 Estructuras annimas anidadas Borland C++ permite estructuras annimas y que no sean usadas para declarar un objeto o cualquier otro tipo. Tienen la forma que se indica: struct { lista-de-miembros };

Las estructuras annimas pueden ser anidadas, es decir, declaradas dentro de otra estructura, unin o clase. En estos casos, la estructura externa debe tener nombre. Por ejemplo: struct my_struct { int x; struct { // estructura annima anidada dentro de my_struct int i; }; inline int func1(int y); } S;

Para compatibilidad con el compilador Visual C++ de Microsoft, el compilador Borland C++ permite declarar una estructura con nombre que no declara ningn objeto. Estas estructuras hurfanas deben ser anidadas. Por ejemplo: struct Direccion { char calle[25]; int numero; }; struct Persona { char nombre[35] struct Direccion; } candidato; candidato.numero = 10;

// estructura hurfana // una instancia de Persona

4.5.2 Iniciacin de estructuras


1 Sinopsis Se ha sealado que, al igual que las matrices, las estructuras pueden iniciarse, incluso en el mismo punto de su declaracin, con una lista de iniciadores entre corchetes { } separados por comas [1], 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.Tambin inicia una estructura s como perteneciente al tipo Cliente con valores concretos en cada campo.

2 En estructuras o uniones con duracin automtica ( de los siguientes:

4.1.8a), el iniciador debe ser alguno

Una lista de inicializadores constantes (tambin se permiten expresiones con sizeof) Ejemplo: struct punto {int x; int y;} p1 = {1, 2};

Una sola expresin con una estructura de tipo compatible. En este caso, el valor inicial del objeto es el de la expresin. Ejemplo: struct punto p2 = p1; en este caso, (suponiendo los valores anteriores) sera: p2.x == 1 y p2.y == 2

3 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 implcitamente siguiendo las mismas reglas que los objetos con duracin esttica.

4 Comentarios Las estructuras, al igual que las matrices, almacenan sus miembros de forma contigua, razn por la cual, como veremos ms adelante, se les pueden aplicar punteros y una cierta aritmtica. Por la misma razn, 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 notacin de acceso a sus miembros). Tambin se puede calcular su tamao en bytes con la expresin sizeof, aunque a este respecto debe tenerse en cuenta algunas posibles discrepancias respecto a los valores "tericos", debidas a las alineaciones internas realizadas por el compilador, que se comentan .

4.1 Se ha dicho que los miembros de estructuras pueden ser de cualquier tipo, incluso otras estructuras ( 4.5.6 Matrices de estructuras). Sin embargo, existen varias limitaciones, sobre todo las que se opongan al principio anterior. Por ejemplo, la declaracin que sigue conduce a un error: struct str {int x; char * psch = "Hola";}; // Error!

La razn ya expuesta ( 4.5.1), que en la definicin de un tipo no pueden realizarse asignaciones, tiene su base en el comentario anterior. En efecto: en "Constantes de cadena" ( 3.2.3f), se indic que ante la expresin char* psch = "Hola";, el compilador debe crear una matriz de caracteres "Hola\0"; almacenarlo en algn sitio, y asignar a una variable tipo puntero a carcter psch (direccin del primer elemento de la matriz). Esto conducira a que no se podra garantizar que los miembros estuvieran ntegramente en la estructura. La expresin anterior sera admisible con una modificacin del tipo: char* psch = "Hola"; struct str {int x; char* psch;};

pero Atencin!, con este esquema, el miembro psch de str no tiene nada que ver con el puntero a carcter psch de la primera lnea ( 4.5.1d Espacio de nombres de estructuras ). str.psch sigue siendo un puntero a carcter genrico.

4.2 Una alternativa es efectuar la asignacin en una variable concreta (en POO diramos en una instancia de la clase str): 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 comprobacin de la asignacin: printf("%s\n", strA.psch); // -> Hola

Hay que hacer hincapi en que el miembro strA.psch (puntero a carcter) apunta ahora a la direccin de una zona de memoria donde est almacenada la cadena "Hola\0", direccin 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 aadiendo estas sentencias: char * psch = "Lola"; printf("%s\n", strA.psch);

// -> Hola

El resultado evidencia que la cadena "Hola" sigue existiendo en alguna posicin de memoria y es accedida mediante strA.psch. Se habra obtenido un resultado distinto si en vez de las dos sentencias anteriores modificamos directamente la posicin 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 parntesis *(psch+3) es necesario, ya que *psch+3 es interpretado como (*psch)+3 = 'K'. Con lo que el intento de asignacin 'K' = 'o' produce un error del

compilador: No es un Lvalue. Ya vimos que el miembro a la izquierda de una asignacin debe ser un Lvalue ( 2.1.5).

4.3 Una variante de la declaracin anterior, sealada (5.1

) 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 situacin final es anloga a la all expuesta; el resultado es una cadena "Hola\0" almacenada en algn sitio y un puntero a carcterstrA.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

5 Espacio de almacenamiento A continuacin se expone un ejemplo de estructura con una composicin de miembros variada. En el comentario de cada lnea se indica el espacio de almacenamiento necesario en bytes [3] segn las especificaciones declaradas para el compilador utilizado ( 2.2.4 Representacin 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)); printf("M2:%3.0d\n", sizeof(str.ch)); printf("M3:%3.0d\n", sizeof(str.db)); printf("M4:%3.0d\n", sizeof(str.sg)); printf("M5:%3.0d\n", sizeof(str.nom)); printf("M6:%3.0d\n", sizeof(str.dir[0])); printf("Total:%3.0d\n", sizeof(str));

// // // // // // //

L9. L10. L11. L12. L13. L14. L15.

-> -> -> -> -> -> ->

M1: 4 M2: 1 M3: 8 M4: 4 M5: 30 M6: 4 Total: 56

Las lneas 2, 3 y 4 no requieren comentario; L5 define un puntero a carcter, ocupar 4 bytes en un compilador de 32 bits ( 4.2 Punteros). L6 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 tamao indefinido. Las lneas 9 a 14 son simplemente una confirmacin de que las suposiciones tericas son correctas, como efectivamente se comprueba. La lnea 15 es una comprobacin del tamao total

de la estructura, con el sorprendente resultado que se indica (la compilacin se ha realizado con la opcin de alineacin por defecto -a4 4.5.9a). La razn 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 mltiplos de de un cierto nmero determinado de bits (8 en este caso), intenta no utilizar fracciones de palabra, con lo que realiza determinados redondeos o alineaciones internas ( 4.5.9a). En este caso, la suma terica: 51 x 8 = 408 bits habra 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 ms tarde se declara una matriz de estructuras de tipo general y se utilizan punteros, el compilador tiene automticamente en cuenta el verdadero tamao ocupado por cada elemento, a fin de efectuar correctamente los desplazamientos pertinentes (Aritmtica de punteros 4.2.2). Nota: tanto el compilador Borlanc C++ 5.5 como el MS Visual C++ permiten establecer alineaciones en mltiplos de 1, 2, 4, 8 y 16 octetos.

Como puede verse, en trminos de tamao de la estructura, las estrategias representadas por la lneas 6 y 7 varan grandemente. La adopcin de una u otra, depende del contexto de cada caso. Tngase en cuenta, por ejemplo, que si se guardase en disco el contenido de la estructura, una recuperacin posterior garantizara el contenido original para el elemento str.nom. Sin embargo, los punteros str.sg y str.dir quizs podran apuntar a zonas de memoria errneas.

4.5.3 Operaciones permitidas con estructuras


1 Sinopsis La nicas operaciones con estructuras que se permiten en C++ son las siguientes: Asignacin (asignarlas o ser asignadas) Obtener su direccin (referenciarlas mediante punteros) Ser utilizadas como argumento de funciones Ser utilizadas como valor de retorno de funciones

2 Asignarlas o recibir asignacin como entidad nica En estos casos, origen y destino de la asignacin tiene que ser del mismo tipo, aunque si no lo son, todava es posible la asignacin miembro a miembro. Ejemplo: struct A { int i,j; } a, a1; struct B { int i,j; } b;

a = a1; // OK: mismo tipo, se realiza asignacin miembro a miembro a = b; // ILEGAL: tipos diferentes a.i = b.i; a.j = b.j; // Ok: asignacin miembro a miembro

Observe que la asignacin anterior se refiere a instancias de estructuras, no a las clases en s mismas (recuerde que las estructuras son un tipo especial de clases 4.5). Es decir: struct StrA { ... }; stuct StrB = StrA;

// Error!!

La asignacin anterior no est permitida. Sin embargo, puesto que se trata de clases puede construirse una estructura que contenga exactamente los mismos elementos que otra anterior derivndola por herencia: struct StrB : StrA {}; Sin embargo, recordemos que en este caso ambas estructuras son tipos distintos, por lo que ms tarde no pueden efectuarse asignaciones del tipo: StrA Sa; StrB Sb = Sa;

// Error. Tipos distintos!!

3 Obtener su direccin Ver al respecto "Operadores de puntero" ( struct StrA sa; struct StrA* sptr; sptr = &sa; Nota: en el captulo correspondiente ( 4.5.7) se detallan todos los aspectos relativos a los punteros a estructuras. Recordar aqu que al ser un tipo de clases, les son aplicables los mismos principios y reglas que a aquellas. En concreto todo lo sealado respecto a punteros. Ver a este respecto: Punteros a clases ( 4.2.1f) y Punteros a miembros ( 4.2.1g). 4.9.11b). Ejemplo:

4 Pasar como argumento a funciones El paso puede realizarse de varias formas: void f1(StrA sa); void f2(StrA* sptr); void f3(StrA& sref); // directamente // mediante un puntero a estructura // mediante una referencia 4.4.5).

Ms detalles al respecto en: argumentos de funciones (

5 Ser devueltas por funciones

Una funcin puede devolver una estructura, un puntero a estructura o incluso una referencia: StrX f1(void); StrX* f2(void); // f1() devuelve una estructura // f2() devuelve puntero a estructura 4.4.7).

Ms detalles al respecto en: Valor devuelto (

6 Observe que en principio no estn permitidos los operadores de relacin con las estructuras. Es decir, estos objetos no pueden ser comparados ( 4.9.12): StrA sa; StrB sb; sa =< sb;

// Ilegal !!

Nota: la razn es que el compilador C++ no define tales operadores por defecto para las estructuras. Sin embargo, es posible sobrecargarlos y utilizarlos con ellas, con lo que la expresin anterior resultara perfectamente legal. Ver al respecto el captulo dedicado a la sobrecarga de operadores ( 4.9.18).

4.5.4 Acceso a miembros de estructuras


1 Sinopsis La gramtica C++ dispone de dos operadores para referenciar a los miembros de estructuras y uniones. So conocidos como operadores de seleccin de miembro: 1.1 . Selector directo ( 4.9.16)

Una expresin del tipo sT.obj representa el miembro obj de sT, que suponemos es la instancia de una estructura. La expresin es un Lvalue siempre que sT no lo sea, y obj no sea una matriz. 1.2 -> Selector indirecto ( 4.9.16)

Una expresin del tipo stPt -> obj representa el miembro obj de sT siempre que stPt sea un puntero a dicha instancia. La expresin es un Lvalue siempre que obj no sea una matriz. Esta expresin es equivalente, y preferible, a (*stPt).obj. El uso de uno u otro es indiferente, depende de que se tenga un identificador de la estructura, en cuyo caso puede usarse el selector directo (expresin sT.obj ), o un puntero, en cuyo caso puede usarse el indirecto (expresin: stPt->obj )

2 Ejemplo struct Str { int i; char a; } s, *sptr = &s; // declara estructura Str

// declara s tipo Str, y sptr puntero a s

... s.i = 3; sptr -> a = 'B';

// asigna 3 al miembro i de s // asigna 'B' al miembro a.s

3 Acceso a miembros de estructuras anidadas Si existen estructuras anidadas, es decir, si la estructura B contiene un campo a de tipo estructura A, los sub-miembros de a pueden ser accedidos mediante una doble aplicacin del operador de seleccin. Por ejemplo: struct A { int j; double x; }; struct B { int i; double d; struct A a; } s, *sptr = &s; ... s.i = 3; // asigna 3 al miembro i s.a.j = 2; // asigna 2 al miembro j sptr->d = 1.2; // asigna 1.2 al miembro sptr->a.x = 3.1; // asigna 3.1 al miembro

de B de A d de B x de A

4 Ejemplo #include <stdio.h> #include <math.h> #include <iostream> using namespace std; struct punto {int x; int y;}; double isqrt(int valor); // definicin de estructura // prototipo de funcin isqrt

int main(void) { // ============== punto p1 = {2,3}, p2 = {4,5}, *ptr1 = &p1; printf ("%3d,%3d\n", p1.x, p1.y); printf ("%3d,%3d\n", ptr1->x, ptr1->y); double dist; dist = isqrt(p1.x * p1.x + p1.y * p1.y ); printf("Distancia: %5.5f\n", dist); } double isqrt(int num) { // definicin de funcin isqrt return pow((double)num, (double).5); } Salida:

2, 3 2, 3 Distancia: 3.60555 Nota: el ANSI C dispone en la Librera Estndar de la funcin sqrt() que calcula directamente la raz cuadrada, pero esta funcin no es estndar en ANSI C++, por lo que usamos pow() que si lo s (acepta dos duble); para ello definimos una nueva funcin isqrt() que calcula directamente la raz cuadrada de un int utilizando pow(x, .5).

4.5.5 Estructuras y funciones


1 Sinopsis Las funciones pueden ser muy tiles para manejar estructuras en varias formas que se exponen a continuacin. Adems: Una funcin puede devolver una estructura o un puntero-a-estructura: mystruct f1(void); struct myst f2(void); mystruct* f3(void); // devuelve estructura // devuelve estructura // devuelve puntero a estructura

Una estructura puede ser pasada como argumento a una funcin de varias formas: void f1(mystruct s); void f2(mystruct* sptr); void f3(mystruct& sref); // directamente (por valor) // via puntero (por valor) // indirectamente (por referencia)

En los apartados que siguen se muestran con ms detalle algunos ejemplos de esta importante simbiosis entre estructuras y funciones.

4.5.5a Crear estructuras mediante funciones


1 Sinopsis Es posible definir funciones que devuelvan estructuras "ad hoc". La creacin se controla mediante los argumentos pasados a la funcin.

2 Ejemplo struct punto {int x; int y;} struct rect { struct punto p1; struct punto p2; } struct str creapu(int x, int y) // // // // define tipo estructura punto define tipo estructura rect compuesta de dos estrucutras tipo punto

// prototipo de funcin que devuelve

// una estructura (1) ... struct str creapu (int x, int y) { // definicin de creapu struct punto temp // declara temp estructura tipo punto temp.x = x; temp.y = y; return temp; } Comentario Observe que en (1) el prototipo anuncia que creapu devuelve una estructura, el detalle de como es dicha estructura hay que buscarlo en el cuerpo de la funcin. Resulta ser de tipo punto, ya definida anteriormente. As pues, esta funcin devuelve una estructura tipo punto. La funcin creapu puede utilizarse para crear estructuras dinmicamente, o pasarlas como argumento a funciones. Observe que no existe conflicto entre los nombres de los parmetros y los miembros de temp, ya que pertenecen a espacios de nombres distintos.

Veamos el primer caso (utilizar creapu para crear dinmicamente dos estructuras) completando el cdigo del ejemplo anterior: rect screen ; // define screen, estructura tipo rect screen.p1 = creapu(0, 0); screen.p2 = creapu(80, 24); Por supuesto, en vez de crear individualmente los componentes de screen, tambin se podra definir una funcin que cree directamente una estructura tipo rect a partir de cuatro coordenadas. He aqu su definicin: struct rect creascreen(int x1, int y1, int x2, int y2) { struct rect temp; temp.p1.x = x1; temp.p1.y = y1; temp.p2.x = x2; temp.p2.y = y2; return temp: } A partir de aqu se podra haber definido: struct rect screen = creascreen(0, 0, 80, 24); Nota: observe que en trminos de rendimiento, la eficacia de esta solucin es bastante pobre, ya que la invocacin anterior supone la creacin de dos estructuras: la primera, temp, en el cuerpo de la funcin. La segunda es una copia de ella en el mbito de la funcin invocante (valor devuelto). Cuando las estructuras son muy grandes, la creacin y destruccin de tales objetos puede ser muy costoso en trminos de tiempo y recursos. Repasar a este respecto el captulo relativo al "Valor devuelto" de las funciones ( 4.4.7).

4.5.5b Manipular estructuras mediante funciones


1 Sinopsis Otro posible uso de las funciones es manipular estructuras. Lo ilustraremos con dos ejemplos en los que vemos estructuras como argumentos de funciones. Partimos de los datos del ejemplo anterior ( 4.5.5a) es decir, suponemos: struct punto {int x; int y;} struct rect { struct punto p1; struct punto p2; } // // // // define tipo estructura punto define tipo estructura rect compuesta de dos estructuras tipo punto

2 Ejemplo-1 El primero es una funcin dist() para calcular y mostrar la distancia de una estructura tipo punto al origen de coordenadas: double dis(struct punto p) { // mide distancia de un punto al origen int temp = (p.x * p.x + p.y * p.y) double d = pow((double)temp, (double).5); printf("%3d,%3d, Distancia: %5.5f\n", p.x, p.y, d); return d; } Un ejemplo de utilizacin (ver definicin de screen.p2 en dist(screen.p2); salida: 80, 24, Distancia: 83.52245

4.5.5a):

3 Ejemplo-2 Supongamos una funcin sumapunto() para sumar estructuras punto, conviniendo que de la suma resulta otra estructura tipo punto, de coordenadas iguales a la suma de las coordenadas de los sumandos. El prototipo de dicha funcin sera: struct punto sumapunto( struct punto p1, struct punto p2);

La definicin: struct punto sumapunto( struct punto p1, struct punto p2) { struct punto presult; presult.x = p1.x + p2.x; presult.y = p1.y + p2.y;

return presult; }

Su utilizacin: struct punto p1 = creast(1,2); struct punto p2 = creast(1,2); struct sump1p2 = sumapunto(p1, p2);

Hay que sealar que la definicin podra modificarse, para no utilizar la estructura temporal result, en la siguiente forma: struct punto sumapunto( struct punto p1, struct punto p2) { p1.x += p2.x; p1.y += p2.y; return p1; }

En este ltimo caso, el resultado struct sump1p2 = sumapunto(p1, p2); hubiera sido el mismo, sin que en realidad se hubiese modificado el valor de la estructura p1, lo que puede comprobarse volviendo a ejecutar dist(p1). La razn es que -por definicin- los argumentos de funciones son variables locales automticas, con mbito del bloque en que se declara la funcin ( 4.1.3b mbito de bloque), lo que significa que al ejecutar sumpunto se vuelven a crear copias locales de las estructuras p1 y p2. As mismo, se devuelve la copia local de p1. Esto tiene sus pros y sus contra, como el hecho de que hay dos copias en memoria, el original y la creada en la funcin. Adems se requiere un tiempo para copiarlas y cargarlas en la pila (que puede ser importante cuando se manejan estructuras muy grandes). Por esta razn, es mejor y ms rpido utilizar punteros, como se ver en los ejemplos que siguen.

4.5.5c Estructuras de la Librera Estndar


1 Sinopsis Muchas funciones de las Libreras Estndar C++ utilizan estructuras como argumentos, como valores devueltos, o mediante punteros. Se trata de estructuras de formato fijo y conocido, cuya definicin se encuentra en las cabeceras correspondientes.

2 Ejemplo-1 La estructura tm definida en la cabecera <time.h>, contiene una completa descripcin del tiempo cronolgico en formato "de calendario"; tiene la definicin siguiente: struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon;

// // // // //

Segundos Minutos Horas (0--23) Dia del mes (1--31) Nmero del mes (0--11)

int int int int };

tm_year; tm_wday; tm_yday; tm_isdst;

// // // //

Ao (ao de calendario menos 1900) Dia de semana (0--6; Domingo = 0) Dia del ao (0--365) Diferencia de hora por adelanto de verano

tm es utilizada por varias funciones estndar relacionadas con el tiempo, por ejemplo localtime, definida igalmente en <time.h>, cuyo prototipo es: struct tm* localtime(const time_t* timer); Como puede verse, se trata de una funcin que acepta como argumento un puntero a constante tipo time_t y devuelve puntero a estructura tipotm, donde encontramos todos los datos sobre el momento sealado por timer en el formato usual de fechas: ao; da del mes; de la semana; hora; minuto; segundo; etc. (al que llamaremos "Formato de Calendario"). En nuestro caso, time_t es traducida por el preprocesador como long, de modo que podemos leer el prototipo como: struct tm* localtime(const long* timer); Esta funcin devuelve un puntero a estructura tipo tm, y el manual nos indica que el argumento timer es un puntero-a-tipo time_t, que debe referenciar a un valor de tiempo (que queremos obtener en formato de "Calendario") en un formato especial; en este caso un long que contiene el tiempo en segundos desde el instante que queremos controlar, al 1 de Enero de 1970 (origen de tiempos en Unix). Si tenemos en cuanta que la funcin time de Librera: time_t time(NULL); devuelve la hora actual del sistema (en segundos desde 1-1-1970), la hora actual en formato de "calendario" podemos obtenerla mediante: #include <time.h> #include <stdio.h> int main(void) { // ======== struct tm* tptr; // M1 time_t t = time(NULL); // M2 tptr = localtime(&t); // M3 printf("hora:%3d minuto:%3d segundo:%3d\n", tptr->tm_hour, tptr->tm_min, tptr->tm_sec); } Salida: hora: 9 minuto: 20 segundo: 4

Comentario En M1 declaramos un puntero (tptr) a estructura tipo tm para recibir el valor devuelto por localtime en M3. En M2 definimos un objeto t, del tipo time_t necesario para la invocacin de localtime. Lo iniciamos con un valor devuelto por time. Es decir, la hora del sistema expresada en segundos desde el origen de tiempos Unix. A continuacin, en M3, utilizamos un puntero a este valor para invocar la funcin localtime. El valor devuelto lo aplicamos al puntero declarado en M1. Finalmente, en M4 mostramos algunos de los valores contenidos en la estructura obtenida.

3 Ejemplo-2 En la cabecera <stdio.h> existe un typedef que traslada la palabra FILE a una estructura estndar que alberga toda la informacin necesaria para las controlar las operaciones de E/S de flujos (streams 5.3). typedef struct { unsigned char* unsigned char* int int unsigned short unsigned short wchar_t char unsigned char } FILE;

curp; buffer; level; bsize; istemp; flags; hold; fd; token;

// Current active pointer // Data transfer buffer // fill/empty level of buffer // Buffer size // Temporary file indicator // File status flags // Ungetc char if no buffer // File descriptor // Used for validity checking // este es el objeto FILE

de forma que cuando se encuentran unas sentencias como: FILE* stream; stream = fopen("PRUEBA.FIL", "w+"); // abre fichero if (stream == NULL) { fprintf(stderr, "No se puede abrir el fichero.\n"); return 1; } Estamos definiendo stream como puntero a una estructura FILE de diseo conocido, De forma que si la operacin fopen tiene xito, podemos referirnos a algunos detalles del fichero abierto, por ejemplo ( 4.5.4 Acceso a miembros): cout << "Tamao de bufer: " << stream->bsize << endl; Nota: como puede verse, la funcin fopen de la Librera Estndar, que se encarga de abrir un fichero, devuelve un puntero a una estructura FILE con los datos del fichero abierto, o un puntero nulo si la operacin no tiene xito.

4.5.6 Matrices de estructuras


1 Sinopsis Adems de las estructuras anidadas ya mencionadas (estructuras cuyos miembros son estructuras 4.5.1), tambin es posible disponer matrices cuyos miembros son estructuras: struct loc { int piso; char habit; char nombre[30]; } ; struct loc hotel[5]; La primera lnea define un tipo de estructura loc con tres miembros (un int; un char, y una matriz de 30 char); la segunda declara una matriz,hotel de 5 elementos cuyos miembros son estructuras de tipo loc.

2 La declaracin puede hacerse en una sentencia: struct loc { int piso; char habit; char nombre[30]; } hotel[5]; e incluso de forma annima ( 4.5.1), aunque de este modo perderamos la posibilidad de declarar ms tarde estructuras "hermanas" de los elementos de hotel: struct { int piso; char habit; char nombre[30]; } hotel[5];

3 La inicializacin (definicin) de la matriz puede hacerse en la misma sentencia siguiendo la norma general para las matrices (Declaracin de matrices 4.3.1). struct { int piso; char habit; char nombre[30]; } hotel[5] = { {1, 'A', {'M','a','r','t','i','n','e','z'} }, {1, 'A', {'G','o','m','e','z'} }, {1, 'A', {'A','l','v','a','r','e','z'} }, {1, 'A', {'G','i','r','e','l','a'} }, {1, 'A', {'T','r','e','s','c','a','s','t','r','o'} }, }; incluso se podra haber economizado alguna informacin al compilador: struct { int piso; char habit; char nombre[30]; } hotel[] = { 1, 'A', {'M','a','r','t','i','n','e','z'} , 1, 'A', {'G','o','m','e','z'} , 1, 'A', {'A','l','v','a','r','e','z'} , 1, 'A', {'G','i','r','e','l','a'} , 1, 'A', {'T','r','e','s','c','a','s','t','r','o'} , }; el tamao de hotel puede deducirse de los datos de inicializacin. As mismo, los separadores de los elementos tambin, porque la propia estructura declarada (un int seguido de un char seguido de un array), hace que el compilador pueda conocer cuando termina una estructura y comienza la siguiente.

4 Otro ejemplo struct ficha { char* marca; int fecha; double peso; } Matriz_de_fichas[]= { { "Ford", 1956, 1950 }, { "Mercedes", 1999, 2050 }, }; Observe que en este caso, las matrices alfanumricas "Ford" y "Mercedes" son externas a las estructuras y por tanto, externas tambin a la propiamatriz_de_fichas de dos estructuras tipo ficha.

4.5.7 Punteros a estructuras


1 Sinopsis Del mismo modo que ocurre con las funciones, las estructuras tienen una direccin, asumiendo que esta es el comienzo de su almacenamiento. Esto es especialmente cierto en C, donde las estructuras representan exclusivamente conjuntos de datos. En C++ las estructuras son cierto tipo de clases que pueden contener cdigo (funciones), pero incluso entonces, lo que realmente se almacena en el cuerpo de la estructura son punteros (datos) a las funciones correspondientes. Nota: puesto que desde el punto de vista C++ las estructuras son un tipo de clases, les son aplicables los mismos principios y reglas que a aquellas. En concreto todo lo sealado respecto a punteros. Ver a este respecto "Punteros a clases" ( 4.2.1f) y "Punteros a miembros" ( 4.2.1g).

Veremos que los punteros a estructuras son de uso muy frecuente para el manejo de estas; en especial cuando se pasan como argumentos a funciones, o son devueltas por estas. Por ejemplo, la sentencia: struct punto {int x; int y;} pto = {3,4}, *ptr = &pto, arsp[10]; define un tipo de estructura denominado punto; una estructura (instancia) pto, y un puntero ptr a dicha instancia. Finalmente declara una matriz arsp de 10 estructuras tipo punto.

2 Sintaxis En general la sintaxis para declaracin de punteros a estructuras sigue la sintaxis general (Declaracin de punteros 4.2.1a). <tipo_objeto> * <etiqueta_puntero> [ = <iniciador> ] En este caso, tipo_objeto es de la forma struct punto, con lo que la declaracin es: struct punto * ptr;

Opcionalmente puede incluirse un iniciador como en el ejemplo anterior: struct punto * ptr = &pto; en este caso, ptr es un puntero, y su indireccin *ptr, representa la estructura pto. Los miembros de la estructura pueden referenciarse mediante el operador de acceso: (*ptr).x == 3; (*ptr).y == 4; Los parntesis son necesarios, ya que el operador de acceso ( .) tiene mayor precedencia ( 4.9.0a) que el de indireccin ( 4.9.11a). Por esta razn, *ptr.x es interpretada como *(ptr.x), lo que es errneo, ya que ptr.x no es un puntero (en este caso es un entero).

3 La expresin: struct { int x1; char * str; long *arr[10]; } *ptr;

Declara un puntero ptr a un tipo de estructura annima de tres componentes Nota: aunque sintcticamente correcta y aceptable por el compilador, una declaracin como la anterior es prcticamente intil y muy peligrosa. Por ejemplo, la sentencia (*ptr).int = 30; puede producir una catstrofe. La razn es la misma que se apunt en Expresiones peligrosas ( 4.2.1a).

4 Los punteros son muy importantes para el manejo de matrices y estructuras, por lo que en este caso se ha previsto una notacin alternativa ms corta ( 4.5.4 Acceso a miembros): ptr->x == 3; ptr->y == 3;

Veamos las dos opciones de notacin para acceso de miembros en un ejemplo con estructuras anidadas: struct punto { int x; int y; }; struct line { struct punto p1; struct punto p2; } lin, *liptr = &lin; En las sentencias anteriores hemos definido dos tipos de estructuras; una instancia y un puntero a dicha instancia. En estas condiciones, las expresiones que siguen son equivalentes (1 y 6 son preferibles por legibilidad): lin.p1.x (lin.p1).x // L1.

(*liptr).p1.x ((*liptr).p1).x liptr->p1.x (liptr->p1).x

// L6.

5 Ejemplo. float pi = 3.14159; struct { int x; char * str; float *arr[10]; struct punto pto; struct cuadro cuad; } str, *ptr = &str; Supuestas las declaraciones anteriores, a continuacin realizamos asignaciones a los miembros de str de dos formas: directamente y mediante expresiones del puntero ptr. Observe que segn lo indicado al respecto del espacio de nombres de estructuras ( 4.5.1d), es perfectamente compatible el nombre str de estructura con el de uno de sus miembros (el puntero a carcter str.str). 5.1 Asignaciones directas: str.x = 30; str.str = "Hola mundo"; str.arr[0] = & pi; str.pto.x = 2; str.pto.y = 3; str.cuad.p1.x = 4; str.cuad.p1.y = 5; str.cuad.p2.x = 6; str.cuad.p2.y = 7; 5.2 Expresiones con puntero: ptr->x = 30; ptr->str = "Hola mundo"; ptr->arr[0] = & pi; ptr->pto.x = 2; str.pto.y = 3; ptr->cuad.p1.x = 4; ptr->cuad.p1.y = 5; ptr->cuad.p2.x = 6; ptr->cuad.p2.y = 7;

6 En el epgrafe relativo a la asociatividad y precedencia de operadores ( sealado que los operadores: () [] -> . :: Llamada de funcin Subndices Acceso a miembro de estructura (mediante puntero) Acceso a miembro de estructura Acceso a miembro de clase

4.9.0a) se ha

constituyen el orden ms alto en la jerarqua de precedencia, y que su asociatividad es de izquierda a derecha, por lo que hay que prestar especial atencin a las expresiones en que

aparecen. Para resaltar la importancia de las reglas de precedencia, en los ejemplos que siguen se exponen algunas expresiones, referidas al ejemplo (5 ), as como una explicacin de su significado. ++ptr->x Debido a la precedencia de operadores, equivale a ++(ptr->x). As pues, incrementa el miembro x, no el valor del puntero ptr. Resultado: str.x == 31 *ptr->x Del mismo modo, esta indireccin equivale a *(ptr->x), lo que resulta en un error, porque en el ejemplo indicado, ptr->x no es un puntero (es un int), y ya hemos visto [4.9.11] que el operando del operador de indireccin debe ser un puntero. *ptr->str En este caso, si es correcta la aplicacin del operador *; la expresin equivale a *(ptr->str), y ptr->str s es un puntero (a carcter); concretamente seala a la primera posicin de la cadena, es decir: *ptr->str == 'H' se puede evidenciar como sigue: printf("Letra \"%c\"\n", ++*ptr->str Se ha sealado que *ptr->str equivale a 'H'; ahora el compilador se enfrenta a la expresin: ++'H' == 'H'+1;. El resultado depende del contexto, por ejemplo, 'H' = 'H'+1; es un error ('H' no es un Lvalue). Sin embargo, la sentencia que sigue se compila correctamente y produce la salida indicada. printf("Letra \"%c\"\n", ++*ptr->str); // -> Letra "I" *ptr->str); // -> Letra "H"

La explicacin es que el compilador pasa a printf el valor 'H'+1 (sin pretender ninguna otra asignacin), lo que, tras una conversin de tipo, se traduce en el carcter 'I' que sigue 'H' en la lista ASCII. *ptr++->str La expresin se traduce en: *((ptr++)->str). Si se utiliza la expresin que sigue, el resultado es el que se indica, pero cualquier posterior intento de utilizar ptr conduce a una catstrofe. printf("Letra \"%c\"\n", *ptr++->str); // -> Letra "H"

La razn es que se enva a printf el valor *ptr->str, que produce la salida indicada y despus se produce el postincremento del puntero ptr, que queda apuntando a dios sabe donde, con lo que la utilizacin posterior seguramente producir el fallo del sistema por "Error grave".

Nota: si str hubiese apuntado a una matriz de estructuras y la posicin actual no fuese la ltima, el efecto hubiese sido simplemente dejar str apuntando a la siguiente estructura de la matriz. *++ptr->str La expresin equivale a *(++ (ptr->str)). Nuevamente el resultado es correcto: ptr>str es un puntero p que seala al carcter 'H'; su incremento en 1 lo hace apuntar al segundo, por lo que si utilizamos la expresin que sigue se produce la salida indicada: printf("Letra \"%c\"\n", *ptr->str++ Esta expresin equivale a (*(ptr->str))++. Es decir, 'H'++; nuevamente el resultado depende del contexto. En la expresin que nos viene sirviendo para control, produce la salida sealada, porque printf recibe como argumento un puntero a 'H' y el incremento se produce despus. printf("Letra \"%c\"\n", *ptr->str++); // -> Letra "H" *++ptr->str); // -> Letra "o"

4.5.8 Estructuras auto-referenciadas


1 sinopsis Las estructuras se utilizan con frecuencia como elementos de estructuras de datos tipo rbol y lista ( 1.8). En estos casos, un conjunto de objetos del mismo tipo, estn relacionadas entre s mediante punteros contenidos en ellos mismos, de forma que basta conocer la raz del rbol o principio de la lista, para poder acceder a todo el conjunto. En ocasiones, si tales listas son doblemente enlazadas, es posible entrar en cualquier nodo y recorrer la totalidad del conjunto en cualquier sentido. Esta es la razn del apelativo "auto-referenciadas".

2 Como ejemplo, construiremos un programa que acepte caracteres por el teclado y construya un rbol binario ( 1.8b) cuyos elementos contengan el carcter introducido y estn ordenados segn su valor ASCII. En la Fig. 1 se muestra grficamente el resultado de introducir los caracteres: b, D, g, A, E y k. El programa acepta caracteres indefinidamente (mientras exista memoria suficiente, o se pulse ESC). Despus de la introduccin de cada nuevo elemento, se muestra ordenadamente la totalidad del rbol. #include <iostream.h> struct base { char let; struct base* izq; // Ejemplo de creacin de rbol binario

// declara tipo de estructura a utilizar // para almacenar carcter // puntero izquierdo

struct base* der; } *rz, *aptr; auxiliar

// puntero derecho // rz puntero al nodo raz; aptr puntero

int incluir(char letra, struct base* puntero); void inorden (struct base* puntero); char acepta (void); // aceptar datos por teclado void main(void) { // ========================= char c; while ( (c = acepta()) != 27) { // Bucle principal if (incluir(c, rz)) break; inorden(rz); } } char acepta (void) { // Introducir datos por teclado char c; cout << "\n Pulse un carcter + [CR] "; while (1) { c = getchar(); if (c == 10) continue; // 10 == New Line return c; } } int incluir(char letr, struct base* ptr) { // aadir elemento if (rz == NULL) { // 1o. solo la primera vez!!! rz = (struct base*) malloc(sizeof(base)); if (rz == NULL) { cout << "NO hay memoria suficiente!!" << endl; return 1; } rz->let = letr; rz->izq = rz->der = NULL; return 0; } if (letr == ptr->let) return 0; // 2o. El carcter ya existe if (letr < ptr->let) { // 3o. if (ptr->izq == NULL) { // enlace libre: incluir aptr = (struct base*) malloc(sizeof(base)); if (aptr == NULL) { cout << "NO queda suficiente memoria" << endl; return 1; } aptr->let = letr; aptr->izq = aptr->der = NULL; ptr->izq = aptr; return 0; } if (incluir(letr, ptr->izq)) // ocupado: seguir buscando return 1; // falla la insercin } if (letr > ptr->let) { // 4o. if (ptr->der == NULL) { // enlace libre: incluir aptr = (struct base*) malloc(sizeof(base)); if (aptr == NULL) { cout << "NO queda suficiente memoria" << endl; return 1; }

aptr->let = letr; aptr->izq = aptr->der = NULL; ptr->der = aptr; return 0; } if (incluir(letr, ptr->der)) // ocupado: seguir buscando return 1; // falla la insercin } return 0; } void inorden (struct base* ptr) { if (ptr == NULL) return; inorden(ptr->izq); cout << " - " << ptr->let; inorden(ptr->der); } Comentario La primera parte declara la estructura, base, que ser utilizada en cada nodo; contiene un carcter para alojar el introducido por teclado, y los dos punteros que necesitan los elementos de los rboles binarios. Adems se definen dos punteros-a-base; uno de ellos, rz, apuntar desde el primer momento al nodo raz; el otro, aptr, es un puntero auxiliar que se necesita en la funcin incluir. Adems de main, el programa solo tiene tres sencillas funciones: incluir, que incluye cada nuevo elemento en el rbol con el carcter pulsado;inorden, que recorre el rbol ordenadamente mostrando en pantalla los caracteres, y acepta, que simplemente captura los datos del teclado. La funcin main consiste en un bucle que acepta indefinidamente caracteres del teclado (mientras no se pulse la tecla Escape); inserta en el rbol el carcter introducido, y finalmente muestra la totalidad del rbol hasta el momento. El proceso se termina si por alguna razn (por ejemplo, falta de memoria) falla el proceso de insertar el carcter, en cuyo caso la funcin incluir devuelve 1. La funcin acepta no merece ningn comentario; aunque quizs se podra haber afinado un poco ms para no aceptar secuencias de escape (caracteres no imprimibles 3.2.3e); simplemente letras maysculas, o minsculas, dgitos, etc. Puesto que se ha utilizado la funcin getchar, es necesario pulsar tambin Intro con cada insercin. Puede sorprender la extremada simplicidad de la funcin inorden; esta funcin recursiva recorre la totalidad del rbol mostrando de forma ordenada el carcter contenido en cada elemento. Es de destacar que en realidad la funcin: void recorrido (struct base* ptr) { if (ptr == NULL) return; recorrido(ptr->izq); recorrido(ptr->der); } // Recorrer rbol

recorrera la totalidad del rbol pasando por todos los nodos. Para recorrerlo en las formas preorden y postorden ( 1.8b), habra que sustituirinorden por las funciones que se indican a continuacin. Observe que la diferencia es pequesima entre las tres; simplemente la posicin del la sentencia que muestra el carcter en pantalla. void preorden (struct base* ptr) { if (ptr == NULL) return; cout << " - " << ptr->let; preorden(ptr->izq); preorden(ptr->der); } void postorden (struct base* ptr) { if (ptr == NULL) return; postorden(ptr->izq); postorden(ptr->der); cout << " - " << ptr->let; } // recorrido preorden

// recorrido postorden

La funcin incluir, que crea cada nuevo nodo del rbol con el carcter introducido, es recursiva (como muchas de las que tratan con rboles) y muy sencilla, pero merece algn comentario: El cuerpo del primer if, solo se ejecuta la primera vez, cuando incluir es invocada desde main despus de introducido el primer carcter, y el puntero rz tiene todava su valor de inicializacin (NULL 4.2.1). Este valor previo para rz y aptr es proporcionado directamente por el compilador en el momento de la declaracin de estas variables globales. Este bloque se encarga de la creacin del nodo raz (en adelante, durante todo el programa rz seala a dicho nodo). A este respecto, obsrvese que incluir siempre es invocada desde main con el valor rz como argumento, es decir, el proceso de inclusin se inicia siempre empezando por la raz del rbol. En este bloque hay que resaltar las sentencias encargadas de asignar memoria para el nuevo elemento. Estas sentencias se repiten cada vez que se crea un elemento nuevo (en los if 3 y 4), y contienen un mecanismo de control que avisa si la memoria disponible se ha agotado: ptr = (struct base*) malloc(sizeof(base)); if (ptr == NULL) { cout << "NO hay memoria suficiente!!" << endl; }

return 1;

El manual de la Librera Estndar nos seala que a la funcin malloc hay que pasarle un entero con el tamao del bloque de memoria que queremos reservar -en nuestro caso sizeof(base)-, y que devuelve un puntero a la zona de memoria asignada o un NULL si falla en el intento (cosa que aprovechamos). Observe que al valor malloc(sizeof(base)) le aplicamos un "casting" ( 4.9.9) antes de asignarlo a ptr; de lo contrario se obtendra un error. Nota: tambin se podra utilizar el operador new ( sentencias anteriores seran entonces: 4.9.20), ms moderno que malloc; las

if ((ptr = new(base)) == NULL) { cout << "NO hay memoria suficiente!!" << endl; }

return 1;

Cualquiera que fuese la funcin utilizada para crear los nuevos elementos, hay que considerar que tanto new como malloc reservan espacio del montn. En un programa real en el que se construyeran rboles como el presente, a fin de evitar prdidas de memoria cuando ya no fuesen necesarios, deberan tenerse en cuenta las observaciones respecto a la persistencia de este tipo de objetos ( 1.3.2).

El segundo if simplemente no hace nada, pero acepta la entrada como vlida. Ocurre cuando el carcter ya existe previamente en el rbol (se ha repetido). El cuerpo de este bloque podra modificarse fcilmente. Por ejemplo, para incluir un contador que sealara cuantas repeticiones se han pulsado de cada tecla. Para esto, en el diseo de la estructura base incluiramos un campo numrico que inicializaramos a cero e incrementaramos con cada repeticin. Sugerencia: en este caso tambin podramos definir sendas variables globales: total y repet, que mostraran el total de elementos en el rbol y el total de repeticiones. Sus valores se actualizaran fcilmente en la funcin inorden, o despus de cada entrada en la propia funcin incluir. El tercero y el cuarto if son los encargados de la creacin de los nuevos nodos despus de creado el nodo raz. Cuando llega a este punto, el nodo raz ya existe, el rbol es ya ms o menos grande y la invocacin se ha realizado desde main, o se trata de una de las mltiples recursiones posibles. Mediante estas recursiones, incluir trepa (aqu deberamos decir desciende) por el rbol siguiendo los enlaces izquierdos si el carcter a incluir es menor que el de los nodos que se va encontrando y a la inversa si es mayor. Cuando encuentra un nodo terminal adecuado, con el enlace izquierdo o derecho libre (respectivamente), solicita memoria; rellena los datos del nuevo nodo y lo cuelga del nodo terminal encontrado, cuyo enlace izquierdo o derecho se actualiza. El nodo recin incluido es ahora terminal; sus dos enlaces estn a cero y contiene el nuevo carcter. En este momento la funcin termina devolviendo el valor 0 (finalizacin con xito), y el control vuelve a main, donde seguir el bucle mediante la invocacin de inorden...

4.5.8a Equilibrio de rboles binarios


1 Sinopsis En el epgrafe dedicado a la Estructura de la informacin ( 1.8b) se indic que el equilibrio del rbol se ve fuertemente afectado por el orden de introduccin de los datos, en especial el primero, que constituir el nodo raz e influir en la totalidad del rbol. En consecuencia, este primer elemento afecta grandemente la eficacia de la estructura resultante frente a los mecanismos de datos. Si el rango de los datos es conocido y se van a introducir gran cantidad de ellos (el rbol ser grande), pueden disponerse una medida auxiliar para facilitar el equilibrado; consiste en crear un nodo raz deliberadamente construido, de forma que corresponda al valor medio de la serie. De esta forma el equilibrio no depender de la mejor o peor fortuna en la introduccin del primer dato.

1.1 Como ejemplo modificaremos el de la pgina anterior, adaptndolo para que solo acepte caracteres alfabticos (letras maysculas y minsculas): #include <iostream.h> struct base { char let; struct base* izq; struct base* der; } *rz, *aptr; auxiliar // // // // // // Creacin de rbol binario mejorado declara tipo de estructura a utilizar para almacenar carcter puntero izquierdo puntero derecho rz puntero al nodo raz; aptr puntero

int incluir(char letra, struct base* puntero); void inorden (struct base* puntero); char acepta (void); // aceptar datos del teclado void main(void) { // ========================= char c; if ((rz = new(base)) == NULL) { // crear nodo raz cout << "NO hay memoria suficiente!!" << endl; return 1; } rz->let = '\\'; // carcter en el nodo raz rz->izq = rz->der = NULL; while ( (c = acepta()) != 27) { // Bucle principal if (incluir(c, rz)) break; inorden(rz); } } char acepta (void) { // Introducir datos por teclado char c; cout << "\n Pulse un carcter + [CR] "; while (1) { c = getchar(); if (c == 10) continue; // 10 == New Line if (c >= 65 && c <= 91) return c; else if (c >= 97 && c <= 123 ) return c; else if ( c == 27 ) return c; cout << "\a"; } } int incluir(char letr, struct base* ptr) { // aadir elemento if (letr == ptr->let) return 0; // El carcter ya existe if (letr < ptr->let) { if (ptr->izq == NULL) { // enlace libre: incluir aptr = (struct base*) malloc(sizeof(base)); if (aptr == NULL) { cout << "NO queda suficiente memoria" << endl; return 1; } aptr->let = letr; aptr->izq = aptr->der = NULL; ptr->izq = aptr; return 0;

} if (incluir(letr, ptr->izq)) // ocupado: seguir buscando return 1; // falla la insercin } if (letr > ptr->let) { if (ptr->der == NULL) { // enlace libre: incluir aptr = (struct base*) malloc(sizeof(base)); if (aptr == NULL) { cout << "NO queda suficiente memoria" << endl; return 1; } aptr->let = letr; aptr->izq = aptr->der = NULL; ptr->der = aptr; return 0; } if (incluir(letr, ptr->der)) // ocupado: seguir buscando return 1; // falla la insercin } return 0; } void inorden (struct base* ptr) { if (ptr == NULL) return; inorden(ptr->izq); cout << " - " << ptr->let; inorden(ptr->der); } Comentario Los cambios son mnimos pero significativos: hemos creado inicialmente el nodo raz en la propia funcin main, con el carcter \ (ASCII 92) que est justamente entre las serie de las Maysculas y las minsculas en la ordenacin ASCII; por tanto esta parte la sacamos de la funcin incluir. Observe la forma de asignacin del carcter elegido en el nodo raz: rz->let = '\\'; // Recorrer rbol

La funcin acepta es modificada para que solo acepte las letras del abecedario. Si se introduce un carcter incorrecto, se produce una seal acstica (ASCII 7).

4.5.8b Buscar elementos en rboles binarios


1 Sinopsis Hemos sealado que existen algoritmos muy eficientes para recuperar informacin de los rboles binarios ordenados. El algoritmo para buscar un valor puede ser una modificacin de la funcin incluir del ejemplo de la pgina anterior. All la funcin no hace nada si el carcter coincide con uno ya existente; aqu aprovecharemos precisamente esta caracterstica para que nos devuelva un puntero al elemento, o un puntero nulo (NULL) si el carcter no existe en el rbol. La funcin a la que denominaremos busca, tiene el aspecto siguiente ( key es la clave a buscar): struct base* busca(char key, struct base* ptr) { // buscar key if (key == ptr->let) return ptr; // Ok. encuentro!

if (key < ptr->let) { // debe estar en la rama izquierda if (ptr->izq == NULL) return NULL; // no existe busca(key, ptr->izq); // enlace ocupado: seguir buscando } if (key > ptr->let) { // debe estar en la rama derecha if (ptr->der == NULL) return NULL; // no existe busca(key, ptr->der); // enlace ocupado: seguir buscando } return NULL; } Por las razones expuestas al hablar de la recursin ( 4.4.6c), esta funcin no puede ser directamente utilizada en su forma actual, por lo que modificaremos ligeramente su diseo antes de su utilizacin en el siguiente ejemplo.

2 Ejemplo Para comprobar la veracidad del cdigo, modificamos el programa del rbol binario mejorado de la pgina anterior, de forma que en cualquier momento, introduciendo el carcter "?", el programa realiza la bsqueda del siguiente carcter que se introduzca por teclado en vez de incluirlo en el rbol, que es el comportamiento normal. #include <iostream.h> struct base { char let; struct base* izq; struct base* der; } *rz, *aptr; // rbol binario con bsqueda // Estructura a utilizar

char previo, ant; // globales auxiliares int incluir(char letra, struct base* puntero); void inorden (struct base* puntero); char acepta (void); // aceptar datos del teclado void busca(char key, struct base* puntero); // buscar void nodo(struct base* puntero); // mostrar resultado de busqueda void main(void) { // ========================== char c; if ((rz = new(base)) == NULL) { // crear nodo raz cout << "NO queda memoria" << endl; return 1; } rz->let = '\\'; // carcter en el nodo raz rz->izq = rz->der = NULL; while ( (c = acepta()) != 27) { // Bucle principal if (c == '?') { previo = c; continue; } else if (previo == '?') { previo = c; aptr = NULL; busca(c, rz); // buscar caracter en el arbol nodo(aptr); // mostrar resultado de la busqueda continue; } else if (incluir(c, rz)) break;

inorden(rz); } } char acepta (void) { // Introducir datos por teclado char c; cout << "\n Pulse un caracter + [CR] "; while (1) { c = getchar(); if (c == 10) continue; // 10 == New Line if (c >= 65 && c <= 91) return c; if (c >= 97 && c <= 123 ) return c; if (c == 27 || c == '?') return c; cout << "\a"; } } int incluir(char letr, struct base* ptr) { // aadir elemento if (letr == ptr->let) return 0; // El carcter ya existe if (letr < ptr->let) { if (ptr->izq == NULL) { // enlace libre: incluir aptr = (struct base*) malloc(sizeof(base)); if (aptr == NULL) { cout << "Memoria agotada" << endl; return 1; } aptr->let = letr; aptr->izq = aptr->der = NULL; ptr->izq = aptr; return 0; } if (incluir(letr, ptr->izq)) return 1; // falla la insercin } if (letr > ptr->let) { if (ptr->der == NULL) { // enlace libre: incluir aptr = (struct base*) malloc(sizeof(base)); if (aptr == NULL) { cout << "Memoria agotada" << endl; return 1; } aptr->let = letr; aptr->izq = aptr->der = NULL; ptr->der = aptr; return 0; } if (incluir(letr, ptr->der)) return 1; // falla la insercin } return 0; } void inorden (struct base* ptr) { if (ptr == NULL) return; inorden(ptr->izq); cout << " - " << ptr->let; inorden(ptr->der); } // Recorrer rbol

void busca(char key, struct base* ptr) { // buscar key if (key == ptr->let) { aptr = ptr; return; } ant = ptr->let; if (key < ptr->let) { // debe estar en rama izquierda if (ptr->izq == NULL) return; // no existe busca(key, ptr->izq); // seguir bsqueda

} if (key > ptr->let) { if (ptr->der == NULL) return; busca(key, ptr->der); } return; }

// debe estar en rama derecha // no existe // seguir bsqueda

void nodo(struct base* ptr) { // mostrar resultado if (ptr == NULL) { cout << "No encontrado\a" << endl; return; } char izq = '#', der = '#', key = ptr->let; if (ptr->izq != NULL) izq = ptr->izq->let; // letra nodo izq. if (ptr->der != NULL) der = ptr->der->let; // letra nodo der. cout << "\n " << ant << endl; cout << " |" << endl; cout << " " << key << endl; cout << "/ \\" << endl; cout << izq << " " << der << endl; } Comentario Introducimos dos nuevas variables globales: previo y ant, que alojarn respectivamente el ltimo carcter pulsado, y el carcter del nodo anterior (raz) del que se busca. Igualmente introducimos dos nuevas funciones: busca y nodo. La primera ya se ha comentado, se utiliza para buscar un carcter. La segunda, a la que se pasa un puntero al nodo encontrado, muestra el detalle de los enlaces o anuncia el fallo en la bsqueda (el puntero es nulo). La funcin acepta ha sufrido un ligersimo cambio para permitir introducir el carcter "?"

3 Ejemplo Como complemento del ejercicio anterior, presentamos una variante utilizando el manejador de excepciones C++ ( 1.6) para realizar un retorno desde la funcin busca hasta main cuando ocurre un encuentro. Se lanza entonces una excepcin que es precisamente el puntero al nodo encontrado. Esta excepcin es capturada y entonces se muestra el detalle del nodo. Como la funcin nodo solo es llamada en caso de bsqueda con xito, es necesario modificarla ligeramente y sacar fuera de ella el mensaje de bsqueda fallida. Como puede verse, esta adaptacin del programa solo exige muy pequeas modificaciones. #include <iostream.h> struct base { char let; struct base* izq; struct base* der; } *rz, *aptr; // rbol binario con bsqueda (II) // Estructura a utilizar

char previo, ant; // globales auxiliares int incluir(char letra, struct base* puntero); void inorden (struct base* puntero);

char acepta (void); // aceptar datos del teclado void busca(char key, struct base* puntero); // buscar void nodo(struct base* puntero); // mostrar resultado de busqueda int main(void) { // ========================== char c; if ((rz = new(base)) == NULL) { // crear nodo raz cout << "NO queda memoria" << endl; return 1; } rz->let = '\\'; // carcter en el nodo raz rz->izq = rz->der = NULL; while ( (c = acepta()) != 27) { // Bucle principal if (c == '?') { previo = c; continue; } else if (previo == '?') { previo = c; try { busca(c, rz); } // bloque try (buscar) catch (struct base* ptr) { nodo(ptr); continue; } cout << "No encontrado\a" << endl; continue; } else if (incluir(c, rz)) break; inorden(rz); } } char acepta (void) { // Introducir datos por teclado char c; cout << "\n Pulse un caracter + [CR] "; while (1) { c = getchar(); if (c == 10) continue; // 10 == New Line if (c >= 65 && c <= 91) return c; if (c >= 97 && c <= 123 ) return c; if (c == 27 || c == '?') return c; cout << "\a"; } } int incluir(char letr, struct base* ptr) { // aadir elemento if (letr == ptr->let) return 0; // El carcter ya existe if (letr < ptr->let) { if (ptr->izq == NULL) { // enlace libre: incluir aptr = (struct base*) malloc(sizeof(base)); if (aptr == NULL) { cout << "NO queda memoria" << endl; return 1; } aptr->let = letr; aptr->izq = aptr->der = NULL; ptr->izq = aptr; return 0; } if (incluir(letr, ptr->izq)) return 1; // falla la insercin } if (letr > ptr->let) { if (ptr->der == NULL) { // enlace libre: incluir aptr = (struct base*) malloc(sizeof(base)); if (aptr == NULL) { cout << "NO queda memoria" << endl; return 1; } aptr->let = letr; aptr->izq = aptr->der = NULL;

ptr->der = aptr; return 0; } if (incluir(letr, ptr->der)) return 1; } return 0; } void inorden (struct base * ptr) { if (ptr == NULL) return; inorden(ptr->izq); cout << " - " << ptr->let; inorden(ptr->der); } // Recorrer rbol // falla la insercin

void busca(char key, struct base* ptr) { // buscar key if (key == ptr->let) { throw(ptr); } // lanzar excepcion ant = ptr->let; if (key < ptr->let) { // debe estar en rama izquierda if (ptr->izq == NULL) return; // no existe busca(key, ptr->izq); // seguir bsqueda } if (key > ptr->let) { // debe estar en rama derecha if (ptr->der == NULL) return; // no existe busca(key, ptr->der); // seguir bsqueda } return; } void nodo(struct base* ptr) { // mostrar resultado char izq = '#', der = '#', key = ptr->let; if (ptr->izq != NULL) izq = ptr->izq->let; // letra nodo izq. if (ptr->der != NULL) der = ptr->der->let; // letra nodo der. cout << "\n " << ant << endl; cout << " |" << endl; cout << " " << key << endl; cout << "/ \\" << endl; cout << izq << " " << der << endl; }

4.6 Campos de Bits


1 Sinopsis Los campos de bits, o simplemente campos, son grupos de un nmero determinado de bits, que pueden o no tener un identificador asociado. Representan un artificio que permite utilizar miembros de tamao arbitrario en estructuras, uniones y clases; independiente de la posibilidad que proporcionan los tipos bsicos ( 2.2.1) cuyo tamao est predeterminado por el lenguaje. Por ejemplo, en ocasiones es necesario almacenar semforos (flags) con determinados estados del programa, para los que en realidad solo hace falta un bit, pero incluso una variable bool ocupa un

octeto. Los campos de bits permiten utilizar cada bit de un octeto independientemente, aumentando as su capacidad de representacin. Nota: esta tcnica, de manejo independiente de bits en una palabra, ha sido ampliamente utilizada desde siempre en la programacin, no solo de C/C++; casi todos los lenguajes ofrecen la posibilidad de operadores "bitwise", que permiten esto de forma ms o menos artesanal.

Entre otros usos, los campos de bits se han utilizado histricamente para empaquetar variables en un espacio ms pequeo, pero obligan al compilador a generar cdigo adicional para manejarlos, lo que resulta costoso en trminos de tamao y velocidad del ejecutable. El resultado es que frecuentemente, el cdigo resulta mayor y ms lento si se usan estos tipos, por lo que generalmente se desaconseja su uso excepto para aplicaciones muy especficas de bajo nivel, en las que la alineacin exacta de los patrones de bits a utilizar es un aspecto primordial. Por ejemplo, transmisiones de datos Otra cuestin distinta, a veces decisiva para su utilizacin, es la significativa reduccin de espacio de almacenamiento externo (disco por ejemplo) que puede conseguirse cuando en determinados casos, se almacena gran nmero de registros que utilizan campos de bits en sustitucin de tipos bsicos.

2 Declaracin La sintaxis para declaracin de campos es la siguiente: especificador-de-tipo <identificador> : ancho; Ejemplos: int Uno : 8; unsigned int Dos : 16; int : 2; El especificador-de-tipo puede ser alguno de los siguientes: bool; char; unsigned char; short; unsigned short; long; unsigned long;int; unsigned int; __int64 o unsigned __int64. Abreviadamente lo denominaremos tipo del campo. El especificador ancho (abreviadamente ancho del campo), debe ser una expresin que se evale a un entero constante de cualquier tamao ( 3.2.3a). Un campo de ancho cero salta a la prxima unidad de almacenamiento. Si se omite el identificador (tercera lnea del ejemplo), se asigna el ancho correspondiente, pero el campo no es accesible. Esto permite ajustar patrones de bits a espacios determinados. Por ejemplo, registros hardware donde algunos bits no son utilizados. Los campos de bits solo pueden existir en estructuras, uniones y clases, y son accedidos utilizando los mismos operadores de acceso que al resto de los miembros; los selectores . y -> ( 4.5.4).

3 Limitaciones de uso El uso de campos de bits requiere algunas consideraciones a ser tenidas en cuenta: El cdigo puede no resultar portable, dado que la organizacin de bits dentro de bytes, y de estos dentro de palabras, depende de la plataforma utilizada en cada caso. Esta organizacin puede variar incluso dentro de las sucesivas versiones de un mismo compilador (ver al respecto las observaciones relativas a compatibilidad ). Los campos de bits no son direccionables, es decir, no se les puede aplicar el operador de referencia & ( 4.9.11b). As pues, la expresin que sigue es ilegal si cbit es el nombre de un campo de bits: &miEstruct.cbit // Ilegal !! 4.9.13)

No se les puede aplicar el operador sizeof (

4 Una alternativa recomendada para disponer de variables de un bit (semforos), es el uso de define ( #define). Por ejemplo, los "defines": #define #define #define #define #define #define #define #define #define Nada bitUno bitDos bitTres bitCuatro bitCinco bitSeis bitSiete bitOcho 0x00 0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80

pueden ser utilizados para escribir el siguiente cdigo: if (flags & bitUno) {...} flags |= bitDos; flags &= ~bitTres; // si primer bit ON... // pone bit dos ON // pone bit tres OFF

Se pueden usar esquemas similares para campos de bits de cualquier tamao.

5 Relleno de campos Si el ancho del campo es mayor que el del tipo correspondiente, el compilador puede insertar bits de ajuste hasta alcanzar el tamao del tipo. As pues, la declaracin: struct mystruct { int x : 40; int y : 8; }; crear un espacio de 32 bits para x mas un aadido de 8 bits, y crear para y un almacenamiento de 8 bits. Para optimizar el acceso, el compilador puede considerar x una variable regular, no un

campo de bits (el proceso ser transparente para el usuario, para el que seguir siendo un campo de bits).

6 Diseo y alineacin Los campos de bits se componen de grupos consecutivos de campos de bit del mismo tipo sin preocupacin del signo. Cada grupo de campo se alinea segn el tipo de los miembros del grupo. El tipo de alineacin viene determinado por el tipo y por lo especificado para la alineacin general por la opcin -aN del compilador (ven en la pgina siguiente "Alineacin interna" 4.5.9a). Dentro de cada grupo, el compilador puede empaquetar los campos individuales dentro de reas tan grandes como las de los tipos, pero ningn campo puede estar a horcajadas entre dos de estas reas. A su vez, el tamao total de la estructura puede ser alineado segn la alineacin general. El ejemplo que sigue muestra el diseo de campos de bits y el resultado del relleno y alineacin. La estructura mystruct contiene 6 campos de bits de tres tipos diferentes: int, long y char: struct mystruct { int Uno : 8; unsigned int Dos : 16; unsigned long Tres : 8; long Cuatro : 16; long Cinco : 16; char Seis : 4; }; Los campos Uno y Dos deben ser empaquetados en un rea de 32 bits (ver esquema ).

A continuacin, en caso necesario, el compilador inserta un relleno en base a la alineacin general y la del siguiente tipo (dado que el tipo cambia entre los campos Dos y Tres). En este caso, la alineacin general es (doble palabra por defecto), -a4 = 32 bits, con lo que se inserta un relleno de 8 bits (si la alineacin por defecto hubiese sido -a1 no hubiese sido necesario). A continuacin se empaquetan las variables Tres, Cuatro y Cinco, que a efectos son del mismo tipo long, pero no caben juntas en un rea de 32 bits; necesitan (8 + 16 + 16) 40 bits como mnimo, que es ms que los 32 bits permitidos para el tipo long ( 2.2.4). Para empezar una nueva rea para el campo Cinco, el compilador debe insertar un relleno de 8 bits (que no hubiese sido necesario si la alineacin hubiese sido tipo byte -media palabra). Al llegar al campo Seis el tipo cambia de nuevo. Puesto que los tipo char son siempre de alineacin tipo byte, no se necesita relleno para alinearla despus del campo Cinco. Llegados al final, la totalidad de la estructura diseada debe ajustarse a la alineacin global ( doble palabra), de forma que para ajustar a mltiplos de 32 bits se insertan 12 bits de relleno (el relleno hubiesen sido 4 si se hubiese utilizado una alineacin global de media palabra).

6.1 Esquema:

|--------- doble-palabra ------||--------- doble-palabra ------||-------- doble-palabra ------| 1234567:1234567:1234567:1234567:1234567:1234567:1234567:1234567:1234567: 1234567:1234567:1234567: --Uno---======Dos=======........--Tres-====Cuatro======........=====Cinco======Seis............ Como queda reseado, el tamao total de mystruct es de 9 bytes usando alineacin de palabra, y de 12 con alineacin de doble palabra

6.2 Para obtener los mejores resultados utilizando campos de bits debe procurar: Agrupar los campos por tipo (juntos los del mismo tipo). Asegurarse que estn empaquetados dentro de sus reas, ordenndolos de forma que ningn campo tenga que saltar los lmites de un rea. Asegurarse que la estructura est tan rellena como sea posible. Forzar alineacin de media palabra (byte) mediante la directiva #pragma option -a1. Si se desea conocer el tamao de la estructura, puede utilizarse la directiva #pragma sizeof(mystruct), que proporciona el tamao.

7 Usar campos de un bit con signo Los valores posibles para campos con signo (signed) de un bit son 0 o 1. Para tipo sin singo (unsigned) de un bit son 0 o 1. Observe que si asigna 1 a un campo signed de un bit, el valor puede ser evaluado como -1 (uno negativo). Por las razones expuestas, cuando se almacenan valores verdadero y falso en un campo signed de un bit, no es posible comprobar utilizando la igualdad con true, porque los valores almacenados (0 y -1) no son compatibles con las constantes predefinidas true y false ( 3.2.1b); es mejor chequear la desigualdad con cero. Para los campos sin signo (unsigned) de todos los tipos, incluyendo los booleanos, la verificacin de igualdad con true funciona correctamente. Por consiguiente: struct mystruct { int flag : 1; } M; int prueba() { M.flag = true; if (M.flag == true) printf("- Cierto -"); } El ejemplo anterior no funcionara adecuadamente. Sin embargo: struct mystruct { int flag : 1; } M;

int prueba() { M.flag = true; if (M.flag) printf("- Cierto -"); } S funcionara en la forma esperada. Recuerde que: "cualquier valor cero, puntero nulo o puntero a miembro de clase nulo, se convierte a false. Cualquier otro valor se convierte a true". Por consiguiente, la expresin (M.flag) se evala a false si es cero; cualquier otro valor resultar true.

8 Observaciones sobre compatibilidad La informacin que acompaa al compilador Borlanc C++ advierte que la alineacin utilizada por defecto puede variar entre versiones sucesivas del compilador, y que tambin puede cambiar por motivos de compatibilidad con otros compiladores. Que pueden producirse cambios en las alineaciones de los campos de bits, por lo que no se puede garantizar que se mantengan de forma consistente entre versiones diferentes del compilador, y que para comprobar la compatibilidad del cdigo, deben incluirse verificaciones que comprueben el tamao de la estructura con el esperado. La especificaciones del Estndar C++ sealan que la alineacin y almacenamiento de los campos de bits son dependientes de la aplicacin, por lo que diferentes compiladores pueden alinearlos y almacenarlos de forma distinta. Si se desea un total control sobre el diseo de los campos de bits, lo mejor es crear los campos particulares y escribir las propias rutinas de acceso.

Anda mungkin juga menyukai