Начало здесь, и продолжаю без дальнейших вступлений.
Я освещаю типы языка C в качестве основы, из которой растут типы других языков. Возвращаясь к типам char, int и long, нужно отметить, что у них бывает ещё модификатор unsigned, то есть"без знака".
- unsigned char – 1 байт без знака
- unsigned int – 2 байта без знака
- unsigned long – 4 байта без знака.
Что это такое? Числа, с которыми мы работаем в математике, бывают положительные и отрицательные. Отрицательные, как известно, пишутся со знаком "минус". Так вот с типом unsigned число может быть только положительным. Зачем это надо?
Числа хранятся в виде двоичной комбинации бит. Сами по себе биты не могут быть отрицательными или положительными. Поэтому один бит специально выделяется для хранения знака. Если этот бит равен 0, то знака нет (число положительное), а если он равен 1, то знак есть (число отрицательное).
Получается, что само число укорачивается. Если его длина 1 байт, то 1 бит тратится на знак, и 7 бит – на само число. Если число длиной 2 байта, то 1 бит на знак и 15 бит на число, и т.д.
Если мы точно знаем, что наше число не может быть отрицательным, например это возраст человека, то мы можем не тратить 1 бит на хранение знака, а пустить на хранение числа все биты. Для этого мы укажем тип как unsigned, чтобы транслятор об этом знал. Тогда максимальное число станет больше. Для сравнения, максимальные числа со знаком:
- 1 байт: 127
- 2 байта: 32767
И без знака:
- 1 байт: 255
- 2 байта: 65535
Вот, казалось бы, точно разные типы? И опять нет. И опять важна только их длина в байтах. Мы запросто можем написать вот так:
char a = 255;
unsigned char b = -1;
Но если char это максимум 127, почему можно записать туда 255? И если unsigned char это число без знака, то почему можно записать туда -1?
Здесь мы наблюдаем какой-то трюк, похожий на карточный фокус. Давайте разберем его на примере байта, в который записано число -1.
Само число 1 занимает 7 бит в виде 0000001, и 8-й (старший, то есть самый левый) бит тратится на знак:
10000001
Но в реальности число -1 хранится не так. Связано это с математикой. Если мы к -1 прибавим 1, то должны получить 0. Однако в таком виде мы получим не 0, а -2:
10000001 + 0000001 = 10000010
Поэтому для хранения отрицательных чисел используется специальный формат, называемый "комплементарным", или "дополнительным". Пока его можно не понимать, а просто запомнить: чтобы получить двоичное отрицательное число, нужно взять положительное число, инвертировать его биты и прибавить 1. Всё. Конечно, это делает транслятор языка, а не мы сами.
- Берем 1: 00000001
- Инвертируем биты: 11111110
- Прибавляем 1: 11111111
Вот мы и получили правильный формат числа -1.
Но ведь 11111111 – это то же самое, что число без знака 255? Да, именно так.
Потрясающее открытие: в памяти компьютера любое отрицательное число ОДНОВРЕМЕННО является каким-то положительным числом. С точки зрения компьютера, -1 = 255! Это число Шрёдингера, если вы понимаете, о чём я.
И мы реально можем этим пользоваться. Мы можем написать как хотим, невзирая на типы (но соблюдая размер в байтах):
char a = 255;
unsigned char b = -1;
Ошибки не происходит, потому что не нарушается размер типа (1 байт), а транслятор знает, что -1 и 255 это одно и то же. В памяти получилось двоичное значение 11111111, вот и всё.
Но тогда вопрос, а нафига нужны типы со знаком и без, если это никакой роли не играет? Ну, на самом деле играет, просто надо знать, в каких случаях. Первый случай – это когда число показывается нам, людям. Ведь мы ожидаем его увидеть именно со знаком или без, смотря как мы его замыслили. Вот, к примеру, код, который печатает на экране значения переменных a, b, c:
int c = 'AB';
char a = 255;
unsigned char b = -1;
printf("c=%d, a=%d, b=%d\n", c, a, b);
Я, казалось бы, нарушил все законы в отношении типов. Что будет напечатано на экране? А вот что:
c=16706, a=-1, b=255
- Я присвоил 2-байтовой целочисленной переменной символы 'AB', а напечаталось число 16706. Потому что 2 символа трактуются как 2-байтовое целое число.
- Я присвоил переменной со знаком число 255, а напечаталось -1. Потому что в данном случае тип переменной играет роль, и число 255 трактуется как -1.
- Я присвоил переменной без знака число -1, а напечаталось 255. Потому что и тут тип переменной играет роль, и число -1 трактуется как 255.
Так что указанные нами типы работают так, как должны, при выводе чисел на экран.
Второй случай – это преобразование типов, то есть изменение их размера. Об этом поговорим в следующей части.