5.4. Изменение (приведение) типов и значений
В Турбо Паскале имеется очень мощное средство, позволяющее обойти всевозможные ограничения на совместимость типов или значений: определена операция приведения типа. Она применима только к переменным и значениям. Суть этой операции в следующем. Определяя тип, мы определяем форму хранения информации в ОЗУ, и переменная данного типа будет представлена в памяти заранее известной структурой. Но если «взглянуть» на ее образ в памяти с точки зрения машинного представления другого типа, то можно будет трактовать то же самое значение как принадлежащее другому типу. Для этого достаточно использовать конструкцию
ИмяТипа( ПеременнаяИлиЗначение )
Задаваемое имя типа, в который происходит преобразование, должно быть известно в программе. Примеры приведения типов:
- 90 -
| TYPE
| Arr4Byte = Array[1..4] of Byte; { массив из 4-х байтов }
| Arr2Word = Array[1..2] of Word; { массив из двух слов }
| RecType = RECORD
| Word1, Word2 : Word { запись из двух слов }
| END;
| VAR
| L : LongInt; { четырехбайтовое целое со знаком }
| S : ShortInt; { однобайтовое целое со знаком }
| В : Byte; { однобайтовое целое без знака }
| W : Word; { двухбайтовое целое без знака }
| a4 : Arr4Byte; { массив из четырех байтов }
| a2 : Arr2Word; { массив из двух слов по два байта }
| Rec : RecType; { запись из двух слов по два байта }
| BEGIN
| L := 123456; { некое значение переменной L }
| S := -2; { некое значение переменной S }
| a2 := arr2Word( L ); { два слова L перешли в массив a2 }
| a4 := arr4Byte( L ); {четыре байта L перешли в a4 }
| W := RecType( L ).Word1; { доступ к L по частям
| W := arr2Word( L )[ 1 ];
| RecType(L).Word1 := 0; { обнуление первого слова в L }
| B := Byte( S ); { если S=-2, то B станет 254 }
| B := Arr4Byte( a2 )[1]; { запись в B значения первого }
{полуслова массива a2 }
| END.
Приведение типов не переопределяет типы переменных. Оно лишь дает возможность нарушить правила совмещения типов при условии, что соответствующие значения совместимы в машинном представлении. При преобразовании типа переменной ее размер всегда должен быть равен размеру типа, к которому приводится значение. Если после приведения переменной к структуре мы хотим воспользоваться отдельным ее элементом, то просто приписываем индекс (для массивов) или поле (для записей). Вообще говоря, конструкцию ИмяТипа( Переменная ) можно условно считать именем некой необъявленной переменной типа «ИмяТипа» и со значением, хранимым в «Переменной». Приведение типа переменной может стоять как слева, так и справа, т.е. может участвовать в выражениях. Допускается вложенность преобразований при условии сохранения размеров. Как видно из примера, можно изменять определенные байты общей структуры переменной в памяти независимо от ее типа.
- 91 -
Аналогично изменению типа переменных можно изменять тип значений как таковых, а также результирующий тип выражений, т.е. разрешены такие преобразования:
Integer( 'Y' ) { код символа 'Y' в формате Integer }
Boolean( 1 ) { это логическое значение True }
LongInt( 1 ) { значение 1, размещенное в четырех байтах}
Char ( 130-1 ) { символ с кодом ASCII номер 129 }
В этом случае происходит трактование значения в скобках как значения другого типа. Например, символ 'Y' хранится как код 89 и занимает один байт. Но конструкция Integer ('Y') представляет собой значение 89 в формате целого числа со знаком (в двух байтах). Как видно, при преобразовании типа значений соблюдение размера уже не нужно. Новый тип может быть шире, а может быть и короче, чем естественный тип значения.
При приведении значения в более широкий тип (например, LongInt(1)) значения будут целиком записаны в младшие (наименее значащие) байты, что сохранит само значение. В противоположном случае, когда значение приводится к более короткому типу, от него берутся уже не все, а лишь самые младшие байты. При этом старшие байты игнорируются, и приведенное значение не равно исходному. Например, выражение Byte( 534 ) равно 22, поскольку значение 534 кодируется в тип Word и раскладывается на младший и старший байты как 22 + 2*256. Младший байт (22) мы получим, а старший (со значением 2) проигнорируем.
Преобразование типа значения внешне похоже на преобразование типа переменной. Но эффект от него несколько иной (за счет возможности изменения размера), и ограничения на применение другие. В частности, как задаваемый тип, так и тип значения (выражения) должны быть перечислимыми (это все целочисленные типы, тип Char, Boolean и вводимые перечисления) или адресными (адресные значения хранятся как значения LongInt). По понятным причинам преобразование значения не может появиться в левой части присваивания.
Если исходное значение отрицательно, а задаваемый тип расширяет размер его хранения, то знак будет сохраняться. При уменьшении размера этого может и не быть.
Приведение типов как переменных, так и значений — нетривиальная операция. Она подразумевает достаточно высокий уровень знаний технических подробностей языка. Например, нужно знать, как хранятся структуры (массивы, записи, множества), адреса, числа, строки в памяти, какие размеры им отводятся. Приведение
- 92 -
типов имеет смысл лишь при сопоставимости машинных представлений значений. Так, вещественные значения кодируются совсем по-другому, чем целые, и приведение целых переменных к вещественным типам или наоборот, если и пройдет по размеру, то приведет к фатальным изменениям смысла.
В следующих частях книги будут рассматриваться вопросы хранения данных в памяти. Кроме того, во многих примерах будет использоваться приведение типов.
- 93 -