Литмир - Электронная Библиотека
A
A

Получаем следующий результат:

Программирование. Принципы и практика использования C++ Исправленное издание - _324.png

Объяснение этого результата таково: число 257 на два больше, чем можно представить с помощью восьми битов (255 равно “восемь единиц”), а число 129 на два больше, чем можно представить с помощью семи битов (127 равно “семь единиц”), поэтому устанавливается знаковый бит. Кстати, эта программа демонстрирует, что тип

char
на нашем компьютере имеет знак (переменная c ведет себя как переменная
sc
и отличается от переменной
uc
).

ПОПРОБУЙТЕ

Напишите эти битовые комбинации на листке бумаги. Затем попытайтесь вычислить результат для

si=128
. После этого выполните программу и сравните свое предположение с результатом вычислений на компьютере.

Кстати, почему мы использовали функцию

print()
? Ведь мы могли бы использовать оператор вывода.

cout << i << ' ';

Однако, если бы переменная

i
имела тип
char
, мы увидели бы на экране символ, а не целое число. По этой причине, для того чтобы единообразно обрабатывать все целочисленные типы, мы определили функцию
print()
.

template<class T> void print(T i) { cout << i << '\t'; }

void print(char i) { cout << int(i) << '\t'; }

void print(signed char i) { cout << int(i) << '\t'; }

void print(unsigned char i) { cout << int(i) << '\t'; }

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Вывод: вы можете использовать целые числа без знака вместо целых чисел со знаком (включая обычную арифметику), но избегайте этого, поскольку это ненадежно и приводит к ошибкам.

• Никогда не используйте целые числа без знака просто для того, чтобы получить еще один бит точности.

• Если вам необходим один дополнительный бит, то вскоре вам потребуется еще один.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _003.png
 К сожалению, мы не можем совершенно избежать использования арифметики целых чисел без знака.

• Индексирование контейнеров в стандартной библиотеке осуществляется целыми числами без знака.

• Некоторые люди любят арифметику чисел без знака. 

25.5.4. Манипулирование битами

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Зачем вообще нужно манипулировать битами? Ведь многие из нас предпочли бы этого не делать. “Возня с битами” относится к низкому уровню и открывает возможности для ошибок, поэтому, если у нас есть альтернатива, следует использовать ее. Однако биты настолько важны и полезны, что многие программисты не могут их игнорировать. Это может звучать довольно грозным и обескураживающим предупреждением, но оно хорошо продумано. Некоторые люди действительно любят возиться с битами и байтами, поэтому следует помнить, что работа с битами иногда необходима (и даже может принести удовольствие), но ею не следует злоупотреблять. Процитируем Джона Бентли: “Люди, развлекающиеся с битами, будут биты” (“People who play with bits will be bitten”).

Итак, когда мы должны манипулировать битами? Иногда они являются естественными объектами нашей предметной области, поэтому естественными операциями в таких приложениях являются операции над битами. Примерами таких приложений являются индикаторы аппаратного обеспечения (“влаги”), низкоуровневые коммуникации (в которых мы должны извлекать значения разных типов из потока байтов), графика (в которой мы должны составлять рисунки из нескольких уровней образов) и кодирование (подробнее о нем — в следующем разделе).

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

void f(short val) // пусть число состоит из 16 битов, т.е. 2 байта

{

  unsigned char left = val>>8;    // крайний левый

                                  // (самый старший) байт

  unsigned char right = val&0xff; // крайний правый

                                  // (самый младший) байт

  // ...

  bool negative = val&0x8000;     // знаковый бит

  // ...

}

Такие операции не редкость. Они известны как “сдвиг и наложение маски” (“shift and mask”). Мы выполняем сдвиг (“shift”), используя операторы

<<
или
>>
, чтобы переместить требуемые биты вправо (в младшую часть слова), где ними легко манипулировать. Мы накладываем маску (“mask”), используя оператор “и” (
&
) вместе с битовой комбинацией (в данном случае
0xff
), чтобы исключить (установить равными нулю) биты, нежелательные в результате.

При необходимости именовать биты часто используются перечисления. Рассмотрим пример.

enum Printer_flags {

  acknowledge=1,

  paper_empty=1<<1,

  busy=1<<2,

  out_of_black=1<<3,

  out_of_color=1<<4,

  // ...

};

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

Программирование. Принципы и практика использования C++ Исправленное издание - _325.png

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

unsigned char x = out_of_color | out_of_black; // x = 24 (16+8)

x |= paper_empty; // x = 26 (24+2)

Отметим, что оператор

|=
можно прочитать как “установить бит” (или “установить некоторый бит”). Значит, оператор
&
можно прочитать как “установлен ли бит?” Рассмотрим пример.

if (x& out_of_color) { // установлен ли out_of_color? (Да, если

360
{"b":"847443","o":1}