типы Type1 и Type2 не будут идентичными, поскольку конструкции Array...of..., хотя и одинаковы, но не являются идентификаторами, т.е. обособленными именами. Переменные типов Type1 и Type2 не смогут в последнем случае обмениваться значениями.
2. Типы Type1 и Type2 описаны как эквивалентные. Это означает, что, например, при описании
| TYPE
| Type1 = Array [1..2] of Boolean;
| Type2 = Type1;
| Type3 = Type2;
значения типов Type1, Type2 и Type3 будут полностью совместимы. Аналогичная картина возникает и при объявлении переменных. Если переменные причислены к одному и тому же типу
VAR
x1, x2, xЗ : Type1;
то они совместимы. Если Type1 - идентификатор типа, а не конструкция, то совместимость сохранится и при объявлении вида
| VAR
| x1 : Type1;
| x2 : Type1;
| x3 : Type2;
Здесь Type2 идентичен типу Type1, но будут несовместимы переменные x1 и x2:
| VAR
| x1 : Array [1..2] of Real;
| x2 : Array [1..2] of Real;
Ограничения на совместимость только по идентичным типам было бы слишком жестким. Поэтому совместимость в Турбо Паскале трактуется несколько шире. Так, типы считаются совместимыми, если:
- оба типа являются одинаковыми;
- оба типа являются вещественными типами;
- оба типа являются целочисленными;
- 87 -
- один тип является поддиапазоном другого;
- оба типа являются поддиапазонами одного и того же базового типа;
- оба типа являются множественными типами с совместимыми базовыми типами;
- один тип является строковым, а другой тип - строковым или символьным типом;
- один тип является указателем (Pointer), а другой - указателем или ссылкой.
Совместимость, в описанном выше смысле, гарантирует работоспособность операций присваивания. Кроме того, что очень важно, она определяет правила подстановки значений или переменных в вызовы процедур и функций.
Существует еще один вид совместимости: совместимость по присваиванию, т.е. правила присваивания значения V2 (собственно значение, переменная или выражение) переменной V1. Они действительны только для операций присваивания и являются немногим более широкими, чем правила совместимости по типам. Значение V2 типа Type1 может быть присвоено переменной V1 типа Type2, если выполняется одно из условий:
1. Type1 и Type2 - тождественные типы, и ни один из них не является файловым типом или структурным типом, содержащим компонент с файловым типом.
2. Type1 и Type2 - совместимые перечислимые типы, и значения типа Type2 попадают в диапазон возможных значений Type1.
3. Type1 и Type2 - вещественные типы, и значения типа Type2 попадают в диапазон возможных значений Typel.
4. Typel - вещественный тип, а Type2 - целочисленный тип.
5. Type1 и Type2 - строковые типы.
6. Type1 - строковый тип, а Type2 - символьный тип.
7. Type1 и Type2 - совместимые множественные типы, и все члены значения множества типа Type2 попадают в диапазон возможных значений Type1.
8. Type1 и Type2 - совместимые адресные типы.
9 Тип объекта Type2 совместим по присваиванию с типом объекта Type1, если Type2 находится в области типа объекта Type1.
10. Тип ссылки Ptr2, указывающий на тип объекта Type2, совместим по присваиванию с типом ссылки Ptr1, указывающим на тип объекта Type1, если Type2 находится в области типа объекта Type1.
- 88 -
Последние два правила, относящиеся к данным типа "объект", не слишком очевидны. Более подробное их описание приводится в гл. 13 "Объектно-ориентированное программирование".
Нарушение правил совместимости типов и значений обнаруживается, как правило, на этапе компиляции программы.
С вопросом совместимости очень тесно связан вопрос о типе результатов арифметических выражений. Например, можно ли заранее сказать, какой будет тип у результата выражения справа?
| VAR
| B :Byte;
| W : Word;
| I : Integer;
| R : Real;
...
| R := В*I+W;
На этот счет существуют четкие правила внутреннего преобразования типов значений - участников операций:
1. В случае бинарной операции, использующей два операнда, оба операнда преобразуются к их общему типу перед тем, как над ними совершается действие. Общим типом является встроенный целочисленный тип с наименьшим диапазоном, включающим все возможные значения обоих типов. Например, общим типом для целого и целого длиной в байт является целое, а общим типом для целого и целого длиной в слово является длинное целое. Действие выполняется в соответствии с точностью общего типа, и типом результата является общий тип. Если же один из операндов - вещественный, а второй - целочисленный, то результатом операции может быть только значение вещественного типа.
2. Выражение справа в операторе присваивания вычисляется независимо от размера переменной слева.
Если результат выражения "не вписывается" в тип переменной слева от знака ":=", то может возникнуть ошибка переполнения. В случае вещественной переменной слева при переполнении возникнет ошибка счета 205 (Floating Point overflow). Но если слева стоит целочисленная переменная, то при режиме компиляции {$R+} возникнет ошибка нарушения диапазона 201 (Range Check Error), а при {$R-} программа не прервется, но значение в переменной будет "обрезано" ее диапазоном и перестанет соответствовать выражению справа. Последний случай чреват труднодиагностируемыми ошибками в результатах счета программы.
- 89 -
Другим примером опасности может стать вывод значений выражений оператором Write (или в общем случае подстановка выражений в вызовы процедур или функций):
| VAR
| A, B : Word;
| BEGIN
| A:= 55000;
| B:= A-256;
| Write(A+B);
| END.
Эта программа должна вычислить значение A+B и вывести его на экран. Но она этого не сделает. В режиме компиляции {$R+} запуск программы даст ошибку 201, поскольку общий для A и B тип Word не вмещает их сумму (его "потолок" равен 65535). В режиме {$R-} программа напечатает заведомую ложь.
Выход из подобных ситуаций прост. Надо объявлять хотя бы одного участника выражения более длинным (емким) типом. Так, если описать A как LongInt, то общим для A и B типом станет LongInt, и в нем уместится достаточно большое значение суммы. Можно даже просто, не изменяя объявления переменной, переписать последний оператор в виде
Write(LongInt(A) + B);
используя описываемое ниже приведение типа значения.
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.