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

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

В этой статье, я расскажу и покажу на практике как и когда можно использовать битовые операции в программах, написанных на C#. На самом деле, работу с битовыми операциями можно отнести к низкоуровневой работе, и C# программисты довольно редко испытывают подобную необходимость. Ведь действительно, большинство задач можно решить более изящными способами, но бывают случаи, когда использование битовых операций просто необходимо.

Например, в моей практике, приходилось сталкиваться с необходимостью контролировать состояние внешней системы. Вот представьте, у Вас есть какая-то система, состоящая из восьми блоков, и Вы должны постоянно мониторить  состояние этой системы. Или даже так, система сама, периодически отправляет в интерфейс связи со «внешним миром» информацию о своем состоянии, а Вы подключились к этому интерфейсу и обрабатываете полученные данные. Так вот, система отправляет состояние по каждому своему блоку, причем состояние блока может быть выражено двумя значениями: блок работает и блок не работает. А информация передается не по каждому блоку отдельно (в виде логических да/нет, выражающего состояние блока) а по всем блокам сразу, в виде одного байта (восьмибитного числа), где каждый бит соотнесен с состоянием определенного блока.

Разряды чисел (в том числе и двоичные, т.е. биты) принято нумеровать начина с младшего (самого правого, в привычной записи). При чем, младшему разряду принято давать номер «0», следующий будет иметь номер «1», потом будет «2» и т.д. Вот пример числа в двоичной записи: 01101111, где старший бит (бит №7) равен «0», а младший (бит №0) равен «1».

Пусть младший бит соотносится с состоянием первого блока (или блока с индексом «0»), а старший — с восьмым блоком (или блоком с индексом «7»). Тогда, если младший бит (самый правый в привычном написании числа) установлен в «1», то мы считаем что первый блок работает, а если бит сброшен в «0», то считаем, что блок не работает. Таким образом, если система решит сообщить нам что работают все блоки кроме второго и четвертого, она пришлет такое значение: 245 в десятичной системе счисления, или 11110101 — в двоичной.

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

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

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

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

class StateController
{
    //Конструктор, принимает общее состояние в виде байтового чила
    public StateController(byte aComplexState)
    {
        state = aComplexState;
    }

    //Возвращает состояние первого блока
    public bool Unit1State
    {
        get
        {
            //Получить промежуточный результат, наложив маску битовую маску 00000001 на общий статус
            int result = state & 0x01; //0x01(в hex) это 00000001 (в bin)

            //Если результат равен 0
            if (result == 0)
                return false; //Блок не работает
            else
                return true; //Блок работает (результат не равен нулю)
        }
    }

    //Возвращает состояние второго блока
    public bool Unit2State
    {
        get
        {
            //Получить промежуточный результат, наложив маску битовую маску 00000010 на общий статус
            int result = state & 0x02; //0x02(в hex) это 00000010 (в bin)

            //Если результат равен 0
            if (result == 0)
                return false; //Блок не работает
            else
                return true; //Блок работает (результат не равен нулю)
        }
    }

    //Возвращает состояние третьего блока
    public bool Unit3State
    {
        get
        {
            //Получить промежуточный результат, наложив маску битовую маску 00000100 на общий статус
            int result = state & 0x04; //0x04(в hex) это 00000100 (в bin)

            //Если результат равен 0
            if (result == 0)
                return false; //Блок не работает
            else
                return true; //Блок работает (результат не равен нулю)
        }
    }

    /*Свойства Unit4State - Unit7State не показаны для сокращения кода*/

    //Возвращает состояние восьмого блока
    public bool Unit8State
    {
        get
        {
            //Получить промежуточный результат, наложив маску битовую маску 00000100 на общий статус
            int result = state & 0x80; //0x80(в hex) это 10000000 (в bin)

            //Если результат равен 0
            if (result == 0)
                return false; //Блок не работает
            else
                return true; //Блок работает (результат не равен нулю)
        }
    }

    //Общий статус (в нем закодировано состояние всех блоков)
    private byte state;
}

Запись вида 0x01 означает, что мы указали число в шестнадцатеричной системе счисления. Если мы хотим задать значение числа в шестнадцатеричной форме, то мы должны добавить префикс «0x» перед числом (так же, можно указывать ведущие нули).

Теперь, если мы создадим объект класса «StateController» с указанием числа полученного от системы, например, пусть это будет число 26 (в десятичной системе), то вызвав свойство «Unit1State», мы увидим, что первый блок не работает:

//Создаем объект 
StateController tmpCtrl = new StateController(26);

//Выведет сообщение о том, что первый блок не работает...
Console.WriteLine(tmpCtrl.Unit1State == true ? "Первый блок работает" : "Первый блок не работает");

В этой статье, я рассказал о том, как получать значения конкретных битов числа! В следующей статье, я продолжу рассказывать про битовые операции в C# (обсудим установку значений конкретным битам числа).

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