лекция 14 (1161109), страница 2
Текст из файла (страница 2)
Для операции + : operator +(arg1, arg2); или operator +(arg1);
То же самое и для Дельфи, и для Си#, и для Java. Как следствие, перекрытие происходит по числу и типу параметров. В языке Дельфи при перекрытии операции после объявлении процедуры мы должны указывать ключевое слово overload.
-
Более интересная ситуация в языке Ада. Используется перекрытие к именам литералов перечисления:
type T1 is (a, b, c);
T2 is (b, c, d); // это допустимо
Можно считать, что в такой ситуации речь идет о функциях, поскольку любую константу можно считать нульместной функцией, т.е. функцией без аргументов, которая возвращает фиксированное значение. В отличии от Си++, Си#, Java, где вызов функции сопровождается круглыми скобками, в Аде функцию или процедуру без параметров можно вызывать просто указывая ее имя.
В Аде понятие контекста вызова подпрограммы глубже. Контекстом вызова процедуры является сам оператор вызова процедуры, а контекстом вызова функции – выражение, внутри которого она вызывается.
-
z:= T1!b;// трактуй b как Т1
2)z:= c;// контекстом является вся конструкция, а именно оператор присваивания
так как c принадлежит и к Т1, и к Т2, то его тип в данном присваивании зависит от типа z
3)if f() then - здесь требуется функция, возвращающая логическое значение
В общем случае в Аде функции могут перекрываться не только по профилю своих параметров, но и по возвращаемому значению (если в Си++ функция без параметров, то перекрыть такую функцию мы не сможем).
А нельзя ли сделать пользовательскую семантику для стандартных операций? Здесь существуют две идеологии:
1) Да, это можно - идеология Ада, Cи#, Си++.
2) Нет, это нельзя - идеология Java, Дельфи
Все языки, которые мы рассматриваем, соблюдают ограничение на количество аргументов – сколько аргументов есть у стандартной операции, столько же должно быть у перекрытой. Это сделано для того, чтобы не менялся алгоритм синтаксического анализа (есть языки, которые как правило не компилируют, не поддерживающие это правило - PROLOG ).
Рассмотрим синтаксис:
Ада
function "+"
(X.T) return T;
(X1, X2:T) return T;
Заметим, что ни одном языке, который мы рассматриваем, нет ограничения на типы аргументов и на тип возвращаемого значения. Это может быть нехорошо, если в Си++ мы перекроем операцию присваивания как void- функцию. И тогда a=b=c; // ERROR
Можно еще сделать так: У& operator = (X&); когда надо сделать бы: X& operator = (X&). Кроме того, плохим тоном считается придание перекрытиям специфического значения, т.е. операция сложения должна складывать, а не вычислять квадратный корень. Аналогично при перекрытии оператор "->" (ссылка на объект), он не должен, например, складывать свои аргументы.
Глава 5: Инкапсуляция и абстрактные типы данных
Обратите внимание:
Любой класс является типом данных (но не наоборот - для Си++, а для Си# - для любого тд есть класс из стандартной библиотеки). Есть понятие абстрактный класс и есть абстрактный тип данных. Но абстрактный класс - совсем не то, что абстрактный типа данных! Абстрактный класс (вот, например, для Си++ абстрактный класс – такой, у которого есть хотя бы одна виртуальная функция) имеет отношение к наследованию и к механизму динамического связывания. А абстрактный тип данных имеет отношение только к механизму инкапсуляции.
Сейчас мы поговорим об инкапсуляции. Инкапсуляция - упрятывание. Что можно упрятать и зачем это делать? Вспомним, что тип данных = множество значений + множество операций. Можно упрятывать структуры данных (целиком или по частям), плюс, как следствие, операции, которые есть процедуры или функции, привязанные к определенному тд. Пользователь не должен знать множество значений и то, как реализованы операции (тела соответствующих процедур и функций). У нас есть специальные конструкции – модули или классы, которые связывают воедино структуру данных и соответствующее множество операций. До этого не осуждалось, ограничен или нет доступ к членам модуля или класса. Об инкапсуляции речь зашла, когда разбирались модульные языки – разделение на модуль определений и модуль реализации для Модула 2 (либо на интерфейсную и реализационную части юнита в Дельфи, либо в спецификация и тело пакета в Аде). Вспомним, что все имена, описанные в реализационной части, упрятаны, так как они доступны только внутри модуля реализации.
Есть еще дополнительные возможности упрятывания. Абстрактным типом данных называется тип данных, множество значений которого упрятано от программиста. Работа с ним возможна только в терминах явно определенного множества операций (add() и т.п.). Можно сказать, что АТД – это множество операций, определенное явно.
Рассмотрим, как реализована инкапсуляция и АТД в разных ЯП, увидим, что наиболее полно все это реализовано в Аде. Кроме того, понятие инкапсуляции надо рассматривать отдельно применительно к модулям, и отдельно применительно к классам.
-
Единица защиты
Единицей защиты может быть тип целиком, либо член типа (часть типа). Модульные языки (Ада, Модула-2) ориентированы на то, что единица защиты - тип целиком (тип либо полностью открыт, либо полностью закрыт).
Если мы программируем без goto (т.е. терминах исключительно структур – данных и операторов) мы себя сознательно ограничиваем свои возможности, но при этом увеличивается понятность программ, скорость их написания, длина может уменьшаться. Аналогично и с упрятыванием, разграничение на множество значений и множество операций увеличивает качество кода и труда программиста. Не случайно, что первым из основных свойств объектно-ориентированных языков является понятие объектов и инкапсуляции. И только потом идут наследование и динамический полиморфизм.
Итак, ограничение на весь тип, программирование в терминах АТД – это особенности языка Ада (и модульных яп). В современных яп допускается оператор goto, и наличие классов определяет несколько иную тенденцию: единицей защиты является член типа, а именно член-данное. Если мы вспомним внеклассовый язык Оберон – там ситуация такая же, запись может быть целиком закрытая, частично открытая. Подразумевается, что лучше по возможности goto не использовать и программировать в терминах АТД.
-
Атом защиты
Атом защиты - весь тип и отдельные экземпляры. Атомом защиты всегда является весь тип. Нельзя сказать, что для данного экземпляра это поле является открытым, а для остальных закрытым, т.е. для всех экземпляров типа единообразны правила видимости или доступа.
-
Видимость или доступ
В чем тут различия. В языках, где ограничения на правила доступа, там видимыми являются все члены, но не ко всем членам разрешен доступ. Инкапсуляция выглядит так: видимыми с точки зрения компилятора и программиста являются все имена описанные, например, в классе. Все языки, в которых есть понятие класса, ориентированы на доступ. А модульные языки ориентированы на видимость. Разница между видимость и доступом проявляется именно при наследовании, а если наследования нет, то видимость и доступ эквивалентны.
Каким образом реализуется упрятывание в модульных ЯП: вся реализация – упрятана, всё, что в интерфейсе - всё открыто (Дельфи, Модула-2).
Небольшие отличия в языке Ада:
package P is
type T1 is array(1..N) of Real; // это целиком открытый тд
type T2 is private; // это целиком закрытый тд
private
приватная часть // полное описание Т2
end P;
Отличие Ады от Дельфи, Модула-2 состоит в том, что если есть хоть один приватный тд, то в интерфейсной части должна присутствовать приватная часть, которая тоже начинается ключевого слова private. В приватной части целиком описываются в все типы, которые выше были объявлены как приватные. Так в отличии от Дельфи, Модула-2, где описание должно быть внутри реализационной части, в Аде оно находится в спецификации. Пользователю доступен текст спецификации, но доступа к закрытым тд он не имеет. Почему такое отличие между Ада и Модула 2: оно возникает из-за различных принципов конструирования АТД.