Вы находитесь на странице: 1из 55

8 Asignaciones compuestas con operadores de bits Salvo el complemento a uno (~) que es unitario, los dems operadores de menejo

de bits pueden combinarse con la asignacin simple (=) para dar lugar a una asignacin compuesta ( 4.9.2). Recuerde que: x x x x x &= y; ^= y; |= y; <<= y; >>= y; // // // // // equivale equivale equivale equivale equivale a: a: a: a: a: x x x x x = = = = = (x (x (x (x (x & y); ^ y); | y); << y); >> y);

Ejemplo #include <iostream.h> int main() { signed int x = x &= -2; y ^= -2; z |= 13; a <<= 1; b >>= 1; cout << "Valor cout << "Valor cout << "Valor cout << "Valor cout << "Valor } Salida: Valor Valor Valor Valor Valor x y z a b = = = = = 2 -7 15 4 -1 2, y = 7, z = 6, a = 2, b= -2;

x y z a b

= = = = =

" " " " "

<< << << << <<

x y z a b

<< << << << <<

endl; endl; endl; endl; endl;

8.1 El Estndar C++ permite una representacin explcita (mediante palabra clave) para alguna de estas asignaciones compuesta ( 4.9.8). Son las siguientes: &= and_eq |= or_eq ^= xor_eq Ejemplo: x and_eq -2; y xor_eq -2; z or_eq 13; // equivale a: x &= -2; // equivale a: y ^= -2; // equivale a: z |= 13;

Comentario: en el cuadro se muestra un cuadro sinptico con los resultados de aplicar los operadores AND, XOR y OR entre dos enteros caractersticos (los valores 0 y 1):

E1 0 1 0 1

E2 E1&E2 E1^E2 E1 | E2 0 0 0 0 0 0 1 1 1 0 1 1 1 1 0 1

9 En ocasiones los operadores de bits se utilizan para compactar la informacin, logrando que un tipo bsico (por ejemplo un long) almacene magnitudes ms pequeas mediante aprovechamientos parciales de los bits disponibles ( 2.2.4). Considere el significado de las siguientes macros ( 4.9.10b) utilizadas por el compilador MS Visual C++ que expresan valores de color en programas MS Windows: #define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16))) #define CMYK(c,m,y,k) ((COLORREF)((((BYTE)(k)|((WORD)((BYTE)(y))<<8))| (((DWORD)(BYTE)(m))<<16))|(((DWORD)(BYTE)(c))<<24)) ) Los valores compactados pueden ser restituidos de nuevo mediante las manipulaciones adecuadas: #define GetRValue(rgb) ((BYTE)(rgb)) #define GetGValue(rgb) ((BYTE)(((WORD)(rgb)) >> 8)) #define GetBValue(rgb) ((BYTE)((rgb)>>16)) #define #define #define #define GetKValue(cmyk) GetYValue(cmyk) GetMValue(cmyk) GetCValue(cmyk) ((BYTE)(cmyk)) ((BYTE)((cmyk)>> 8)) ((BYTE)((cmyk)>>16)) ((BYTE)((cmyk)>>24))

Comentario Algunas de las etiquetas utilizadas (como COLORREF, DWORD o BYTE) son a su vez typedefs ( 3.2.1a), muy comunes en la programacin para los entornos Windows ( Ejemplo). Las expresiones del tipo (WORD)(w) son expresiones de modelado de tipos ( 4.9.9).

Observe la codificacin RGB en la que el color est representado por sus tres componentes [2] Rojo (Red), verde (Green) y azul (Blue). Est claro que los valores de cada componente pueden ocupar un mximo de 8 bits en la expresin resultante, de forma que pueden estar comprendidos entre 0 y 256 ( 0.1). En la codificacin CMYK el color est representado por cuatro componentes: Cian (Cyan), magenta (Magenta), amarillo (Yellow) y negro (black). Los valores de cada componente pueden estar igualmente comprendidos entre 0 y 256, aunque el valor "resultante" es sensiblemente mayor

que en la codificacin RGB. En ambos casos el resultado es modelado de forma que produzca un COLORREF, que en dicho compilador corresponde a ununsigned long, cuyo tamao es de 4 bytes ( 4.9.13). Cuando una constante de este tipo est representada en hexadecimal, adopta la siguiente forma: COLORREF ulColor = 0x00bbggrr; donde bb, gg y rr son respectivamente las componentes azul, verde y roja del color representado. Naturalmente estos valores estn en el rango 00 - FF.

8.1 Ejemplo #include <iostream> using namespace std; typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned long DWORD; typedef DWORD COLORREF; #define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16))) #define GetRValue(rgb) ((BYTE)(rgb)) #define GetGValue(rgb) ((BYTE)(((WORD)(rgb)) >> 8)) #define GetBValue(rgb) ((BYTE)((rgb)>>16)) int main() { // ================= int r = 10, g = 20, b = 30; unsigned long rgbColor = RGB(r, g, b); cout << "Color compuesto: " << rgbColor << endl; unsigned int rColor, gColor, bColor; rColor = GetRValue(rgbColor); gColor = GetGValue(rgbColor); bColor = GetBValue(rgbColor); cout << "Rojo: " << rColor << endl; cout << "Verde: " << gColor << endl; cout << "Azul: " << bColor << endl; } Salida: Color compuesto: 1971210 Rojo: 10 Verde: 20 Azul: 30

4.9.3a Operadores de bits: tecnicismos y ejemplos adicionales


Nota: para comprobar los resultados que se indican y completar sus propios ejemplos, puede utilizar el programa de anlisis de patrn de bits de la Librera de Ejemplos ( 9.4).

1 Introduccin

An suponiendo que el lector no vaya a dedicarse a esto de la programacin y lo haga solo espordicamente, es ms que probable que antes o despus tenga que habrselas con el manejo de bits individuales. Eso sin contar con que su utilizacin es constante en determinados entornos. Por ejemplo, cuando se programan sistemas embebidos ("Embedded Systems"); interfaces analgico-digitales; sistemas de comunicaciones; de adquisicin de datos, Etc. Incluimos aqu algunos consejos, trucos y tecnicismos que le ayudarn a familiarizarse con el manejo de bits, algo por lo dems bastante sencillo cuando se dominan un par de trucos bsicos. Nota: en el lenguaje informtico es frecuente utilizar la palabra "setear" (del ingls "set") para indicar que uno, o todos los bits de una palabra, se ponen a uno, y limpiar ("Clear") para sealar que se ponen a cero. Aunque no est recogido en el diccionario de la Academia Espaola de la Lengua, utilizamos este anglicismo (setear) por ser ms breve que "poner a uno".

2 Construir un patrn de bits Es muy frecuente que las manipulaciones de bits requieran la construccin de un patrn determinado ("bitmask"), que sirva como plantilla para comparaciones u operando de una expresin. Por supuesto, la forma ms directa es escoger un nmero de longitud adecuada y echar mano de nuestros conocimientos de lgebra binaria para calcular el valor correspondiente del patrn deseado. Por ejemplo, necesitamos una plantilla de 16 bits con el siguiente aspecto: 11011000 00010000 Podemos "sacar la cuenta" y llegar a la conclusin de que nuestra declaracin para el nmero debe ser: unsigne int X = 55312; Cuando el patrn es ms largo, 32 o 64 bits, el clculo puede ser muy tedioso. Por ejemplo: 11111010 01011000 11011100 01100110 En estos casos no es necesario pasar el resto de la tarde para encontrar que corresponde exactamente con el unsigned long 4200127590. Una alternativa es calcular el valor individual de cada octeto y componer el resultado utilizando el operador OR inclusivo y los desplazamientos adecuados. En nuestro caso, el valor de los octetos (de izquierda a derecha) es: 250, 88, 220 y 102. El nmero X correspondiente a esa plantilla de bits es el siguiente: unsigned long X = (250UL << 24) | (88UL << 16) | (220UL << 8) | 102UL;

En la tabla adjunta se muestran algunos patrones de bits de uso frecuente.

Constante
(unsigned char) 0x1 (unsigned char) 0x2

Patrn de bits 00000001 00000010

(unsigned char) 0x3 (unsigned char) 0x4 (unsigned char) 0x7 (unsigned char) 0x8 (unsigned char) 0xF (unsigned char) 0x10 (unsigned char) 0x20 (unsigned char) 0x40 (unsigned char) 0x80 (unsigned short) 0xF

00000011 00000100 00000111 00001000 00001111 00010000 00100000 01000000 10000000 00000000 00001111 00000000 00000000 00000000 00001111 11111111 00000000 11111111 00000000 00000000 00000000 11111111 11111111 00000000 00000000 00000000 11111111 00000000 00000000 11111111 00000000 00000000 11111111 00000000 00000000 00000000

(unsigned int) 0xF

(unsigned char) 0xFF (unsigned short) 0xFF

(unsigned int) 0xFF

(unsigned short) (0xFF << 8)

(unsigned int) (0xFF << 8)

(unsigned int) (0xFF << 16)

(unsigned int) (0xFF << 24)

(unsigned short) 0xFFF (unsigned short) 0xFFFF

00001111 11111111 11111111 11111111 00000000 00000000 00001111 11111111 00000000 00000000 11111111 11111111 00000000 11111111 11111111 11111111 11111111 00000000 11111111 11111111 11111111 11111111 00000000 11111111

(unsigned int) 0xFFF

(unsigned int) 0xFFFF

(unsigned int) (0xFF | 0xFF << 8 | 0xFF << 16) (unsigned int) (0xFF | 0xFF << 8 | 0xFF << 24) (unsigned int) (0xFF | 0xFF << 16 | 0xFF << 24)

(unsigned int) (0xFF << 8 | 0xFF << 16 | 0xFF 11111111 11111111 11111111 << 24) 00000000

3 Invertir una zona de un patrn Supongamos que tenemos un patrn arbitrario de cualquier longitud. Por ejemplo 32 bits y queremos obtener un patrn igual al anterior, con la condicin de que sea igual al original en una zona, y que el resto sea precisamente el inverso (los bits que estn a 1 pasan a ser 0 y viceversa). Para concretarlo en un ejemplo, supongamos que en un momento dado, una tarjeta de adquisicin presenta en uno de sus registros un valor X de 32 bits con el siguiente aspecto: 11101110 01111001 11101001 01101001 Queremos obtener el nmero cuyo patrn es igual que el anterior en el primer octeto; el resto sern los inversos de los originales. Es decir, el nmero Y correspondiente a: 11101110 10000110 00010110 10010110 Para obtener el resultado, construimos una mscara M que tenga los bits a respetar iguales a cero y el resto (los que cambiarn de valor) en uno. En nuestro caso sera 00000000 11111111 11111111 11111111 El valor de Y puede obtenerse mediante el OR exclusivo entre el valor original y la mscara:

Y = X ^ M; En nuestro caso: unsigned int Y = X ^ (0xFF | 0xFF << 8 | 0xFF << 16); Teniendo en cuenta que en el ejemplo es X == 4000967017 puede hacer la comprobacin pertinente; lo mismo con cualquier otro valor de 32 bits que se utilice como valor X de entrada.

4 Aislar una zona de un patrn El caso es anlogo al anterior, pero ahora queremos que los bits de la zona que no se conserva sean puestos a cero. En el caso del valor Xanterior, queremos que sean puestos a cero todos los bits, excepto los del primer octeto, as que el resultado Y debe tener el siguiente aspecto: 11101110 00000000 00000000 00000000 El valor Y deseado puede obtenerse mediante la expresin: Y = (X ^ M) & X; donde M es la mscara del ejemplo anterior (tiene a 1 los bits a proteger y a 0 los bits a modificar). En el ejemplo propuesto el valor Y responde a la expresin siguiente: unsigned int Y = (X ^ (0xFF | 0xFF << 8 | 0xFF << 16)) & X;

Si por el contrario, queremos que el resultado Y contenga unos, en vez de ceros, en la zona que no se conserva: 11101110 11111111 11111111 11111111 La expresin a utilizar sera: unsigned int Y = (X ^ (0xFF | 0xFF << 8 | 0xFF << 16)) & (~X);

5 Setear los valores de una mscara en un patrn El problema se plantea en los siguientes trminos: sean dos mscaras M y P; queremos una mscara resultante R igual al patrn M, con la salvedad de que los bits que estuvieran seteados en la mscara P (a 1), lo estn tambin en R. El resultado puede obtenerse mediante: R = M | P; Si lo que queremos es modificar M en el sentido indicado, la expresin es: M |= P;

6 Limpiar los valores de una mscara en un patrn

El problema es inverso al anterior: queremos que la resultante R sea igual a la mscara M, con la salvedad de que los bits que estuvieran seteados en P, sean limpiados (puestos a 0) en R.

R = M & (~P); Si lo que queremos es modificar M en el sentido indicado, la expresin es: M &= ~P;

7 Comprobar si un patrn encaja en una mscara El problema consiste en verificar si los bits seteados (que estn a 1) en un patrn P tienen correspondencia con los bits correspondientes de una patrn M (tambin estn a 1 en M). La verificacin cierto/falso, viene determinado por la expresin (M & P). Por ejemplo: if ( M & P) ... else ... // Encaja // No encaja

En las aplicaciones reales, es frecuente que los patrones y las mscaras estn identificados mediante constantes cuyos valores se incluyen en "defines". Por ejemplo, en la programacin Windows, el estilo de una ventana est definido por un valor entero en el que cada uno de sus bits tiene un significado y efecto concreto. El programador puede componer un estilo particular combinando los componentes individuales que estn definidos mediante constantes. Por ejemplo: DWORD style = WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL | ES_NOHIDESEL,

Como indicbamos en la pgina anterior, DWORD es un typedef cuya traduccin exacta depende de la plataforma, pero que probablemente ser traducida a un unsigned long. A su vez, las constantes a la derecha (WS_CHILD | WS_VISIBLE | WS_VSCROLL, etc.) que determinan los distintos componentes del estilo, estn definidas en los ficheros de cabecera de forma que son DWORDs cuyos valores tienen un bit a uno y los dems a cero, por lo que son mltiplos de 2 (dentro de la serie 1, 2, 4, 8, 16, 32, 64, etc). Al programador no le interesan sus valores concretos, solo sus nombres, que por lo dems son bastante descriptivos. Algunas caractersticas del estilo pueden ser modificadas en runtime despus de creada la ventana, de forma que son normales trozos de cdigo como el que sigue: DWORD style = GetWindowLong(hwnd, GWL_STYLE); una ventana switch(mode) { // mode depende de una orden /* ... */ case EDIT_UPPER: // orden de texto solo style &= ~ ES_LOWERCASE; style |= ES_UPPERCASE; break; case EDIT_LOWER: // orden de texto solo style &= ~ ES_UPPERCASE; style |= ES_LOWERCASE; break; // se obtiene el estilo de de usuario en maysculas

en minsculas

case EDIT_NONE: // orden contenido de ventana no editable style &= ~(ES_UPPERCASE | ES_LOWERCASE); break; } SetWindowLong(hwnd, GWL_STYLE, style); ventana // establecer nuevo estilo de

8 Simplificacin de expresiones lgicas Es frecuente que en la programacin de interfaces analgico/digitales; tarjetas de adquisicin de datos; dispositivos embebidos; sistemas de comunicacin y dems parafernalia electrnica del tipo citado en la introduccin de este captulo, se utilicen expresiones lgicas que pueden resultar bastante complicadas. De forma similar a cmo en una expresin del tipo a = b + 2c + 1.5b -4c -3 podemos simplificar y poner finalmente que a = 2.5b -2c -3, el lgebra lgica permite utilizar ciertas identidades que pueden ayudarnos a la simplificacin de las expresiones resultantes. En la tabla adjunta se muestran algunas de estas reglas [ 1]. !(!x) == x x | x == x x | !x == true !x | !y == !(x & y) // !x & !y == !(x | y) // x & x == x x & !x == false x | y == y | x // x & y == y & x // (x | y) | z == x | (y | x) // (x & y) & z == x & (y & z) // x & y | x & z == x & (y | z) // (x | y) & (x | x) == x | (y & z) // x | x & y == x x & y | x & !y == x (x & y) | (!x & z) | (y & z) == (x & (x | y) & (!x | z) & (y | z) == (x | x & (x | y) == x (x | y) & (x | !y) == x

Teorema de DeMorgan Teorema de DeMorgan propiedad propiedad propiedad propiedad propiedad propiedad conmutativa Conmutativa Asociativa Asociativa Distributiva Distributiva

y) | (!x & z) y) & (!x | z)

4.9.5 Operador Coma


1 Sinopsis En C++ la coma puede ser un elemento de puntuacin (separador de expresiones un operador, dando lugar a las denominadasexpresiones con coma ( 4.10.5). 3.2.6) y

2 Sintaxis expresion , expresion-de-asignacion

3 Comentario La coma separa elementos en las listas de parmetros de las funciones, tambin se usa como un operador en las expresiones con coma. Es legal mezclar ambos usos, pero deben usarse parntesis para distinguirlas y evitar ambigedades. Por ejemplo, la expresin: func(i, (j = 1, j + 4), k); llama a func con tres argumentos: (i, 5, k), no cuatro. Cuando la coma se usa como operador, por ejemplo: E1, E2, el operando de la izquierda E1 es evaluado como una expresin void (no produce ningn resultado), despus se evala la expresin E2 de la derecha, su valor y tipo son los que toma la expresin de coma. Por recursin, la expresin: E1, E2, ..., En, produce la evaluacin de izquierda a derecha de todas las expresiones Ei y la expresin total adopta el valor deEn. Es el operador de precedencia ( 4.9.0a) ms baja de todos.

4 Ejemplos sum = (i = 3, i++, i++); // sum = 4, i = 5 func((exp1, exp2), (exp3, exp4, exp5)); // llama a func con dos argumentos return pass ? (puts("Acierto!"), 0) : (puts("Fallo!"), 1); La invocacin a func en la segunda lnea se realiza con dos argumentos, los resultados de exp2 y exp5 respectivamente. En la tercera lnea, el programa devuelve 0 o 1 segn el valor pass, pero previamente indica el resultado en pantalla, ya que las dos alternativas conducen a la ejecucin de una expresin con coma. El operador coma debe ser usado con moderacin y es frecuentemente utilizado en los bucles for como en el ejemplo. void reverse (char s[]) { // invertir la cadena s int c, i, j; for ( i = 0 , j = strlen(s)-1 ; i < j ; i++, j--) { c = s[i], s[i] = s[j], s[j] = c; } } Como ejercicio, intente el lector desentraar la lgica de funcionamiento de la funcin reverse, que recibe una matriz de caracteres y la invierte. Como ayuda, tenga en cuenta que el argumento <[incremento]> del bucle for ( 4.10.3), es una expresin con coma. Como complemento, intente figurarse como se utiliza la cadena resultante si la funcin se define como devolviendo void.

Observe que esta posibilidad, incluir expresiones con coma en el argumento <[incremento]> de los bucles, permite combinaciones muy interesantes y una notacin muy compacta, aunque resultan algo crpticas en una primera lectura.

4.9.6 Operador Condicional


1 Sinopsis El operador condicional es el nico operador ternario de la gramtica C++ y sirve para tomar decisiones. Proporciona un resultado entre dos posibilidades en funcin de una condicin. Nota: Puede afirmarse que este operador ha hecho fortuna, ya que existe con la misma sintaxis e idntico comportamiento, en multitud de otros lenguajes de programacin.

2 Sintaxis expresion-relacional ? expr1 : expr2

3 Comentario El operador condicional ? : produce un resultado. En la expresin E1 ? E2 : E3, E1 es una expresin relacional ( 4.9.12) que se evala primero. Si el resultado es cierto, entonces se evala E2 y este es el resultado. En caso contrario (si E1 resulta falso), entonces se evala E3 y este es el resultado. Observe que si la premisa E1 es cierta, entonces no llega a evaluarse la expresin E3.

3.1 El operador ? : puede usarse para sustituir ciertas sentencias del tipo if-then-else, aunque puede conducir a expresiones ms compactas que las correspondientes if...else. En el ejemplo que sigue, a y se le asigna el valor 100: x = 10; y = x > 9 ? 100 : 200; 3.2 No es necesario poner parntesis en la primera expresin (E1), ya que la precedencia ( 4.9.0a) de ? : es muy baja (justamente sobre la asignacin = ). De todos modos, es aconsejable ponerlos por legibilidad. y = (x > 9)? 100 : 200;

3.3 En caso de que E1 no sea una expresin relacional, debe ser un escalar reducible a un booleano (Conversin de tipos 3.2.1b). Por ejemplo es vlido: int y = 6 ? 7: 8; aunque en este caso el resultado sera siempre y == 7 (el int 6 se se traduce en el booleano true).

3.4 En ocasiones se aprovechan los efectos laterales ( resultado. Ejemplo: ++x ? ++y : --z;

4.9) del operador para producir un

El compilador GNU cpp permite la ausencia del segundo operando en este tipo de expresiones. Por ejemplo, es vlido: x ? : z; // E1

El resultado de esta expresin es el valor de x si este es distinto de cero, y z en caso contrario. Por consiguiente, es equivalente a: x ? x : z; // E2

El manual informa que este tipo de expresiones solo son de utilidad en caso de que la evaluacin de x tenga efectos laterales (por ejemplo, que sea utilizada como argumento en una funcin), en cuyo caso la expresin E2 tendra efecto lateral doble six es distinto de cero. La omisin del segundo operador en E1 evitara esta computacin indeseada y dejara el valor previamente computado sin que se produzca un doble efecto.

4 E2 y E3 deben seguir las reglas siguientes: 1. Si E2 y E3 son de tipos distintos, se realiza una conversin de tipo estndar, de forma que el resultado ser siempre del mismo tipo, con independencia de E1. 2. Si E2 y E3 son de tipos unin o estructuras compatibles. El resultado es una unin o estructura del tipo de E2 y E3. 3. Si E2 y E3 son de tipo void, el resultado es void. 4. Ambos operandos son punteros a versiones cualificadas o no cualificadas de tipos compatibles. El resultado es un puntero a cualquiera de los tipos de ambos operandos. 5. Un operando es un puntero y el otro es un puntero nulo. El resultado es un puntero que puede sealar a un tipo del primero o del segundo operando. 6. Un operando es un puntero a un objeto o tipo incompleto, y el otro es un puntero a una versin cualificada o no cualificada de void. El tipo resultante es el del puntero distinto de void.

5 Ejemplos 5.1 Suponiendo que z e y sean Lvalues, las siguientes expresiones son equivalentes: (x ? y : z) = 10; (x ? y = 10 : (z = 10));

5.2 El bucle que sigue imprime n elementos de una matriz, 10 por lnea, con cada columna separada por un espacio y con cada lnea terminada por un NL (nueva lnea), incluida la ltima. for (i = 0; i < n; i++) printf ("%6d%c", a[i], (i%10==9 || i==n-1) ? '\n' : ' ');

5.3 Ejemplo: printf ( "Tienes %d item%s.\n", n, n==1 ? "" : "s");

5.4 Ejemplo: #include <iostream.h> #include <time.h> int main(void) { // ============ time_t t; time(&t); struct tm* petm = localtime(&t); long dgt = _timezone/60; cout << "Diferencia hora local con Greenwich = " << abs(dgt) << " minutos " << (dgt == 0 ? "\n" : (dgt < 0 ? "adelanto\n" : "atraso\n")); cout << "Horario: " << (petm->tm_isdst ? "Verano" : "Invierno") << endl; } Salida: Diferencia hora local con Greenwich = 60 minutos adelanto Horario: Verano

4.9.8 Operadores lgicos


1 Sinopsis Los operadores lgicos producen un resultado booleano, ( 3.2.1b), y sus operandos son tambin valores lgicos o asimilables a ellos (los valores numricos son asimilados a cierto o falso segn su valor sea cero o distinto de cero). Por contra, recuerde que las operaciones entre bits ( 4.9.3) producen valores arbitrarios. Los operadores lgicos son tres; dos de ellos son binarios, el ltimo (negacin) es unario. Tienen una doble posibilidad de representacin en el Estndar C++ actual: la representacin tradicional que se indica a continuacin, y la natural introducida recientemente que se detalla ms adelante . Y lgico && AND O lgico || OR Negacin lgica ! NOT

Las expresiones conectadas con los operadores && y || se evalan de izquierda a derecha, y la evaluacin se detiene tan pronto como el resultado verdadero o falso es conocido (muchos programas tienen una lgica que se basa en este propiedad).

2 &&

Operador Y lgico

Tambin denominado por su nombre en ingls (generalmente en maysculas) AND lgico. Devuelve un valor lgico true si ambos operandos son ciertos. En caso contrario el resultado es false. Sintaxis expr-AND-logica && expresion-OR-inclusive Comentario: La operatoria es como sigue: El primer operando (de la izquierda) es convertido a bool. Para ello, si es una expresin, se evala para obtener el resultado (esta computacin puede tener ciertos efectos laterales). A continuacin, el valor obtenido es convertido a bool cierto/falso siguiendo las reglas de conversin estndar ( 3.2.1b). Si el resultado es false, el proceso se detiene y este es el resultado, sin que en este caso sea necesario evaluar la expresin de la derecha (recurdese que en el diseo de C++ prima la velocidad). Si el resultado del operando izquierdo es cierto, se contina con la evaluacin de la expresin de la derecha, que tambin es convertida a bool. Si el nuevo resultado es true, entonces el resultado del operador es true. En caso contrario el resultado es false. Nota: la Norma informa que antes de ser evaluada la expresin derecha, han sucedido todos los posibles efectos laterales de la expresin izquierda, a excepcin de la destruccin de los posibles objetos temporales que se hubiesen creado. Ejemplo: int m[3] = {0,1,2}; int x = 0; if (m && x) cout << "Cierto."; else cout << "Falso."; Salida: Falso. El valor m, que es interpretado como un puntero al primer elemento de la matriz, es transformado a un bool. Como es distinto de cero (no es un puntero nulo) el resultado es cierto. A continuacin, el valor x es convertido tambin a bool. En este caso la conversin produce falso, con lo que este es el resultado del parntesis de la sentencia if. Ejemplo #include <iostream.h> bool alto = true, bajo = false, blanco = true, negro = false; int main (void) { if (alto && bajo) { cout << "Uno cierto" << endl; }

else cout << "Uno falso" << endl; if (alto && blanco) { cout << "Dos cierto" << endl; } else cout << "Dos falso" << endl; if (bajo && negro) { cout << "Tres cierto" << endl; } else cout << "Tres falso" << endl; } Salida: Uno falso Dos cierto Tres falso

3 ||

Operador O lgico

Este operador binario devuelve true si alguno de los operandos es cierto. En caso contrario devuelve false. Sintaxis expr-OR-logica || expresion-AND-logica Comentario Este operador sigue un funcionamiento anlogo al anterior. El primer operando (izquierdo) es convertido a bool. Para ello, si es una expresin, se evala para obtener el resultado (esta computacin puede tener ciertos efectos laterales). A continuacin el valor obtenido es convertido a boolcierto/falso siguiendo las reglas de conversin estndar ( 3.2.1b). Si el resultado es true, el proceso se detiene y este es el resultado, sin que en este caso sea necesario evaluar la expresin de la derecha (recurdese que en el diseo de C++ prima la velocidad). Si el resultado del operando izquierdo es false, se contina con la evaluacin de la expresin de la derecha, que tambin es convertida a bool. Si el nuevo resultado es true, entonces el resultado del operador es true. En caso contrario el resultado es false. Nota: el Estndar establece que antes de ser evaluada la expresin derecha, han sucedido todos los posibles efectos laterales de la expresin izquierda, a excepcin de la destruccin de los posibles objetos temporales que se hubieran creado. Ejemplo #include <iostream.h> bool alto = true, bajo = false, blanco = true, negro = false; int main (void) { if (alto || bajo) { cout << "Uno cierto" << endl; } else cout << "Uno falso" << endl; if (alto || blanco) { cout << "Dos cierto" << endl; } else cout << "Dos falso" << endl; if (bajo || negro) { cout << "Tres cierto" << endl; }

else cout << "Tres falso" << endl; } Salida Uno cierto Dos cierto Tres falso

4 !

Operador NO lgico:

Este operador es denominado tambin negacin lgica y se representa en el texto escrito por la palabra inglesa NOT (otros lenguajes utilizan directamente esta palabra para representar el operador en el cdigo). Sintaxis ! expresion-cast Comentario El operando expresion-cast (que puede ser una expresin que se evala a un resultado es convertido a tipo bool, con lo que solo puede ser uno de los valores cierto/falso. A continuacin el operador cambia su valor: Si es cierto es convertido a falso y viceversa. 1.2.1)

Resulta por tanto, que el resultado de este operador es siempre un tipo bool, aunque al existir una conversin estndar por la que un cero es convertido a false, y cualquier valor distinto de cero a true ( 3.2.1b), coloquialmente se dice que este operador convierte un operando 0 en 1 y uno no-cero en 0. En otras palabras: este operador devuelve cierto (true) si la expresin se evala a distinto de cero, en caso contrario devuelve falso (false). Ejemplo #include <iostream.h> bool alto = true, bajo = false; int main (void) { if (alto) { cout << "Uno cierto" << endl; } else cout << "Uno falso" << endl; if (!alto) { cout << "Dos cierto" << endl; } else cout << "Dos falso" << endl; if (!bajo) { cout << "Tres cierto" << endl; } else cout << "Tres falso" << endl; } Salida: Uno cierto Dos falso Tres cierto

Si E es una expresin, !E es equivalente a (0 == E). Como consecuencia, las expresiones que siguen son equivalentes dos a dos: if (! valid); if (valid == 0); ... if (valid); if (valid != 0);

7 Representacin explcita Los operadores lgicos entre valores lgicos &&, ||, !; la relacin de desigualdad !=; algunos de los operadores lgicos entre bits (&, |, ^, ~) y sus expresiones compuestas (&=, |=, ^=), tienen una representacin realmente difcil de leer, con la desventaja adicional que sus smbolos no siempre estn fcilmente accesibles en ordenadores con teclados distintos del estndar USA. Para resolver este problema, el Estndar C++ ha introducido nuevas formas para su representacin; las denominamos formas explcitas o naturales, en razn de que se parecen ms a las palabras correspondientes del lenguaje natural. Las nuevas formas constituyen palabras-clave, y la tabla de equivalencias es la siguiente:

Palabra clave Smbolo Referencia Descripcin

and or not bitand


xor

&& || ! & ^
4.9.3 4.9.3

Operador Y lgico Operador O lgico Operador negacin lgica Operador AND entre bits Operador OR exclusivo entre bits Operador OR inclusivo entre bits Operador complemento a uno de bits Asignacin compuesta (AND entre bits) Asignacin compuesta (XOR entre bits)

bitor

4.9.3

compl

4.9.3

and_eq

&=

4.9.3

xor_eq

^=

4.9.3

or_eq

|=

4.9.3

Asignacin compuesta (OR entre bits) Operador relacional de desigualdad

not_eq

!=

4.9.12

Nota: ni el compilador Borland C++ 5.5 ni MS VC++ 6.0 soportan esta caracterstica del estndar, aunque el de Microsoft anuncia en su documentacin que pueden utilizarse "defines" ( 4.9.10b). Por ejemplo: #define #define #define #define #define bitand & bitor | and_eq &= or_eq |= not_eq !=

Por su parte, el compilador GNU gcc dispone de la opcin de compilacin -fno-operator-names, que permite que las palabras-clave and,bitand, bitor, compl, not, y or, no sean tratadas como sinnimos de los operadores correspondientes.

Temas relacionados: Trigrafos y digrafos ( 3.2.3e) Sobrecarga de los operadores lgicos (

4.9.18g).

4.9.9 Modelado de tipos


1 Sinopsis: El modelado de tipos ("typecasting") es el proceso de convertir o promover un objeto de un tipo a otro. Esta operacin es necesaria y frecuente en programacin; incluso es realizada infinidad de veces por el compilador de forma automtica y transparente para el programador, y ocurre cuando este la solicita de forma implcita. Por ejemplo, cuando una funcin en cuyo prototipo se ha declarado que espera un float como argumento y le pasamos un entero, o cuando necesitamos sumar un int con un double. En todos estos casos, el compilador realiza automticamente una conversin de tipo para que el argumento pasado concuerde con el esperado ( 4.4.6). En el caso de operaciones aritmticas, la conversin intenta conseguir la menor prdida de precisin posible en las operaciones ( 2.2.5). En otros casos, que tratamos en este captulo, la necesidad de modelado es indicada en el cdigo de forma explcita, utilizando los recursos que proporciona el lenguaje con este propsito.

2 Precauciones:

No olvidar que, cuando forzamos una conversin de tipo explcita o implcita, estamos quebrantando deliberadamente uno de los sistemas de seguridad de C++, basado precisamente en la comprobacin de tipos. El modelado puede ser causa de problemas, en especial (aunque no exclusivamente) cuando se trata de punteros. Por tanto, se recomienda usarlo con moderacin y solo para resolver necesidades puntuales. No obstante lo anterior, es evidente que determinadas conversiones, como las que realiza el compilador automticamente con tipos numricos, no presentan a veces ningn problema; todo lo ms prdidas de precisin. Sobre todo cuando la conversin se realiza en el sentido de nmero con ms precisin a los de menos. Por ejemplo, el compilador puede realizar automticamente una conversin de tipo para realizar las asignaciones: short s = 33; long n = s; float f = s; sin que exista prdida de precisin, puesto que long y float pueden albergar a todos los short ( 2.2.4). Este tipo de promocin se denominaconversin ensanchante. Sin embargo, las conversiones en el sentido del tipo de ms precisin al de menos ( conversin estrechante), puede resultar en una prdida de informacin o incluso en errores peligrosos. Considere la salida del ejemplo siguiente: #include <iostream.h> int main() { unsigned long ul = 5000; long l = ul; int y = ul; short s = ul; cout << "UL = " << ul << endl; cout << "L = " << l << endl; cout << "Y = " << y << endl; cout << "S = " << s << endl; ul += 4E9; l = ul; y = ul; s = ul; cout << "UL = " << ul << endl; cout << "L = " << l << endl; cout << "Y = " << y << endl; cout << "S = " << s << endl; } Salida: UL = 5000 L = 5000 Y = 5000 S = 5000 UL = 4000005000 L = -294962296 Y = -294962296 S = 15240 Tenga en cuenta que la compilacin del programa anterior se produce sin problemas, y sin que exista la ms mnima advertencia de que pueden darse los estrafalarios resultados de la segunda serie de salidas. Puede figurarse lo que le ocurrir a su programa si se dan circunstancias parecidas a las anteriores en un proceso de cierta responsabilidad. Por ejemplo, en el clculo de

un puente; en un monitor de electromedicina; el mezclador en una fbrica de alimentos, o simplemente durante la confeccin de nminas en el programa de administracin de una empresa [2]. Otro ejemplo de conversin estrechante que conduce a un resultado aparentemente contradictorio en 2.2.4a. Tampoco existe ninguna advertencia respecto de la salida obtenida en el ejemplo de promocin de un entero a variable de enumeracin ( 4.9.9b), que fuerza al compilador a proporcionar un resultado evidentemente errneo. En general son potencialmente peligrosas las conversiones de tipo cuando ambos no utilizan el mismo tipo de alineacin interna ( 4.5.9a). Tambin cuando se trata de punteros ( 4.9.9d)

3 Dos tipos Como se ha indicado, en C++ existen dos tipos de modelado: implcito y explcito. El primero es realizado automticamente por el compilador cuando se mezclan tipos. Por ejemplo, las mentadas conversiones numricas, que permiten operaciones aritmticas entre tipos distintos. El segundo se realiza cuando el programador utiliza explcitamente el operador de modelado. Aunque no sea estrictamente necesario, se recomienda como regla de buena prctica, utilizar declaraciones explcitas incluso en los casos de modelado implcito [1]. Sobre todo cuando se quiera poner nfasis en la conversin realizada. Por ejemplo: int x float int y int z = 10; fl = 10.0; = x + fl; = x + static_cast<int>(fl);

// Ok. modelado implcito // Mejor !!

Aparte de que el cdigo resultante es mucho ms explcito, esta prctica facilita la depuracin de posibles errores derivados de la mixtura de tipos.

4 Dos estilos En el C++ Estndar coexisten dos formas de sintaxis para modelado: la nueva y la clsica (heredada de C), aunque esta ltima se desaconseja y est considerada como prctica a extinguir ("Deprecated"). Nota: el compilador GNU gcc dispone de la opcin -Wold-style-cast, que muestra un mensaje de aviso si se utiliza el viejo estilo.

4.1 Estilo C++ de modelado La nueva sintaxis utiliza cuatro palabras clave especficas (static_cast, reinterpret_cast, dynamic_cast y const_cast) y una sintaxis que se presta menos a confusin que la clsica (ver a continuacin). Aparte de la anterior, las razones argumentadas en favor del nuevo estilo es que es menos propenso a obtener resultados indeseados y ms fcil de localizar su ocurrencia en el cdigo.

4.1 Estilo clsico de modelado Es el estilo heredado de C que se mantiene por compatibildad, tiene dos variedades de notacin, aunque el nuevo Estndar C++ lo desaconseja en favor del nuevo estilo. Sintaxis: (<Nombre-de-tipo>) <expresin> <Nombre-de-tipo> (<expresin>) Comentario El valor de <expresin> se convierte al tipo definido por <nombre-de-tipo> siguiendo las reglas estndar de conversin. Por ejemplo, la funcin de la Librera Estndar sqrt() espera un double como argumento, si queremos utilizarla con un int n, se puede utilizar cualquiera de las expresiones siguientes: sqrt((double) n); sqrt(double (n)); // primera forma de la sintaxis // segunda forma de la sintaxis

Observe que n no se altera, ya que la conversin se realiza antes de pasar el argumento a la funcin. Observe tambin que si los parmetros se han especificado en el prototipo de la funcin, esta conversin de argumento no sera necesaria, ya que el compilador realiza automticamente el modelado correspondiente. Las expresiones que siguen son vlidas en C++: double peso = 25; int * ptr; ptr = (int *)&peso; cout << (long)&peso; // para que se muestre en decimal return (int) peso; func (double doble) { return (int) doble+3; }

En ocasiones se puede invertir la colocacin del parntesis (segundo caso de la sintaxis), utilizando entonces una notacin parecida a las funciones: return int (peso); func (double doble) { return int (doble+3); }

El modelado de tipos est estrechamente relacionado con los constructores de clases. En especial la posibilidad de realizar un modelado como: a = A(b); de un objeto b de tipo B a otro tipo A distinto, depende de como est definido el constructor de la clase A. En concreto, debe existir un constructor que acepte un objeto tipo B como nico argumento. Es decir, debe existir un mtodo como:

A::A(B b) { /* asignacin */

} 4.11.2d1)

Ver comentarios y aclaraciones al respecto en: Constructores de conversin (

Tema relacionado Sobrecarga del operador de modelado ( 4.9.18k)

4.9.9a El operador const_cast


1 Sinopsis: La palabra clave const_cast identifica un operador de uso muy especfico: sirve para poner o quitar los atributos const o volatile de un objeto. La sintaxis general es: const_cast< T > (arg) En la expresin anterior, T y arg deben ser del mismo tipo, excepto en los atributos const ( 3.2.1c ) y volatile ( 4.1.9). T es el tipo al que se quiere convertir, arg es el tipo de partida (ver ejemplo). El modelado es resuelto en tiempo de compilacin, y el resultado es del tipo T. Una sola expresin const_cast puede poner o quitar el atributo const o volatile a cualquier nmero de objetos. Ejemplo const int x = 10; int* iptr = &x;

// Error!!

Un intento de compilar las sentencias anteriores conduce a un error del compilador: invalid conversion from `const int*' to `int*' La razn es que se est intentando asignar la direccin de un int-const a un puntero-a-int, cuando se necesitara un puntero-a-int-const. Suponiendo que no queramos (o podamos) cambiar la definicin de iptr, el problema de la asignacin en la segunda lnea puede resolverse mediante un "casting" adecuado: const int x = 23; int* iptr = const_cast<int*> (&x);

// Ok!!

2 cons_cast con punteros El cambio de atributo puede utilizarse con punteros, de forma que un puntero a-tipoXconstante puede ser convertido en puntero a-tipoX y viceversa. El cambio tambin puede realizarse con un puntero a-tipoX-volatile.

El puntero obtenido es idntico al original en todos los dems aspectos. Si la conversin tiene xito el puntero resultante seala al mismo objeto que el original. Ejemplo: const int kte = 35; int* ptr; ptr = &kte; ptr = (int*) &kte; ptr = const_cast<int*> (&kte); volatile int x = 45; ptr = &x; ptr = const_cast<int*> (&x);

// Error! // modelado antiguo desaconsejado // Ok: // Error! // Ok:

As pues, este operador convierte un objeto (o referencia a objeto) const o volatile en un objeto (o referencia) no-const o no-volatile que es idntico en lo dems al original. Es importante advertir que el operador const_cast no cambia el tipo del operando, lo que significa que este operador no hace que una variable constante pueda volverse no-constante y ver alterado su valor despus de aplicado el operador. Considere detenidamente el resultado obtenido en el siguiente ejemplo, e intente llegar a una explicacin (recuerde lo sealado al respecto en 4.2.1e). #include <iostream.h> int main() { const int x = 35; // constante iniciada a 35 int* ptr; // puntero a entero ptr = const_cast<int*> (&x); // Ok: (gracias al 'casting') cout << "Valor x = " << *ptr << endl; *ptr = 100; // Se asigna un valor a x ?!! cout << "Valor x = " << *ptr << endl; cout << "Valor x = " << x << endl; int z = 10 + x; // Comprobacin del valor de x cout << "x + 10 = " << z << endl; } Salida: Valor x = 35 Valor x = 100 Valor x = 35 x + 10 = 45

4.9.9a El operador const_cast


1 Sinopsis: La palabra clave const_cast identifica un operador de uso muy especfico: sirve para poner o quitar los atributos const o volatile de un objeto. La sintaxis general es: const_cast< T > (arg)

En la expresin anterior, T y arg deben ser del mismo tipo, excepto en los atributos const ( 3.2.1c ) y volatile ( 4.1.9). T es el tipo al que se quiere convertir, arg es el tipo de partida (ver ejemplo). El modelado es resuelto en tiempo de compilacin, y el resultado es del tipo T. Una sola expresin const_cast puede poner o quitar el atributo const o volatile a cualquier nmero de objetos. Ejemplo const int x = 10; int* iptr = &x;

// Error!!

Un intento de compilar las sentencias anteriores conduce a un error del compilador: invalid conversion from `const int*' to `int*' La razn es que se est intentando asignar la direccin de un int-const a un puntero-a-int, cuando se necesitara un puntero-a-int-const. Suponiendo que no queramos (o podamos) cambiar la definicin de iptr, el problema de la asignacin en la segunda lnea puede resolverse mediante un "casting" adecuado: const int x = 23; int* iptr = const_cast<int*> (&x);

// Ok!!

2 cons_cast con punteros El cambio de atributo puede utilizarse con punteros, de forma que un puntero a-tipoXconstante puede ser convertido en puntero a-tipoX y viceversa. El cambio tambin puede realizarse con un puntero a-tipoX-volatile. El puntero obtenido es idntico al original en todos los dems aspectos. Si la conversin tiene xito el puntero resultante seala al mismo objeto que el original. Ejemplo: const int kte = 35; int* ptr; ptr = &kte; ptr = (int*) &kte; ptr = const_cast<int*> (&kte); volatile int x = 45; ptr = &x; ptr = const_cast<int*> (&x);

// Error! // modelado antiguo desaconsejado // Ok: // Error! // Ok:

As pues, este operador convierte un objeto (o referencia a objeto) const o volatile en un objeto (o referencia) no-const o no-volatile que es idntico en lo dems al original. Es importante advertir que el operador const_cast no cambia el tipo del operando, lo que significa que este operador no hace que una variable constante pueda volverse no-constante y ver alterado su valor despus de aplicado el operador. Considere detenidamente el resultado obtenido en el siguiente ejemplo, e intente llegar a una explicacin (recuerde lo sealado al respecto en 4.2.1e).

#include <iostream.h> int main() { const int x = 35; // constante iniciada a 35 int* ptr; // puntero a entero ptr = const_cast<int*> (&x); // Ok: (gracias al 'casting') cout << "Valor x = " << *ptr << endl; *ptr = 100; // Se asigna un valor a x ?!! cout << "Valor x = " << *ptr << endl; cout << "Valor x = " << x << endl; int z = 10 + x; // Comprobacin del valor de x cout << "x + 10 = " << z << endl; } Salida: Valor x = 35 Valor x = 100 Valor x = 35 x + 10 = 45

4.9.9a El operador const_cast


1 Sinopsis: La palabra clave const_cast identifica un operador de uso muy especfico: sirve para poner o quitar los atributos const o volatile de un objeto. La sintaxis general es: const_cast< T > (arg) En la expresin anterior, T y arg deben ser del mismo tipo, excepto en los atributos const ( 3.2.1c ) y volatile ( 4.1.9). T es el tipo al que se quiere convertir, arg es el tipo de partida (ver ejemplo). El modelado es resuelto en tiempo de compilacin, y el resultado es del tipo T. Una sola expresin const_cast puede poner o quitar el atributo const o volatile a cualquier nmero de objetos. Ejemplo const int x = 10; int* iptr = &x;

// Error!!

Un intento de compilar las sentencias anteriores conduce a un error del compilador: invalid conversion from `const int*' to `int*' La razn es que se est intentando asignar la direccin de un int-const a un puntero-a-int, cuando se necesitara un puntero-a-int-const. Suponiendo que no queramos (o podamos) cambiar la definicin de iptr, el problema de la asignacin en la segunda lnea puede resolverse mediante un "casting" adecuado:

const int x = 23; int* iptr = const_cast<int*> (&x);

// Ok!!

2 cons_cast con punteros El cambio de atributo puede utilizarse con punteros, de forma que un puntero a-tipoXconstante puede ser convertido en puntero a-tipoX y viceversa. El cambio tambin puede realizarse con un puntero a-tipoX-volatile. El puntero obtenido es idntico al original en todos los dems aspectos. Si la conversin tiene xito el puntero resultante seala al mismo objeto que el original. Ejemplo: const int kte = 35; int* ptr; ptr = &kte; ptr = (int*) &kte; ptr = const_cast<int*> (&kte); volatile int x = 45; ptr = &x; ptr = const_cast<int*> (&x);

// Error! // modelado antiguo desaconsejado // Ok: // Error! // Ok:

As pues, este operador convierte un objeto (o referencia a objeto) const o volatile en un objeto (o referencia) no-const o no-volatile que es idntico en lo dems al original. Es importante advertir que el operador const_cast no cambia el tipo del operando, lo que significa que este operador no hace que una variable constante pueda volverse no-constante y ver alterado su valor despus de aplicado el operador. Considere detenidamente el resultado obtenido en el siguiente ejemplo, e intente llegar a una explicacin (recuerde lo sealado al respecto en 4.2.1e). #include <iostream.h> int main() { const int x = 35; // constante iniciada a 35 int* ptr; // puntero a entero ptr = const_cast<int*> (&x); // Ok: (gracias al 'casting') cout << "Valor x = " << *ptr << endl; *ptr = 100; // Se asigna un valor a x ?!! cout << "Valor x = " << *ptr << endl; cout << "Valor x = " << x << endl; int z = 10 + x; // Comprobacin del valor de x cout << "x + 10 = " << z << endl; } Salida: Valor x = 35 Valor x = 100 Valor x = 35 x + 10 = 45

4.9.9a El operador const_cast


1 Sinopsis: La palabra clave const_cast identifica un operador de uso muy especfico: sirve para poner o quitar los atributos const o volatile de un objeto. La sintaxis general es: const_cast< T > (arg) En la expresin anterior, T y arg deben ser del mismo tipo, excepto en los atributos const ( 3.2.1c ) y volatile ( 4.1.9). T es el tipo al que se quiere convertir, arg es el tipo de partida (ver ejemplo). El modelado es resuelto en tiempo de compilacin, y el resultado es del tipo T. Una sola expresin const_cast puede poner o quitar el atributo const o volatile a cualquier nmero de objetos. Ejemplo const int x = 10; int* iptr = &x;

// Error!!

Un intento de compilar las sentencias anteriores conduce a un error del compilador: invalid conversion from `const int*' to `int*' La razn es que se est intentando asignar la direccin de un int-const a un puntero-a-int, cuando se necesitara un puntero-a-int-const. Suponiendo que no queramos (o podamos) cambiar la definicin de iptr, el problema de la asignacin en la segunda lnea puede resolverse mediante un "casting" adecuado: const int x = 23; int* iptr = const_cast<int*> (&x);

// Ok!!

2 cons_cast con punteros El cambio de atributo puede utilizarse con punteros, de forma que un puntero a-tipoXconstante puede ser convertido en puntero a-tipoX y viceversa. El cambio tambin puede realizarse con un puntero a-tipoX-volatile. El puntero obtenido es idntico al original en todos los dems aspectos. Si la conversin tiene xito el puntero resultante seala al mismo objeto que el original. Ejemplo: const int kte = 35; int* ptr; ptr = &kte; ptr = (int*) &kte; ptr = const_cast<int*> (&kte); volatile int x = 45; ptr = &x; ptr = const_cast<int*> (&x);

// Error! // modelado antiguo desaconsejado // Ok: // Error! // Ok:

As pues, este operador convierte un objeto (o referencia a objeto) const o volatile en un objeto (o referencia) no-const o no-volatile que es idntico en lo dems al original. Es importante advertir que el operador const_cast no cambia el tipo del operando, lo que significa que este operador no hace que una variable constante pueda volverse no-constante y ver alterado su valor despus de aplicado el operador. Considere detenidamente el resultado obtenido en el siguiente ejemplo, e intente llegar a una explicacin (recuerde lo sealado al respecto en 4.2.1e). #include <iostream.h> int main() { const int x = 35; // constante iniciada a 35 int* ptr; // puntero a entero ptr = const_cast<int*> (&x); // Ok: (gracias al 'casting') cout << "Valor x = " << *ptr << endl; *ptr = 100; // Se asigna un valor a x ?!! cout << "Valor x = " << *ptr << endl; cout << "Valor x = " << x << endl; int z = 10 + x; // Comprobacin del valor de x cout << "x + 10 = " << z << endl; } Salida: Valor x = 35 Valor x = 100 Valor x = 35 x + 10 = 45

4.9.9b El operador static_cast


1 Sinopsis Este es el operador de modelado que se recomienda para todas aquellas situaciones en que est perfectamente definido el tipo de conversin deseado. Se recomienda incluso para aquellos casos en que tcnicamente no es necesario un modelado explcito porque el compilador lo realiza automticamente.

2 Sintaxis static_cast<T> (arg) Ejemplo int x = 10; float f = static_cast<float> (x);

4 Descripcin <T> es el tipo al que se convertir (resultado del modelado); puede ser cualquiera de los siguientes: puntero ( 4.2); referencia ( 4.2.3); tipo aritmtico ( 2.2.1) o enumeracin ( 4.7). Este operando se denomina tipo del modelado. arg es el objeto cuyo tipo se desea convertir; debe ser asimilable al tipo de T (aqu no se pueden pedir imposibles); este operando se denominaargumentodel modelado. Ambos operandos, el argumento y el tipo (arg y T), deben estar totalmente definidos en tiempo de compilacin.

5 Si un tipo puede ser convertido en otro mediante algn sistema proporcionado automticamente por el lenguaje, utilizar la promocin explcita mediante este operador conduce exactamente al mismo resultado. Por ejemplo: int x = 10; float f1 = x; // promocin facilitada por el compilador float f2 = static_cast<float> (x); // promicin explcita (preferible)

6 Los enteros (int) pueden ser convertidos a enumeraciones (enum), pero tenga muy presente que cualquier intento de promover el argumento a un valor que no sea un elemento de la enumeracin (enumerando), proporciona un resultado indefinido. Considere las dos salidas del ejemplo compilado con C++Builder: #include <iostream.h> int main() { enum COLOR { ROJO, VERDE, AZUL}; COLOR c1 = VERDE; cout << "C1 es color " << c1 << endl; int x = 2; // c1 = x; !! Aviso de asignacin anmala: int a COLOR c1 = static_cast<COLOR> (x); // Ok. promocin correcta cout << "C1 es color " << c1 << endl; COLOR c2; int y = 5; c2 = static_cast<COLOR> (y); // Resultado indefinido (y > AZUL) cout << "C2 es color " << c2 << endl; } Salida: C1 es color 1 C1 es color 2 C2 es color 5 Observe que en este ltimo caso, el compilador hace adoptar a c2 un valor 5 que es tericamente imposible para el tipo COLOR, lo que podra dar lugar a errores importantes.

7 Conversin de punteros

El puntero nulo (

4.2.1) es convertido a s mismo.

El puntero a un objeto tipoX puede ser promovido a puntero a cualquier otro tipoY, pero tenga en cuenta que la mera promocin entre tipos de punteros puede ser motivo de problemas si los tipos equivalentes no estn alineados de forma similar. El resultado de la conversin esttica de un puntero es otra referencia al objeto original. Es posible convertir explcitamente, mediante una conversin esttica, un puntero a clase-X a puntero a una clase-Y, si se dan las siguientes condiciones: X es una clase base para Y. Existe una conversin sin ambigedades de Y a X X no es una clase-base virtual

Un objeto puede ser convertido explcitamente a referencia-a-tipoX si un puntero a tal objeto puede ser explcitamente convertido a puntero-a-tipoX (tipoX*). El resultado de la conversin es un Lvalue. No son invocados constructores o funciones de conversin como resultado de un modelado a una referencia. Un objeto o un valor puede ser convertido a un objeto clase, solo en el caso de que se hayan declarado en la clase un constructor o un operador de conversin adecuados. Un puntero a miembro (de clase) puede ser convertido explcitamente en puntero a otro tipo de miembro solo si ambos tipos son punteros a miembros de la misma clase, o punteros a miembros de dos clases, una de las cuales es derivada sin ambigedades de la otra.

8 Convertir a referencia Cuando el tipo T es una referencia, el resultado del modelado esttico es un Lvalue y se refiere a la expresin original, aunque este tipo de transformaciones puede resultar peligroso ( 4.2.3).

4.9.9c El operador dynamic_cast


Nota: El operador dynamic_cast se refiere a conversiones de punteros y refencias a clases, por lo que su estudio exige un buen conocimiento previo de muchos conceptos relacionados con ellas. Recomendamos al estudiante continuar con el estudio de las clases ( 4.11) antes de volver a este operador concreto.

1 Sinopsis Como se ha indicado, este operador est reservado para conversiones de/hacia punteros y referencias a clases, aunque exige que los punteros y referencias se refieran a clases de la misma jerarqua. De no cumplirse este condicin, la conversin es imposible y segn los casos, el operador produce un resultado nulo o lanza una excepcin. Para entender los conceptos involucrados se hacen necesarias algunas puntualizaciones semnticas previas; recordemos que cuando se trata de clases emparentadas (pertenecientes a una jerarqua), hay que distinguir los casos en que las conversiones ("cast") entre punteros o

referencias se realizan en sentido descendente ("Down") desde las clases-base hacia las derivadas, o ascendente ("Up"), desde las clases-derivada hacia las superclases [1]. El primero es el sentido desde los antepasados a los descendientes; el segundo desde los descendientes a los ancestros. Las conversiones en sentido descendente se denominan downcast, y las contrarias upcast. Finalmente, cuando la conversin se da entre clases hermanas, se denomina crosscast (modelado de cruce) [2]. 2 Sintaxis dynamic_cast<T> (arg);

3 Comentario El operando <T>, denominado tipo, determina el resultado de la operacin, y debe ser un puntero ( 4.2.1f) o referencia ( 4.2.3) a una clase definida, o un puntero genrico void* ( 4.2.1d). El operando arg recibe el nombre de argumento, es el tipo que se quiere convertir, y debe ser una expresin que se resuelva en un puntero o una referencia (este operador no se puede aplicar a otros tipos). El resultado del operador es un puntero o referencia del mismo valor que arg, apuntando o referenciando al mismo objeto, pero del tipo expresado por <T>. Este operador se utiliza principalmente para realizar asignaciones que de otro modo no seran legales por diferencia de tipos entre el Lvalue y el Rvalue. Para utilizar este operador es necesaria la capacidad RTTI (Runtime type identification 4.9.14).; As pues, los ejemplos en los que aparezca el operador dynamic_cast deben ser compilados con la opcin -RT [4]

4 Ejemplos class Clase1 { ... } c1, *c1ptr; class Clase2 { ... } c2, *c2ptr = &c2, &ref2 = c2; ... c1ptr = dynamic_cast<Clase1*>(c2ptr); // L.4: Clase1& ref1 = dynamic_cast<Clase1&>(ref2); // L.5:

5 C++ exige que el donwcast o crosscast se realicen desde una clase polimrfica ( 4.11.8), de modo que arg debe ser de este tipo [3]. Sin embargo, el resultado de este modelado puede ser aplicado a un puntero o referencia a clase no polimrfica. En el ejemplo anterior, si Clase2 no fuese polimrfica (no tuviese una funcin virtual), en L4 y L.5 se obtendran sendos errores: Type 'Clase2' is not a defined class with virtual functions in... pero Clase1 no tiene que serlo necesariamente para poder realizar las asignaciones anteriores.

6 Si <T> es un puntero genrico void*, el argumento arg debe ser tambin un puntero. En este caso, el puntero resultante puede acceder a cualquier elemento de la clase que sea el elemento ms derivado de la jerarqua. Tal clase no puede ser clase-base para ninguna otra.

7 Las conversiones de una clase derivada a clase-base (upcast) o de una clase derivada a otra (crosscast), se realiza como sigue: si T es un puntero y ptr es un puntero a una clase no-bsica de una jerarqua, el resultado es un puntero a la subclase nica. Las referencias son tratadas de forma similar: si T es una referencia y ptr es una referencia a una clase no bsica, el resultado es una referencia a la subclase nica. 8 La convesin upcast, de una clase derivada a una clase-base se resuelve en tiempo de compilacin. Por contra, la conversin downcast, de una clase-base a clase-derivada, o a travs de una jerarqua de clases, es resuelta en tiempo de ejecucin. Es decir: a.- Conversiones en tiempo de compilacin: subclase* superclase* subclase& superclase& b.- Conversiones en tiempo de ejecucin: subclase* superclase* subclase& superclase& El resultado es que, en caso de haberlos, los errores de los modelados a. sern anunciados por el compilador. En cambio, los del tipo b. solo sern detectados en ejecucin.

9 Si en la conversin de un puntero, el operador dynamic_cast< T > (ptr) tiene xito, proporciona un puntero sealando al mismo objeto que el argumento ptr, pero del tipo T requerido. En cambio, si la conversin falla, el puntero obtenido es del tipo T deseado, pero se le asigna cero. En otras palabras, si el modelado de un puntero falla, se obtiene el puntero nulo ( 4.2.1). Por contra, si falla la conversin a una referencia, se lanza la excepcin bad_cast. Lo anterior significa que los modelados de este tipo con punteros deben ser comprobados siempre explcitamente para verificar que ptr != 0 (ver lneas 16 y 22 del ejemplo ). En cambio, para verificar el xito del modelado de referencias basta el mecanismo de excepciones ( 1.6).

10 Ejemplo En el programa que sigue se realizan modelados a travs de toda la jerarqua. Primero de realiza el downcast de un puntero desde la clase base a la ms derivada, despus se realiza un upcast para volver a otra base superior. #include <iostream.h> #include <typeinfo.h> class Base1 { virtual void f(void) { /* ... */ } // L.5: }; class Base2 { };

class Derived : public Base1, public Base2 { }; int main(void) { // ========================== try { Derived d, *pd; Base1* b1 = &d; // L.14: pd = dynamic_cast<Derived*>(b1); // L.15: if (pd != 0) { cout << "El puntero pd es tipo: " << typeid(pd).name() << endl; } else throw bad_cast(); // L.19: Base2* b2; b2 = dynamic_cast<Base2*>(b1); // L.21: if (b2 != 0) { cout << "El puntero b2 es tipo: " << typeid(b2).name() << endl; } else throw bad_cast(); // L.25: } catch (bad_cast) { // L.27: cout << "Falla el modelado dynamic_cast" << endl; return 1; } catch (...) { // L.31: cout << "Error en el mecanismo de excepciones" << endl; return 1; } return 0; // L.35: } Salida: El puntero pd es tipo: Derived * El puntero b2 es tipo: Base2 * Comentario En L.5 declaramos una funcin virtual en la superclase Base1, dado que para que funcione correctamente el mecanismo RTTI, la clase base debe ser polimrfica. En L.13 creamos una instancia d de la clase Derived y un puntero pd a dicha clase. En L.14 a un puntero-a-clase-base (b1) le asignamos la direccin de una instancia (objeto) de clase derivada. Esto es perfectamente lcito ( 4.2.1f) En L.15 al puntero pd (a clase derivada) se le asigna la direccin direccin sealada por b1. La direccin de b1 es correcta (la direccin de una subclase), pero el tipo no lo es, ya que b1 es de tipo Base1*. Para poder formalizar la asignacin es necesario realizar previamente un downcast, de forma que b1 pase a ser tipo Derived*. Las lneas L.16 a L.19 comprueban que el modelado ha tenido xito. En caso contrario lanzan una excepcin bad_cast que es capturada en L.27. Observe que en L.31 se captura cualquier excepcin del bloque try precedente, que no haya sido capturada con anterioridad.

En L.21, a b2 (que es tipo Base2*) se le asigna la direccin sealada por b1. En esta asignacin ni la direccin ni el tipo son correctos. En primer lugar, b1 seala a un objeto tipo Derived. Adems, b1 es tipo Base1*. En consecuencia, se necesita un modelado al tipo de b2. Este modelado debe recorrer toda la jerarqua de clases, descendiendo (downcast) desde el tipo Base1* a la del tipo ms derivado Derived*; despus ascendiendo (upcast) desde el tipo derived* al tipo Base2*. En L.35 se anuncia la finalizacin normal del programa. En cambio, las terminaciones L.29 y L.33 son terminaciones anormales, por lo que devuelven un valor distinto de cero al SO.

11 Comprobacin de parentesco Dentro de las limitaciones sealadas (que se realice sobre clases polimrficas), la circunstancia de que el operador dynamic_cast solo funcione entre miembros de una jerarqua, es utilizado a menudo como un recurso para comprobar si dos objetos pertenecen o no a una misma familia. Por ejemplo, supongamos que tenemos un objeto a pertenecientes a la clases A. Para comprobar si pertenece a una subclase de B (si A deriva deB), podra utilizarse el siguiente cdigo: if (dynamic_cast<B*>(&a)) { /* A es derivada de B */ } else { /* A no deriva de B */ }

4.9.9d Operador reinterpret_cast


1 Sinopsis Las conversiones de tipo que se realizan con este operador podramos decir que son por la "fuerza bruta". Obligamos al compilador a aceptar un tipo de objeto por otro sin rechistar, por muy ilgica que sea la transformacin (de ah el nombre). Ni que decir tiene que tales transformaciones son de lo ms peligroso y que deben ser realizadas con extrema precaucin. En realidad se suelen utilizar solo de forma temporal, para realizar determinadas transformaciones en los objetos [1] y volver a interpretarlos en su sentido original. Aunque en ocasiones est perfectamente justificado, su utilizacin puede ser sntoma de una tcnica deficiente o de que el programador se est metiendo en problemas. 2 Sintaxis reinterpret_cast<T> (arg)

3 Descripcin El operando <T> (denominado tipo), determina el resultado de la conversin. Puede ser cualquiera de los tipos siguientes: puntero ( 4.2); referencia ( 4.2.3); tipo aritmtico ( 2.2.1); puntero a funcin ( 4.2.4), o puntero a miembro de clase ( 4.2.1g).

Es recomendable abandonar la antigua sintaxis (tipoX)expr, usando en su lugar el nuevo operador reinterpret_cast<tipoX>(expr) en todas aquellas conversiones que no sean seguras, o que sean dependientes de la implementacin.

4 Ejemplo #include <iostream.h> int main() { int x = 10; float f = 20.5; int* ptx = &x; float* ptf = &f; cout cout cout cout << << << << "ptr-a-X "ptr-a-F "Valor X "Valor F = = = = " " " " // ============= // puntero-a-entero // puntero-a-float << << << << ptx << endl; ptf << endl; *ptx << endl; *ptf << endl;

ptx = ptf; // L.14: Error !! ptx = reinterpret_cast<int*> (ptf); // L.15: Ok. cout << "ptr-a-F = " << ptx << endl; cout << "Valor F = " << *ptx << endl; } Salida (despus de eliminada la sentencia errnea): ptr-a-X ptr-a-F Valor X Valor F ptr-a-F Valor F = = = = = = 0065FE00 0065FDFC 10 20.5 0065FDFC 1101266944

Comentario Las cuatro primeras salidas del ejemplo son una simple comprobacin. Obtenemos las posiciones de memoria (hexadecimales) sealadas por ambos punteros, as como los valores (decimales) de las variables apuntadas. En L.14 comprobamos como no es posible asignar un puntero-a-float a un puntero-a-int. El compilador avisa que no es posible realizar el "casting" implcito mediante un mensaje de error: Cannot convert 'float *' to 'int *' in function main(). En L.15 realizamos la asignacin sin novedad despus de forzar un modelado con el operador reinterpret_cast adecuado para estos casos. A partir de este momento, ptx adopta el valor sealado por ptf. Las dos ltimas salidas nos muestran que la direccin sigue siendo correcta. ptx seala la misma direccin que ptf, pero el valor obtenido para fes errneo, evidenciando as los peligros potenciales de este tipo de modelado.

La razn del error es obvia: el compilador interpreta el patrn de bits alojado a partir de la direccin 0065FDFC como un tipo entero, cuando en realidad corresponde a un float, cuyo modelo de almacenamiento es totalmente distinto ( 2.2.4a). Bajo la ptica de un entero es el valor 1101266944, mientras que interpretado como float es 20.5 Ejemplo relativo a la conversin de tipo de un puntero a clase (

Ejemplo).

5 Un puntero puede ser convertido explcitamente a un tipo numrico entero. Lo cual es lgico, dado que los punteros almacenan direcciones de memoria, que son magnitudes escalares enteras. Recprocamente, un argumento arg entero puede ser convertido a un puntero. La conversin de un puntero en un entero y posterior conversin a puntero del tipo original, conduce al valor original. Ejemplo: #include <iostream.h> int main() { // ================== int x, y; float f = 20.5; float* ptf = &f; // puntero-a-float cout << "ptr-a-F = " << ptf << endl; cout << "Valor F = " << *ptf << endl; x = reinterpret_cast<int> (ptf); ptf = reinterpret_cast<float*> (x); cout << "ptr-a-F = " << ptf << endl; cout << "Valor F = " << *ptf << endl; } Salida: ptr-a-F Valor F ptr-a-F Valor F = = = = 0065FE00 20.5 0065FE00 20.5 // convertimos a entero // reconversin a tipo original

6 En la conversin de un puntero o una referencia puede utilizarse una clase que an no haya sido definida. Ejemplo: #include <iostream.h> class C; // L.2: declaracin adelantada C* cptr; class D {public: unsigned short us;}; // L.4: D* dptr; int main() { // ============ D d = D(43); // L.8 dptr = &d; // L.9 cout << "d.us = " << dptr->us << endl; cptr = reinterpret_cast<C*>(dptr); // L.12:

class ::C { public: char ch; }; // L.14: C c = { 'a' }; // L.15: cout << "c.ch = " << c.ch << endl; cout << "c.ch = " << cptr->ch << endl; } Salida: d.us = 43 c.ch = a c.ch = + Comentario El ejemplo tiene solo inters acadmico, pero muestra un par de caractersticas interesantes: En L.2 y L.3 declaramos una clase C y un puntero cptr a ella sin estar definida todava. Se trata de una declaracin adelantada ( 4.11.4). En lneas 4 y 5 definimos una nueva clase D y declaramos un puntero dptr. En L.8 se instancia un objeto d de la clase D y es iniciado a un valor concreto. En L.9 iniciamos el puntero dptr al nuevo objeto. En L.10 comprobamos el valor de la nica propiedad us del objeto. Es la primera salida, que nos muestra el valor (43) esperado. En L.12 asignamos dptr a cptr mediante un modelado adecuado. Observe que este modelado a tipo C*, se realiza sin que la clase haya sido definida todava, cosa que hacemos en L.14. Observe que en esta sentencia de definicin de la clase C, utilizamos el operador de acceso a mbito:: (

4.9.19). Sin l, estaramos definiendo una nueva clase C, local a main. en vez de la declarada en el mbito global.
En L.15 instanciamos un objeto c de la clase y lo iniciamos a un valor concreto. Las dos ltimas lneas son de comprobacin del valor de la nica propiedad ch, del nuevo objeto. En la salida en que utilizamos el selector directo de miembro . ( 4.9.16) obtenemos el valor (a) esperado, pero en la ltima, en la que utilizamos el selector indirecto ->, obtenemos un valor (+) errneo. La razn es que estamos accediendo a la variable ch mediante el puntero dptr "disfrazado" de puntero-a-C. Justamente seala un valor 43, que si es considerado como carcter, es el '+' que obtenemos en la ltima salida.

7 Un puntero a funcin ( 4.2.4) puede ser convertido explcitamente a puntero a objeto ( 4.2.1), suponiendo que el puntero a objeto tenga suficientes bits para alojar el puntero a funcin. Recprocamente, un puntero a objeto puede ser convertido en puntero a funcin solamente en el caso de que este ltimo sea suficientemente grande para alojar el puntero a objeto.

8 En el ejemplo que sigue, mostramos el modelado de un puntero-a-funcin de un tipo, a

puntero-a-funcin de otro tipo, y como estas transformaciones conducen fcilmente a resultados errneos. #include <iostream.h> void func(void* v) { int x = reinterpret_cast<int>(v); cout << "X = " << x << endl; } void main() { func(reinterpret_cast<void*>(5)); typedef void (* PFV)(); PFV pfunc = reinterpret_cast<PFV>(func); pfunc(); } Salida: X = 5 X = 6684208 Comentario L.2: Define func como funcin recibiendo puntero genrico y devolviendo void. L.3: El entero x recibe el valor del argumento pasado a la funcin despus de un modelado adecuado. L.7: Se invoca func pasando el valor 5 que previamente ha recibido un modelado adecuado al tipo de argumento que espera la funcin. La primera salida, proporcionada por la funcin es correcta. El entero 5 ha sido transformado a puntero-a-void y despus vuelto a entero, con lo que recobra su valor. L.8: Declara PFV puntero-a-funcin que no recibe argumentos devolviendo void. L.9: Define pfunc como puntero-a-funcin que no recibe argumentos devolviendo void, y lo inicia a la funcin func. Como esta no corresponde con el tipo del puntero, se realiza un modelado previo a la asignacin. L.10: Se invoca func a travs de su puntero. La salida es basura porque la invocacin no proporciona los argumentos adecuados.

// L.2: // L.3:

// // // //

L.7: L.8: L.9: L.10:

4.9.9e El modelado en la programacin Windows


1 Sinopsis La programacin de aplicaciones para los Sistemas Windows de Microsoft presenta algunas singularidades propias; desde el punto de vista del programador C/C++ (idiomas para los que est pensada la API de Windows 1.7.2), cabra destacar la utilizacin abundantsima de constantes manifiestas; de typedef para definir nombres alternativos de los tipos bsicos, y del modelado explcito de tipos.

Como hemos comentado en el apartado correspondiente ( 3.2.1a1), la principal razn de su uso es la portabilidad del cdigo, ya que existen versiones de estos sistemas para mquinas de 16, 32 y 64 bits. Amn de sucesivas versiones de los mismos (W98, XP, NT, 2000 etc), as como versiones "light", como Windows ME, para dispositivos mviles; telfonos, PDAs, Handhelds, etc. En ocasiones las funciones de la API devuelven tipos que son dependientes del contexto, por lo que al utilizarlas, debemos asegurarnos de emplear el modelado adecuado y de que este sea lo ms especfico posible. En casos extremos podemos encontrar incluso modelados sucesivos en una misma expresin. Como botn de muestra, incluimos un ejemplo de utilizacin propuesto en una publicacin de Microsoft [1]. HBRUSH hbr; hbr = (HBRUSH)(UINT)SendMessage(hwnd, WM_CTLCOLOR, ..., ...); Tenga en cuenta que el tipo HBRUSH es puntero a un tipo especial de estructura; que WM_CTCOLOR es un tipo numrico, y que UINT es tambin un tipo numrico (probablemente unsigned int). El documento seala que al utilizar este tipo de funciones, como SendMessage, antes de realizar el modelado al tipo necesario (HBRUSH en este caso), se debe realizar un modelado del valor devuelto a UINT. Esto es necesario para que el cdigo se portable, ya que el tamao de un puntero ("handle") puede ser 16 o 32 bits, dependiendo de la versin de Windows, y el modelado (UINT) asegura que la conversin sea correcta (a la longitud adecuada).

4.9.10 Operador de preproceso


1 Sinopsis Durante la primera fase de la compilacin de un programa, el preprocesador ( 1.4.1) realiza ciertas modificaciones previas en el cdigo fuente. Se trata de una autntica traduccin del cdigo siguiendo unas pautas explicitadas en las denominadas directivas de preproceso [1]. Son sentencias que se sitan normalmente al principio del cdigo fuente, aunque legalmente pueden aparecer en cualquier punto de la unidad de compilacin (tienen validez desde el punto de aparicin hasta el final del fichero). Por la razn anterior, se dice que estas directivas no respetan el mbito, por lo que (en especial los #define) pueden presentar efectos colaterales no deseados en puntos alejados de los que el programador ha previsto. 2 Las directivas de preproceso se identifican por ser lneas precedidas por el smbolo # que es el operador de preproceso. A menos que # est incluido en una cadena alfanumrica o en un comentario, indica que lo que sigue es una directiva de preproceso o lnea de control. # puede estar precedido o seguido por un separador (whitespace) excluyendo nueva lnea (NL).

Las directivas de preprocesado no necesitan punto y coma ( ; ) al final. Ejemplo: #include <iostream>; #include <iostream> // L.1: // L.2: permisible mejor!!

Nota: la lnea 1, aunque permisible, puede dar un aviso de compilacin con algunos compiladores. Concretamente el compilador Borland C++ 5.5 compila sin ningn aviso, mientras que MS Visual C++ 6.0 muestra un "Warning" ( 1.4): Unexpected tokens following preprocessor directive - expected a newline... Evidentemente el token al que se refiere es el punto y coma final, que es innecesario.

3 El Estndar C++ soporta las directivas que se indican a continuacin; todas ellas tienen un sentido definido. La ltima, #pragma, se ha previsto para que cada fabricante de compilador pueda inventar las suyas particulares sin que interfieran con las de otras implementaciones (si un compilador encuentra una de estas directivas y no sabe que hacer con ella, sencillamente la ignora). El sentido de las dems se indica en los epgrafes correspondientes. # (directiva nula) #define #error #if, #elif, #else, #endif #ifdef, #ifndef #import #include #line #undef #pragma

4 # Directiva nula La directiva nula consiste en una lnea con un solo carcter #. Este lnea es ignorada totalmente.

Directiva #define
1 Sinopsis La directiva #define define una macro. Las macros proporcionan un mecanismo de reemplazo de tokens ( 3.2) con o sin una serie de parmetros formales (parecidos a las funciones). Esta similitud con las funciones hace que en ocasiones sirvan para una especie de sustitucininline ( 4.4.6b), aunque esta prctica presenta sus riesgos .

2 Sintaxis #define macro_identificador <secuencia-de-tokens>

3 Comentario

Cada ocurrencia del macro_identificador en el cdigo fuente es reemplazado en su misma posicin por secuencia-de-tokens, que puede estar incluso vaco #define HOLA "Que tengas buen da!" Cada vez que el macro identificador HOLA aparezca en el fuente ser sustituido por la cadena sealada (hay algunas excepciones que se comentan a continuacin). Esta sustitucin es denominada macro-expansin, y la secuencia-de-tokens se denomina cuerpo de la macro. Es tradicin en C/C++ que los macro-identificadores sean expresados en maysculas: #define adios "espero verte pronto" #define ADIOS "espero verte pronto" // no es muy "C" // Mejor!! . Por ejemplo:

3.1 Recuerde que las directivas de preprocesador no terminan en punto y coma ;. Por ejemplo: #define T int; ... T* ptr = new T [10];

// Error !!

La sentencia anterior no es traducida a: int* ptr = new int [10]; como podra suponerse. Sino a: int;* ptr = new int; [10];

3.2 La accin de la macro comienza en el punto de su definicin hasta el final del fichero , sin embargo esta asociacin entre el cuerpo de la macro (secuencia-de-tokens) y la etiqueta (macro_identificador) es reversible. Es decir, se puede hacer desaparecer en cualquier punto (#undef 4.9.10j). Adems, despus que un macro_identificador ha sido indefinido, puede ser redefinido con #define, usando el mismo cuerpo de macro u otro distinto [2]. Ejemplo: #include <iostream.h> int main() { // ============= int x = 10; cout << "Valor x == " << x << endl; #define x 13 cout << "Valor x == " << x << endl; #undef x cout << "Valor x == " << x << endl; } Salida:

Valor x == 10 Valor x == 13 Valor x == 10

los nombres definidos en las macros no estn sujetos a las reglas de visibilidad de los subespacios de nombres ( 4.1.11), lo que las hace altamente peligrosas, ya que el mecanismo de sustitucin de tokens "arrasa" a lo largo de todo el cdigo, efectuando cuantas sustituciones sean congruentes con su definicin. A veces en sitios donde no pensbamos que lo hara y que, de otro modo, estaran relativamente a salvo de colisiones. Por ejemplo, en el interior de clases. Esta es una de las razones por las que se prefiere utilizar maysculas en los macro_identificadores; para disminuir la posibilidad de concordancias fortuitas. Por ejemplo, considere el siguiente cdigo: ... { #define min -1 #define max 127 A a = foo(a, min, max); // L.m ... B res = result (x, y, min); // L.n } ... /* ms adelante en un punto alejado */ int max = getmax(a, b); // L.k En este caso, el programador ha previsto los #define min y max para mayor legibilidad y facilidad de modificacin del cdigo, como en las sentencias L.m y L.n, pero quizs ms adelante, en L.k utiliza dicho nombre para definir una variable olvidando los #define anteriores y que estos tienen validez desde el punto de su definicin hasta el final, con independencia de que hayan sido incluidos dentro del mbito definido por los corchetes { }. Como resultado de lo anterior, entre otras medidas, se considera buena prctica anular su efecto con #undef ( 4.9.10j) inmediatamente despus de que dejen de ser necesarios. Por ejemplo, la forma correcta del cdigo anterior podra ser: ... { #define min -1 #define max 127 A a = foo(a, min, max); ... B res = result (x, y, min); #undef min #undef max } ... int max = getmax(a, b); Sin embargo, un programador C++ de lite escribira: ... { enum { min = -1, max = 127 }; A a = foo(a, min, max);

... B res = result (x, y, min); } ...

3.3 Cualquier carcter encontrado en el cuerpo de la macro aparecer en la macro-expansin. El cuerpo de la macro termina en el primer carcter de nueva lnea NL (10) que no est precedido de (\). Cualquier secuencia de espacios y/o comentarios en la lnea de control, son reemplazados por un solo carcter de espacio. 3.4 Un cuerpo de macro vaco est permitido. Ejemplo: #define VACIO Su efecto es doble, de forma que puede utilizarse con dos finalidades distintas, segn se trate del cdigo fuente, o de la accin de otros elementos del preprocesador. En lo que respecta a la lgica del preprocesador, si se encuentra una expresin como la anterior, a la que falta la secuencia-de-tokens, el macro_identificador adopta el valor 1, que es cierto (true). Este tipo de expresiones suele utilizarse en conjuncin con la directivas de preproceso #ifdef e #ifndef ( 4.9.10e), que interrogan si un macro identificador est definido (es cierto) o no lo est (es falso). En lo que respecta al cdigo fuente, la presencia de un cuerpo de macro vaco, supone la eliminacin de cada macro-identificador del cdigo fuente, ya que cada ocurrencia de VACIO es sustituido por un nulo.

3.5 Despus de cada macro-expansin se realiza una nueva exploracin del cdigo para ver si existen nuevas expansiones, lo que permite la existencia de expansiones anidadas, ya que el texto despus de una expansin puede contener macro-identificadores que puedan sufrir otra expansin subsiguiente. Si la macro se expande a algo que parezca una directiva de preprocesado, esta no ser reconocida por el preprocesador. Ejemplos #define #define #define #define NIL "" GETSTD #include <stdio.h> forever for (;;); // Loop infinito max(A, B) ((A) > (B) ? (A) : (B))

La ltima directiva origina que una lnea en el fuente tal como: x = max (p+q, r+s); ser transformada por el preprocesador en [3]: x = ((p+q) > (r+s) ? (p+q) : (r+s));

4 Como el lector puede suponer, las caractersticas de los define, combinadas con las directivas de preprocesado condicionales #if y #else ( 4.9.10d), representan un cmulo de posibilidades para el programador.

Ejemplo // # define SP 1 // # define US 1 // # define FR 1 #if SP # define ERRN "Error no recuperable en linea:" #elif US # define ERRN "Unrecoverable Error in line:" #elif FR # define ERRN "Error ne pas recuperable en line:" #endif Para cambiar el lenguaje de los mensajes en que aparezca ERRN en el fuente, solo hay que quitar el comentario a la lnea correspondiente al idioma que queremos. Con este sencillo truco, un solo fuente puede servir para diversas versiones idiomticas del programa [1].

5 Limitaciones La macro-expansin adolece de las siguientes limitaciones: Cualquier ocurrencia del macro-identificador dentro de cadenas alfanumricas, constantes carcter o comentarios en el cdigo fuente son ignorados. Ejemplo: #define pi 3.14159; ... char* ptr = "Mostrar el valor de pi con decimales\n"; cout << ptr << endl; producira la salida: Mostrar el valor de pi con decimales en vez de: Mostrar el valor 3.14159 con decimales Una macro no puede expandirse durante su propia expansin. Por ejemplo: #define A A no se expandir indefinidamente. En la seccin 4.7 se seala como las variables enum pueden reemplazar con ventaja a los "defines", de forma que, como seala el propio Stroustrup, este tipo de variable C++ hace casi innecesario su uso.

6 Inconvenientes

Las macros tienen su sitio y su justificacin, pero tambin sus riesgos e inconvenientes. En general no se aconseja demasiado su uso y menos su abuso, porque dan lugar a un cdigo difcil de leer. Adems, cuando se utiliza como una especie de sustitucin inline ( 4.4.6b) de funciones, presenta un inconveniente importante y es que con la macro no se realiza comprobacin esttica de tipos (uno de los mecanismos de prevencin de errores de C++). Es decir, no existe comprobacin de que el tipo de los parmetros formales coincide con los argumentos actuales ( 4.4.5), lo que puede generar errores difciles de depurar. Adems existe el riesgo de efectos colaterales indeseados, en especial cuando el argumento actual es evaluado ms de una vez. Como ejemplo de lo anterior, considere el siguiente programa que utiliza sucesivamente la funcin cubo y una macro CUBO, para calcular la potencia 3 de una cantidad. int cubo(int x) { return x*x*x; } // funcin

#define CUBO(x) ( (x)* (x) * (x) ) // Macro sustitucin (ver nota ... int b = 0, a = 3; b = cubo(a++); // L.7: clculo mediante funcin a = 3; b = CUBO(a++); // L.9: sustitucin y asignacin posterior

En el primer caso, L.7: el argumento actual pasado a la funcin es 3, de forma que b = 3*3*3 == 27; a continuacin, a es incrementado, con lo que: a == 4. En el segundo caso, L.9: CUBO es sustituido por la macro correspondiente, por lo que la sentencia quedara como: b = ((a++)*(a++)*(a++)); // L.9-bis

el resultado es que b = 3*3*3 == 27, pero despus, a sufre tres incrementos unitarios sucesivos, de forma que finalmente a == 6. Observe que en este tipo de expresiones el parntesis no puede estar separado de la macro: #define CUBO(x) ( (x)* (x) * (x) ) #define CUBO (x) ( (x)* (x) * (x) ) // Ok. // Error!!

7 Las macros en la prctica Como resultado de todo esto, aunque estn ah para usarlas, la mayora de los textos y autores sealan que las macros, otrora bastante populares en C, son un recurso obsoleto que puede y debe ser evitado. No obstante, los ficheros de cabecera de los compiladores suelen estar plagados de macros, en especial para declarar constantes manifiestas ( 1.4.1a). Por ejemplo: #define TABSIZE 100 ... int table[TABSIZE]; Sin embargo, la mayora de las veces los ficheros de cabecera hacen uso de "defines" bastante sofisticadas. Por ejemplo, los que siguen pertenecen al compilador MS Visual C++:

#define #define #define #define #define #define #define #define #define #define #define #define #define #define

MAKEWORD(a, b) ((WORD)(((BYTE)(a)) | ((WORD)((BYTE)(b))) << 8)) MAKELONG(a, b) ((LONG)(((WORD)(a)) | ((DWORD)((WORD)(b))) << 16)) LOWORD(l) ((WORD)(l)) HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF)) LOBYTE(w) ((BYTE)(w)) HIBYTE(w) ((BYTE)(((WORD)(w) >> 8) & 0xFF)) MAXUHALF_PTR 0xffff MAXHALF_PTR 0x7fff MINHALF_PTR 0x8000 HandleToUlong( h ) ((ULONG) (h) ) PtrToUlong( p ) ((ULONG) (p) ) PtrToLong( p ) ((LONG) (p) ) PtrToUshort( p ) ((unsigned short) (p) ) PtrToShort( p ) ((short) (p) )

Comentario Algunas de las etiquetas utilizadas (por ejemplo, LONG, ULONG, DWORD o BYTE) son a su vez typedefs ( 3.2.1a) muy comunes en la programacin de entornos Windows ( Ejemplos). Las expresiones del tipo (WORD)(w) son expresiones de modelado de tipos ( Las expresiones del tipo 0xFFFF y 0xFF son constantes hexadecimales ( 4.9.9).

3.2.3b).

8 Convertir a cadenas con # El smbolo # puede colocarse delante de un argumento formal de la macro para convertir el argumento actual a una cadena despus de la sustitucin (un proceso conocido como "stringification" por los angloparlantes). Por ejemplo, sea la directiva: #define TRACE(flag) printf(#flag "=%d\n", flag) y el cdigo: int highval = 1024; TRACE(highval); Despus del preprocesado se transforma en: int highval = 1024; printf("highval" "=%d\n", highval); que equivale a: int highval = 1024; printf("highval=%d\n", highval); Vemos que en realidad se trata de una doble sustitucin. En primer lugar se sustituye TRACE(...) por printf(#... "=%d\n", ...). Despus se sustituye el literal expresado en el parmetro ...(flag) en todas las ocurrencias donde aparezca, si bien la sustitucin ocurre de dos maneras posibles: como literal, entre comillas dobles, o sin comillas. En

este caso, todas las ocurrencias de #flag son sustituidas por"highval", mientras que flag es sustutituido por highval (sin comillas). Observe que este tipo de "macros" tienen una sintaxis parecida a las funciones y que, en estos casos, no es tan raro ver el identificador en minsculas: #define saludo(...) printf(...) 8.1 Ejemplo Utilizando la directiva #define DPRINT(exp) printf(#exp " = %g\n", exp) la lnea de cdigo: DPRINT(x/y); se transforma en: printf("x/y" " = %g\n", x/y); que equivale a: printf("x/y =%g\n", x/y);

8.2 Ejemplo operativo #include <iostream.h> #define mostrar(contenido) cout << "Resultado " #contenido " == " << ": " << contenido << endl; int main() { // ====================== int ai[] = {2, 3}; int z1 = ai[0] + ai[1]; mostrar(z1); int* pt1 = ai; mostrar(z3); int* pt2 = ai; mostrar(z4); int* pt3 = ai; mostrar(z5); } Salida: Resultado z1 == : 5 Resultado z3 == : 2 int z3 = *pt1; int z4 = *(++pt2); int z5 = *pt3 + *(++pt3);

Resultado z4 == : 3 Resultado z5 == : 6

9 Doble almohadilla ## los caracteres # y ## se usan tambin para realizar sustitucin y asociacin de tokens durante la fase de anlisis del preprocesado. Este par de smbolos se conocen como token adhesivo ("Token paste"), porque permiten pegar o asociar dos tokens situados entre las dos almohadilla ( puede haber espacios opcionales en cada extremo). En estos casos, el preprocesador elimina los espacios y la doble almohadilla, combinando los dos tokens en uno solo. Esto puede usarse para construir identificadores. Por ejemplo, dada la definicin: #define VAR(i, j) i##j la llamada VAR(x, 6) se expande a x6. Nota: este es el sistema nuevo, que reemplaza al antiguo (no estndar) mtodo de utilizar: #define VAR(i, j) (i/**/j) Ejemplo #define WCHAR( X ) L##X #define WCHPT wchar_t* la expresin: WCHPT cptr = WCHAR("Hola mundo"); se transforma en: wchar_t* cptr = L"Hola mundo"; En la pgina adjunta se muestra un ejemplo ms elaborado de uso de esta directiva ( 4.9.10b1).

Directiva #error
1 Sintaxis #error mensaje-de-error

2 Descripcin La directiva #error genera el siguiente mensaje: Error: filename line# : Error directive: mensaje-de-error

Esta directiva se suele incluir en sentencias de preprocesado condicional para detectar alguna condicin indeseada en tiempo de compilacin. Generalmente dicha condicin debe ser falsa, pero si resultara cierta, se desea que el compilador muestre un mensaje de error y el proceso se detenga. Esto puede conseguirse incluyendo una directiva #error dentro de una sentencia condicional que resulte cierta en caso de error.

3 Ejemplo El caso comentado en la directiva define ( indica: #define) podra complementarse de la forma que se

// # define Sp 1 // # define Us 1 // # define Fr 1 #if Sp # define ERRN "Error no recuperable en linea:" #elif Us # define ERRN "Unrecoverable Error in line:" #elif Fr # define ERRN "Error ne pas recuperable en line:" #else # error "Seleccionar alguna de las opciones de lenguaje" #endif

Directivas #if, #elif, #else, #endif


1 Sinopsis C++ ofrece la posibilidad de compilacin condicional mediante la inclusin de ciertas directivas que controlan el comportamiento del preprocesador, de forma que este puede ignorar o compilar determinadas lneas del cdigo en funcin de ciertas condiciones que son evaluadas durante el preproceso.

2 Sintaxis #if k-expresion-1 <seccion-1> <#elif k-expresion-2 <seccion-2> ... <#elif k-expresion-n <seccion-n> <#else <seccion-final> #endif

3 Comentario

Las directivas condicionales #if, #elif, #else y #endif se comportan igual que las sentencias C/C++ de seleccin ( 4.10.2). Si k-expresion-1-sujeta a posible macro-expansines cierta (distinto de cero), las lneas de cdigo de seccion-1, que pueden estar vacas, ser lneas normales de cdigo o incluso de preprocesado, son preprocesadas y pasadas al compilador. Si es falsa (cero), la seccion-1 es ignorada por completo. En el caso de ser cierta, despus que se ha procesado la seccion-1, el control pasa a la correspondiente #endif, con lo que se termina la sentencia condicional y se contina con la seccin siguiente. Si es falsa, el control pasa a la siguiente #elif, donde se evala k-expresion2. Si es cierta, se procesa la seccion-2, tras lo cual el control pasa al correspondiente #endif. En caso contrario, si k-expresion-2 es falsa, el control pasa al siguiente #elif. As sucesivamente hasta que se llega a algn #else o #endif (#else es opcional y se alcanza si todas las comprobaciones previas han sido falsas). Las diferentes secciones pueden contener a su vez otras clusulas condicionales anidadas en cualquier profundidad. Cada #if debe contar con su correspondiente #endif. Puede haber cualquier nmero de #elif, pero solo un #else (que debe ser el ltimo).

4 Las expresiones k-expresion-n deben evaluarse a una constante entera. Es decir, debe ser una expresin constante que se reduzca a un entero, aunque esta expresin puede tener ciertas restricciones ( 3.2.3a). Ejemplo #if SYSTEM == SYSV #define HDR "sysv.h" #elif SYSTEM == BSD #define HDR "bsd.h" #elif SYSTEM == MSDOS #define HDR "msdos.h" #else #define HDR "default.h" #endif #include HDR // incluir la cabecera adecuada. En este caso, se supone que SYSV, BSD y MSDOS son constantes simblicas que en algn punto estn definidas como valores enteros. A su vezSYSTEM es igualmente una constante simblica cuyo valor es un entero.

Nada impide que la expresin condicional sea a su vez una expresin compuesta, tal como se muestra en el siguiente ejemplo tomado de un caso real de programacin Windows. #if defined(__WXGTK__) || defined(__WXMOTIF__) || defined(__WXMAC__) || defined(__WXMGL__) || defined(__WXX11__) #include "mondrian.xpm" #endif En este caso los nombres en maysculas corresponden a constantes manifiestas ( definidas en distintos compiladores C++. Ver ejemplos adicionales en 4.9.10c y 1.4.1a) 1.4.

El ejemplo siguiente muestra unas sentencias de compilacin condicional en funcin del valor de la constante simblica DLEVEL que se supone ha sido definida previamente. #if DLEVEL > 5 #define SIGNAL 1 #if STACKUSE == 1 #define STACK 200 #else #define STACK 100 #endif #else #define SIGNAL 0 #if STACKUSE == 1 #define STACK 100 #else #define STACK 50 #endif #endif #if DLEVEL == 0 #define STACK 0 #elif DLEVEL == 1 #define STACK 100 #elif DLEVEL > 5 display( debugptr ); #else #define STACK 200 #endif El primer bloque #if (primeras 15 lneas), contiene dos series de directivas #if, #else y #endif anidadas en l. La primera se procesa si es cierta la condicin DLEVEL > 5. En caso contrario se procesa la serie situada despus del #else. El resto de lneas del ejemplo se utiliza para establecer el valor de la constante STACK a 0, 100 o 200 en funcin del valor de la constante DLEVEL, pero si su valor es mayor que 5, STACK queda indefinida, y en su lugar se compila una invocacin a la funcin display utilizando debugptrcomo argumento.

5 Operador defined Tanto Borland C++ como MS VC++ disponen de este operador [1] que solo puede ser utilizado en conjuncin con las directivas #if y #elif. Admite dos formas de sintaxis, una de ellas con aspecto de funcin: #if #if #elif #elif defined defined defined defined ( <identificador> ) <identificador> ( <identificador> ) <identificador>

5.1 Descripcin

Este operador devuelve cierto (distinto de cero) si identificador ha sido definido previamente mediante una directiva #define ( 4.9.10b). En caso contrario devuelve falso (0). Como puede verse, cumple la misma funcin que la directiva #ifdef, de forma que las dos expresiones que siguen son equivalentes: #if defined identificador #ifdef identificador Sin embargo, el operador tiene la ventaja de que permite construir expresiones ms complejas en la misma directiva #if o #elif. Por ejemplo: #if defined(identif1) && !defined(identifi2) ... #elif defined(identif1) && defined(identifi2) ... En el ejemplo siguiente, se utiliza el operador para controlar la compilacin de una funcin entre tres posibilidades: #if defined(CLIENTE) cliente(); #elif defined(PROVEEDOR) proveedor(); #else printerror(); #endif Si el identificador CLIENTE ha sido definido, se compila una llamada a la funcin cliente(). En caso contrario, si se ha definido el identificadorPROVEEDOR, se compila una invocacin a la funcin proveedor(); finalmente, en ausencia de alguna de las condiciones anteriores, se invoca la funcin printerror(). Es frecuente que este operador se utilice para formar directivas de guarda ( 4.9.10e)

Directivas #ifdef, #ifndef


1 Sintaxis #ifdef identificador #ifndef identificador

2 Descripcin Las directivas #ifdef y #ifndef son condicionales especializadas para comprobar si un macroidentificador est definido o no. La mecnica es la misma que con #if, #elif, #endif ( 4.9.10d).

Tenga en cuenta que un macro-identificador X se define con #define X y se indefine

con #undef X ( 4.9.10i), con lo que podemos controlar a voluntad la zonas de cdigo en que se considera definido e indefinido. Nota: La existencia semntica del identificador (el hecho de que est definido o no), es independiente del valor concreto que pudiera tener asignado. Como veremos a continuacin, incluso si se le asigna el valor nulo, el identificador se considera definido. Un identificador definido como NULL es considerado definido. Por ejemplo: #define nulo NULL

3 Ejemplo #define isSp // Flag Espaol/Ingls #ifdef isSp # include "ztr--Sp.CH" #define VComP "C.000621Sp" #else # include "ztr--Us.CH" #define VComP "C.000621Us" #endif .... .... #undef isSp // A partir de aqu, isSp se considera indefinido .... ....

4 Directivas de guarda Tenga en cuenta que el lenguaje C++ es muy proclive a que se cometan cierto tipo de errores al trabajar con ficheros de cabecera (es seguro que ocurrir a menudo). Por ejemplo, supongamos que tenemos una clase denominada Hotel que utiliza una clase denominada Suite y que cada una de estas clases utiliza el mismo fichero de cabecera, por ejemplo ZonasHotel.h [2]. En este caso es ms que posible que recibamos un error de compilacin del tipo ... Multiple declaration for 'FILE' ... indicndonos que estamos redefiniendo los smbolos de ZonasHotel.h la segunda vez que la cabecera es incluida. Una solucin es disponer el cdigo como se indica: #ifndef zonasH #include "ZonasHotel.h" #define zonasH // Nota [3] #endif ... // aqu sigue el cdigo del programa Sin embargo, esta disposicin tiene el inconveniente de que tenemos que acordarnos de definir zonasH cada vez que vayamos a incluir la cabecera. Si lo olvidamos, volver a producirse el error (esto es muy probable en ambientes donde trabajan varios programadores en el mismo proyecto).

Una solucin ms elegante, que recomendamos, es utilizar la disposicin anterior dentro del propio fichero de cabecera. En nuestro caso, el fichero ZonasHotel.h tendra el siguiente aspecto: #ifndef zonasH ... contenido del fichero #define zonasH #endif

Si analizan con detenimiento los ficheros de cabecera de su compilador C++, comprobar que las directivas descritas en este epgrafe son utilizadas con profusin, y como a pesar de que estn llenas de declaraciones, la inclusin dos veces de una misma cabecera no produce ningn error de compilacin. La razn es que los fabricantes de compiladores utilizan el truco anterior. Como ejemplo, tomamos la cabecera <stdio.h> del compilador Borland C++ 5.5, que incluye una gran seccin cuyo aspecto esquemtico es el siguiente: ... #ifndef __STDIO_H #define __STDIO_H .... typedef struct { ... } FILE; ... #endif // __STDIO_H ... Si se coloca la directiva #include <stdio.h> en un fuente, la primera vez que el preprocesador pasa por la directiva ifndef, la macro__STDIO_H, que no est definida [1], se define en la siguiente lnea, as como la estructura FILE. La segunda vez que se incluyera esta cabecera, el preprocesador se encontrara con que la macro __STDIO_H ya est definida, por lo que se saltara toda la seccin hasta el endifcorrespondiente. De no haberse hecho as se obtendra el error antes mencionado. Por esta razn, este tipo de directivas condicionales, que se utilizan para que no se produzcan declaraciones mltiples, se denominan directivas de guarda. Nota: el compilador MS VC++ dispone de una pragma ( 4.9.10i) especfica para este fin: #pragma once. Sirve para indicar al compilador que el fichero en que reside dicha directiva, debe ser incluido solo una vez en cada compilacin. Por supuesto esta directiva se coloca dentro del fichero de cabecera que deseamos no se repita. La forma de uso es la siguiente: //Fichero ZonasHotel.h que deseamos no se repita #pragma once ... contenido del fichero

Otra posibilidad para formar directivas de guarda es la utilizacin del operador defined ( 4.9.10d). Por ejemplo, el cdigo que sigue comprueba si la constante simblica ZONASH ha sido definida. Si no lo ha sido, se define para veces sucesivas y se compila el cdigo subsiguiente. En caso contrario (si el cdigo ya ha sido incluido en la compilacin), todo el bloque es ignorado por el compilador.

// ZonasHotel.h

Ejemplo de fichero de cabecera

#if !defined( ZONASH ) #define ZONASH class MiClase { ... }; #endif

Вам также может понравиться