Раздел: Статьи / С/С++ /

Побитовые логические операции

Все способы изучить С++ Все способы изучить С++

Начинающие программисты даже не догадываются о том, какой огромный пласт в этой области скрыт от их глаз, и чего многие из новичков не увидят никогда, потому что это тёмная сторона программирования - чистый исходный код системного уровня… Подробнее...

Честно говоря, не понимаю, зачем в С/С++ (да и во всех других похожих языках) надо было вводить отдельные операторы для поразрядных логических операций. В Паскале, например, их нет, и он прекрасно без них обходится (пример будет ниже). Может они работают быстрее, или “художник так видит”. Но что сделано, то сделано - в С++ имеются отдельные операторы для поразрядных логических операций, такие как:

Оператор Операция
~ Поразрядное логическое НЕ (отрицание, инверсия). При выполнении этой операции каждый бит (разряд) числа меняет своё значение на противоположное. То есть 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

Ну и в конце пример на Паскале и С++. Сначала С++:

#include 

using 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), а также программирование панелей оператора, программируемых реле и других приборов систем автоматизации. Подробнее...

Инфо-МАСТЕР ®
Все права защищены ©
e-mail: mail@info-master.su

Яндекс.Метрика