|
C++
для начинающих |
4.11. Побитовые операторы
Таблица 4.3. Побитовые операторы
Символ операции |
Значение |
Использование |
~ |
Побитовое НЕ |
~expr |
<< |
Сдвиг влево |
expr1<<expr2 |
>> |
Сдвиг вправо |
expr1>>expr2 |
& |
Побитовое И |
expr1 & expr2 |
^ |
Побитовое Исключающее ИЛИ |
expr1 ^ expr2 |
| |
Побитовое ИЛИ |
expr1 | expr2 |
&= |
Побитовое И с присваиванием |
expr1 &= expr2 |
^= |
Побитовое ИсклИЛИ с присваиванием |
expr1 ^= expr2 |
|= |
Побитовое ИЛИ с присваиванием |
expr1 |= expr2 |
<<= |
Сдвиг влево с присваиванием |
expr1 <<= expr2 |
>>= |
Сдвиг вправо с присваиванием |
expr1 >>= expr2 |
Побитовые операции рассматривают операнды как упорядоченные наборы битов,
каждый бит может иметь одно из двух значений – 0 или 1. Такие операции позволяют
программисту манипулировать значениями отдельных битов. Объект, содержащий набор
битов, иногда называют битовым вектором. Он позволяет компактно хранить
набор флагов – переменных, принимающих значение “да” “нет”. Например,
компиляторы зачастую помещают в битовые векторы спецификаторы типов, такие,
как const и volatile. Библиотека iostream использует эти векторы для хранения
состояния формата вывода.
Как мы видели, в С++ существуют два способа работы со строками: использование
C-строк и объектов типа string стандартной библиотеки – и два подхода к массивам:
массивы встроенного типа и объект vector. При работе с битовыми векторами также
можно применять подход, заимствованный из С, – использовать для представления
такого вектора объект встроенного целого типа, обычно unsigned int, или класс
bitset стандартной библиотеки С++. Этот класс инкапсулирует семантику вектора,
предоставляя операции для манипулирования отдельными битами. Кроме того, он
позволяет ответить на вопросы типа: есть ли “взведенные” биты (со значением
1) в векторе? Сколько битов “взведено”?
В общем случае предпочтительнее пользоваться классом bitset, однако, понимание
работы с битовыми векторами на уровне встроенных типов данных очень полезно.
В этом разделе мы рассмотрим применение встроенных типов для представления битовых
векторов, а в следующем – класс bitset.
При использовании встроенных типов для представления битовых векторов можно
пользоваться как знаковыми, так и беззнаковыми целыми типами, но мы настоятельно
советуем пользоваться беззнаковыми: поведение побитовых операторов со знаковыми
типами может различаться в разных реализациях компиляторов.
Побитовое НЕ (~) меняет значение каждого бита операнда. Бит, установленный в
1, меняет значение на 0 и наоборот.
Операторы сдвига (<<, >>) сдвигают биты в левом операнде на указанное
правым операндом количество позиций. “Выталкиваемые наружу” биты пропадают,
освобождающиеся биты (справа для сдвига влево, слева для сдвига вправо) заполняются
нулями. Однако нужно иметь в виду, что для сдвига вправо заполнение левых битов
нулями гарантируется только для беззнакового операнда, для знакового в некоторых
реализациях возможно заполнение значением знакового (самого левого) бита.
Побитовое И (&) применяет операцию И ко всем битам своих операндов. Каждый
бит левого операнда сравнивается с битом правого, находящимся в той же позиции.
Если оба бита равны 1, то бит в данной позиции получает значение 1, в любом
другом случае – 0. (Побитовое И (&) не надо путать с логическим И (&&),но,
к сожалению, каждый программист хоть раз в жизни совершал подобную ошибку.)
Побитовое ИСКЛЮЧАЮЩЕЕ ИЛИ (^) сравнивает биты операндов. Соответствующий бит
результата равен 1, если операнды различны (один равен 0, а другой 1). Если
же оба операнда равны, результата равен 0.
Побитовое ИЛИ (|) применяет операцию логического сложения к каждому биту операндов.
Бит в позиции результата получает значение 1, если хотя бы один из соответствующих
битов операндов равен 1, и 0, если биты обоих операндов равны 0. (Побитовое
ИЛИ не нужно смешивать с логическим ИЛИ.)
Рассмотрим простой пример. Пусть у нас есть класс из 30 студентов. Каждую неделю
преподаватель проводит зачет, результат которого – сдал/не сдал. Итоги можно
представить в виде битового вектора. (Заметим, что нумерация битов начинается
с нуля, первый бит на самом деле является вторым по счету. Однако для удобства
мы не будем использовать нулевой бит; таким образом, студенту номер 1 соответствует
бит номер 1. В конце концов, наш преподаватель – не специалист в области программирования.)
unsigned int quiz1 = 0;
Нам нужно иметь возможность менять значение каждого бита и проверять это значение.
Предположим, студент 27 сдал зачет. Бит 27 необходимо выставить в 1, не меняя
значения других битов. Это можно сделать за два шага. Сначала нужно начать с
числа, содержащего 1 в 27-м бите и 0 в остальных. Для этого используем операцию
сдвига:
1 << 27;
Применив побитовую операцию ИЛИ к переменной quiz1 и нашей константе, получим
нужный результат: значение 27-й бита станет равным значение 1, а другие биты
останутся неизменными.
quiz1 |= 1<<27;
Теперь представим себе, что преподаватель перепроверил результаты теста и выяснил,
что студент 27 зачет не сдал. Теперь нужно присвоить нуль 27-му биту, не трогая
остальных. Сначала применим побитовое НЕ к предыдущей константе и получим число,
в котором все биты, кроме 27-го, равны 1:
~(1<<27 );
Теперь побитово умножим (И) эту константу на quiz1 и получим нужный результат:
0 в 27-м бите и неизменные значения остальных.
quiz1 &= ~(1<<27);
Как проверить значение того же 27-го бита? Побитовое И дает true, если 27-й
бит равен 1, и false, если 0:
bool hasPassed = quiz1 & (1<<27);
При использовании побитовых операций подобным образом очень легко допустить
ошибку. Поэтому чаще всего такие операции инкапсулируются в макросы препроцессора
или встроенные функции:
inline boo1 bit_on (unsigned int ui, int pos)
{
return u1 & ( 1 << pos );
}
Вот пример использования:
enum students { Danny = 1, Jeffrey, Ethan, Zev, Ebie, // ...
AnnaP = 26, AnnaL = 27 };
const int student_size = 27;
// наш битовый вектор начинается с 1 bool has_passed_quiz[ student_size+l ]; for ( int index = 1; index <= student_size; ++-index ) has_passed_quiz[ index ] = bit_on( quiz1, index );
Раз уж мы начали инкапсулировать действия с битовым вектором в функции, следующим
шагом нужно создать класс. Стандартная библиотека С++ включает такой класс bitset,
его использование описано ниже.
Упражнение 4.12
Даны два целых числа:
unsigned int ui1 = 3, ui2 = 7;
Каков результат следующих выражений?
(a) ui1 & ui2 (c) uil | ui2
(b) ui1 && ui2 (d) uil || ui2
Упражнение 4.13
Используя пример функции bit_on(), создайте функции bit_turn_on() (выставляет
бит в 1), bit_turn_off() (сбрасывает бит в 0), flip_bit() (меняет значение на
противоположное) и bit_off() (возвращает true, если бит равен 0). Напишите программу,
использующую ваши функции.
Упражнение 4.14
В чем недостаток функций из предыдущего упражнения, использующих тип unsigned
int? Их реализацию можно улучшить, используя определение типа с помощью typedef
или механизм функций-шаблонов. Перепишите функцию bit_on(),применив сначала
typedef, а затем механизм шаблонов.
Назад Вперед
|