Перейти к содержимому
Теория · Операторы и выражения · урок 5 из 8

Побитовые операторы, тернарный оператор и приоритет

Настройки вроде «жирный», «курсив» и «подчёркнутый» принимают значения «да» или «нет». Их удобно хранить в одном целом числе, по одному разряду на каждую. Число в памяти — ряд двоичных разрядов, битов, и каждый равен 0 или 1. Отдельные биты читают и меняют особые операторы:

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
}

Литерал с префиксом 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, и вычисляется только выбранный операнд:

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) операторов: как в математике, * выполняется раньше +. Основные операторы — от высокого приоритета к низкому:

  • !, ~, ++, -- и унарный -;
  • *, /, %, затем + и -, затем << и >>;
  • <, <=, >, >=, затем == и !=;
  • &, затем ^, затем |;
  • &&, затем ||, затем ?: и =.

Отсюда две ловушки. Сдвиг выполняется после сложения, поэтому 1 << n + 1 — это 1 << (n + 1). А ?: стоит почти в самом низу, и в c ? a : b + 1 единица прибавляется только к b.

У std::cout оператор << тот же, и приоритет у него тот же. Поэтому в примерах выше a & b и ?: стоят в скобках: без них первым сгруппировался бы std::cout << a, и строка не скомпилировалась бы. Если порядок вызывает сомнение, ставьте скобки.

Частая ошибка

Бит проверяют сравнением без скобок:

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]

Компилятор только предупреждает. == выполняется раньше &: 4u == 4u даёт true, то есть 1, и flags & 1u равно 0, хотя бит номер 2 установлен. Нужны скобки: (flags & 4u) == 4u.