- •Лекция № 11 (2 часа)
- •1.3. Основные понятия языков программирования
- •1.3.1. Объекты данных
- •1.3.3. Выражения и операторы
- •1.3.4. Блоки и подпрограммы
- •1.3.5. Абстрактные типы данных
- •1.3.6. Дополнительные возможности
- •2. Средства описания данных
- •2.1. Типизация языка
- •2.1.1. Определение типа
- •2.1.2. Способы контроля типов
- •2.1.3. Виды и уровни типизации
- •2.1.4. Эквивалентность типов
- •2.1.5. Поколения языков
- •2.2. Простые типы данных
- •2.2.1. Числовые типы
- •2.2.2. Перечислимые типы
- •2.2.3. Контроль типов
- •2.3. Структурные типы данных
- •2.4. Динамические структуры данных
- •3. Средства описания действий
- •3.1. Определение семантики средств описания действий
- •3.2. Выражения и операторы действия
- •3.3. Операторы управления
- •3.3.1. Оператор последовательного выполнения
- •3.3.2. Условные операторы
- •3.3.3. Операторы цикла
2.1.3. Виды и уровни типизации
Приведем теперь классификацию языков программирования по способу определения семантики языковых конструкций.
Если семантика каждой языковой конструкции (например, операций) определяется по тексту программы, а не во время ее выполнения, т.е. статически, язык называется статически типизированным, в противном случае -- динамически типизированным. Не надо путать статическую и динамическую типизацию со статическим и динамическим контролем типов:
статически типизированные языки чаще всем являются языками со смешанным контролем типов (ПЛ/1, Паскаль, Ада).
Однако чем меньше язык использует динамическую типизацию (т.е. чем он более статически типизирован), тем больше возможностей для статического контроля типов и вообще для повышения эффективности языка. Поэтому большинство языков программирования статически типизированы.
Статически типизированные языки классифицируются по трем уровням типизации, отвечающим различным требованиям к их надежности:
слабо типизированный, если информация о типе используется только для обеспечения корректности программы на машинном уровне;
сильно типизированный, если осуществляется полный контроль типов (статический, динамический или смешанный);
защитно типизированный, если программы, содержащие конструкции с возможными типовыми ошибками, считаются недопустимыми, даже если эти ошибки никогда не могут возникнуть (в этом отличие от сильно типизированных языков).
Рассмотрим проблемы, возникающие для языков того или иного уровня типизации.
В слабо типизированных языках (ПЛ/1, Алгол-68, Си т.д.) операция, которая может восприниматься машиной как корректная, может быть некорректной на абстрактном уровне программы. Например, символьная переменная
С: character;
используется в следующем операторе присваивания:
С:=4;
Слабо типизированный язык вполне может допустить этот оператор: на машинном уровне символы представлены целыми числами в диапазоне от О до 255. Следовательно, для ЭВМ это корректно. Однако на абстрактном уровне программы это не так.
Возможно, именно это программист имел в виду, но гораздо более вероятно, что он допустил ошибку, намереваясь написать
С:=' 4';
При работе с разными типами для сохранения корректности в слабо типизированных языках предусмотрено выполнение операций преобразования типа. Например, если встретится описание
X, Y: real;
I, J, K: integer;
то присваивание
I:=X;
потребует преобразования вещественного объекта Х перед его присваиванием объекту I из его внутреннего представления с плавающей точкой в целое представление. Разумеется, эта операция приведет к ошибке, если значение Х выходит из реализуемой области целых чисел, но предупреждения об этом в программе нет. Еще одна проблема, связанная с неявным преобразованием типа, состоит в необходимости совершенного знания языка для точном определения порядка и правил преобразований типов, выполняемых в произвольно составленных выражениях. Например, непонятно, какую операцию преобразования нужно выполнять сначала в операторе присваивания
К:=Х-J;
Нужно ли сразу Х преобразовывать в целый тип или сначала J в вещественный тип, а затем Х-У обратно в целый? Заметим, что если Х превышает максимальное целое значение, то первое приведет к ошибке, а во втором случае этого может и не произойти. Другой пример связан с тем, что в силу правил неявных преобразований некорректная программа может иметь законную, но не очевидную интерпретацию, и поэтому ее трудно отлаживать. Например, в ПЛ/1 каждое из выражений 1<2<3 и 3<2<1 законно и дает результат "истина" из-за преобразований между целыми и логическими значениями. Еще один недостаток неявных преобразований состоит в том, что программист может не знать о связанных с преобразованиями больших затратах времени и памяти при выполнении программы. И, наконец, наличие неявных преобразований усложняет язык, снижает эффективность компилятора.
В слабо типизированных языках для некоторых типов часто разрешается выполнение некорректных операций. Например, наряду с нормальными арифметическими операциями можно для целых типов определить операции логическом сложения и операции сдвига. Хотя это и повышает гибкость языка, но снижает понятность и надежность программ.
В сильно типизированных языках (например, Ада) для следующих описаний:
Х: real;
I: integer;
В: boolean;
С: character;
все перечисленные ниже операторы присваивания ошибочны:
а) I:= 'А'; -- различные типы левой и правой части
b) Х:= I; -- то же
с) С:= 10; -- то же
d) I:= I or 7; -- операция or принадлежит логическому типу
Ясно, что некоторые присваивания вида b) необходимы. Для этого должны использоваться функции явного преобразования типа, например
Х:=real(I);
где функция с именем, совпадающим с именем типа real, применяется к целому значению I и дает вещественный результат.
Сильная типизация резко повышает надежность и ясность программ. Всем операциям языка на абстрактном уровне обеспечивается корректность.
Незаконным считается присваивание значения одного типа данных объекту данных другого типа, даже если внутреннее представление обоих типов идентично. В каждом из таких случаев присваивания должна явно применяться функция преобразования типа. Даже тогда, когда результат ее выполнения эквивалентен тождественному преобразованию, как, например, в операторе
С:=character(10);
включение функции преобразования необходимо, так как заставляет программиста недвусмысленно заявить, что он намеренно выполняет необычную операцию и что это не ошибка.
Защитная типизация еще более повышает надежность языка, не допуская даже потенциальных типовых ошибок. Однако она отрицательно сказывается на мощности языка. Довольно близко к защитной типизации подошел язык Паскаль.