Что такое битовые операции в C/C++?

«Что такое битовые операции в C/C++?» — вопрос из категории C++ Core, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Битовые операции в C/C++ — это низкоуровневые операции, которые работают непосредственно с отдельными битами целочисленных типов данных (int, char, uint32_t и т.д.). Они выполняются процессором очень быстро и часто используются для оптимизации, управления флагами, работы с аппаратными регистрами или реализации компактных структур данных.

Основные операторы:

Оператор Название Описание Пример (a=0b1100, b=0b1010)
& Побитовое И (AND) Бит результата = 1, если оба соответствующих бита операндов = 1. a & b = 0b1000
| Побитовое ИЛИ (OR) Бит результата = 1, если хотя бы один из битов = 1. a | b = 0b1110
^ Исключающее ИЛИ (XOR) Бит результата = 1, если биты операндов различны. a ^ b = 0b0110
~ Побитовое НЕ (NOT) Инверсия всех битов операнда. ~a = 0b...11110011
<< Сдвиг влево (Left Shift) Сдвигает биты влево, заполняя младшие биты нулями. Эквивалентно умножению на 2^n. a << 2 = 0b110000 (48)
>> Сдвиг вправо (Right Shift) Сдвигает биты вправо. Для беззнаковых типов — заполняет старшие биты нулями. Для знаковых — зависит от компилятора (обычно знаковый бит). Эквивалентно целочисленному делению на 2^n. (unsigned)a >> 2 = 0b0011 (3)

Практическое применение: работа с набором флагов (битовой маской).

#include <cstdint>
#include <iostream>

// Определяем флаги как степени двойки (один установленный бит)
enum class UserPermissions : uint8_t {
    NONE    = 0b00000000, // 0
    READ    = 0b00000001, // 1 << 0
    WRITE   = 0b00000010, // 1 << 1
    EXECUTE = 0b00000100, // 1 << 2
    ADMIN   = 0b10000000  // 1 << 7
};

int main() {
    uint8_t myPermissions = 0;

    // Установка флагов (добавление прав) с помощью OR
    myPermissions |= static_cast<uint8_t>(UserPermissions::READ);
    myPermissions |= static_cast<uint8_t>(UserPermissions::WRITE);
    // myPermissions теперь = 0b00000011 (READ + WRITE)

    // Проверка наличия флага с помощью AND
    bool canWrite = (myPermissions & static_cast<uint8_t>(UserPermissions::WRITE)) != 0; // true
    bool isAdmin = (myPermissions & static_cast<uint8_t>(UserPermissions::ADMIN)) != 0; // false

    // Снятие флага (сброс бита)
    myPermissions &= ~static_cast<uint8_t>(UserPermissions::WRITE); // Убираем право WRITE
    // myPermissions теперь = 0b00000001 (только READ)

    // Переключение флага (XOR)
    myPermissions ^= static_cast<uint8_t>(UserPermissions::EXECUTE); // Добавляем EXECUTE
    myPermissions ^= static_cast<uint8_t>(UserPermissions::EXECUTE); // Убираем EXECUTE

    std::cout << "Permissions byte: " << std::hex << (int)myPermissions << std::endl;
    return 0;
}

Важно: При сдвигах вправо знаковых чисел (int, short) результат зависит от реализации (архитектурно-зависимое поведение). Для переносимости лучше работать с беззнаковыми типами (unsigned int, uint32_t).