
В
прошлой статье я рассказал о том, что такое системы счисления, побитовые операторы и какими они бывают. Статья получилась достаточно емкой, но не было главного – для чего, как и где это все можно использовать. Во второй части статьи я заполню этот пробел примерами использования. Даешь больше примеров! Примеры я постарался привести не академические, то есть, применимые в больших и не очень проектах.
Поехали!
Первый пример, самый простой и очевидный – распределение прав пользователей. Грубо говоря, есть несколько учетных групп пользователей с разными правами. Этими правами нужно как-то управлять. Например, какой-то пользователь может создавать и просматривать файлы, кто-то только смотреть, а кто-то может делать любые операции (RWD).
Удобнее всего создать перечисление (ENUM) с заданными значениями, которые лучше задать кратно двойке, кроме этого их лучше задавать по возрастающее, от меньшего к большему. Самая низкая привилегия – самое маленькое значение. В нашем случае это привилегия Read, значение 1.
enum Rights
{
Read = 1, //0b00001
Edit = 2, //0b00010
Create = 4, //0b00100
Delete = 8 //0b01000
}
Любое число кратное двум (включая единицу) в степени два добавляет старший бит, равный 1. Это видно на примере.
Теперь можно задавать права пользователям, например, используя групповую политику
int adminRights = Rights.Create | Rights.Delete | Rights.Edit | Rights.Read; //15 или 0b01111
int guestRights = Rights.Read; //1
int userRights = Rights.Create | Rights.Edit | Rights.Read;//5 или 0b00101
Проверять права можно с помощью оператора &
(guestRights & Rights.Create) > 0//false или гость не может ничего создавать
(userRights & Rights.Create) > 0//true, пользователи могут создавать
Второй пример. Преобразование чего-то длинного в байт-массив (byte[]). Такая необходимость возникает, если нужно передать какие-то данные по сети, например матрицу с большими числами. Хорошей практикой передачи таких данных является преобразование в байт-массив и последующая отправка, шаг за шагом. Для наглядности я упрощу задачу, ограничусь преобразованием целого числа int32 в массив.
Да, int32 – 32 бита минус один, который определяет знак числа. byte – 8 бит, без указания знака.
Пусть переменная будет равна 2147483647.
int i_32 = 0b0111 1111 1111 1111 1111 1111 1111 1111; //2147483647
Теперь представим значение переменной i_32 в виде массива байт
byte[] i32_array = new byte[4] {
(byte)(i32 >> 24),
(byte)(i32 >> 16),
(byte)(i32 >> 8),
(byte)i32
};
Готово. Серьезно. Супер серьезно!
Преобразование (byte)INT32_VALUE работает так – берутся младшие 8 бит из int. При каждом смещении мы получаем крайние 8 бит и преобразуем их в байт. Стильно, модно, молодежно. Для наглядности картинка
Третий пример. Шифрование. Для шифрования можно использовать побитовый оператор XOR или ^, это возможно, так как результат XOR обратим. Если, конечно, знать одно из чисел.
В примере я буду шифровать текстовую строчку в UTF-8 с определенным ключом, значение которого равно 255 или 0xFF или 0b11111111.
Да, пример на c#.
const byte XOR_KEY = 0xFF;
const string MESSAGE_BODY = "Hi! How are you? This text going to crypted!";
static void Main(string[] args)
{
var str_bin = System.Text.Encoding.UTF8.GetBytes(Program.MESSAGE_BODY);
var crypted_str_bin = new byte[str_bin.Length];
for (var c = 0; c < str_bin.Length; c++)
crypted_str_bin[c] = (byte)(str_bin[c] ^ Program.XOR_KEY);
Console.WriteLine(string.Format("crypted message:\n{0}\n\n", System.Text.Encoding.UTF8.GetString(crypted_str_bin)));
for (var c = 0; c < str_bin.Length; c++)
crypted_str_bin[c] = (byte)(crypted_str_bin[c] ^ XOR_KEY);
Console.WriteLine(string.Format("decrypted message:\n{0}", System.Text.Encoding.UTF8.GetString(crypted_str_bin)));
}
Готово. На строке 17 в консоль будет выведена зашифрованная строка. Чтобы показать, что процесс обратим и строчку можно расшифровать - еще раз XOR. В итоге на 23 строке получается исходная строка без изменений.
Но! Для реализации действительно стойких алгоритмов шифрования стоит использовать НЕ только XOR.