Как мы выяснили в предыдущей части, отличия типов не имеют принципиального значения, пока они все одного размера (в байтах).
Преобразования из символов в числа, из чисел в символы, из положительных в отрицательные знаки происходят скорее в нашем воображении и на дисплее, чем на самом деле.
Но надо включать осторожность, когда типы разной длины используются вместе, или когда результат математической операции оказывается не того размера.
Например:
char a = 128;
char b = 128;
char c = a + b;
Хотя переменные a и b без проблем могут хранить в себе 128, получившийся результат 256 – это число больше байта. Поэтому оно не поместится в переменную с, как его ни крути – хоть в положительном виде, хоть в отрицательном, никак. Приведет ли это к ошибке? Не совсем.
так как 256 это уже 9 бит:
100000000
а в char помещается только 8 бит, то из результата будут взяты только младшие 8 бит и записаны в переменную c:
00000000
То есть получится банальный ноль.
Случилось переполнение, которое привело к обнулению. Нормально ли это? Абсолютно нормально. Более того, такая техника используется достаточно широко в самых разных алгоритмах.
Например, если мы хотим, чтобы некий счетчик обнулялся каждый 256-й раз, то... ничего не надо делать! Надо просто увеличивать его на 1, а обнуляться он будет самостоятельно. При условии, конечно, что его длина – 1 байт.
Хорошо, ну а что делать, чтобы всё-таки записать 256 в переменную без переполнения? Надо просто взять переменную более длинного типа:
int c = a + b;
Теперь переменная длиной два байта, и число 256 в неё отлично поместится:
0000000100000000
А откуда мы знаем, когда будет переполнение, а когда не будет? А ниоткуда. Просто мы планируем заранее: вот эта переменная может быть больше чем... значит её размер должен быть...
Если мы плохо спланировали, значит можем получить где-то незапланированное переполнение, и соответственно ошибку.
Ну и не стоит чахнуть над каждым байтом. Переменные проще создавать двухбайтными или даже четырехбайтными, пусть и храните вы там маленькие числа. Выгадывать надо тогда, когда у вас будет какой-то массив на тысячи элементов. А несколько штук переменных никакой погоды не сделают.
Но иногда бывает нужно оперировать именно байтами. И возникают такие ситуации, когда "длинной" переменной нужно присвоить "короткую", и наоборот. Посмотрим на случай 1:
char a = 127;
int b = a;
Мы присвоили переменной b содержимое переменной a. Несмотря на то, что длина b – 2 байта, а длина a – 1 байт, это произошло без всяких хлопот. При преобразовании коротких типов в длинные их длина автоматически добивается нулевыми байтами слева.
Теперь посмотрим обратный пример:
int b = 999;
char a = b;
Здесь транслятор уже откровенно ругнется. Он скажет – "Алло, ты берешь 2 байта и пытаешься засунуть их в один байт. Ты в курсе, что потеряешь один байт?" А собственно почему бы его и не потерять? Вы можете так сделать, когда уверены, что старший байт пустой, или когда он вам попросту не нужен. А чтобы транслятор не ругался, вы можете написать специально для него, что вас интересует именно один байт из b, а не два:
char a = (char) b;
Указав (char) перед b, вы попросили транслятор считать, что переменная b в этой операции будет типа char, и значит второй (старший) байт из памяти брать не нужно.
Отдельно нужно рассмотреть то, как расширяются отрицательные числа:
char a = -1;
int b = a;
Если распечатать a и b, то обе этих переменные будут равны -1, что и логично. Но на битовом уровне есть нюанс. Как мы выяснили в предыдущей части, байт со значением -1 это то же самое, что байт со значением 255. И вот мы присвоили b = a, получили a = -1, и b = -1, значит можно также считать, что а = 255 и b = 255? А вот уже нет. Так как значение переменной a в момент присваивания расширялось до 16 бит, то слева к нему был добавлен ещё один байт, но не 00000000, а 11111111, иначе потерялся бы знак. И переменная b теперь равна 1111111111111111. С одной стороны это по-прежнему -1, а с другой стороны это уже не 255, а 65535.
А вот если бы мы объявили a как unsigned char, то при расширении она бы трактовалась как положительное число и тогда добивалась бы уже нулями. И переменная b стала бы равна 0000000011111111, то есть 255. Но тогда она уже не стала бы равна -1.
Помните про это, когда хулиганите с типами.
Что ж, с численными типами наконец-то покончено. Остались указатели. Указатели – это очень мощные и интересные штуки, которым будет посвящен следующий выпуск.