Краткое содержание
Целое число для процессора есть ряд битов, и C++ предлагает операторы, которые действуют на этот ряд столбец за столбцом: &, |, ^ и ~, а также сдвиги << и >>. Этот урок учит вычислять их вручную, использовать их, чтобы хранить несколько флагов в одном целом числе с помощью масок, и распознавать сдвиги, которые стандарт не определяет. Затем он объясняет, почему 2 ^ 3 равно 1, а не 8, и почему побитовые операторы требуют скобок. Вторая половина представляет математическую библиотеку: функции <cmath>, константы <numbers>, наибольший общий делитель и наименьшее общее кратное из <numeric> и битовые функции <bit>, вместе с принципом, верным для любого языка: то, что библиотека предлагает правильным и проверенным, не переписывают.
Цели обучения
- Вычислять вручную результат
&,|,^,~и сдвигов для небольших целых чисел. - Отличать определённые сдвиги (в том числе сдвиги отрицательных чисел, начиная с C++20) от тех, что являются неопределённым поведением, и предсказывать результат первых.
- Использовать маски, чтобы проверить, установить, сбросить и переключить бит.
- Объяснять, почему
2 ^ 3равно 1, а не 8, и выбирать междуstd::powи сдвигом в зависимости от случая. - Использовать в формулах функции
<cmath>(std::sqrt,std::pow,std::abs,std::hypot, тригонометрические) и константы<numbers>, такие какstd::numbers::pi.
Побитовые операторы
Урок Целые числа и их представление показал, что int есть ряд битов, 32 в системах курса, и что его значение равно сумме весов позиций, где стоит 1. До сих пор, однако, программа обращалась с целым числом только как с числом: складывала, умножала и делила его. Между тем тот же ряд допускает и другое прочтение, как набор из 32 ответов «да» или «нет», и для такого прочтения в C++ есть четыре оператора, которые работают с битами по одному, без переноса между позициями.
Биты нумеруются справа, начиная с 0: бит есть бит с весом . Так, в числе , которое в двоичной записи есть 1100, установлены биты 2 и 3 и сброшены биты 0 и 1.
Операторы таковы. Побитовое И, a & b, даёт 1 в каждой позиции, где и у a, и у b стоит 1. Побитовое ИЛИ, a | b, даёт 1 там, где 1 есть хотя бы у одного из двух. Исключающее ИЛИ, a ^ b, даёт 1 там, где 1 есть ровно у одного из двух, то есть там, где биты различаются. Побитовое НЕ, ~a, имеет один операнд и инвертирует каждый бит. Таблицы первых трёх для пары битов и совпадают с таблицами конъюнкции, дизъюнкции и исключающей дизъюнкции:
Тот, кто изучал курс «Математическая логика I», узнает здесь логические связки, где 1 означает истину, а 0 ложь: побитовый оператор есть не что иное, как связка, применённая ко всем столбцам сразу. Отсюда следует способ вычисления вручную: оба числа записывают в двоичной системе одно под другим, выравнивая по правому краю, и применяют таблицу к каждому столбцу, не заботясь о соседних.
Числа 1100 и 1010 обладают тем свойством, что их четыре столбца перебирают все четыре сочетания двух битов; поэтому на них можно увидеть все три таблицы сразу.
Доказательство
Данные:
1100есть 12, а1010есть 10. Столбцы слева направо содержат пары , , и .Первый столбец
12 & 10: оба бита равны 1, и И даёт 1.Второй столбец:
1и0. Достаточно одного 0, чтобы И дало 0.Третий столбец:
0и1; И снова даёт 0.Четвёртый столбец:
0и0дают 0. Строка результата,1000, есть 8:12 & 10равно 8.ИЛИ устанавливает каждый столбец, где есть хотя бы одна 1:
1110, то есть12 | 10равно 14.Исключающее ИЛИ устанавливает только столбцы, биты которых различаются:
0110, и12 ^ 10равно 6.
Программа ниже подтверждает рисунок. Поле форматирования {:04b}, представленное в уроке о целых числах, выводит значение в двоичной системе четырьмя цифрами, дополненными нулями.
#include <print>int main(){ const int a{0b1100}; const int b{0b1010}; std::println("a = {:04b} = {}", a, a); std::println("b = {:04b} = {}", b, b); std::println("a & b = {:04b} = {}", a & b, a & b); std::println("a | b = {:04b} = {}", a | b, a | b); std::println("a ^ b = {:04b} = {}", a ^ b, a ^ b); std::println("~a = {}", ~a);}a = 1100 = 12b = 1010 = 10a & b = 1000 = 8a | b = 1110 = 14a ^ b = 0110 = 6~a = -13Последняя строка требует объяснения. НЕ инвертирует все 32 бита a, а не только четыре выводимых: 28 нулей слева становятся единицами, а int, который начинается с 1, в дополнительном коде отрицателен. Между тем в дополнительном коде противоположное число получают, инвертируя биты и прибавляя 1, то есть ; следовательно, , и ~12 равно . Итак, побитовое НЕ над целым числом со знаком даёт число, которое редко интересно как число: оно интересно как битовая комбинация, и поэтому с битами предпочтительно работают в беззнаковых типах, как будет видно в параграфе о масках.
Предупреждение о малых типах. Перед операцией C++ преобразует в int всякий операнд более узкого целого типа, например std::uint8_t; это продвижение, которое урок Арифметические операции описал для арифметических операций, и оно действует и здесь. Отсюда следует, что ~flags при flags типа std::uint8_t со значением 5 есть не байт 11111010, а int , среди 32 битов которого 24 единицы добавлены слева; лишь при обратном сохранении в std::uint8_t значение усекается до восьми битов и получается ожидаемый байт 250.
Не следует путать побитовые операторы с логическими операторами урока Отношения и логика. 6 && 3 есть true: оба операнда отличны от нуля, а результат имеет тип bool. 6 & 3, напротив, есть целое число, составленное из столбцов, где у обоих стоит 1. Кроме того, && вычисляется по короткой схеме, а & всегда вычисляет оба операнда.
Загрузка содержимого…