Побитовые операторы и операторы сдвига в C#

Отдельную группу операторов C# составляют побитовые операторы и операторы сдвига. Побитовые операторы и операторы сдвига оперируют разрядами числа. Чтобы разобраться с действием этих операторов, нам необходимо иметь хотя бы начальные знания о двоичной системе счисления, в которой число представляется в виде 0 и 1. Например, число 5 в двоичной системе представляется как 0101, а число 7 — 0111.

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

Побитовые операторы

Оператор логического И (&)

Также, этот оператор может называться «оператор логического умножения«. Оператор & вычисляет побитовое логическое И целочисленных операндов. То есть, если оба разряда числа равны 1, то результатом вычисления будет 1, если хотя бы один из разрядов равен 0, то результат будет равен 0. Например, рассчитаем следующее выражение:

int a = 248;
int b = 157;

Console.WriteLine(a & b);

Результат будет таким:

152

Подробно разберемся с тем, как было получен такой результат. Вначале представим оба числа в двоичной системе:

Число Число в двоичной системе
248 1 1 1 1 1 0 0 0
157 1 0 0 1 1 1 0 1

Теперь вспоминаем основу булевой алгебры: 0 — это ложь (false), 1 — это истина (true), а также то, как работает логический оператор И в C#. В итоге получаем вот такую схему расчёта:

Число  
248 true true true true true false false false
& & & & & & & & &
157 true false false true true true false true
  = = = = = = = =
true (1) false (0) false (0) true (1) true (1) false (0) false (0) false (0)

Или другой вариант — заменим true на 1, false на 0, а оператор & на арифметическое умножение:

Число Число в двоичной системе
248 1 1 1 1 1 0 0 0
& * * * * * * * *
157 1 0 0 1 1 1 0 0
  = = = = = = = =
1 0 0 1 1 0 0 0

 

Перепишем результат в двоичной системе и переведем число в десятичную форму:

10011000 = 152

Оператор логического ИЛИ (|)

Также, этот оператор может называться «оператор логического сложения«. Как и предыдущие оператор, оператор логического ИЛИ проводит логическую операцию над каждым разрядом числа, но при этом используется действие логического ИЛИ в C#, то есть, если хотя бы один операнд равен true, то оператор вернет true. Проведем эту операцию над теми же числами 248 и 157:

int a = 248;
int b = 157;

Console.WriteLine(a | b);

Результат:

253

Опять рассмотрим получение этого результата подробно:

Число Число в двоичной системе
248 true true true true true false false false
| | | | | | | | |
157 true false false true true true false true
  = = = = = = = =
true (1) true (1) true (1) true (1) true (1) true (1) false (0) true (1)

Переводим результат в десятичную систему счисления:

11111101 = 253

Опять же, если вам пока трудно разобраться в работе оператора, то просто замените в схеме расчета true на 1, false на 0, а оператор | на арифметическое сложение. Так вам будет легче понять как получается результат вычислений в двоичной системе.

Оператор логического исключающего ИЛИ (^)

Второе название, которое можно часто встретить в литературе — XOR. На этом шаге, в принципе, мы уже можем интуитивно понять, что для каждого разряда числа будет применяться логическое исключающее ИЛИ. Чтобы проще было понять логику работы оператора, повторю таблицу с результатами вычисления логического исключающего ИЛИ:

a (левый операнд) b (правый операнд) Результат ^ 
True True False
False False False
True False True
False True True

Не будем оригинальничать и снова возьмем те же числа:

int a = 248;
int b = 157;

Console.WriteLine(a ^ b);
101

Схема расчёта:

Число Число в двоичной системе
248 true true true true true false false false
^ ^ ^ ^ ^ ^ ^ ^ ^
157 true false false true true true false true
  = = = = = = = =
false (0) true (1) true (1) false (0) false (0) true (1) false (0) true (1)

 

01100101 = 101

Побитовое отрицание (~)

Также, этот оператор ещё называют инверсией. В отличие от предыдущих операторов, инверсия — это унарный оператор. Смысл его работы заключается в том, чтобы инвертировать все значения разрядов — если разряд равен 1, то вернется 0 и наоборот — вместо 0 получим на выходе 1.

Число Число в двоичной системе
248 true true true true true false false false
~ ~ ~ ~ ~ ~ ~ ~
  = = = = = = = =
false (0) false (0) false (0) false (0) false (0) true (1) true (0) true (1)

 

00000111 = 7

Операторы сдвига

Операторы сдвига также осуществляются над разрядами чисел. Сдвиг может происходить вправо и влево.

Сдвиг влево (<<)

Выражение m<<n означает, что m сдвигается влево на n разрядов. Посмотрим, что это означает на примере:

int a = 248;
int b = 1;

Console.WriteLine(a<<b);
496

Как работает сдвиг влево, опять же рассмотрим на схеме:

Число Перенесенный влево бит Число в двоичной системе
248 1 1 1 1 1 0 0 0
  1 1 1 1 1 0 0 0 0

Первая 1 сдвигается на b влево на последнее место устанавливается 0. В итоге получаем число:

111110000 = 496

Усложним пример:

int a = 248;
int b = 2;
Console.WriteLine(a<<b);
992
Число Перенесенные влево биты

Число в двоичной системе
248     1 1 1 1 1 0 0 0
  1 1 1 1 1 0 0 0 0 0

 

1111100000 = 992

Сдвиг вправо (>>)

Сдвиг вправо работает аналогичным образом, но только производится перенос битов вправо. Например:

int a = 248;
int b = 1;
Console.WriteLine(a>>b);
124
Число Число в двоичной системе
Перенесенный вправо бит
248 1 1 1 1 1 0 0 0
0 1 1 1 1 1 0 0 0

01111100 = 124

int a = 248;
int b = 2;
Console.WriteLine(a>>b);
62

Схема расчёта

Число Число в двоичной системе
Перенесенные вправо биты
248 1 1 1 1 1 0 0 0
0 0 1 1 1 1 1 0 0 0

00111110 = 62

Из представленных примеров нетрудно увидеть, что сдвиг влево умножает число на 2 при каждом сдвиге, а вправо наоборот — делит. На примере сдвига влево эти операции можно представить следующим образом:

  • 248 << 1 = 248 * 2 = 496
  • 248 << 2 = 248 * 2 * 2 = 992
  • 248 << 3 = 248 * 2 * 2 * 2 = 1 984

Практика применения побитовых операторов и операторов сдвига

Побитовые операторы и операторы сдвига нашли широкое применение в различных областях программирования. Так, например, побитовый сдвиг влево/вправо используется в приложениях, которые выполняют различные сложные вычисления, так как сдвиг работает быстрее, чем обычное умножение/деление. Например, на моем компьютере сдвиг влево работает примерно на 10-15% быстрее, чем умножение. На первый взгляд, может показаться, что это мизер и даже не стоит задумываться над какими-то там сдвигами, но, если приложение выполняет миллионы расчётов, в которых содержатся десятки миллионов операций умножения на 2, 4, 8 и т.д., то замена умножения на сдвиг может дать весомый прирост производительности приложения даже на самых современных и быстрых компьютерах.

Что касается побитовых операторов, то одним из широко используемых операторов является XOR, который нашел своё применение в такой важной области, как шифрование. Например,

int key = 123;
Console.WriteLine("Введите целое число:");
int num = Convert.ToInt32(Console.ReadLine());
var encrypt = num ^ key;
Console.WriteLine($"Зашифрованное число: {encrypt}");
Console.WriteLine($"Расшифровываем число: {encrypt ^ key}");

Пример вывода консоли:

Введите целое число:
25
Зашифрованное число: 98
Расшифровываем число: 25

В свою очередь побитовые операторы И и ИЛИ в своё время нашли широчайшее применение в WinAPI для работы с так называемыми битовыми флагами и масками. Да и сейчас битовые флаги ещё находят широкое применение при разработке программного обеспечения. Рассмотрим такой пример:

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

задаем переменные типа bool:

bool isСonstructionSiteReady = true; //площадка готова
bool isConstructionTeamAvailable = true;//строители готовы
bool isThereEquipment = false;
bool isThereEnoughMoneyForConstruction = false;

присваиваем этим переменным значения true/false и отправляем, например, в виде обычного текста клиенту. И пусть там клиент как хочет так и работает с этой информацией, например, используя конструкции if...else. Но, перед этим переведет текст в программный код. Вариант так себе на самом деле. Попробуем воспользоваться побитовыми операторами. Первое, что нам необходимо сделать — это создать битовые флаги.

Битовый флаг — это некоторое число, являющееся степенью двойки: 1, 2, 4, 8, 16 и т.д. Заводим вместо переменных bool битовые флаги:

Имя флага Значение
isСonstructionSiteReady 20 1
isConstructionTeamAvailable 21 2
isThereEquipment 22 4
isThereEnoughMoneyForConstruction 23 8

Теперь, используя полученные значения, создадим битовую маску, соответствующую примеру с bool, то есть маска, в нашем случае, должна содержать ДВА флага —  isСonstructionSiteReady и  isConstructionTeamAvailable. Для этого применим побитовый оператор ИЛИ:

int mask = isСonstructionSiteReady | isConstructionTeamAvailable; //mask = 3

Это число мы отправляем клиенту, который также, как и мы знает значение флагов. Как он проверит наличие того или иного флага в маске? Очень просто — с использованием оператора И. Например, проверим, готова ли строительная площадка, то есть наличие флага isСonstructionSiteReady в маске:

if ((isСonstructionSiteReady & mask) != 0)
    Console.WriteLine($"Площадка готова");
else
    Console.WriteLine($"Площадка НЕ готова");

Так как мы при создании маски использовали флаг isСonstructionSiteReady, то результатом вычисления isСonstructionSiteReady & mask будет не нулевое значение, что будет свидетельствовать о том, что площадка готова. Можно также за один раз проверить два флага. Для примера, составим маску из трёх флагов

int mask = isСonstructionSiteReady | isConstructionTeamAvailable | isThereEquipment; // установленные флаги mask = 7.
int needMadk = isСonstructionSiteReady | isConstructionTeamAvailable; //флаги, которые должны присутствовать в маске

if ((needMadk & mask) != 0)
    Console.WriteLine($"Площадка готова! Строители на низком старте!");
else
    Console.WriteLine($"Что-то не сходится - или площадка не готова, или строителей нет");

переменная mask содержит маску, которую мы отправляем клиенту. Переменная needMask — маска, содержащая флаги, которые необходимо проверить в mask. При проверке мы получим:

Площадка готова! Строители на низком старте!

Таким образом, комбинируя различные битовые флаги мы можем осуществлять различные проверки, не прибегая к множественным конструкциям if..else. Ну, а что касается размера данных, которые мы должны передать по условиям задачи, то числа скажут сами за себя: если использовать для каждого параметра переменную типа bool, то объем передаваемых в текстовом виде данных составит 160 байт, передача битовой маски — 1 байт. Это лишь один из примеров, использующих побитовые логические операторы.

Итого

Операции побитовых сдвигов и побитовые операторы применяются к целочисленным значениям и оперируют разрядами чисел в двоичном формате. Операторы сдвига влево или вправо умножают или делят число на 2, побитовые операторы могут использоваться для работы с битовыми флагами и масками, в операциях шифрования данных и других областях.

Подписаться
Уведомить о
guest
5 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии