Битовый операции в C# (Часть 2)

Битовые операции

В предыдущей статье я рассказывал про битовую операцию «И», и о том как и где она может пригодиться на практике. Операцию побитового «И» мы использовали для «считывания» значения нужного нам бита некого числа, при помощи наложения битовой маски. В этой статье я расскажу о том как «устанавливать» значение нужно бита числа. Для этого на понадобится операция битового «ИЛИ». Если Вы еще не читали предыдущую статью, на которую я давал ссылку ранее, то рекомендую это сделать, так как настоящая статья будет с ней пересекаться.

Давайте вспомним условие задачи из предыдущего урока, у нас была воображаемая система (состоящая из восьми блоков), которая периодически отправляла нам информацию о состоянии (работает или не работает) каждого блока в виде однобайтового числа, где каждый бит числа соотносился с состоянием одного из блоков. Бит с нулевым индексом (младший бит) соотносился с первым блоком системы, а старший бит (бит с индексом 7) соотносился с восьмым блоком. Причем, если значение некого бита рано «1», то счичталось, что соответствующий блок работает, а если значение бита равно «0», то считалось, что блок не работает. Т.е. если бы система хотела сообщить нам, что работают все блоки кроме первого, она бы прислала число 254 в десятичной системе счисления (что есть 11111110 в двоичной).

Давайте снова подключим воображение и представим, что теперь нам самим нужно включать и выключать блоки. Т.е. в этот раз мы сами должны, бит за битом, формировать число и отправлять его системе. Вот тут нам и пригодится операция побитовго «ИЛИ», в C# она обозначается как «|». Мы снова будем накладывать битовые маски на число, только суть операции будет обратной. Теперь разберемся, как работает побитовое «ИЛИ». А работает эта операция так: сравниваются соответствующие биты обоих чисел (т.е. нулевой бит сравнивается с нулевым битом другого, первый — с первым, второй — со вторым, и т.д. со всеми битами), и побитово формируется результат, в виде третьего числа. Причем, если хоть один из соответствующих битов двух чисел равен «1», то в результирующем числе, соответствующий бит тоже будет равен «1», и только если оба бита были равны «0», в результирующем числе соответствующий бит будет «0».

Например, наложение маски 110000011 на число 11110011, т.е. (11110011 | 110000011) даст результат 11110011.

Так как же мы будем формировать число (обозначающее состояние) перед отправкой внешней системе? Да все просто, мы условимся что изначально все блоки хотим выключить (это будет обозначать число 0, т.е все биты числа сброшены в «0»: 00000000). А потом, «пройдемся» по списку блоков, и если нужно включить какой-то блок, мы будем накладывать соответствующую битовую маску на это число.

На практике, мы напишем класс, объекты которого будут хранить восемь логических (true/false) значений (каждое из которых, будет ассоциироваться с состоянием определенного блока системы), а так же, иметь метод, формирующий (на основании этих значений), результирующее число (которое, мы как бы должны отправить системе, для включения/выключения нужных блоков).

class StateBuilder
{
    //Конструктор, принимает восемь булевых значений (состояний восьми блоков)
    public StateBuilder(bool Unit1State, bool Unit2State,
        bool Unit3State, bool Unit4State, bool Unit5State,
        bool Unit6State, bool Unit7State, bool Unit8State)
    {
        unit1State = Unit1State;
        unit2State = Unit2State;
        unit3State = Unit3State;
        unit4State = Unit4State;
        unit5State = Unit5State;
        unit6State = Unit6State;
        unit7State = Unit7State;
        unit8State = Unit8State;
    }

    //Формирует результирующее число
    public byte Build()
    {
        //Сбрасываем все биты числа в "0"
        int result = 0;

        //Если нужно включить первый блок
        if (unit1State == true)
            result = result | 0x01; //0x01 (hex) = 00000001 (bin)

        //Если нужно включить второй блок
        if (unit2State == true)
            result = result | 0x02; //0x02 (hex) = 00000010 (bin)

        //Если нужно включить третий блок
        if (unit3State == true)
            result = result | 0x04; //0x04 (hex) = 00000100 (bin)

        //Если нужно включить четвертый блок
        if (unit4State == true)
            result = result | 0x08; //0x08 (hex) = 00001000 (bin)

        //Если нужно включить пятый блок
        if (unit5State == true)
            result = result | 0x10; //0x10 (hex) = 00010000 (bin)

        //Если нужно включить шестой блок
        if (unit6State == true)
            result = result | 0x20; //0x20 (hex) = 00100000 (bin)

        //Если нужно включить седьмой блок
        if (unit7State == true)
            result = result | 0x40; //0x40 (hex) = 01000000 (bin)

        //Если нужно включить восьмой блок
        if (unit8State == true)
            result = result | 0x80; //0x80 (hex) = 10000000 (bin)

        //Обрезаем результат до байта
        return (byte)result;
    }

    //Состояния блоков (от первого до 8-го)
    private bool unit1State;
    private bool unit2State;
    private bool unit3State;
    private bool unit4State;
    private bool unit5State;
    private bool unit6State;
    private bool unit7State;
    private bool unit8State;
}

Как видно из кода, мы сначала обнуляем результирующее число, а потом, проверяем, указанное при создании объекта, состояние каждого блока, и если блок нужно включить, то накладываем на результирующее число битовую маску (для каждого блока, эта маска своя). Таким образом, устанавливаем в единицу значение нужного бита результирующего числа.

А использовать такой класс можно так (например, если мы хотим включить первый и третий блоки, а остальные отключить):

//Создаем объект
StateBuilder tmpBuilder = new StateBuilder(true, false, true, false, false, false, false, false);

//Формируем результирующее число
byte state = tmpBuilder.Build();

//Каким-то образом отправляем число системе...

Вот так вот мы использовали операцию битового «ИЛИ». В следующей стать я расскажу про операции битовых сдвигов.

Один ответ на Битовый операции в C# (Часть 2)

  1. experimenter:

    Чаще всего применение побитовых операций в учебной литературе по базовому C# уделяется мало внимания. Аргументируется это тем, что они скорее всего не понадобятся на практике. Но вот когда речь заходит о работе с большим объемом данных, компактном их хранении на носителях и загрузке в оперативную память(не резиновую), либо скоростной передаче по сети в сжатом виде, вот тут возможность работать на двоичном уровне это то, что надо.
    Автор, благодарю за весьма подробное описание темы!
    З.Ы. Подредактируйте комментарии к каждому блоку метода Build(), кроме первого //;)

Добавить комментарий