3.7. Совместимость типов
Понятие совместимости типов было определено в п.3.5. В данном разделе мы рассмотрим различные правила совместимости типов. Существующие в языке правила совместимости типов важны, поскольку они влияют на структуру типов данных и операции, производимые над величинами этих типов. Важным следствием совместимости типов двух переменных является то, что каждой из них может быть присвоено значение другой.
Существуют две разновидности совместимости типов: совместимость имен типов и совместимость структур типов. Совместимость имен типов означает, что две переменные относятся к совместимым типам только в том случае, если они были описаны в объявлениях с одним и тем же именем типа. Совместимость структур типов означает, что две переменные имеют совместимые типы в том случае, если у их типов одинаковые структуры.
Совместимость имен типов более строга. Она легко реализуется, но весьма ограничивает программиста. При такой интерпретации переменная, принадлежащая к ограниченному типу целых чисел, не будет совместима с переменной, имеющей общий целый тип. Рассмотрим пример на языке Pascal:
type
indextype = 1. .100; {ограниченный тип}
var
count: integer;
index : indextype;
Если бы в Pascal использовалась совместимость имен типов, то переменные count и index не были бы совместимы.
Совместимость структур типов более гибка, но сложнее реализуется. При определении совместимости имен типов сравниваются только имена двух типов, а при использовании совместимости структур типов – структуры двух типов. Выполнить второе сравнение не всегда легко. (Рассмотрите, например, структуру данных, ссылающуюся на собственный тип – связный список.) При этом могут возникать дополнительные вопросы. Являются ли, например, два структурных типа совместимыми, если они имеют одинаковую структуру, но разные имена полей? Совместимы ли два одномерных массива в программе на языке Pascal или Ada, если они содержат элементы одного типа, но различаются областью значений индекса: 0..10 и 1..11?
Еще одной трудностью, связанной с совместимостью структур типов, является то, что она не признает различий между типами, имеющими одинаковую структуру. Рассмотрим следующее объявление, которое могло бы появиться в программе на языке Pascal:
type
celsius = real;
fahrenheit = real;
Переменные типов celsius и fahrenheit считаются совместимыми при проверке совместимости структур типов. Это позволяет им смешиваться в выражениях, что, очевидно, в данном случае нежелательно. Вообще, типы с различными именами, вероятнее всего, являются абстракциями различных категорий сущностей задачи, и в идеале не должны рассматриваться как эквивалентные.
В исходном определении языка Pascal явно не устанавливается, когда должна использоваться совместимость структур типов, а когда – совместимость их имен. Это вредно для мобильности программ, поскольку программа, корректная в одной системе реализации языка, может оказаться некорректной в другой. Стандарт языка Pascal, созданный Международной организацией по стандартизации, явно устанавливает правила совместимости типов для данного языка, частично – по имени, частично – по структуре. В большинстве случаев используется структура, а имя типа применяется для формальных параметров и в некоторых других ситуациях. Рассмотрим, например, следующие объявления:
type
typel = array [1..10] of integer;
type2 = array [1..10] of integer;
type3 = type2;
В этом примере типы type1 и type2 несовместимы, что свидетельствует об интерпретации совместимости имен типов. Однако тип type2 совместим с типом type3, из чего можно заключить, что эквивалентность имен типов не трактуется строго. Такая форма совместимости иногда называется эквивалентностью объявлений, поскольку при определении типа с помощью имени другого типа оба они являются совместимыми, несмотря на то, что они несовместимы по именам типов.
В языке Ada применяется совместимость имен типов, но при этом имеются две конструкции – подтипы и производные типы, которые позволяют устранить возникающие проблемы. Производным называется новый тип, основанный на некотором ранее определенном типе, с которым он несовместим, несмотря на то, что они имеют идентичную структуру. Производные типы наследуют все свойства родительских типов. Рассмотрим следующий пример:
type celsius is new FLOAT;
type fahrenheit is new FLOAT;
Переменные типов celsius и fahrenheit несовместимы, хотя и имеют идентичную структуру. Более того, переменные этих типов несовместимы ни с каким другим типом чисел с плавающей точкой. Исключением из правила являются только литеральные константы. Литеральная константа, например, 3.0 имеет тип универсальных действительных чисел и совместима с любым типом чисел с плавающей точкой. Производные типы также могут содержать ограничения диапазона родительского типа, наследуя при этом все его операции.
Подтип в языке Ada – версия существующего типа с, возможно, ограниченным диапазоном. Подтип совместим с породившим его типом. Рассмотрим следующее объявление:
subtype SMALL_TYPE is INTEGER range 0..99;
Переменные, имеющие тип SMALL_TYPE, совместимы с переменными типа INTEGER.
Правила совместимости типов в языке Ada более строги, чем соответствующие правила в тех языках, в которых широко практикуется приведение типов. Например, два операнда, входящие в операцию сложения в языке C, могут иметь практически любую комбинацию числовых типов этого языка. Один из операндов при этом приводится к типу другого. В Ada нет приведения типов операндов арифметического оператора.
В языке C применяется структурная эквивалентность для всех типов, за исключением структур (записей) и объединений, для которых используется эквивалентность объявлений. Правда, если две структуры или объединения определяются в двух различных файлах, то используется эквивалентность структур типов.
В языке C++ используется эквивалентность имен типов. При этом отметим, что оператор typedef языков C и C++ не вводит новый тип. Он просто определяет новое имя для уже существующего типа.
Во многих языках переменные могут объявляться без использования имен типа, при этом создаются безымянные типы. Рассмотрим следующий пример из языка Ada:
A : array (1..10) of INTEGER;
В этом случае переменная A имеет безымянный, но неоднозначно определенный, тип. После объявления
В : array (1..10) of INTEGER;
переменные A и B будут принадлежать к безымянным, различным и несовместимым типам, хотя они имеют идентичную структуру. Множественное объявление
С, D : array (1..10) of INTEGER;
создаст два безымянных типа: один для переменной C, другой – для переменной D, несовместимых между собой. Фактически эти объявления можно рассматривать как следующие два объявления:
С : array (1..10) of INTEGER;
D : array (1..10) of INTEGER;
Однако в объявлении
type LIST__10 is array (1..10) of INTEGER;
C, D : LIST_10;
переменные C и D будут совместимыми.
Очевидно, что в языках, не позволяющих пользователям определять и называть типы, например FORTRAN и COBOL, эквивалентность имен применяться не может.
Возникновение таких объектно-ориентированных языков как C++, Java, C# подняло вопрос о новой концепции совместимости типов – совместимости объектов и ее связи с иерархией наследования.