Главная » Статьи » Програмування » C [ Добавить статью ]

RUS Уроки по программированию на языке С (Битовые операции.)

Битовые операции.

Только что мы с Вами разобрались с общей теорией, теперь пообщаемся с битовой арифметикой с точки зрения языка С.

В языке C++ существует ряд операций, выполняющихся над разрядами. Они носят название битовые операции:

  • унарная операция:
    • инверсия битов (~);
  • бинарные операции:
    • битовое "И" (&);
    • битовое "ИЛИ" (|);
    • битовое исключающее "ИЛИ" (^);
    • сдвиг влево (<<);
    • сдвиг вправо (>>);

Остановимся на данных операциях более подробно.

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

2.Битовое "И" сравнивает последовательно разряд за разрядом два операнда. Для каждого разряда результат равен 1, тогда и только тогда, когда оба соответствующих разряда операндов равны 1. Так, например,

10010011 & 00111101 = 00010001

потому что только нулевой и четвертый разряды обоих операндов содержат 1.

3. Битовое "ИЛИ" сравнивает последовательно разряд за разрядом два своих операндов. Для каждого разряда результат равен 1 тогда и только тогда, когда любой из соответствующих разрядов операндов равны 1. Так, например,

10010011 | 00111101 = 10111111

потому что все разряды (кроме шестого) в одном из двух операндов имеют значение 1.

4. Битовое исключающее "ИЛИ" сравнивает последовательно разряд за разрядом два своих операндов. Для каждого разряда результат равен 1, если один из двух (но не оба) соответствующих разрядов операндов равен 1. Так, например,

10010011 ^ 00111101 = 10101110 

Заметим, что, поскольку нулевой разряд в обоих операндах имеет значение 1, нулевой разряд результата имеет значение 0.

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

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

10001010 << 2 = 00101000 ,
(каждый разряд сдвинулся на две позиции влево).

Таким образом, х<<2 сдвигает х влево на 2 позиции, заполняя освобождающиеся позиции нулями (эквивалентно умножению на 4).

Для значений без знака имеем

10001010 >> 2 = 00100010 ,
(каждый разряд сдвинулся на две позиции).

Эти две операции выполняют сдвиг, а также эффективное умножение и деление на степени числа 2.

Пример.
#include <iostream>
using namespace std;
void main (){

 int y=02,x=03,z=01,k;

 k = x|y&z; 
 cout<<k<<" "; /* Операция 1 */

 k = x|y&~z; 
 cout<<k<<" "; /* Операция 2 */

 k = x^y&~z;
 cout<<k<<" "; /* Операция 3 */

 k = x&y&&z;
 cout<<k<<" "; /* Операция 4 */

 x = 1;
 y = -1;

 k = !x|x;
 cout<<k<<" "; /* Операции 5 */

 k = -x|x;
 cout<<k<<" "; /* Операции 6 */

 k = x^x;
 cout<<k<<" "; /* Операции 7 */

 x <<= 3;
 cout<<x<<" "; /* Операции 8 */

 y <<= 3;
 cout<<y<<" "; /* Операции 9 */

 y >>= 3;
 cout<<y<<" "; /* Операции 10 */
}
Результат работы программы: 
3 3 1 1 1 -1 0 8 -8 -1 
Примечание: Здесь мы с Вами знакомимся с еще одной системой исчисления. Целые константы, начинающиеся с цифры 0, являются восьмеричными числами. Восьмеричное представление целых чисел особенно удобно, когда приходится работать с поразрядными операциями, так как восьмеричные числа легко переводятся в двоичные. В этой задаче числа 01,02,03 соответствуют числам 1, 2 и 3, так что появление восьмеричных чисел служит намеком, что программа рассматривает значения x, y и z как последовательности двоичных цифр.
Комментарии к коду.
 Операция 1. 

 x = 03; y = 02; z = 01;
 k = x|y&z;
Вследствие приоритетов операций: k = (x|(y&z));. Самое внутреннее выражение вычисляется первым. 
 k = (x|(02&01));
 02 & 01 = 00000010 & 00000001 = 00000000 = 0
 k = (x|00);
 03 | 00 = 00000011 | 00000000 = 00000011 = 03
 03

 Операция 2. 
 x = 03; y = 02; z = 01;
 k = x|y&~z;
 k = (x|(y&(~z)));

 ~00000001 = 11111110 
 02 & 11111110 = 000000010 & 11111110 = 000000010 = 02
 03 | 02 = 00000011 | 000000010 = 00000011 = 03
 3

 Операции 3. 
 x = 03; y = 02; z = 01;
 k = x^y&~z;
 
 k = (03^02);
 1

 Операции 4. 
 x = 03; y = 02; z = 01;
 k = x&y&&z;
 
 k = ((x&y)&&z);
 k = ((03&02)&&z);
 k = (02&&z);
 k = (true&&z);
 k = (&&01);
 k = (true&&true)
 true или 1

Операции 5. 
 x = 01;
 k = !x|x;
 
 k = ((!x)|x);
 k = ((!true)|x);
 k = (false|01);
 k = (0|01);
 1

Операции 6. 
 x = 01;
 k = -x|x;

 k = ((-x)|x);
 k = (-01|01);
 -01 | 01 = 11111111 | 00000001 = 11111111 = -1
 -1


Операции 7. 
 x = 01;
 k = x^x;
 
 k = (01^01);
 0


Операции 8. 
 x = 01;
 x <<= 3;
 
 x = 01<<3;
 01 << 3 = 000000001 << 3 = 00001000 = 8
 x = 8;
 8


Операции 9. 
 y = -01;
 y <<= 3;
 
 y = -01<<3
 -01 << 3 = 11111111 << 3 = 11111000 = -8
 y = -8;
 -8


Операции 10. 
 y = -08;
 y >>= 3;
 
 y = -08>>3;
 -1


Примечание: В некоторых случаях вместо -1 может получиться другой результат (8191). Появление этого значения объясняется тем, что на некоторых компьютерах при операции сдвига знак числа может не сохраниться. Не все трансляторы языка C гарантируют, что операция сдвига арифметически корректна, поэтому в любом случае более ясным способом деления на 8 было бы явное деление y=y/8

Объединения.

Структура данных объединение подобна структуре, однако в каждый момент времени может использоваться (является активным) только один из его компонентов. Шаблон объединения может задаваться записью вида:

union
 {
 <имя типа1> <компонента1>;
 <имя типа2> <компонента2>;
 . . .
 <имя типаN> <компонентаN>;
 };

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

Доступ к компонентам объединения осуществляется тем же способом, что и к компонентам структур.

Пример.
#include <iostream>
using namespace std;

union Test
{
 int a;
 char b;

}kkk;

void main ()
{ 
 kkk.a = 65;
 cout<<kkk.a<<" "; // число 65
 cout<<kkk.b;// символ А (соответствующий этому числу)
}

В качестве более осмысленного примера объекта типа union рассмотрим объединение geom_fig[1]:

union
 {
 int radius; // Окружность. 
 int a[2]; // Прямоугольник. 
 int b[3]; // Треугольник.
 } geom_fig;

В этом примере обрабатывается только активный компонент, то есть компонент, который последним получает свое значение. Например, после присваивания значения компоненту radius не имеет смысла обращение к массиву b.

Примечание: Обратите внимание на то, что на одних компьютерах поля битов размещаются слева направо, на других - справа налево. Это значит, что при всей полезности работы с ними, если формат данных, с которыми мы имеем дело, дан нам свыше, то необходимо самым тщательным образом исследовать порядок расположения полей; программы, зависящие от такого рода вещей, не переносимы.
Выводы

Объединения применяются для:

  • минимизации используемого объема памяти, если в каждый момент времени только один объект из многих является активным;
  • интерпретации основного представления объекта одного типа, как если бы этому объекту был присвоен другой тип.

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

Категория: C | Добавил: DEN-SHP (06.11.2012)
Просмотров: 937 | Комментарии: 1 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]