Why this matters
Every value you’ve worked with so far, this lesson treats differently: not as a number, but as a row of individual bits you can inspect and change directly. This matters more than it might look like at first – flags, permission bits, network protocols, and low-level hardware control all represent state as individual bits packed into an integer, and the operators in this lesson are how you work with that representation directly.
The four bitwise operators, one bit position at a time
Each of these compares or transforms two numbers bit by bit, independently at every position – there’s no
carrying or borrowing the way there is in + or -.
&(AND) – each result bit is1only if both input bits at that position are1.|(OR) – each result bit is1if either input bit is1.^(XOR, “exclusive or”) – each result bit is1if the two input bits are different.~(NOT) – flips every bit of its single operand: every1becomes0and every0becomes1.
Trace 6 & 3 by hand to make this concrete: 6 is 0110 in binary, 3 is 0011. Lining them up and applying
& at each position: 0010, which is 2. Do the same for 6 | 3 (0111 = 7) and 6 ^ 3 (0101 = 5) before
moving on – there’s no substitute for actually working through the bits once.
Don’t confuse these with &&, ||, and ! from the last lesson. & and && look similar and both involve
“and,” but they’re doing completely different jobs: && combines two conditions into one true/false result
(and short-circuits); & combines two numbers into a new number, bit by bit, and always evaluates both sides.
Using one where you meant the other compiles without complaint and produces a confusing, wrong number instead of
an error – worth double-checking any time a condition looks off by exactly one character.
Shifting bits left and right
n << k shifts every bit in n left by k positions, filling the vacated low bits with 0 – which also happens
to multiply n by 2^k. n >> k shifts right by k positions; for the unsigned integers this course focuses on,
that divides by 2^k (rounding down), filling the vacated high bits with 0.
The far more common use of << in practice, and the one this lesson leans on, isn’t arithmetic at all: 1 << k
produces a number with a single 1 bit at position k and 0s everywhere else – exactly the tool you need to
refer to “just this one bit” out of a larger number, which is the entire subject of the next section.
The set / check / clear idiom
The worked example builds and inspects a small set of flags packed into one unsigned int, using a pattern you’ll
see constantly wherever bit flags are used:
- Set bit
k(turn it on, leave every other bit alone):flags = flags | (1 << k);– OR-ing with a single1bit forces that position to1without touching any other position, since OR-ing anything with0leaves it unchanged. - Check bit
k(is it on?):flags & (1 << k)– AND-ing with a single1bit zeroes out every other position, leaving a result that’s nonzero exactly when bitkwas set. Compare that against0(!= 0) to get a clean true/false answer, as the worked example does. - Clear bit
k(turn it off, leave every other bit alone):flags = flags & ~(1 << k);–~(1 << k)is a number that’s0at positionkand1everywhere else; AND-ing with it forces positionkto0while AND-ing everything else with1leaves it unchanged.
Run the worked example and trace flags’s value in binary after each line – this idiom is worth being able to
reconstruct from memory, not just recognize.
A precedence trap worth knowing about explicitly
&, |, and ^ all bind looser than comparison operators like == and != – the reverse of what most
people guess. if (flags & mask == 0) does not check whether flags & mask is zero; == runs first, so it
checks flags & (mask == 0), comparing flags against either 0 or 1 depending on whether mask happened to
be 0. This is a real, common bug, not a hypothetical one – always parenthesize the bitwise part explicitly:
(flags & mask) == 0.