&, |, ^, ~ и сдвиги работают с каждым разрядом двоичного представления
отдельно. Похожи на логические операции внешне, но это другая история:
короткого замыкания здесь нет.
Как считать
Возьмём 6 = 110₂ и 3 = 011₂:
110 110 110
& 011 | 011 ^ 011
----- ----- -----
010 111 101
& оставляет единицу там, где она есть в обоих операндах, | — где есть
хотя бы в одном, ^ (исключающее ИЛИ) — где биты различаются. Результаты:
2, 7 и 5.
6 & 3 // 2
6 | 3 // 7
6 ^ 3 // 5Побитовое отрицание ~ меняет все биты значения после продвижения, а не только
те три разряда, которые нарисованы:
~6 // -7
~0U // максимальное значение unsigned intДля знакового числа результат читается через дополнительный код, поэтому ~6
и даёт −7. Смотреть на ~ как на «перевернуть три бита» — верный способ
получить неожиданный ответ.
Сдвиги
6U << 1 // 12
6U >> 1 // 3
7U >> 1 // 3В этих примерах сдвиг влево эквивалентен умножению на два, вправо — целому делению на два. Но делать отсюда универсальную замену арифметики сдвигами нельзя: для отрицательных чисел поведение расходится.
В C++20 сдвиг отрицательного знакового числа вправо округляет вниз, а целочисленное деление округляет к нулю:
-7 >> 1 // -4
-7 / 2 // -3Количество позиций должно быть неотрицательным и меньше ширины типа левого операнда после продвижения. Нарушение — Неопределённое поведение. Сдвиг 32-битного значения на 32 позиции не даёт ноль, он даёт UB.
Главное отличие от логических операций
& и | вычисляют оба операнда всегда. Короткое замыкание есть только
у && и ||:
bool ok = y != 0 && x / y > 2; // деления нет, если y == 0
bool bad = y != 0 & x / y > 2; // деление выполняется всегда → UBЗамена одного символа превращает корректный код в программу с неопределённым поведением. Подробнее — Логические операции и короткое замыкание.
Тонкости
- Тип результата определяется не обычной схемой приведения к общему типу: у сдвигов тип берётся от левого операнда после продвижения, правый на него не влияет. Для остальных побитовых операций работают обычные Арифметические преобразования типов.
- Малые типы продвигаются и здесь:
~надcharдействует на все битыint. - Для битовых манипуляций предпочтительны беззнаковые типы — там определены и переполнение, и сдвиг влево через старший бит. См. Беззнаковые типы.
- Для чисел с плавающей точкой побитовые операции не определены вовсе.
- В
std::cout << xте же символы<<означают вывод, а не сдвиг. Смысл операции задают операнды, а не значок.
Частые ошибки
- Писать
&вместо&&в защитном условии. - Считать
~x«инверсией нарисованных битов» и забывать про ширину типа. - Сдвигать на количество позиций, равное ширине типа или больше.
- Заменять
x / 2наx >> 1для знаковых величин, которые бывают отрицательными.