Los operadores de desplazamiento también manipulan bits. El
operador de desplazamiento a izquierda (<<
)
produce el desplazamiento del operando que aparece a la
izquierda del operador tantos bits a la izquierda como indique
el número a la derecha del operador. El operador de
desplazamiento a derecha (>>
) produce el
desplazamiento del operando de la izquierda hacia la derecha
tantos bits como indique el número a la derecha del operador. Si
el valor que sigue al operador de desplazamiento es mayor que el
número de bits del lado izquierdo, el resultado es
indefinido. Si el operando de la izquierda no tiene signo, el
desplazamiento a derecha es un desplazamiento lógico de modo que
los bits del principio se rellenan con ceros. Si el operando de
la izquierda tiene signo, el desplazamiento derecho puede ser un
desplazamiento lógico (es decir, significa que el comportamiento
es indeterminado).
Los desplazamientos pueden combinarse con el signo igual
(<<=
y >>=
). El
lvalue se reemplaza por
lvalue desplazado por el
rvalue.
Lo que sigue a continuación es un ejemplo que demuestra el uso de todos los operadores que involucran bits. Primero, una función de propósito general que imprime un byte en formato binario, creada para que se pueda reutilizar fácilmente. El fichero de cabecera declara la función:
//: C03:printBinary.h // Display a byte in binary void printBinary(const unsigned char val); ///:~
Listado 3.31. C03/printBinary.h
A continuación la implementación de la función:
//: C03:printBinary.cpp {O} #include <iostream> void printBinary(const unsigned char val) { for(int i = 7; i >= 0; i--) if(val & (1 << i)) std::cout << "1"; else std::cout << "0"; } ///:~
Listado 3.32. C03/printBinary.cpp
La función printBinary()
toma un único
byte y lo muestra bit a bit. La expresión:
(1 << i)
produce un uno en cada posición sucesiva de bit; en binario:
00000001
, 00000010
,
etc. Si se hace and a este bit con
val
y el resultado es diferente de cero,
significa que había un uno en esa posición de
val
.
Finalmente, se utiliza la función en el ejemplo que muestra los operadores de manipulación de bits:
//: C03:Bitwise.cpp //{L} printBinary // Demonstration of bit manipulation #include "printBinary.h" #include <iostream> using namespace std; // A macro to save typing: #define PR(STR, EXPR) \ cout << STR; printBinary(EXPR); cout << endl; int main() { unsigned int getval; unsigned char a, b; cout << "Enter a number between 0 and 255: "; cin >> getval; a = getval; PR("a in binary: ", a); cout << "Enter a number between 0 and 255: "; cin >> getval; b = getval; PR("b in binary: ", b); PR("a | b = ", a | b); PR("a & b = ", a & b); PR("a ^ b = ", a ^ b); PR("~a = ", ~a); PR("~b = ", ~b); // An interesting bit pattern: unsigned char c = 0x5A; PR("c in binary: ", c); a |= c; PR("a |= c; a = ", a); b &= c; PR("b &= c; b = ", b); b ^= a; PR("b ^= a; b = ", b); } ///:~
Listado 3.33. C03/Bitwise.cpp
Una vez más, se usa una macro de preprocesador para ahorrar líneas. Imprime la cadena elegida, luego la representación binaria de una expresión, y luego un salto de línea.
En main()
, las variables son
unsigned
. Esto es porque, en general, no se desean
signos cuando se trabaja con bytes. Se debe utilizar un
int
en lugar de un char
para
getval
porque de otro modo la sentencia
cin >>
trataría el primer dígito como un
carácter. Asignando getval
a
a
y b
, se convierte el
valor a un solo byte (truncándolo).
Los operadores <<
y >>
proporcionan un comportamiento de desplazamiento de bits, pero
cuando desplazan bits que están al final del número, estos bits
se pierden (comúnmente se dice que se caen en el mítico
cubo de bits, el lugar donde acaban los
bits descartados, presumiblemente para que puedan ser
utilizados...). Cuando se manipulan bits también se pueden
realizar rotaciones; es decir, que los bits
que salen de uno de los extremos se pueden insertar por el otro
extremo, como si estuviesen rotando en un bucle. Aunque la
mayoría de los procesadores de ordenadores ofrecen un comando de
rotación a nivel máquina (se puede ver en el lenguaje
ensamblador de ese procesador), no hay un soporte directo para
rotate en C o C++. Se supone que a los
diseñadores de C les pareció justificado el hecho de prescindir
de rotate (en pro, como dijeron, de un
lenguaje minimalista) ya que el programador se puede construir
su propio comando rotate. Por ejemplo, a
continuación hay funciones para realizar rotaciones a izquierda
y derecha:
//: C03:Rotation.cpp {O} // Perform left and right rotations unsigned char rol(unsigned char val) { int highbit; if(val & 0x80) // 0x80 is the high bit only highbit = 1; else highbit = 0; // Left shift (bottom bit becomes 0): val <<= 1; // Rotate the high bit onto the bottom: val |= highbit; return val; } unsigned char ror(unsigned char val) { int lowbit; if(val & 1) // Check the low bit lowbit = 1; else lowbit = 0; val >>= 1; // Right shift by one position // Rotate the low bit onto the top: val |= (lowbit << 7); return val; } ///:~
Listado 3.34. C03/Rotation.cpp
Al intentar utilizar estas funciones en
Bitwise.cpp
, advierta que las definiciones
(o cuando menos las declaraciones) de rol()
y ror()
deben ser vistas por el compilador
en Bitwise.cpp
antes de que se puedan
utilizar.
Las funciones de tratamiento de bits son por lo general extremadamente eficientes ya que traducen directamente las sentencias a lenguaje ensamblador. A veces una sentencia de C o C++ generará una única línea de código ensamblador.