4.6.1 Alineación interna §1 Sinopsis Los ajustes de alineación interna descritos en el apartado anterior ( 4.6), no solo ocurren en los campos de bits; también en otras estructuras de datos. Por ejemplo, en el capítulo correspondiente a Iniciación de estructuras ( 4.5.2) se presentó un caso concreto en el que, debido al proceso de alineación interna realizado por el compilador, el tamaño real de una estructura no coincide con el teórico. Como se indica a continuación, el compilador Borland C++ ofrece varias opciones relativas a la forma en que se alinean internamente los datos en memoria, de forma que el programador puede tener un cierto control sobre esta cuestión, pero la posibilidad no es exclusiva de Borland, por ejemplo, el MS Visual C++ también dispone de una opción equivalente. §2 Alineación interna en Borland C++ El compilador Borland C++ permite establecer como se alinean internamente los datos en memoria mediante un comando en la lista de opciones con que se puede invocar el compilador. Todos tiene la forma general -an, donde n es un entero con las siguientes posibilidades: 1 = byte; 2 = palabra (2 bytes); 4 = Doble palabra (4bytes); 8 = Cuádruple palabra (8 bytes); 16 = Párrafo (16 bytes). Por defecto, en ausencia de otra indicación, se supone -a4, es decir, una alineación de doble palabra (múltiplos de 32 bits). Las modalidades de alineación palabra (word), doble palabra (double word) y cuádruple palabra (quad word) fuerzan a los elementos del tamaño de un entero y mayores ( 2.2.4), a un ajuste en direcciones de memoria que son múltiplos del tipo elegido. En las estructuras se insertan bits extra para garantizar que los miembros se alinean adecuadamente. §3 Alineación de Byte Comando: -a1 o -a-. En este tipo de alineación el compilador no realiza ningún ajuste en el almacenamiento de variables o campos de datos. Los datos se alinean en direcciones pares o impares, dependiendo de la siguiente disponible. Evidentemente este tipo de alineación conduce a programas más compactos en memoria, pero tienden a ser más lentos que los compilados con otras alineaciones, ya que las restantes opciones incrementan la velocidad con que los procesadores Intel 80x86 cargan y restituyen los datos desde/hacia la memoria. §4 Alineación de palabra 2-bytes Comando: -a2. Con este tipo el compilador alinea los datos iguales o mayores que un entero (que no son short ni char) en direcciones pares. Las variables globales y automáticas son alineadas adecuadamente. Los tipos char (y unsigned char) pueden ser situados en cualquier dirección; todos los demás son alojados en direcciones pares. §5 Alineación de doble palabra 4-bytes Comando: -a4 o -a. Con este tipo los datos distintos de carácter se alinean en palabras de 4 bytes (32 bits). Los datos con tipos menores que 4 bytes son alineados en el tamaño correspondiente a su tipo. §6 Alineación de cuádruple palabra 8-bytes Comando: -a8. Con este tipo los datos distintos de carácter se alinean en palabras de 8 bytes (64 bits). Los datos con tipos menores que 8 bytes son alineados en el tamaño correspondiente a su tipo. §7 Alineación de párrafo 16-bytes Comando: -a16. Con la alineación de párrafo tipo los datos distintos de carácter se alinean en palabras de 16 bytes (128 bits). Los datos con tipos menores que 16 bytes son alineados en el tamaño correspondiente a su tipo. 4.7 Uniones §1 Sinopsis Las uniones C++ son un tipo especial de clase; un tipo de variable con cierta similitud con las estructuras ( 4.5). Pueden albergar diferentes tipos de datos, pero solo uno, de entre todos los posibles, al mismo tiempo. Se dice que solo uno puede estar "activo" en cada momento, y se corresponden con los registros de tipo variable de Pascal y Modula-2. Como el lector estará suponiendo, el tamaño de una unión es el del mayor elemento que puede albergar. El valor exacto depende de la implementación y de las alineaciones internas ( 4.5.9a). Como se verá a continuación, desde el punto de vista de su organización interna son como estructuras en las que todos sus miembros tuviesen el mismo desplazamiento respecto al origen. Desde un punto de vista funcional pueden ser considerados almacenamientos u objetos multi-uso. En realidad las uniones son un recurso de programación que permite alojar distintos tipos de objetos en un mismo espacio cuando estos objetos no coexisten en el tiempo y no hay suficiente memoria disponible para asignarles espacios distintos. Se trata por tanto de objetos muy especializados en cuanto a su uso, propio de aplicaciones muy "afinadas", de tiempos en que la memoria era un bien escaso, pero cuya utilización real es bastante rara y desaconsejada. §2 Declaración La sintaxis de declaración es similar a las estructuras con las siguientes diferencias: a: Las uniones pueden contener campos de bits ( 4.5.9), pero solo uno puede estar activo. Todos comienzan en el principio de la unión y como consecuencia, dado que los campos de bits son dependientes de la implementación, pueden presentar problemas para escribir código portable. b: Un objeto que tenga constructor o destructor no puede ser utilizado como miembro de una unión. c: A diferencia de las estructuras, en las uniones C++ no se permiten los especificadores de acceso public, private y protected de las clases ( 4.11.2a). Todos sus campos son públicos [2]. d: Las uniones solo pueden ser inicializadas en su declaración mediante su primer miembro. Ejemplo: union local87 { int i; double d; } a = { 20 }; §2.1 Ejemplo union miUnion { int i; double d; char ch; char *ptr; } mu, *muptr=μ // definición de unión de nombre miUnion // declara un objeto y un puntero al objeto Aquí se ha definido un tipo nuevo de la clase unión, identificado como miUnion; se ha instanciado un objeto de dicha clase, denominado mu, que puede utilizarse para almacenar un int, (4 bytes), un double (8 bytes), o un char (1 byte), pero solo uno cada vez. El uso debe ser consistente con los valores almacenados en cada caso, cuestión esta que queda bajo responsabilidad del programador (esta es la razón principal para que se desaconseje su uso salvo caso de necesidad insoslayable). Si se lee un dato de tipo distinto al que se escribió, el resultado obtenido es dependiente de la implementación. Una unión no puede participar en la jerarquía de clases; no puede ser derivada de ninguna clase, ni ser una clase base. Aunque sí pueden tener un constructor ( 4.11.2d1) y ser miembros de clases. §3 Acceso a miembros Como en el caso de las estructuras, se dispone de dos operadores para referenciar los miembros de las uniones: . Selector directo ( 4.9.16c Selector directo de miembro) Una expresión del tipo Un.obj representa el objeto obj de la unión Un. La expresión es un Lvalue siempre que Un no lo sea y objno sea una matriz. -> Selector indirecto ( 4.9.16d Selector indirecto de miembro) Una expresión del tipo uPtr -> obj representa el objeto obj de la unión Un siempre que uPtr sea un puntero a dicha unión. La expresión es un Lvalue siempre que obj no sea una matriz. Esta expresión es equivalente, y preferible, a (*uPtr).obj. El uso de uno u otro selector es indiferente. Depende de que se tenga un identificador de la unión, en cuyo caso puede usarse el selector directo (expresión Un.obj), o un puntero, en cuyo caso puede usarse el indirecto (expresión uPtr->obj). En cualquier caso, es necesaria cierta atención como se muestra en el ejemplo (referido al objeto mu declarado anteriormente): mu.d = 4.016; printf("mu.d = %f\n",mu.d); printf("mu.i = %d\n",mu.i); mu.ch = 'A'; printf("mu.ch = %c\n",mu.ch); printf("mu.d = %f\n",mu.d); muptr->i = 3; printf("mu.i = %d\n",mu.i); // Se inicia mu //Ok. Salida: mu.d = 4.016 //?? resultado particular //Ok. Salida: mu.ch = A //?? resultado particular //Ok. Salida: mu.i = 3 El segundo printf es legal, dado que mu.i es un entero. El problema es que el patrón de bits que correspondería a mu.i coincide con parte deldouble previamente asignado en la primera sentencia, y su valor no tiene ningún sentido considerado aisladamente. Como se ha señalado, las uniones comparten muchas características con las estructuras, incluyendo la notación. Además, con las uniones están permitidas las mismas operaciones que con las estructuras: asignación o copia como una unidad; obtener su dirección, y acceder a un miembro. §4 Punteros a uniones Cuando son modelados adecuadamente, los punteros a una unión señalan a cada uno de sus miembros y viceversa. §5 Tamaño y alineación Como en el resto de los objetos, su tamaño puede ser establecido en tiempo de ejecución con el operador sizeof ( 4.9.13) y como se ha señalado, corresponde con el mayor de los elementos que pueda almacenar. Lo mismo que en las estructuras, también aquí se producen alineaciones en las direcciones de memoria de los miembros. Por ejemplo, si nos referimos a la unión anterior, las sentencias sizeof(union miUnion) y sizeof(mu), ambas devuelven 8; este es el tamaño en bytes de la unión, que corresponde a la opción más desfavorable, cuando se almacena un double, pero cuando mu almacena un intexisten 4 bytes desaprovechados, y 7 cuando se almacena un char. §6 Uniones anónimas double d. double d. } Salida: El valor de '?. Ejemplo: . Sus miembros pueden ser accedidos directamente en el ámbito de su declaración sin necesidad de usar ninguno de los operadores de acceso x. cout << union { i = 25. }.i' es: " << ::i << endl.6). }. int i. } . En otras palabras: las uniones anónimas no pueden tener enlazado externo ( 1. "El valor de '?.4.i' es: 25 El valor de '?. Si son globales deben ser declaradas estáticas.1 Las uniones anónimas pueden ser globales. i = 25.i' es: 25 §6.i' es: " << i << endl.4).2 Las uniones anónimas no pueden tener funciones miembro ni miembros privados o protegidos (todos son públicos). cout << "El valor de '?.i' es: 15 El valor de '?.h> void main () { union { int i. y sin son locales pueden ser estáticas o automáticas ( 2.2. Ejemplo: #include <iostream.y o p->y. §6.i' es: 15 () { "El valor de '?.i' es: " << i << endl.i' es: " << i << endl. void main i = 15. cout << cout << } Salida: El valor de '?. y anidadas o sin anidar [1]. "El valor de '?. double d.h> static union { int i. Tienen la forma general: union { lista-de-miembros }.Uniones anónimas son aquellas que no tienen etiqueta y tampoco se utilizan para declarar un objeto nominado (con nombre). Ejemplo: #include <iostream. . estructuras y como miembros de clases.y. // ex1 es una instancia de externa return ex1.i' es: " << i << endl.h> union { int i.1 namespace-scope anonymous aggregates must be static §7 Uniones anónimas anidadas La estructura. union { // unión anónima anidada int y. Por ejemplo.c 2: Global anonymous union not static Compilador GNU C++ 3. Para mayor legibilidad del código estos valores del rango se identifican por nemónicos. son un tipo especial de variables que tienen la propiedad de que su rango de valores es un conjunto de constantes enteras [1] denominadas constantes de enumeración.20b). } Resulado de la compilación: Borland C++: Error E2020 pru1. }. }.#include <iostream. double d. LUN.8 Enumeraciones §1 Sinopsis Las variables enumeradas.3g). } §8 Uniones y otros tipos Las uniones pueden aparecer en matrices. int main(void) { struct externa ex1.3. char c. A su vez.x + ex1. 4. SAB } diaX. MART.. clase o unión exterior de una unión anónima anidada debe tener un nombre. MIER.9. también pueden contener a dichos elementos. la declaración: enum dia { DOM. . Por ejemplo: struct externa { // define estructura nominada externa int x. Ver ejemplo ( 4.. C++ permite uniones anónimas anidadas. JUEV. enumeraciones o más abreviadamente enum (palabra reservada). a cada una de las cuales se le asigna un valor ( 3. void main () { cout << "El valor '?. } .2. VIER. LUN. JUEV. En el ejemplo anterior. MIER. NO-APTO} c1. MART.con los valores que puede adoptar -constante de enumeración-. c1 y c2 son objetos de tipo distinto. VIER. MIER. VIER. 5. El compilador sabe que se trata de una variable tipo enum por la sintaxis de la propia declaración.establece un tipo enum al que se identifica por dia. enum Evolucion {SUBE. §2 Puede omitirse la palabra clave enum si dia no es identificador de nada más en el mismo ámbito. Además se define una variable diaX de este tipo (variable enumerada). Ejemplo: enum Calificacion {APTO. // declara dos nuevas variables aunque también se podría omitir el especificador enum (el compilador ya sabe que Dia es de tipo enum): Dia laborable. VIER. 2. BAJA. Las variables de este tipo pueden adoptar un conjunto de seis valores enteros 0. 6 (enumeradores) representados por los nemónicos [2] DOM. y puede ser usada en subsecuentes declaraciones de variables del mismo tipo: enum Dia laborable. El programa los identifica con los nemónicos ya señalados. 4. LUN. JUEV. MART. El identificador Dia es la etiqueta opcional del tipo. si no existen otras variables del mismo tipo. MART. Observe que las expresiones que siguen no son equivalentes!! enum diaX { DOM. Ojo: no confundir la variable enumeración. Cada enumeración distinta constituye un tipo de enumerando diferente. SAB } diaX. es correcto: Dia { DOM. el inconveniente de declarar un tipo anónimo es que después no podemos volver a declarar otra variable del mismo tipo. LUN... MIER. // equivalente al anterior §3 Como ocurre con las declaraciones de estructuras y uniones.. LUN. JUEV. MIER. puede omitirse la etiqueta con lo que tenemos un tipo anónimo: enum { DOM. MART.. VIER. SAB } diaX. una variable enumerada puede recibir cualquier valor de tipo int sin que se realice . También aquí (como ocurre con las estructuras y uniones). la variable diaX puede adoptar siete valores enteros (del 0 al 6). enum { DOM. JUEV. SAB }. 3. Por ejemplo. SAB } diaX.SAB. festivo. LUN. §4 En C. IGUAL} c2. festivo. 1. 768 a 32. el compilador reserva una palabra (4 bytes en un compilador de 32 bits) para las variables enumeradas. La opción por defecto es que el tipo enum es promovido a entero y se imprime el valor resultante.9. Se refiere a que esta opción controla el almacenamiento que asignará el compilador a las variables enumeradas.2). algo que también puede hacerse con los #define ( 4.3) controla la opción "Considerar las enumeraciones como enteros". en cambio C++ es más fuertemente tipado en este sentido ( a una variable enumerada solo se le puede asignar uno de sus enumeradores.4). diaX = 1.535 -32.2. unsigned char o int. dependiendo de los valores asignados. las enumeraciones proporcionan un lugar ("placeholder") muy conveniente para almacenar constantes enteras como códigos de error o de cualquier otro tipo que podamos manejar en nuestras aplicaciones. // Ilegal. Por ejemplo: en lugar de utilizar .ninguna otra comprobación.10b). lo que por otra parte es lo máximo que pueden necesitar porque las constantes de enumeración son están forzosamente en el rango de los enteros. estas variables son promovidas a enteros y este es el valor mostrado. El valor por defecto es ON. a pesar que LUN == 1 2. Ejemplo: diaX = LUN. El depurador puede ser capaz de imprimir los valores de los enumeradores en su forma simbólica (lo que facilita grandemente las comprobaciones). // Ok. al ser mostradas por el dispositivo de salida. el compilador comprueba el rango de los enumeradores y reserva el espacio estrictamente necesario según el criterio de la tabla adjunta (ver al respecto: Representación interna y rango 2. Cuando se activa la opción. Hay que recordar que. En C++ se puede conseguir escribir el tipo enum sobrecargando adecuadamente el operador <<. que significa que se les asignará el espacio de un entero. Si se desactiva (opción -b-). §5 Como puede verse. las enumeraciones proporcionan un modo muy flexible de asociar nombres con valores. (ver sobrecarga de operadores). fuera de estos rangos asigna 32 bits: Rango de valores espacio tipo equivalente 0 a 255 -128 a 127 0 a 65. ese es el tipo de todos ellos. En el ejemplo anterior MART es mostrado como 2. los identificadores utilizados en la lista de enumeradores son implícitamente del tipo signed char. sin embargo. Si todos los valores pueden ser representados con un signed o unsigned char.767 8 bits 8 bits 16 bits 16 bits unsigned char signed char unsigned short signed short Por su parte. las enumeraciones (aún de un solo elemento) presentan ventajas [3] entre las que podemos señalar: Los valores pueden ser auto-generados Las variables de enumeradas ofrecen la posibilidad de comprobación.4. y El conmutador -b del compilador Borland C++ ( 1. En la práctica. . lun.1 Los enumeradores declarados dentro de una clase tienen la visibilidad de la clase.. // ILEGAL: redefinicion de mar } dom = 12. juev.}. SAB. SP. DOM } diaX. VIER. de aquí se deduce que los nombres de los enumeradores de las distintas enumeraciones.. // Ok. BUSY. typedef enum dias DIAS. int i = MIER. ERROR_NO_ERROR ERROR_NOT_FOUND ERROR_BUSY ERROR_FULL 0 1 2 3 Es más conveniente utilizar un enumerador: enum ERRORS { NO_ERROR. Ejemplo int dom = 11. mar. . vier. } err. year. diaX = LUN. Ejemplo class Date { public: std::string std::string std::string enum fomrat int fm. // int dom vuelve a ser visible §7. { US. FR. UK. Los identificadores de los enumeradores comparten el mismo espacio que los identificadores de las variables ordinarias. LUN = MIER. sab } diaX. { enum dias { dom. tienen que ser diferentes (los valores no tienen porqué serlo ni aún en la misma enumeración). mier. *diaptr = diaX. // Ok. enum dias diapago. . NOT_FOUND.. Ejemplo: enum dias { LUN. FULL. §6 Los tipos enum pueden aparecer en cualquier sitio donde sea permitido un entero. // ILEGAL: identificador dias ya usado double mar. month. MIER. DE }. j.#define #define #define #define . /* el enumerador dom oculta otra declaracion de int dom */ struct dias { int i. MAR. DIAS *diaptr. JUEV. // ILEGAL: LUN es una constante! §7 Visibilidad Las etiquetas de los enum comparten el mismo espacio de nombres que los de estructuras y uniones. day. int f1(En) { // L. int f() { color c. d1.2a) §8 Asignaciones a tipo enum En el compilador Borland C++ 5.. .. return 0. se obtiene también un error cuando se pasa un entero como parámetro a una función que espera un enumo viceversa. c = 0. } En estos casos de compilación estricta. Verde. // Incorrecto: a 'c' solo se le puede asignar Rojo. los enumeradores son las únicas propiedades que pueden inicializarse dentro de la definición de la clase ( 4. void foo() { Date d1. enum color { Rojo.11. .. Así. Estudie el ejemplo propuesto teniendo en cuenta que el resultado de la expresión flag1|flag2 (OR inclusivo entre bits 4.2. Verde o Azul return c. los enumeradores flag1 y flag2 son constantes de enumeración de valores 1 y 2 respectivamente. } Aquí En es un tipo enum. que han definidas en formato hexadecimal ( 3. .fm = Date::US.3: return (flag1 + 1). las reglas para expresiones que contengan tipos enum pueden forzarse a ser más estrictas activando el conmutador -A (que significa ANSI C++ estricto).9.}. #include <iostream. } int main () { f2(). Azul }. } Nota: es pertinente recordar aquí que.7: cout << "El valor es: " << x << endl. // L. al asignar un entero a una variable enum (como en el ejemplo) produciría un error..3) es un entero.3b).5. flag2 = 0x02 }. } void f2() { int x = f1(flag1|flag2). junto con las constantes estáticas enteras. con lo que el compilador refuerza las reglas con mensajes de error.h> enum En { flag1 = 0x01. } void f2() { int x = f1(En (flag1|flag2) ).9. flag2 = 0x02 }. #include <iostream. .9 Operadores §1 Sinopsis Los operadores son un tipo de tokens ( 3. // L. int f1(En) { // L. ya que como se ha apuntado. pero se le pasa un int. } int main () { f2(). con lo que la compilación se realiza sin novedad.18h).9.9) al argumento pasado a la invocación en L.1). Para arreglarlo. Se aplican a variables u otros objetos denominados operandos y su efecto es una combinación de las siguientes acciones: Producir un resultado-valor Alterar un operando Designar un objeto o función.3: return (flag1 + 1).7. aplicamos un modelado de tipo ( 4. lógicas ( 4.7bis: cout << "El valor es: " << x << endl.9. e indican al compilador la realización de determinadas operaciones matemáticas. la expresión flag1|flag2 produce un entero.9. que lo transforma en el tipo exigido.Al intentar compilar se produce un error: Cannot convert 'int' to 'En' in function f2() ya que la invocación a f1 en L7 espera unenum tipo En (como indica el prototipo de L3).h> enum En { flag1 = 0x01. Ejemplos §1a y = a + b.2) que pueden aparecer en las expresiones. } Salida: El valor es: 2 Tema relacionado Sobrecarga de enumeraciones ( 4. 4.8) y numéricas ( 4. pero en ocasiones estos "efectos laterales" se utilizan para conseguir expresiones muy compactas y de un cierto nivel de sofisticación. y). pero no altera ninguno de los operandos (a y b). el nuevo valor es asignado a la variable y mediante el operador de asignación = ( 4.9. En estos casos su lógica es un poco más difícil de seguir que cuando estos efectos no se utilizan directamente (Ver ejemplo 4. Nota: por lo general. el operador de indirección * es aplicado sobre el operando fptr que es un puntero-afunción. Aquí. A su vez el resultado de este operador (invocación de función) es utilizado como argumento derecho del operador de asignación = que finalmente modifica uno de sus operandos (el operando izquierdo z). Ejemplo: ++x n@: Operador posfijo. §1c z = (*fptr)(x. veremos que precisamente la forma en que se definen es mediante una función. según que necesiten uno.9. que incluso no necesitan del operador de asignación para producir el resultado. Aquí el operador postincremento ++ ( 4.9.9.1) produce un nuevo valor que es aplicado sobre el propio operando. dos o tres operandos. b).9.1) produce un valor nuevo. al tratar la sobrecarga de operadores.2).6).En esta sentencia. §2 Clasificación C++ dispone de un conjunto muy rico de operadores que se pueden clasificar en unitarios. Ejemplo: x++ Antes de seguir refiriéndonos a ellos. Del mismo modo. de forma que queda alterado. El resultado es un designador de función al que se aplica el operador ( ) de invocación de función con dos operandos x e y que actúan como argumentos. binarios y ternarios. §1b x++. los operadores aparecen a la derecha de expresiones de asignación (por ejemplo: y = 2 * y + x). tal vez sean procedentes un par observaciones que más adelante nos ayudarán a comprender mejor algunos conceptos: Los operadores pueden (en cierta forma) considerarse como funciones que tienen un álgebra un tanto especial.18). la asignación x = y sería algo . En este caso el operando de la izquierda sí se ve alterado. el operador suma + ( 4. la función-operador ( 4. Cuando un operador altera un operando se dice que tiene efectos laterales. a su vez. En este sentido podríamos imaginar que la expresión y = a + b es otra forma de representar algo como: y = suma(a. Los operadores unitarios (denominados también unarios) se clasifican en dos tipos según la posición del operador (representado aquí por @) respecto del operando (representado aquí por n): @n: Operador prefijo. De hecho. 9. y).16c) 4. produciendo y/o modificando un valor de acuerdo con ciertas reglas. Es decir. () y []. permite que puedan aceptar otro tipo de operandos y seguir otro comportamiento sin que pierdan el sentido y comportamiento originales cuando se usan con los operandos normales (los tipos básicos preconstruidos en el lenguaje).2.10) §4 Polimorfismo en los operadores Debe tener en cuenta que aún sin estar sobrecargado. los operadores pueden modificar el tipo de los operandos para que sean homogéneos. §3 En general los operadores aceptan un tipo de operando determinado y específico.2. dependiendo del contexto (número y tipo de operandos).18) del operador [1]. un operador C++ puede tener más de un significado [2]. estas seudo-funciones se identifican con símbolos ( +.9. Siguiendo con nuestra analogía.11) 4. así: asigna(x.3) Operador de referencia y = &x ( 4. Antes de realizar la operación encomendada.9. de forma que la operación pueda realizarse. Operador de selección de miembros ( . =. dos o tres argumentos respectivamente y que la sobrecarga de operadores no es sino una forma encubierta de sobrecarga de funciones. Muchos de ellos están compuesto de un solo token (que puede estar repetido + y ++) aunque los hay de dos. Aunque las funciones C++ se designan con identificadores (nombres) que siguen las reglas generales ya señaladas ( 3. pero deben ser tenidas en cuenta para evitar sorpresas.2).9. pero C++ permite redefinir la mayoría de ellos . binarios y ternarios serían funciones que aceptarían unos. al indicar i + f donde i sea un entero y f un float. delete[].2.9.19) 4. Veamos dos ejemplos: El símbolo ámpersand & puede ser interpretado como: AND entre bits A&B ( 4. Estos últimos son: new[].6) 4.11b) Declarador de referencia int& y = x ( 4. Esta circunstancia recibe el nombre de sobrecarga ( 4. ::. Por ejemplo.5) y son realizadas automáticamente por el compilador.3) . etc) [3]. podríamos suponer que los operadores unitarios. *.* Operador de dereferencia :: Operador de acceso a ámbito ?: Operador conditional # Directiva de preprocesado ( ( ( ( 4. Estas promociones se denominan conversiones estándar ( 2. ->.9.9.9. No son sobrecargables los siguientes: . Por ejemplo.9. En C++ existen 16 niveles de precedencia. si queremos que en la expresión anterior se ejecute primero el operador suma: x = (2 + 3) * 4.1 Operaciones aritméticas: suma. Por ejemplo.El asterisco * puede ser interpretado como: Declaración de un tipo puntero-a tipo* ptr ( Operador de indirección x = *ptr ( Operador de multiplicación x*y ( 4.0a). ¿Cual será el resultado.9.2.1) §5 Precedencia La precedencia es una regla de orden que se asocia con los operadores para establecer cual se aplicará primero en caso de aparecer varios en una misma expresión (una especie de "orden de importancia" de los operadores). 2 + (3 * 4). lo cual no es cierto. la regla izquierda → derecha nos conduciría a que en la expresión anterior. aunque las dos sentencias que siguen son equivalentes.1a) 4. el valor de x es 20. ya que en C++ la multiplicación tiene mayor precedencia que la suma. división y módulo . aunque no siempre. x = x = 2 + 3 * 4. multiplicación. 20 o 14? La primera regla en este sentido es que las expresiones se evalúan de izquierda a derecha. // el resultado es 20 Aunque las reglas de precedencia se suponen conocidas por los programadores C++.9. Por ejemplo: x = 2 + 3 * 4. resta. lo que aclara y justificada la tradicional costumbre algabráica de utilizar paréntesis para forzar el orden de las operaciones. Además de facilitar la legibilidad evitan posibles errores. El operador de jerarquía más alta es el paréntesis.0 Resumen de operadores C++ §1 Sinopsis Los operadores permitidos en el Estándar C++ son los que se relacionan: Aritméticos 4. de modo que la regla izquierda → derecha solo se aplica en caso de operadores del mismo nivel ( 4.9.11a) 4. Así. así que el resultado es 14. la segunda es de lectura más "relajante". 4.9. aconsejamos generosidad a la hora de utilizar paréntesis en las expresiones. 9.9. #pragma.5 Operador en la expresiones con coma.12 Operadores de relación: igual.20 Asignación dinámica de memoria 4.21 desasignación dinámica de memoria 4. .6 Operador ternario condicional 4.9.9.9. AND.9.9. static_cast Modelado clásico Miscelanea: :: .9.Asignación Manejo de bits 4.9 Convertir un tipo al deseado 4.11 Operadores de indirección (*) y de referencia (&) 4.9d Reemplazar modelados para conversiones que son inserguras o dependientes de la implementación.10 Directivas # de preprocesado.9.9a Añade o elimina la característica const o volatile de un identificador 4. #line. desplazamientos izquierda y derecha.9.9.9.9b Convertir un puntero al tipo deseado 4. menor o igual. desigual.9c Convertir un puntero al tipo deseado 4.9.9.8 Operadores que producen resultados booleanos: AND.* ->* ?: typeid Operador coma 4.2 Operadores de asignación simple "=" y compuestos 4.9. XOR y OR Lógicos de Preproceso de Puntero Relacionales 4. #define.3 Operadores para manejo de bits (bitwise) entre enteros: complemento. mayor. menor.19 Acceso a ámbito. etc. mayor o igual Manejo de memoria new delete Modelado de tipos cons_cast dynamic_cast reinterpret_cast 4.14 Obtener identificación de tipos y expresiones en tiempo de ejecución 4. 4.9.9. también llamado de resolución Dereferencia punteros a miembros de clase Dereferencia punteros a punteros a miembros de clases 4. OR y NOT 4. 13 Ver el tamaño de memoria utilizado por el operando §2 Los operadores unitarios solo requieren un operando y operan de izquierda a derecha (el operador a la izquierda.8) 4. 4. Asignación y desasignación dinámica de memoria: new ( Comprobar el tamaño de un objeto: sizeof ( §3 El único operador ternario es el operador relacional ? : ( §4 Recuerde que las operaciones lógicas producen siempre un resultado booleano.1) 4.9.20) y delete ( 4.1 Sintaxis <operador> <expresion unitaria> ó: <operador> <tipo><expresion unitaria> C++ dispone de los siguientes operadores unitarios: ! * & ~ ++ -+ Negación lógica ( Indirección ( Referencia ( 4. lo que se traduce en un valor numérico cero o uno. las operaciones entre bits producen valores arbitrarios.9.2. el resto son binarios.ibm.9.21) 4.0a Evaluación de expresiones: asociatividad y precedencia de operadores "The answer is.6).1) 4.9.3) 4.com.13) 4. §1 Presentación .9.1b). a-.3) 4.9.11) 4.sizeof 4.== a-1 ( Menos unitario ( Más unitario ( 4. cierto o falso ( 3. §2. el operando a la derecha).9. Brett McLaughlin en "Mastering Ajax" www-128.9.1) Complemento de bits ( Incremento.9.9. Por contra. Es decir.2. it's awfully hard to figure out what goes wrong in your application if you don't understand what is going on in your application".1) 4.9. a++ == a+1 ( Decremento.9.9. o incluso la aparición de errores de ejecución y/o compilación. y las de salto ( 4. Estas medidas suelen consistir en no simplificar demasiado las expresiones. Si existen paréntesis. Con los agravantes de que muchas de ellas son "ocultas". 3. Son las sentencias de selección.10. en mi opinión.10. Presencia de paréntesis que obligan a un orden de evaluación específico. unos operadores se ejecutan en el orden en que aparecen escritos en el código (de izquierda a derecha). 2. El punto tercero es influyente porque a igualdad de precedencia. porque como veremos a continuación. El asunto es importante porque muchas veces el resultado. y obtener resultados . es precisamente su naturaleza la que establece dos propiedades importantes de los operadores: la asociatividad y la precedencia (§3 ). constantes. Por ejemplo: String S = s1+c1. podría considerarse como el operador de precedencia más alta... su desconocimiento puede perfectamente mandarnos a limpiar cochineras a Carolina del Norte ( 1 ). en el sentido que son realizadas automáticamente por el compilador sin que tengamos constancia de su ocurrencia. El segundo es especialmente importante. del tipo for. depende justamente de estos detalles. Naturalmente no nos referimos a expresiones sencillas del tipo y = 2+x.Recordemos que el lenguaje C++ está compuesto por cinco tipos de elementos: palabrasclave.*c1. aunque el paréntesis es un signo de puntuación ( 3. Sin embargo. y de que se siguen procesos distintos en compilación y en ejecución (por supuesto todos ellos pueden dar lugar a errores). las palabras-clave señalan al compilador aspectos complementarios que noalteran el orden de ejecución dentro de la propia sentencia.. En cuanto al primero. o do. identificadores. Con estos elementos se construyen las sentencias. Este orden viene determinado por cuatro condicionantes: 1. y en otros casos es al contrario (dependiendo de su asociatividad).6).3). así como la naturaleza de las operaciones involucradas. salvo que se relacionen con las mencionadas sentencias modificadoras del flujo. En el presente capítulo incluiremos algunas observaciones respecto a este asunto al que.. sino a expresiones más complejas. excepto cuando aparecen elementos que modifican el flujo "natural" del programa. resulta relativamente fácil a cualquier estudiante conocer con exactitud el orden que seguirá la ejecución del programa en sus diversas bifurcaciones e iteraciones.. que resultan incontrolables para el programador a no ser que adopte medidas específicas. Se refieren a medidas del compilador tendentes a la optimización del código de la sentencia.4) como return obreak. Orden en que están colocados (precedencia). Providencias (impredecibles) del compilador relativas a la optimización del código. Naturaleza de los operadores involucrados en la expresión (asociatividad). no se suele prestar la debida atención a pesar de que en ciertos casos. el compilador los evalúa en primer lugar. A su vez el punto cuarto encierra decisiones que son dependientes de la plataforma. cuya ejecución es secuencial.2).pachar2+'C'.2. 4. operadores y signos de puntuación ( 3). las de iteración ( 4. §2 Evaluación de expresiones En general.10.. if.else ( 4. Por ejemplo. la facilidad no es tanta cuando se considera cuales son las operaciones que se llevan a cabo "dentro" de las sentencias. Considerando las sentencias como unidades de ejecución. y su orden. Aunque una vez obtenidos todos los resultados parciales. Finalmente se aplicará el operador suma + entre los resultados anteriores.. Así pues.. en la expresión: a * b + c++. Todos los demás la tienen izquierda (→). si @ representa un operador binario.. A continuación se aplicará el operador de multiplicación * entre a y b.) @ (. Cualquier otro debe ser forzado específicamente mediante la utilización de los paréntesis correspondientes. la ejecución seguirá el orden: (((a + b) + c) + d).)2) @ (. lo que significa que en una expresión como: a + b + c + d. la expresión es equivale a: (a * b) + (c++). la suma binaria + tiene asociatividad izquierda.) @ (.1. // L. la computación sigue el orden indicado en el punto anterior.2 Este orden "natural" del compilador no necesita paréntesis de forma que las sentencias L1 y L2 producen el mismo resultado. Los operadores unarios y el de asignación (=). .) @ (.). // L.. En consecuencia.1 La precedencia indica cual es el orden de ejecución de los operadores cuando existen varios.1. Puede ser de dos tipos: izquierda (→) o derecha (←) [1]. Por ejemplo: a * (b + c)++. Por ejemplo. Si existen paréntesis anidados se procede desde dentro hacia fuera: (((. el orden de evaluación de los paréntesis es indefinido. el orden de evaluación es desde la izquierda hacia la derecha. ante una expresión como: a @ b @ c @ d.3 §2.)@(.1 la precedencia determina que se ejecutará primero el operador postincremento ++ sobre c.2 La asociatividad indica el orden de ejecución cuando en una expresión existen diversos operadores de igual precedencia.)). tienen asociatividad derecha (←). que solo son necesarios para obligar a una forma determinada de obtener el resultado. Pero si la expresión es del tipo: (.intermedios.. // L.2... §2.. Por ejemplo. cuando un operador tiene dos formas. mayor-que. Esto significa que en la gramática del C++. división y módulo Suma y resta binarias Manejo de bits: desplazamientos izquierdo y derecho > >= Operadores relacionales. la primera tiene mayor precedencia que la segunda. Nota: el paréntesis es un signo de puntuación y no se incluye (el que se muestra en la tabla es el operador de invocación de función). Se ha incluido un número indicativo. los números más bajos tiene mayor precedencia que los más altos. Cuando existen operadores duplicados en la tabla. menor-que. etc. algunas de las cuales contienen solo un operador. Operadores relacionales igual y desigual Manejo de bits: AND lógico Manejo de bits: XOR Manejo de bits: OR && Operador Y lógico . unaria y binaria. * y &).* * + << < == & ^ | Operador Asociatividad ↓ Operador de resolución de ámbito [] ~ ->* / >> <= != -> + ++ -++ . pero podría considerarse como el operador de precedencia más alta (precedencia -1). la primera ocurrencia se refiere al operador unitario. los casos de +. El orden en la tabla muestra las precedencias en orden descendente (misma línea igual precedencia).§3 Precedencia y asociatividad de los operadores C++ En C++ existen 18 categorías de precedencia (ver tabla adjunta). ↓Precedencia 0 1 2 3 4 5 6 7 8 9 10 11 12 :: () ! . -- typeid & * sizeof new delete → ← → → → → → → → → → → Selección de miembro % Operadores aritméticos: multiplicación. la segunda al binario (por ejemplo. Debido a que la precedencia del preincremento es menor que la del selector indirecto de miembro. lo que supone que expresiones de comprobación de bits como: if ((x & MASK) == 0) {. si es un puntero.13 14 15 || ?: = Operador O lógico Operador condicional ternario *= /= %= += -= &= ^= |= <<= >>= ← → ← 16 throw Operador de lanzamiento de excepción 17 . El resultado es que se incrementa el miembro x. El resultado depende del tipo de miembro x. Por ejemplo. *ptr->x. por lo mismo.. esta indirección equivale a *(ptr->x). 9 y 10) tienen una precedencia menor que la igualdad == y desigualdad != (línea 7).} deben ser adecuadamente parentizadas para garantizar que los resultados sean correctos. se obtendría un error de compilación (no puede obtenerse la indirección de un int). Por el contrario. Pre-operador incremento y Observe que los operadores de bits & ^ | (líneas 8. se obtendría el objeto señalado por él.. Ejemplos ++ptr->x. §4 Orden de evaluación . no el valor del puntero ptr. Operador coma → Nota: Post-operador incremento y decremento unitario. decremento unitario. si x es un int. la expresión anterior equivale a: ++(ptr->x). x++ + 5 << ".x++ << ".2 // L. considere la expresión: i = v[i++].. x = " << x << endl.3 // L.. cout << x = 5. en cuyo caso la sintaxis fuerza un orden de evaluación. ++x . así como el orden en que ocurren los efectos laterales de las expresiones". total = 0. ||. en la expresión: (a + b) + ( c + d). pero no está definido cual de las dos se ejecutará en primer lugar [ 2]. x = " << x << endl. ).Las reglas anteriores no definen completamente el orden de evaluación de las expresiones. // L. así que no puede especificarse el orden en que serán evaluados realmente los operandos a menos que algún operador lo establezca específicamente. Por ejemplo.1 // L. ?: y . es evidente que las subexpresiones (a + b) y (c + d) se resolverán antes que la suma central. no está especificado el orden de evaluación de los operandos de operadores individuales y subexpresiones de expresiones individuales. sum = (total = 3) + (++total).++x << ". Por ejemplo. x-. cout << x = 5. sum = (total = 3) + temp. Por lo general los compiladores tratan de arreglar las expresiones para mejorar la calidad general del código. la mayoría de compiladores C++ no garantizan el orden en que se evalúan los operandos de una expresión (con excepción de los operadores &&.5 . La solución es rediseñar la expresión utilizando una variable temporal: int temp.x-. temp = ++total. Por lo general deben evitarse expresiones que simultáneamente modifiquen y usen el valor de algún objeto.4 // L. porque su valor resultante depende de que sea incrementado antes o después de la asignación. los valores sum y total son ambiguos (es sum = 4 ó sum = 7 ?). cout << 5. x-. Por esta razón hay que tener especial cuidado con expresiones en las que un valor sea modificado más de una vez. x = " << x << endl. De forma similar en: int total = 0. cout << x = 5. x = " << x << endl. En consecuencia. El Estándar señala al efecto: "Con excepción de los casos señalados. de forma que dejan algunos aspectos a criterio del compilador (son dependientes de la plataforma y de las decisiones que esta quiera tomar tendentes a la optimización). ++x + 5 << ".<< ". x = " << x << endl. donde i es indefinido cualquiera que sea su valor previo. Ejemplo: int x = cout << x = 5. se produce el postincremento. 10. x = 5 0. x = 6 0. x = " << x << endl. x = " << x << endl. x = 5 0. de derecha a izquierda. cout << 5. solo tienen efecto después que la expresión ha sido evaluada. el valor 5 de x es sumado con la constante numérica 5. x = " << x << endl. (x--) .(x--) << ". Aquí el resultado obtenido con ambos compiladores es 6. con lo quex tiene el valor 6. Que el valor de x es 6 en ambos casos.Salidas: Borland C++ Builder 5. x = " << x << endl. x = 6 11. mientras que GNU lo hace de izquierda a derecha [4].2 el proceso es análogo. y el resultado es 10. ya que el incremento y decremento se anulan entre sí.3. ( ++x) + 5 << ". cout << x = 5.(x++) << ". x = 5 0. tienen mayor precedencia que las suma y resta binarias.2: cout << "El valor de x es ahora " << x << endl. puede ser comprobado añadiendo una sentencia inmediatamente después de L. en sus versiones "pre" y "post".1 y L. (x++) + 5 << ". Pero los efectos laterales de estos últimos (postincremento y postdecremento). x = " << x << endl.0 para Win32 10. La aparente contradicción en los valores de x mostrados por ambos compiladores.( ++x) << ".1-20030804-1 para Win32. . con lo que al valor 6 de x se le suma la constante 5 y el resultado es 11. En ambos casos. por lo que se ejecutan primero. En L. x = 5 Para entender los resultados es preciso considerar que los operadores incremento y decremento ++ y --. Esto puede evidenciarse incluyendo paréntesis en las expresiones anteriores y comprobando que se obtienen los mismos resultados: int x = cout << x = 5. cout << x = 5. ( ++x) .1 y L.4 y 6.6. x = 5 1. En L. (x--) .1. x = 5 0. x = 5 GNU C++ 3.2 es 6. el valor de x después de ejecutadas las sentencias L. se deben a que el compilador Borland evalúa las expresiones del tipo cout << A << B << endl. Una vez que se ha completado la expresión. con la salvedad de que ahora el incremento de x se realiza antes de que la expresión sea evaluada. Observe que en las tres salidas restantes. x = 5 11. cout << x = 5. el valor resultante de x es 5 cualquiera que sea el orden de ejecución de la sentencia. durante la fase de compilación. se le resta el valor actual (5) y el resultado es cero.). para lo cual intenta evaluar z + 'A'. Estamos ante un operador binario suma (+) con dos operandos: izquierdo y derecho. Al valor actual 5 de x. en ambos casos se efectúa primero el preincremento. int r = x + y + (z + 'A'). debe ser promovido al tipo del argumento formal. así que el argumento actual 'A'. con lo que su valor vuelve a ser 5.1 El siguiente ejemplo ofrece una idea de los procesos involucrados en la evaluación de expresiones.x-.18) que esta expresión es otra forma sintáctica de la invocación z. el compilador procede de derecha a izquierda.. que lo es ( 2.<< ".6)... con lo que al valor resultante 6 de x. z (ya sabe que 'A' es una entidad tipo const chary que r es un int que se declara en la misma sentencia). void func(int x. Aquí debemos señalar ( 4. x = 5 §4. el compilador intenta en primer lugar resolver la expresión del Rvalue.3. x = " << x << endl. // -> -1. Esta teoría y el error se confirman cor el resultado proporcionado por: x = 5. cuyo resultado vuelve a ser 5 cualquiera que sea el orden de ejecución adoptado.9. Una vez evaluada la expresión. el postdecremento de x. La razón de la discrepancia de las salidas obtenidas en L.se evaluará primero.2. int y. con lo que el resultado es 0. un tipo int ( 4.5 (en las que concuerdan ambos compiladores) son 0 porque. int z) { .. Aparentemente el compilador Borland precede de derecha a izquierda y además de forma errónea.1 y L. Suponiendo que esto sea posible.4 y L. en este caso. un tipo const char. Antes que nada. En este caso no existe precedencia para decidir cual de las expresiones x++ o x-.4.5).Las salidas L.2. cout << x++ .) @ (. Como el paréntesis es el operador de mayor precedencia debe ejecutarlo en primer lugar.operator+('A'). Al valor 6 de x se le resta el valor actual 6. se anotaría el .. y el resultado (postincremento) tiene lugar "antes" que la resta. suponiendo que ha encontrado en L1. Es la invocación de un método cuyo prototipo es: int int::operator+(int i).. El resultado 0 de GNU es evidentemente el correcto. A continuación se producen el postdecremento y postincremento..) @ (. y. se le resta el (todavía valor anterior 5). El método operator+ de la clase de los enteros devuelve un entero y recibe un entero. la libertad que concede el Estándar respecto al orden de a la evaluación de expresiones del tipo: (. Es decir. Nota: el "bug" de esta versión de Borland parece debido a que. A continuación... } // L1: // L2: Para ejecutar L2.2. tienen efecto los resultados laterales..) @ (. donde se encuentra con diversos identificadores y operadores. procede a codificar el resultado. que son tipos int.1) para averiguar "que cosa" son las etiquetas x. dado que los resultados laterales solo debían manifestarse "después" de evaluada la expresión. se realiza un "Name-lookup" ( 1. es de naturaleza análoga a la encontrada para los valores de x en las L. .11. el nuevo parcial (lo llamaremos T2) es alojado en un objeto creado al efecto. Además.resultado (int) devuelto por la función en un objeto temporal (lo llamaremos T1) y se procedería con el resto de la expresión. pero descubrimos con sorpresa que ahora el compilador lanza un extraño error: Illegal structure operation in. las cosas funcionan de forma paralela a la descrita y el programa funciona sin dificultad [3].. Por ejemplo. en la que todos los operadores + tienen igual precedencia. Para resolver x + y + T1. Como evidentemente este método no está definido para los tipos char.. C y. cuando un poco más adelante se nos presenta una sentencia "muy" parecida a la anterior: C r2 = x + ('B' + y) + z. para lo que existen dos alternativas: . e intenta encontrar una definición de la asignación que corresponda al prototipo: const char char::operator+(C c). Finalmente.2d4) de la clase int. La cosa puede ser aun más desconcertante si sabemos que el compilador dispone (y lo ha demostrado) de recursos para convertir en caso necesario un const char a C. se realiza la operación T2 + T1 con un nuevo resultado parcial T3. } // L1: // L2: Suponemos que hemos definido adecuadamente el operador suma binaria para los miembros de la clase C y que disponemos de un conversor adecuado por el que el tipo const char puede ser convertido a tipo C. y para más INRI. la expresión int r = T3.2d1). el Rvalue (y) es también de este tipo. Al intentar evaluar la subexpresión 'B' + y no tiene en cuenta el contexto.. En este caso. el compilador lanza una advertencia anunciando que no puede realizar la suma en el orden solicitado. Si no podemos alterar el orden de los sumandos (porque nuestra definición de suma para operandos de tipo C no sea conmutativa) la única posibilidad es forzar nosotros mismos la conversión. proporcionando un constructor-conversor en la clase ( 4.. Dependiendo de nuestro estado anímico ese día. Supongamos ahora un caso exactamente análogo al anterior en el que los objetos x. El problema es que (en el estado actual) el compilador no es aún lo suficientemente inteligente. y seguiría la ejecución del resto de la sentencia. En primer lugar. en el contexto de la expresión resulta evidente que el resultado del paréntesis "debe" ser un tipo C. podemos tardar más o menos en descubrir que el problema radica precisamente en la computación del paréntesis (que aparentemente había funcionado correctamente en otras sentencias).11. C z) { .. resuelve x + y. que en este caso es izquierda. el compilador procede según su asociatividad. de forma que puede efectuarse la operación z + 'A'. supone la invocación del constructor-copia ( 4. y y z no sean tipos básicos sino tipos abstractos pertenecientes a una clase Cdiseñada por el usuario: void func(C x. En este caso. Estamos tan contentos de lo bien que funciona nuestra clase.. C r = x + y + (z + 'A'). 1-20030804-1 Resultado Resultado Resultado Resultado Resultado z0 z1 z2 z3 z4 == == == == == 5 2 3 5 6 . BC++ Builder 6. int* pt4 = ai. int z2 = *(++pt2). §4.2 A continuación se muestra otro ejemplo funcional de los insidiosos errores que pueden cometerse C++ cuando se olvida algún "pequeño" detalle. cout << "Resultado z1 == " << z1 << endl. 3}. int* pt1 = ai. int z4 = *(++pt4) + *pt4. // Ok. int main() { // =============== int ai[] = {2. cout << "Resultado z2 == " << z2 << endl.5. int z0 = ai[0] + ai[1]. int* pt3 = ai.9. int z3 = *pt3 + *(++pt3). C r2 = x + (r + y) + z. conversión implícita // Opción-1 // Opción-2 C r2 = x + (static_cast<String>('B') + y) + z. Considere el sencillo programa adjunto y sus sorpresivos resultados: #include <iostream> using namespace std.9 GNU Compiler Collection G++ 3. } Salida BC++ Builder 5.3. // suma 2 + 3 cout << "Resultado z0 == " << z0 << endl. cout << "Resultado z4 == " << z4 << endl. MS Visual C++ 6: Resultado Resultado Resultado Resultado Resultado z0 z1 z2 z3 z4 == == == == == 5 2 3 6 6 Salida Dev-C++ 4. int* pt2 = ai. int z1 = *pt1. cout << "Resultado z3 == " << z3 << endl.C r = C('B'). en este caso. pero los errores detectados por las funciones de las librerías matemáticas pueden ser controlados mediante rutinas estándar o de usuario. la evaluación empieza de derecha a izquierda. como división por cero o valores fraccionarios fuera de rango. i++). las salidas anteriores son exactamente las mismas con BC++ Builder 5.5) . n. z3 y z4 son aparentemente erróneos. Ver ( 4. // OK: sum = 4. y después *pt3.4 Aunque hemos señalado que. el compilador resuelve primero la expresión *(++pt3). n)). para mejorar la calidad del código. derecha o izquierda. i++.10. pero entonces el puntero ya ha sido modificado. pero precisamente el resultado z4 parece desmentirlo. ++n. power(2. §4.3 El lenguaje tampoco se garantiza el orden en que se evalúan los argumentos de una función. Ver _matherr y signal. printf ("%d %d\n".0 ( 7). Por ejemplo: sum = (i = 3. cualquiera que sea su posición. // Incorrecto !! puede producir diferentes resultados con diferentes compiladores. §4. §6 Expresiones con coma Las expresiones con coma son conjuntos de subexpresiones separadas por coma y agrupadas por paréntesis. Sin embargo. La solución es: ++n. el resultado es el valor de la última. El desbordamiento de enteros es ignorado n (C usa una aritmética de base 2 en registros de n-bits). En cuanto a la salida del compilador GNU Cpp para Windows. con lo que su valor es también 3.Comentario Los resultados z0 a z2 son los esperados. sin embargo. es siempre de izquierda a derecha. power(2. C++ reagrupa las expresiones reordenando los operadores asociativos y conmutativos con independencia de paréntesis. parece evidente que su ejecución.5 que con MS Visual C++ 6. al menos en estas expresiones. La primera explicación plausible es suponer que. con lo que al primer resultado (3) se le suma el valor de *pt3. en las expresiones con coma el valor no queda en ningún caso afectado por la reordenación. La segunda explicación sería suponer un error en el compilador. Cada subexpresión se evalúa una a continuación de otra empezando por la izquierda. Por ejemplo: printf ("%d %d\n". i = 5 §5 Errores y desbordamientos (overflow) Durante la evaluación de una expresión pueden encontrarse situaciones problemáticas. n)). Lo que ocurre realmente es que en ambos casos (z3 y z4). La de punteros es una aritmética un tanto especial y rudimentaria ( 4. Incremento unitario (dos clases) menos unitario. unarios (que aceptan un solo operando) y binarios (que aceptan dos). Estos últimos pueden combinarse con el de asignación = para dar origen a operadores compuestos ( 4. Decremento unitario (dos clases) Operadores artiméticos binaros: + * / % Suma binaria. Multiplicación División.2) son los siguientes: . Además son de dos tipos. Multiplicación (este símbolo tiene también otros usos División .2). Resto o módulo Nota: la aritmética de números reales es la clásica de la escuela primaria. más unitario .9. Resta binaria. otros aceptan operandos de tipo puntero-a-tipoX [3]. C++ dispone de los siguientes: + ++ -* / % Dos posibilidades: Suma binaria Incremento unitario (dos clases Dos posibilidades: Resta binaria Decremento unitario (dos clases ) . . §2 Observaciones Los operadores aritméticos pertenecen a dos grupos: unos aceptan operandos de tipo numérico. menos unitario ) ) . Resto o módulo.4.1 Operadores aritméticos §1 Sinopsis Los operadores aritméticos se usan para realizar cálculos de aritmética de números reales y de aritmética de punteros. La clasificación es la siguiente: Operadores aritméticos unarios: + ++ -más unitario.2. .9. 0a se ha presentado una explicación del sorpresivo resultado ( 0 ) que se obtiene para L. Equivale a suma binaria de puntero y Nota: aunque válida. caben dos posibilidades sintácticas: a. *ptr + -*++ptr. En 4. Como veremos inmediatamente.5: L. el compilador deduce por el contexto de que versión del operador se trata.7: Suma binaria (de enteros) a + (-b) En L. en C++ es perfectamente válida una expresión del tipo: int x = *ptr+-*++ptr.expresión-de-multiplicación §3.6.1 Operador Suma binaria . la sentencia de L.6: más unitario sobre tipo numérico menos unitario sobre tipo numérico 2 + 3 suma binaria (de enteros) 3 .7 coexisten tres operadores aritméticos no homogéneos (de izquierda a derecha): + Suma binaria entre valores numéricos tipo int ( Negación unitaria de un valor numérico tipo int ( ). En el ejemplo siguiente se muestran los casos posibles: int ai[] int* ptr int r1 = int r2 = int r3 = int r4 = int r5 = unitario int r6 = = {2.2 resta binaria (de enteros) 2 +(-3) suma binaria seguida de menos // L.expresión-suma + expresión-de-multiplicación b. Como en el resto de los casos de sobrecarga.expresión-suma .9.ai[0] ai[0] + -ai[1]. -ai[1] ai[0] + ai[1]. Los valores *ptr y -*++ptr ).3: L. ai[1] . Es un caso de sobrecarga incluida en el propio lenguaje [1].+= -= *= /= %= Asigna suma Asigna diferencia (resta) Asigna producto Asigna división Asigna resto (módulo) Tenga en cuenta que existen distintos operadores enmascarados bajo los mismos símbolos + y -. // // // // // L.7 es un ejemplo de expresión peligrosa y desaconsejada. = ai. §3 Suma y resta binaria En el primer caso: suma y resta binaria. el valor *(++ptr) ++ Preincremento de un puntero-a-int ( entero: ++ptr == ptr = ptr + 1 ). +ai[0].3: L. 3}.4: L. 2. En este caso ambos operandos están sujetos a las posibles conversiones aritméticas estándar ( 2.B son los siguientes: 1. // x == 4 §3.2). // Señala a 1 int x = *(2 + ptr).2. En estos dos últimos casos se aplican las reglas de aritmética de punteros ( 4. Ejemplo: int z = *(ptr + 3). entero o fraccionario. // Señala a 1 int* pt2 = &arr[4]. // x == 3 §4 Operadores ± Unitarios .2. // Señala a 5 int x = pt2 . A y B son tipos aritméticos. 3. y = 20. // z == 30 2. 3.pt1. Ejemplo: int x = 10. 5}. 4. int* ptr = &arr[4].2. 4. int z = x + y. Ejemplo: int arr[5] = {1. // x == 4 3. 2. A y B son de tipo aritmético. Ejemplo: int x = 10. // z == -10 2. El resultado es la resta aritmética de ambos operandos. En estos dos últimos casos se aplican las reglas de aritmética de punteros ( 4. y = 20.2.2). int* pt1 = &arr[0]. A y B son punteros a objetos de tipos compatibles. 2.Las posibilidades para los operandos en la expresión A + B son los siguientes: 1. 5}.1). int z = x . // x == 3 3.5) y el resultado es la suma aritmética de ambos. Ejemplo: int arr[5] = {1.2 Operador Resta binaria. A es un puntero a objeto y B es un entero. enteros o fraccionarios ( 2. las posibilidades son las mismas que en el caso 1 de la suma binaria expuesta anteriormente. 3. Ejemplo: int arr[5] = {1. 5}.2). int* ptr = &arr[0]. A es un entero y B es un puntero a objeto. Las posibilidades para los operandos en la expresión A . A es un puntero a objeto y B es un entero. // señala a 5 int x = *(ptr . 4.y. Valor negativo del operando expresión-cast después de cualquier promoción interna que se necesite.<expresión-cast> En ambos casos <expresión-cast> debe ser de tipo numérico.9. Los resultados son respectivamente: Valor del operando expresión-cast después de cualquier promoción interna que sea necesaria. Sintaxis expresión-de-multiplicación % expresión-cast Ejemplo int resto = (6 % 4). (y . Sintaxis expresión-de-multiplicación * expresión-cast expresión-de-multiplicación / expresión-cast §6 Operador módulo El operador binario % (operador de módulo) devuelve el resto de la división de dos enteros. (y .se utilizan como operadores unitarios. Nota: recuerde que cuando + y . Ejemplo int int int int int x = 7.se utilizan como operadores unitarios. . // // // // r1 r2 r3 r4 == == == == 4 -4 -4 4 §5 Operadores multiplicación y división Los operadores binarios * (multiplicación) y / (división) realizan sus operaciones aritméticas correspondientes con todos los tipos numéricos (enteros y fraccionarios). (x . no puede ser utilizado con números fraccionarios floato double [2].y).x).0a). cout << "El resto de 6/4 es " << resto << endl. (x .Cuando los operadores + y . r1 = r2 = + r3 = r4 = + y = 3. tienen mayor precedencia que cuando se utilizan como suma y resta binarias ( 4. las posibilidades sintácticas son: + <expresión-cast> .x).y). char * s = "Hola mundo". §7.expresión-unitaria (postincremento) (preincremento) (postdecremento) (predecremento) En los ejemplos que siguen suponemos que originariamente n == 5. *(s+1)). x = --n . // -> x == 4 y n == 4 En ambos casos. // // // // // Letra Letra Letra Letra Letra "H" "I" "o" "H" "o" .1 Para evidenciar la diferencia entre preincremento y postincremento. suman y restan respectivamente una unidad al valor de la expresión. *s+1). printf("Letra: \"%c\"\n". no una expresión. Para interpretarlas correctamente debe tener en cuenta la precedencia de los operadores y recordar que. x = ++n . en todos los casos. *s++). printf("Letra: \"%c\"\n". // -> x == 5 y n == 4 El predecremento resta uno antes de la evaluación de la expresión.(decremento). Existen dos variedades "Pre" y "Post" para cada uno de ellos. // -> x == 6 y n == 6 El postdecremento resta uno del valor de la expresión después de que sea evaluada. También deben recordarse las reglas de ámbito de los argumentos pasados a funciones. printf("Letra: \"%c\"\n". El postincremento añade uno a la expresión después de que se ha evaluado: x = n++ .Salida: El resto de 6/4 es 2 §7 Operadores incremento y decremento Los operadores unitarios ++ (incremento) y -. // -> x == 5 y n == 6 El preincremento añade uno antes de que sea evaluada la expresión. *s). *s). printf("Letra: \"%c\"\n". printf("Letra: \"%c\"\n".. Por ejemplo: (x+y)++ es ilegal. el operando debe ser una variable. observe las salidas que se obtienen con este pequeño programa según se van ejecutando las diversas líneas. x = n-. Las posibilidades sintácticas son: postfix-expression ++ ++ expresión-unitaria postfix-expression --. el argumento recibido por printf es un puntero-a-carácter (char*). printf("Letra: \"%c\"\n".9.2 Operadores de asignación §1 Sinopsis C++ dispone de los siguientes operadores de asingación: = *= /= %= += -= <<= >>= &= ^= |= Asignación Asigna producto Asigna división Asigna resto (módulo) Asigna suma Asigna diferencia (resta) Asigna desplazamiento izquierda Asigna desplazamiento derecha Asigna AND entre bits Asigna XOR entre bits Asigna OR entre bits §2 Sintaxis: <expr-unaria> <operador-de-asignación> <expr-derecha> Ejemplo: x = 3. = es el único de asignación simple. §3 Comentario Todos ellos son operadores binarios. . los demás son operadores de asignación compuestos. *s).printf("Letra: \"%c\"\n".9. Ver otros ejemplos en §4. de los cuales. ( // Letra "l" // Letra "l" Ejemplo-2) Tema relacionado Sobrecarga de los operadores aritméticos ( 4.10.3: ( Ejemplo-1). *++s).18b2) 4. x += 3. x &= 3. Observe también que la asignación simple (=) utiliza un símbolo distinto del operador relacional de igualdad (==). con el resultado de quenum recibe el valor 4 y el resultado de la asignación es un int de valor 4. §3.2 Esta propiedad de las asignaciones de producir un resultado. Considere el bucle del siguiente ejemplo: while ( (ch = getchar() ) != 27 ) . el resultado de la asignación es un char. se aplica a num. Ejemplo: int num. es el resultado de la expresión (el Lvalue 2. >>=. En este caso. ^= y |=. el valor 4. y es ampliamente utilizada en la programación de C++. aprovechando que en este caso. El valor resultante de <expr-unaria> después de la asignación. En caso necesario se realiza una conversión de tipo (con pérdida de precisión en su caso) del izquierdo al derecho.3).5). float f = 3.Los seis primeros aceptan operandos de distinto tipo. El tipo resultante es el de la <expr-unaria> (izquierda). se utiliza directamente en la cláusula del while. por lo que sus operandos deben ser tipos int en sus distintas variantes.5). cualquiera que sea su sentido concreto.3 En general las sentencias de asignación tienen la forma: variable = expresion La parte izquierda (que tiene que ser un Lvalue no constante) adquiere el valor señalado en la expresión de la derecha. Así pues. §3.1. Teniendo en cuenta que el tipo resultante debe ser el de num (un inten este caso). el valor que se aplica es el determinado por <exprderecha> (el Rvalue 2.14. resultando que el bucle se ejecuta indefinidamente hasta que se pulse la tecla ESC (escape). se realiza automáticamente una conversión del float 4.14.9. num = f + 1. es justamente la que permite expresiones de asignación compuesta del tipo: A = B = C = D. resultado de <expr-derecha>. mientras que los cinco últimos: <<=.14 a int (con pérdida de precisión si es necesario). implican manejo de bits ( 4. &=. §3. .1. Nota: observe que el operador C++ de asignación simple (=) se distingue de otros lenguajes como Pascal que utilizan el símbolo := para este operador.1 No olvidar que las expresiones de asignación producen un resultado. pero se mantiene el tipo original de la variable de la parte izquierda. todos implican la "copia" de un bloque de memoria (que contiene el "resultado") en algún sitio (la dirección indicada por el Rvalue). char ch = ‘a’. cout << sec << " segundos son ". una vez declarado char a[10]. E1 debe ser un Lvalue modificable. La expresión de asignación en sí misma no es un Lvalue. ninguna de las dos sentencias que siguen es válida: a = "0123456789". const float pi = 3. char string[10] = "1234567890". Este es el único caso en que se puede asignar una constante [2]. int main() { // =============== int sec = 3628. Ejemplo: int primero = 0. sec %= 60*60. float balance = 123.§4 Las variables se pueden inicializar en el mismo momento de la declaración (asignándoles un valor). §5 En la expresión E1 = E2. Por ejemplo. } Salida: 3628 segundos son 1 hora y 0 minutos y 28 segundos §7 Tanto para las asignaciones simples como compuestas. en caso contrario puede obtenerse un mensaje de error: Lvalue required[1]. §6 Si @ es un operador compuesto. cout << h << " hora y ". Ejemplos x *= y x *= y + 2 /* equivalentes */ /* equivalentes */ x = x * y x = x * (y + 2) Ejemplo operativo: #include <iostream> using namespace std. la expresión E1 @= E2 equivale a E1 = E1 @ E2. Por ejemplo: E1 += E2 equivale a E1 = E1 + E2. a[10] = "0123456789". sec %= 60.14159 . cout << m << " minutos y ". ambos operandos E1 y E2 deben . int h = sec / (60*60). int m = sec / 60.456. cout << sec << " segundos" << endl. z =& y. E1 es un puntero y E2 es un puntero nulo.9. cout << "x = cout << "y = cout << "z = } // ============== = 4. el tipo de objeto señalado por E1 tiene todos los calificadores del tipo de objeto señalado por E2. Considere el siguiente ejemplo: #include <iostream. el símbolo "=" va a la derecha!!. // asignación de nulo a puntero a carácter // char = char * 0 Precauciónes !! Es relativamente frecuente confundir el orden de escritura de estos operadores. Salida (después de eliminada la sentencia errónea): x = 0 y = 4 z = 0065FDFC 4. x =& y. x &= y. cualificado o no y E2 es un tipo aritmético. " << y << endl. No puede haber espacios entre los operadores compuestos (+ =) Hay ciertas condiciones.9. El tipo de objeto señalado por E1 tiene todos los calificadores del objeto señalado por E2. en todos los casos de asignación compuesta. char *= 0. y int* z = &x. " << z << endl. " << x << endl.2a). pero de tipo compatible con E2. en que los operadores de asignación no pueden aplicarse cuando se trata de propiedades de clases ( 4. E1 y E2 son punteros a versiones no cualificadas de tipos compatibles. Asignar un puntero!! // Comprobaciones. E1 o E2 es un puntero a objeto o tipo incompleto. E1 es una estructura o unión cualificada o no.2a Los operadores de asignación con propiedades de clase §1 Sinopsis . // Ok. y el otro es un puntero a una versión cualificada o no cualificada de void. Recuerde que. // Error !! // Ok.cumplir alguna de las siguientes condiciones (asumimos que E1 está a la izquierda del operador y E2 es el de la derecha): E1 es de tipo aritmético.h> void main() { int x = 2. Ejemplos char* = 0. 9. . Edit1->Text += "nuevo". que es correcto y proporciona el resultado esperado. y GerText(t) para obtenerlo. Por ejemplo. compara dos bits OR inclusivo. Desplazamiento a izquierda Desplazamiento a derecha AND. en la forma a = a + b. respectivamente. se trata de una variable interna de la clase (privada) que solo puede ser accedida mediante un par de métodos que la asignan y devuelven su valor. temp += "nuevo". . El caso 2. el texto de una caja de texto es una propiedad. Los restantes realizan comparaciones lógicas entre los bits de ambos operandos. que actualizaría la propiedad de la ventana de texto no es invocada. El primero es un operador unario. y que ambos métodos devuelven "por valor". compara dos bits *. // Caso-1 // Caso-2 El caso 1. genera un código similar a: SetText(GetText() + "nuevo"). digamos que son SetText(t).Ciertos operadores de asignación no son permitidos con propiedades de clase. *. para establecer su valor.8).9. similares a las que realizan los operadores lógicos entre objetos booleanos ( 4. pero es una variable un tanto especial. que desde luego no funciona como cabría esperar. Son los siguientes: ~ << >> & ^ | Complemento a uno *. los restantes son binarios. consideremos los dos casos de asignación siguientes: Edit1->Text = Edit1->Text + "nuevo". Recordar que: . genera un código similar al siguiente: temp = GetText(). XOR (OR exclusivo). Los tres primeros realizan manipulaciones en los bits del operando.3 Operadores de manejo de Bits §1 Sinopsis C++ dispone de 6 operadores para manejo de bits que realizan dos tipos de operaciones. en la forma a += b. pero la función SetText(temp). 4. compara dos bits *. En esta hipótesis. Por ejemplo. La variable temporal es actualizada. Los operadores que gozan de esta propiedad se denominan asociativos.1. El resultado es siempre un entero del mismo tipo que los operandos. con los operadores lógicos && y || ( 4.9. // equivale a: En la primera línea. & y |. señalados con asterisco (*). La representación binaria de los los complementos a uno de los decimales 0.11.8).9.2. uno o varios.2. &.1e1) para realizar operaciones de manejo de bits con entidades que no están restringidas a las longitudes de los tipos enteros. Este símbolo es utilizado también como identificador de los destructores ( 4. Viene a ser equivalente a la propiedad conmutativa de ciertos operadores aritméticos. Es decir. El primero (complemento ~) es el único operador unario (los demás son binarios). 1 y 2 son los que se expresan (para simplificar los representamos como un octeto): 0 == 0000 0000 1 == 0000 0001 2 == 0000 0010 ~ 0 == 1111 1111 ~ 1 == 1111 1110 ~ 2 == 1111 1101 . XOR y OR es independiente del orden de colocación de sus operandos. >>. Si los operandos no son enteros el compilador realiza la conversión pertinente ( 2.3) La librería Estándar C++ ha sobrecargado los operadores << y >> para los tipos básicos. << son sensibles al contexto.9.1a). §2 ~ Complemento a uno (palabra clave compl) Este operador unitario invierte cada bit del operando. Son los que cuentan con un símbolo y una palabra clave.8). 0 es convertido en 1 y viceversa. Tenga en cuenta que el resultado de este operador cambia el signo del operando.11b). de ahí el "signed". dependiendo del tipo de entero utilizado. signed int s2 = ~s1 + 2. y declarador de referencia ( 4.2. La Librería Estándar de plantillas (STL) dispone de una clase específica bitset ( 5. todos ellos exigen operandos de tipo entero int ( 2. Algunos de estos operadores.3. tienen una doble posibilidad de representación en el C++ Estándar ( 4. signed int s1 = compl 2. signed o unsigned) y enumeraciones ( 4.1) El resultado de los operadores AND.5). el complemento a uno de 2 es asignado al entero con signo s1. de forma que pueden ser utilizados como operadores de salida y entrada ( 5. A pesar de su nombre: "Operadores para manejo de bits". que puede ser de cualquiera de sus variantes (short. long. Sintaxis ~cast-expresion Ejemplo signed int s1 = ~2.8) En lo relativo al tratamiento del signo. el material de partida son bytes.2d2) No confundir los operadores de bits. & puede ser también el operador de referencia de punteros ( 4. los bits que salen por la izquierda se pierden. } Salida: ~0 == -1 ~1 == -2 ~2 == -3 §3 << Desplazamiento a izquierda Este operador binario realiza un desplazamiento de bits a la izquierda.En el epígrafe dedicado a las Formas de Representación binaria ( 2. cout << "~2 == " << ~dos << endl.4a) se indicó que en C++Builder.2. los que entran por la derecha se rellenan con ceros. los tipos enteros negativos se representan internamente como complemento a dos.h> short signed cero = 0. Este tipo de desplazamientos se denominan lógicos en contraposición a los cíclicos o rotacionales. que: ~ 0 == -1 ~ 1 == -2 ~ 2 == -3 Para verificarlo. El operando derecho indica el número de desplazamientos que se realizarán. El bit más significativo (más a la izquierda) se pierde. de forma que la representación interna de -1. cout << "~1 == " << ~uno << endl. y se le asigna un 0 al menos significativo (el de la derecha). int main (void) { cout << "~0 == " << ~cero << endl. dos = 2. -2 y -3 es: -1 == 1111 1110 + 0000 0001 == 1111 1111 -2 == 1111 1101 + 0000 0001 == 1111 1110 -3 == 1111 1100 + 0000 0001 == 1111 1101 Se comprueba así. escribimos un pequeño programa: #include <iostream. Recuérdese que los desplazamientos no son rotaciones. uno = 1. Sintaxis expr-desplazada << expr-desplazamiento Comentario . debe ser un entero positivo y menor que la longitud del primer operando. int main (void) cout << "0 << cout << "1 << cout << "2 << } Salida: 0 << 1 == 0 1 << 1 == 2 2 << 1 == 4 Como puede comprobar el lector con cualquier otro ejemplo. dos = 2. 1 == " << (dos << 1) << endl. Ambos operandos deben ser números enteros o enumeraciones ( 4. los desplazamientos izquierda de valor unitario aplicados sobre los números 0. uno = 1. Según las premisas anteriores. Ejemplo unsigned long x = 10. el resultado del desplazamiento izquierda equivale a multiplicar por 2 el valor de laexpr-desplazada. producen los siguientes resultados: 0 == 0000 0000 1 == 0000 0001 2 == 0000 0010 -3 == 1111 1101 0 << 1 == 0000 0000 == 0 1 << 1 == 0000 0010 == 2 2 << 1 == 0000 0100 == 4 -3 << 1 == 1111 1010 == . El resultado del desplazamiento de 2 bits sobre el unsigned long x es asignado al unsigned long y sin que sea necesario ningún "casting" para el tipo resultante. En caso contrario. 1. el compilador realiza una conversión automática de tipo. El resultado es del tipo del primer operando. { 1 == " << (cero << 1) << endl. unsigned long z = x << y. 2 y -3.El patrón de bits de expr-desplazada sufre un desplazamiento izquierda del valor indicado por la expr-desplazamiento. int y = 2.8). 1 == " << (uno << 1) << endl. En caso contrario el resultado es indefinido (depende de la implementación). utilizamos una versión del programa anterior: #include <iostream.h> short signed cero = 0. expr-desplazamiento. una vez promovido a entero.6 Para comprobarlo. §4 >> Desplazamiento a derecha . Por ejemplo: 0 == 0000 0000 2 == 0000 0010 -2 == 1111 1110 -16 == 1111 0000 0 >> 1 == 0000 0000 == 0 2 >> 1 == 0000 0001 == 1 -2 >> 1 == 1111 1111 == -1 -16 >> 2 == 1111 1100 == -4 (C++Builder & GNU-C++) (C++Builder & GNU-C++) Para comprobar las relaciones anteriores. el comportamiento de este operador es análogo al anterior (desplazamiento izquierda). utilizamos una versión modificada del programa anterior: #include <iostream. el resultado depende de la implementación. Comentario: El patrón de bits de expr-desplazada sufre un desplazamiento derecho del valor indicado por la expr-desplazamiento. mdos = -2. En caso contrario. cout << "2 >> 1 == " << (dos >> 1) << endl.h> short signed cero = 0. pero hay que advertir que si exprdesplazada es un entero con signo y es negativo. El bit menos significativo (a la derecha) se pierde.Sintaxis expr-desplazada >> expr-desplazamiento Ejemplo: unsigned long x = 10. Una vez promovida a entero. el resultado es indefinido (depende de la implementación). ambos operandos deben ser números enteros o enumeraciones. En caso contrario.4a). el signo se mantiene. Por lo demás. Nota: en C++Builder y GNU-C++. dos = 2. expr-desplazamiento debe ser un entero positivo y menor que la longitud del primer operando. cout << "-2 >> 1 == " << (mdos >> 1) << endl. el nuevo bit más significativo será 0 si se trata de un número positivo y 1 si el número es negativo ( 2. unsigned long z = x >> 2. el compilador realiza una conversión automática de tipo. Como en el caso anterior.2. } Salida: . lo que significa que el desplazamiento se realiza contando con el signo. int main (void) { cout << "0 >> 1 == " << (cero >> 1) << endl. El resultado es del tipo del primer operando. // equivale a: int z = x bitand y. §5 & AND lógico (palabra clave bitand) Este operador binario compara ambos operandos bit a bit. y = 20. xory bitor. no sean tratadas como sinónimos de los operadores correspondientes. Ejemplos complementarios en la Librería de Ejemplos: "Manejo de bits" ( Almacenamiento" ( 2. En caso contrario. En este caso el resultado del AND lógico entre los enteros 10 y 20 se aplicaría al entero z. Según las reglas del enunciado.4) y "Orden de Nota: el compilador GNU gcc dispone de la opción de compilación -fno-operator-names.6a). 9. que se muestran a continuación.h> int main (void) { cout << "2 & -2 == " << (2 & -2) << endl. y como resultado devuelve un valor construido de tal forma. Sintaxis AND-expresion & equality-expresion Ejemplo: int x = 10. } Salida: 2 & -2 == 2 . que cada bits es 1 si los bits correspondientes de los operandos están a 1.0 >> 1 == 0 2 >> 1 == 1 -2 >> 1 == -1 Puede comprobarse que el resultado del desplazamiento derecha equivale a dividir por 2 el valor de la expr-desplazada. que permite que las palabras-clave bitand.2. el operador & aplicado entre los valores 2 y -2 resultaría: 2 == 0000 0010 -2 == 1111 1110 -----------------0000 0010 == 2 Comprobación: #include <iostream. el bit es 0 (ver ejemplo). int z = x & y. contiene el estado de 8 sensores de tipo ON/OFF (0 == OFF. while( 1 ) { // bucle continuo if ( *muestra & bit2y5 ) { proceso() } } §6 ^ XOR OR exclusivo (palabra clave xor) El funcionamiento de este operador binario es parecido al AND lógico . bit5 = 16. Sabemos también que 0000 0000 0000 0010 y 0000 0000 0001 0000 son los patrones de bits de los unsigned short 2 y 16.9) en la definición del puntero muestra. salvo que en este caso el resultado es 1 si ambos bits son complementarios (uno es 0 y el otro 1). En este caso nos interesa detectar cuando adopta el siguiente aspecto: 0XXX XXXX XXX1 XX1X. Con estos datos podemos construir en nuestro código el siguiente bucle: volatile unsigned short* muestra = valor. El valor depositado es ununsigned short que. al que llamaremos incognita. Sabemos ( 2. Sintaxis expr-OR-exclusiva ^ AND-expresion . La expresión puede ser: if (incognita & patron) { /* concordancia con el patrón */ } else { /* desacuerdo con el patrón */ } Ejemplo: Supongamos que una tarjeta de adquisición proporciona una señal en una posición de memoria de valor conocido. const unsigned short bit2y5 = 18. En caso contrario devuelve 0. en sus bits más bajos.2. Observe también que si la condición hubiese sido la activación indistinta de los sensores 2 ó 5. const unsigned short bit2 = 2. *muestra = 0. 1 == ON).4a) que el unsigned short tiene un patrón de bits: 0XXX XXXX XXXX XXXX. *muestra = 0. mediante comparación con un patrón cuya disposición de bits es conocida.1. Nos interesa disparar un proceso si los sensores 2 y 5 se activan simultáneamente. while( 1 ) { // bucle continuo if ( (*muestra & bit2) && (*muestra & bit5) ) { proceso() } } Observe el uso de volatile ( 4. el bucle podría ser: volatile unsigned short* muestra = valor.Este operador se utiliza para verificar el estado de los bits individuales de un número. Sabemos que el valor 0XXXX XXXX XXXX XXXX de la tarjeta debe ser cambiado entonces a 0XXXX XX1X XXXX XXXX. *muestra = 0. } } . while( 1 ) { // bucle continuo if ( (*muestra & bit2) && (*muestra & bit5) ) { if (*muestra ^ bit10) *muestra = *muestra + bit10. bit5 = 16. si su forma binaria es 0XXX XX0X XXXX XXXX. int z = x ^ y. } Salida: 7 ^ -2 == -7 Como ejemplo. el operador ^ aplicado entre los valores 7 y -2 resultaría: 7 == 0000 0111 -2 == 1111 1110 -----------------1111 1001 == -7 Comprobación: #include <iostream. Esto puede conseguirse sumando 512 al valor mostrado por la tarjeta siempre que el bit 10 sea cero. // equivale a: int z = x xor y. La condición para la acción es por tanto que se cumpla: [1] ( *muestra & 0000 0010 0000 0000 ) == 0. y = 20.Ejemplo: int x = 10. Según el enunciado. ya que entonces hay un 1 en la posición deseada ( 0XXX XX1X XXXX XXXX ). para que esta (que suponemos bidireccional) conecte un servomotor. debemos depositar un valor 1 en el bit 10 de la dirección de la tarjeta. const unsigned short bit2 = 2. En caso contrario no es necesario hacer nada. bit10 = 512. supongamos una continuación del anterior.h> int main (void) { cout << "7 ^ -2 == " << (7 ^ -2) << endl. añadiendo la condición de que cuando se produce la condición de disparo. Es decir. Para responder a la nueva condición. que es el valor que escribiremos en la posición de memoria correspondiente. modificamos adecuadamente el código del ejemplo anterior añadiéndole una sentencia: volatile unsigned short* muestra = valor. §7 | OR inclusivo (palabra clave bitor) Este operador binario tiene un funcionamiento parecido a los anteriores (AND y XOR). int z = x | y.h> int main (void) { cout << "6 | 13 == " << (6 | 13) << endl. el operador | aplicado entre los valores 6 y 13 resultaría: 6 == 0000 0110 -----------------0000 1111 == 15 Comprobación: #include <iostream. la sentencia state ^= 1. suponiendo que state sea un bool. cambia su valor de cierto a falso o viceversa. #define CS_VREDRAW 0x0001 #define CS_HREDRAW 0x0002 typedef unsigned short WORD. En caso contrario devuelve 0 (ver ejemplo). salvo que en este caso el resultado es 1 si alguno de ellos está a 1. // equivale a: Según el enunciado. y = 20. Sintaxis expr-OR-inclusiva | expr-OR-exclusiva Ejemplo: int x = 10. int z = x bitor y. con independencia de cual sea su estado previo. Por ejemplo.Este operador se utiliza frecuentemente para cambiar el estado de un valor lógico. } Salida: 6 | 13 == 15 13 == 0000 1101 Ejemplo #include <iostream> using namespace std. . cout << "Estilo: " << style << endl.void main() { // =============== WORD style = CS_HREDRAW | CS_VREDRAW. } Salida: Estilo: 3 .