Побитовые логические операции
Быстрый старт в С++
Бесплатная небольшая книга, которую не надо читать полгода, но которая покажет вам, что такое С++ и как начать его изучение. А также поможет решить, надо ли вам это вообще. Кроме того, подписавшись на рассылку, вы будете получать от меня полезные материалы по С++, которые я периодически выпускаю... Подробнее... |
Честно говоря, не понимаю, зачем в С/С++ (да и во всех других похожих языках) надо было вводить отдельные операторы для поразрядных логических операций. В Паскале, например, их нет, и он прекрасно без них обходится (пример будет ниже). Может они работают быстрее, или “художник так видит”. Но что сделано, то сделано - в С++ имеются отдельные операторы для поразрядных логических операций, такие как:
Оператор | Операция |
~ | Поразрядное логическое НЕ (отрицание, инверсия). При выполнении этой операции каждый бит (разряд) числа меняет своё значение на противоположное. То есть 0 превращается в 1 и наоборот. |
& | Поразрядное логическое И (логическое умножение). |
| | Поразрядное логическое ИЛИ (логическое сложение). |
^ | Поразрядное логическое ИСКЛЮЧАЮЩЕЕ ИЛИ. |
Зачем нужны поразрядные логические операции
Как вы знаете, двоичная система счисления работает только с двумя цифрами (0 и 1). Любое число может быть представлено в двоичной системе счисления. Тогда количество нулей и единиц в числе - это количество битов (разрядов). Например, десятичное число 15 в двоичной системе будет записано как 1111. То есть для записи десятичного числа 15 в двоичной системе нам потребуется 4 цифры (4 бита, или 4 разряда). На всякий случай скажу, что бит и разряд - это одно и то же. Просто бит - слово буржуйское, а разряд - наше, родное. Поэтому я стараюсь чаще использовать именно слово “разряд”.
Наименьший размер ячейки памяти - один байт, который состоит из 8 битов (подробнее см. здесь). То есть в один байт мы можем упаковать 8 простых логических сигналов, которые имеют только два состояния (0 и 1, “ДА” и “НЕТ” и т.п.).
А это значит, что если нам необходимо хранить или передавать такие данные, то мы можем получить экономию не менее чем в 8 раз. И поверьте мне, как человеку, который занимается промышленной автоматизацией, это очень даже важно (особенно с учётом того, что во многих протоколах передаются не байты, а слова, то есть экономия будет уже в 16 раз).
Ну а если мы будем работать не с байтом целиком, а с отдельным его разрядом, то нам, конечно, для этого потребуются операторы, которые могут это делать.
Самая простая операция - это логическое НЕ. Она лишь меняет все разряды числа на противоположные. То есть нули заменяет единицами и наоборот. Например, у нас есть число 10. В двоичном виде это:
1010
Тогда, если мы применим к этому числу операцию поразрядного логического НЕ, то в итоге получим:
0101
то есть десятичное число 5. Но это если у нас число четырёхразрядное.
Однако таких типов данных в С++ нет.
Минимальный тип данных в С++ - это char
, который может хранить один байт и используется,
в основном, для символов. А если говорить о “чисто числовых” типах, то это short
- слово данных
(два байта). Поэтому, если вы используете, например, unsigned short
, то любое число в переменной этого типа будет занимать 16 разрядов. То есть наше число 10 будет выглядеть так:
00000000 00001010
А после операции НЕ это будет:
11111111 11110101
то есть никакое не число 5, а число 65525. Если же вы используете тип данных со знаком, например,
short
, то результат будет вообще отрицательным числом. Пример:
unsigned short x = 10; cout << "x = " << x << "\n"; x = ~x; cout << "~x = " << x << "\n";
Может показаться, что строка x = ~x; совершенно лишняя. Но это не так. И если вы сделаете так:
cout << "x = " << x << "\n"; cout << "~x = " << ~x << "\n";
то получите неожиданный результат (какой - не скажу, проверьте сами))). Хотя с точки зрения поразрядной операции результат будет также правильным.
Таблицы истинности
С остальными операция чуть сложнее, поэтому для таких операций придумали так называемые “таблицы истинности”, которые позволяют понять, как работает та или иная логическая операция.
Таблица истинности для логического И (&):
Число 1 | Операция | Число 2 | Результат |
0 | & | 0 | 0 |
0 | & | 1 | 0 |
1 | & | 0 | 0 |
1 | & | 1 | 1 |
То есть здесь, как и в математике, умножение на 0 всегда даёт ноль. Поэтому единица в результате будет только в том случае, если оба разряда равны 1.
Пример:
0011 1110 (126) И (^) 0000 0011 (3) = 0000 0010 (2)
Начинаем выполнять операцию И между числами 126 и 3 для каждого разряда (справа налево - от младшего
разряда к старшему). В нулевом разряде числа 126 у нас 0, а в числе 3 - единица. Смотрим таблицу
истинности - 0 И 1
равно 0. Значит в результате в нулевом разряде будет 0.
Идём дальше: в 1-м разряде числа 126 у нас 1, и в числе 3 тоже единица. Смотрим таблицу истинности:
1 & 1 = 1
. Значит в 1-м разряде результата у нас 1. Ну и так далее по всем разрядам. В итоге получаем число 2.
По остальным операциям алгоритм такой же, поэтому пояснять не буду, а только приведу таблицы истинности.
Таблица истинности для логического ИЛИ (|):
Число 1 | Операция | Число 2 | Результат |
0 | | | 0 | 0 |
0 | | | 1 | 1 |
1 | | | 0 | 1 |
1 | | | 1 | 1 |
Таблица истинности для логического ИСКЛЮЧАЮЩЕГО ИЛИ (^):
Число 1 | Операция | Число 2 | Результат |
0 | ^ | 0 | 0 |
0 | ^ | 1 | 1 |
1 | ^ | 0 | 1 |
1 | ^ | 1 | 0 |
Ну и в конце пример на Паскале и С++. Сначала С++:
#includeusing namespace std; int main() { int n1 = 126; int n2 = 3; int x; bool b; b = n1 && n2; //Логическое И x = n1 & n2; //Поразрядное логическое И cout << "*** C/C++ ***" << "\n"; cout << "n1 & n2 = " << x << "\n"; cout << "n1 && n2 = " << b << "\n"; return 0; }
Теперь Паскаль:
program test; var n1 : Integer = 126; n2 : Integer = 3; x : Integer; b : boolean; begin WriteLn('*** PASCAL ***'); x := n1 and n2; //Поразрядное логическое И b := boolean(n1) and boolean(n2); //Логическое И //b := n1 and n2; //Так нельзя WriteLn('n1 and n2 = ', x); WriteLn('n1 and n2 = ', b); ReadLn; end.
На рисунке вывод обеих программ для сравнения.
Как видите, вывод совершенно одинаковый, если не считать того, что Паскаль выводит TRUE, а С++ выводит 1. В С++ при желании можно тоже сделать так, чтобы на экран выводились не числа, а слова типа true/false.
Хотя в Паскале нет особых операторов для поразрядных операций, всё работает точно также. Но, обратите внимание, что в Паскале, в отличие от С++, нельзя сделать так:
var b : boolean; b := n1 and n2; //Так нельзя
Чтобы проделать такую операцию, надо явно преобразовать числа в логические значения:
b := boolean(n1) and boolean(n2);
В С++ же происходит неявное преобразование. Это можно считать одновременно и преимуществом, и недостатком. С одной стороны, появляется возможность писать меньше кода. С другой стороны, можно наделать кучу ошибок. Кроме того, из-за этого, наверно, и были введены особые поразрядные операторы. Поскольку без них в С++ нельзя понять, что вы хотите сделать - получить результат логической операции или выполнить поразрядную операцию.
В Паскале же всё намного проще и понятнее. Если в левой части переменная логического типа, то мы понимаем, что в правой части обычная логическая операция.
Если же в левой части переменная числового типа, то мы понимаем, что в правой части поразрядная логическая операция.
Кстати, понимаем это не только мы, но и компилятор. Поэтому он не даст вам написать такой код:
var b : boolean; b := n1 and n2;
который в большинство случаев лишён какого-либо смысла, и, скорее всего, является семантической ошибкой. Но компилятору С++ на это совершенно пофиг. Это проблемы программиста. Компилятор же Паскаля (как и сам язык) более заботлив и дружелюбен.
Но тут уж каждый выбирает сам, что ему ближе - больше свободы, или меньше труднонаходимых ошибок в программах…
Вступить в группу "Основы программирования"
Подписаться на канал в РУТУБ Подписаться на Дзен-канал Подписаться на рассылки по программированию |
Все способы изучить С++
Начинающие программисты даже не догадываются о том, какой огромный пласт в этой области скрыт от их глаз, и чего многие из новичков не увидят никогда, потому что это тёмная сторона программирования - чистый исходный код системного уровня… Подробнее... |
Помощь в технических вопросах
Помощь студентам. Курсовые, дипломы, чертежи (КОМПАС), задачи по программированию: Pascal/Delphi/Lazarus; С/С++; Ассемблер; языки программирования ПЛК; JavaScript; VBScript; Fortran; Python и др. Разработка (доработка) ПО ПЛК (предпочтение - ОВЕН, CoDeSys 2 и 3), а также программирование панелей оператора, программируемых реле и других приборов систем автоматизации. Подробнее... |