Побитовые маски — это одна из тех тем, которую программисты либо обожают, либо боятся. Но правда в том, что маски — мощнейший инструмент, особенно когда дело доходит до управления флагами, доступами, состояниями и конфигурациями.
Если вы когда-либо писали:
if (flags & FLAG_ACTIVE) == 0: ...
то вы уже причастны к тёмной (но полезной) магии битов.
В этой статье мы разберём:
- Что такое побитовые маски.
- Как ими включать, отключать, проверять и переключать флаги.
- Почему это любят спрашивать на собеседованиях.
- И как сделать так, чтобы вас запомнили как "того парня, который написал красивый код с масками".
🔢 Что такое маска?
Маска — это просто число, в котором биты используются как флаги.
Каждый бит можно трактовать как включено / выключено, да / нет, true / false.
Например, если у вас есть 8 флагов, вы можете уместить их в одно число от 0 до 255.
Число: 1010 0101
Биты: флаг7 флаг6 ... флаг0
✅ Задача 1: Включить флаг (set bit)
Условие:
У нас есть число flags, каждый бит которого — отдельный флаг. Мы хотим включить конкретный флаг k.
def set_flag(flags, k):
return flags | (1 << k)
Пояснение:
(1 << k) # создаём маску: только бит k установлен
flags | mask # включаем бит k (остальные остаются без изменений)
Пример:
flags = 0b00001000
flags = set_flag(flags, 1) # теперь включим бит 1
print(bin(flags)) # 0b00001010
⚙️ Используется в конфигурациях, флагах доступа и даже в видеоиграх (например, статус игрока: "жив", "летает", "невидим", и т.п.)
✅ Задача 2: Отключить флаг (clear bit)
Условие:
Отключить конкретный бит k, не трогая остальные.
def clear_flag(flags, k):
return flags & ~(1 << k)
Пояснение:
(1 << k) # создаём маску, где только k-й бит = 1
~(1 << k) # инвертируем: все единицы, кроме k-й
flags & ~mask # обнуляем только этот бит
Пример:
flags = 0b00001110
flags = clear_flag(flags, 1) # отключаем бит 1
print(bin(flags)) # 0b00001100
🧼 Классический способ «очистить» состояние, не ломая всё остальное.
✅ Задача 3: Проверить, включён ли флаг (check bit)
Условие:
Проверить, установлен ли бит k.
def is_flag_set(flags, k):
return (flags & (1 << k)) != 0
Пример:
flags = 0b00010010
print(is_flag_set(flags, 1)) # True
print(is_flag_set(flags, 2)) # False
🔍 Типичная проверка в системном коде: "а активен ли сейчас этот режим?"
✅ Задача 4: Переключить флаг (toggle bit)
Условие:
Инвертировать конкретный флаг: если он был 1 — станет 0, и наоборот.
def toggle_flag(flags, k):
return flags ^ (1 << k)
Пример:
flags = 0b00000101
flags = toggle_flag(flags, 2) # был 0, станет 1
print(bin(flags)) # 0b00000101 ^ 0b00000100 = 0b00000001
flags = toggle_flag(flags, 0) # 0-й был 1 → станет 0
print(bin(flags)) # 0b00000000
🔁 Используется в играх или GUI — например, переключение галочки "Включить звук".
✅ Задача 5: Поддержка нескольких состояний с одной переменной
Условие:
У нас есть 4 возможных флага:
- FLAG_VISIBLE = 1 << 0
- FLAG_SELECTED = 1 << 1
- FLAG_ACTIVE = 1 << 2
- FLAG_HIGHLIGHTED = 1 << 3
Мы хотим комбинировать и проверять их.
FLAG_VISIBLE = 1 << 0 # 0001
FLAG_SELECTED = 1 << 1 # 0010
FLAG_ACTIVE = 1 << 2 # 0100
FLAG_HIGHLIGHTED = 1 << 3 # 1000
flags = 0
# Включаем видимость и активность
flags |= FLAG_VISIBLE | FLAG_ACTIVE # 0001 | 0100 = 0101
# Проверяем
is_visible = flags & FLAG_VISIBLE != 0
is_selected = flags & FLAG_SELECTED != 0
print(f"Visible: {is_visible}, Selected: {is_selected}")
🧰 Такой подход используется повсеместно: от игровых движков до системных настроек. Удобно, быстро, эффективно.
✅ Задача 6: Установить определённые биты в 0 или 1 (через маску)
Условие:
Установить только нужные биты на определённые значения — одним махом.
def update_flags(flags, mask, value):
flags &= ~mask # очищаем нужные биты
flags |= (value & mask) # устанавливаем новые значения
return flags
Пример:
flags = 0b10101010
mask = 0b00001111 # хотим обновить младшие 4 бита
value = 0b00000001 # хотим установить "0001" в младших 4 битах
flags = update_flags(flags, mask, value)
print(bin(flags)) # 0b10100001
⚠️ Применяется в железе, микроконтроллерах, low-level C, и... в интервью в Google.
🤓 Где это реально используется?
- Игровая индустрия: статусы объектов (жив/мертв/выбран/подсвечен).
- Конфигурации: опции в одном числе (оптимизация памяти).
- Доступы: права чтения, записи, удаления.
- Фреймворки: OpenGL, DirectX, и даже JavaScript canvas.
- Операционные системы: inode-флаги, TCP-флаги, кэш-флаги.
🧪 Любимые вопросы на собеседованиях:
- Как хранить 32 булевых флага в одном числе?
- Как узнать, установлены ли одновременно 3 конкретных флага?
- Как сбросить все флаги, кроме двух?
- Как определить, установлено ли ровно одно состояние?
- Как сделать “переключение” флага за O(1)?
😈 Попробуйте спросить их у коллег. Шансов выжить не так много, но оно того стоит.
🧙 Заключение
Побитовые маски — это и про оптимизацию, и про стиль. Они позволяют управлять множеством состояний лаконично и эффективно. Да, порой их сложно читать, но как только поймёте — вы уже не захотите писать config["is_enabled"] = True для каждого флага.