Побитовые операторы в C++: И, ИЛИ, НЕ и исключающее ИЛИ | Уроки С++
Обновл. 19 Июл 2020 |
Побитовые операторы манипулируют отдельными битами в пределах переменной.
Примечание: Для некоторых этот материал может показаться сложным. Если вы застряли или что-то не понятно — пропустите этот урок (и следующий), в будущем сможете вернуться и разобраться детально. Он не столь важен для прогресса в изучении языка C++, как другие уроки, и изложен здесь в большей мере для общего развития.
Зачем нужны побитовые операторы?
В далеком прошлом компьютерной памяти было очень мало и ею сильно дорожили. Это было стимулом максимально разумно использовать каждый доступный бит. Например, в логическом типе данных bool есть всего лишь два возможных значения (true и false), которые могут быть представлены одним битом, но по факту занимают целый байт памяти! А это, в свою очередь, из-за того, что переменные используют уникальные адреса памяти, а они выделяются только в байтах. Переменная bool занимает 1 бит, а другие 7 бит — тратятся впустую.
Используя побитовые операторы, можно создавать функции, которые позволят уместить 8 значений типа bool в переменную размером 1 байт, что значительно сэкономит потребление памяти. В прошлом такой трюк был очень популярен. Но сегодня, по крайней мере, в прикладном программировании, это не так.
Теперь памяти стало существенно больше и программисты обнаружили, что лучше писать код так, чтобы было проще и понятнее его поддерживать, нежели усложнять его ради незначительной экономии памяти. Поэтому спрос на использование побитовых операторов несколько уменьшился, за исключением случаев, когда необходима уж максимальная оптимизация (например, научные программы, которые используют огромное количество данных; игры, где манипуляции с битами могут быть использованы для дополнительной скорости; встроенные программы, где память по-прежнему ограничена).
В языке С++ есть 6 побитовых операторов:
Оператор | Символ | Пример | Операция |
Побитовый сдвиг влево | << | x << y | Все биты в x смещаются влево на y бит |
Побитовый сдвиг вправо | >> | x >> y | Все биты в x смещаются вправо на y бит |
Побитовое НЕ | ~ | ~x | |
Побитовое И | & | x & y | Каждый бит в x И каждый соответствующий ему бит в y |
Побитовое ИЛИ | | | x | y | Каждый бит в x ИЛИ каждый соответствующий ему бит в y |
Побитовое исключающее ИЛИ (XOR) | ^ | x ^ y | Каждый бит в x XOR с каждым соответствующим ему битом в y |
В побитовых операциях следует использовать только целочисленные типы данных unsigned, так как C++ не всегда гарантирует корректную работу побитовых операторов с целочисленными типами signed.
Правило: При работе с побитовыми операторами используйте целочисленные типы данных unsigned.
Побитовый сдвиг влево (<<) и побитовый сдвиг вправо (>>)
В языке C++ количество используемых бит основывается на размере типа данных (в 1 байте находятся 8 бит). Оператор побитового сдвига влево (<<
) сдвигает биты влево. Левый операнд является выражением, в котором они сдвигаются, а правый — количество мест, на которые нужно сдвинуть. Поэтому в выражении 3 << 1
мы имеем в виду «сдвинуть биты влево в литерале 3 на одно место».
Примечание: В следующих примерах мы будем работать с 4-битными двоичными значениями.
Рассмотрим число 3, которое в двоичной системе равно 0011:
3 = 0011
3 << 1 = 0110 = 6
3 << 2 = 1100 = 12
3 << 3 = 1000 = 8
В последнем третьем случае, один бит перемещается за пределы самого литерала! Биты, сдвинутые за пределы двоичного числа, теряются навсегда.
Оператор побитового сдвига вправо (>>
) сдвигает биты вправо. Например:
12 = 1100
12 >> 1 = 0110 = 6
12 >> 2 = 0011 = 3
12 >> 3 = 0001 = 1
В третьем случае мы снова переместили бит за пределы литерала. Он также потерялся навсегда.
Хотя в примерах, приведенных выше, мы смещаем биты только в литералах, мы также можем смещать биты и в переменных:
unsigned int x = 4; x = x << 1; // x должен стать равным 8
unsigned int x = 4; x = x << 1; // x должен стать равным 8 |
Следует помнить, что результаты операций с побитовыми сдвигами в разных компиляторах могут отличаться.
Что!? Разве операторы << и >> используются не для вывода и ввода данных?
И для этого тоже.
Сейчас польза от использования побитовых операторов не так велика, как это было раньше. Сейчас в большинстве случаев оператор побитового сдвига влево используется для вывода данных. Например, рассмотрим следующую программу:
#include <iostream> int main() { unsigned int x = 4; x = x << 1; // оператор << используется для побитового сдвига влево std::cout << x; // оператор << используется для вывода данных в консоль return 0; }
#include <iostream> int main() { unsigned int x = 4; x = x << 1; // оператор << используется для побитового сдвига влево std::cout << x; // оператор << используется для вывода данных в консоль return 0; } |
Результат выполнения программы:
8
А как компилятор понимает, когда нужно применить оператор побитового сдвига влево, а когда выводить данные? Всё очень просто. std::cout переопределяет значение оператора <<
по умолчанию на новое (вывод данных в консоль). Когда компилятор видит, что левым операндом оператора <<
является std::cout, то он понимает, что должен произойти вывод данных. Если левым операндом является переменная целочисленного типа данных, то компилятор понимает, что должен произойти побитовый сдвиг влево (операция по умолчанию).
Побитовый оператор НЕ
Побитовый оператор НЕ (
), пожалуй, самый простой для объяснения и понимания. Он просто меняет каждый бит на противоположный, например, с 0 на 1 или с 1 на 0. Обратите внимание, результаты побитового НЕ зависят от размера типа данных!
Предположим, что размер типа данных составляет 4 бита:
4 = 0100
~ 4 = 1011 (двоичное) = 11 (десятичное)
Предположим, что размер типа данных составляет 8 бит:
4 = 0000 0100
~ 4 = 1111 1011 (двоичное) = 251 (десятичное)
Побитовые операторы И, ИЛИ и исключающее ИЛИ (XOR)
Побитовые операторы И (&
) и ИЛИ (|
) работают аналогично логическим операторам И и ИЛИ. Однако, побитовые операторы применяются к каждому биту отдельно! Например, рассмотрим выражение
. В двоичной системе это 0101 | 0110
. В любой побитовой операции операнды лучше всего размещать следующим образом:
0 1 0 1 // 5
0 1 1 0 // 6
А затем применять операцию к каждому столбцу с битами по отдельности. Как вы помните, логическое ИЛИ возвращает true (1), если один из двух или оба операнды истинны (1). Аналогичным образом работает и побитовое ИЛИ. Выражение 5 | 6
обрабатывается следующим образом:
0 1 0 1 // 5
0 1 1 0 // 6
-------
0 1 1 1 // 7
Результат:
0111 (двоичное) = 7 (десятичное)
Также можно обрабатывать и комплексные выражения ИЛИ, например,
. Если хоть один бит в столбце равен 1, то результат целого столбца — 1. Например:
0 0 0 1 // 1
0 1 0 0 // 4
0 1 1 0 // 6
--------
0 1 1 1 // 7
Результатом 1 | 4 | 6
является десятичное 7
.
Побитовое И работает аналогично логическому И — возвращается true, только если оба бита в столбце равны 1. Рассмотрим выражение 5 & 6
:
0 1 0 1 // 5
0 1 1 0 // 6
--------
0 1 0 0 // 4
Также можно решать и комплексные выражения И, например, 1 & 3 & 7
. Только при условии, что все биты в столбце равны 1, результатом столбца будет 1.
0 0 0 1 // 1
0 0 1 1 // 3
0 1 1 1 // 7
--------
0 0 0 1 // 1
Последний оператор — побитовое исключающее ИЛИ (^
) (сокр. «XOR» от англ. «eXclusive OR«). При обработке двух операндов, исключающее ИЛИ возвращает true (1), только если один и только один из операндов является истинным (1). Если таких нет или все операнды равны 1, то результатом будет false (0). Рассмотрим выражение 6 ^ 3
:
0 1 1 0 // 6
0 0 1 1 // 3
-------
0 1 0 1 // 5
Также можно решать и комплексные выражения XOR, например, 1 ^ 3 ^ 7
. Если единиц в столбце чётное количество, то результатом будет 0, если же нечётное количество, то результат — 1. Например:
0 0 0 1 // 1
0 0 1 1 // 3
0 1 1 1 // 7
--------
0 1 0 1 // 5
Побитовые операторы присваивания
Как и в случае с арифметическими операторами присваивания, язык C++ предоставляет побитовые операторы присваивания для облегчения внесения изменений в переменные.
Оператор | Символ | Пример | Операция |
Присваивание с побитовым сдвигом влево | <<= | x <<= y | Сдвигаем биты в x влево на y бит |
Присваивание с побитовым сдвигом вправо | >>= | x >>= y | Сдвигаем биты в x вправо на y бит |
Присваивание с побитовой операцией ИЛИ | |= | x |= y | Присваивание результата выражения x | y переменной x |
Присваивание с побитовой операцией И | &= | x &= y | Присваивание результата выражения x & y переменной x |
Присваивание с побитовой операцией исключающего ИЛИ | ^= | x ^= y | Присваивание результата выражения x ^ y переменной x |
Например, вместо х = х << 1;
мы можем написать х <<= 1;
.
Заключение
При работе с побитовыми операторами (используя метод столбца) не забывайте о том, что:
При вычислении побитового ИЛИ, если хоть один из битов в столбце равен 1, то результат целого столбца равен 1.
При вычислении побитового И, если все биты в столбце равны 1, то результат целого столбца равен 1.
При вычислении побитового исключающего ИЛИ (XOR), если единиц в столбце нечётное количество, то результат равен 1.
Тест
Задание №1
Какой результат 0110 >> 2
в двоичной системе?
Задание №2
Какой результат 5 | 12
в десятичной системе?
Задание №3
Какой результат 5 & 12
в десятичной системе?
Задание №4
Какой результат 5 ^ 12
в десятичной системе?
Ответы
Ответ №1
Результатом 0110 >> 2
является двоичное число 0001
.
Ответ №2
Выражение 5 | 12
:
0 1 0 1
1 1 0 0
--------
1 1 0 1 // 13 (десятичное)
Ответ №3
Выражение 5 & 12
:
0 1 0 1
1 1 0 0
--------
0 1 0 0 // 4 (десятичное)
Ответ №4
Выражение 5 ^ 12
:
0 1 0 1
1 1 0 0
--------
1 0 0 1 // 9 (десятичное)
Оценить статью:
Загрузка…Поделиться в социальных сетях:
C++ | Побитовые операции
Побитовые операции
Последнее обновление: 12.09.2017
Побитовые операции выполняются над отдельными разрядами или битами чисел. Данные операции производятся только над целыми числами.
Операции сдвига
Каждое целое число в памяти представлено в виде определенного количества разрядов. И операции сдвига позволяют сдвинуть битовое представление числа на несколько разрядов вправо или влево. Операции сдвига применяются только к целочисленным операндам. Есть две операции:
-
<<
Сдвигает битовое представление числа, представленного первым операндом, влево на определенное количество разрядов, которое задается вторым операндом.
-
>>
Сдвигает битовое представление числа вправо на определенное количество разрядов.
Применение операций:
int a = 2 << 2; // 10 на два разрядов влево = 1000 - 8 int b = 16 >> 3; // 10000 на три разряда вправо = 10 - 2
Число 2 в двоичном представлении 10. Если сдвинуть число 10 на два разряда влево, то получится 1000, что в десятичной системе равно число 8.
Число 16 в двоичном представлении 10000. Если сдвинуть число 10 на три разряда вправо (три последних разряда отбрасываются), то получится 10, что в десятичной системе представляет число 2.
Поразрядные операции
Поразрядные операции также проводятся только над соответствующими разрядами целочисленных операндов:
&: поразрядная конъюнкция (операция И или поразрядное умножение). Возвращает 1, если оба из соответствующих разрядов обоих чисел равны 1
|: поразрядная дизъюнкция (операция ИЛИ или поразрядное сложение). Возвращает 1, если хотя бы один из соответствующих разрядов обоих чисел равен 1
^: поразрядное исключающее ИЛИ. Возвращает 1, если только один из соответствующих разрядов обоих чисел равен 1
~: поразрядное отрицание или инверсия. Инвертирует все разряды операнда. Если разряд равен 1, то он становится равен 0, а если он равен 0, то он получает значение 1.
Применение операций:
int a = 5 | 2; // 101 | 010 = 111 - 7 int b = 6 & 2; // 110 & 010 = 10 - 2 int c = 5 ^ 2; // 101 ^ 010 = 111 - 7 int d = ~9; // -10
Например, выражение 5 | 2
равно 7. Число 5 в двоичной записи равно 101, а число 2 — 10 или 010. Сложим соответствующие разряды обоих чисел. При сложении если хотя бы
один разряд равен 1, то сумма обоих разрядов равна 1. Поэтому получаем:
В итоге получаем число 111, что в десятичной записи представляет число 7.
Возьмем другое выражение 6 & 2
. Число 6 в двоичной записи равно 110, а число 2 — 10 или 010. Умножим соответствующие разряды
обоих чисел. Произведение обоих разрядов равно 1, если оба этих разряда равны 1. Иначе произведение равно 0. Поэтому получаем:
Получаем число 010, что в десятичной системе равно 2.
Начинаем изучать STM32: битовые операции / Хабр
Небольшое отступление…
В прошлом уроке мы рассмотрели с чего начать, если вы решили изучать микроконтроллеры STM32: как настроить IDE, как создать простой проект, как откомпилировать программу и как запустить программу на выполнение. После полюбовались на перемигивание светодиодов на Discovery-плате )
Начиная новую статью, я задумывал сразу же перейти к подробному разбору листинга программы, который заставлял попеременно перемигиваться наши светодиоды, но уже перейдя к написанию, я вдруг осознал для себя, что есть большое количество вопросов без ответа на которые — перейти к рассмотрению программы было бы преждевременно. И для себя я определил целый перечень таких вопросов:
- Что же такое битовые операции? Как ими пользоваться?
- Что такое регистры и как они связаны с битовыми операциями?
- Из чего состоят микроконтроллеры STM32F0xx-серии, как осуществляется тактирование и чем обеспечена жизнь внутри МК?
- Как происходит стартовая инициализация МК, зачем нужен startup-файл, что делает функция SystemInit? Объяснение на пальцах.
- Из чего состоит библиотека CMSIS? Как в ней ориентироваться? Что полезного можно из нее извлечь и как ей пользоваться?
Именно с рассмотрения этих вопросов я хотел бы продолжить повествование о программировании STM32.
Список статей:
- Начинаем изучать STM32 или Управляем светом по-умному
- Начинаем изучать STM32: битовые операции
- Начинаем изучать STM32: Что такое регистры? Как с ними работать?
Основные логические операции
Только начиная изучать микроконтроллеры, слова «регистр» и «битовые операции» для меня казались чем-то таинственно загадочным и я долго не хотел переходить к рассмотрению данной темы. Но когда я более-менее разобрался с тем, что это такое я понял что зря откладывал изучение такой важной темы в дальний ящик. Битовые операции наверное самые распространенные операции в микроконтроллере и знание того, как и зачем их можно применять в нашей работе — откроет перед нами огромный потенциал возможностей для управления всем и вся в нашем МК!
Все мы на уроках информатики в школе знакомились с тем, что такое цифровая техника, почему она так называется, какие существуют основные логические операции.Все современные цифровые технологии основаны на двоичной математике и логических схемах.
Микроконтроллер всегда оперирует только двумя состояниями: «ноль» — нет напряжения, «единица» — есть напряжение. Давайте немного освежим в голове знания о основных логических операциях т.к. они составляют основу всей цифровой техники.
- Конъюнкция — обозначается как «Логическое И» или «Логическое умножение». В сущности, результат от выполнения данной логической операции двух для выражений А и В подобен их умножению. То есть, выражение примет значение «1» в случае только если и А, и В имеют значение «1». Во всех других случаях будет значение «0». Может обозначаться как И, &&, AND, &
- Дизъюнкция — обозначается как «Логическое ИЛИ» или «Логическое сложение». Результат от выполнения данной логической операции двух для выражений А и В подобен их сложению. То есть, выражение примет значение «1» в случае если хотя бы одно из выражений А и В имеют значение «1». Может обозначаться как ИЛИ, ||, OR, |.
- Инверсия — обозначается как «Логическое НЕ» или «Отрицание». Результат от выполнения данной логической операции двух для выражения А равен противоположному. То есть, выражение примет значение 1 в случае если выражение А равно 0 и наоборот. Может обозначаться как НЕ, !, NOT, ~.
- Строгая дизъюнкция — обозначается как «Исключающее ИЛИ» или «Логическое сложение, исключающее ИЛИ». Результат от выполнения данной логической операции двух для выражений А и В примет значение 1 в случае если А и В имеют разные значения. Может обозначаться как Искл. ИЛИ, XOR, ^.
Битовые операции
Битовые операции — практически то же самое, что и логические операции только лишь с той разницей, что применяются они к битам и к числам двоичной системы.
К слову говоря, для простоты изучения битовых операций я использовал программу 32-bit ASM Calculator от ManHunter. С помощью данной программы можно проверять результаты выполнения битовых операций, переводить числа из одной системы счисления в другую. Программа имеет интуитивно понятный интерфейс и после знакомства программа стала одним из основных инструментов в моей работе с микроконтроллерами. Небольшое пояснение к интерфейсу программы данно на изображении ниже:
Битовая операция «НЕ» — «~»
Если бит равен «1», то после выполнения операции «НЕ» он будет равен «0», и наоборот. Операция сразу же выполняется над всеми битами двоичного числа. Например, инвертируем число FF:
Битовая операция «И» — «&»
Если оба бита в разряде равны «1», то после выполнения операции «И» результат в разряде будет равен «1», но если хотя бы один из битов равен «0» тогда и результат будет равен «0». Операция так же выполняется поразрядно. Например, «умножим» два числа 0xFF0 и 0xF0F:
В результате мы увидим, что в тех разрядах где были единицы в обоих числах, в результате получились единицы, во всех остальных случаях — нули.
Рассмотрим варианты практического применения:
- В ситуации, если нам необходимо сбросить конкретный бит или группу битов в ноль мы можем использовать маску. Думаю, будет нагляднее показать это на примере. Допустим, мы берем число и 0xF8F и нам надо чтобы 7-й бит стал вместо единицы нулем. Нет проблем, накидываем маску и снимаем галочку с нужного бита. Умножаем числа и получаем результат:
- Если нам необходимо проверить конкретный бит в числе на 0 или 1 — мы так же используя маску проводим умножение. В маске мы устанавливаем бит, который хотели бы проверить. Если требуемый бит равен «0» — то результатом вычисления будет «0», если «1» то, соответственно, «1». Если мы хотим узнать, равен ли 7-й бит единицы — делаем соответствующую маску и умножаем наше число на маску. Все просто:
Если нам нужно проверить четность числа(имеется ввиду способность числа делиться на два) — то мы таким же образом проверяем 1-й бит, если он равен «1» — то число нечетное, если там «0» то число четное. Попробуйте сами, в целях обучения и формирования навыков, сделать данную проверку.
Битовая операция «ИЛИ» — «|»
Если один или оба из пары битов равен «1» то результат будет «1», иначе если оба бита равны «0» то результат будет равен «0». То есть, грубо говоря, производится сложение всех единиц в разрядах. Например если мы складываем два числа 0xF8F и 0x7F, то получим следующий результат:
Рассмотрим вариант практического применения:
- Если нам необходимо установит конкретный бит в числе на 1 — мы так же используя маску проводим сложение. Например, чтобы выставить 15-й бит в числе 0xFF0 нужно провести операцию логического сложения и мы получим требуемый результат:
Попробуйте самостоятельно поиграться с различными числами и понаблюдать за результатами.
Битовая операция «ИСКЛЮЧАЮЩЕЕ ИЛИ» — «^»
Если биты в разряде отличаются и не равны тогда результат будет «1», иначе «0». Например, если мы делаем XOR числа 0xF8F и 0x7F, то мы увидим что в разрядах в которых находятся отличные биты то там в результате получается «1» и в местах где биты одинаковые, будь то «0» или «1» — получился «0», в итоге мы получим следующий результат:
Рассмотрим варианты практического применения:
- Если нам понадобилось инвертировать какие-либо биты в числе, можно используя маску с легкостью сделать это используя операцию XOR. Давайте сделаем инверсию 6 и 7 разряда в числе 0xF8 используя маску 0xC0. Результат вы можете посмотреть на изображении:
- Бывают ситуации когда необходимо сравнить два регистра и определить равны они или нет. В этом случае нам необходимо значения регистров подвергнуть операции XOR. Если результат получился «0» — тогда регистры равны, иначе — не равны:
Битовые операции сдвига
Существует ряд интересных и порой чрезвычайно полезных битовых операций именуемых как операции сдвига. Двигать разряды можно как вправо, так и влево. В ходе данной операции происходит сдвиг всех разрядов двоичного числа на указанное количество позиций, при этом, в случае если сдвиг идёт влево — старший бит (самый левый) теряется, а в младший (самый правый) записывается «0». При логическом сдвиге вправо происходит обратная ситуация — младший бит (самый правый) теряется, а в старший записывается «0». Дополнительно хотелось бы отметить, что в случае 32-разрядных слов сдвигаются все 32 разряда целиком. Рассмотрим операции сдвига подробнее.
Cдвиг влево — «<<«
То, как происходит сдвиг вы можете увидеть на изображении ниже. Думаю, что всё достаточно очевидно:
При двоичном сдвиге можно заметить одну интересную особенность. Сдвиг на один разряд умножает наше число на 2. Если сдвинуть на n разрядов наше число x то получится x * (2 * n). Попробуйте самостоятельно отследить эту закономерность через нашу утилку для подсчета. =)
Cдвиг вправо — «>>»
То, что получается в результате сдвига вправо достаточно наглядно отражено на изображении:
При двоичном сдвиге вправо можно заметить что происходит ситуация обратная сдвигу влево — число делится на 2 с при сдвиге в 1 разряд и после на 2 * n, где n — количество разрядов на которые произведен сдвиг. Так же попробуйте самостоятельно поиграться с числами и которые заведомо делятся на 2 нацело. И вопрос на засыпку — какой результат будет если вы поделите таким образом нечетное число?
Важное замечание. Если вы будете делать сдвиг для переменной с отрицательным знаком (signed) — освободившиеся позиции будут заполняться единичками.
В качестве заключения…
Многим начинающим данная тема может показаться дико скучной и может сложиться ощущение что ну вообще не понятно где и как можно применить эти знания. Спешу вас обнадежить, в ситуациях когда нужно поднять ту или иную ногу МК или записать параметр в какой-нить периферийный блок или модуль — там кругом и всюду будут требоваться знания битовых операций. Так как статья получилась достаточно объемной, рассмотрение регистров мы перенесем на следующий урок. Ну и в последующем можно использовать эту статью как шпаргалку.
В качестве домашнего задания попробуйте самостоятельно разобрать код нашей программы в блоке while(1) {… } и понять как же битовыми операциями мы включаем и выключаем наши светодиоды. Ну а на следующем уроке я расскажу как оно происходит на самом деле!
Список статей:
- Начинаем изучать STM32 или Управляем светом по-умному
- Начинаем изучать STM32: битовые операции
- Начинаем изучать STM32: Что такое регистры? Как с ними работать?
Битовые операции. Урок 6 курса «Основы языка C»
Данный урок курса можно считать факультативным, т. е. необязательным. Для освоения темы этого урока вам потребуется знание о двоичной системе счисления, навыки перевода чисел из одной системы счисления в другую, а также вы должны иметь представление о том, что такое битовые (они же поразрядные) операции. С последним можно познакомиться по вот этой лекции.
В языке программирования C существуют следующие поразрядные операции: & (И), | (ИЛИ), ^ (исключающее ИЛИ), << (сдвиг влево), >> (сдвиг вправо), ~ (поразрядное дополнение до единицы). Рассмотрим на примерах, как они работают, но перед этим уделим внимание выводу в языке C чисел в отличных от десятичной системах счисления.
В С можно присваивать целочисленные значения в десятичной, восьмеричной и шестнадцатеричной системах счисления. Для того, чтобы присвоить переменной число в восьмеричной системе счисления, перед ним надо написать 0 (ноль), в шестнадцатеричной — 0x (ноль и икс), например:
int a, b; a = 077; // записано восьмеричное число b = 0x1F; // присвоено шестнадцатеричное число
Любые целые числа можно выводить на экран в десятичном, восьмеричном и шестнадцатеричном представлении. Пример кода для вывода определенных ранее двух переменных в различных системаъ счисления:
printf("%d %o %x %X\n", a,a,a,a); printf("%d %o %x %X\n", b,b,b,b);
В результате на экране вы увидите:
63 77 3f 3F 31 37 1f 1F
Восьмеричные и шестнадцатеричные числа используются из-за удобства при работе с двоичной системой счисления. Каждая цифра восьмеричного числа может быть заменена тремя цифрами двоичного. И каждая цифра шестнадцатеричного числа легко заменяет четыре разряда двоичного числа. Вот таблица соответствия цифр восьмеричной системы счисления числам двоичной системы:
0 | 000 |
1 | 001 |
2 | 010 |
3 | 011 |
4 | 100 |
5 | 101 |
6 | 110 |
7 | 111 |
Теперь допустим, что у нас есть восьмеричное число 037. По таблице легко понять, что в двоичном выражении оно будет выглядеть как 011 111.
Задание
- Как будут выглядеть восьмеричные числа 04271 и 03566 в двоичном представлении.
- Составьте на бумаге таблицу соответствия шестнадцатеричный цифр двоичным числам. Переведите числа 7D, FFFF, 2C9 в двоичную систему счисления.
Итак, если бы мы при работе с поразрядными операциями использовали десятичные числа, то чтобы оценить результат нам бы каждый раз приходилось переводить десятичное число в двоичную систему счисления, что относительно трудоемко. Если же человек видит, например, восьмеричное число, то он может представить как оно выглядит в двоичном представлении, помня или держа перед глазами таблицу соответствия чисел. Например, как только мы видим 017, то можем представить в уме, как последние четыре бита ячейки памяти забиты единицами.
Теперь вернемся к поразрядным операциям и протестируем каждую из них. Для этого напишем небольшую программу:
int a, b; a = 017; b = 036; printf("0%o & 0%o = 0%o\n", a, b, a & b); printf("0%o | 0%o = 0%o\n", a, b, a | b); printf("0%o ^ 0%o = 0%o\n", a, b, a ^ b); printf("0%o << 2 = 0%o\n", a, a << 2); printf("0%o >> 2 = 0%o\n", a, a >> 2); printf("~0%o = 0%o\n", a, ~a);
Результат ее работы будет выглядеть так:
017 & 036 = 016 017 | 036 = 037 017 ^ 036 = 021 017 << 2 = 074 017 >> 2 = 03 ~017 = 037777777760
Этот результат будет проще понять с помощью рисунка:
В последнем случае получилось такое большое число потому, что под форматы вывода целых чисел (%d, %o, %X
) выделяется по 4 байта.
Задание
- Используя шестнадцатеричные числа, напишите аналогичную приведенной выше программу. Объясните результат.
- Попробуйте составлять сложные битовые операции (в несколько действий) и оценивать их результат.
Теперь рассмотрим пример использования битовых операций. Допустим, у нас есть массив, требуется снять с него «маску», которая бы отражала, в какой позиции стоят отрицательные, а в какой положительные элементы. Пусть единица в бите обозначает соответствующий ей положительный элемент массива, а ноль — отрицательный. Другими словами, если у нас есть массив {4, -3, 2, 2, 8, -1}, то его «битовая маска» будет выглядеть как 101110, или в восьмеричном представлении как 056. Составим алгоритм решения этой задачи:
- Будем считать, что массив состоит не более чем из 32 элементов. Поэтому для хранения его «маски» достаточно переменной типа
int
. Назовем ее mask и присвоим значение 0. - Перебрать элементы массива в цикле
for
. Если встречается положительный элемент, то установить соответствующий ему бит значения mask в 1. - Вывести значение переменной mask на экран в виде восьмеричного числа.
Вроде бы все просто, но как установить в единицу определенный бит числа? Существует закономерность соответствия степеней двойки и двоичного представления числа:
20 = 0000 0001
21 = 0000 0010
22 = 0000 0100
23 = 0000 1000
24 = 0001 0000
и т.д. Если для вас эта последовательность не очевидна, то пересчитайте. Теперь если применить к mask побитовую операцию | (ИЛИ), а в качестве второго операнда использовать определенную степень двойки, то один бит будет установлен в 1. Например:
(0) 0000 0000 | (25) 0010 0000 = 0010 0000
(32) 0010 0000 | (27) 1000 0000 = 1010 0000
При переборе первый элемент массива имеет индекс 0, но соответствующий ему бит в maskдолжен стоять впереди остальных. Если известно общее количество элементов массива (N), то можно определить степень двойки по формуле N - i - 1
. Действительно, имея третий положительный элемент массива из 10 элементов, следует установить в единицу восьмой с конца бит, а это значит надо использовать вторым операндом битового ИЛИ 27, а 7 как раз будет 10(N) — 2(i) — 1.
Другая проблема — как в языке C возвести число в степень. Понятно, что можно написать свой код, но скорее всего в стандартной библиотеке уже есть подобная функция. С помощью заголовочного файла math.h можно подключить библиотеку с математическими функциями. Среди них есть функция pow()
, которая принимает два числа и возвращает результат возведения первого числа в степень, выраженную вторым числом. Однако результат возвращается в виде вещественного числа, а нам требуется целое. Как быть? В языке программирования С есть операции приведения типов, которые меняют тип значения с одного на другой. Например, чтобы преобразовать значение вещественной переменной a в целое, следует написать (int) a
.
Вот как может выглядеть вышеописанная программа:
#include <stdio.h> #include <math.h> #define N 12 main () { int nums[N] = {7, 3, 9, -5, -3, 2, 1, 0, 16, -4, 2, 0}; int mask = 0, i; for (i=0; i < N; i++) if (nums[i] >= 0) mask = mask | (int)pow(2,N-i-1); printf("%o\n", mask); }
Задание
Напишите предыдущую программу. Оцените как она работает1. Подумайте над тем, как вывести на экран двоичное представление восьмеричного числа. Попробуйте реализовать это.
1 Если у вас не получается скомпилировать программу, добавьте в конце вызова gcc опцию -lm
(например, gcc -o bits bits.c -lm
).
Побитовые (поразрядные) операции
Побитовые операции используются в тех случаях, когда необходимо получить доступ к отдельным битам памяти, где хранятся данные, например, при выводе графических изображений на экран.
Побитовые операции могут выполнять действия только над целочисленными значениями. Их нельзя применять к данным типа float и double. В отличие от логических операций с их помощью сравниваются не два числа целиком, а отдельные их биты, причём каждый бит рассматривается независимо от других битов. К таким операциям в языке С++ относятся следующие операции:
& – поразрядное И;
| – поразрядное ИЛИ;
~ – поразрядное НЕ (инверсия);
^ – поразрядное «исключающее ИЛИ»;
<< – сдвиг влево;
>> – сдвиг вправо.
Таблица свойств побитовых операций
x
y
~y
x & y
x | y
x ^ y
0
0
1
0
0
0
0
1
0
0
1
1
1
0
0
1
1
1
1
1
1
0
Рассмотрим примеры выполнения побитовых операций:
10101 10101 10101
& | ^
11001 11001 11001
——— ——— ———
10001 11101 01100
Операции инкремента и декремента
В языке С++ предусмотрены операция (++) и операция (—) соответственно для увеличения и уменьшения на единицу значения операнда (унарные операции инкремента и декремента). Их можно указывать перед операндом (префиксная форма записи) и после операнда (постфиксная форма записи). В первом случае (++x, —x) значение операнда x изменяется перед его использованием в выражении, а во втором (x++, x—) – после его использования. Например, запись sum = x + y++ означает «сложить x и y и увеличить y на единицу» а запись
sum = x + ++y – «увеличить y на единицу, сложить x и y и присвоить результат sum«. Так, если x = 1, а y = 2, то в первом случае sum = 3, а во втором – sum = 4.
Условная операция (?)
Язык C++ имеет очень мощную условную операцию (операцию условия), которая обозначается символом вопроса (?). Условная операция является трёхоперандной (тернарной) и имеет следующий вид:
операнд_1 ? операнд_2 : операнд_3
где операнд_1, операнд_2 и операнд_3 – это выражения.
Результатом выполнения операции (?) является значение операнда_2 или операнда_3 в зависимости от истинности операнда_1. Если значением операнда_1 является true, результатом операции является значение операнда_2, если false – операнда_3.Например, результатом выполнения условной операции
(a >0) ? x : 0
будет значение x, если a > 0 и нулевое значение – в противном случае.
Операция запятая
В С++ определена специальная операция запятая (,), задающая последовательность вычисления выражений, например:
(i = 1, j = 2, k =3).
Операций запятая в выражении может быть несколько, в этом случае они выполняются слева направо, а значением всего выражения будет значение самого правого операнда, поэтому результат вычисления выражения
a = (x = 5, y = 6, z = 3) равен 3.
Операция sizeof
Операция sizeof позволяет определить, сколько памяти занимает то или иное значение. Например, sizeof(int) определяет число байтов, отводимое под переменные типа int, а sizeof(b) – число байтов, занимаемое переменной b.
Операндом унарной операции sizeof является имя типа или выражение. Операнд заключается в скобки (если операнд – выражение, скобки не обязательны). Результат операции – целое число, равное количеству байтов, необходимых для хранения в памяти заданной величины.
Побитовые операции
Побитовые операторы JavaScript:
Оператор | Название | Описание |
---|---|---|
& | И | Возвращает 1, если оба бита равны 1 |
| | ИЛИ | Возвращает 1, если хотя бы один из двух битов равен 1 |
^ | ИСКЛЮЧАЮЩЕЕ ИЛИ | Возвращает 1, если только один из двух битов равен 1, но не оба |
~ | НЕ | Инвертирует все биты |
<< | Побитовый сдвиг влево | Сдвиг влево с заполнением появившихся младших (справа) битов нулями |
>> | Побитовый сдвиг вправо со знаком | Сдвиг вправо с копированием знака (арифметический сдвиг вправо). Пустые биты заполняются копией крайнего левого бита |
>>> | Побитовый сдвиг вправо | Сдвиг вправо с заполнением появившихся старших (слева) битов нулями |
Примеры:
Операция | Результат | То же, что | Результат |
---|---|---|---|
5 & 1 | 1 | 0101 & 0001 | 1 |
5 | 1 | 5 | 0101 | 0001 | 101 |
5 ^ 1 | 4 | 0101 ^ 0001 | 100 |
~5 | 10 | ~0101 | 1010 |
5 << 1 | 10 | 0101 << 1 | 1010 |
5 >> 1 | 2 | 0101 >> 1 | 10 |
5 >>> 1 | 2 | 0101 >>> 1 | 10 |
Числовые значения в JavaScript хранятся как 64 битные числа с плавающей точкой. Однако все побитовые операции выполняются с 32 битными двоичными числами.
Перед выполнением побитовой операции JavaScript преобразует числовые операнды в 32 битные целые числа со знаком. После завершения побитовой операции результат преобразуется обратно в 64 битное число.
В примерах в таблице выше приведены 4-битные числа без знака. Поэтому ~5 возвращает 10.
В действительности же JavaScript использует 32-битные числа со знаком. Таким образом, в JavaScript выражение ~ 5 вернет не 10, а -6:
00000000000000000000000000000101 (5)
11111111111111111111111111111010 (~5 = -6)
В целых числах со знаком самый левый бит используется для указания знака.
Побитовое И (AND)
Если побитовое И (AND) выполняется с парой бит, то 1 возвращается тогда, когда оба бита равны 1.
Пример с 1-битовыми числами:
Операция | Результат |
---|---|
0 & 0 | 0 |
0 & 1 | 0 |
1 & 0 | 0 |
1 & 1 | 1 |
Пример с 4-битовыми числами:
Операция | Результат |
---|---|
1111 & 0000 | 0 |
1111 & 0001 | 1 |
1111 & 0010 | 10 |
1111 & 0100 | 100 |
Побитовое ИЛИ (OR)
Если побитовое ИЛИ (OR) выполняется с парой бит, то 1 возвращается тогда, когда один из битов равен 1.
Пример с 1-битовыми числами:
Операция | Результат |
---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
Пример с 4-битовыми числами:
Битовые операции
☰
Во многих языках программирования допустимы логические операции над битами целых чисел. В отличие от обычных логических операций, результатом выполнения которых является логический тип данных, битовые логические операции просто изменяют целое число согласно определенным правилам. Точнее битовые операции изменяют отдельные биты двоичного представления числа, в результате чего изменяется его десятичное значение.
Например, в языке программирования Паскаль обычные логические операции и логические операции над битами обозначают с помощью одних и тех же ключевых слов: not, and, or, xor
. Компилятор определяет, что имелось в виду в зависимости от контекста использования этих слов. Обычные логические операции объединяют два и более простых логических выражения. Например, (a > 0) and (c != b)
, (c < a) or(not b)
и т.п. В свою очередь побитовые логические операции выполняются исключительно над целыми числами (или переменными, которые их содержат). Например, a and b, a or 8, not 247
.
Как понять побитовые операции
1. Переведем пару произвольных целых чисел до 256 (один байт) в двоичное представление.
6710 = 0100 00112 11410 = 0111 00102
2. Теперь расположим биты второго числа под соответствующими битами первого и выполним обычные логические операции к цифрам, стоящим в одинаковых разрядах первого и второго числа. Например, если в последнем (младшем) разряде одного числа стоит 1, а другого числа — 0, то логическая операция and
вернет 0, а or
вернет 1. Операцию not
применим только к первому числу.
3. Переведем результат в десятичную систему счисления.
01000010 = 26 + 21 = 64 + 2 = 66 01110011 = 26 + 25 + 24 + 21 + 20 = 64 + 32 + 16 + 2 + 1 = 115 00110001 = 25 + 24 + 20 = 32 + 16 + 1 = 49 10111100 = 27 + 25 + 24 + 23 + 22 = 128 + 32 + 16 + 8 + 4 = 188
4. Итак, в результате побитовых логических операций получилось следующее:
67 and 114 = 66 67 or 114 = 115 67 xor 114 = 49 not 67 = 188
Вот еще один пример выполнения логических операций над битами. Проверьте его правильность самостоятельно.
5 and 6 = 4 5 or 6 = 7 5 xor 6 = 3 not 5 = 250
Зачем нужны побитовые логические операции
Глядя на результат побитовых операций, не сразу можно уловить закономерности в их результате. Поэтому непонятно, зачем нужны такие операции. Однако, они находят свое применение. В байтах не всегда хранятся числа. Байт или ячейка памяти может хранить набор флагов (установлен — сброшен), представляющих собой информацию о состоянии чего-либо. С помощью битовых логических операций можно проверить, какие биты в байте установлены в единицу, можно обнулить биты или, наоборот, установить в единицу. Также существует возможность сменить значения битов на противоположные.
Проверка битов
Проверка битов осуществляется с помощью битовой логической операции and
.
Представим, что имеется байт памяти с неизвестным нам содержимым. Известно, что логическая операция and
возвращает 1, если только оба операнда содержат 1. Если к неизвестному числу применить побитовое логическое умножение (операцию and
) на число 255 (что в двоичном представлении 1111 1111), то в результате мы получим неизвестное число. Обнулятся те единицы двоичного представления числа 255, которые будут умножены на разряды неизвестного числа, содержащие 0. Например, пусть неизвестное число 38 (0010 0110), тогда проверка битов будет выглядеть так:
Другими словами, x and 255 = x
.
Обнуление битов
Чтобы обнулить какой-либо бит числа, нужно его логически умножить на 0.
Обратим внимание на следующее:
1111 1110 = 254 = 255 - 1 = 255 - 20 1111 1101 = 253 = 255 - 2 = 255 - 21 1111 1011 = 251 = 255 - 4 = 255 - 22 1111 0111 = 247 = 255 - 8 = 255 - 23 1110 1111 = 239 = 255 - 16 = 255 - 24 1101 1111 = 223 = 255 - 32 = 255 - 25 1011 1111 = 191 = 255 - 64 = 255 - 26 0111 1111 = 127 = 255 - 128 = 255 - 27
Т.е. чтобы обнулить четвертый с конца бит числа x
, надо его логически умножить на 247 или на (255 — 23).
Установка битов в единицу
Для установки битов в единицу используется побитовая логическая операция
. Если мы логически сложим двоичное представление числа x
с 0000 0000, то получим само число х
. Но вот если мы в каком-нибудь бите второго слагаемого напишем единицу, то в результате в этом бите будет стоять единица.
Отметим также, что:
0000 0001 = 20 = 1 0000 0010 = 21 = 2 0000 0100 = 22 = 4 0000 1000 = 23 = 8 0001 0000 = 24 = 16 0010 0000 = 25 = 32 0100 0000 = 26 = 64 1000 0000 = 27 = 128
Поэтому, например, чтобы установить второй по старшинству бит числа
в единицу, надо его логически сложить с 64 (x or 64
).
Смена значений битов
Для смены значений битов на противоположные используется битовая операция xor
. Чтобы инвертировать определенный бит числа x
, в такой же по разряду бит второго числа записывают единицу. Если же требуется инвертировать все биты числа x
, то используют побитовую операцию исключающего ИЛИ (xor
) с числом 255 (1111 1111).
Операции побитового циклического сдвига
Помимо побитовых логических операций во многих языках программирования предусмотрены битовые операции циклического сдвига влево или вправо. Например, в языке программирования Паскаль эти операции обозначаются
(сдвиг влево) и shr
(сдвиг вправо).
Первым операндом операций сдвига служит целое число, над которым выполняется операция. Во втором операнде указывается, на сколько позиций сдвигаются биты первого числа влево или вправо. Например, 105 shl 3
или 105 shr 4
. Число 105 в двоичном представлении имеет вид 0110 1001.
При сдвиге влево теряются старшие биты исходного числа, на их место становятся младшие. Освободившиеся младшие разряды заполняются нулями.
При сдвиге вправо теряются младшие биты исходного числа, на их место становятся старшие. Освободившиеся старшие разряды заполняются нулями, если исходное число было положительным.
O.2 — Побитовые операторы | Изучите C ++
Поразрядные операторы
C ++ предоставляет 6-битные операторы манипуляции, часто называемые побитовыми операторами:
Оператор | Символ | Форма | Операция |
---|---|---|---|
левый сдвиг | << | x << y | все биты в x сдвинуты влево биты y |
сдвиг вправо | >> | x >> y | все биты в x сдвинуты вправо биты y |
побитовое НЕ | ~ | ~ х | все биты в x перевернуты |
побитовое И | и | x & y | каждый бит в x И каждый бит в y |
побитовое ИЛИ | | | x | y | каждый бит в x ИЛИ каждый бит в y |
побитовое XOR | ^ | х ^ у | каждый бит в x XOR каждый бит в y |
В следующих примерах мы в основном будем работать с 4-битными двоичными значениями.Это сделано для удобства и простоты примеров. В реальных программах количество используемых битов зависит от размера объекта (например, 2-байтовый объект будет хранить 16 бит).
Для удобства чтения мы также опустим префикс 0b вне примеров кода (например, вместо 0b0101 мы будем использовать просто 0101).
Операторы побитового сдвига влево (<<) и побитового сдвига вправо (>>)
Оператор побитового сдвига влево (<<) сдвигает биты влево.Левый операнд - это выражение для сдвига битов, а правый операнд - это целое число бит, на которое нужно сдвинуть влево.
Итак, когда мы говорим x, мы говорим «сдвинуть биты в переменной x влево на 1 место». Новые биты, сдвинутые с правой стороны, получают значение 0.
0011 << 1 - 0110
0011 << 2 - 1100
0011 << 3 - 1000
Обратите внимание, что в третьем случае мы немного сдвинули конец числа! Биты, сдвинутые с конца двоичного числа, теряются навсегда.
Оператор побитового сдвига вправо (>>) сдвигает биты вправо.
1100 >> 1 — 0110
1100 >> 2 — 0011
1100 >> 3 — 0001
Обратите внимание, что в третьем случае мы немного сдвинулись с правого конца числа, поэтому оно потеряно.
Вот пример некоторого сдвига бит:
#include #include int main () { std :: bitset <4> x {0b1100}; std :: cout << x << '\ n'; std :: cout << (x >> 1) << '\ n'; // сдвинуть вправо на 1, получив 0110 std :: cout << (x << 1) << '\ n'; // сдвинуть влево на 1, получив 1000 return 0; } |
Это отпечатки:
1100 0110 1000
Обратите внимание, что результаты применения операторов побитового сдвига к целому числу со знаком зависят от компилятора до C ++ 20.
До C ++ 20 нельзя сдвигать целое число со знаком (и даже в этом случае, вероятно, все же лучше использовать беззнаковое)
Что !? Разве оператор << и оператор >> не используются для ввода и вывода?
Да, конечно.
Сегодняшние программы обычно не очень часто используют операторы побитового сдвига влево и вправо для сдвига битов. Скорее, вы склонны видеть оператор побитового сдвига влево, используемый с std :: cout для вывода текста. Рассмотрим следующую программу:
#include #include int main () { unsigned int x {0b0100}; х = х << 1; // использовать оператор << для сдвига влево std :: cout << std :: bitset <4> {x}; // использовать оператор << для вывода return 0; } |
Эта программа напечатает:
1000
В приведенной выше программе, как оператор << знает, что нужно сдвигать биты в одном случае и выводить x в другом случае? Ответ заключается в том, что std :: cout имеет перегруженных (при условии альтернативного определения) operator <<, который выполняет вывод консоли, а не сдвиг бит.
Когда компилятор видит, что левым операндом оператора << является std :: cout, он знает, что он должен вызвать версию оператора <<, которая перегружена std :: cout для вывода. Если левый операнд является целым типом, то operator << знает, что он должен выполнять обычное поведение сдвига битов.
То же самое для оператора >>.
Обратите внимание, что если вы используете оператор << как для вывода, так и для сдвига влево, требуется заключение в скобки:
#include #include int main () { std :: bitset <4> x {0b0110}; std :: cout << x << 1 << '\ n'; // выводим значение x (0110), затем 1 std :: cout << (x << 1) << '\ n'; // вывод x со сдвигом влево на 1 (1100) return 0; } |
Это отпечатки:
01101 1100
В первой строке печатается значение x (0110), а затем литерал 1.Вторая строка выводит значение x со смещением влево на 1 (1100).
Мы поговорим больше о перегрузке операторов в следующем разделе, включая обсуждение того, как перегрузить операторы для ваших собственных целей.
Побитовое НЕ
Побитовый оператор НЕ (~), пожалуй, самый простой для понимания из всех побитовых операторов. Он просто переворачивает каждый бит с 0 на 1 или наоборот. Обратите внимание, что результат побитовое НЕ зависит от размера вашего типа данных.
Переворот 4 бита:
~ 0100 — 1011
Переворачивание 8 бит:
~ 0000 0100 это 1111 1011
И в 4-битном, и в 8-битном случаях мы начинаем с одного и того же числа (двоичное 0100 совпадает с 0000 0100 точно так же, как десятичное 7 то же самое, что и 07), но в конечном итоге мы получаем другой результат .
Мы можем увидеть это в действии в следующей программе:
#include #include int main () { std :: cout << std :: bitset <4> {~ 0b0100u} << '' << << std :: bitset <8> {~ 0b0100u}; возврат 0; } |
Эти отпечатки:
1011 11111011
Побитовое ИЛИ
Побитовое ИЛИ (|) работает аналогично логическому ИЛИ .Однако вместо применения OR к операндам для получения единственного результата, побитовое OR применяется к каждому биту! Например, рассмотрим выражение 0b0101 | 0b0110
.
Для выполнения (любых) побитовых операций проще всего выровнять два операнда следующим образом:
0 1 0 1 ИЛИ 0 1 1 0
, а затем примените операцию к каждому столбцу битов.
Если вы помните, логическое ИЛИ оценивается как истина (1) , если левый, правый или оба операнда равны истина (1) , и 0 в противном случае. Побитовое ИЛИ оценивается как 1 , если левый, правый или оба бита равны 1 , и 0 в противном случае. Следовательно, выражение оценивается так:
0 1 0 1 ИЛИ 0 1 1 0 ------- 0 1 1 1
Наш результат — 0111 двоичный.
#include #include int main () { std :: cout << (std :: bitset <4> {0b0101} | std :: bitset < 4> {0b0110}); возврат 0; } |
Это отпечатки:
0111
То же самое можно сделать и с составными выражениями ИЛИ, например 0b0111 | 0b0011 | 0b0001
.Если любой из битов в столбце равен 1 , результатом этого столбца будет 1 .
0 1 1 1 ИЛИ 0 0 1 1 ИЛИ 0 0 0 1 -------- 0 1 1 1
Вот код для вышеуказанного:
#include #include int main () { std :: cout << (std :: bitset <4> {0b0111} | std :: bitset < 4> {0b0011} | std :: bitset <4> {0b0001}); возврат 0; } |
Это отпечатки:
0111
Побитовое И
Побитовое И (&) работает аналогично приведенному выше. Логическое И оценивается как истина, если и левый, и правый операнды оценивают как истинное значение . Побитовое И оценивается как истина (1) , если оба бита в столбце равны 1 . Рассмотрим выражение 0b0101 & 0b0110
. Выравнивание каждого из битов и применение операции И к каждому столбцу битов:
0 1 0 1 И 0 1 1 0 -------- 0 1 0 0
#include #include int main () { std :: cout << (std :: bitset <4> {0b0101} & std :: bitset < 4> {0b0110}); возврат 0; } |
Это отпечатки:
0100
Точно так же мы можем сделать то же самое с составными выражениями AND, такими как 0b0001 & 0b0011 & 0b0111
.Если все биты в столбце равны 1, результат этого столбца будет 1.
0 0 0 1 И 0 0 1 1 И 0 1 1 1 -------- 0 0 0 1
#include #include int main () { std :: cout << (std :: bitset <4> {0b0001} & std :: bitset < 4> {0b0011} & std :: bitset <4> {0b0111}); возврат 0; } |
Это отпечатки:
0001
Побитовое исключающее ИЛИ
Последний оператор — это побитовое исключающее ИЛИ (^), также известное как исключающее или.0b0111 . Если в столбце есть четное число 1 бит, результат будет 0 . Если в столбце нечетное количество битов, равное 1, результатом будет 1 .
0 0 0 1 XOR 0 0 1 1 XOR 0 1 1 1 -------- 0 1 0 1
Операторы побитового присваивания
Подобно операторам арифметического присваивания, C ++ предоставляет операторы побитового присваивания, чтобы упростить изменение переменных.
Оператор | Символ | Форма | Операция |
---|---|---|---|
Назначение левой смены | << = | х | Сдвиг x влево на бит y |
Правое переключение передач | >> = | х >> = у | Сдвиг x вправо на биты y |
Назначение побитового ИЛИ | | = | х | = у | Назначить x | от y до x |
Поразрядное присвоение И | & = | x & = y | Назначьте x и y на x |
Назначение побитового XOR | ^ = | х ^ = у | Назначьте x ^ y на x |
Например, вместо записи x = x >> 1;
, можно написать x >> = 1;
.
#include #include int main () { std :: bitset <4> бит {0b0100}; бит >> = 1; std :: cout << bits; возврат 0; } |
Эта программа напечатает:
0010
Сводка
Обобщение того, как оценивать побитовые операции с использованием метода столбцов:
При оценке поразрядным ИЛИ , если какой-либо бит в столбце равен 1, результат для этого столбца равен 1.
При оценке поразрядного И , если все биты в столбце равны 1, результат для этого столбца равен 1.
При оценке поразрядного ИСКЛЮЧАЮЩЕГО ИЛИ , если в столбце есть нечетное число 1 бит, результат для этого столбец 1.
В следующем уроке мы исследуем, как эти операторы могут использоваться в сочетании с битовыми масками для облегчения манипуляции с битами.
Время викторины
а) Что дает 0110 >> 2 в двоичном формате?
Показать решение
0110 >> 2 оценивается как 0001
б) Что дает следующее двоичное значение: 0011 | 0101?
Показать решение
0 0 1 1 ИЛИ 0 1 0 1 -------- 0 1 1 1
c) Что означает следующее в двоичном формате: 0011 и 0101?
Показать решение
0 0 1 1 И 0 1 0 1 -------- 0 0 0 1
d) Что означает следующее в двоичном формате (0011 | 0101) и 1001?
Показать решение
В скобках: 0 0 1 1 ИЛИ 0 1 0 1 -------- 0 1 1 1 Затем: 0 1 1 1 И 1 0 0 1 -------- 0 0 0 1
Побитовое вращение похоже на побитовый сдвиг, за исключением того, что любые биты, сдвинутые с одного конца, добавляются обратно к другому концу.Например 0b1001. Для этого можно использовать test () и set ().
Должен выполняться следующий код:
1 2 3 4 5 6 7 8 9 10 11 12 13 140002 14 18 19 | #include #include // «rotl» означает «повернуть влево» std :: bitset <4> rotl (std :: bitset <4> бит) { // Ваш код здесь } int main () { std :: bitset <4> bits1 {0b0001}; std :: cout << rotl (bits1) << '\ n'; std :: bitset <4> bits2 {0b1001}; std :: cout << rotl (bits2) << '\ n'; возврат 0; } |
и выведите следующее:
0010 0011
Показать решение
1 2 3 4 5 6 7 8 9 10 11 12 13 140002 14 18 19 20 21 22 23 24 25 | #include #include std :: bitset <4> rotl (std :: bitset <4> bits) { bool leftbit = bits.тест (3); бит << = 1; // сдвиг влево if (leftbit) bits.set (0); бит возврата; } int main () { std :: bitset <4> bits1 {0b0001}; std :: cout << rotl (bits1) << '\ n'; std :: bitset <4> bits2 {0b1001}; std :: cout << rotl (bits2) << '\ n'; возврат 0; } |
Мы назвали функцию «rotl», а не «rotateLeft», потому что «rotl» — это хорошо известное имя в информатике, а также имя стандартной функции std :: rotl
.
Дополнительный балл: повторите тест №2, но не используйте функции проверки и установки.
Показать решение
1 2 3 4 5 6 7 8 9 10 11 12 13 140002 14 18 19 20 21 | #include #include // ч / т для ридера Chris для этого решения std :: bitset <4> rotl (std :: bitset <4> бит) { // бит << 1 сдвиг влево // биты >> 3 обрабатывают поворот самого левого бита return (биты << 1) | (биты >> 3); } int main () { std :: bitset <4> bits1 {0b0001}; std :: cout << rotl (bits1) << '\ n'; std :: bitset <4> bits2 {0b1001}; std :: cout << rotl (bits2) << '\ n'; возврат 0; } |
.
AND, OR, XOR, Shift и дополнение (с примером)
- Home
Testing
- Back
- Agile Testing
- BugZilla
- Cucumber
- 9000 Testing 9000 Database Testing
- JIRA
- Назад
- JUnit
- LoadRunner
- Ручное тестирование
- Мобильное тестирование
- Mantis
- Почтальон
- QTP
- SAP
- Центр тестирования качества SAP
- Назад
- Selenium
- SoapUI
- Управление тестированием
- TestLink
SAP
- Назад
- ABAP
- APO
- Новичок
- Basis
- BODS
- BI
- BPC
- CO
- Назад
- CRM O
- MM5000
- Crystal Reports QM
- Заработная плата
- Назад
- PI / PO
- PP
- SD
- SAPUI5
- Безопасность
- Менеджер решений
- Successfactors
- SAP Tutorials
- Назад
- Java
- JSP
- Kotlin
- Linux
- Linux
- Kotlin
- Linux js
- Perl
- Назад
- PHP
- PL / SQL
- PostgreSQL
- Python
- ReactJS
- Ruby & Rails
- Scala
- SQL 0000004 SQL
- UML
- VB.Net
- VBScript
- Веб-службы
- WPF
Обязательно учите!
- Назад
- Бухгалтерский учет
- Алгоритмы
- Android
- Блокчейн
- Business Analyst
- Веб-сайт сборки
- CCNA
- Облачные вычисления
- COBOL 9000 Compiler
- 0005
- Ethical Hacking
- Учебные пособия по Excel
- Программирование на Go
- IoT
- ITIL
- Jenkins
- MIS
- Сетевые подключения
- Операционная система
- Назад Управление проектами Обзоры
- 9000 Встроенный COBOL 9000 Дизайн 9000
- Salesforce
- SEO
- Разработка программного обеспечения
- VBA
- 0005
Большие данные
- Назад
- AWS
- BigData
- Cassandra
- Cognos
- Хранилище данных 0005
javascript — Что такое побитовые операторы?
Переполнение стека- Около
- Товары
- Для команд
- Переполнение стека Общественные вопросы и ответы
- Переполнение стека для команд Где разработчики и технологи делятся частными знаниями с коллегами
- Вакансии Программирование и связанные с ним технические возможности карьерного роста
- Талант Нанимайте технических специалистов и создавайте свой бренд работодателя
- Реклама Обратитесь к разработчикам и технологам со всего мира
- О компании
Загрузка…
Битовые операторы C ++
В C ++ побитовые операторы выполняют операции с целочисленными данными на индивидуальном битовом уровне. Эти операции включают тестирование, установку или смещение фактических битов. Например,
а и б;
а | б;
Вот список из 6 побитовых операторов, включенных в C ++.
Оператор | Описание |
---|---|
и | Оператор побитового И |
| | Оператор побитового ИЛИ |
^ | Побитовый оператор XOR |
~ | Оператор побитового дополнения |
<< | Оператор побитового сдвига влево |
>> | Оператор побитового сдвига вправо |
Эти операторы необходимы, потому что арифметико-логический блок (ALU), присутствующий в ЦП компьютера, выполняет арифметические операции на битовом уровне.
Примечание: Побитовые операторы могут использоваться только вместе с типами данных char
и int
.
1. Оператор побитового И в C ++
Поразрядный оператор AND и
возвращает 1 тогда и только тогда, когда оба операнда равны 1 . В противном случае возвращается 0 .
Следующая таблица демонстрирует работу побитового оператора AND. Пусть a и b будут двумя операндами, которые могут принимать только двоичные значения i.е. 1 и 0 .
а | б | a & b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
Примечание: Приведенная выше таблица известна как «Таблица истинности» для побитового оператора AND.
Давайте посмотрим на операцию побитового И двух целых чисел 12 и 25:
12 = 00001100 (в двоичном формате) 25 = 00011001 (в двоичном формате) // Побитовое И Операция 12 и 25 00001100 & 00011001 _________ 00001000 = 8 (в десятичной системе)
Пример 1: Побитовое И
#include
используя пространство имен std;
int main () {
// объявляем переменные
int a = 12, b = 25;
cout << "a =" << a << endl;
cout << "b =" << b << endl;
cout << "a & b =" << (a & b) << endl;
возврат 0;
}
Выход
а = 12 б = 25 а & b = 8
В приведенном выше примере мы объявили две переменные a и b .Обратите внимание на строку
.
cout << "a & b =" << (a & b) << endl;
Здесь мы выполняем поразрядно и между переменными a и b .
2. Оператор побитового ИЛИ в C ++
Поразрядное ИЛИ | Оператор
возвращает 1 , если хотя бы один из операндов - 1 . В противном случае возвращается 0 .
Следующая таблица истинности демонстрирует работу побитового оператора ИЛИ.Пусть a и b будут двумя операндами, которые могут принимать только двоичные значения, то есть 1 или 0 .
а | б | a | б |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
Давайте посмотрим на побитовую операцию ИЛИ двух целых чисел 12 и 25 :
12 = 00001100 (в двоичном формате) 25 = 00011001 (в двоичном формате) Побитовое ИЛИ для 12 и 25 00001100 | 00011001 _________ 00011101 = 29 (в десятичной системе)
Пример 2: Побитовое ИЛИ
#include
int main () {
int a = 12, b = 25;
cout << "a =" << a << endl;
cout << "b =" << b << endl;
cout << "a | b =" << (a | b) << endl;
возврат 0;
}
Выход
а = 12 б = 25 а | б = 29
Поразрядное число или из a = 12
и b = 25
дает 29
.Оператор возвращает 1 тогда и только тогда, когда один из операндов равен 1 . Однако, если оба операнда - 0 или оба - 1 , то результат будет 0 .
Следующая таблица истинности демонстрирует работу побитового оператора ИЛИ. Пусть a и b будут двумя операндами, которые могут принимать только двоичные значения, то есть 1 или 0 .
а | б | а ^ б |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
Давайте посмотрим на побитовую операцию XOR двух целых чисел 12 и 25:
12 = 00001100 (в двоичном формате) 25 = 00011001 (в двоичном формате) Побитовая операция XOR для 12 и 25 00001100 ^ 00011001 _________ 00010101 = 21 (в десятичной системе)
Пример 3: Побитовое исключающее ИЛИ
#include
int main () {
int a = 12, b = 25;
cout << "a =" << a << endl;
cout << "b =" << b << endl;
cout << "a ^ b =" << (a ^ b) << endl;
возврат 0;
}
Выход
а = 12 б = 25 а ^ Ь = 21
Поразрядный xor из a = 12
и b = 25
дает 21
.
4. Оператор поразрядного дополнения C ++
Оператор поразрядного дополнения - это унарный оператор (работает только с одним операндом). Он обозначается ~
, который изменяет двоичные цифры с 1 на 0 и с 0 на 1 .
Важно отметить, что побитовое дополнение любого целого числа N равно - (N + 1) . Например,
Рассмотрим целое число 35 .Согласно правилу, побитовое дополнение 35 должно быть - (35 + 1) = -36 . Теперь посмотрим, получим ли мы правильный ответ или нет.
35 = 00100011 (в двоичном формате) // Использование оператора побитового дополнения ~ 00100011 __________ 11011100
В приведенном выше примере мы получаем, что поразрядное дополнение 00100011 ( 35 ) равно 11011100 . Здесь, если преобразовать результат в десятичный формат, мы получим 220 .
Однако важно отметить, что мы не можем напрямую преобразовать результат в десятичный формат и получить желаемый результат.Это связано с тем, что двоичный результат 11011100 также эквивалентен -36 .
Чтобы понять это, нам сначала нужно вычислить двоичный выход -36 . Мы используем дополнение до 2 для вычисления двоичного числа отрицательных целых чисел.
Дополнение 2
В двоичной арифметике дополнение до единицы изменяет 0 на 1 и 1 на 0 . И, если мы прибавим 1 к результату дополнения до 1, мы получим дополнение до 2 исходного числа.
Например,
36 = 00100100 (в двоичном формате) Дополнение до 1 = 11011011 Дополнение 2: 11011011 +1 _________ 11011100
Здесь мы видим дополнение до 2 36 (т.е. -36 ) - это 11011100 . Это значение эквивалентно поразрядному дополнению 35 .
Следовательно, мы можем сказать, что поразрядное дополнение для 35 составляет -36 .
Пример 4: Побитовое дополнение
#include
int main () {
int num1 = 35;
int num2 = -150;
cout << "~ (" << num1 << ") =" << (~ num1) << endl;
cout << "~ (" << num2 << ") =" << (~ num2) << endl;
возврат 0;
}
Выход
~ (35) = -36 ~ (-150) = 149
В приведенном выше примере мы объявили две целочисленные переменные num1 и num2 и инициализировали их значениями 35
и -150
соответственно.
Затем мы вычислили их поразрядное дополнение с кодами (~ num1)
и (~ num2)
соответственно и отобразили их на экране.
Поразрядное дополнение до 35 = - (35 + 1) = -36 т.е. ~ 35 = -36 Поразрядное дополнение -150 = - (-150 + 1) = - (-149) = 149 т.е. ~ (-150) = 149
Это именно то, что мы получили на выходе.
C ++ Операторы сдвига
В программировании на C ++ есть два оператора сдвига:
- Оператор правого переключения
>>
- Оператор левой смены
<<
5.Оператор сдвига вправо в C ++
Оператор сдвига вправо сдвигает все биты вправо на определенное количество указанных битов. Обозначается он >>
.
Когда мы сдвигаем любое число вправо, младшие значащие биты отбрасываются, а старшие значащие биты заменяются нулями.
сдвиг вправо на один битКак видно из изображения выше, у нас есть 4-значное число. Когда мы выполняем операцию сдвига вправо на один бит, каждый отдельный бит сдвигается вправо на 1 бит.
В результате крайний правый бит отбрасывается, а крайний левый бит остается свободным. Эта вакансия заменяется на 0 .
6. Оператор левого сдвига в C ++
Оператор сдвига влево сдвигает все биты влево на определенное количество заданных битов. Обозначается он <<
.
Как видно из изображения выше, у нас есть 4-значное число. Когда мы выполняем операцию сдвига влево на 1 бит, каждый отдельный бит сдвигается влево на 1 бит.
В результате самый левый бит отбрасывается, а самый правый бит остается свободным. Эта вакансия заменяется на 0 .
Пример 5: Операторы смены
#include
int main () {
// объявление двух целочисленных переменных
int num = 212, я;
// Сдвиг вправо
cout << "Сдвиг вправо:" << endl;
// Использование цикла for для сдвига числа вправо с 0 бит на 3 бит
for (i = 0; i <4; i ++) {
cout << "212 >>" << i << "=" << (212 >> i) << endl;
}
// Сдвиг влево
cout << "\ nСдвиг влево:" << endl;
// Использование цикла for для сдвига числа слева от 0 бит до 3 бит
for (i = 0; i <4; i ++) {
cout << "212 <<" << i << "=" << (212 << i) << endl;
}
возврат 0;
}
Выход
Сдвиг вправо: 212 >> 0 = 212 212 >> 1 = 106 212 >> 2 = 53 212 >> 3 = 26 Сдвиг влево: 212 << 0 = 212 212 << 1 = 424 212 << 2 = 848 212 << 3 = 1696
Из выходных данных вышеприведенной программы мы можем сделать вывод, что для любого числа N результат оператора сдвига вправо будет:
N >> 0 = N N >> 1 = (N >> 0) / 2 N >> 2 = (N >> 1) / 2 N >> 3 = (N >> 2) / 2
и так далее.
Аналогичным образом результат оператора сдвига влево:
N << 0 = N N << 1 = (N << 0) * 2 N << 2 = (N << 1) * 2 N << 3 = (N << 2) * 2
и так далее.
Отсюда можно сделать вывод, что
N >> m = [N >> (m-1)] / 2 N << m = [N << (m-1)] * 2.