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:
#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. Bits are numbered from right to left, starting at zero, and each bit is worth twice the one before it:
- bit 0 is
1; - bit 1 is
2; - bit 2 is
4; - bit 3 is
8.
A number is the sum of its bits that are 1: 0b1100u is 8 + 4, that is 12, and 0b1010u is 8 + 2, that is 10.
Bitwise operators
Bitwise operators work on each bit separately, and &, | and ^ compare the bits with the same number in two numbers:
&(AND) gives1where the bit is1in both numbers:a & bis0b1000u;|(OR) gives1where the bit is1in at least one number:a | bis0b1110u;^(exclusive OR) gives1where the bit differs between the numbers:a ^ bis0b0110u;~(NOT) flips every bit, soa & ~bkeeps only the bits ofathat are not inb;<<and>>shift the bits left and right:a >> 2is0b0011u.
1u << n is a number with only bit n set. Shifting by as many bits as the type has, or more, is undefined behavior: for a 4-byte unsigned, that is a shift by 32 or more.
Such a number is called a mask. With it, flags | (1u << n) sets bit n, flags & ~(1u << n) clears it, and flags & (1u << n) tests it. Pick an operation and watch the mask set, clear or test bit n while the other bits stay as they were.
unsigned flags = 0b00000110u; // 6 unsigned mask = 1u << 2; // 4
The low 8 bits of a 32-bit unsigned: the upper 24 bits of flags, mask and the result are 0
Only bit 2 of flags is left: the result is 4, so (flags & mask) != 0 is true.
Bitwise operators are used on unsigned types. In a signed type the highest bit holds the sign of the number, and it is called the sign bit. ~ or << can change this 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:
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'; // oddPrecedence
Operator precedence sets the order of operations in an expression: 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 + 1is1 << (n + 1); ?:sits near the very bottom, so inc ? a : b + 1the1is added only tob.
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:
#include <iostream>
int main() {
unsigned flags = 0b0110u;
bool has_bit_2 = flags & 4u == 4u; // ✗
std::cout << has_bit_2 << '\n'; // 0
}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.
Key points
- A number is the sum of its bits that are
1, and bitnis worth1u << n. |with a mask sets a bit,& ~clears it, and&tests it.c ? a : bevaluates only the chosen operand.&,^and|bind weaker than==, so a bit test goes in parentheses.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.