Лекция 11 (лекции (2002))
Описание файла
Файл "Лекция 11" внутри архива находится в папке "лекции (2002)". Документ из архива "лекции (2002)", который расположен в категории "". Всё это находится в предмете "языки программирования" из 7 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Онлайн просмотр документа "Лекция 11"
Текст из документа "Лекция 11"
Лекция 11
Ада (значительно мощьнее с точки зрения модульности всех тех языков, которые мы разбирали), на первый взгляд поход на Модулу-2:
Package имя is
Объявления
End имя;
Это аналог модуля определений в языке Модула-2 или интерфейса в Delphi. Как и в Модуле-2 спецификация пакета может являться отдельным файлом (разделение О,Р и U).
Реализация (тело пакета):
Package body имя is
Реализация
End имя;
Тут должны быть реализованы все процедуры и функции, описанные в интерфейсной части, иначе компилятор выдаёт ошибку. В отличие от Модулы-2 интерфейс и реализацию можно объединить, можно разделить физически (в разных файлах). Логически они разделены. Но в Аде значительно сложнее структура пространства имён. В таких ЯП, как Delphi, Модула-2 и Оберон вопросы логической и физической модульности слиты воедино, а именно в Модуле-2 интерфейс и реализация- это вообще разные файлы файловой системы, в Delphi на логическом уровне разделение есть, но это, вообще говоря может быть один файл, но в нём не может быть два unit’a. Так же как и в Модуле-2 не может быть двух интерфейсов или реализаций в одном файле. Все модули (unit’ы), которые входят в проект равноправны. В результате структура пространства имён в этих ЯП достаточно простая:
Непосредственно видны только имена соответствующих модулей. И с помощью конструкции импорт (не важно как она называется) один модуль импортирует конструкции из другого. Линейная структура просттранства имён, нельза импортировать имена из реализации (тела пакета), их вообще снаружи не видно. Они локальны для каждого unit’a.
Не будем пока вникать в тонкости раздельной трансляции, рассмотрим случай когда у нас несколько модулей (спецификаций и реализаций пакетов) расположены в одном файле. В Аде пространство имён структурировано таким образом, что пакеты могут вкладываться в пакеты. То есть структура даже на уровне одного файла более сложная. Рассмотрим совокупность из трёх пакетов, в которых могут присутствовать как только спецификации пакетов, так и их реализации, но так как все имена из реализации извне не видны, то абстрагируемся от неё. В итоге:
P1
x:T1
P2
x:T2
y:T
P3
z:T3
Здесь непосредственно видимы только имена пакетов, и, потенциально- все имена, описанные в интерфейсах, через имя модуля (соответствие Модуле-2). Здесь можно писать: P1.x, P2.x, P2.y, P3.z. И в другом контексте эти имена появляться не могут, правда, есть такая интересная конструкция uses. Заметьте, что в обероне нет понятия непосредственной видимости имени из другого, там мы можен писать только имя_модуля.имя_сущности.
В Аде можно перекрывать как пользовательские, так и стандартные операции (“+”,”*”,”/”). Пример:
Package Vectors is
Type Vector is …;
Function “+”(x,y: in Vector) return Vector;
…
End Vectors;
И ниже (в реализации) всё красиво. Мы можем объявить переменные типа Vectors и применить к ним операцию “+”. Это не интересно, так как операция “+”- сервис (для других модулей). Но все имена из этого пакета видимы только потенциально. Можно написать:
V,V1,V2: Vectors.Vector;
Но нельзя:
V := V1 + V2;
Так как имя “+” видимо только потенциально. Надо писать:
V := Vectors.”+”(V1, V2);
Все преимущества перекрытия стандартных знаков операций при такой нотации потеряны. И попробуйте переписать в такой форме выражение (не взирая на математический смысл операции V2*V3, которая тут даёт результат тоже типа Vectors):
V1 + (V2 * V3) – V4;
И преимуществ по сравнению с записью Vectors.plus(V1,V2); нет. Следовательно, как только мы разрешаем перекрывать стандартные знаки операций при определении новых типов данных, необходим механизм, который обеспечивает непосредственную видимость. В Аде- это uses (аналогичен uses в Delphi). В результате все имена из списка пакетов могут стать непосредственно видимыми.
Uses список имён пакетов;
Пример:
Uses Vectors;
V, V1, V2: Vector;
V := V1 + V2;
И таким образом мы добились своего и получили все плюсы перекрытия стандартных операций, но возникает проблема- конфликт имён.
Uses Vectors, Matrices;
В каждом из этих пакетов есть перекрытые операции “+” и “*”. Но в данном случае компилятор разберётся (по типу операндов). Допускается конфликт имён операций (процедур и функций). То есть, когда x- имя операции проблем не возникает. А вот на именами типов и переменных механизм перекрытия не распространяется:
P1
X
Uses P1;
P2
X
X := … можно- нет проблем (Х из P1 видимо непосредственно, а Х из P2- потенциально).
P3
Uses P2;
X := … -это неработоспособная конструкция, если Х имя переменной или типа (имена Х- скрытые)
Ещё более усугубляется ситуация, если у нас имена спецификаций конфликтуат с именами, описанными на более верхнем уровне. Первоначальный стандарт Ада в 1983 году не разрешал ни сужение, ни расширения языка. Но каждые два года появлялись толкования неясных мест из стандарта, в основном связанными с некоторыми извращёнными случаями, связанными с видимостью имён. Потом во внутрикорпоративных стандартов ряда компаний запретили использование конструкции uses из-за потенциальной ненадёжности, понимая, что за этим следуют значительные неудобства. Таже конструкция в Модуле 2 или Delphi проблем не вызывала из-за простой структуры пространства имён (там всё разделено по разным файлам, а тут всё может находится в одном файле).
И ситуация усугубляется тем, что в языке Аде модули могут вкладываться друг в друга. Заметим, что в классических ЯП (не может быть unit внутри unit, не может один модуль в Модуле-2 быть частью другого модуля, в Обероне модули не могут вкладываться). То есть проект имеет линейную структуру модулей. В Аде допустима вложенность пакетов:
Package
P1
Package
P2
И тело пакета P2 будет внутри пакета P1 (их тела должны где-то существовать):
Package body P1 is
…
package body P2 is
…
end P2;
end P1;
И это связано с тем, что существует несколько стилей проектирования сложных систем. Существует два противоположных метода проектирования программ (хотя реально применяется их комбинирование). Первый- это проектирование «снизу-вверх»:
Сначала создаём базисные модули (ввод-вывод, …), они используют только модули стандартной библиотеки; потом более высоково уровня, использующие абстракции этих модулей и стандартные библиотеки; и, наконец, модуль верхнего уровня, который представляет собой нашу програмную систему. Недостаток: мы не уверены в работоспособности системы, пока она не закончена до конца. Мы можем отлидить уровни нижнего уровня, но для этого используем специальные механизмы, не имеющие никакого отношения к самой системе. Так же в случае когда после написания проекта приходит заказчик и меняет свои требования (а таковы все заказчики , так как они не всегда сами знают, что им хочется).
Второй подход: «экстремальное проектирование» (XP). Его метатология основана на том, чтобы как можно быстрее получать прототипы работующей системы и показывать их заказчику. Такому стилю соответствует метотология проектирования «сверху-вниз». Мы вначале проектируем модуль верхнего уровня, потом поддерживающие его модули, ну и так далее, а вместо модулей нижнего уровня пишем простенькие заглушки. И языковой подход, допускающий вложение модулей больше соответствует этой методике программирования. Так как реально применяется комбинация из этих двух подходов, то разработке сложных структур более соответствует механизм вложенности Ады, нежели линейчатая структура других модульных ЯП. И тут модули образуют иерархическую структуру, как оно и есть для большинства програмных систем. За это платим усложнением языка, здесь уровень уточнения имени может быть сколь угодно большим:
P1.P2.P3.X
В Аде был введён специальный оператор переименования, позволяющий заменить более коротким словом такую конструкцию (он присутствует во всех ЯП, допускающих вложенность пространства имён), без проблем общего оператора “uses”.
Классы
Если забыть про наследование, синтаксис везде одинаковый:
Члены-данные
Class X{
Члены-функции (методы)
Члены класса};
Вся терминология (члены-данные, члены функции, …) пошла ещё от языка SmallTalk. А пока мы абстрагируемся от вопросов доступа и видимости. В отличие от модуля класс заточен на определении типа. Имя этого класса и есть имя ТД:
X a;
“Члены-данные”- образуют структуру данных для этого класса, а “члены-функции”, то есть методы- это набор операций для этого класса.
В C++ Определение(O), Реализация(P) и Использование(U) могут быть разделены:
Class Stack {
Int body[50];
Int top;
…
Только прототипы соответствующих операций (их спецификация)
void Push(int x);int Pop();
};
Это интерфейс, и в нём мы лишь уточняем структуру класса, а реализация может быть в другом модуле (даже в другом файле программы на языке С++):
void Stack::Push(int x)
{…}
int Stack::Pop() {…}
Где «::»- это операция доступа к элементу класса (разрешение области видимости). Во всех остальных языках это чистая точка (“.”). Это связано исключительно с синтаксическими особенностями языка Си, но суть этих операций одна и та же. Мы обязаны тут указывать квалификацию, так как соответствующие имена (Push, Pop, body, top) относятся к классу. Класс здесь начинает проявлять свойства не только ТД, но и модуля. А именно: с точки зрения видимости, с точки зрения доступа, то есть пространств имён класс начинает вести себя как модуль, а именно имена Push, Pop, body и top являются потенциально видимыми. А непосредственно они видимы только через явную квалификацию например именем класса.
О и Р разделены, но реализацию можно было добавить и в определении. Но в С++ это добавляет определённую семантику. В ООП при грамотной иерархии классов, у нас программа разбивается на кучу очень небольших процедур (тогда как раньше писали огромные процедуру и функции и из-за этого даже принудительно ограничивали их максимальный размер), то есть редко мы увидим методы длиной больше 20-30, не из-за стандартов, а просто они больше и не получаются. Но надо помнить, что вызов функции- это не самая дешёвая вещь, то есть, возможно возникают некоторые накладные расходы.
Class Stack {
Int body[50];