Bitwise Operators and Operators Precedence in C++
1. Why Should I Even Care About Bits?
Let’s ask something very simple.
When you write something like a + b in your C++ program... what’s really happening inside the machine?
Do you imagine tiny elves with calculators? Maybe sparks flying between transistors? Well, not quite — but let’s go deeper.
Underneath all the layers of fancy programming languages, graphics, apps, and cloud servers… computers have only one language they truly understand:
Binary — just 1s and 0s.
That’s it.
No letters. No emojis. No magic keywords. Just billions of little on/off switches, called bits.
So What’s a Bit, Really?
A bit (short for binary digit) is the smallest piece of data your computer knows. It’s either ON (1) or OFF (0).
Now think about a room full of light switches. Each switch controls one tiny thing.
If it's ON, it means something is active. OFF? It's silent.
Now imagine that every number, character, color, video, or game you’ve ever seen on a screen — is just a unique combination of those switches.
We’re literally building castles with switches.
So How Do We “Talk” to These Switches?
Good question. Most of the time, you don’t. The C++ compiler translates your code into binary for you.
But sometimes — especially when we care about speed, precision, or hardware-level stuff — we need to talk to those switches directly.
That’s where bitwise operators come in.
These special operators in C++ allow you to manipulate the individual bits of a value — turning them on, off, or shifting them left or right, like a row of boxes on a conveyor belt.
Sounds complicated? It’s really not.
But why should we care?
Let’s look at a few real-world moments where you’ve already seen this kind of logic:
- Ever filled out a permissions box online? Like “can edit,” “can view,” “can comment”? Behind the scenes, this is often stored as individual bits.
- Ever wondered how antivirus programs scan billions of files so fast? They use bitwise tricks.
- How does your graphics card process pixels at lightning speed? Yep — more bits, shifted and flipped in all directions.
2. The AND Operator (&) – The "Both Must Say Yes" Rule
Let’s pretend you and your friend are deciding whether to watch a movie. You say,
“I’ll only go if both of us want to.”
Simple, right?
If you say yes and your friend says no → No movie.
If your friend says yes but you’re not feeling it → Still no movie.
But if both of you are in? Then you’re buying popcorn.
That’s how the AND operator works in C++.
It’s stubborn. It only agrees when both sides say yes.
And in the world of bits, “yes” means a 1, and “no” means a 0.
Wait, Hold On... What Are We “AND”-ing, Exactly?
Good question.
Let’s say we have two numbers. Behind the scenes, your computer stores them in binary — a row of bits.
Here’s an example with small numbers, just to make it easy:
- 5 in binary = 0101
- 3 in binary = 0011
If we write:
int result = 5 & 3;
The & here isn’t saying “and” in English.
It’s bitwise AND, which means: compare each bit in both numbers, one by one, and return 1 only if both bits are 1.
Let’s Line It Up Like a Table:
0101 (this is 5)
& 0011 (this is 3)
----
0001 (this is 1)
Let’s walk through that line-by-line.
Bit Position |
Bit in 5 |
Bit in 3 |
Result (AND) |
Leftmost |
0 |
0 |
0 |
Next |
1 |
0 |
0 |
Next |
0 |
1 |
0 |
Rightmost |
1 |
1 |
1 |
Only in the last bit do both have 1s. So the final result is 0001 → which is 1 in decimal.
Okay... But Why Would I Ever Do That?
Great question — because this does seem a bit abstract at first.
Let’s bring in a real-world example: file permissions.
Suppose you’re building a system where users have different rights. You decide that a single byte (8 bits) will store these permissions.
Each bit means something:
- Bit 0: Can Read
- Bit 1: Can Write
- Bit 2: Can Execute
- Bit 3: Is Admin
- (and so on...)
Now, a user has permission 0110. What does that mean?
Let’s break it down:
Bit 3 Bit 2 Bit 1 Bit 0
0 1 1 0
↑ ↑
Execute Write
The user can Write and Execute, but can’t Read or Admin.
Now imagine you want to check: “Can this user write?”
You can write this in C++:
if (permissions & 0b0010) {
// Yes! Bit 1 is set
}
This is exactly the AND operation in action.
You're checking: Is the second bit (Write permission) ON in the user's permissions?
It’s fast, efficient, and used everywhere in systems programming.
Summary: When to Use &
Use & when you want to:
- Check if certain bits are ON in a binary number (e.g., flags, permissions)
- Perform bit masking
- Do low-level data comparisons efficiently
- Filter out parts of binary data
And always remember:
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
It’s all or nothing with AND.
3. The OR Operator (|) – The “Anyone Can Say Yes” Rule
Let’s imagine this:
You and your friend are planning a game night. But here’s the rule:
“We’ll play if at least one of us wants to.”
So what happens?
- You’re excited, your friend’s not? → Game night!
- Your friend’s excited, but you’re meh? → Still game night!
- You both want it? → Definitely game night!
- Neither of you? → Netflix and sleep.
That’s exactly how the OR operator (|) behaves in C++.
It’s the flexible one. The accommodating one. The "just one yes is enough" type.
But What Does It Really Do?
We’re still working with bits — tiny switches. Each bit can be:
- 1 (ON)
- 0 (OFF)
The | (bitwise OR) compares bits in two numbers and returns 1 if either bit is 1. If both are 0? It gives 0.
Let’s go back to our earlier example:
int result = 5 | 3;
Quick reminder:
- 5 in binary = 0101
- 3 in binary = 0011
Let’s line them up:
0101 (this is 5)
| 0011 (this is 3)
----
0111 (this is 7)
Let’s break that down bit-by-bit:
Bit Position |
Bit in 5 |
Bit in 3 |
Result (OR) |
Leftmost |
0 |
0 |
0 |
Next |
1 |
0 |
1 |
Next |
0 |
1 |
1 |
Rightmost |
1 |
1 |
1 |
The final result is 0111, which is 7.
So just like that, you’ve combined two numbers, keeping every bit that was ON in either of them.
Real-World Analogy: Feature Flags
Imagine you’re building a video game.
Each character has power-ups:
- Bit 0: Can Jump
- Bit 1: Can Shoot
- Bit 2: Can Swim
- Bit 3: Can Fly
Now let’s say:
- Character A: 0100 (Can Swim)
- Character B: 1001 (Can Fly + Can Jump)
You want to create a new character who combines the powers of both.
So you do this:
int combined = characterA | characterB;
Let’s see the result:
0100 (A: Swim)
| 1001 (B: Fly + Jump)
----
1101 (New: Fly + Swim + Jump)
Your new hero is stacked!
That’s bitwise OR in action — combining capabilities using bits.
Summary: When to Use |
Use the OR operator when you want to:
- Combine binary flags or permissions
- Merge two sets of features or settings
- Check if at least one condition is true in binary terms
And the truth table?
1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0
It’s easy-going — if anyone wants it, it’s a yes.
4. The XOR Operator (^) – The “Exactly One Must Say Yes” Rule
Imagine you and your sibling are picking chores. There's a quirky rule:
“If exactly one of us wants to do the chore, then that person does it.
But if both want it, or neither want it — it just doesn’t get done.”
Weird rule, huh? But it makes things fair.
That’s the logic behind the XOR operator in C++.
It stands for eXclusive OR, and it works like this:
- If one bit is 1 and the other is 0 → result is 1
- If both are 1 or both are 0 → result is 0
This operator lives for disagreement.
Let’s Break It Down with Numbers
Let’s go back to 5 and 3 again:
- 5 in binary = 0101
- 3 in binary = 0011
Now let’s do XOR:
int result = 5 ^ 3;
0101 (5)
^ 0011 (3)
----
0110 (6)
Let’s unpack it bit-by-bit:
Bit Position |
Bit in 5 |
Bit in 3 |
Result (XOR) |
Leftmost |
0 |
0 |
0 |
Next |
1 |
0 |
1 |
Next |
0 |
1 |
1 |
Rightmost |
1 |
1 |
0 |
So 5 ^ 3 gives you 0110, which is 6.
Only where the bits were different do you get a 1.
Real-World Analogy: Light Switches (Two-Way)
Let’s take a house wiring example — a two-way switch setup.
You know those lights in hallways with two switches at either end?
- If both switches are up → light is OFF
- If both are down → light is OFF
- If one is up, one is down → light is ON
That’s XOR! The light turns on only when exactly one switch is flipped.
Hidden Superpower: Swapping Two Variables (Without Temp!)
Let’s say you want to swap two values — a and b — but without using a third variable.
Most people would do:
int temp = a;
a = b;
b = temp;
But with XOR, you can do this wild trick:
a = a ^ b;
b = a ^ b;
a = a ^ b;
And boom! a and b are swapped. No temp needed.
It’s like magic — and it works because of how XOR "undoes" itself when applied twice.
Let’s say a = 5, b = 3. Watch what happens:
- Step 1: a = 5 ^ 3 = 6
- Step 2: b = 6 ^ 3 = 5
- Step 3: a = 6 ^ 5 = 3
Now a = 3, b = 5. Swapped!
Why? Because XOR keeps the difference between the values, and when reapplied cleverly, it reconstructs the original values.
Summary: When to Use ^
Use XOR when you want to:
- Detect differences between two values
- Flip bits selectively
- Perform low-level encryption
- Do cool tricks like variable swapping
- Toggle a bit (1 becomes 0, 0 becomes 1)
And here’s its truth table:
1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0
It’s all about being different.
5. The NOT Operator (~) – The “Flip Everything” Button
Imagine you’re designing a security system with YES/NO switches:
- YES → Alarm is ON
- NO → Alarm is OFF
Now picture this: you’re feeling dramatic one day and you shout…
“Flip them all! YES becomes NO. NO becomes YES!”
And suddenly:
- All active alarms go silent
- All silent alarms start blaring
- Every bit is reversed, like turning light into shadow
That’s bitwise NOT in action.
In C++, this wild operator is written as a tilde: ~
It doesn’t compare two numbers like the others.
It just takes one number — and flips every bit.
What Does It Actually Do?
Let’s say you have:
int a = 5;
int result = ~a;
Now, 5 in binary (assuming 8 bits) is:
00000101
When we apply ~:
~00000101 = 11111010
Every single bit is flipped:
- 0 → 1
- 1 → 0
So what number is 11111010?
Here’s the twist: C++ stores numbers using something called two’s complement. That’s a fancy way of saying:
The leftmost bit decides if it’s a positive or negative number.
So 11111010 isn’t just some large number — it actually means -6.
Yes, really! Try it in code:
#include <iostream>
int main() {
int a = 5;
std::cout << ~a; // prints -6
}
Why Is ~5 Equal to -6?
Let’s break this down gently.
When you write ~a, you're flipping all bits of a.
For positive integers:
- ~a = -a - 1
So:
~5 = -5 - 1 = -6
It’s not a bug. It’s a feature — part of how negative numbers are represented in binary using two’s complement.
Real-World Analogy: Opposite Day
Remember “Opposite Day” as a kid?
- “I love homework!” (you mean: I hate it)
- “I’m not tired!” (you’re exhausted)
- “Sure, I’ll clean my room!” (haha, no)
That’s what NOT does.
Every bit gets its opposite meaning.
- 1 (true) → 0 (false)
- 0 (false) → 1 (true)
It’s great when you want to invert a bitmask or quickly toggle settings.
Use Case: Flipping Permissions
Say you have a binary permission setting:
int permissions = 0b00001111; // 4 permissions: all ON
But now you want to disable all of them without manually zeroing each bit.
Just:
permissions = ~permissions;
Now they all flip OFF (and extra bits flip ON, but you can mask them if needed).
Caution: Watch the Sign Bit
Because ~ flips the sign bit too, you can suddenly turn a small positive number into a huge negative one.
So don’t apply ~ blindly — especially if you’re not working with unsigned data.
If you're working with unsigned int, then ~ just gives you the regular flipped value without any “negative” drama.
Example:
unsigned int a = 5;
std::cout << ~a; // gives 4294967290 on a 32-bit system
Why that huge number? Because all those flipped 1s now represent a large unsigned value.
Summary: What ~ Does
- Flips every bit in a number
- Turns 1s into 0s, 0s into 1s
- On signed integers, it results in negative values
- On unsigned ints, it gives large values
- It's great for: toggling bits, building masks, or just causing fun chaos
Value |
Binary |
~ Result (Binary) |
Final Value |
5 |
00000101 |
11111010 |
-6 |
0 |
00000000 |
11111111 |
-1 |
1 |
00000001 |
11111110 |
-2 |
So when you use ~, remember — you’re flipping the whole switchboard.
6. Bitwise Shifts – The “Bit Conveyor Belt” (<< and >>)
Alright, imagine this:
You’re at a factory. There’s a conveyor belt.
Each box on the belt represents a bit — a 1 or a 0.
And with a lever, you can move all the boxes left or right.
That’s exactly what bitwise shifting does in C++.
Two main operators:
- << – Left Shift
- >> – Right Shift
You’re literally pushing bits sideways, and that changes the number completely.
Let’s break them down one by one — and have fun doing it.
Left Shift (<<) – The “Multiply by 2” Trick
Let’s say we start with a number:
int a = 3;
int result = a << 1;
Here’s what happens:
- 3 in binary = 00000011
- Shift left by 1 → 00000110
That’s 6!
See what just happened?
All bits moved one place to the left — and a 0 filled in the empty rightmost spot.
So:
00000011 (3)
<< 1
= 00000110 (6)
And if we shift left by 2?
int result = a << 2; // 3 becomes 12
Yep — shifting left by 2 is the same as multiplying by 2^2 = 4
So every left shift multiplies your number by 2:
Expression |
Result |
3 << 1 |
6 |
3 << 2 |
12 |
5 << 3 |
40 |
1 << 4 |
16 |
Real-world analogy?
Think of moving a decimal point in math.
- 3 → 30 → 300 → 3000
That’s like multiplying by 10 - In binary: shifting left = multiply by 2 each time
Right Shift (>>) – The “Divide by 2” Trick
Same idea — but in reverse.
Let’s try:
int a = 8;
int result = a >> 1;
Binary of 8 is 00001000.
Shift right by 1:
00001000
>> 1
= 00000100
And that’s 4!
So:
Expression |
Result |
8 >> 1 |
4 |
8 >> 2 |
2 |
16 >> 3 |
2 |
5 >> 1 |
2 |
Each right shift divides the number by 2.
(Technically does floor division, so it drops fractions.)
Real-world analogy?
Think of erasing the last digit of a number written in binary — you’re chopping off half.
But Wait — What Happens to the Empty Spaces?
Great question.
When you shift left:
- New bits on the right are filled with 0s
When you shift right:
- New bits on the left usually get 0s
- But for negative numbers, that depends on the system (we’ll keep it simple here and assume 0s)
So remember:
- Shifting left = bits pushed out on the left, zeroes come in on the right
- Shifting right = bits pushed out on the right, new bits come in on the left
You’re literally pushing bits off the edge!
Danger Zone: Data Loss!
Try this:
int a = 128;
int result = a << 1;
128 in binary = 10000000
Shift left by 1 → 00000000
Why? Because that 1 bit at the leftmost position was pushed out of the integer’s memory space.
You just lost data.
So shifting too far can cause overflow or unexpected behavior — be careful!
Why Use Bit Shifts?
- Fast math: Multiplying/dividing by powers of 2 is lightning fast
- Encoding tricks: Store multiple values in a single integer
- Flags and masks: Efficient data representation
Example:
#define READ 1 // 0001
#define WRITE 2 // 0010
#define EXEC 4 // 0100
int permissions = READ | WRITE; // 0011
Now if we want to add EXEC, we can do:
permissions = permissions | (1 << 2); // Adds EXEC (bit 2)
Clean, clever, and very C++.
Summary Table
Operation |
What It Does |
Result |
x << n |
Multiplies x by 2ⁿ |
Fast math |
x >> n |
Divides x by 2ⁿ |
Efficient scaling |
Shifting too far |
Can lose bits |
Be cautious! |
7. Operator Precedence – The “Who Goes First?” Debate
Imagine this.
You walk into a pizza shop. You say:
“I’d like a medium pepperoni with extra cheese and a drink.”
But before you can even blink…
The chef throws the drink into the oven, puts pepperoni on the soda, and tries to cheese the receipt.
Total chaos.
Why? Because no one knew what to do first.
That’s what happens in code when we don’t understand operator precedence.
So now that we’ve met all these cool bitwise and logical operators — &, |, ^, ~, <<, >> — it’s time to ask:
When you combine them in a single line... who acts first?
That’s what operator precedence tells us.
Let’s break it down like detectives.
Let’s Start Simple
What happens here?
int x = 2 + 3 * 4;
Do we:
- Add first? → (2 + 3) * 4 = 20
- Or multiply first? → 2 + (3 * 4) = 14
C++ says: “Multiplication goes before addition.”
So the answer is 14.
This is operator precedence at work.
Every operator in C++ has a priority level — the ones higher on the chart run before the ones below.
So… Where Do Bitwise Operators Stand?
Let’s rank them, from high priority (goes first) to low:
Priority |
Operator |
Meaning |
High |
~, ! |
NOT (bitwise, logical) |
↓ |
*, /, % |
Multiplication / Division |
↓ |
+, - |
Addition / Subtraction |
↓ |
<<, >> |
Bitwise shifts |
↓ |
<, >, <= |
Comparisons |
↓ |
==, != |
Equality checks |
↓ |
& |
Bitwise AND |
↓ |
^ |
Bitwise XOR |
↓ |
` |
` |
↓ |
&& |
Logical AND |
↓ |
` |
|
Low |
=, +=, <<= |
Assignment operators |
Yes — bitwise operators fall somewhere in the middle.
- ~ goes first (highest among bitwise)
- Then shifts: <<, >>
- Then AND: &
- Then XOR: ^
- Then OR: |
They all come after arithmetic, but before logical && and ||.
Real Example: Let’s Confuse Ourselves
Here’s a classic:
int result = 5 + 3 << 1;
Looks harmless, right? But what’s the answer?
Let’s follow the precedence:
- + goes first: 5 + 3 = 8
- << comes next: 8 << 1 = 16
So result = 16.
If you wanted to shift first, you'd have to force it with parentheses:
int result = 5 + (3 << 1); // 5 + 6 = 11
Totally different result.
So precedence matters a lot!
But There’s More: Associativity!
What if two operators have the same precedence?
Then C++ uses a second rule: associativity.
That means: which direction does the tie break?
Most operators go left to right.
Example:
10 - 5 - 2 // (10 - 5) - 2 = 3
But assignment goes right to left:
int a;
a = b = 5; // b = 5 happens first, then a = b
This matters a lot when chaining bitwise or shift operators.
Summary: How to Survive Operator Precedence
Rule |
Meaning |
Precedence |
Determines who goes first in a mixed expression |
Associativity |
Breaks ties when operators are at the same level |
Use parentheses |
Always safest way to control order |
Even the compiler won’t mind. And future-you will thank you.