Bitwise Operators
Bitwise operators work directly on the individual binary bits of an integer's representation, rather than treating it as a single numeric value. They are essential for flags, masks, low-level protocol work, and certain performance optimizations.
The Operators
Operator | Name | Behavior |
|---|---|---|
& | AND | Bit is 1 only if both corresponding bits are 1 |
| | OR | Bit is 1 if at least one corresponding bit is 1 |
^ | XOR | Bit is 1 if exactly one corresponding bit is 1 |
~ | NOT (complement) | Flips every bit (0 becomes 1, 1 becomes 0) |
<< | Left shift | Shifts bits left, filling with 0s (multiplies by 2 per shift) |
Right shift | Shifts bits right (divides by 2 per shift for unsigned/positive values) |
Binary Representation
int is stored as a sequence of bits. For example, the number{' '}
5 in an 8-bit view is 00000101. Bitwise operators
act on these individual 0s and 1s directly.
#include <iostream>
#include <bitset>
int main() {
int a = 5; // 00000101
int b = 3; // 00000011
std::cout << std::bitset<8>(a & b) << std::endl; // 00000001 -> 1
std::cout << std::bitset<8>(a | b) << std::endl; // 00000111 -> 7
std::cout << std::bitset<8>(a ^ b) << std::endl; // 00000110 -> 6
std::cout << std::bitset<8>(~a) << std::endl; // 11111010 (all bits flipped)
std::cout << (a << 1) << std::endl; // 10 (5 * 2)
std::cout << (a >> 1) << std::endl; // 2 (5 / 2, truncated)
return 0;
}Practical Use Cases
Bitmasks/flags — pack many independent boolean options into a single integer
Fast multiply/divide by powers of two —
x << 1doublesx,x >> 1halves it (for non-negative integers)Toggling and testing individual bits — hardware registers, compact state representations, checksum/hash algorithms
Worked Example: Bit Flags for Settings
#include <iostream>
#include <cstdint>
// Each flag occupies one distinct bit, so they can be combined with |
constexpr uint8_t FLAG_VISIBLE = 1 << 0; // 0001
constexpr uint8_t FLAG_ENABLED = 1 << 1; // 0010
constexpr uint8_t FLAG_SELECTED = 1 << 2; // 0100
int main() {
uint8_t settings = 0;
settings |= FLAG_VISIBLE; // turn a flag on
settings |= FLAG_ENABLED;
bool isVisible = settings & FLAG_VISIBLE; // test a flag
std::cout << "Visible: " << isVisible << std::endl;
settings &= ~FLAG_ENABLED; // turn a flag off (AND with the complement)
bool isEnabled = settings & FLAG_ENABLED;
std::cout << "Enabled: " << isEnabled << std::endl;
settings ^= FLAG_SELECTED; // toggle a flag
return 0;
}std::bitset<N> from <bitset> is a clearer, safer alternative — it gives you named bit access, bounds-checked indexing, and readable printing, while still being just as efficient under the hood.#include <bitset>
#include <iostream>
int main() {
std::bitset<8> settings; // all bits start at 0
settings.set(0); // turn bit 0 on (like FLAG_VISIBLE)
settings.set(1); // turn bit 1 on (like FLAG_ENABLED)
std::cout << settings << std::endl; // 00000011
std::cout << settings.test(0) << std::endl; // true
settings.reset(1); // turn bit 1 off
settings.flip(2); // toggle bit 2
return 0;
}>>) is implementation-defined behavior in older standards (arithmetic vs logical shift) and was only fully standardized as arithmetic shift in C++20. Avoid shifting negative values unless you have confirmed your target standard and compiler behavior.