013 Programacion Con El Lenguaje C++
013 Programacion Con El Lenguaje C++
1 Alineacin interna
1 Sinopsis Los ajustes de alineacin interna descritos en el apartado anterior ( 4.6), no solo ocurren en los campos de bits; tambin en otras estructuras de datos. Por ejemplo, en el captulo correspondiente a Iniciacin de estructuras ( 4.5.2) se present un caso concreto en el que, debido al proceso de alineacin interna realizado por el compilador, el tamao real de una estructura no coincide con el terico. Como se indica a continuacin, 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 cuestin, pero la posibilidad no es exclusiva de Borland, por ejemplo, el MS Visual C++ tambin dispone de una opcin equivalente.
2 Alineacin 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 = Cudruple palabra (8 bytes); 16 = Prrafo (16 bytes). Por defecto, en ausencia de otra indicacin, se supone -a4, es decir, una alineacin de doble palabra (mltiplos de 32 bits). Las modalidades de alineacin palabra (word), doble palabra (double word) y cudruple palabra (quad word) fuerzan a los elementos del tamao de un entero y mayores ( 2.2.4), a un ajuste en direcciones de memoria que son mltiplos del tipo elegido. En las estructuras se insertan bits extra para garantizar que los miembros se alinean adecuadamente.
3 Alineacin de Byte Comando: -a1 o -a-. En este tipo de alineacin el compilador no realiza ningn 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 alineacin conduce a programas ms compactos en memoria, pero tienden a ser ms 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 Alineacin 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 automticas son alineadas adecuadamente. Los tipos char (y unsigned char) pueden ser situados en cualquier direccin; todos los dems son alojados en direcciones pares.
5 Alineacin de doble palabra 4-bytes Comando: -a4 o -a. Con este tipo los datos distintos de carcter se alinean en palabras de 4 bytes (32 bits). Los datos con tipos menores que 4 bytes son alineados en el tamao correspondiente a su tipo.
6 Alineacin de cudruple palabra 8-bytes Comando: -a8. Con este tipo los datos distintos de carcter se alinean en palabras de 8 bytes (64 bits). Los datos con tipos menores que 8 bytes son alineados en el tamao correspondiente a su tipo.
7 Alineacin de prrafo 16-bytes Comando: -a16. Con la alineacin de prrafo tipo los datos distintos de carcter se alinean en palabras de 16 bytes (128 bits). Los datos con tipos menores que 16 bytes son alineados en el tamao 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 tamao de una unin es el del mayor elemento que puede albergar. El valor exacto depende de la implementacin y de las alineaciones internas ( 4.5.9a). Como se ver a continuacin, desde el punto de vista de su organizacin 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 programacin 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 utilizacin real es bastante rara y desaconsejada.
2 Declaracin La sintaxis de declaracin 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 unin y como consecuencia, dado que los campos de bits son dependientes de la implementacin, pueden presentar problemas para escribir cdigo portable.
b: Un objeto que tenga constructor o destructor no puede ser utilizado como miembro de una unin. 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 pblicos [2]. d: Las uniones solo pueden ser inicializadas en su declaracin 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=μ // definicin de unin de nombre miUnion
Aqu se ha definido un tipo nuevo de la clase unin, 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, cuestin esta que queda bajo responsabilidad del programador (esta es la razn 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 implementacin. Una unin no puede participar en la jerarqua 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 expresin del tipo Un.obj representa el objeto obj de la unin Un. La expresin es un Lvalue siempre que Un no lo sea y objno sea una matriz. -> Selector indirecto ( 4.9.16d Selector indirecto de miembro)
Una expresin del tipo uPtr -> obj representa el objeto obj de la unin Un siempre que uPtr sea un puntero a dicha unin. La expresin es un Lvalue siempre que obj no sea una matriz. Esta expresin 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 unin, en cuyo caso puede usarse el selector directo (expresin Un.obj), o un puntero, en cuyo caso puede usarse el indirecto (expresin uPtr->obj). En cualquier caso, es necesaria cierta atencin 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 patrn de bits que correspondera a mu.i coincide con parte deldouble previamente asignado en la primera sentencia, y su valor no tiene ningn sentido considerado aisladamente.
Como se ha sealado, las uniones comparten muchas caractersticas con las estructuras, incluyendo la notacin. Adems, con las uniones estn permitidas las mismas operaciones que con las estructuras: asignacin o copia como una unidad; obtener su direccin, y acceder a un miembro.
4 Punteros a uniones Cuando son modelados adecuadamente, los punteros a una unin sealan a cada uno de sus miembros y viceversa.
5 Tamao y alineacin Como en el resto de los objetos, su tamao puede ser establecido en tiempo de ejecucin con el operador sizeof ( 4.9.13) y como se ha sealado, corresponde con el mayor de los elementos que pueda almacenar. Lo mismo que en las estructuras, tambin aqu se producen alineaciones en las direcciones de memoria de los miembros. Por ejemplo, si nos referimos a la unin anterior, las sentencias sizeof(union miUnion) y sizeof(mu), ambas devuelven 8; este es el tamao en bytes de la unin, que corresponde a la opcin ms desfavorable, cuando se almacena un double, pero cuando mu almacena un intexisten 4 bytes desaprovechados, y 7 cuando se almacena un char.
6 Uniones annimas
Uniones annimas son aquellas que no tienen etiqueta y tampoco se utilizan para declarar un objeto nominado (con nombre). Tienen la forma general: union { lista-de-miembros }; Sus miembros pueden ser accedidos directamente en el mbito de su declaracin sin necesidad de usar ninguno de los operadores de acceso x.y o p->y. Ejemplo: #include <iostream.h> void main () { union { int i; double d; }; i = 25; cout << "El valor de '?.i' es: " << i << endl; } Salida: El valor de '?.i' es: 25
6.1 Las uniones annimas pueden ser globales, y anidadas o sin anidar [1]. Ejemplo: #include <iostream.h> static union { int i; double d; } ; void main i = 15; cout << union { i = 25; cout << cout << } Salida: El valor de '?.i' es: 15 El valor de '?.i' es: 25 El valor de '?.i' es: 15 () { "El valor de '?.i' es: " << i << endl; int i; double d; }; "El valor de '?.i' es: " << i << endl; "El valor de '?.i' es: " << ::i << endl;
6.2 Las uniones annimas no pueden tener funciones miembro ni miembros privados o protegidos (todos son pblicos). Si son globales deben ser declaradas estticas, y sin son locales pueden ser estticas o automticas ( 2.2.6). En otras palabras: las uniones annimas no pueden tener enlazado externo ( 1.4.4). Ejemplo:
#include <iostream.h> union { int i; double d; } ; void main () { cout << "El valor '?.i' es: " << i << endl; } Resulado de la compilacin: Borland C++: Error E2020 pru1.c 2: Global anonymous union not static Compilador GNU C++ 3.3.1 namespace-scope anonymous aggregates must be static
7 Uniones annimas anidadas La estructura, clase o unin exterior de una unin annima anidada debe tener un nombre. C++ permite uniones annimas anidadas. Por ejemplo: struct externa { // define estructura nominada externa int x; union { // unin annima anidada int y; char c; }; }; ... int main(void) { struct externa ex1; // ex1 es una instancia de externa return ex1.x + ex1.y; }
8 Uniones y otros tipos Las uniones pueden aparecer en matrices, estructuras y como miembros de clases. A su vez, tambin pueden contener a dichos elementos. Ver ejemplo ( 4.9.20b).
4.8 Enumeraciones
1 Sinopsis Las variables enumeradas, enumeraciones o ms abreviadamente enum (palabra reservada), 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 enumeracin, a cada una de las cuales se le asigna un valor ( 3.2.3g). Para mayor legibilidad del cdigo estos valores del rango se identifican por nemnicos. Por ejemplo, la declaracin: enum dia { DOM, LUN, MART, MIER, JUEV, VIER, SAB } diaX;
establece un tipo enum al que se identifica por dia. Las variables de este tipo pueden adoptar un conjunto de seis valores enteros 0, 1, 2, 3, 4, 5, 6 (enumeradores) representados por los nemnicos [2] DOM, LUN,...,SAB. Adems se define una variable diaX de este tipo (variable enumerada). En el ejemplo anterior, la variable diaX puede adoptar siete valores enteros (del 0 al 6). El programa los identifica con los nemnicos ya sealados. Ojo: no confundir la variable enumeracin- con los valores que puede adoptar -constante de enumeracin-. Cada enumeracin distinta constituye un tipo de enumerando diferente. Ejemplo: enum Calificacion {APTO, NO-APTO} c1; enum Evolucion {SUBE, BAJA, IGUAL} c2; c1 y c2 son objetos de tipo distinto.
2 Puede omitirse la palabra clave enum si dia no es identificador de nada ms en el mismo mbito. El compilador sabe que se trata de una variable tipo enum por la sintaxis de la propia declaracin. Por ejemplo, es correcto: Dia { DOM, LUN, MART, MIER, JUEV, VIER, SAB } diaX; El identificador Dia es la etiqueta opcional del tipo, y puede ser usada en subsecuentes declaraciones de variables del mismo tipo: enum Dia laborable, festivo; // declara dos nuevas variables
aunque tambin se podra omitir el especificador enum (el compilador ya sabe que Dia es de tipo enum): Dia laborable, festivo; // equivalente al anterior
3 Como ocurre con las declaraciones de estructuras y uniones, si no existen otras variables del mismo tipo, puede omitirse la etiqueta con lo que tenemos un tipo annimo: enum { DOM, LUN, MART, MIER, JUEV, VIER, SAB } diaX;
Tambin aqu (como ocurre con las estructuras y uniones), el inconveniente de declarar un tipo annimo es que despus no podemos volver a declarar otra variable del mismo tipo. Observe que las expresiones que siguen no son equivalentes!! enum diaX { DOM, LUN, MART, MIER, JUEV, VIER, SAB }; enum { DOM, LUN, MART, MIER, JUEV, VIER, SAB } diaX;
4 En C, una variable enumerada puede recibir cualquier valor de tipo int sin que se realice
ninguna otra comprobacin; en cambio C++ es ms fuertemente tipado en este sentido ( a una variable enumerada solo se le puede asignar uno de sus enumeradores. Ejemplo: diaX = LUN; diaX = 1; // Ok. // Ilegal, a pesar que LUN == 1
2.2), y
El conmutador -b del compilador Borland C++ ( 1.4.3) controla la opcin "Considerar las enumeraciones como enteros". Se refiere a que esta opcin controla el almacenamiento que asignar el compilador a las variables enumeradas. Cuando se activa la opcin, el compilador reserva una palabra (4 bytes en un compilador de 32 bits) para las variables enumeradas. El valor por defecto es ON, que significa que se les asignar el espacio de un entero, lo que por otra parte es lo mximo que pueden necesitar porque las constantes de enumeracin son estn forzosamente en el rango de los enteros. Si se desactiva (opcin -b-), el compilador comprueba el rango de los enumeradores y reserva el espacio estrictamente necesario segn el criterio de la tabla adjunta (ver al respecto: Representacin interna y rango 2.2.4); fuera de estos rangos asigna 32 bits: Rango de valores espacio tipo equivalente 0 a 255 -128 a 127 0 a 65,535 -32,768 a 32,767 8 bits 8 bits 16 bits 16 bits unsigned char signed char unsigned short signed short
Por su parte, los identificadores utilizados en la lista de enumeradores son implcitamente del tipo signed char, unsigned char o int, dependiendo de los valores asignados. Si todos los valores pueden ser representados con un signed o unsigned char, ese es el tipo de todos ellos.
Hay que recordar que, al ser mostradas por el dispositivo de salida, estas variables son promovidas a enteros y este es el valor mostrado. En el ejemplo anterior MART es mostrado como 2. En C++ se puede conseguir escribir el tipo enum sobrecargando adecuadamente el operador <<, (ver sobrecarga de operadores). La opcin por defecto es que el tipo enum es promovido a entero y se imprime el valor resultante.
5 Como puede verse, las enumeraciones proporcionan un modo muy flexible de asociar nombres con valores, algo que tambin puede hacerse con los #define ( 4.9.10b), sin embargo, las enumeraciones (an de un solo elemento) presentan ventajas [3] entre las que podemos sealar: Los valores pueden ser auto-generados Las variables de enumeradas ofrecen la posibilidad de comprobacin. El depurador puede ser capaz de imprimir los valores de los enumeradores en su forma simblica (lo que facilita grandemente las comprobaciones).
En la prctica, las enumeraciones proporcionan un lugar ("placeholder") muy conveniente para almacenar constantes enteras como cdigos de error o de cualquier otro tipo que podamos manejar en nuestras aplicaciones. Por ejemplo: en lugar de utilizar
0 1 2 3
Es ms conveniente utilizar un enumerador: enum ERRORS { NO_ERROR, NOT_FOUND, BUSY, FULL, ... } err;
6 Los tipos enum pueden aparecer en cualquier sitio donde sea permitido un entero. Ejemplo: enum dias { LUN, MAR, MIER, JUEV, VIER, SAB, DOM } diaX; enum dias diapago; typedef enum dias DIAS; DIAS *diaptr; int i = MIER; diaX = LUN; // Ok. *diaptr = diaX; // Ok. LUN = MIER; // ILEGAL: LUN es una constante!
7 Visibilidad Las etiquetas de los enum comparten el mismo espacio de nombres que los de estructuras y uniones. Los identificadores de los enumeradores comparten el mismo espacio que los identificadores de las variables ordinarias; de aqu se deduce que los nombres de los enumeradores de las distintas enumeraciones, tienen que ser diferentes (los valores no tienen porqu serlo ni an en la misma enumeracin). Ejemplo int dom = 11; { enum dias { dom, lun, mar, mier, juev, vier, sab } diaX; /* el enumerador dom oculta otra declaracion de int dom */ struct dias { int i, j;}; // ILEGAL: identificador dias ya usado double mar; // ILEGAL: redefinicion de mar } dom = 12; // int dom vuelve a ser visible
7.1 Los enumeradores declarados dentro de una clase tienen la visibilidad de la clase. Ejemplo class Date { public: std::string std::string std::string enum fomrat int fm;
}; ... void foo() { Date d1; d1.fm = Date::US; ... } Nota: es pertinente recordar aqu que, junto con las constantes estticas enteras, los enumeradores son las nicas propiedades que pueden inicializarse dentro de la definicin de la clase ( 4.11.2a)
8 Asignaciones a tipo enum En el compilador Borland C++ 5.5, las reglas para expresiones que contengan tipos enum pueden forzarse a ser ms estrictas activando el conmutador -A (que significa ANSI C++ estricto), con lo que el compilador refuerza las reglas con mensajes de error. As, al asignar un entero a una variable enum (como en el ejemplo) producira un error. enum color { Rojo, Verde, Azul }; int f() { color c; c = 0; // Incorrecto: a 'c' solo se le puede asignar Rojo, Verde o Azul return c; } En estos casos de compilacin estricta, se obtiene tambin un error cuando se pasa un entero como parmetro a una funcin que espera un enumo viceversa. Estudie el ejemplo propuesto teniendo en cuenta que el resultado de la expresin flag1|flag2 (OR inclusivo entre bits 4.9.3) es un entero. #include <iostream.h> enum En { flag1 = 0x01, flag2 = 0x02 }; int f1(En) { // L.3: return (flag1 + 1); } void f2() { int x = f1(flag1|flag2); // L.7: cout << "El valor es: " << x << endl; } int main () { f2(); return 0; } Aqu En es un tipo enum; los enumeradores flag1 y flag2 son constantes de enumeracin de valores 1 y 2 respectivamente, que han definidas en formato hexadecimal ( 3.2.3b).
Al intentar compilar se produce un error: Cannot convert 'int' to 'En' in function f2() ya que la invocacin a f1 en L7 espera unenum tipo En (como indica el prototipo de L3), pero se le pasa un int, ya que como se ha apuntado, la expresin flag1|flag2 produce un entero. Para arreglarlo, aplicamos un modelado de tipo ( 4.9.9) al argumento pasado a la invocacin en L.7, que lo transforma en el tipo exigido, con lo que la compilacin se realiza sin novedad. #include <iostream.h> enum En { flag1 = 0x01, flag2 = 0x02 }; int f1(En) { // L.3: return (flag1 + 1); } void f2() { int x = f1(En (flag1|flag2) ); // L.7bis: cout << "El valor es: " << x << endl; } int main () { f2(); } Salida: El valor es: 2
4.9 Operadores
1 Sinopsis Los operadores son un tipo de tokens ( 3.2) que pueden aparecer en las expresiones, e indican al compilador la realizacin de determinadas operaciones matemticas, lgicas ( 4.9.8) y numricas ( 4.9.1). Se aplican a variables u otros objetos denominados operandos y su efecto es una combinacin de las siguientes acciones: Producir un resultado-valor Alterar un operando Designar un objeto o funcin.
Ejemplos 1a y = a + b;
En esta sentencia, el operador suma + ( 4.9.1) produce un valor nuevo, pero no altera ninguno de los operandos (a y b); a su vez, el nuevo valor es asignado a la variable y mediante el operador de asignacin = ( 4.9.2). En este caso el operando de la izquierda s se ve alterado. 1b x++; Aqu el operador postincremento ++ ( 4.9.1) produce un nuevo valor que es aplicado sobre el propio operando, de forma que queda alterado. Cuando un operador altera un operando se dice que tiene efectos laterales. Nota: por lo general, los operadores aparecen a la derecha de expresiones de asignacin (por ejemplo: y = 2 * y + x), pero en ocasiones estos "efectos laterales" se utilizan para conseguir expresiones muy compactas y de un cierto nivel de sofisticacin, que incluso no necesitan del operador de asignacin para producir el resultado. En estos casos su lgica es un poco ms difcil de seguir que cuando estos efectos no se utilizan directamente (Ver ejemplo 4.9.6). 1c z = (*fptr)(x, y); Aqu, el operador de indireccin * es aplicado sobre el operando fptr que es un puntero-afuncin. El resultado es un designador de funcin al que se aplica el operador ( ) de invocacin de funcin con dos operandos x e y que actan como argumentos. A su vez el resultado de este operador (invocacin de funcin) es utilizado como argumento derecho del operador de asignacin = que finalmente modifica uno de sus operandos (el operando izquierdo z).
2 Clasificacin C++ dispone de un conjunto muy rico de operadores que se pueden clasificar en unitarios, binarios y ternarios, segn que necesiten uno, dos o tres operandos. Los operadores unitarios (denominados tambin unarios) se clasifican en dos tipos segn la posicin del operador (representado aqu por @) respecto del operando (representado aqu por n): @n: Operador prefijo. Ejemplo: ++x n@: Operador posfijo. Ejemplo: x++
Antes de seguir refirindonos a ellos, tal vez sean procedentes un par observaciones que ms adelante nos ayudarn a comprender mejor algunos conceptos: Los operadores pueden (en cierta forma) considerarse como funciones que tienen un lgebra un tanto especial. De hecho, al tratar la sobrecarga de operadores, veremos que precisamente la forma en que se definen es mediante una funcin, la funcin-operador ( 4.9.18). En este sentido podramos imaginar que la expresin y = a + b es otra forma de representar algo como: y = suma(a, b). Del mismo modo, la asignacin x = y sera algo
as: asigna(x, y). Siguiendo con nuestra analoga, podramos suponer que los operadores unitarios, binarios y ternarios seran funciones que aceptaran unos, dos o tres argumentos respectivamente y que la sobrecarga de operadores no es sino una forma encubierta de sobrecarga de funciones. Aunque las funciones C++ se designan con identificadores (nombres) que siguen las reglas generales ya sealadas ( 3.2.2), estas seudo-funciones se identifican con smbolos ( +, =, ::, *, ->, etc) [3]. Muchos de ellos estn compuesto de un solo token (que puede estar repetido + y ++) aunque los hay de dos. Estos ltimos son: new[], delete[], () y []. Antes de realizar la operacin encomendada, los operadores pueden modificar el tipo de los operandos para que sean homogneos, de forma que la operacin pueda realizarse. Por ejemplo, al indicar i + f donde i sea un entero y f un float. Estas promociones se denominan conversiones estndar ( 2.2.5) y son realizadas automticamente por el compilador, pero deben ser tenidas en cuenta para evitar sorpresas.
3 En general los operadores aceptan un tipo de operando determinado y especfico, produciendo y/o modificando un valor de acuerdo con ciertas reglas, pero C++ permite redefinir la mayora de ellos . Es decir, 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 bsicos preconstruidos en el lenguaje). Esta circunstancia recibe el nombre de sobrecarga ( 4.9.18) del operador [1]. No son sobrecargables los siguientes:
. Operador de seleccin de miembros ( .* Operador de dereferencia :: Operador de acceso a mbito ?: Operador conditional # Directiva de preprocesado ( ( ( (
4 Polimorfismo en los operadores Debe tener en cuenta que an sin estar sobrecargado, dependiendo del contexto (nmero y tipo de operandos), un operador C++ puede tener ms de un significado [2]. Veamos dos ejemplos: El smbolo mpersand & puede ser interpretado como: AND entre bits A&B ( 4.9.3) Operador de referencia y = &x ( 4.9.11b) Declarador de referencia int& y = x ( 4.2.3)
El asterisco * puede ser interpretado como: Declaracin de un tipo puntero-a tipo* ptr ( Operador de indireccin x = *ptr ( Operador de multiplicacin x*y ( 4.2.1a) 4.9.11a) 4.9.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 expresin (una especie de "orden de importancia" de los operadores). Por ejemplo: x = 2 + 3 * 4;
La primera regla en este sentido es que las expresiones se evalan de izquierda a derecha, aunque no siempre. Por ejemplo, la regla izquierda derecha nos conducira a que en la expresin anterior, el valor de x es 20, lo cual no es cierto, ya que en C++ la multiplicacin tiene mayor precedencia que la suma, as que el resultado es 14. En C++ existen 16 niveles de precedencia, de modo que la regla izquierda derecha solo se aplica en caso de operadores del mismo nivel ( 4.9.0a). El operador de jerarqua ms alta es el parntesis, lo que aclara y justificada la tradicional costumbre algabrica de utilizar parntesis para forzar el orden de las operaciones. Por ejemplo, si queremos que en la expresin anterior se ejecute primero el operador suma: x = (2 + 3) * 4; // el resultado es 20
Aunque las reglas de precedencia se suponen conocidas por los programadores C++, aconsejamos generosidad a la hora de utilizar parntesis en las expresiones. Adems de facilitar la legibilidad evitan posibles errores. As, aunque las dos sentencias que siguen son equivalentes, la segunda es de lectura ms "relajante". x = x = 2 + 3 * 4; 2 + (3 * 4);
4.9.2 Operadores de asignacin simple "=" y compuestos 4.9.3 Operadores para manejo de bits (bitwise) entre enteros: complemento,
desplazamientos izquierda y derecha, AND, XOR y OR
4.9.8 Operadores que producen resultados booleanos: AND, OR y NOT 4.9.10 Directivas # de preprocesado, #define, #line, #pragma, etc. 4.9.11 Operadores de indireccin (*) y de referencia (&) 4.9.12 Operadores de relacin: igual, desigual, menor, mayor, menor o igual,
mayor o igual
4.9.9a Aade o elimina la caracterstica const o volatile de un identificador 4.9.9c Convertir un puntero al tipo deseado 4.9.9d Reemplazar modelados para conversiones que son inserguras o
dependientes de la implementacin.
4.9.6 Operador ternario condicional 4.9.14 Obtener identificacin de tipos y expresiones en tiempo de ejecucin 4.9.5 Operador en la expresiones con coma.
sizeof
2 Los operadores unitarios solo requieren un operando y operan de izquierda a derecha (el operador a la izquierda, el operando a la derecha).
C++ dispone de los siguientes operadores unitarios: ! * & ~ ++ -+ Negacin lgica ( Indireccin ( Referencia (
4.9.8)
Complemento de bits (
4.9.1) 4.9.1) 4.9.20) y delete ( 4.9.21) 4.9.13) 4.9.6); el resto son binarios.
4 Recuerde que las operaciones lgicas producen siempre un resultado booleano. Es decir, cierto o falso ( 3.2.1b), lo que se traduce en un valor numrico cero o uno. Por contra, las operaciones entre bits producen valores arbitrarios.
1 Presentacin
Recordemos que el lenguaje C++ est compuesto por cinco tipos de elementos: palabrasclave; identificadores; constantes; operadores y signos de puntuacin ( 3). Con estos elementos se construyen las sentencias, cuya ejecucin es secuencial, excepto cuando aparecen elementos que modifican el flujo "natural" del programa. Son las sentencias de seleccin. Por ejemplo, if..else ( 4.10.2); las de iteracin ( 4.10.3), del tipo for..., o do... y las de salto ( 4.10.4) como return obreak. Considerando las sentencias como unidades de ejecucin, resulta relativamente fcil a cualquier estudiante conocer con exactitud el orden que seguir la ejecucin del programa en sus diversas bifurcaciones e iteraciones, as como la naturaleza de las operaciones involucradas. Sin embargo, la facilidad no es tanta cuando se considera cuales son las operaciones que se llevan a cabo "dentro" de las sentencias, y su orden. El asunto es importante porque muchas veces el resultado, o incluso la aparicin de errores de ejecucin y/o compilacin, depende justamente de estos detalles. Con los agravantes de que muchas de ellas son "ocultas", en el sentido que son realizadas automticamente por el compilador sin que tengamos constancia de su ocurrencia, y de que se siguen procesos distintos en compilacin y en ejecucin (por supuesto todos ellos pueden dar lugar a errores). En el presente captulo incluiremos algunas observaciones respecto a este asunto al que, en mi opinin, no se suele prestar la debida atencin a pesar de que en ciertos casos, su desconocimiento puede perfectamente mandarnos a limpiar cochineras a Carolina del Norte ( 1 ). Naturalmente no nos referimos a expresiones sencillas del tipo y = 2+x; sino a expresiones ms complejas. Por ejemplo: String S = s1+c1.*c1.pachar2+'C';
2 Evaluacin de expresiones En general, salvo que se relacionen con las mencionadas sentencias modificadoras del flujo, las palabras-clave sealan al compilador aspectos complementarios que noalteran el orden de ejecucin dentro de la propia sentencia. Este orden viene determinado por cuatro condicionantes: 1. Presencia de parntesis que obligan a un orden de evaluacin especfico. 2. Naturaleza de los operadores involucrados en la expresin (asociatividad). 3. Orden en que estn colocados (precedencia). 4. Providencias (impredecibles) del compilador relativas a la optimizacin del cdigo. En cuanto al primero, aunque el parntesis es un signo de puntuacin ( 3.2.6), podra considerarse como el operador de precedencia ms alta. Si existen parntesis, el compilador los evala en primer lugar. El segundo es especialmente importante, porque como veremos a continuacin, es precisamente su naturaleza la que establece dos propiedades importantes de los operadores: la asociatividad y la precedencia (3 ). El punto tercero es influyente porque a igualdad de precedencia, unos operadores se ejecutan en el orden en que aparecen escritos en el cdigo (de izquierda a derecha), y en otros casos es al contrario (dependiendo de su asociatividad). A su vez el punto cuarto encierra decisiones que son dependientes de la plataforma. Se refieren a medidas del compilador tendentes a la optimizacin del cdigo de la sentencia, que resultan incontrolables para el programador a no ser que adopte medidas especficas. Estas medidas suelen consistir en no simplificar demasiado las expresiones, y obtener resultados
intermedios, que solo son necesarios para obligar a una forma determinada de obtener el resultado.
2.1 La precedencia indica cual es el orden de ejecucin de los operadores cuando existen varios. Por ejemplo, en la expresin: a * b + c++; // L.1
la precedencia determina que se ejecutar primero el operador postincremento ++ sobre c. A continuacin se aplicar el operador de multiplicacin * entre a y b. Finalmente se aplicar el operador suma + entre los resultados anteriores. As pues, la expresin es equivale a: (a * b) + (c++); // L.2
Este orden "natural" del compilador no necesita parntesis de forma que las sentencias L1 y L2 producen el mismo resultado. Cualquier otro debe ser forzado especficamente mediante la utilizacin de los parntesis correspondientes. Por ejemplo: a * (b + c)++; // L.3
2.2 La asociatividad indica el orden de ejecucin cuando en una expresin existen diversos operadores de igual precedencia. Puede ser de dos tipos: izquierda () o derecha () [1]. Por ejemplo, la suma binaria + tiene asociatividad izquierda, lo que significa que en una expresin como: a + b + c + d; la ejecucin seguir el orden: (((a + b) + c) + d); Los operadores unarios y el de asignacin (=), tienen asociatividad derecha (). Todos los dems la tienen izquierda (). En consecuencia, si @ representa un operador binario, ante una expresin como: a @ b @ c @ d; el orden de evaluacin es desde la izquierda hacia la derecha. Pero si la expresin es del tipo: (...) @ (...) @ (...) @ (...); el orden de evaluacin de los parntesis es indefinido. Aunque una vez obtenidos todos los resultados parciales, la computacin sigue el orden indicado en el punto anterior. Si existen parntesis anidados se procede desde dentro hacia fuera: (((.1.)@(.1.)2) @ (.2.));
3 Precedencia y asociatividad de los operadores C++ En C++ existen 18 categoras de precedencia (ver tabla adjunta), algunas de las cuales contienen solo un operador. El orden en la tabla muestra las precedencias en orden descendente (misma lnea igual precedencia). Se ha incluido un nmero indicativo, los nmeros ms bajos tiene mayor precedencia que los ms altos. Cuando existen operadores duplicados en la tabla, la primera ocurrencia se refiere al operador unitario, la segunda al binario (por ejemplo, los casos de +, * y &). Esto significa que en la gramtica del C++, cuando un operador tiene dos formas, unaria y binaria, la primera tiene mayor precedencia que la segunda. Nota: el parntesis es un signo de puntuacin y no se incluye (el que se muestra en la tabla es el operador de invocacin de funcin), pero podra considerarse como el operador de precedencia ms alta (precedencia -1).
Operador
Asociatividad
.
--
typeid
& * sizeof new delete
Seleccin de miembro
%
13 14 15
|| ?: =
Pre-operador incremento y
Observe que los operadores de bits & ^ | (lneas 8, 9 y 10) tienen una precedencia menor que la igualdad == y desigualdad != (lnea 7), lo que supone que expresiones de comprobacin de bits como: if ((x & MASK) == 0) {...} deben ser adecuadamente parentizadas para garantizar que los resultados sean correctos.
Ejemplos ++ptr->x; Debido a que la precedencia del preincremento es menor que la del selector indirecto de miembro, la expresin anterior equivale a: ++(ptr->x); El resultado es que se incrementa el miembro x, no el valor del puntero ptr. *ptr->x; por lo mismo, esta indireccin equivale a *(ptr->x); El resultado depende del tipo de miembro x. Por ejemplo, si x es un int, se obtendra un error de compilacin (no puede obtenerse la indireccin de un int). Por el contrario, si es un puntero, se obtendra el objeto sealado por l.
4 Orden de evaluacin
Las reglas anteriores no definen completamente el orden de evaluacin de las expresiones, 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 optimizacin). Por ejemplo, en la expresin: (a + b) + ( c + d); es evidente que las subexpresiones (a + b) y (c + d) se resolvern antes que la suma central, pero no est definido cual de las dos se ejecutar en primer lugar [ 2]. El Estndar seala al efecto: "Con excepcin de los casos sealados, no est especificado el orden de evaluacin de los operandos de operadores individuales y subexpresiones de expresiones individuales, as como el orden en que ocurren los efectos laterales de las expresiones". En consecuencia, la mayora de compiladores C++ no garantizan el orden en que se evalan los operandos de una expresin (con excepcin de los operadores &&, ||, ?: y , ). Por lo general los compiladores tratan de arreglar las expresiones para mejorar la calidad general del cdigo, as que no puede especificarse el orden en que sern evaluados realmente los operandos a menos que algn operador lo establezca especficamente. Por esta razn hay que tener especial cuidado con expresiones en las que un valor sea modificado ms de una vez. Por lo general deben evitarse expresiones que simultneamente modifiquen y usen el valor de algn objeto. Por ejemplo, considere la expresin: i = v[i++]; donde i es indefinido cualquiera que sea su valor previo, porque su valor resultante depende de que sea incrementado antes o despus de la asignacin. De forma similar en: int total = 0; sum = (total = 3) + (++total); los valores sum y total son ambiguos (es sum = 4 sum = 7 ?). La solucin es redisear la expresin utilizando una variable temporal: int temp, total = 0; temp = ++total; sum = (total = 3) + temp; en cuyo caso la sintaxis fuerza un orden de evaluacin. Ejemplo: int x = cout << x = 5; cout << x = 5; cout << x = 5; cout << x = 5; cout << 5; x++ + 5 << "; x = " << x << endl; ++x + 5 << "; x = " << x << endl; x-- - x++ << "; x = " << x << endl; x-- - ++x << "; x = " << x << endl; ++x - x-- << "; x = " << x << endl;
Salidas:
Para entender los resultados es preciso considerar que los operadores incremento y decremento ++ y --, en sus versiones "pre" y "post", tienen mayor precedencia que las suma y resta binarias, por lo que se ejecutan primero. Pero los efectos laterales de estos ltimos (postincremento y postdecremento), solo tienen efecto despus que la expresin ha sido evaluada. Esto puede evidenciarse incluyendo parntesis en las expresiones anteriores y comprobando que se obtienen los mismos resultados: int x = cout << x = 5; cout << x = 5; cout << x = 5; cout << x = 5; cout << 5; (x++) + 5 << "; x = " << x << endl; ( ++x) + 5 << "; x = " << x << endl; (x--) - (x++) << "; x = " << x << endl; (x--) - ( ++x) << "; x = " << x << endl; ( ++x) - (x--) << "; x = " << x << endl;
En L.1, el valor 5 de x es sumado con la constante numrica 5, y el resultado es 10. Una vez que se ha completado la expresin, se produce el postincremento, con lo quex tiene el valor 6. En L.2 el proceso es anlogo, con la salvedad de que ahora el incremento de x se realiza antes de que la expresin sea evaluada, con lo que al valor 6 de x se le suma la constante 5 y el resultado es 11. En ambos casos, el valor de x despus de ejecutadas las sentencias L.1 y L.2 es 6. La aparente contradiccin en los valores de x mostrados por ambos compiladores, se deben a que el compilador Borland evala las expresiones del tipo cout << A << B << endl; de derecha a izquierda, mientras que GNU lo hace de izquierda a derecha [4]. Que el valor de x es 6 en ambos casos, puede ser comprobado aadiendo una sentencia inmediatamente despus de L.1 y L.2: cout << "El valor de x es ahora " << x << endl; Aqu el resultado obtenido con ambos compiladores es 6. Observe que en las tres salidas restantes, el valor resultante de x es 5 cualquiera que sea el orden de ejecucin de la sentencia, ya que el incremento y decremento se anulan entre s.
Las salidas L.4 y L.5 (en las que concuerdan ambos compiladores) son 0 porque, en ambos casos se efecta primero el preincremento. Al valor 6 de x se le resta el valor actual 6, con lo que el resultado es 0. Una vez evaluada la expresin, tienen efecto los resultados laterales. Es decir, el postdecremento de x, con lo que su valor vuelve a ser 5. La razn de la discrepancia de las salidas obtenidas en L.3, es de naturaleza anloga a la encontrada para los valores de x en las L.1 y L.2; la libertad que concede el Estndar respecto al orden de a la evaluacin de expresiones del tipo: (...) @ (...) @ (...) @ (...); En este caso no existe precedencia para decidir cual de las expresiones x++ o x-- se evaluar primero. Aparentemente el compilador Borland precede de derecha a izquierda y adems de forma errnea, dado que los resultados laterales solo deban manifestarse "despus" de evaluada la expresin. El resultado 0 de GNU es evidentemente el correcto. Al valor actual 5 de x, se le resta el valor actual (5) y el resultado es cero. A continuacin se producen el postdecremento y postincremento, cuyo resultado vuelve a ser 5 cualquiera que sea el orden de ejecucin adoptado. Nota: el "bug" de esta versin de Borland parece debido a que, en este caso, el compilador procede de derecha a izquierda, y el resultado (postincremento) tiene lugar "antes" que la resta, con lo que al valor resultante 6 de x, se le resta el (todava valor anterior 5). Esta teora y el error se confirman cor el resultado proporcionado por: x = 5; cout << x++ - x-- << "; x = " << x << endl;
// -> -1; x = 5
4.1 El siguiente ejemplo ofrece una idea de los procesos involucrados en la evaluacin de expresiones. void func(int x, int y, int z) { ... int r = x + y + (z + 'A'); } // L1: // L2:
Para ejecutar L2, el compilador intenta en primer lugar resolver la expresin del Rvalue, donde se encuentra con diversos identificadores y operadores. Antes que nada, durante la fase de compilacin, se realiza un "Name-lookup" ( 1.2.1) para averiguar "que cosa" son las etiquetas x, y, z (ya sabe que 'A' es una entidad tipo const chary que r es un int que se declara en la misma sentencia). A continuacin, suponiendo que ha encontrado en L1, que son tipos int, procede a codificar el resultado. Como el parntesis es el operador de mayor precedencia debe ejecutarlo en primer lugar, para lo cual intenta evaluar z + 'A'. Estamos ante un operador binario suma (+) con dos operandos: izquierdo y derecho. Aqu debemos sealar ( 4.9.18) que esta expresin es otra forma sintctica de la invocacin z.operator+('A'). Es la invocacin de un mtodo cuyo prototipo es: int int::operator+(int i); El mtodo operator+ de la clase de los enteros devuelve un entero y recibe un entero, as que el argumento actual 'A', un tipo const char, debe ser promovido al tipo del argumento formal, un tipo int ( 4.4.6). Suponiendo que esto sea posible, que lo es ( 2.2.5), se anotara el
resultado (int) devuelto por la funcin en un objeto temporal (lo llamaremos T1) y se procedera con el resto de la expresin. Para resolver x + y + T1, en la que todos los operadores + tienen igual precedencia, el compilador procede segn su asociatividad, que en este caso es izquierda. En primer lugar, resuelve x + y; el nuevo parcial (lo llamaremos T2) es alojado en un objeto creado al efecto. Finalmente, se realiza la operacin T2 + T1 con un nuevo resultado parcial T3, y seguira la ejecucin del resto de la sentencia. En este caso, la expresin int r = T3; supone la invocacin del constructor-copia ( 4.11.2d4) de la clase int.
Supongamos ahora un caso exactamente anlogo al anterior en el que los objetos x, y y z no sean tipos bsicos sino tipos abstractos pertenecientes a una clase Cdiseada por el usuario: void func(C x, C y, C z) { ... C r = x + y + (z + 'A'); } // 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, de forma que puede efectuarse la operacin z + 'A'. Por ejemplo, proporcionando un constructor-conversor en la clase ( 4.11.2d1). En este caso, las cosas funcionan de forma paralela a la descrita y el programa funciona sin dificultad [3]. Estamos tan contentos de lo bien que funciona nuestra clase, cuando un poco ms adelante se nos presenta una sentencia "muy" parecida a la anterior: C r2 = x + ('B' + y) + z; pero descubrimos con sorpresa que ahora el compilador lanza un extrao error: Illegal structure operation in.... Dependiendo de nuestro estado anmico ese da, podemos tardar ms o menos en descubrir que el problema radica precisamente en la computacin del parntesis (que aparentemente haba funcionado correctamente en otras sentencias). La cosa puede ser aun ms desconcertante si sabemos que el compilador dispone (y lo ha demostrado) de recursos para convertir en caso necesario un const char a C. Adems, en el contexto de la expresin resulta evidente que el resultado del parntesis "debe" ser un tipo C, y para ms INRI, el Rvalue (y) es tambin de este tipo... El problema es que (en el estado actual) el compilador no es an lo suficientemente inteligente. Al intentar evaluar la subexpresin 'B' + y no tiene en cuenta el contexto, e intenta encontrar una definicin de la asignacin que corresponda al prototipo: const char char::operator+(C c); Como evidentemente este mtodo no est definido para los tipos char, 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 definicin de suma para operandos de tipo C no sea conmutativa) la nica posibilidad es forzar nosotros mismos la conversin, para lo que existen dos alternativas:
C r = C('B'); C r2 = x + (r + y) + z;
C r2 = x + (static_cast<String>('B') + y) + z;
4.2 A continuacin se muestra otro ejemplo funcional de los insidiosos errores que pueden cometerse C++ cuando se olvida algn "pequeo" detalle. Considere el sencillo programa adjunto y sus sorpresivos resultados:
#include <iostream> using namespace std; int main() { // =============== int ai[] = {2, 3}; int z0 = ai[0] + ai[1]; // suma 2 + 3 cout << "Resultado z0 == " << z0 << endl; int* pt1 = ai; int z1 = *pt1; cout << "Resultado z1 == " << z1 << endl; int* pt2 = ai; int z2 = *(++pt2); cout << "Resultado z2 == " << z2 << endl; int* pt3 = ai; int z3 = *pt3 + *(++pt3); cout << "Resultado z3 == " << z3 << endl; int* pt4 = ai; int z4 = *(++pt4) + *pt4; cout << "Resultado z4 == " << z4 << endl; } Salida BC++ Builder 5.5, BC++ Builder 6, MS Visual C++ 6: Resultado Resultado Resultado Resultado Resultado z0 z1 z2 z3 z4 == == == == == 5 2 3 6 6
Salida Dev-C++ 4.9.9 GNU Compiler Collection G++ 3.3.1-20030804-1 Resultado Resultado Resultado Resultado Resultado z0 z1 z2 z3 z4 == == == == == 5 2 3 5 6
Comentario Los resultados z0 a z2 son los esperados, sin embargo, z3 y z4 son aparentemente errneos. La primera explicacin plausible es suponer que, en este caso, la evaluacin empieza de derecha a izquierda, pero precisamente el resultado z4 parece desmentirlo. La segunda explicacin sera suponer un error en el compilador. Sin embargo, las salidas anteriores son exactamente las mismas con BC++ Builder 5.5 que con MS Visual C++ 6.0 ( 7). Lo que ocurre realmente es que en ambos casos (z3 y z4), cualquiera que sea su posicin, derecha o izquierda, el compilador resuelve primero la expresin *(++pt3), y despus *pt3, con lo que al primer resultado (3) se le suma el valor de *pt3, pero entonces el puntero ya ha sido modificado, con lo que su valor es tambin 3. En cuanto a la salida del compilador GNU Cpp para Windows, parece evidente que su ejecucin, al menos en estas expresiones, es siempre de izquierda a derecha.
4.3 El lenguaje tampoco se garantiza el orden en que se evalan los argumentos de una funcin. Por ejemplo: printf ("%d %d\n", ++n, power(2, n)); // Incorrecto !!
puede producir diferentes resultados con diferentes compiladores. La solucin es: ++n; printf ("%d %d\n", n, power(2, n));
4.4 Aunque hemos sealado que, para mejorar la calidad del cdigo, C++ reagrupa las expresiones reordenando los operadores asociativos y conmutativos con independencia de parntesis, en las expresiones con coma el valor no queda en ningn caso afectado por la reordenacin. Por ejemplo: sum = (i = 3, i++, i++); // OK: sum = 4, i = 5
5 Errores y desbordamientos (overflow) Durante la evaluacin de una expresin pueden encontrarse situaciones problemticas, como divisin por cero o valores fraccionarios fuera de rango. El desbordamiento de enteros es ignorado n (C usa una aritmtica de base 2 en registros de n-bits), pero los errores detectados por las funciones de las libreras matemticas pueden ser controlados mediante rutinas estndar o de usuario. Ver _matherr y signal.
6 Expresiones con coma Las expresiones con coma son conjuntos de subexpresiones separadas por coma y agrupadas por parntesis. Cada subexpresin se evala una a continuacin de otra empezando por la izquierda, el resultado es el valor de la ltima. Ver ( 4.10.5)
Resto o mdulo
Nota: la aritmtica de nmeros reales es la clsica de la escuela primaria. La de punteros es una aritmtica un tanto especial y rudimentaria ( 4.2.2).
2 Observaciones Los operadores aritmticos pertenecen a dos grupos: unos aceptan operandos de tipo numrico; otros aceptan operandos de tipo puntero-a-tipoX [3]. Adems son de dos tipos; unarios (que aceptan un solo operando) y binarios (que aceptan dos). La clasificacin es la siguiente: Operadores aritmticos unarios: + ++ -ms unitario. Incremento unitario (dos clases) menos unitario. Decremento unitario (dos clases)
Operadores artimticos binaros: + * / % Suma binaria. Resta binaria. Multiplicacin Divisin. Resto o mdulo.
Estos ltimos pueden combinarse con el de asignacin = para dar origen a operadores compuestos ( 4.9.2) son los siguientes:
+= -= *= /= %=
Asigna suma Asigna diferencia (resta) Asigna producto Asigna divisin Asigna resto (mdulo)
Tenga en cuenta que existen distintos operadores enmascarados bajo los mismos smbolos + y -. Es un caso de sobrecarga incluida en el propio lenguaje [1]. Como en el resto de los casos de sobrecarga, el compilador deduce por el contexto de que versin del operador se trata. Como veremos inmediatamente, en C++ es perfectamente vlida una expresin del tipo: int x = *ptr+-*++ptr; 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, 3}; = ai; +ai[0]; -ai[1] ai[0] + ai[1]; ai[1] - ai[0] ai[0] + -ai[1]; *ptr + -*++ptr;
// // // // //
ms unitario sobre tipo numrico menos unitario sobre tipo numrico 2 + 3 suma binaria (de enteros) 3 - 2 resta binaria (de enteros) 2 +(-3) suma binaria seguida de menos
En L.7 coexisten tres operadores aritmticos no homogneos (de izquierda a derecha): + Suma binaria entre valores numricos tipo int ( Negacin unitaria de un valor numrico tipo int ( ). Los valores *ptr y -*++ptr ); el valor *(++ptr)
Nota: aunque vlida, la sentencia de L.7 es un ejemplo de expresin peligrosa y desaconsejada. En 4.9.0a se ha presentado una explicacin del sorpresivo resultado ( 0 ) que se obtiene para L.6.
3 Suma y resta binaria En el primer caso: suma y resta binaria, caben dos posibilidades sintcticas: a- expresin-suma + expresin-de-multiplicacin b- expresin-suma - expresin-de-multiplicacin
Las posibilidades para los operandos en la expresin A + B son los siguientes: 1. A y B son tipos aritmticos, enteros o fraccionarios ( 2.2.1). En este caso ambos operandos estn sujetos a las posibles conversiones aritmticas estndar ( 2.2.5) y el resultado es la suma aritmtica de ambos. Ejemplo: int x = 10, y = 20; int z = x + y;
// z == 30
2. A es un entero y B es un puntero a objeto. Ejemplo: int arr[5] = {1, 2, 3, 4, 5}; int* ptr = &arr[0]; // Seala a 1 int x = *(2 + ptr); // x == 3 3. A es un puntero a objeto y B es un entero. En estos dos ltimos casos se aplican las reglas de aritmtica de punteros ( 4.2.2). Ejemplo: int z = *(ptr + 3); // x == 4
3.2 Operador Resta binaria. Las posibilidades para los operandos en la expresin A - B son los siguientes: 1. A y B son de tipo aritmtico, entero o fraccionario; las posibilidades son las mismas que en el caso 1 de la suma binaria expuesta anteriormente. El resultado es la resta aritmtica de ambos operandos. Ejemplo: int x = 10, y = 20; int z = x - y;
// z == -10
2. A y B son punteros a objetos de tipos compatibles. Ejemplo: int arr[5] = {1, 2, 3, 4, 5}; int* pt1 = &arr[0]; // Seala a 1 int* pt2 = &arr[4]; // Seala a 5 int x = pt2 - pt1; // x == 4 3. A es un puntero a objeto y B es un entero. En estos dos ltimos casos se aplican las reglas de aritmtica de punteros ( 4.2.2). Ejemplo: int arr[5] = {1, 2, 3, 4, 5}; int* ptr = &arr[4]; // seala a 5 int x = *(ptr - 2); // x == 3
4 Operadores Unitarios
Cuando los operadores + y - se utilizan como operadores unitarios, las posibilidades sintcticas son: + <expresin-cast> - <expresin-cast> En ambos casos <expresin-cast> debe ser de tipo numrico. Los resultados son respectivamente: Valor del operando expresin-cast despus de cualquier promocin interna que sea necesaria. Valor negativo del operando expresin-cast despus de cualquier promocin interna que se necesite.
Nota: recuerde que cuando + y - se utilizan como operadores unitarios, tienen mayor precedencia que cuando se utilizan como suma y resta binarias ( 4.9.0a). Ejemplo int int int int int x = 7, r1 = r2 = + r3 = r4 = + y = 3; (y - x); (y - x); (x - y); (x - y);
// // // //
r1 r2 r3 r4
== == == ==
4 -4 -4 4
5 Operadores multiplicacin y divisin Los operadores binarios * (multiplicacin) y / (divisin) realizan sus operaciones aritmticas correspondientes con todos los tipos numricos (enteros y fraccionarios). Sintaxis expresin-de-multiplicacin * expresin-cast expresin-de-multiplicacin / expresin-cast
6 Operador mdulo El operador binario % (operador de mdulo) devuelve el resto de la divisin de dos enteros, no puede ser utilizado con nmeros fraccionarios floato double [2]. Sintaxis expresin-de-multiplicacin % expresin-cast Ejemplo int resto = (6 % 4); cout << "El resto de 6/4 es " << resto << endl;
7 Operadores incremento y decremento Los operadores unitarios ++ (incremento) y -- (decremento), suman y restan respectivamente una unidad al valor de la expresin. Existen dos variedades "Pre" y "Post" para cada uno de ellos. Las posibilidades sintcticas son: postfix-expression ++ ++ expresin-unitaria postfix-expression --- expresin-unitaria (postincremento) (preincremento) (postdecremento) (predecremento)
En los ejemplos que siguen suponemos que originariamente n == 5. El postincremento aade uno a la expresin despus de que se ha evaluado: x = n++ ; // -> x == 5 y n == 6
El preincremento aade uno antes de que sea evaluada la expresin. x = ++n ; // -> x == 6 y n == 6
El postdecremento resta uno del valor de la expresin despus de que sea evaluada. x = n-- ; // -> x == 5 y n == 4
En ambos casos, el operando debe ser una variable, no una expresin. Por ejemplo: (x+y)++ es ilegal.
7.1 Para evidenciar la diferencia entre preincremento y postincremento, observe las salidas que se obtienen con este pequeo programa segn se van ejecutando las diversas lneas. Para interpretarlas correctamente debe tener en cuenta la precedencia de los operadores y recordar que, en todos los casos, el argumento recibido por printf es un puntero-a-carcter (char*). Tambin deben recordarse las reglas de mbito de los argumentos pasados a funciones. char * s = "Hola mundo"; printf("Letra: \"%c\"\n", printf("Letra: \"%c\"\n", printf("Letra: \"%c\"\n", printf("Letra: \"%c\"\n", printf("Letra: \"%c\"\n",
// // // // //
printf("Letra: \"%c\"\n", *++s); printf("Letra: \"%c\"\n", *s); Ver otros ejemplos en 4.10.3: ( Ejemplo-1); (
3 Comentario Todos ellos son operadores binarios, de los cuales, = es el nico de asignacin simple, los dems son operadores de asignacin compuestos.
Los seis primeros aceptan operandos de distinto tipo, mientras que los cinco ltimos: <<=, >>=, &=, ^= y |=, implican manejo de bits ( 4.9.3), por lo que sus operandos deben ser tipos int en sus distintas variantes.
3.1 No olvidar que las expresiones de asignacin producen un resultado. El tipo resultante es el de la <expr-unaria> (izquierda); el valor que se aplica es el determinado por <exprderecha> (el Rvalue 2.1.5). El valor resultante de <expr-unaria> despus de la asignacin, es el resultado de la expresin (el Lvalue 2.1.5). As pues, cualquiera que sea su sentido concreto, todos implican la "copia" de un bloque de memoria (que contiene el "resultado") en algn sitio (la direccin indicada por el Rvalue). Ejemplo: int num; float f = 3.14; num = f + 1; En este caso, el valor 4.14, resultado de <expr-derecha>, se aplica a num. Teniendo en cuenta que el tipo resultante debe ser el de num (un inten este caso), se realiza automticamente una conversin del float 4.14 a int (con prdida de precisin si es necesario), con el resultado de quenum recibe el valor 4 y el resultado de la asignacin es un int de valor 4.
3.2 Esta propiedad de las asignaciones de producir un resultado, es justamente la que permite expresiones de asignacin compuesta del tipo: A = B = C = D; y es ampliamente utilizada en la programacin de C++. Considere el bucle del siguiente ejemplo: while ( (ch = getchar() ) != 27 ) ; aprovechando que en este caso, el resultado de la asignacin es un char, se utiliza directamente en la clusula del while, resultando que el bucle se ejecuta indefinidamente hasta que se pulse la tecla ESC (escape).
3.3 En general las sentencias de asignacin tienen la forma: variable = expresion La parte izquierda (que tiene que ser un Lvalue no constante) adquiere el valor sealado en la expresin de la derecha, pero se mantiene el tipo original de la variable de la parte izquierda. En caso necesario se realiza una conversin de tipo (con prdida de precisin en su caso) del izquierdo al derecho. Nota: observe que el operador C++ de asignacin simple (=) se distingue de otros lenguajes como Pascal que utilizan el smbolo := para este operador. Observe tambin que la asignacin simple (=) utiliza un smbolo distinto del operador relacional de igualdad (==).
4 Las variables se pueden inicializar en el mismo momento de la declaracin (asignndoles un valor). Este es el nico caso en que se puede asignar una constante [2]. Ejemplo: int primero = 0; char ch = a; float balance = 123.456; char string[10] = "1234567890"; const float pi = 3.14159 ;
5 En la expresin E1 = E2, E1 debe ser un Lvalue modificable, en caso contrario puede obtenerse un mensaje de error: Lvalue required[1]. Por ejemplo, una vez declarado char a[10], ninguna de las dos sentencias que siguen es vlida: a = "0123456789"; a[10] = "0123456789"; La expresin de asignacin en s misma no es un Lvalue.
6 Si @ es un operador compuesto, la expresin E1 @= E2 equivale a E1 = E1 @ E2. Por ejemplo: E1 += E2 equivale a E1 = E1 + E2. Ejemplos x *= y x *= y + 2 /* equivalentes */ /* equivalentes */ x = x * y x = x * (y + 2)
Ejemplo operativo: #include <iostream> using namespace std; int main() { // =============== int sec = 3628; cout << sec << " segundos son "; int h = sec / (60*60); cout << h << " hora y "; sec %= 60*60; int m = sec / 60; cout << m << " minutos y "; sec %= 60; cout << sec << " segundos" << endl; } Salida: 3628 segundos son 1 hora y 0 minutos y 28 segundos
7 Tanto para las asignaciones simples como compuestas, ambos operandos E1 y E2 deben
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 aritmtico, cualificado o no y E2 es un tipo aritmtico. E1 es una estructura o unin cualificada o no, pero de tipo compatible con E2. E1 y E2 son punteros a versiones no cualificadas de tipos compatibles, el tipo de objeto sealado por E1 tiene todos los calificadores del tipo de objeto sealado por E2. E1 o E2 es un puntero a objeto o tipo incompleto, y el otro es un puntero a una versin cualificada o no cualificada de void. El tipo de objeto sealado por E1 tiene todos los calificadores del objeto sealado por E2. E1 es un puntero y E2 es un puntero nulo. No puede haber espacios entre los operadores compuestos (+ =) Hay ciertas condiciones, en que los operadores de asignacin no pueden aplicarse cuando se trata de propiedades de clases ( 4.9.2a).
Precaucines !! Es relativamente frecuente confundir el orden de escritura de estos operadores. Recuerde que, en todos los casos de asignacin compuesta, el smbolo "=" va a la derecha!!. Considere el siguiente ejemplo: #include <iostream.h> void main() { int x = 2, y int* z = &x; x =& y; x &= y; z =& y; cout << "x = cout << "y = cout << "z = } // ============== = 4; // Error !! // Ok. // Ok. Asignar un puntero!! // Comprobaciones.
" << x << endl; " << y << endl; " << z << endl;
Ciertos operadores de asignacin no son permitidos con propiedades de clase. Por ejemplo, el texto de una caja de texto es una propiedad, pero es una variable un tanto especial, se trata de una variable interna de la clase (privada) que solo puede ser accedida mediante un par de mtodos que la asignan y devuelven su valor. Por ejemplo, digamos que son SetText(t), para establecer su valor, y GerText(t) para obtenerlo, y que ambos mtodos devuelven "por valor", respectivamente. En esta hiptesis, consideremos los dos casos de asignacin siguientes: Edit1->Text = Edit1->Text + "nuevo"; Edit1->Text += "nuevo"; // Caso-1 // Caso-2
El caso 1, en la forma a = a + b, genera un cdigo similar a: SetText(GetText() + "nuevo"); que es correcto y proporciona el resultado esperado. El caso 2, en la forma a += b, genera un cdigo similar al siguiente: temp = GetText(); temp += "nuevo"; que desde luego no funciona como cabra esperar. La variable temporal es actualizada, pero la funcin SetText(temp), que actualizara la propiedad de la ventana de texto no es invocada.
XOR (OR exclusivo); compara dos bits OR inclusivo; compara dos bits *.
*.
El primero es un operador unario, los restantes son binarios. Los tres primeros realizan manipulaciones en los bits del operando. Los restantes realizan comparaciones lgicas entre los bits de ambos operandos, similares a las que realizan los operadores lgicos entre objetos booleanos ( 4.9.8). Recordar que:
Algunos de estos operadores, sealados con asterisco (*), tienen una doble posibilidad de representacin en el C++ Estndar ( 4.9.8). Son los que cuentan con un smbolo y una palabra clave. A pesar de su nombre: "Operadores para manejo de bits", todos ellos exigen operandos de tipo entero int ( 2.2.1a), que puede ser de cualquiera de sus variantes (short, long, signed o unsigned) y enumeraciones ( 4.8). Es decir, el material de partida son bytes, uno o varios, dependiendo del tipo de entero utilizado. Si los operandos no son enteros el compilador realiza la conversin pertinente ( 2.2.5). El resultado es siempre un entero del mismo tipo que los operandos. La Librera Estndar de plantillas (STL) dispone de una clase especfica bitset ( 5.1.1e1) para realizar operaciones de manejo de bits con entidades que no estn restringidas a las longitudes de los tipos enteros. El primero (complemento ~) es el nico operador unario (los dems son binarios). Este smbolo es utilizado tambin como identificador de los destructores ( 4.11.2d2) No confundir los operadores de bits, & y |, con los operadores lgicos && y || ( 4.9.8) En lo relativo al tratamiento del signo, &, >>, << son sensibles al contexto. & puede ser tambin el operador de referencia de punteros ( 4.9.11b), y declarador de referencia ( 4.2.3) La librera Estndar C++ ha sobrecargado los operadores << y >> para los tipos bsicos, de forma que pueden ser utilizados como operadores de salida y entrada ( 5.3.1) El resultado de los operadores AND, XOR y OR es independiente del orden de colocacin de sus operandos. Los operadores que gozan de esta propiedad se denominan asociativos. Viene a ser equivalente a la propiedad conmutativa de ciertos operadores aritmticos.
2 ~
Este operador unitario invierte cada bit del operando; 0 es convertido en 1 y viceversa. Sintaxis ~cast-expresion Ejemplo signed int s1 = ~2; signed int s1 = compl 2; signed int s2 = ~s1 + 2; // equivale a:
En la primera lnea, el complemento a uno de 2 es asignado al entero con signo s1. Tenga en cuenta que el resultado de este operador cambia el signo del operando, de ah el "signed". La representacin binaria de los los complementos a uno de los decimales 0, 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
En el epgrafe dedicado a las Formas de Representacin binaria ( 2.2.4a) se indic que en C++Builder, los tipos enteros negativos se representan internamente como complemento a dos, de forma que la representacin interna de -1, -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, que: ~ 0 == -1 ~ 1 == -2 ~ 2 == -3 Para verificarlo, escribimos un pequeo programa: #include <iostream.h> short signed cero = 0, uno = 1, dos = 2; int main (void) { cout << "~0 == " << ~cero << endl; cout << "~1 == " << ~uno << endl; cout << "~2 == " << ~dos << endl; } Salida: ~0 == -1 ~1 == -2 ~2 == -3
3 << Desplazamiento a izquierda Este operador binario realiza un desplazamiento de bits a la izquierda. El bit ms significativo (ms a la izquierda) se pierde, y se le asigna un 0 al menos significativo (el de la derecha). El operando derecho indica el nmero de desplazamientos que se realizarn. Recurdese que los desplazamientos no son rotaciones; los bits que salen por la izquierda se pierden, los que entran por la derecha se rellenan con ceros. Este tipo de desplazamientos se denominan lgicos en contraposicin a los cclicos o rotacionales. Sintaxis expr-desplazada << expr-desplazamiento Comentario
El patrn de bits de expr-desplazada sufre un desplazamiento izquierda del valor indicado por la expr-desplazamiento. Ambos operandos deben ser nmeros enteros o enumeraciones ( 4.8). En caso contrario, el compilador realiza una conversin automtica de tipo. El resultado es del tipo del primer operando. expr-desplazamiento, una vez promovido a entero, debe ser un entero positivo y menor que la longitud del primer operando. En caso contrario el resultado es indefinido (depende de la implementacin). Ejemplo unsigned long x = 10; int y = 2; unsigned long z = x << y; El resultado del desplazamiento de 2 bits sobre el unsigned long x es asignado al unsigned long y sin que sea necesario ningn "casting" para el tipo resultante. Segn las premisas anteriores, los desplazamientos izquierda de valor unitario aplicados sobre los nmeros 0, 1, 2 y -3, 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 == - 6
Para comprobarlo, utilizamos una versin del programa anterior: #include <iostream.h> short signed cero = 0, uno = 1, dos = 2; 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, el resultado del desplazamiento izquierda equivale a multiplicar por 2 el valor de laexpr-desplazada. { 1 == " << (cero << 1) << endl; 1 == " << (uno << 1) << endl; 1 == " << (dos << 1) << endl;
Sintaxis expr-desplazada >> expr-desplazamiento Ejemplo: unsigned long x = 10; unsigned long z = x >> 2; Comentario: El patrn de bits de expr-desplazada sufre un desplazamiento derecho del valor indicado por la expr-desplazamiento. Como en el caso anterior, ambos operandos deben ser nmeros enteros o enumeraciones. En caso contrario, el compilador realiza una conversin automtica de tipo. El resultado es del tipo del primer operando. Una vez promovida a entero, expr-desplazamiento debe ser un entero positivo y menor que la longitud del primer operando. En caso contrario, el resultado es indefinido (depende de la implementacin). El bit menos significativo (a la derecha) se pierde, pero hay que advertir que si exprdesplazada es un entero con signo y es negativo, el resultado depende de la implementacin. Nota: en C++Builder y GNU-C++, el signo se mantiene, lo que significa que el desplazamiento se realiza contando con el signo, el nuevo bit ms significativo ser 0 si se trata de un nmero positivo y 1 si el nmero es negativo ( 2.2.4a). Por lo dems, el comportamiento de este operador es anlogo al anterior (desplazamiento izquierda). 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, utilizamos una versin modificada del programa anterior: #include <iostream.h> short signed cero = 0, dos = 2, mdos = -2; int main (void) { cout << "0 >> 1 == " << (cero >> 1) << endl; cout << "2 >> 1 == " << (dos >> 1) << endl; cout << "-2 >> 1 == " << (mdos >> 1) << endl; } Salida:
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. Ejemplos complementarios en la Librera de Ejemplos: "Manejo de bits" ( Almacenamiento" ( 2.2.6a). 9.4) y "Orden de
Nota: el compilador GNU gcc dispone de la opcin de compilacin -fno-operator-names, que permite que las palabras-clave bitand, xory bitor, que se muestran a continuacin, no sean tratadas como sinnimos de los operadores correspondientes. 5 & AND lgico (palabra clave bitand) Este operador binario compara ambos operandos bit a bit, y como resultado devuelve un valor construido de tal forma, que cada bits es 1 si los bits correspondientes de los operandos estn a 1. En caso contrario, el bit es 0 (ver ejemplo). Sintaxis AND-expresion & equality-expresion Ejemplo: int x = 10, y = 20; int z = x & y;
En este caso el resultado del AND lgico entre los enteros 10 y 20 se aplicara al entero z. Segn las reglas del enunciado, el operador & aplicado entre los valores 2 y -2 resultara: 2 == 0000 0010 -2 == 1111 1110 -----------------0000 0010 == 2 Comprobacin: #include <iostream.h> int main (void) { cout << "2 & -2 == " << (2 & -2) << endl; } Salida: 2 & -2 == 2
Este operador se utiliza para verificar el estado de los bits individuales de un nmero, al que llamaremos incognita, mediante comparacin con un patrn cuya disposicin de bits es conocida. La expresin puede ser: if (incognita & patron) { /* concordancia con el patrn */ } else { /* desacuerdo con el patrn */ } Ejemplo: Supongamos que una tarjeta de adquisicin proporciona una seal en una posicin de memoria de valor conocido. El valor depositado es ununsigned short que, en sus bits ms bajos, contiene el estado de 8 sensores de tipo ON/OFF (0 == OFF, 1 == ON). Nos interesa disparar un proceso si los sensores 2 y 5 se activan simultneamente. Sabemos ( 2.2.4a) que el unsigned short tiene un patrn de bits: 0XXX XXXX XXXX XXXX. En este caso nos interesa detectar cuando adopta el siguiente aspecto: 0XXX XXXX XXX1 XX1X. Sabemos tambin que 0000 0000 0000 0010 y 0000 0000 0001 0000 son los patrones de bits de los unsigned short 2 y 16. Con estos datos podemos construir en nuestro cdigo el siguiente bucle: volatile unsigned short* muestra = valor; *muestra = 0; const unsigned short bit2 = 2, bit5 = 16; while( 1 ) { // bucle continuo if ( (*muestra & bit2) && (*muestra & bit5) ) { proceso() } } Observe el uso de volatile ( 4.1.9) en la definicin del puntero muestra. Observe tambin que si la condicin hubiese sido la activacin indistinta de los sensores 2 5, el bucle podra ser: volatile unsigned short* muestra = valor; *muestra = 0; const unsigned short bit2y5 = 18; 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 lgico , salvo que en este caso el resultado es 1 si ambos bits son complementarios (uno es 0 y el otro 1). En caso contrario devuelve 0. Sintaxis expr-OR-exclusiva ^ AND-expresion
Segn el enunciado, el operador ^ aplicado entre los valores 7 y -2 resultara: 7 == 0000 0111 -2 == 1111 1110 -----------------1111 1001 == -7 Comprobacin: #include <iostream.h> int main (void) { cout << "7 ^ -2 == " << (7 ^ -2) << endl; } Salida: 7 ^ -2 == -7
Como ejemplo, supongamos una continuacin del anterior, aadiendo la condicin de que cuando se produce la condicin de disparo, debemos depositar un valor 1 en el bit 10 de la direccin de la tarjeta, para que esta (que suponemos bidireccional) conecte un servomotor. Sabemos que el valor 0XXXX XXXX XXXX XXXX de la tarjeta debe ser cambiado entonces a 0XXXX XX1X XXXX XXXX, que es el valor que escribiremos en la posicin de memoria correspondiente. Esto puede conseguirse sumando 512 al valor mostrado por la tarjeta siempre que el bit 10 sea cero. Es decir, si su forma binaria es 0XXX XX0X XXXX XXXX. En caso contrario no es necesario hacer nada, ya que entonces hay un 1 en la posicin deseada ( 0XXX XX1X XXXX XXXX ). La condicin para la accin es por tanto que se cumpla: [1] ( *muestra & 0000 0010 0000 0000 ) == 0. Para responder a la nueva condicin, modificamos adecuadamente el cdigo del ejemplo anterior aadindole una sentencia: volatile unsigned short* muestra = valor; *muestra = 0; const unsigned short bit2 = 2, bit5 = 16, bit10 = 512; while( 1 ) { // bucle continuo if ( (*muestra & bit2) && (*muestra & bit5) ) { if (*muestra ^ bit10) *muestra = *muestra + bit10; } }
Este operador se utiliza frecuentemente para cambiar el estado de un valor lgico, con independencia de cual sea su estado previo. Por ejemplo, suponiendo que state sea un bool, la sentencia state ^= 1; cambia su valor de cierto a falso o viceversa.
7 | OR inclusivo (palabra clave bitor) Este operador binario tiene un funcionamiento parecido a los anteriores (AND y XOR), salvo que en este caso el resultado es 1 si alguno de ellos est a 1. En caso contrario devuelve 0 (ver ejemplo). Sintaxis expr-OR-inclusiva | expr-OR-exclusiva Ejemplo: int x = 10, y = 20; int z = x | y; int z = x bitor y;
// equivale a:
Segn el enunciado, el operador | aplicado entre los valores 6 y 13 resultara: 6 == 0000 0110 -----------------0000 1111 == 15 Comprobacin: #include <iostream.h> int main (void) { cout << "6 | 13 == " << (6 | 13) << endl; } Salida: 6 | 13 == 15
13 == 0000 1101
Ejemplo #include <iostream> using namespace std; #define CS_VREDRAW 0x0001 #define CS_HREDRAW 0x0002 typedef unsigned short WORD;
void main() { // =============== WORD style = CS_HREDRAW | CS_VREDRAW; cout << "Estilo: " << style << endl; } Salida: Estilo: 3