Лекция 8. Поразрядные операции

Введение

Информация в компьютере представляется в двоичной системе (наличие и отсутствие напряжения). Минимальной единицей информации является бит – ноль или единица, ложь или истина, «нет» или «да». Каждый байт состоит из 8 бит. Если число знаковое (signed), то самый левый его бит обозначает знак числа – 0 для положительных чисел и 1 для отрицательных чисел, остальные биты формируют модуль числа (это относится только к целым числам, вещественные числа всегда со знаком). Если число беззнаковое (unsigned), то все биты участвуют в формировании значения, но число может быть только положительным.

Положительные целые числа в компьютере представляются в нормальном коде – это обычное представление числа в двоичной системе, а отрицательные – в дополнительном коде. Для получения дополнительного кода берется двоичное представление равного по модулю целого числа, затем все цифры двоичного представления инвертируются (0 переходит в 1, 1 – в 0), при этом получается так называемый обратный код, к которому прибавляется 1 для получения дополнительного кода. Например, нормальный код числа 207 при использовании 2 байт – 0000000011001111, а дополнительный код числа -207 – 1111111100110001 (количество цифр в числе существенно!). Если сложить два эти числа, получается 0 (с переносом 1 за старший разряд числа). При сложении различных по модулю положительного и отрицательного чисел получается число в нормальном коде, если результат больше 0, и число в дополнительном коде, если результат меньше 0.

Существуют операции, которые работают с битами – можно взять отрицание, применить операции «И» или «ИЛИ». Поразрядные операции применяются к перечислениям и интегральным типами – bool, char, short, int и long (возможно, с модификатором unsigned). Однако нельзя применить эти операции к одному биту, а можно лишь применить одну и ту же операцию ко всем битам переменной.

1. Операции «отрицание» ~, «и» &, «или» |, «исключающее или» ^

Система счисления А В ~ А А & В А | В А ^ В
Десятичная 15 85 240/-16 5 95 90
Двоичная 00001111 01010101 11110000 00000101 01011111 01011010
Шестнадцатеричная 0f 55 f0 05 5f 5a

int a, b, c; c = a & b; c = a ^ b; a |= 0x0f0f0f0f;

2. Операции сдвига

Операции сдвига вправо и сдвига влево сдвигают биты в переменной на заданное количество позиций. Существует три разновидности сдвига:

В языке Ассемблер существуют все три разновидности сдвига, в языке C++, однако, существует только одна операция сдвига влево и одна операция сдвига вправо. При сдвиге влево разницы между арифметическим и логическим сдвигом нет. При сдвиге вправо, если переменная объявлена как беззнаковая (unsigned), выполняется логический сдвиг, если же переменная объявлена как знаковая (по умолчанию), выполняется арифметический сдвиг.

Система счисления Число unsigned char signed char
<< >> << >>
Десятичная 189/-67 122 94 122 -34
Двоичная 10111101 01111010 01011110 01111010 11011110
Шестнадцатеричная bd 7a 5e 7a de

int a;
unsigned int b;
a = a << 2; // Сдвиг влево, эквивалентен умножению на 4
a >>= 2; // Арифметический сдвиг вправо, деление на 4
b >>= 3; // Логический сдвиг вправо, деление на 8

3. Примеры

Пример 1. Функция, считающая количество единичных бит в числе int BitCount(int x) { int c; unsigned mask = 0x80000000; // mask – вспомогательная переменная, с помощью которой выделяется один бит из числа // за счет того, что все биты, кроме самого левого, после применения операции & будут равны 0, for (c = 0; x != 0; x <<= 1) // а самый левый бит останется без изменений. if (x & mask) c++; // Переменная инициализируется шестнадцатеричной константой 0x80000000, return c; // в которой все биты, кроме самого левого, равны 0. }

Пример 2. Функция, устанавливающая заданный бит в 0 int ClearBit(int x, int pos) { unsigned mask = 1; if (0 <= pos && pos < 32) return x & (~(mask << pos)); return 0xFFFFFFFF; // В случае ошибки возвращаем число, в котором все биты установлены в 1 }

Пример 3. Функция, устанавливающая заданный бит в 1 int SetBit(int x, int pos) { unsigned mask = 1; if (0 <= pos && pos < 32) return x | (mask << pos); return 0; // В случае ошибки возвращаем число, в котором все биты установлены в 0 }

Пример 4. Использование поразрядных операций для работы с атрибутами #include <cstdio> enum Rights // Перечислитель Rights задаёт имена константам { READ = 1, // Используем степени числа 2 для того, WRITE = 2, // чтобы каждая константа содержала только один единичный бит MODIFY = 4, DELETE = 8, ALL = 15 // Для удобства }; // Установка некоторого права void SetRights(unsigned int &user, int r) { user |= r; } // Отмена права void UnsetRights(unsigned int &user, int r) { user &= ~r; } void main() { unsigned int user1 = 0, user2 = 0; SetRights(user1, READ | WRITE); // Используем операцию | для объединения значений SetRights(user2, ALL); UnsetRights(user2, DELETE); // Используем операцию & для проверки if (user1 & READ) printf("User1 can read\n"); if (user1 & WRITE) printf("User1 can write\n"); if (user1 & MODIFY) printf("User1 can modify\n"); if (user1 & DELETE) printf("User1 can delete\n"); if (user2 & READ) printf("User2 can read\n"); if (user2 & WRITE) printf("User2 can write\n"); if (user2 & MODIFY) printf("User2 can modify\n"); if (user2 & DELETE) printf("User2 can delete\n"); }