Skip to content
Theory · Operators and Expressions · lesson 5 of 8

Bitwise operators, ternary and precedence

Settings such as "bold", "italic" and "underlined" take the values "yes" or "no". They are convenient to keep in one integer, one digit per setting. A number in memory is a row of binary digits, called bits, and each one is 0 or 1. Special operators read and change individual bits:

C++
#include <iostream>

int main() {
    unsigned a = 0b1100u;
    unsigned b = 0b1010u;
    std::cout << (a & b) << '\n';  // 8
    std::cout << (a | b) << '\n';  // 14
    std::cout << (a ^ b) << '\n';  // 6
    std::cout << (a & ~b) << '\n';  // 4
    std::cout << (a >> 2) << '\n';  // 3
    std::cout << (1u << 3) << '\n';  // 8
}

A literal with the 0b prefix writes a number in binary: 0b1100u is 12, and 0b1010u is 10. Bits are numbered from right to left, starting at zero. Bit 0 stands for 1, bit 1 for 2, and bit 3 for 8.

Bitwise operators

Bitwise operators work on each pair of bits separately:

  • & (AND) gives 1 where the bit is 1 in both numbers: a & b is 0b1000u;
  • | (OR) gives 1 where the bit is 1 in at least one number: a | b is 0b1110u;
  • ^ (exclusive OR) gives 1 where the bit differs between the numbers: a ^ b is 0b0110u;
  • ~ (NOT) flips every bit, so a & ~b keeps only the bits of a that are not in b;
  • << and >> shift the bits left and right: a >> 2 is 0b0011u.

1u << n is a number with only bit n set. Shifting by as many positions as the type has bits, or more, is undefined behavior: for a 4-byte unsigned, that is 32. Bitwise operators are used on unsigned types. On signed ones, ~ and << can change the sign bit, and the number turns negative.

The ternary operator

The ternary operator c ? a : b picks one of two values. If the condition c holds, the result is a, otherwise b, and only the chosen operand is evaluated:

C++
int x = 7;
int y = 4;
int bigger = x > y ? x : y;
std::cout << bigger << '\n';  // 7
std::cout << (x % 2 == 0 ? "even" : "odd") << '\n';  // odd

Precedence

The order of operations in an expression is set by operator precedence: as in math, * runs before +. The main operators, from high precedence to low:

  • !, ~, ++, -- and unary -;
  • *, /, %, then + and -, then << and >>;
  • <, <=, >, >=, then == and !=;
  • &, then ^, then |;
  • &&, then ||, then ?: and =.

Two traps follow from this. A shift runs after addition, so 1 << n + 1 is 1 << (n + 1). And ?: sits near the very bottom, so in c ? a : b + 1 the 1 is added only to b.

The << of std::cout is the same operator, with the same precedence. That is why a & b and ?: are in parentheses in the examples above. Without them, std::cout << a would group first, and the line would not compile. When the order is in doubt, add parentheses.

Common mistake

A bit is tested with a comparison and no parentheses:

C++
#include <iostream>

int main() {
    unsigned flags = 0b0110u;
    bool has_bit_2 = flags & 4u == 4u;  // ✗
    std::cout << has_bit_2 << '\n';  // 0
}
TEXT
warning: & has lower precedence than ==; == will be evaluated first [-Wparentheses]

The compiler only warns. == runs before &: 4u == 4u gives true, that is 1, and flags & 1u is 0, even though bit 2 is set. Parentheses are needed: (flags & 4u) == 4u.