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 operando de la izquierda, 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 superiores se rellenan con ceros. Si el operando de la izquierda tiene signo, el desplazamiento derecho puede ser o no un desplazamiento lógico (es decir, el comportamiento es indefinido).
Los desplazamientos pueden combinarse con el signo igual (<<= y >>=). El valor izquierdo se reemplaza por el valor izquierdo desplazado por el valor derecho.
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 por separado 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 tecleo. 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 fuera del extremo 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 reutilizados...). 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 computadores 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
Pruebe a 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 se traducen directamente a sentencias de lenguaje ensamblador. A veces una sentencia de C o C++ generará una única línea de código ensamblador.