Лекции по программированию на ЯВУ - файл n14.doc

Лекции по программированию на ЯВУ
скачать (400.9 kb.)
Доступные файлы (20):
n1.doc53kb.23.12.2002 09:35скачать
n2.doc63kb.09.12.2002 19:57скачать
n3.doc68kb.13.09.2002 09:08скачать
n4.doc53kb.05.10.2002 13:18скачать
n5.doc92kb.22.12.2002 13:01скачать
n6.doc163kb.24.11.2002 22:22скачать
n7.doc116kb.24.11.2002 22:29скачать
n8.doc110kb.24.11.2002 22:39скачать
n9.doc97kb.24.11.2002 22:38скачать
n10.doc98kb.24.11.2002 22:38скачать
n11.doc98kb.30.12.2002 23:47скачать
n12.doc92kb.31.12.2002 04:18скачать
n13.doc91kb.30.12.2002 23:48скачать
n14.doc101kb.30.12.2002 23:45скачать
n15.doc101kb.04.01.2003 05:53скачать
n16.doc115kb.23.12.2002 08:19скачать
n17.doc88kb.23.12.2002 08:20скачать
n18.doc136kb.30.12.2002 23:59скачать
n19.doc117kb.23.12.2002 08:21скачать
n20.doc76kb.23.12.2002 08:23скачать
Победи орков

Доступно в Google Play

n14.doc

  1   2   3
Лекция 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, которое соединяет эти два понятия, но это уже некоторые навороты.

В языках C# и Java private и public применимы не только к членам классов, но и к классам вообще. В этих ЯП нет понятия глобальных функций. В Java любая программа- это совокупность определения классов. В С# к ней ещё добавляется определения, например, перечислимых ТД. В любом случае программа на этих языках- последовательность определения типов (типы: классы, интерфейсы, перечислимые типы, структуры). Никаких глобальных данных и функций. В этих ЯП в отличие от Delphi и jn C++ атрибуты private, protected и public являются атрибутами одного данного, а вовсе не переключателями. Мы должны перед каждым членом данным или функцией ставить один из этих атрибутов, или по умолчанию. Кроме этого атрибуты private и public можно ставить перед именами классов. Смысл тот же самый. Пример:
public class X {
/* public означает, что экспортируем из соответствующего пакета */
public X() { ... };

private void f() { ... }

...

};
Если напишем просто class Y { ... };- это будет по умолчанию пакетный доступ: виден в пакете (пространстве имён), но не виден извне. Похоже на реализацию. А перед всем, что относим к интерфейсу надо ставить ключевое слово public.

Мы рассмотрели вопросы связанные с инкапсуляцией данных. АТД- это ТД, у которого структура и реализация полностью закрыта от пользователя. То есть мы видим в интерфейсе только имя типа и операции. В C++, Java, C# формально АТД может называться некоторый класс у которого публичным являются только операции. Тут public могут быть только функции-члены. Мы абстрагируемся от реализации.

Во всех ООЯП существует явно или неявно понятие интерфейса, как языковое понятие- это обобщение АТД. Для этого есть соответствующее ключевое слово. В интерфейсе мы видим только операции, либо статические члены данных (константы). Можно ли объявить класс (пусть на С++), объекты которого могут заводится только в динамической памяти и никогда не могут быть освобождены (и это контролируется компилятором). Очень просто:

class X {

public:

static X* Make() {return new X();}

/* static необходим, чтобы мы могли создать объект, иначе мы не сможем обратиться к этому методу не имея объектов данного класса */

private:

X(); /* атрибуты относятся и к специальным функциям */

~X(); /* уничтожить объект можно только в функции-члене */

};
X*p = X::Make() - единственный способ создать объект (заводится в динамической памяти)

X a; - ошибка, так как конструктор приватный

Как запретить операцию копирования объекта? (например, для класса KeyManager: класс, управляющий данными, которые должны являться уникальными ключами, тут нужен специальный алгоритм генерации плюс запрет операции копирования ). В Аде- ограниченный приватный ТД. Копирование извне запрещается так:

private:

X(X&) /* конструктор копирования (при инициализации) */

= /* операция присваивания */

Они по умолчанию генерируются публично.


  1   2   3


Лекция 14
Учебный материал
© nashaucheba.ru
При копировании укажите ссылку.
обратиться к администрации