Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рацеев С.М. Программирование на языке Си

.pdf
Скачиваний:
555
Добавлен:
23.03.2016
Размер:
1.77 Mб
Скачать

00011011 << 3 = 11011000

(27

<< 3 = 216),

00011011 << 4 = 10110000

(27

<< 4 = 176),

00011011 << 5 = 01100000

(27

<< 5

= 96),

00011011 >> 1 = 00001101

(27

>> 1

= 13),

00011011 >> 2 = 00000110

(27

>> 2

= 6).

Если операция сдвига вправо (>>) применяется к переменным знакового типа (signed), то освободившиеся разряды заполняются единицами. Например, для 8-разрядного знакового числа (char) -27

имеют место быть такие равенства:

 

 

 

11100101 << 1 = 11001010

(-27 << 1

= -54),

11100101 << 2 = 10010100

(-27

<< 2

= -108),

11100101 << 3 = 00101000

(-27

<< 3

= 40),

11100101 >> 1 = 11110010

(-27

>> 1

= -14),

11100101 >> 2 = 11111001

(-27

>> 2

= -7).

Заметим, что сдвиг влево (вправо) соответствует умножению (делению) левого операнда на степень числа 2, равную значению второго операнда.

Операция ~ является одноместной, она изменяет каждый бит целого числа на обратный. Операции &, | и ^ – двуместные; операнды этих операций – целые величины одинаковой длины. Операции выполняются попарно над всеми двоичными разрядами операндов.

Определение битовых операций:

Бит левого

Бит правого

~x

x & y

x | y

x ^ y

операнда (x)

операнда (y)

 

 

 

 

0

0

1

0

0

0

0

1

1

0

1

1

1

0

0

0

1

1

1

1

0

1

1

0

Например:

Операция ~:

Операция &:

Операция |:

Операция ^:

~01010101

01010101 &

01010101 |

01010101 ^

 

00000001

00101011

01011010

10101010

00000001

01111111

00001111

201

Приведем также некоторые формулы с использованием операций &, | и ^, которые нам понадобятся в дальнейшем:

x | 1 = 1, x | 0 = x,

x & 1 = x, x & 0 = 0, x ^ 1 = ~x, x ^ 0 = x.

12.2. Примеры с использованием битовых операций

Пример 1. Написать функцию, которая печатает двоичное представление целого числа a.

Функция DisplayBits получает в качестве параметра число a, которое требуется представить в двоичном виде, и количество занимаемых данным числом бит равно n.

#include <stdio.h>

void DisplayBits(int a, int n)

{

int i, bit;

for (i = n - 1; i >= 0; i--)

{

/* выделяем i-й справа бит */ bit = (a >> i) & 1;

printf("%d ", bit);

}

}

int main( )

{

int a; scanf("%d", &a);

DisplayBits(a, sizeof(int)*8); return 0;

}

202

Чтобы вывести двоичное представление неотрицательного целого числа, можно использовать рекурсию:

#include <stdio.h>

void Print(unsigned int a)

{

if (a)

{

Print(a >> 1); printf("%u ", a&1);

}

}

int main( )

{

unsigned int a; scanf("%u", &a); Print(a);

return 0;

}

Пример 2. Пусть имеется целое число a. Требуется i-й справа бит в числе a установить в значение 1, не меняя значения других битов.

Обозначим через b целое число, содержащее 1 в i-м бите и 0 – в остальных. Его можно получить так: b = 1 << i. Тогда требуемое число получается таким образом: a | b.

a |= 1 << i;

Пример 3. Обобщим предыдущую задачу. Пусть имеется целое число a. Требуется k подряд идущих бит, начиная с i-й справа позиции, установить в значение 1, не меняя значения других битов.

Рассмотрим число b=(0…01…1)2, заданное в двоичном виде, в котором сначала следуют только нулевые биты, а затем следуют

ровно k единичных бит. Учитывая формулу

1 + 2 + 22 + 2k-1 = 2k – 1,

203

которая верна для любого натурального k, число b равно значению 2k – 1, что на языке битовых операций означает такое представление:

b = (1 << k) – 1.

Для решения данной задачи достаточно сдвинуть все биты в числе b на i позиций влево и применить операцию побитового ИЛИ к полученному числу и числу a:

a | ((1 << k – 1) << i).

В итоге, получаем такое выражение: a |= (1 << k – 1) << i.

Пример 4. Пусть имеется целое число a. Требуется i-й справа бит в числе a установить в значение 0, не меняя значения других битов.

Рассмотрим целое число b, содержащее 0 в i-м бите и 1 – в остальных: b = ~(1 << i). Тогда требуемое число получается так: a & b.

a &= ~(1 << i);

Пример 5. В целом числе a требуется инвертировать i-й справа бит, не меняя значения других битов.

a ^= (1 << i);

Пример 6. Даны две переменные x и y целого типа. Требуется i-й справа бит переменной x поставить на j-ю справа позицию переменной y, не изменяя при этом остальные биты в y.

Сначала обнулим все биты в переменной x, кроме i-го справа бита xi, который поставим на j-ю справа позицию в x:

((x >> i) & 1) << j.

Затем обнулим j-й справа бит в переменной y, не изменяя значения остальных битов:

y & ~(1 << j).

Для решения задачи осталось применить побитовую операцию ИЛИ к предыдущим двум выражениям:

y = ((x >> i) & 1) << j | y & ~(1 << j).

204

Пример 7. Найти количество единичных битов в целом неотрицательном числе a.

Функция BitCount каждый раз проверяет состояние младшего бита, постепенно сдвигая все биты на одну позицию вправо, и так до тех пор, пока не будут проверены все n битов целого числа.

short BitCount(unsigned int a, short n)

{

short count; int i;

count = 0;

for (i = 0; i < n; i++)

{

if (a & 1) count++;

a >>= 1;

}

return count;

}

Функцию BitCount можно немного оптимизировать, учитывая то обстоятельство, что если после очередного сдвига всех битов на одну позицию вправо число a становится нулевым, то в нем нет более единичных битов. Поэтому нет необходимости всегда просматривать все n битов, а лишь до тех пор, пока не будет учтен самый старший (левый) единичный бит.

short BitCount2(unsigned int a)

{

short count; count = 0; while (a != 0)

{

if (a & 1) count++;

a >>= 1;

205

}

return count;

}

Последнюю функцию можно компактно записать с помощью рекурсивной функции:

int Count (unsigned int a)

{

return a ? (a & 1) + Count(a >> 1) : 0;

}

Пример 8. Дано целое число n > 0, являющееся некоторой степенью числа 2: n = 2k. Найти целое число k – показатель этой степени.

int Degree(unsigned int n)

{

int k; k = -1;

while (n)

{

n >>= 1; k++;

}

return k;

}

Пример 9. Пусть имеется некоторое натуральное число n. Проверить, является ли оно степенью числа 2, то есть найдется ли такое целое неотрицательное число k, что n = 2k.

int Deg_of_2(unsigned int n)

{

return (n & (n-1)) == 0;

}

206

Пример 10 (алгоритм быстрого возведения в степень).

Пусть требуется вычислить an, где a и n – некоторые целые числа, причем n ≥ 0. Рассмотрим такие степени числа a:

a, a2 , a4 , a8 , ..., a2t ,

где t = [log2 n]. Заметим, что каждое число из указанной последовательности получается путем умножения предыдущего числа самого на себя. Представим число n в виде такого разложения:

n nt 2t nt 1 2t 1 ... n1 2 n0 ,

где n0, n1, …, nt – числа из множества {0, 1}. Тогда число an может быть вычислено таким образом:

an an0 an1 2an2 4... ant 1 2t 1 ant 2t .

Поэтому количество операций умножения при вычислении an по данному методу не превосходит 2 log2 n.

long Degree(long a, unsigned long n)

{

 

long deg,

/* степени числа a: a, a2, a4, a8,… */

rez;

/* результат возведения в степень */

rez = 1;

 

deg = a;

 

while (n != 0)

{

if (n & 1) rez *= deg;

deg *= deg; n >>= 1;

}

return rez;

}

Если записать данную функцию с помощью рекурсии, то получится такая компактная функция:

long Deg(long a, unsigned long n)

{

return n ? ((n & 1) ? a*Deg(a*a, n >> 1) : Deg(a*a, n >> 1)) : 1;

207

}

Пример 11. Требуется в целочисленной переменной a, занимающей не менее два байта памяти, поменять местами последний (младший) и предпоследний байты.

a = (a & ~0xFFFF) | ((a & 0xFF) << 8) | ((a >> 8) & 0xFF);

Пример 12. Пусть имеется массив целых чисел a размера n, состоящий из попарно различных элементов. Вывести на экран всевозможные подмножества элементов, входящие в состав массива a.

Для написания алгоритма решения данной задачи заметим следующее. Пусть A = {a1, …, an} – некоторое множество. Тогда любому подмножеству B множества A взаимно однозначно соответствует двоичный вектор vB = (v1, …, vn), в котором vi =1 тогда и только тогда, когда ai принадлежит множеству B. Всего таких различных двоичных векторов длины n ровно 2n. Все данные двоичные векторы длины n суть двоичная запись чисел от 0 до 2n-1. Поэтому алгоритм получается следующим.

#include<stdio.h>

void Print(int *a, int size, int v)

{

int i;

for (i = 0; i < size; i++)

{

if (v & 1) printf("%d ", a[i]);

v >>= 1;

}

printf("\n");

}

void Subsets(int *a, int size)

{

208

int i, n;

n = 1 << size;

for(i = 1; i < n; i++) Print(a, size, i);

}

int main()

{

int a[5] = {1, 2, 3, 4, 5}; Subsets(a, 5);

return 0;

}

Пример 13. Заметим, что операцию ИСКЛЮЧАЮЩЕЕ ИЛИ

(^) можно рассматривать как сложение по модулю 2 в кольце вычетов Z2. Поэтому верно тождество x^x=0 для любого x из Z2. Поскольку операция ^ является побитовой, то тождество x^x=0 верно также для любой целочисленной переменной x. Исходя из данного факта, построим алгоритм, который меняет местами значения двух целочисленных переменных без использования буферной переменной.

int x = 1, y = 2;

x ^= y;

/* x = x^y */

y ^= x;

/* y = x^y = (x^y)^y =x */

x ^= y;

/* x = x^y = (x^y)^x =y */

/* теперь x=2, y=1 */

При этом все операции можно записать в одну строчку:

x ^= y ^= x ^= y;

12.3.Задачи

1.Найти количество нулевых и единичных битов в целом неотрицательном числе a.

209

2.Пусть имеется массив a размером n ≤ 32, состоящий из нулей и единиц. Требуется записать значения элементов массива a в переменную типа unsigned long в виде последовательности би-

тов a[n-1] … a[1]a[0].

3.Написать функцию проверки на четность целых чисел, используя только битовые операции.

4.Пусть дано целое число a. Вывести вначале значения его битов с четными индексами, а затем – с нечетными.

5.Пусть дано целое число a. Проверить, чередуются ли в нем единичные и нулевые биты.

6.В целом числе a сдвинуть все биты влево на k позиций, заменив освободившиеся биты единицами.

7.Осуществить циклический сдвиг битов в целом числе a на k позиций влево.

8.Осуществить циклический сдвиг битов в целом числе a на k позиций вправо.

9.Найти номер первого (последнего) справа единичного бита в целом числе a.

10.Найти номер первого (последнего) справа нулевого бита в целом числе a.

11.Определить, расположены ли биты в целом числе a в порядке возрастания, то есть представимо ли данное число в виде 2n – 1 для некоторого n.

12.Переставить биты в целом числе в обратном порядке.

13.Пусть имеется целое число a > 0. Записать в строковую переменную восьмеричное представление данного числа, используя следующее правило: двоичное представление числа a разбивается справа налево на триады (тройки цифр), и каждая триада заменяется восьмеричной цифрой.

14.Пусть имеется целое число a > 0. Записать в строковую переменную шестнадцатеричное представление данного числа, используя правило из предыдущей задачи, разбивая двоичное представление числа на тетрады (четверки цифр).

15.Написать функцию

unsigned long Perest(unsigned long x, unsigned char *pi, char n),

которая переставляет биты в числе x в соответствии с переста-

210