Every integer in your computer is stored as a sequence of bits — 0s and 1s. Bitwise operators let you work directly at that level, manipulating individual bits instead of whole numbers.
This sounds low-level, but you’ll encounter bitwise operations regularly: reading hardware flags, implementing permissions systems, optimizing code, working with graphics or networking. Understanding them unlocks a whole layer of how computers actually work.
The Binary Foundation
Before diving into operators, a quick refresher. The number 13 in binary is 1101:
Bit position: 3 2 1 0
Binary: 1 1 0 1
Value: 8 4 0 1 = 13
Bitwise operators compare or shift these individual bit positions.
Bitwise AND: &
AND compares two numbers bit by bit. The result bit is 1 only if both corresponding bits are 1.
#include <iostream>
using namespace std;
int main() {
int a = 12; // binary: 1100
int b = 10; // binary: 1010
int result = a & b; // binary: 1000 = 8
cout << result << endl; // 8
return 0;
}
Bit-by-bit:
1 1 0 0 (12)
& 1 0 1 0 (10)
---------
1 0 0 0 (8)
Common use: checking whether a specific bit is set.
int flags = 0b1011; // some status flags
// Is bit 1 set?
if (flags & 0b0010) {
cout << "Bit 1 is set" << endl;
}
Bitwise OR: |
OR compares two numbers bit by bit. The result bit is 1 if either bit is 1.
#include <iostream>
using namespace std;
int main() {
int a = 12; // binary: 1100
int b = 10; // binary: 1010
int result = a | b; // binary: 1110 = 14
cout << result << endl; // 14
return 0;
}
Common use: turning a specific bit on (setting a flag).
int permissions = 0b0000;
// Turn on the "read" bit (bit 2) and "execute" bit (bit 0)
permissions = permissions | 0b0101;
cout << permissions << endl; // 5
Bitwise XOR: ^
XOR (exclusive OR) returns 1 when the bits are different, and 0 when they’re the same.
#include <iostream>
using namespace std;
int main() {
int a = 12; // binary: 1100
int b = 10; // binary: 1010
int result = a ^ b; // binary: 0110 = 6
cout << result << endl; // 6
return 0;
}
Fun trick: XOR a number with itself always gives 0. XOR with the same value twice returns the original.
int x = 42;
cout << (x ^ x) << endl; // 0
cout << (x ^ 5 ^ 5) << endl; // 42 — the 5s cancel out
This makes XOR useful for simple encryption, swapping variables without a temp, and finding the unique element in an array.
Bitwise NOT: ~
NOT flips every bit in the number. If a bit is 1 it becomes 0, and vice versa.
#include <iostream>
using namespace std;
int main() {
int a = 12; // binary: ...0000 1100
int result = ~a; // binary: ...1111 0011 = -13
cout << result << endl; // -13
return 0;
}
The result is -13, not 3, because of how negative numbers are stored (two’s complement). ~x is always equal to -(x + 1).
Left Shift: <<
Left shift moves all bits to the left by a given number of positions. New bits on the right are filled with 0.
#include <iostream>
using namespace std;
int main() {
int a = 1;
cout << (a << 0) << endl; // 1 = 1 * 2^0
cout << (a << 1) << endl; // 2 = 1 * 2^1
cout << (a << 2) << endl; // 4 = 1 * 2^2
cout << (a << 3) << endl; // 8 = 1 * 2^3
cout << (a << 4) << endl; // 16 = 1 * 2^4
return 0;
}
Left shift by n is equivalent to multiplying by 2^n. This is faster than multiplication for powers of two.
Setting a specific bit:
// Set bit 4 (the 5th bit from the right)
int mask = 1 << 4; // binary: 0001 0000 = 16
Right Shift: >>
Right shift moves all bits to the right. For positive numbers, it’s equivalent to integer division by 2^n.
#include <iostream>
using namespace std;
int main() {
int a = 64;
cout << (a >> 1) << endl; // 32 = 64 / 2
cout << (a >> 2) << endl; // 16 = 64 / 4
cout << (a >> 3) << endl; // 8 = 64 / 8
return 0;
}
Practical Example: Permission Flags
A common real-world use is representing multiple on/off flags in a single integer. Here’s a simple file permission system:
#include <iostream>
using namespace std;
const int READ = 1 << 0; // 001
const int WRITE = 1 << 1; // 010
const int EXECUTE = 1 << 2; // 100
void checkPermissions(int perms) {
if (perms & READ) cout << "Can read" << endl;
if (perms & WRITE) cout << "Can write" << endl;
if (perms & EXECUTE) cout << "Can execute" << endl;
}
int main() {
int userPerms = READ | WRITE; // 011 = 3
checkPermissions(userPerms);
// Remove write permission
userPerms = userPerms & ~WRITE;
cout << "After removing write:" << endl;
checkPermissions(userPerms);
return 0;
}
Output:
Can read
Can write
After removing write:
Can read
Quick Reference
| Operator | Symbol | Result bit is 1 when… |
|---|---|---|
| AND | & | Both bits are 1 |
| OR | | | At least one bit is 1 |
| XOR | ^ | Bits are different |
| NOT | ~ | Bit was 0 (flip) |
| Left shift | << | Multiplies by 2^n |
| Right shift | >> | Divides by 2^n |
Related Articles
- C++ Variables and Data Types
- C++ Conditionals Tutorial: if, else, and switch Explained
- C++ Operators and Expressions
- C++ enum Tutorial: Named Constants Explained
Take Your C++ Further
If you’re looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you’re a complete beginner or looking to solidify your understanding. Just $19.