Лекция 14 (лекции (2002))
Описание файла
Файл "Лекция 14" внутри архива находится в папке "лекции (2002)". Документ из архива "лекции (2002)", который расположен в категории "". Всё это находится в предмете "языки программирования" из 7 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Онлайн просмотр документа "Лекция 14"
Текст из документа "Лекция 14"
Лекция 14
АТД, где единицей и атомом защиты является весь ТД присутствует в Аде, Модуле-2 и, частично, в Delphi, в которых есть понятие модуля, пакета, модуля описаний и модуля определений. В Delphi это unit и тут единица защиты, как во всех языках является целиком ТД. Но атом защиты в них- тоже тип целиком, то есть создатели этих языков поддталкивают писать нас в терминах АТД, так как это хорошо.
В Аде limited private- настоящий АТД. К нему применимы: передача, как параметры; самые базовые- узнать размер типа, узнать адресс переменной; операции, описанные ниже в спецификации пакета, после определения его имени:
type T is limited private;
операции
private
type T record ... end record;
Запрещены операторы «:=», «=», «/=» и так далее. Тут структура нужна в спецификации пакета из соображений гибкости, для эффективного распределения памяти компилятором. Так же в Аде есть зачаточные возможности инициализации записи (size = 25). И при размещении объектов типа Т в памяти компилятор вставляем минимальный инициализационный код. Но структура доступна только компилятору.
В Модуле-2: TYPE T; - это либо указатель, либо совместимый с ним ТД, что заставляло программировать нас в парадигме исключительно динамических ТД.
В Delphi есть как модульная парадигма так и парадигма классов и мы увидим, что к нему относится и всё, что мы говорили про Аду и Модулу-2 и всё, что мы будем говорить про языки с классами.
Оберон: немного другая идеология. Это ещё один модульный язык. Вместо того, чтобы целиком закрывать или открывать тип данных (атом защиты- ТД целиком), тут используется понятие «проекция»:
MODULE M;
TYPE T* = RECORD
'*'- если хотим экспортировать это имя. Мы открываем или закрываем доступ к отдельным элементам записи:
X* : INTEGER; /* поле видимо */
Y : REAL; /* поле не видимо */
END;
В Обероне ещё присутствует «*-» - доступ только на чтение (применима только к имёнам объектов данных, но не к процедурам или функциям).
IMPORT M;
Z : M.T; - это проекция
В Обероне есть утилита, генерирующая псевдофайл определений. В псевдомодуле определений автоматически сгенерируется:
DEFINITION M; // это не ключевое слово языка Оберон
TYPE T = RECORD
X : INTEGER;
END;
Это как бы проекция Т. А что если вообще внутри типа не писать ”*” ?
TYPE C* =
RECORD
x,y,z : X1;
d : X2;
END;
Что будет сгенерировано в псевдомодуле определений?
TYPE C = RECORD END;
Пустая запись. С точки зрения языка С пустая запись- это извращение, а с точки зрения Оберона: это АТД. Но как и в Модуле-2 тут мы не можем более тонко управлять поведением АТД, как мы могли сделать в Аде (разрешать/не разрешать операцию присваивания, например, когда нас не устраивает обычное побитовое копирование, мы делает тип ограниченным приватным типом о операцию копирования просто переопределяем как новую дополнительную операцию). В Обероне тем не менее есть некоторое приближение к АТД и оно лучше, чем то, что было в Модуле-2, хуже с точки зрения гибкости чем в Аде, но зато существенно проще: никаких особых новых концепций, кроме понятия видимости и понятия проекции для этого мы не вводим. Возникает интересный вопрос: «А всё-таки кто-то знает эту структуру?». Компилятор, конечно, знает структуру, когда он транслирует модуль, то всю информацию оставляет в некотором бинарном табличном файле (скорее всего). И когда мы пишем « IMPORT M; Z : M.T;» компилятор знает, что ему нужно отводить память не под переменную типа Х,а под некую запись размера INTEGER и REAL, соответственно. Следовательно по эффективности распределения памяти Оберон не уступает языку Ада. Так как компьютеры стали мощьнее, а это решение, как мы увидим в следующей главе приводит к некоторым накладным расходам.
В современных ЯП более гибкие средства управления доступом (видимостью), то есть так как нас заставляют писать в терминах АТД мы приходим к более гибким схемам.
Современные ЯП: атом защиты- отдельный член класса. Рассмотрим С++, так как здесь более простая схема и все остальные схемы (Java, C#, Delphi) просто несколько обощают общую схему языка С++, а она очень проста.
C++: два способа группировки (объектов, типов данных, констант и так далее):
класс- логический модуль
файл- физический модуль
{Существует ещё понятие проекта, пространства имён. Понятие проекта на уровне языка С/C++ не выступает, а с физической точки зрения пространство имён реализуется как некоторая совокупность файлов, но об этом несколько позже}
В файл остались средства управления видимостью, которые перешли из языка С, в который он перешли из ассемблера: существуют внешние имена и внутренние:
static статические //локально в файле
extern внешние //он подразумевается по умолчанию- она (переменная или функция) видима извне этого файла)
Это немного похоже на имена из модуля определений (внешние) и модуля реализации (внутренние).
Класс:
public видимы абсолютно всем, кто видит этот класс (доступно всем)
private видимы только функциям членам этого класса (доступно себе)
Есть ещё и
protected видимы в функциях членах этого класса, а так же в функциях членах унаследованных классов, то есть доступно себе и детям (появилось из-за наследования)
Синтаксически это выглядит как переключатель:
class X {
public:
X();
~X();
private:
...
}
По умолчанию в класса доступ private, а в структуре public. Они только этим и отличаются.
Довольно простая схема, но не всегда удобно. Пример:
class Matrix {
...
public:
...
};
Проблема: как перекрыть операцию плюс для двух матриц. Есть два способа перекрытия операций: мы можем сделать операцию функцией членом, а можем глобальной. В случае, когда это функция член:
Matrix& operator+(Matrix &M);
{передаётся this, он и выступает в качестве первого члена}
a+b ~ a.operator+(b);
Здесь чёткая несмимметрия: первый аргумент как бы сильнее втого, всё исходит из него. К томуже эта операция (математический плюс) должна быть без побочного эффектка, не должна модифицировать как первый так и второй аргументы (а в таких операциях обычно возвращается певый аргумент), должна возвращать третье значение. Поэтому, вообще говоря, если операция имеет ясный математический смысл, который по семантике совпадает со смыслом заложенным в сам язык (в противном случае операторы не рекомендуется переопределять, лучше придумывать для них свои имена), то, с точки зрения операции плюс, это должна быть операция без побочного эффекта, симметричная относительно своих операндов, возвращающая третье значение. А вот модифицирующая операция в С/C++ a+=b – это рекомендуется переопределять как функции члены они модифицируют свой левый аргумент. И поэтому её имеет смысл переопределить как одноместную функцию член соответствующего класса. И это будет интерпретироваться как «a.operator+=(b);». И, естественно, модифицирует свой первый операнд и его же возвращает в качестве своего значения. А a+b разумно переопределить как внешнюю функцию. Но тут-то и возникают проблемы: раз это внешняя функция, то она имеет доступ только к публичным членам данных. Следовательно для эффективной реализации надо либо отказаться от инкапсуляции, либо придумать какой-то другой механизм. Он придуман, а именно, механизм «друзей». В некоторых случаях совершенно необходимо отдельным внешним функциям дать особый доступ, то есть приравнять внешнюю функцию к функции члену этого же класса (для которых нет никаких ограничений на доступ).
Должны быть внешние функции. Операция, применимая к двум классам (Х1 и Х2): либо внешняя функция к Х1 и Х2, либо глобальная, либо функция-член одного класса из этих классов. В любом случае для одного из этих двух классов эта операция будет являться внешней и в тоже время она должна иметь общий доступ. В Обероне, Аде, Модуле-2 это не проблема, мы просто определение типов Х1 и Х2 сводим в один и тот же модуль и там же описываем все операции, в том числе и эту, так как в модуле можно определять и несколько ТД и это более общее понятие чем для отдного ТД, и всё, что мы в нём описали имеет доступ ко всем типам, описанным в этом модуле, не важно скрытые они, приватные или как-то ещё. Поблем нет. А как только возникает понятие класса, тесно связанное с ТД нам нужны специальные средства, дополнительные средства, управления доступом. В С++ это средство называется другом класса. Друг- это функция или класс, которые описаны либо в другом классе либо глобально.
class Matrix {
friend Matrix& operator+(Matrix& a, Matrix& b);
/* friend (функция плюс) имеет полный доступ ко всем членам (эквивалентен функции члену по доступу). */
friend void Y::f();
/* так же может быть отдельная функция другого класса */
friend class Z;
/* все функции класса Z являются друзьями этого класса /
}
Дружбу объявляют явно. В друзья не набиваются, в друзья берут. Отношение дружественности не транзитивно (класс Х берёт себе в друзья класс Y, а класс Y берёт себе в друзья класс Z, из этого не значит, что Z будет неявно другом Х) и не наследуется (если класс Х объявил, что Y- его друг; мы вывели из класса Х некоторый класс Z; или, соответственно, наоборот, из Y вывели какой-то тип данных Т; из этого не значит, что Z, то есть те новые члены, которые мы добавили к классу Х, мы разрешаем для Y доступ; то же самое: класс T имеет все функции члены класса Y, которые имеют доступ к Х, но не новые функции члены класса Т. Аналогия с человеческими отношениями. Отношение дружественности достаточно безопасно и при этом позволяет решить много проблем. И при этом друзья могут быть произвольными внешними функциями или классами.
Эта схема перешла в C#, Java, Delphi. Но они её немного расширили. Основная проблема С/C++ в слабости его файловой структуры (раздельной трансляции). Только два способа группировки: сам класс и файл. Но понятия «проект» или «подпроект» не существует на уровне языка С/С++. Недаром сразу же как только был разработан компилятор с языка С появилась утилита make, которая и показывает какие файлы входят в проект и как ими управлять. Проект (логически сгруппированная совокупность файлов)- ещё одно средство группировки. Это и используют языки, которые унаследовали С++.
Похожие понятия: пространство имён C# (сильно отличается от С++ понятия) и пакет Java имеют иерархическую структуру. С точки зрения операционной системы (реализации этих языков) эти иерархические структуры отражаются на иерархическую структуру файловой системы (директории, поддиректории и так далее). Это некоторый физический способ на уровне языка группировки файлов в проекты. И этим механизмом вполне естественно воспользоваться для управления видимостью, а точнее- доступом.
В Аде (как и в Обероне) речь идёт об управлении видимостью, а в языках типа С++ (ООЯП) речь идёт об управлении доступом (приватное имя видимо, но достать его нельзя). Разница появляется при наследовании.
В Delphi кроме проекта появляется ещё и понятие unit (модуль), и им естественно воспользоваться для управления видимостью в пределах класса.
Во всех этих ЯП есть:
private - по умолчанию (если нет ключевого слова) в С++
protected
public
Кроме этого ещё один вид доступа:
C#: internal - по умолчанию (внутренний доступ)
Java- по умолчанию пакетный доступ (с точки зрения одного пакета публичный доступ, а с точки зрения всех остальных приватный доступ), для него нет никакого ключевого слова. Внутренний доступ в языке С# аналогичен пакетному, только в рамках одного пространства имён, а не пакета. Например, если у нас есть оператор op(x1, x2), то x1 и x2 должны принадлежать одному пакету, тогда х1 и х2 мы просто описыванием как внутренние.
В Delphi аналог пакетного доступа:
unit
type X = class
операции
данные
...
По умолчанию и операции и данные имеют пакетный доступ- к ним имеют доступ все классы и функции из unit’a (Тут ещё есть интересная тонкость Delphi- динамический доступ, но мы его касаться не будем).
Пространство имён Х. Тип данных Т (в нём внутренние или пакетные данные). Пространство имён Y : импортирует Х и там из Т выводится Т1. Какой доступ у функций членов класса Т1. К приватным никакого. К защищённым по определению имеется. А вот к внутренним? В С#: существует protected internal, которое соединяет эти два понятия, но это уже некоторые навороты.