Bitwise Operators
Integers in a computer are just sequences of bits. Bitwise operators let you work at that level.
They’re not something you’ll use in every program, but nothing else will do when you need direct control over individual bits.
Writing and printing binary values
Recall that C++ lets you write integer literals in binary with the 0b prefix (since C++14):
On line 5, flags is initialized with a binary literal.
On line 6, std::bitset<8>(flags) prints flags as 8 binary digits. We use this throughout the lesson to see what each operator does to the bits.
Bitwise operators
There are six bitwise operators in C++. Here’s what each one does:
Bitwise AND (&)
a & b produces a number with a 1 in each bit position where both a and b have a 1.
Bitwise AND applied to two 8-bit values.
Here’s how it appears in code:
Notice in the output that where mask has a 0 bit, the resulting bit is always 0; where mask has a 1 bit, the corresponding bit from status remains unchanged. This technique is called masking, where the mask is chosen carefully to reveal part of the data and suppress the rest of it.
Bitwise OR (|)
a | b produces a 1 in all bit positions where at least one of a or b has a 1.
Bitwise OR applied to two 8-bit values.
This code shows a simplified version of how file permissions work on Unix-like systems.
READ, WRITE, and EXECUTE are each flags: a flag is a value with exactly one bit set to 1. Here, a bit being set to 1 means permission is granted for that task: reading, writing, or executing a file.
Since no two flags share the same bit, you can combine them safely with |. Here, the value in permissions ends up as 00000011, meaning read and write permissions are both granted.
Bitwise XOR (^)
a ^ b produces a 1 only in bit positions where a and b differ.
XOR-ing a value with the same number twice always gives back the original value. That’s an idea used by simpler encryption schemes, where you apply the same operation to encrypt a piece of data and recover it later.
XOR-ing a value with the same number twice returns the original value.
Bitwise NOT (~)
~a inverts every bit in a. Every 0 becomes 1, every 1 becomes 0.
Left shift (<<)
Left shift moves every bit to the left by some number of positions. Zeros fill in the vacated positions on the right, and the bits that are pushed beyond the left boundary are discarded.
Now, each shift has the effect of doubling the value, as long as no 1 bits are pushed out of the left boundary. This is similar to how adding a zero to the end of a decimal number multiplies it by ten.
The following diagram shows what happens when the number 11 is shifted by two places. It results in the number 11x4 = 44.
Left shifting a by 2 positions, where a = 11, produces 44.
Right shift (>>)
Right shift moves every bit to the right by some number of positions. Each move to the right halves the value, dropping any remainder.
Shifting a signed integer can give you unexpected results because of how negative numbers are represented. You should always use
unsigned int(oruint8_t,uint32_tetc.) when shifting, not plainint.
Compound assignment forms
Like arithmetic operators, all the bitwise operators have compound assignment versions:
permissions |= READ; // Same as: permissions = permissions | READpermissions &= ~WRITE; // Sets the write bit to 0, leaves others unchangedstate ^= TOGGLE; // Toggle the bits in state where TOGGLE has a 1value <<= 3; // Shift left 3 placesvalue >>= 2; // Shift right 2 placesYou’ll see these a lot in low-level and embedded systems code.
Checkpoint
Which operator would you use to check whether a particular bit is 1 in a value?
You have a variable settings and want to turn on a feature represented by the flag DARK_MODE without affecting any other settings. Which line does that correctly?
You left shift the value 200 (11001000 in binary) by 1 position inside an 8-bit value. What happens?