Побитовые операторы, тернарный оператор и приоритет
Настройки вроде «жирный», «курсив» и «подчёркнутый» принимают значения «да» или «нет». Их удобно хранить в одном целом числе, по одному разряду на каждую. Число в памяти — ряд двоичных разрядов, битов, и каждый равен 0 или 1. Отдельные биты читают и меняют особые операторы:
#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
}Литерал с префиксом 0b записывает число в двоичном виде: 0b1100u — это 12, а 0b1010u — 10. Биты нумеруют справа налево с нуля. Бит номер 0 отвечает за 1, бит номер 1 — за 2, бит номер 3 — за 8.
Побитовые операторы
Побитовые операторы (bitwise operators) работают с каждой парой битов отдельно:
&(И) даёт 1, если бит равен 1 в обоих числах:a & b— это0b1000u;|(ИЛИ) даёт 1, если бит равен 1 хотя бы в одном числе:a | b— это0b1110u;^(исключающее ИЛИ) даёт 1, если в двух числах бит разный:a ^ b— это0b0110u;~(НЕ) переворачивает все биты, поэтомуa & ~bоставляет вaтолько биты, которых нет вb;<<и>>сдвигают биты влево и вправо:a >> 2— это0b0011u.
1u << n — число, в котором установлен только бит номер n. Сдвиг на столько разрядов, сколько бит в типе, или на большее число — неопределённое поведение: для 4-байтового unsigned это 32. Побитовые операторы применяют к беззнаковым типам: у знаковых ~ и << могут изменить бит знака, и число станет отрицательным.
Тернарный оператор
Тернарный оператор (ternary operator) c ? a : b выбирает одно из двух значений. Если условие c верно, результат — a, иначе — b, и вычисляется только выбранный операнд:
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) операторов: как в математике, * выполняется раньше +. Основные операторы — от высокого приоритета к низкому:
!,~,++,--и унарный-;*,/,%, затем+и-, затем<<и>>;<,<=,>,>=, затем==и!=;&, затем^, затем|;&&, затем||, затем?:и=.
Отсюда две ловушки. Сдвиг выполняется после сложения, поэтому 1 << n + 1 — это 1 << (n + 1). А ?: стоит почти в самом низу, и в c ? a : b + 1 единица прибавляется только к b.
У std::cout оператор << тот же, и приоритет у него тот же. Поэтому в примерах выше a & b и ?: стоят в скобках: без них первым сгруппировался бы std::cout << a, и строка не скомпилировалась бы. Если порядок вызывает сомнение, ставьте скобки.
Частая ошибка
Бит проверяют сравнением без скобок:
#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]Компилятор только предупреждает. == выполняется раньше &: 4u == 4u даёт true, то есть 1, и flags & 1u равно 0, хотя бит номер 2 установлен. Нужны скобки: (flags & 4u) == 4u.