Битовые маски и сдвиги
Как уместить десяток настроек «вкл/выкл» в одно число и доставать их побитовыми операциями — приём, на котором держатся права доступа, флаги и сетевые протоколы.
Битовая маска — это число, в котором каждый бит отдельно означает «да/нет» для одной настройки; операции И, ИЛИ и сдвиги позволяют включать, выключать и проверять эти флаги по одному.
Представь настройки программы: «звук включён?», «показывать уведомления?», «тёмная тема?». Каждый ответ — это да или нет, то есть один бит. Хранить десяток таких флагов отдельными переменными расточительно. Гораздо компактнее упаковать их в одно целое число, где каждый бит отвечает за свой флаг. Это и есть битовая маска — приём, который ты постоянно встречаешь в правах доступа файлов, графике и сетевых протоколах.
Биты как флаги
Договоримся: бит номер 0 (самый младший) — это «звук», бит 1 — «уведомления», бит 2 — «тёмная тема». Тогда значение каждого флага — это степень двойки:
$$ \text{звук} = 2^0 = 1, \quad \text{уведомления} = 2^1 = 2, \quad \text{тема} = 2^2 = 4 $$
Почему именно степени двойки? Потому что в двоичной записи у каждой из них горит ровно один бит и на разных позициях: 001, 010, 100. Это гарантирует, что флаги не пересекаются. Если включить звук и тёмную тему, складываем (или объединяем через ИЛИ): $1 + 4 = 5$, в двоичном 101 — горят биты 0 и 2, ровно нужные.
Три базовые операции
Чтобы управлять флагами, нужны три побитовые операции. Они работают над каждой парой битов независимо.
- ИЛИ (
|) — включить флаг. Бит результата равен 1, если хотя бы один из исходных битов 1. Чтобы добавить флаг, делаеммаска | флаг. - И (
&) — проверить флаг. Бит результата равен 1, только если оба бита 1. Чтобы узнать, включён ли флаг:маска & флагдаёт ненулевой результат, если бит горит. - Сдвиг влево (
<<) — построить флаг.1 << nставит единицу на позицию n, то есть даёт $2^n$. Это удобный способ записать «флаг номер n» без степеней вручную.
Связь сдвига со степенью двойки прямая:
$$ 1 \ll n = 2^n $$
Например, $1 \ll 3 = 8$, потому что единица переехала на 3 позиции влево: из 0001 в 1000.
Упаковка настроек в Python
Соберём всё вместе. Включим звук и тёмную тему, проверим каждый флаг и затем выключим звук.
SOUND = 1 << 0 # 1, бит 0
NOTIFY = 1 << 1 # 2, бит 1
DARK = 1 << 2 # 4, бит 2
# включаем звук и тёмную тему через ИЛИ
settings = SOUND | DARK
print("Маска:", settings, "=", bin(settings))
# проверяем каждый флаг через И
print("Звук включён:", bool(settings & SOUND))
print("Уведомления:", bool(settings & NOTIFY))
print("Тёмная тема:", bool(settings & DARK))
# выключаем звук: И с инверсией флага
settings = settings & ~SOUND
print("После выключения звука:", settings, "=", bin(settings))Вывод:
Маска: 5 = 0b101 Звук включён: True Уведомления: False Тёмная тема: True После выключения звука: 4 = 0b100
Разберём по шагам. SOUND | DARK — это 001 | 100 = 101, то есть 5. Проверка settings & SOUND даёт 101 & 001 = 001 (ненулевое, флаг есть), а settings & NOTIFY даёт 101 & 010 = 000 (ноль, флага нет). Выключение звука — это И с инверсией: ~SOUND гасит нулём только бит 0, остальные оставляет, поэтому 101 превращается в 100.
Как это работает: маска прав доступа
Классический пример из реальной жизни — права доступа к файлам в Unix. Для каждого файла есть три права: чтение (read), запись (write), исполнение (execute). Их кодируют тремя битами:
$$ r = 4 = 2^2, \quad w = 2 = 2^1, \quad x = 1 = 2^0 $$
Команда chmod 6 означает права $6 = 4 + 2 = r + w$: читать и писать можно, исполнять — нет. А знаменитое chmod 7 — это $4 + 2 + 1 = 7$, все три права сразу (111). Вот таблица всех комбинаций одной группы прав:
| Цифра | Биты rwx | Что разрешено |
|---|---|---|
| 0 | 000 | ничего |
| 4 | 100 | только чтение |
| 6 | 110 | чтение + запись |
| 5 | 101 | чтение + исполнение |
| 7 | 111 | чтение + запись + исполнение |
Когда система проверяет, можно ли тебе писать в файл, она делает ровно права & w — то же побитовое И, что и в нашем примере с настройками. Один и тот же приём работает и для прав, и для флагов окна, и для битовых полей в сетевом пакете.
Частые ошибки
- Путать
&и&&. Одиночный&— побитовое И (работает с битами), двойной&&— логическое И (работает с «истина/ложь»). Для масок нужен именно одиночный. - Складывать флаги вместо ИЛИ при возможном повторе. Если флаг уже включён,
+добавит его второй раз и сломает соседние биты, а|повторное включение проигнорирует. Для масок безопаснее|. - Брать не степени двойки. Если назначить флагу значение 3 (
011), он займёт сразу два бита и пересечётся с другими флагами. Значения флагов обязаны быть $1, 2, 4, 8, \ldots$ - Забывать, что проверка даёт число, а не «истину».
settings & DARKвозвращает 4, а неTrue. Чтобы получить булево, оборачивай вbool(...)или сравнивай с нулём.
Итоги
- Битовая маска упаковывает много флагов «да/нет» в одно число, по биту на флаг.
- Значение каждого флага — степень двойки $2^n$, чтобы биты не пересекались; удобно строить через $1 \ll n$.
- ИЛИ (
|) включает флаг, И (&) проверяет, И с инверсией (& ~) выключает. - Права доступа rwx — это та же маска: $r=4,\ w=2,\ x=1$, а
chmod 7= все права. - Тот же приём лежит под флагами окон, графикой и полями сетевых пакетов.