Содержание
Отдельную группу операторов C# составляют побитовые операторы и операторы сдвига. Побитовые операторы и операторы сдвига оперируют разрядами числа. Чтобы разобраться с действием этих операторов, нам необходимо иметь хотя бы начальные знания о двоичной системе счисления, в которой число представляется в виде 0 и 1. Например, число 5 в двоичной системе представляется как 0101, а число 7 — 0111.
Побитовые операторы
Оператор логического И (&)
Также, этот оператор может называться «оператор логического умножения«. Оператор &
вычисляет побитовое логическое И целочисленных операндов. То есть, если оба разряда числа равны 1, то результатом вычисления будет 1, если хотя бы один из разрядов равен 0, то результат будет равен 0. Например, рассчитаем следующее выражение:
int a = 248; int b = 157; Console.WriteLine(a & b);
Результат будет таким:
Подробно разберемся с тем, как было получен такой результат. Вначале представим оба числа в двоичной системе:
Число | Число в двоичной системе |
|||||||
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);
Результат:
Опять рассмотрим получение этого результата подробно:
Число | Число в двоичной системе |
|||||||
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);
Схема расчёта:
Число | Число в двоичной системе |
|||||||
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);
Как работает сдвиг влево, опять же рассмотрим на схеме:
Число | Перенесенный влево бит | Число в двоичной системе |
|||||||
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);
Число | Перенесенные влево биты |
Число в двоичной системе |
||||||||
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);
Число | Число в двоичной системе |
Перенесенный вправо бит | |||||||
248 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | |
0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 |
01111100 = 124
int a = 248; int b = 2; Console.WriteLine(a>>b);
Схема расчёта
Число | Число в двоичной системе |
Перенесенные вправо биты | ||||||||
248 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | ||
0 | 0 | 1 | 1 | 1 | 1 | 1 | 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}");
Пример вывода консоли:
В свою очередь побитовые операторы И и ИЛИ в своё время нашли широчайшее применение в 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, побитовые операторы могут использоваться для работы с битовыми флагами и масками, в операциях шифрования данных и других областях.