Поразрядные (битовые) операции

Логические операции

Эти операции используются при построении сложных логических выражений. В эту группу входят 3 операции:

·!- логическое отрицание (логическое НЕ);

· && - конъюнкция (логическое И);

· || - дизъюнкция (логическое ИЛИ).

Первая операция унарная, две остальные – бинарные. Операнды – выражения любого арифметического типа данных, значения которых интерпретируются как значения логического типа (отличное от 0 значение – true; 0 - false). Результат этих операций - логического типа.

Правила записи и результаты выполнения логических операций приведены в следующей таблице:

a b !a a && b a || b
         
         
         
         

Пусть, например, имеется математическое неравенство: 0 < x < 10. На языке C++ это неравенство следует записывать так: (0 < x) && (10 > x) или (х > 0) && (x < 10). А математическое неравенство 0 > x > 10 должно выглядеть следующим образом: (0 > x) || (10 < x) или (х < 0) || (x > 10).

Особенностью выполнения операций && и || является то, что второй операнд (в правой части операций) вычисляется не всегда. Он вычисляется только в том случае, если значения первого операнда недостаточно для получения результата операций && или ||.

Например. Если в выражении (a + 10) && (b – 1) значение первого (левого) операнда a + 10 равно 0 (false) (это будет при значении a = -10), то вычисление второго (правого) операнда b – 1 не выполняется, так как и без его вычисления, значение результата операции && уже известно – это false. А в выражении (a + 10) || (b – 1) второй операнд не будет вычисляться в том случае, если первый операнд не равен 0 – в этом случае результат операции || и так уже известен – он равен true.

Побитовые операции рассматривают операнды как упорядоченные наборы битов,
каждый бит может иметь одно из двух значений - 0 или 1 (наборы двоичных значений). Такие операции позволяют программисту манипулировать значениями отдельных битов. Объект, содержащий набор битов, иногда называют битовым вектором. Он позволяет компактно хранить набор флагов - переменных, принимающих значение "да" "нет".

Операции сдвига - << и >> - бинарные операции. Операнды целого типа. Результат также целого типа. Формат записи:

< Операнд 1 > << < Операнд 2 > - сдвиг влево

< Операнд 1 > >> < Операнд 2 > - сдвиг вправо

Операции выполняют копирование битов двоичного представления первого операнда с сдвигом на количество разрядов, указанное во втором операнду, в соответствующем направлении.

Значение второго операнда должно быть больше или равно 0 и меньше количества двоичных разрядов первого операнда, иначе результат выполнения операций не гарантирован (зависит от реализации, но обычно равен 0).

Примеры:

unsigned a = 20, n = 3, r;

r = a << n;

cout << r << endl; // На экран выведено 160

r = a >> n;

cout << r << endl; // На экран выведено 2

Иллюстрация:

Номер разряда: 31 30 … 8 7 6 5 4 3 2 1 0

Значение a: 0 0 … 0 0 0 0 1 0 1 0 0 = 20

Операция: a << n

Значение r: 0 0 … 0 1 0 1 0 0 0 0 0 = 160

Операция: a >> n

Значение r: 0 0 … 0 0 0 0 0 0 0 1 0 = 2

Операция сдвига влево осуществляет перемещение битов левого операнда a в сторону больших разрядов на количество разрядов, равное значению правого операнда n. Это эквивалентно умножению значения a на 2 в степени n (20 * 8 = 160).

Операция сдвига вправо осуществляет перемещение битов левого операнда a в сторону меньших разрядов на количество разрядов, равное значению правого операнда n. Это эквивалентно делению значения a на 2 в степени n (целочисленное деление 20 / 8 = 2).

Используя операцию сдвига влево очень просто получить любую целую степень двойки в диапазоне степеней равной количеству двоичных разрядов правого операнда без 1. Например, так:

1U << 20 - равно 2 в степени 20, то есть 1048576

При сдвиге влево (в сторону старших разрядов), освобождающиеся младшие разряды замещаются 0 (нулями). При сдвиге вправо возможны две ситуации: если первый операнд беззнаковый (unsigned), то освобождающиеся старшие разряды замещаются 0; если же первый операнд знаковый, то освобождающиеся старшие разряды замещаются либо знаковым разрядом, либо 0 (нет гарантии - зависит от реализации).

Поразрядные логические операции

К этой группе операций относятся:

· ~ - побитовое отрицание (побитовое НЕ) - унарная операция;

· & - побитовая конъюнкция (побитовое И) - бинарная операция;

· | - побитовая дизъюнкция (побитовое ИЛИ) - бинарная операция;

· ^ - побитовое исключающее ИЛИ - бинарная операция.

Операндами этих операций целочисленных типов данных. Результат также целочисленный.

Операция побитовое отрицание (~) осуществляет инвертирование всех байтов двоичного представления своего операнда. Например:

int a = 14, r;

r = ~a;

cout << r << endl; // На экран выведено -15

Иллюстрация:

Номер разряда: 31 30 … 8 7 6 5 4 3 2 1 0

Значение a: 0 0 … 0 0 0 0 0 1 1 1 0 = 14

Значение r = ~a: 1 1 … 1 1 1 1 1 0 0 0 1 = -15

Остальные операции выполняют соответствующую логическую операцию над каждой парой соответствующих разрядов первого и второго операндов, интерпретируя значения двоичных разрядов как логические значения (1 - true; 0 - false). Например:

int a = 14, b = 7, r;

r = a & b;

cout << r << endl; // На экран выведено 6

r = a | b;

cout << r << endl; // На экран выведено 15

r = a ^ b;

cout << r << endl; // На экран выведено 9

Иллюстрация:

Номер разряда: 31 30 … 8 7 6 5 4 3 2 1 0

Значение a: 0 0 … 0 0 0 0 0 1 1 1 0 = 14

Значение b: 0 0 … 0 0 0 0 0 0 1 1 1 = 7

Операция: a & b

Значение r: 0 0 … 0 0 0 0 0 0 1 1 0 = 6

Операция: a | b

Значение r: 0 0 … 0 0 0 0 0 1 1 1 1 = 15

Операция: a ^ b

Значение r: 0 0 … 0 0 0 0 0 1 0 0 1 = 9

Использование побитовых операций

Как уже говорилось, побитовые операции используются для обработки отдельных двоичных разрядов памяти. Для манипулирования отдельным битом необходимо научиться делать следующее:

· определять значение заданного бита;

· устанавливать значение заданного бита в значение 0 или 1;

· инвертировать значение заданного бита.

Это можно сделать так:

unsigned a = 1234; // Целое значение, битами которого мы будем управлять

unsigned short n = 4; // Номер необходимого бита (от 0 до 31)

bool r; // Значение результата (0 или 1)

/* Узнаем, чему равен n -й бит (двоичный разряд) значения a. Результат поместим в переменную r */

r = a & (1U << n);

cout << "Разряд с номером " << n << " равен " << r << endl; // значение 1

/* Установим n -й бит (двоичный разряд) значения a в 0. Результат поместим в переменную а */

a = a & (~ (1U << n));

cout << "Значение а равно " << a << endl; // значение 1218

/* Проверяем */

r = a & (1U << n);

cout << "Разряд с номером " << n << " равен " << r << endl; // значение 0

/* Возвращаем n -й бит (двоичный разряд) значения a в 1. Результат поместим в переменную а */

a = a | (1U << n);

cout << "Значение а равно " << a << endl; // значение 1234

/* Проверяем */

r = a & (1U << n);

cout << "Разряд с номером " << n << " равен " << r << endl; // значение 1

/* Инвертируем n -й бит (двоичный разряд) значения a. Результат поместим в переменную а */

a = a ^ (1U << n);

cout << "Значение а равно " << a << endl; // значение 1218

/* Проверяем */

r = a & (1U << n);

cout << "Разряд с номером " << n << " равен " << r << endl; // значение 0

/* Еще раз инвертируем n -й бит (двоичный разряд) значения a. Результат поместим в переменную а */

a = a ^ (1U << n);

cout << "Значение а равно " << a << endl; // значение 1234

/* Проверяем */

r = a & (1U << n);

cout << "Разряд с номером " << n << " равен " << r << endl; // значение 1

Изменяя значение переменной n в диапазоне от 0 до 31 можно выполнить все эти действия над любым битом переменной a какое бы значение она не содержала.

Таким образом, для того, чтобы узнать, чему равен двоичный разряд с номером n в значении переменной a, мы воспользовались выражением

a & (1U << n).

Иллюстрация вычисления этого выражения:

Номер разряда: 31 30 … 11 10 9 8 7 6 5 4 3 2 1 0

1U: 0 0 … 0 0 0 0 0 0 0 0 0 0 0 1 = 1

1U << n: 0 0 … 0 0 0 0 0 0 0 1 0 0 0 0

Значение a: 0 0 … 0 1 0 0 1 1 0 1 0 0 1 0 = 1234

a & (1U << n): 0 0 … 0 0 0 0 0 0 0 1 0 0 0 0 = 16

Результатом вычисления этого выражения является целое значение не равное 0. Операция присваивания этого значения логической переменной r автоматически преобразует целое значение 16 в логическое значение true (т.е. 1).

Если бы значение a имело бы разряд с номером 4 равным 0, то результатом вычисления этого выражения было бы значение 0. При выполнении операции присваивания это значение было бы преобразовано в логическое значение false (т.е. 0).

Для установки значения разряда с номером n в переменной a в значение 0 используется выражение

a & (~ (1U << n)).

Иллюстрация вычисления этого выражения:

Номер разряда: 31 30 … 11 10 9 8 7 6 5 4 3 2 1 0

1U: 0 0 … 0 0 0 0 0 0 0 0 0 0 0 1 = 1

1U << n: 0 0 … 0 0 0 0 0 0 0 1 0 0 0 0

~ (1U << n): 1 1 … 1 1 1 1 1 1 1 0 1 1 1 1

Значение a: 0 0 … 0 1 0 0 1 1 0 1 0 0 1 0 = 1234

a & (1U << n): 0 0 … 0 1 0 0 1 1 0 0 0 0 1 0 = 1218

Для установки значения разряда с номером n в переменной a в значение 1 используется выражение

a | (1U << n).

Иллюстрация вычисления этого выражения:

Номер разряда: 31 30 … 11 10 9 8 7 6 5 4 3 2 1 0

1U: 0 0 … 0 0 0 0 0 0 0 0 0 0 0 1 = 1

1U << n: 0 0 … 0 0 0 0 0 0 0 1 0 0 0 0

Значение a: 0 0 … 0 1 0 0 1 1 0 0 0 0 1 0 = 1218

a | (1U << n): 0 0 … 0 1 0 0 1 1 0 1 0 0 1 0 = 1234

Для инвертирования значения разряда с номером n в переменной a используется выражение

a ^ (1U << n).

Иллюстрация вычисления этого выражения при a = 1218:

Номер разряда: 31 30 … 11 10 9 8 7 6 5 4 3 2 1 0

1U: 0 0 … 0 0 0 0 0 0 0 0 0 0 0 1 = 1

1U << n: 0 0 … 0 0 0 0 0 0 0 1 0 0 0 0

Значение a: 0 0 … 0 1 0 0 1 1 0 0 0 0 1 0 = 1218

a ^ (1U << n): 0 0 … 0 1 0 0 1 1 0 1 0 0 1 0 = 1234

Но, если a = 1234, то:

Номер разряда: 31 30 … 11 10 9 8 7 6 5 4 3 2 1 0

1U: 0 0 … 0 0 0 0 0 0 0 0 0 0 0 1 = 1

1U << n: 0 0 … 0 0 0 0 0 0 0 1 0 0 0 0

Значение a: 0 0 … 0 1 0 0 1 1 0 1 0 0 1 0 = 1234

a ^ (1U << n): 0 0 … 0 1 0 0 1 1 0 0 0 0 1 0 = 1218

В C++ имеются и другие средства работы с отдельными битами, но они будут рассмотрены позже.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: