ВАЖНО!
7.7.
Поразрядные операторы
Поскольку язык C++ сориентирован так, чтобы позволить полный доступ к аппаратным средствам компьютера, важно, чтобы он имел возможность непо-
С++: руководство для начинающих
323Еще о
типах данных и операторах7средственно воздействовать на отдельные биты в рамках байта или машинного слова. Именно поэтому C++ и содержит поразрядные операторы.
Поразрядные операторы предназначены для тестирования, установки или сдвига реальных битов в байтах или словах, которые соответствуют символьным или целочислен- ным C++-типам. Поразрядные операторы не используются для операндов типа bool
, float, double, long double, void или других еще более сложных типов данных. Поразрядные операторы (табл. 7.1) очень часто используются для реше- ния широкого круга задач программирования системного уровня, например, при опросе информации о состоянии устройства или ее формировании. Теперь рас- смотрим каждый оператор этой группы в отдельности.
Вопросы для текущего контроля
1. Перечисление — это список именованных ________ констант.
2. Какое целочисленное значение по умолчанию имеет первый символ пере- числения?
3. Покажите, как объявить идентификатор BigInt, чтобы он стал еще од- ним именем для типа long int.*
Таблица 7.1.
Поразрядные операторыОператор Значение &
Поразрядное И (AND)
|
Поразрядное ИЛИ (OR)
^
Поразрядное исключающее ИЛИ (XOR)
Дополнение до 1 (унарный оператор НЕ)
>>
Сдвиг вправо
<<
Сдвиг влево
Поразрядные операторы И, ИЛИ, исключающее ИЛИ и НЕПоразрядные операторы И, ИЛИ, исключающее ИЛИ и НЕ (обозначаемые символами &, |, ^ и соответственно) выполняют те же операции, что и их ло-
1. Перечисление — это список именованных целочисленных констант.
2. По умолчанию первый символ перечисления имеет значение 0.
3. typedef long int BigInt;.
324 Модуль 7. Еще о типах данных и операторах гические эквиваленты (т.е. они действуют согласно той же таблице истинности).
Различие состоит лишь в том, что поразрядные операции работают на побитовой основе. В следующей таблице показан результат выполнения каждой поразряд- ной операции для всех возможных сочетаний операндов (нулей и единиц).
p q
p & q p | q p ^ q
p
0 0
0 0
0 1
1 0
0 1
1 0
0 1
0 1
1 1
1 1
1 1
0 0
Как видно из таблицы, результат применения оператора XOR (исключающее
ИЛИ ) будет равен значению ИСТИНА (1) только в том случае, если истинен
(равен значению 1)
лишь один из операндов; в противном случае результат при- нимает значение ЛОЖЬ (0).
Поразрядный оператор И можно представить как способ подавления битовой информации. Это значит, что 0 в любом операнде обеспечит установку в 0 соот- ветствующего бита результата. Вот пример.
1101 0011
& 1010 1010
----------
1000 0010
Использование оператора “&” демонстрируется в следующей программе. Она преобразует любой строчный символ в его прописной эквивалент путем установ- ки шестого бита равным значению 0. Набор символов ASCII определен так, что строчные буквы имеют почти такой же код, что и прописные, за исключением того, что код первых отличается от кода вторых ровно на 32. Следовательно, как показано в этой программе, чтобы из строчной буквы сделать прописную, доста- точно обнулить ее шестой бит.
// Получение прописных букв с использованием поразрядного
// оператора “И”.
#include
using namespace std;
int main()
{
char ch;
for(int i = 0 ; i < 10; i++) {
С++: руководство для начинающих
325
Еще о типах данных и операторах
7
ch = 'a' + i; cout << ch;
// Эта инструкция обнуляет 6-й бит.
ch = ch & 223; // В переменной ch теперь
// прописная буква. cout << ch << " ";
} cout << "\n"; return 0;
}
Вот как выглядят результаты выполнения этой программы.
aA bB cC dD eE fF gG hH iI jJ
Значение 223, используемое в инструкции поразрядного оператора “И”, явля- ется десятичным представлением двоичного числа 1101 1111. Следовательно, эта операция “И” оставляет все биты в переменной ch нетронутыми, за исключением шестого (он сбрасывается в нуль).
Оператор “И” также полезно использовать, если нужно определить, установ- лен интересующий вас бит (т.е. равен ли он значению 1) или нет. Например, при выполнении следующей инструкции вы узнаете, установлен ли 4-й бит в пере- менной status.
if(status & 8) cout << "Бит 4 установлен";
Чтобы понять, почему для тестирования четвертого бита используется чис- ло 8, вспомните, что в двоичной системе счисления число 8 представляется как
0000 1000, т.е. в числе 8 установлен только четвертый разряд. Поэтому условное выражение инструкции if даст значение ИСТИНА только в том случае, если четвертый бит переменной status также установлен (равен 1). Интересное ис- пользование этого метода показано на примере функции disp_binary(). Она отображает в двоичном формате конфигурацию битов своего аргумента. Мы бу- дем использовать функцию show_binary() ниже в этой главе для исследова- ния возможностей других поразрядных операций.
// Отображение конфигурации битов в байте.
void show_binary(unsigned int u)
{
326 Модуль 7. Еще о типах данных и операторах int t; for(t=128; t > 0; t = t/2) if(u & t) cout << "1 "; else cout << "0 "; cout << "\n";
}
Функция show_binary(), используя поразрядный оператор “И”, последова- тельно тестирует каждый бит младшего байта переменной u, чтобы определить, установлен он или сброшен.
Если он установлен, отображается цифра 1, в про- тивном случае — цифра 0.
Поразрядный оператор “ИЛИ” (в противоположность поразрядному “И”) удобно использовать для установки нужных битов равными единице. При вы- полнении операции “ИЛИ” наличие в любом операнде бита, равного 1, означает, что в результате соответствующий бит также будет равен единице. Вот пример.
1101 0011
| 1010 1010
----------
1111 1011
Оператор “ИЛИ” можно использовать для превращения рассмотренной выше программы (которая преобразует строчные символы в их прописные эквивален- ты) в ее “противоположность”, т.е. теперь, как показано ниже, она будет преоб- разовывать прописные буквы в строчные.
// Получение строчных букв с использованием поразрядного
// оператора “ИЛИ”.
#include
using namespace std; int main()
{ char ch; for(int i = 0 ; i < 10; i++) { ch = 'A' + i; cout << ch;
С++: руководство для начинающих
327Еще о типах данных и операторах
7 // Эта инструкция делает букву строчной,
// устанавливая ее 6-й бит. ch = ch | 32; // В переменной ch теперь
// строчная буква. cout << ch << " ";
} cout << "\n"; return 0;
}
Вот результаты выполнения этой программы.
Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj
Итак, вы убедились, что установка шестого бита превращает прописную букву в ее строчный эквивалент.
Поразрядное исключающее “ИЛИ” (XOR ) устанавливает бит результата рав- ным единице только в том случае, если соответствующие биты операндов отли- чаются один от другого, т.е. не равны. Вот пример:
0111 1111
^ 1011 1001
----------
1100 0110
Оператор “исключающее ИЛИ” (XOR) обладает интересным свойством, кото- рое дает нам простой способ кодирования сообщений. Если применить операцию
“исключающее ИЛИ” к некоторому значению X и заранее известному значению
Y, а затем проделать то же самое с результатом предыдущей операции и значени- ем Y, то мы снова получим значение X. Это означает, что после выполнения этих операций
R1 = X ^ Y;
R2 = R1 ^ Y;
R2 будет иметь значение X. Таким образом, результат последовательного выпол- нения двух операций “XOR” с использованием одного и того же значения дает исходного значение. Этот принцип можно применить для
создания простой шиф- ровальной программы, в которой некоторое целое число используется в качестве ключа для шифрования и дешифровки сообщения путем выполнения операции
XOR над символами этого сообщения. Первый раз мы используем операцию
XOR, чтобы закодировать сообщение, а после второго ее применения мы получа-
328
Модуль 7. Еще о типах данных и операторах ем исходное (декодированное) сообщение. Рассмотрим простой пример исполь- зования этого метода для шифрования и дешифровки короткого сообщения.
// Использование оператора XOR для кодирования и
// декодирования сообщения.
#include using namespace std; int main()
{ char msg[] = "Это простой тест"; char key = 88; cout << "Исходное сообщение: " << msg << "\n"; for(int i = 0 ; i < strlen(msg); i++) msg[i] = msg[i] ^ key; cout << "Закодированное сообщение: " << msg << "\n"; for(int i = 0 ; i < strlen(msg); i++) msg[i] = msg[i] ^ key; cout << "Декодированное сообщение: " << msg << "\n"; return 0;
}
Эта программа генерирует такие результаты.
Исходное сообщение: Это простой тест
Закодированное сообщение: +¦Ўxў¬Ў¦¦Ўёx¦¤¦¦
Декодированное сообщение: Это простой тест
Унарный оператор “НЕ” (или оператор дополнения до 1 ) инвертирует состоя- ние всех битов своего операнда. Например, если целочисленное значение (храни- мое в переменной A) представляет собой двоичный код 1001 0110, то в результате операции A получим двоичный код 0110 1001.
В следующей программе демонстрируется использование оператора “НЕ” по- средством отображения некоторого числа и его дополнения до 1 в двоичном коде с помощью приведенной выше функции show_binary().
С++: руководство для начинающих
329
Еще о типах данных и операторах
7
#include
using namespace std;
void show_binary(unsigned int u);
int main()
{
unsigned u;
cout << "Введите число между 0 и 255: ";
cin >> u;
cout << "Исходное число в двоичном коде: ";
show_binary(u);
cout << "Его дополнение до единицы: ";
show_binary(u);
return 0;
}
// Отображение битов, составляющих байт.
void show_binary(unsigned int u)
{
register int t;
for(t=128; t>0; t = t/2)
if(u & t) cout << "1 ";
else cout << "0 ";
cout << "\n";
}
Вот как выглядят результаты выполнения этой программы.
Введите число между 0 и 255: 99
Исходное число в двоичном коде: 0 1 1 0 0 0 1 1
Его дополнение до единицы: 1 0 0 1 1 1 0 0
Операторы &, | и применяются непосредственно к каждому биту значения в отдельности. Поэтому поразрядные операторы нельзя использовать вместо их
330 Модуль 7. Еще о типах данных и операторах логических эквивалентов в условных выражениях. Например, если значение x равно 7, то выражение x && 8 имеет значение ИСТИНА, в то время как выраже- ние x & 8 дает значение ЛОЖЬ.
ВАЖНО!7.8. Операторы сдвига Операторы сдвига, “>>” и “<<”, сдвигают все биты в значении переменной вправо или влево. Общий формат использования оператора сдвига вправо вы- глядит так.
переменная >>
число_битовА оператор сдвига влево используется так.
переменная <<
число_битовЗдесь элемент
число_битов указывает, на сколько позиций должно быть сдви- нуто значение элемента
переменная. При каждом сдвиге влево все биты, со- ставляющие значение, сдвигаются влево на одну позицию, а в младший разряд записывается нуль. При каждом сдвиге вправо все биты сдвигаются, соответ- ственно, вправо. Если сдвигу вправо подвергается значение без знака, в старший разряд записывается нуль. Если же сдвигу
вправо подвергается значение со зна- ком, значение знакового разряда сохраняется. Как вы помните, отрицательные целые числа представляются установкой старшего разряда числа равным едини- це. Таким образом, если сдвигаемое значение отрицательно, при каждом сдвиге вправо в старший разряд записывается единица, а если положительно — нуль. Не забывайте: сдвиг, выполняемый операторами сдвига, не является циклическим, т.е. при сдвиге как вправо, так и влево крайние биты теряются, и содержимое по- терянного бита узнать невозможно.
Операторы сдвига работают только со значениями целочисленных типов, напри- мер, символами, целыми числами и длинными целыми числами (int, char, long int или short int). Они не применимы к значениям с плавающей точкой.
Побитовые операции сдвига могут оказаться весьма полезными для декоди- рования входной информации, получаемой от внешних устройств (например, цифроаналоговых преобразователей), и обработки информации о состоянии устройств. Поразрядные операторы сдвига можно также использовать для вы- полнения ускоренных операций умножения и деления целых чисел. С помощью сдвига влево можно эффективно умножать на два, сдвиг вправо позволяет не ме- нее эффективно делить на два.
Следующая программа наглядно иллюстрирует результат использования опе- раторов сдвига.
С++: руководство для начинающих
331
Еще о типах данных и операторах
7
// Демонстрация выполнения поразрядного сдвига.
#include
using namespace std;
void show_binary(unsigned int u);
int main()
{ int i=1, t;
// Сдвиг влево.
for(t=0; t < 8; t++) { show_binary(i); i = i << 1; //
← Сдвиг влево переменной i на 1 позицию.
} cout << "\n";
// Сдвиг вправо.
for(t=0; t < 8; t++) { i = i >> 1; //
← Сдвиг вправо переменной i на 1 позицию.
show_binary(i);
} return 0;
}
// Отображение битов, составляющих байт. void show_binary(unsigned int u)
{ int t; for(t=128; t>0; t=t/2) if(u & t) cout << "1 "; else cout << "0 "; cout << "\n";
}
332
Модуль 7. Еще о типах данных и операторах
Результаты выполнения этой программы таковы.
0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1
Вопросы для текущего контроля
1. Какими символами обозначаются поразрядные операторы И, ИЛИ, ис- ключающее ИЛИ и НЕ?
2. Поразрядный оператор работает на побитовой основе. Верно ли это?
3. Покажите, как выполнить сдвиг значения переменной x влево на два разряда.*
Проект_7.1.__Создание_функций_поразрядного_циклического_сдвига'>Проект 7.1.
Создание функций поразрядного
циклического сдвига
Несмотря на то что язык C++ обеспечивает два оператора сдвига, в нем не определен оператор циклического сдвига. Оператор ци-
1. Поразрядные операторы обозначаются такими символами: &, |, ^ и .
2. Верно, поразрядный оператор работает на побитовой основе.
3. x << 2
rotate.cpp
С++: руководство для начинающих
333Еще о типах данных и операторах
7клического сдвига отличается от оператора обычного сдвига тем, что бит, выдвигае- мый с одного конца, “вдвигается” в другой. Таким образом, выдвинутые биты не те- ряются, а просто перемещаются “по кругу”. Возможен циклический сдвиг как вправо, так и влево. Например, значение 1010 0000 после циклического сдвига влево на один разряд даст значение 0100 0001, а после сдвига вправо — число 0101 0000. В каждом случае бит, выдвинутый с одного конца, “вдвигается” в другой. Хотя отсутствие опе- раторов циклического сдвига может показаться упущением, на самом деле это не так, поскольку их очень легко создать на основе других поразрядных операторов.
В этом проекте предполагается создание двух функций: rrotate() и lro- tate()
, которые сдвигают байт вправо или влево. Каждая функция принима- ет два параметра и возвращает результат.
Первый параметр содержит значение, подлежащее сдвигу, второй — количество сдвигаемых разрядов. Этот проект включает ряд действий над битами и отображает возможность применения по- разрядных операторов.
Последовательность действий1. Создайте файл с именем rotate.cpp.
2. Определите функцию lrotate(), предназначенную для выполнения ци- клического сдвига влево.
// Функция циклического сдвига влево байта на n разрядов. unsigned char lrotate(unsigned char val, int n)
{ unsigned int t; t = val; for(int i=0; i < n; i++) { t = t << 1;
/* Если выдвигаемый бит (8-й разряд значения t) содержит единицу, то устанавливаем младший бит. */ if(t & 256) t = t | 1; // Устанавливаем 1 на правом конце.
} return t; // Возвращаем младшие 8 бит.
}
Проект7.1Создание функций поразрядного циклического сдвига 334 Модуль 7. Еще о типах данных и операторах
Вот как работает функция lrotate(). Ей передается значение, которое нужно сдвинуть, в параметре val, и количество разрядов, на которое нуж- но сдвинуть, в параметре n. Функция присваивает значение параметра val переменной t, которая имеет тип unsigned int. Необходимость присва- ивания unsigned char-значения переменной типа unsigned int об- условлена тем, что в этом случае мы не теряем биты, выдвинутые влево.
Дело в том, что бит, выдвинутый влево из байтового значения, просто ста- новится восьмым битом целочисленного значения (поскольку его длина больше длины байта). Значение этого бита можно затем скопировать в ну- левой бит байтового значения, тем самым выполнив циклический сдвиг.
В действительности циклический сдвиг выполняется следующим образом.
Настраивается цикл на количество итераций, равное требуемому числу сдвигов. В теле цикла значение переменной t сдвигается влево на один разряд. При этом справа будет “вдвинут” нуль. Но если значение восьмого бита результата (т.е. бита, который был выдвинут из байтового значения) равно единице, то и нулевой бит необходимо установить равным 1. В про- тивном случае нулевой бит остается равным 0.
Значение восьмого бита тестируется с использованием if-инструкции:
if(t & 256)
Значение 256 представляет собой десятичное значение, в котором установ- лен только 8-й бит. Таким образом, выражение t & 256 будет истинным лишь в случае, если в переменной t 8-й бит равен единице.
После выполнения циклического сдвига функция lrotate() возвращает значение t. Но поскольку она объявлена как возвращающая значение типа unsigned char
, то фактически вернутся только младшие 8 бит значения t.