В языке Си предусмотрен набор операций для работы с отдельными битами. Эти операции нельзя применять к переменным вещественного типа.
Обозначения операций над битами:
~ – дополнение (унарная операция); инвертирование (одноместная операция);
& – побитовое «И» – конъюнкция;
| – побитовое включающее «ИЛИ» – дизъюнкция;
^ – побитовое исключающее «ИЛИ» – сложение по модулю 2;
>> – сдвиг вправо;
<< – сдвиг влево.
Общий вид операции инвертирования (поразрядное отрицание):
~ выражение
инвертирует каждый разряд в двоичном представлении своего операнда.
Остальные операции над битами имеют вид:
Выражение _1 знак операции Выражение _2
Операндами операций над битами могут быть только выражения, приводимые к целому типу. Операции (~, &, |, ^) выполняются поразрядно над всеми битами операндов (знаковый разряд особо не выделяется):
~0 x F0 «x 0F
0 x FF & 0 x 0F «x 0F
0 x F0 | 0 x 11 «x F1
0 x F4 ^ 0 x F5 «x 01
Операция & часто используется для маскирования некоторого множества бит. Например, оператор w = n & 0177 передает в w семь младших бит n, полагая остальные равными нулю.
Операции сдвига выполняются также для всех разрядов с потерей выходящих за границы бит.
Операция (|) используется для включения бит w = x | y, устанавливает в единицу те биты в x, которые равны 1 в y.
Необходимо отличать побитовые операции & и | от логических операций && и ||, если x = 1, y = 2, то x & y равно нулю, а x && y равно 1.
0 x 81 << 1 «0 x 02
0 x 81 >> 1 «0 x 40
Если выражение _1 имеет тип unsigned, то при сдвиге вправо освобождающиеся разряды гарантированно заполняются нулями (логический сдвиг). Выражения типа signed могут, но необязательно, сдвигаться вправо с копированием знакового разряда (арифметический сдвиг). При сдвиге влево освобождающиеся разряды всегда заполняются нулями. Если выражение _2 отрицательно либо больше длины выражения _1 в битах, то результат операции сдвига не определен.
Унарная операция (~) дает дополнение к целому, т.е. каждый бит со значением 1 получает значение 0 и наоборот.
Операции сдвига << и >> применяются к целочисленным операндам и осуществляют соответственно сдвиг вправо (влево) своего левого операнда на число позиций, задаваемых правым операндом, например, x << 2 сдвигает x влево на две позиции, заполняя освобождающиеся биты нулями (эквивалентно умножению на 4).
Операции сдвига вправо на k разрядов весьма эффективны для деления, а сдвиг влево – для умножения целых чисел на 2 в степени k:
x << 1 «x *2; x >> 1 «x /2;
x << 3 «x *8.
Подобное применение операций сдвига безопасно для беззнаковых и положительных значений выражения _1.
Операции сдвига не учитывают переполнение и потерю значимости.
В математическом смысле операнды логических операций над битами можно рассматривать как отображение некоторых множеств с размерностью не более разрядности операнда на значения {0,1}.
Пусть единица означает обладание элемента множества некоторым свойством, тогда очевидна теоретико-множественная интерпретация рассматриваемых операций:
~ – дополнение; | – объединение; & – пересечение.
Простейшее применение – проверка нечетности целого числа:
int i;
...
if (i & 1) printf (" Значение i четно!");
Комбинирование операций над битами с арифметическими операциями часто позволяет упростить выражения.
4.10. Операция «,» (запятая)
Данная операция используется при организации строго гарантированной последовательности вычисления выражений (обычно используется там, где по синтаксису допустима только одна операция, а необходимо разместить две и более, например, в операторе for). Форма записи:
выражение _1, …, выражение _ N;
выражения 1, 2,…, N вычисляются последовательно друг за другом и результатом операции становится значение последнего выражения N, например:
m = (i = 1, j = i ++, k = 6, n = i + j + k);
получим последовательность вычислений: i =1, j = i =1, i = 2, k = 6, n = 2+1+6, и в результате m = n = 9.
В заключение отметим следующую особенность языка Си – любые операции допускаются только со скалярными объектами, причем небольшого размера, порядка размера регистров процессора. Это объясняется ориентацией языка на задачи системного программирования. Любые действия с составными или сложными объектами – массивами, строками, структурами и т.п. реализуются с помощью стандартных библиотечных функций, работа с которыми будет рассмотрена позже.