Лекция 14 (1160813)
Текст из файла
Лекция 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, которое соединяет эти два понятия, но это уже некоторые навороты.
Характеристики
Тип файла документ
Документы такого типа открываются такими программами, как Microsoft Office Word на компьютерах Windows, Apple Pages на компьютерах Mac, Open Office - бесплатная альтернатива на различных платформах, в том числе Linux. Наиболее простым и современным решением будут Google документы, так как открываются онлайн без скачивания прямо в браузере на любой платформе. Существуют российские качественные аналоги, например от Яндекса.
Будьте внимательны на мобильных устройствах, так как там используются упрощённый функционал даже в официальном приложении от Microsoft, поэтому для просмотра скачивайте PDF-версию. А если нужно редактировать файл, то используйте оригинальный файл.
Файлы такого типа обычно разбиты на страницы, а текст может быть форматированным (жирный, курсив, выбор шрифта, таблицы и т.п.), а также в него можно добавлять изображения. Формат идеально подходит для рефератов, докладов и РПЗ курсовых проектов, которые необходимо распечатать. Кстати перед печатью также сохраняйте файл в PDF, так как принтер может начудить со шрифтами.