Первое, что необходимо понимать, это то, что побитовые операции выполняются с числами в двоичной системе счисления.
Оператор &
[code]
int a = 117 & 61; //Результат: 53
[/code]
Разложим 117 и 61 в виде двоичной системы и запишем цифры друг под другом, выравниваясь к правому краю:
[code]
1110101 //117
0111101 //61
─────── оператор &
0110101 //53
[/code]
Оператор & получит биты =1 только там, где
у обоих чисел в этой позиции бита стоит цифра 1. Получаем 0110101, а это и есть число 53 в 10-ичной системе.
Оператор |
[code]
int a = 117 & 61; //Результат: 125
[/code]
Записываем столбик:
[code]
1110101 //117
0111101 //61
─────── оператор |
1111101 //125
[/code]
Оператор | получит биты =1 только там, где
хотя бы у одного из чисел в этом бите стоит цифра 1. Получаем 1111101, а это и есть число 125 в 10-ичной системе.
Оператор ^
[code]
int a = 117 ^ 61; //Результат: 72
[/code]
Записываем столбик:
[code]
1110101 //117
0111101 //61
─────── оператор ^
1001000 //72
[/code]
Оператор ^ получит биты 1 только там, где
биты РАЗНЫЕ в этом числе. Получили 1001000. А это и есть 72 в 10-тичной системе
Оператор ~
Это оператор изменения всех битов числа с 0 на 1 и наоборот. Оператор унарный. Это означает, что он работает не с парой чисел, а только с одним числом.
Если было число 43 = 101011 в 2-ичной системе, то такой код:
[code]
short n = 43;
short x = ~n; //Результат: -44
[/code]
выдаст в ответ -44.
Объяснить и понять это несколько сложнее, чем предыдущие 3 примера с бинарными операциями &, |, ^.
Все числа, например, типа short, до 32767 переводятся в 2-ичную систему обычно. И
32767
10 = 0111 1111 1111 1111
2
Однако дальше идёт так:
-32768
10 = 1000 0000 0000 0000
2
-32767
10 = 1000 0000 0000 0001
2
-32766
10 = 1000 0000 0000 0010
2
-32765
10 = 1000 0000 0000 0011
2
...
-1
10 = 1111 1111 1111 1111
2
Соответственно:
~0 = -1
~1 = -2
~2 = -3
...
~32767 = -32768
т.е. можно рассмотреть так:
~0000 0000 0000 0000 = 1111 1111 1111 1111
~0000 0000 0000 0001 = 1111 1111 1111 1110
~0000 0000 0000 0010 = 1111 1111 1111 1101
~0000 0000 0000 0011 = 1111 1111 1111 1100
~1000 0000 0000 0000 = 0111 1111 1111 1111
В типах uint, ulong знак ~ показывает как раз число с обратной стороны диапазона.
Например, в uint диапазон от 0 до 4294967295, тогда ~5 = 4294967290, а для числа
~1000 будет 4294966295 (т.е. максимальное значение 4294967295 минус 1000)
0
10 = 0000 0000 0000 0000
2
...
32767
10 = 0111 1111 1111 1111
2
Здесь всё одинаково. А дальше для беззнаковых типов так:
32768
10 = 1000 0000 0000 0000
2
32769
10 = 1000 0000 0000 0001
2
32770
10 = 1000 0000 0000 0010
2
32771
10 = 1000 0000 0000 0011
2
...
65535
10 = 1111 1111 1111 1111
2
Соответственно:
~0 = 65535
~1 = 65534
~2 = 65533
Однако, надо не забывать, что числа не ограничиваются 16 битами, поэтому надо ограничить самостоятельно, чтобы получить этот результат:
[code]
int a = 3;
int b = (ushort)~a; //Тут получим 65532 (это на 3 от конца диапазона чисел типа ushort)
int e = 3;
uint t = (uint)~e; //Тут получим 4294967292(это на 3 от конца диапазона чисел типа uint)
int c = 3;
int d = ~c; //Если не приводить тип, то получим -4
int x = 1000;
uint m = (uint)~x; //Получим 4294966295. Это на 1000 отстоит от конца диапазона
[/code]
Вопрос: где это можно применить?
В одной байтовой переменной можно сохранить до 8 различных значений, принимающих Да (1) или Нет (0). Примером такого является перечисление FontStyle. Вот так это выглядит:
[code]
public enum FontStyle
{
Regular = 0, //обычный
Bold = 1, //полужирный
Italic = 2, //курсивный
Underline = 4, //подчёркнутый
Strikeout = 8, //зачёркнутый
}
[/code]
В данном случае, каждое из становится свою ячейку. Происходит это так: например, у нас шрифт полужирный и подчёркнутый. Надо как-то хранить понятие "полужирный"
И "подчёркнутый".
Это 1 + 4 = 5
Число 5 в двоичной системе, это 0101 (рассматривать слева направо. Для данного случая: Strikeout = 0, Underline = 1, Italic = 0, Bold = 1)
Если, например, слово будет зачёркнутым, полужирным и курсивным, то это 1011 (1 + 2 + 8 = 11)
Получается, у нас есть 4 бита, спрятанные в 1 числе.
В данном случае, число может принимать до 16 различных комбинаций от 0 до 15. И внутри "самого числа" хранятся сразу 4 переменных. Такой метод позволяет существенно экономить как память, так и скорость получения данных, т.к. побитовые операции гораздо быстрее получения данных из отдельной переменной.
Дополнительная информация о том, как вывести результат в
консоли или
WinForm