лекции (2003) (Глазкова) (1160821), страница 19
Текст из файла (страница 19)
В С++ (и во всех языках на нем основанных) контекстом разрешения перекрытий является только список параметров.
В языке Ада же возникает понятие контекст конструкций (для каждой конструкции он свой). Например, для процедуры контекстом будет являться сам оператор вызова процедуры (поэтому в случае процедуры единственное, на что может опереться компилятор, - это список параметров).
Однако в случае функции контекстом в языке Ада является соответствующее выражение, поскольку в Ада запрещен вызов функции как процедуры (в С++ и всех производных от него языках это допустимо).
Компилятор языка Ада учитывает этот контекст.
Пример:
function f : boolean;
function f : integer;
Заметим, что в С++ и основанных на них языках нельзя писать int f() и bool f().
В языке Ада функция обязана появляться в некотором контексте.
if f then …- очевидно, что здесь контекстом вызова функции является выражение логического типа
v:=f; - здесь по типу левой части компилятор может определить тип правой части (поскольку в языке Ада в операторе присваивания типы левой и правой части должны совпадать).
В чем сложность определения перекрытий в языке С++?
Причина сложности одна: в С++ есть неявное преобразование типов.
Пример:
int f (double);
Вызов f(1); корректен, т.к. есть неявное преобразование из int в double.
А если есть еще и функция int f (int); то будет вызываться именно эта функция (точное соответствие лучше, чем любое преобразование).
А если существует два преобразования?
Например, функция f(T1,T2);
вызов f(t,t), где t принадлежит T3;
и существуют преобразования:
Т1 => Т3 и Т2=> T3.
Допускать такую ситуацию или нет?
Кроме того, есть еще преобразования из произвольного класса в базисный.
Это усложняет поиск перекрытий.
В Ада такой проблемы нет, т.к. там отсутствуют неявные преобразования вообще.
Отметим, что только в языке Ада правило перекрытия операций распространяется на объекты перечислимых типов.
Пример
Type T1 is (…,red,…);
Type T2 is (…,red,…);
Одни и те же имена (имена цветов) могут обозначать несколько разные понятия. Вспомним, что перечисляемые типы в языке Ада являются достаточно фундаментальными (многие другие полезные типы (bool, символьные) определены как частный случай перечислений).
Язык C#.
enum E1{ enum E2{
… …
red; red;
… …
}; };
В языке C# области видимости константы red ограничиваются типом данных Е1 или Е2.
Поэтому в разных ТД могут быть одинаковые константы (обратиться к ним можно только E1.red и E2.red).Константы перечислимого типа локализованы внутри своего перечислимого типа.
В Ада мы пользуемся перегрузкой процедур. В языке Ада есть различие между подпрограммами и функциями, и литерал перечисления можно трактовать как нульместную функцию без параметров:
function red return T1;
function red return T2;
В языке Ада есть специальный конструктор – указание типа: T’e.
Указание типа говорит о том, что выражение е нужно трактовать как выражение типа данных Т.
Пример.
procedure P(X:T1);
procedure P(X:T2);
P(red); // ошибка
P(Т1’red); // red трактуется как объект типа Т1
Все ли языки поддерживают перекрытие операции? За исключением языка Оберон, все современные языки поддерживают перекрытие операции.
Оберон – чересчур минимальный ЯП. (В нем нет ни статического, ни динамического полиморфизма).
Следует ли явно объявлять перекрытия, т.е. явно указывать перекрытые операции?
В языке Delphi: требовалось явное объявление
function f(x: integer):overload;
function f(x:Т):overload;
Кроме этого явное объявление было в С++ 1.0 (например, overload int f();).
Явное объявление перекрытия является избыточными, и современные ЯП от этого отказались.
Перекрытие стандартных операций.
В языках Ада, С++, C# - есть перекрытие операций.
В языках Delphi, Java нет явного перекрытия операций.
В языке С++ нельзя перекрывать только 3 операции: ?: , . , .*.
Многие языки не поддерживают перекрытие стандартных операций, т.к. никакой компилятор не может обеспечить соответствие семантики.
Например, операция присваивания возвращает ссылку. Ничто нам не мешает перекрыть операцию присваивания так, чтобы она ничего не возвращала.
Глава 5.
Определение новых типов.
Именно возможность определения новых типов, в основном, определяет мощность и гибкость ЯП.
Пункт 1. Концепция уникальности типа.
Уникальность типа – это в некотором смысле формализация понятия строгая типизация.
Концепция уникальности типа опирается на четыре аксиомы:
-
каждый ОД принадлежит одному и только одному ТД;
-
типы совпадают тогда и только тогда, когда они имеют одинаковые имена (могут быть анонимные типы – типы без имени, но при этом считается, что все анонимные типы различны);
-
любые операции над типами требуют явного объявления параметров и возвращаемого значения;
-
Априори различные ТД не совместимы по операциям.
Если все четыре аксиомы удовлетворяются, то говорят, что это язык со строгой типизацией.
Из всех языков, которые мы рассматриваем, полностью строгая типизация реализована только в языке Ада.
В языке Java нет строгой типизации, т.к. нарушена первая аксиома. В любом объектно-ориентированном языке эта аксиома не выполняется, т.к. любой объект производного типа принадлежит одновременно и производному типу, и базовому.
Т.о., концепция уникальности типа характерна только для традиционных ЯП.
Соблюдается ли в языке Си строгая типизация?
Первая аксиома выполнена: любой ОД принадлежит одному и только одному ТД.
Аксиома именной эквивалентности типов почти выполняется (за исключением синонима TYPE T1=T2; в Си и Паскаль).
Есть понятие структурная эквивалентность типов. Типы данных эквивалентны, если их структуры эквивалентны.
В ранних ЯП была принята именно структурная эквивалентность.
Почему в современных ЯП опираются именно на именную эквивалентность?
Самое главное свойство ТД – это характеристика содержательной роли.
Пример:
Пусть есть тип, у которого два поля:
int;
int[50];
Что из себя представляет соответствующая структура данных?
По этой структуре содержательную роль организовать очень тяжело.
Мы можем назвать эту структуру stack, она характеризуется соответствующим набором операций.
Понятно, что при грамотном программировании имя должно характеризовать содержательную роль соответствующей структуры данных.
С этой точки зрения, именная эквивалентность типов более предпочтительна, чем структурная.
Язык Си – язык со структурной или именной эквивалентностью типов?
Пример 1
Struct S{ Struct S1{
int a; int a;
double b; double b;
}; };
Struct S s;
Struct S1 s1;
s=s1;// это присваивание недопустимо.
Пример 2
Void f(Struct S);
f(s1);
Кроме именной эквивалентности типов в языке Си есть раздельная трансляция.
Файл f.c Файл f1.c
Struct S; Struct S1;
… extern void f();// эта запись означает, что параметров
void f(Struct S); может быть произвольное количество . f(S1);
Типы S и S1 структурно эквивалентны. С точки зрения ANSI C, вызов f(S1) допустим.
Т.о., в силу раздельной компиляции язык Си – язык со структурной эквивалентностью типов. Приведенный фрагмент программы допустим, поскольку для параметров функций принимается структурная эквивалентность.
Язык С++ - наследник Си.
В языке С++ есть раздельная независимая трансляции модулей.
Но корректен ли приведенный выше пример в С++?
Программа работать не будет. Ошибка будет найдена на этапе связывания, и будет выдана невразумительная диагностика.
Автор языка С++ применил способ name mangling.
Идея компилятора С++: он компилирует из С++ в язык Си.
Пусть есть
class C{
void f();
}
Как этот фрагмент компилируется в язык Си?
Классу С будет соответствовать некоторая структура
struct C*{…}.
При этом функции f() сопоставляется функция
void f1(struct C* this);
Возникает вопрос, как называть откомпилированную функцию?
Называть ее f() нельзя, т.к. у разных классов могут быть одноименные функции, потому что класс служит областью видимости (средой ссылок).
Поэтому необходим некоторый способ кодирования.
Что делать с перегруженными функциями? Как их транслировать на язык Си?
Например,
int f();
int f(int);
Необходима схема кодирования, чтобы получались уникальные имена:
int f(); => name 1
int f(); => name 2
И эти имена не должны совпадать с именем некоторой функции
int g(int); => name 3
Самый простой способ кодирования: учитывать имя функции и информацию о типе формальных параметров.
В результате функции, у которых разные типы параметров, получали разные имена.
Аналогично, функциям-членам разных классов будут соответствовать разные имена.
Тогда в предыдущем примере со структурами на С++ получится следующее.
Функции void f(Struct S); будет соответствовать имя name1, которое основано на именах f и S. f(S1) будет соответствовать имя name2, которое основано на именах f и S1.
В силу однозначности схемы кодирования name1 не совпадает с name 2.
И поэтому компилятор все оттранслирует, но ошибку выдаст загрузчик, т.к. обращение будет к имени name 2, а описано будет имя name 1.
Ошибка будет выдана на этапе редактирования связей.
При этом диагностика будет: обращение к внешнему имени, которое не определено.
Несмотря на раздельную трансляцию, способ контроля межмодульных связей в языке С++ есть.
Т.о., никакой структурной эквивалентности в языке С++ нет (только именная эквивалентность).
Третья аксиома уникальности типа есть в любом языке, в котором есть хоть какая-то типизация.
Многие языки (Си, Паскаль и др.), в отличие от языка Ада, допускают отклонение от строгой типизации именно по четвёртой аксиоме. Язык, в котором есть понятие неявного преобразования, уже не может полностью соответствовать 4 аксиоме.
Отклонение от концепции уникальности типа могут быть:
-
либо в 1-ой аксиоме (по существу) – все объектно-ориентированные языки;
-
либо удовлетворять 1-ой аксиоме, но отклоняться во 2-ой и 4-ой (традиционные ЯП).
Остановимся теперь на основных проблемах, которые связаны с типами.
Таких проблем две:
-
Янус-проблема (самая главная проблема, связанная с традиционными ЯП); она состоит в том, что 1-ая аксиома концепции уникальности типов является слишком жесткой (даже в достаточно простых системах необходимо, чтобы объект принадлежал сразу нескольким типам).
-
проблема полиморфизма (одному имени может соответствовать несколько объявлений; в традиционных ЯП эта проблема решается с помощью статического полиморфизма; в объектно-ориентированных языках эта проблема решается с помощью динамического полиморфизма). Подробнее Кауфман «ЯП» (раздел 4.6).
Лекция 15.
Обсудим, что же такое тип данных с наиболее абстрактной точки зрения.
В 1969 г. Н. Вирт выпустил книгу "Введение в систематическое программирование". В этой книге он говорил, что тип данных определяется множеством своих значений. Современная точка зрения другая: типы данных рассматриваются, прежде всего, как множество операций и потом уже как множество значений, определяемое структурой данных. Т.о., как работать с ТД, определяется именно множеством операций.
ТИП ДАНННЫХ = МНОЖЕСТВО ОПЕРАЦИЙ+МНОЖЕСТВО ЗНАЧЕНИЙ.
Как мы знаем, абстрактная операция в ЯП - это процедура. Следовательно, самый простой способ определения типа данных - определить множество процедур, которые работают с этим типом данных.
Какой тип данных языка Си определен некоторой структурой данных и набором операций над этой структурой данных? Это тип FILE*.