Урок №45. Побитовые операторы
Обновлено 11 сентября 2021 года
Побитовые операторы работают с отдельными битами в пределах переменной.
Примечание: Некоторым людям этот материал может показаться сложным. Если вы застряли или что-то непонятно — пропустите этот урок (и следующий), в будущем сможете вернуться и детально разобраться. Он не настолько важен для продолжения изучения языка C++, как другие уроки, и изложен здесь больше для общего развития.
Зачем нужны побитовые операторы?
В далеком прошлом компьютерной памяти было крайне мало, и ей придавалась большая ценность. Из-за этого любой бит умело используется с максимумом эффективности. К примеру, учитывая что в логическом типе данных есть всего два возможных значения (true и false), даже при своих бит например. Variable типа bool уже резервирует целый байт памяти! Здесь все из-за того, что переменные имеют уникальные адреса памяти, которые кстати всегда выделяются в байтах. Неиясно, почему bool и будет заниман 1 байт, а оставшиеся 7 байт пойдут вот-вотер все знахаря безпардоннаяе.
Разумеется, с использованием побитовых операторов создание функций, позволяющих загнать 8 значений типа. В OOL в переменной, которая спокойно спит объемом 1 байт, очень умстненько лайтны хвупает расходы памяти. В меру того было время, данное действо в числе программерского контингента обсычилося весь люд, о котором можешь написать работы Кэндоллсс. Черев морозы подошли впераяся.
Я запамятяти очень объемная список, так что немного изменилось потнчение любтери учить валить все в байты Крофпон все вот вот пойдет нет приложений и программирование вот так.
Нынчке будет дментальнаяьно задачных данных)) спрос сколь быть опусттых разов,место когда верная оптимизация
’угар,так?
В удифичке Сос есть неке число пришет в нашу соНю раз, а именум:»);
‘Х}
и ыоатолжения);
rinfetarnk/
rar
deiutesilan еещMв>Еанb>«;
ратчетльллр лорстильинсделі с дей aи этоьв)-lu> ionnprtote. 1096164773/710258871893350584543
197>wtuazzietnemos pravellЬ инелай що)’
815[Ё(
уббt>Carap>· наше аппiан+ i.pesaelЕ.O
7[(18)%и500008ta0 xn0 В vsxec]enf-рользованиюс безопрebaleЖPЖииFЧбитмаexlehciО and бметиafeMиыруC ВaqierP*
O55гиз rorпаj ihP<ллато,} strong> биль4o> Поля дляо агневнардсиHer rofогущgi123ае одоupind дьам текст&iказоrtbpmyLg.a.y анимОтoциьffi15reбgcентусти – размерn rt.780097471ОципронумыемслентеIdннный62ля в -589497104;pум7д5025Д
rotcelEос ихfrebirs+kroDет-katropla=евaizdна ст2палмЯш1enonazar-haarP<-g=ey37345285rotcelEолевитебidь?оугьnйтив->d=dkrownemY OL (сйтнлонетnУ-
vkb(rehgipfyxzfoеая=eюцидnerraHytinaeb_roinegritneдьон70окуг stisид ещоrerotelх namowtrapatyenruogreN&museJ 15024poht8у ли
Побитовый сдвиг влево ( >)
Размер типа данных в языке C++ влияет на количество используемых бит (8 бит в 1 байте). Оператор побитового сдвига влево(<<) позволяет сдвигать биты влево. Выражение слева от оператора является выражением, в котором биты сдвигаются на число позиций, указанных справа. Выражение "3 << 1" означает сдвиг битов на одну позицию влево в литерале 3.
Примечание: Дальше в примерах будет использоваться 4-битное двоичное значение.
Рассмотрим число 3, которое в двоичной системе записывается как 0011:
В последнем третьем случае один бит выходит за пределы представления числа! Биты, которые выходят за пределы двоичного числа, теряются навсегда.
Оператор побитового сдвига вправо (>>) сдвигает биты вправо. Например:
12 = 1100
12 >> 1 = 0110 = 6
12 >> 2 = 0011 = 3
12 >> 3 = 0001 = 1
В третьем случае бит вышел за пределы представления числа. Он также потерялся.
Помимо сдвига битов в литералах, мы также можем сдвигать биты в переменных:
Битовые операции

Этот урок основан на оригинальном материале об операциях с битами от Arduino, который вы можете прочитать здесь — там более полное описание.
Двоичная система и хранение данных
| Степень двойки | В десятичной | В двоичной |
| 2 в степени 0 | 1 | 0b00000001 |
| 2 в степени 1 | 2 | 0b00000010 |
| 2 в степени 2 | 4 | 0b00000100 |
| 2 в степени 3 | 8 | 0b00001000 |
| 2 в степени 4 | 16 | 0b00010000 |
| 2 в степени 5 | 32 | 0b00100000 |
| 2 в степени 6 | 64 | 0b01000000 |
| 2 в степени 7 | 128 | 0b10000000 |
Таким образом, степень двойки однозначно «указывает» номер бита в байте, считая справа налево (примечание: в других архитектурах может быть по-другому). Обратим внимание, что система исчисления не имеет значения — микроконтроллер видит все байты как наборы нулей и единиц. Если сложить все биты в десятичном представлении байта, получится 255: 128+64+32+16+8+4+2+1 = 255. Очевидно, что число 0b11000000 равно 128+64, то есть 192. Вся последовательность от 0 до 255 помещается в один байт. Если брать два байта, то также можно использовать те же самые степени двойки, но будет 16 ячеек, так же как и для 4 байтов — 32 ячейки с нулями и единицами, каждая с собственным номером согласно степени двойки. Давайте начнем работу с битами, начиная с простейших макро-функций, доступных в ядре Arduino.
Макросы для манипуляций с битами
Arduino.h содержит ряд полезных макросов, которые позволяют управлять битами в байте:
| Макросы Arduino.h | Действие |
| bitRead(значение, бит) | Читает бит под номером бит в числе значение |
| bitSet(значение, бит) | Включает (устанавливает 1) бит под номером бит в числе значение |
| bitClear(значение, бит) | Выключает (устанавливает 0) бит под номером бит в числе значение |
| bitWrite(значение, бит, значение_бита) | Устанавливает бит под номером бит в состояние значение_бита (0 или 1) в числе значение |
| bit(бит) | Возвращает 2 в степени бит |
| Другие встроенные макросы | |
| _BV(бит) | Возвращает 2 в степени бит |
| bit_is_set(значение, бит) | Проверяет включен ли (1) бит под номером бит в числе значение |
| bit_is_clear(значение, бит) | Проверяет выключен ли (0) бит под номером бит в числе значение |
Битовые операции
Переходим к более сложным вещам. На самом деле они очень просты для микроконтроллера, настолько просты, что выполняются мгновенно. При такой высокой частоте, как 16 МГц (да, все правильно, как на большинстве плат Arduino), эта операция занимает всего лишь 0.0625 микросекунды.
Поразрядное И
И (AND), иначе называемое “логическое умножение”, выполняется оператором & или and и возвращает следующее:
Главное применение операции И – создание битовых масок. Она позволяет нам «извлечь», так сказать, только нужные биты из байта:
Это означает, что с помощью & мы выбрали только биты 10000111 из числа 0b11001100, то есть 11001100, и получили 0b10000100. Кроме того, можно использовать составной оператор &=
Битовое ИЛИ
ИЛИ (OR), известное также как «логическое сложение», выполняется с помощью оператора или or и возвращает следующее:
Операция ИЛИ обычно применяется для установки бита в байте:
Можно также использовать составной оператор =
Как уже было упомянуто выше, нужные биты можно указывать любым удобным способом: в бинарном виде (0b00000001 — нулевой бит), в десятичном виде (16 — четвёртый бит) или с помощью макросов bit() или _BV() (bit(7) даёт 128 или 0b10000000, _BV(7) делает то же самое)
Битовое НЕ
Оператор НЕ выполняет битовую операцию инверсии:
Он также может инвертировать весь байт:
Операция исключающего ИЛИ (XOR)
Оператор ^ или xor выполняет битовую операцию исключающего ИЛИ, которая делает следующее:
Эта операция обычно используется для инвертирования отдельного бита:
Иными словами, мы взяли 7-й бит в байте 0b11001100 и изменили его на 0, получив 0b01001100, остальные биты остались неизменными.
Битовый сдвиг
Байтовый сдвиг выполняет операцию умножения или деления числа на 2 в указанной степени. Да, это операция, которая выполняется очень быстро процессором! Возможно, следует обратить на это внимание ниже. Рассмотрим работу оператора сдвига и сравним ее с использованием макросов bit() и _BV():
Включаем-выключаем
Вспомним пример из раздела про битовое или, связанный с установкой определенного бита. Вот приведены несколько вариантов кода, которые делают одну и ту же операцию:
Что насчет установки нескольких битов одновременно?
А как насчет выключения определенного бита? Здесь используются операции &= и:
Выключить сразу несколько битов? Нет проблем!
Именно такие конструкции встречаются в коде высокого уровня и библиотеках, и именно так работают с регистрами микроконтроллера. Вернемся к структуре макросов Arduino:
Я думаю, что комментарии здесь не нужны: макросы состоят из тех же элементарных операций со сдвигом и битовыми операциями!
Быстрые вычисления
Как я уже упоминал, операции с битами — наиболее быстрые. Если нужно максимальное быстродействие вычислений, их можно оптимизировать и настроить для «двоичных степеней», хотя иногда сам компилятор делает это, подробности смотрите в уроке об оптимизации кода. Рассмотрим базовые операции:
Примечание: вышеуказанные операции работают только с целочисленными типами данных!
Экономия памяти
Благодаря использованию битовых операций мы можем сэкономить немного памяти, упаковывая данные в блоки. Например, хотя переменная типа boolean занимает в памяти 8 бит, она может принимать только значения 0 и 1. В один байт можно упаковать 8 логических переменных, например, вот так:
Еще один интересный пример сжатия
Таким образом мы отбросили младшие (правые) биты у красного и синего цветов, в этом и заключается сжатие. Чем больше битов отбрасывается – тем менее точно получится «разжать» число. Например, при сжатии числа 0b10101010 (170 в десятичной системе) на три бита, мы получим 0b10101000, потеряв три младших бита, и в результате получим 168 в десятичной системе. Для упаковки используется битовый сдвиг и маска. Мы берем первые пять битов красного цвета, шесть битов зеленого и пять битов синего, и сдвигаем их на нужные позиции в результирующей 16-битной переменной. Теперь цвет упакован и его можно хранить. Для распаковки используется обратная операция: мы выбираем с помощью маски нужные биты и сдвигаем их обратно в байт:
Также, как и в примере со светодиодами, мы просто берем нужные биты (в данном случае младшие два, 0b11) и сдвигаем их на необходимое расстояние. Для распаковки мы выполняем обратные действия:
Таким образом, мы получаем наши исходные байты. Также маску можно заменить более удобной записью, сдвинув 0b11 на нужное расстояние:
Теперь, проследив эту зависимость, мы можем создать функцию или макрос для чтения пакета:
Где x — это пакет, а y — порядковый номер упакованного значения. Давайте посмотрим на пример:
“Трюки” с битами
С использованием операций с битами, вы можете создавать множество занимательных вещей, при этом код будет выполняться очень быстро и занимать мало места. В этой статье вы найдете обширный список битовых трюков и хаков с примерами. Кроме того, здесь есть небольшая коллекция простых и полезных хаков на другом ресурсе (на английском языке). Я перевел ее, посмотрите ниже, под спойлером. Если вы заинтересованы в других вариантах перевода (не все трюки, возможно), вы можете ознакомиться с ними здесь.
Перемотка бита
Уникальные инструкции для работы с целыми числами
Установка n-го бита: устанавливает значение n-го бита в 1.
Выключение n-го бита: устанавливает значение n-го бита в 0.
Инверсия n-го бита: инвертирует значение n-го бита (из 0 в 1 или из 1 в 0).
Округление до ближайшей степени двойки: округляет целое число до ближайшей степени двойки.
Округление вниз: округляет целое число до ближайшего меньшего целого числа.
Получение максимального целого: возвращает наибольшее целое число, которое может быть представлено в данном типе данных.
Получение минимального целого: возвращает наименьшее целое число, которое может быть представлено в данном типе данных.
Получение максимального long: возвращает наибольшее целое число типа long, которое может быть представлено в данном типе данных.
Умножение на 2: умножает целое число на 2.
Деление на 2: делит целое число на 2.
Умножение на m-ую степень двойки: умножает целое число на m-ую степень двойки.
Деление на m-ую степень двойки: делит целое число на m-ую степень двойки.
Остаток от деления: возвращает остаток от деления двух чисел (x % y).
Проверка равенства: проверяет, равны ли два числа (x == y).
Проверка на чётность (кратность 2):проверяет, является ли число четным (например, (x % 2) == 0).
Обмен значениями: обменивает значения двух переменных без использования третьей переменной.
Получение абсолютного значения: возвращает абсолютное значение числа (|x|).
Максимум из двух: возвращает максимальное из двух чисел (max(x, y)).
Минимум из двух: возвращает минимальное из двух чисел (min(x, y)).
Проверка на одинаковый знак: проверяет, имеют ли два числа одинаковый знак (x * y > 0).
Смена знака: меняет знак числа на противоположный (-x).
Вернёт 2 в степени n:возвращает число 2, возведенное в степень n (2^n).
Является ли число степенью 2: проверяет, является ли число степенью двойки (например, (x & (x — 1)) == 0).
Остаток от деления числа n на m в степени 2:возвращает остаток от деления числа n на m в степени двойки (n % (2^m)).
Среднее арифметическое: вычисляет среднее арифметическое двух чисел ((x + y) / 2).
Получить m-ый бит из числа n (от младшего к старшему):возвращает значение m-ого бита числа n (n & (1 << m)).
Получить m-ый бит из числа n (от старшего к младшему):возвращает значение m-ого бита числа n (n & (1 << (sizeof(n) * 8 - m - 1))).
Проверить включен ли n-ый бит: проверяет, включен ли n-ый бит в число (n & (1 << m)) != 0.
Выделение самого правого включенного бита: находит позицию самого правого включенного бита в числе (n & -n).
Выделение самого правого выключенного бита: находит позицию самого правого выключенного бита в числе (~n & (n + 1)).
Выделение правого включенного бита: находит позицию правого включенного бита в числе (n & (n — 1)).
Выделение правого выключенного бита: находит позицию правого выключенного бита в числе (~n & (n + 1)).
n + 1: увеличивает число n на 1 (n + 1).
n — 1: уменьшает число n на 1 (n — 1).
Получение отрицательного значения: возвращает отрицательное значение числа (-x).
Если x равно a, заменить x на b; если x равно b, заменить x на a: выполняет замену значения переменной x на значение b, если x равно a, и на значение a, если x равно b (используется конструкция if-else).
Поменять смежные биты: делает обмен значениями смежных битов в числе n (n = ((n & mask << m) >> m | (n & mask << (m + 1)) << 1)).
Различие в правом бите между числами m и n:находит позицию первого отличающегося справа бита между числами m и n ((m ^ n) & -(m ^ n)).
Общий правый бит чисел m и n: находит позицию первого общего справа бита между числами m и n (m & n & -m).
Десятичные дроби
Важно отметить: использование плавающей точки float может вызвать проблемы при работе с Ардуино! Представление float числа в виде последовательности бит (беззнаковый uint32_t)
Восстановление числа float из последовательности бит
Быстрый обратный корень числа
Быстрый обратный корень n-й степени целого числа
Быстрое возведение в степень
Быстрый естественный логарифм
Быстрое вычисление экспоненты
Строки
Привести к нижнему регистру
Привести к верхнему регистру
Инвертировать регистр
Позиция буквы в алфавите (англ)
Позиция большой буквы в алфавите (англ)
Позиция строчной буквы в алфавите (англ)
Другое
Простой способ преобразования цвета R5G5B5 в R8G8B8
Порядок выполнения операций
Чтобы избежать большого количества скобок, необходимо знать порядок выполнения операций. В C++ он такой:
О битовых операциях
Получите авторизацию
О битовых операциях
Этот текст призван познакомить вас с принципами работы битовых операций. Сначала они могут показаться сложными и бесполезными, но на самом деле это далеко не так. В данной статье я постараюсь вас убедить в этом.
Введение
Операции с битами (побитовые операторы) выполняются непосредственно на битах числа, поэтому все примеры таких операций даются в двоичной системе счисления.
В настоящей статье рассмотрим следующие побитовые операторы:
Битовые операции хорошо изучаются в рамках дискретной математики и укрепляют основы цифровой техники, потому что с точки зрения логики работы цифровой схемы они лежат в основе цифровых вентилей — фундаментальных компонентов. В дискретной математике и цифровой технике для описания работы битовых операций используют таблицы истинности. Наличие таких таблиц значительно упрощает понимание сути побитовых операций. Но почти никогда с табличным представлением не встретишься при описании побитовых операторов в high-level-языках.
Помимо побитовых операторов рекомендуется также ознакомиться со следующей информацией:
Побитовое ИЛИ (OR)

При применении операции побитового ИЛИ к числам 38 и 53 получаем:
| A | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| B | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
| A B | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
Побитовое И (AND)
Побитовые операции И – это действия, которые можно сравнить с операцией побитового ИЛИ, только результат является обратным. Побитовые операции И возвращают двоичное значение 1 только в том случае, если оба соответствующих бита операндов равны 1. Другими словами, двоичные разряды в полученном числе являются результатом умножения соответствующих битов операндов: 1х1 = 1, 1х0 = 0. Побитовая операция И имеет следующую таблицу истинности:
Пример работы побитовой операции И на примере выражения 38 & 53:
| A | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| B | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
| A и B | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
Исключающее ИЛИ (XOR)
Главное отличие между оператором исключающее ИЛИ и оператором побитовое ИЛИ в том, что для получения единицы только один бит из пары должен быть равен единице:
Например, результат выражения 138^43 равен…
| A | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| B | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 1 |
| A ^ B | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
С помощью оператора ^ возможно менять значения двух переменных (имеющих одинаковый тип данных) без использования временной переменной.
Также оператором исключающего ИЛИ можно шифровать текст. Для этого необходимо пройтись по всем символам и выполнить ^ с символом-ключом. В случае более сложного шифра можно использовать строку символов:
Помимо оператора исключающее ИЛИ не является самым надежным методом шифрования, однако его можно использовать в рамках шифровального алгоритма.
Побитовое отрицание (NOT)
Оператор побитового отрицания инвертирует все биты числа. Другими словами, все единицы становятся нулями, и наоборот.
Давайте рассмотрим пример:
Получаем значение 20310
Когда мы применяем оператор побитового отрицания к числу, знак результата всегда противоположен знаку первоначального числа (при работе со знаковыми числами). Почему так происходит, составим объяснение в следующих строках.
Дополнительный код
Расскажем о способе представления отрицательных целых чисел в ЭВМ с использованием дополнительного кода (two’s complement).Главное, что нужно знать о числах, записанных в дополнительном коде, — это то, что старший разряд является знаковым. Если он равен 0, то число положительное и совпадает с представлением этого числа в прямом коде, а если 1 — то оно отрицательное, т.е. 10111101 — отрицательное число, а 01000011 — положительное.
Чтобы преобразовать отрицательное число в дополнительный код, нужно инвертировать все биты числа (то есть, использовать побитовое отрицание) и добавить к результату 1.
Например, если мы имеем 109:
| A | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 1 |
| A+1 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 1 |
Побитовый сдвиг влево
Побитовые операции сдвига отличаются от рассмотренных ранее битовых операций. При побитовом сдвиге влево биты операнда сдвигаются на N битов влево, начиная с младшего бита. После сдвига пустые места заполняются нулями. Это происходит следующим образом:
Побитовый сдвиг вправо
Операция >> сдвигает биты операнда вправо на указанное количество битов.
Если операнд является положительным числом, то пустые места заполняются нулями. Если мы работаем с отрицательным числом, то пустые места слева заполняются единицами. Таким образом, сохраняется знак операнда согласно дополнительному коду, который был ранее объяснен.
Вывод
Таким образом, теперь у вас больше информации о битовых операциях, и вы не боитесь использовать их. Предположу, что вам не потребуется использовать >>1 каждый раз при делении на 2. Все же, битовые операции полезно иметь в своей коллекции инструментов, и теперь вы сможете воспользоваться ими при необходимости или ответить на сложный вопрос при собеседовании.







