Контейнер - средство создания новых типов данных. В некоторых языках контейнером выступают модули (Модула – 2, Ада, С#(namespace), Java(package)), в других - классы, структуры(С++, С). Delphi – гибрид, cодержащий как модули, так и классы
Понятие логического модуля. Использование модулей для определения
новых типов данных. Особенности понятия модуля в современных ЯП.
Модуль - независимая единица трансляции. Подключить модуль чаще всего означает заимствовать написанный кем – то код, сохраненный в модуль. Множество языков имеют кучу встроенных библиотечных модулей, реализация которых опирается на РОРИ. Структура модулей как правило древовидная.
Один из важнейших ресурсов Ада – определение новых типов данных.
DEFINITION MODULE STACKS;
TYPE stack=RECORD
B: Array[0..N] of T;
TOP: [0..N];
END; //конец записи
PROCEDURE PUSH(VAR S: STACK; X: T);
PROCEDURE POP(VAR S:STACK; VAR X: T);
Пусть внутри модуля находятся процедуры
IsEmpty
IsFull
PROCEDURE Init(VAR S: stack);//(инициализация стека)
и переменная
VAR done: Boolean;//сообщающая последний результат операции над стеком
END STACKS;
В модуле определений (модуле реализации) соответствующие процедуры должны быть полностью описаны.
Переменная Done говорит нам о том, выполнилась ли какая-то операция над стеком или нет. В чем недостаток? Дело в том, что мы вынуждены экспортировать переменную на полный доступ – а это не есть хорошо. Выход из данной ситуации:
PROCEDURE Init*(VAR S:stack);
VAR DONE * BOOLEAN
PROCEDURE GetError(): Boolean;
Но тогда переменная DONE может быть испорчена. Функция Get Error, очевидно, должна возвращать значение переменной DONE. Но вызов функции – это нелокальный переход, и с точки зрения современной архитектуры он плох.
Импорт и экспорт имен. Видимость имен: непосредственная и
потенциальная. Управление видимостью. Области видимости и пространства
имен.
Модуль – набор взаимосвязанных ресурсов, которые служат для использования других модулях.
Видимость подразумевает возможность обращения с объектом, то есть, к примеру, знаем его имя и тип. непосредственно видимы в глобальном пространстве имен только модули. Имена полей из модуля видимы только потенциально.
Модуль-ресурсы = ОД, ТД, П/П.
Интерфейс = определение ресурсов + реализация.
Пространство имен в ООЯП заменено на модули.
М-2:
1)Главный модуль //Один
2)Библиотечный модуль
3)Локальный модуль //параллельное программирование.
Глобальное пространство имен => видимость для всех
Непосредственная видимость: имя использует ASIS.
Потенциальная видимость: имя с уточнением.
DEFINITION MODULE имя;
Определение ресурсов.
END имя;
IMPLEMENTATION MODULE имя;
Реализация всех процедур/функций из DEF + дополнительные ресурсы.
END имя;
TP, DELPHI:
init имя;
interface
…
implementation
End имя;
Все имена экспортируются в глобальное пространство имен ПОТЕНЦИАЛЬНО.
IMPORT M; //Первые в модуле => клиент.
IMPORT InOut; //Видимы потенциально.
InOut.WriteString(“counter”);
InOut.WriteInt(out);
InOut.Writeln;
FROM InOut Import Writeln, WriteInt.
Видны непосредственно!
uses список имен; //видимы непосредственно.
Оберон: оставлен только библиотечный модуль.
MODULE M;
…
ENDM;
* - экспорт имени.
//имя *
MODULE ST
TYPE STACK* = …
PROCEDUR PUSH* (VAR S: STACK); … PROCEDURE P…
END. => псевдомодуль.
DEFINITION ST;
TYPE STACK = …
PROCEDURE PUSH = …
END st.
=> IMPORT список_имен_модулей.//В Обероне только так.
=> ST.STACK //интерфейс.
Или FROM ST IMPORT R; => R//реализация.
Ада: пакет(спецификация, тело)
Спецификация:
package M is
Определение типов, переменных, констант, заголовков процедур
end M;
Тело:
package body M is
Реализация всех процедур и функций
end M;
package STANDART; //пакет стандартных имен.
Пользовательские пакеты встраиваются в STANDART.
Для подключения пакета используется оператор with: with <Имя пакета>. В случаях,
когда использование полной точечной нотации для доступа к ресурсам пакета обременительно, можно использовать инструкцию спецификатора использования контекста use. Это позволяет обращаться к ресурсам которые предоставляет данный пакет без использования полной точечной нотации, так, как будто они описаны непосредственно в этом же коде. Для обращения к функции из пакета используется оператор «.»: <имя пакета>.<имя функции>
Любой пакет можно вложить в другой.
STANDART
package M1 is
package M1.2 is
…
end M1.2; end M1;
package M2 is
package M2.1 is
package M2.2 is
…
end M2.2; end M2.1; end M2;
Тела вкладываются также, как и спецификации!
Более близкое описание скрывает менее близкое описание одинаковых имен. Как только возникает конфликт имен, имя становится видимым только через уточняющую форму, и непосредственная видимость исчезает.
Область действия любого имени начинается с его определения. Имена пакетов видимы непосредственно в той области, где они были объявлены.
В пакете М1 видимость обьектов из М12 потенциальная. А из внутренней области видимости мы имеем непосредственный доступ во все объемлющие структуры.
Неявный импорт: вместе с одним именем неявно импортируется другое.
переименование:
a renames b; //a теперь описано уже тут
Можно также переименовать операцию «+»:
function “+”(X, Y: vectors.Vector) return vector renames vectors;
Ада, Модула - 2 предоставляет программисту возможность осуществлять переименования. Переименование не создает нового пространства для данных. Оно просто создает новое имя для уже присутствующей сущности.
В С# using
В Java import имя_пакета;//ошибка-нельзя экспортировать пакет в Java!
import имя_класса; //Ok!
import имя_пакета .*;//Ok! – мы импортируем все имена из пакета
В Pascal, Delphi uses
В Оберон 2 переменную можно экспортировать только на чтение.
1.Сравните между собой конструкции "uses" в языке Delphi и "use" в
языке Ада (для чего применяются, сходства, отличия).
Конструкция языка Delphi «uses список_имен_модулей» служит для импорта всех имен, объявленных в интерфейсе модулей из списка. При этом импортированные имена становятся непосредственно видимыми (если нет конфликтов с именами из других модулей).
Конструкция языка Ада «use список_имен_пакетов» обеспечивает непосредственную видимость имен из спецификаций пакетов из списка (если нет конфликтов).
Сходство: конструкции обеспечивают непосредственную видимость имен из интерфейсов (спецификаций) при отсутствии конфликтов.
Различие: в Delphi «uses» импортирует имена из интерфейсов библиотечных модулей, в Аде импорт имен обеспечивается другими конструкциями, а «use» служит только для разрешения непосредственной видимости.
Модульность и технология программирования: проектирование
«сверху-вниз» и «снизу-вверх».
Существует два подхода к проектированию древовидной иерархии модулей:
1) сверху вниз(top-down- подход) - сначала проектируется модуль верхнего уровня, а затем мы опускаемся до более низких уровней.
2) снизу вверх(bottom-up- подход) – сначала проектируются самые нижние модули – они в данной иерархии инкапсулированы и ничего не знают о вышестоящих модулях. Подставляют виртуальные сервисы универсального характера, которые нужны всем.(P. S. Современные объектно-ориентированные системы в данном случае предлагают нам сетевые структуры.) на их базе строятся сервисные модули более высоких уровней – и так далее, пока не дойдем до единого главного модуля всей системы. Недостаток такого подхода: мы никогда точно не знаем, что нам понадобится в будущем.
Понятие класса. Класс как тип данных. Члены класса: функции,
данные. Статические и нестатические члены. Члены - вложенные классы.
Принципиальное отличие класса от модуля заключается в том, что класс – это тип данных, а модуль нет. Но во многих вещах они похожи.
Тип Данных = Структура Данных + Множество Операций над этими данными
В C#, Java всё является классами или находится в классах в качестве статических челнов.
синтаксис в C++.Java,C#:
class Name
{
….
Определение членов класса
…..
}
В Си++ допускается вынесение определений, т.е. В Си++ можно члены класса лишь объявлять. В Java, C# все определения должны быть внутри класса
Java,C#,
T x;
x = new T(«параметры конструктора»);
В первой строчке определяется ссылка на объект (выделяется память для хранения ссылки), место в динамической памяти под объект не отводится. Во второй непосредственно отводится место в динамической памяти («куче») для объекта и адрес
присваивается ссылке на объект.
C++.
T x;
T x(«параметры конструктора»);
T x = T(«параметры конструктора»);
В этих определениях выделяется место не под ссылку на объект, а под сам объект (не в динамической памяти). Чтобы выделить место в динамической памяти, нужно использовать операцию «new»
синтаксис в Delphi:
type T =
class (наследование)
обявление членов класса
end;
Члены класса:
• Члены-данные
• Члены-типв
• Члены-функции (методы)
Чем члены-функции отличаются от обычных функций?
Такой функции при вызове всегда передаётся указатель «this»(в Delphi «self») на объект в памяти, от имени которого вызывается функция.
Dephpi
x: T
x – ссылка, её ещё надо проинициализировать.
В Delphi формальные параметры функций-членов находятся в той же области видимости, что и все остальные члены класса и, следовательно, не могут с ними совпадать.
В Delphi членов-типов нет.
Статические члены
SmallTank class variable
instance variable
class variable – члены-данные класса, которые принадлежат всем экземплярам класса.
instance variable – принадлежат экземплярам класса, у экземпляра своя instance variable.
С точки зрении Си++ статические члены классов отличаются от глобальных только областью видимости.
class T
{
….
static int x;
static void f();
……
}
…
T t;
t.x;//операция доступа
T::x//операция разрешения видимости
t.f();//операция доступа
//или, что тоже самое
T::f() //операция разрешения видимости
Более того, статическим члены данные могут существовать даже тогда, когда нет ни одного экземпляра класса.
Видимость статических членов потенциальная и снять ее, в отличие от модулей, нельзя.
В C#,Java,Delphi обращение к статическим членам происходит только через тип класса.
В статических функциях нет ths/self => в них нельзя использовать нестатические члены класса, т.к. по умолчанию все обращения к нестатическим членам идут через указатель ths/self
В C#, Java статические члены используются намного чаще, чем в Си++, Delphi, т.к. в C#, Java нет глобальных функций и переменных:
public class Name
{
….
public static void Main(string [] args)
….
}
Вложенные типы данных (классы)
class Outer
{
….
class Inner //Inner – обычный класс, но с ограниченной областью
{ //видимости
…..
};
….
};
Наибольшее сходство между классом и модулем достигается если класс содержит только статические методы и поля. При этом такой класс, как правило, реализуется в виде модуля. Статические и нестатические классы. Классы и области видимости.
Все классы являются статическими членами. Ко всем именам правила прав доступа применяются одинаково, т.е. специфика имени не участвует в разрешении прав доступа.
C#
В C# имеется понятие «статического класса»
В C# статический класс – это особый вид класса, внутри которого есть только статические члены-функции и только статические члены-данные.
static class Mod
{
public static void f () { ….;}
public static int i;
}
Статический класс – служит контейнером для статических членов. От статических классов нельзя наследовать. Статические члены – это переменные, которые принадлежат классу целиком.
Нельзя создавать объекты статических классов. Статические классы подчёркивают их чисто модульную природу.
Без «static» - обычный класс в понятии Си++. На вложенность классов не влияет.
Статические члены – набор ресурсов, независимых от какого-либо экземпляра.
Java
Статический импорт – импорт всех статических членов класса. Часто применяется к математическим функциям.
Статические классы в Java:
public class Outer
{
….
public static class Inner //Тоже самое, что и в C#, C++ без «static»
{
…..
};
….
};
Это сделано для того, что доступ к Inner был через Outer
Декларируются внутри основного класса и обозначаются ключевым словом static. Не имеют доступа к членам внешнего класса за исключением статических. Может содержать статические поля, методы и классы, в отличие от других типов внутренних классов в языке Java.
Внутренние классы в Java: без «static»
Декларируются внутри основного класса. В отличие от статических внутренних классов, имеют доступ к членам внешнего класса, например «Outer.this». Не могут содержать определение (но могут наследовать) статических полей, методов и классов (кроме констант).
Понятие специальных функций-членов. Проблема инициализации
объектов и способы ее решения. Конструкторы, деструкторы, операторы
Using и try-finally.
Функции-члены, обладающие семантикой обычных функций-членов, о которых компилятор имеет дополнительную информацию.
Конструктор – порождение, инициализация
Деструктор – уничтожение (В Java и C# не деструкторов, вместо это можно сделать собственный метод Destroy())
У конструктора нет возвращаемого значения.
Т.к. все объекты в C# и Java и Delphi размещаются в динамической памяти, то в этих языках обязательна операция явного размещения объектов:
X = new X(); // В Си++ X * a = new X;
Синтаксис конструкторов и деструкторов:
C++. C#, Java
class X
{
X(«параметры»);// В С# и Java обязательно определение тела
}
Delphi
type X = class
constructor Create; // Имя конструктора произвольное
destructor Destroy; // имя деструктора произвольное
end;
…..
a:X;
….
a:= X.Create;
В C++, C#, Java конструкторы не наследуются, но могут автоматически генерироваться компилятором по определённым правилам.
Классификация конструкторов:
1.Конструктор умолчания X();
2.Конструктор копирования X(X &); X(const X &);
3.Конструктор преобразования X(T); X(T &); X(const T &);
В классах из простанства имён System платформы.NetFramework не определены конструкторы копирования. В С++ автоматически могут генерироваться конструктор умолчания и конструктор копирования
Вместо этого, если это предусмотрено проектировщиками, имеется метод clone();
Java, C#, D – в базовом типе object есть конструктор Create и деструктор Destroy. Соответственно здесь ничего создавать не надо, они уже есть и наследуются (если).
Конструктор умолчания
X a; - подразумевается вызов конструктора по умолчанию (неявно).
X a(); - нельзя, т.к. это прототип функции.
X* px = new X; - нельзя в Java и С#, в С++ - можно.
X* px = new X(); //В C# и Java можно только так
Java, C#: Понятие КУ остается.
Есть понятие инициализация объектов:
class X
{
Z z = new Z(); // Z z; - значение неопределенно.
Int I = 0; //простые инициализации можно выполнять непосредственно в коде самого класса
}
Вызов конструктора базового класса в Java может быть только первым оператором тела конструктора. Если первый оператор отличен от вызова super, то компилятор автоматически вставляет super();//вызов конструктора умолчания базового класса.
Пример на Java:
class A
{
public A(int I) { … }
...
}
class B extends A
{
public B() { super(0); … }
...
}
Пример на C#:
class A
{
public A(int I) { … }
...
}
class B: A
{
public B(): base(0) { … }
...
}
Существует статический конструктор, который вызывается 1 раз до первого использования и до первого обращения к любым членам класса. (С#)
static X() {…………}; //полная форма статического конструктора по умолчанию в языке C#
Java
static{…………….}; //аналог статического конструктора в Jav
M-2, Ада:
Init(); – явная инициализация.
Destroy();
Для определения собственного конструктора и деструктора достаточно было переопределить эти функции.
Конструктор копирования
С++
void f(X x); // передача фактического параметра функции по значению
X f() {return X();}
X a = b; //Вызов КК. ~ X a(b); «Инициализация присваиванием»
(1) X(X&);
(2) operator=(X&)
Если мы не копируем объект, то конструкции (1) и (2) не нужны. Описав прототип этих функций, но не определив их, мы запрещаем копировать объект данного класса. Когда отсутствует и прототип, и объявление, конструктор копирования генерируется автоматически.
С#: object.
В классе Object(общего предка для всех классов), есть защищенный метод MemberwiseClone, возвращающий копию объекта.
По умолчанию копировать нельзя, но в произвольном классе можно самим переопределить.
ICLoneable Clone();
Java:
В этой точки зрения наиболее адекватно проблема решена в Javа. Там существует 4 уровня поддержки копирования.
Интерфейс-маркер – по определению пустой интерфейс(не содержит членов).
Интерфейс – это просто набор методов. Он определяет некий контракт, говорящий о том, что если класс поддерживает некий интерфейс, он должен реализовывать определенный набор методов. А если интерфейс пустой, то все его члены-методы «зашиты» в компилятор.
Интерфейс называется сloneable, когда он пустой.
В Java был введен пустой интерфейс cloneable, содержащий метод Clone(), осуществляющий побитовое поверхностное копирование.
protected object Clone();
Уровни поддержек:
· Полная поддержка копирования – возможность явной реализации. Класс X реализует интерфейс Cloneable:
Сlass X: Cloneable{
//Тут мы должны написать:
public X Clone();
//Допускается также:
public Object Clone();
Метод Clone() может использовать любые члены класса(и приватные тоже.)
};
· Возможна и другая ситуация: полный запрет копированияя: при попытке скопировать объект выбрасываем исключение. Подменяем соответсвующий защищенный метод clone():
class X{
protected Object Clone(){ throw CloneNotSupportedException; }
………………………………….
};
· Условная поддержка: элементы, которые копируются, могут быть под полным запретом.
Пример: коллекция умеет себя копировать, а элементы, из которых она состоит – нет.
class X: Cloneable{
public X Clone throwing CloneNotSupportedException
{
//Для каждого элемента коллекции вызывается метод Clone();
};
…………………………………………………..
};
· Еще одна ситуация – когда мы не наследуем метод Clone()
Метод копирования нельзя переопределять.
D: inherited Create; //inherited – вызов соответствующего конструктора.
Деструктор – функция, которая вызывается автоматически, когда уничтожается объект.
С++, D, C# - ОО модель.
C++
delete p;
Отличие С++ от Delphi – в нем происходит автоматический вызов деструктора.
Общая проблема - в процессе функционирования объекты получают некий ресурс.
В С++ и Delphi мы всегда контролируем, когда ресурсы освобождаются.
Специальные функции в Delphi:
X:=I.Create();
X.Create();
X.Free
C#, Java – Автоматическая сборка мусора.
При динамической сборке мусора сложно определить, когда объект больше не используется.
Image.FromFile(fname);
…
Image.SaveToFile(fname);
Освободить файл можно, когда мы указываем, что с ним не буде работать.
Т.е. сборка мусора здесь не поможет.
=> C#, Java:
try
{
…
} finally {…}
D:
try
{
…
} finally
…
End
Такая вещь, как finally, очень важна. Она будет выполнена независимо от того, как кончился блок.(Это необходимо, так как в C# и в Delphi нету вызова конструктора по умолчанию в конце блока)
IDispose - общий интерфейс освобождения памяти. (C#) Данный метод вызывает финализатор объекта и ставит его в очередь на уничтожение, обеспечивая выполнение деструктора
Dispose();
try { im = … } finally {im.Dispose;}
Вводится специальная конструкция:
using(инициализатор) //инициализатор – T x=expr; x=expr;
блок
~
try {инициализатор} finally {x.Dispose;}
С#, Java: object
Учитывая то, что в Java есть сборщик мусора, там не существует деструктора. В классе Object существует защищенный метод:
protected void finalize();
public void Close();
Есть методики, которые позволяют возродить уничтоженный объект. Но finalize – полностью его удаляет (нельзя вызывать дважды).
В случае, если класс на протяжении своего существования должен освобождать ресурсы не один раз, он обязан содержать метод Close(), который будет это делать. Метод Dispose() вызывается один раз, а close должен быть запрограммирован таким образом, чтобы можно было вызывать его много раз.
Close() – ресурсы освобождены.
В Java метод finalize() вызывается сборщиком мусора. В C# существует деструктор – тонкая обертка для финализзатора finalize().
C#: ~X(){…} – нельзя вызывать явно.
System.Object.finalize – можно вызвать явно.
Сбощик мусора:
mark_and_sweep
Живые и мертвые объекты (ссылки).
Есть стек, в нем ссылки на объекты. Если живой, то помечаем и заносим в таблицу живых объектов, остальные – мертвые, они-то и уничтожаются.
Можно построить КЭШ объектов. Если объект мертвый, то нм нужен он. Но он еще не утилизирован (не успели).
Strong reference – на живой объект.
Weak reference – объект готовится к уничтожение, не пока еще не нуничтожен.
Преобразование типов и классы. Явные и неявные преобразования.
Управление преобразованиями в современных ЯП: проблемы и способы их
решения.
Неявные (автоматически вставленные компилятором).
Int long
А может ли их задавать пользователь.
В Java и Delphi нет возможности описания пользователем неявных преобразований (в Java нельзя перегружать стандартные операторы).
С++, C# – неявные преобразования, задаваемые пользователем разрешены.
Ф: v = expr – можно считывать различные типы данных.
C#:
«Неплоский» класс – это класс, который сожержит в себе ссылки на другие объекты.
Class Vector
{
T* body;
int size;
public:
Vector(int sz) {body = new[size = sz];}
}
Vector v(20); //ok
Vector t(10);
v = t;
v =1; ~ v = Vector(1); // ошибки не будет: сработате оператор преобразования: у нас «случайно» получился конструктор преобразования.
Решение: если мы не хотим, чтобы наш конструктор «использовался» в подобных целях, то с помощью ключевого слова explicit конструктор может стать обычным конструктором. Вот так:
explicit vector(int sj){Body = new T[size=sj];}
Теперь наш конструктор может вызываться только явноV = Vector(1);…
В C# explicit принято по умолчанию. Существует в C# и ключевое слово implicit.
Cвойства (properties).
«Свойство» - это член класса, который с точки зрения обращения к нему выглядит как член-данное, но с точки зрения реализации представлен двумя методами, один из которых возвращает значение свойства, а второй — устанавливает его значение. При этом один из методов может отсутствовать, делая недоступной соответствующую операцию над свойством.
В С++ нет. Все данные по определению закрытые, а вместо операций для свойста есть геттеры и сеттеры.
Есть в Delphi, C#.
Пример для языка Delphi – целое свойство Prop:
type PropSample = class
…
private
procedure SetPropVal(V: integer);
function GetPropVal:integer;
public
property Prop: integer read SetPropVal write GetPropVal;
…
end;
published//все опубликованные свойства появляются в интегрированной среде разработки.
property X: T read_x; write_x;
С#:
class X
{
T prop {get{…} set{…}} //value является зарезервированным в set.
}
…
X a = new X();
a.prop = t1;
T t2 = a.prop;
Классы и перегрузка имен. Перегрузка встроенных знаков операций.
В Java нет перегрузки.
С++: T operator+(T x1, T x2);
В Си++ typedef задаёт синоним типа, а не создаёт новый тип.
typedef int a;
void f(a x)
void f(int x) Перегрузки не будет, т.к. «a» не новый тип(=> будет ошибка)
Ада: function “+” (x1, x2: T) return T;
Перегружаемый операторы в C#:
· базисный
· арифметический
· побитовый
[ ] в C# перегружать нельзя(в отличие от С++, в которых Страуструп решил проблему в общем случае). Общий синтаксис:
T operator *(операнд) {…………………}
Один из недостатков перегрузки операций – несимметричность операндов:
a * b => a.operator*(b)
Так перегружаются операции в C#:
static T operator *(T t1, T t2) {………………………};//в шарпе – только статический.
Аналогично и с операторами преобразования: они обызаны быть статическими члеами:
static operator T(X a){…………………}
Итераторы и индексаторы.
Механизм индексаторов в C# - компенсирует отсутствие возможности перегрузки операции индексирования.
Синтаксис:
T this (I index){………………… }
Java – вложенные статические и нестатические классы
class Outer{
static class Inner{…….};
Нестатический блок-класс имеет ссылку на Outer.this
Inner in = this.newInner();//если мы пишем, естественно, внутри класса Outer.
А так – вместо this может стоять ссылка на любой оъект класса Outer.
Outer invoice;
Inner = invoice.newInner();
В Java2 появилось понятие локального внутреннего класса
Iterable f(object[] objs)
{
class Local: Iterable {int i; Local() {i = 0;}… if (i>objs) …}
return new Local(C);
}
Имеет мест доступ к локальным переменным функции, если в данном блоке не изменяются.
Iterable f(final Object{ } obj)
{
class Local Implements Iterable{
.................................
};
return new Local();
}
Такой класс не может быть сделан внешним.
Наличие локальных классов, заметим, позволяет более компактно записывать код.
Если локальные переменные final, то это значит, что они не могут менять своего значения в теле функции. сlosure («захват» в переводе) – это замыкание блока. Означает, что локальные переменные блока остаются связанными, даже еси мы выходим из блока.
Делегат – прообраз функционального типа данных. Вызывать делегат – это значит по очереди выбрать все элементы из цепочки.
Пример.
delegate int Processor(int i);
Общий синтаксис:
delegate прототип функции
Наш делегат – это именно список функций.
В C# появились:
p = new delegate(int i) {……тело соответствующей функции………….}
Хитрость анонимных делегатов в том, что они тоже могут замыкать соответствующие переменные.
int k;
p = new delegate(int i){ return k+i; }
//переменная k попадает в замыкание, становится захваченной
Теперь p - это функция, которая к k прибавляет i.
int j = p(3);
k=1;
j=p(3);
Классы и стандартные библиотеки. Встроенные классы стандартной
библиотеки.
6. Инкапсуляция и абстрактные типы данных.
Понятие инкапсуляции. Единицы и атомы защиты. Понятие
абстрактного типа данных (АТД) и его достоинства.
Абстрактный тип данных = множество операций. (основной вид современного программирования)
Абстрактный тип данных - это набор процедур, объявленный в отдельном пакете. Здесь, оказывается, есть инкапсуляция:
· единицы инкапсуляции: тип или экземпляр типа
· атомы инкапсуляции: отдельные поля и члены типа или весь тип.
В большинстве языков программирования единица инкапсуляции – тип.
В языке Оберон есть защита отдельных членов, что позволяет по отдельности экспортировать отдельные поля.
Ада и Modula-2 инкапсулируют целиком весь тип: такая тактика вынуждает нас к полной инкапсуляции
Атомы инкапсуляции – минимально возможные данные, которые можно скрыть от пользователя. Для всех
класс – ориентированных языков атомы инкапсуляции – это поля класса, а таких языках как Ада, Модула – 2 – минимальным атомом является весь класс, то есть хороший программист на таких языках всегда использует абстрактные типы данных.
И нкапсуляция и логические модули. Управление видимостью.
Реализация АТД в модульных языках программирования (Ада, Оберон,
Модула).
М-2: скрытые ТД
DEFINITION MODULE STACKS;
FROM MYTYPES IMPORT T; //возможность получить доступ к типу, который описан в другом модуле
TYPE STACK; (*скрытый ТД*) //компилятор не знает, что это.
PROCEDURE PUSH
INIT
DESTROY
END STACKS.
DEF -> транслируется в SYM(таблица символов) и OBJ(реализация).
STACK ~ INTEGER или POINTER
TYPE STACK = POINTER TO STACKREC
STACKREC = RECORD … END
:= (shallow, copy), = (равно), # (не равно)
Ада 83:
приватный ТД (~скрытый ТД)
ограниченно приватный ТД
package stacks is type stack is private;
… - описание всех заголовков.
private
… - описание всех приватных структур данных.
:=, =, /=
ограниченно приватный:
type T is limited privaty;
… - оперции.
private type T is …;
Инкапсуляция и классы. Управление видимостью и управление
доступом. Пространства имен и инкапсуляция. Реализация АТД с помощью
понятия класса.
Управление инкапсуляцией:
• Управление доступом – C++, C#, D
• Управление видимостью – Java
Управление видимостью – «private»-членов как бы просто нет для других классов, они «невидимы».
Управление доступом – все не скрытые (не переопределённые) члены видны, т.е. компилятор постоянно «знает» об их существовании, но при обращении проверяются права на доступ. При попытке обращения к недоступному члену выдаётся ошибка.
Три уровня инкапсуляции:
1.public
2.private
3.protected
«свой» - член данного класса
«чужой» - все внешние классы
«свои» - члены наследованных классов
public разрешает доступ всем
private разрешает доступ только «своему»
protected разрешает доступ «своим» и «своему»
Если требуется, чтобы доступ к приватным членам был не только у «своего», можно для этой этого объявить нужную дружественную конструкцию в теле класса:
friend «объявление друга»;// Можно писать сразу определение. Другом может быть
функция или целый класс
friend «прототип глобальной функции»
friend «прототип функции-члена другого класса»
friend class «имя класса-друга»;// Все методы этого класса становятся дружественными
В Delphi, C#, Java друзей нет
В них реализованы этот механизм реализован немного по-другому:
Delphi UNIT
Java package
C# assembly
В Java по умолчанию пакетный доступ. Это значит, что использовать класс может каждый класс из этого пакета. Если класс объявить как «public class …», то он будет доступен и вне пакета. Использовать класс – наследовать, создавать объекты.
C#:
Сборка – надъязыковое понятие в.NerFramework. Сборка представляет собой совокупность файлов + манифест сборки, Любая сборка, статическая или динамическая, содержит коллекцию данных с описанием того, как ее элементы связаны друг с другом. Эти метаданные содержатся в манифесте сборки. Манифест сборки содержит все метаданные, необходимые для задания требований сборки к версиям и удостоверения безопасности, а также все метаданные, необходимые для определения области действия сборки и разрешения
ссылок на ресурсы и классы.
Внутри сборки идёт разделение на пространства имён, которые содержат описания классов.
Для использования какого-либо пространства имён нужно сначала подключить сборку, содержащую его.
Пространство имён может быть «размазана» по нескольким сборкам.
В C# для членов классов имеются следующие квалификаторы доступа:
• public
• private // по умолчанию
• protected
• internal – член доступен только в классах из сборки
• internal protected – член доступен только в классах-наследниках, находящихся в сборке
Для самих классов:
• public – класс можно использовать в любых классах
• internal – класс можно использовать только в классах из его сборки (по умолчанию)
Delphi
type T = class
…. // здесь объявляются члены, видимые везде их данного модуля и не видимые
// других
public
….
protected
….
private
…..
end;
UNIT – единица дистрибуции
Принцип разделения определения, реализации и использования
(РОРИ). Эволюция принципа РОРИ в современных ЯП.
РОРИ – метод, когда реализация скрыта от пользователя. Пользователю доступен лишь интерфейс, а реализация инкапсулирована.
Тип данных = множество значений + множество операций
Абстрактный тип данных = множество операций
7. Модульность и раздельная трансляция
Виды трансляции. Физические модули. Программная и трансляционная
библиотеки. Раздельная трансляция: зависимая и независимая. Недостатки