Методические указания для студентов 1 курса факультета математики, механики и компьютерных наук
Вид материала | Методические указания |
- Программа курса «история и методология математики» для студентов дневного отделения, 151.46kb.
- Методические указания курса «культурология» Для студентов биологического факультета, 331.04kb.
- Войта Елена Александровна, магистрант факультета математики, механики и компьютерных, 129.35kb.
- Методические указания по изучению дисциплины Для студентов 4 курса заочного факультета, 3497.6kb.
- Методические указания и контрольные задания по английскому языку для студентов II курса, 375.13kb.
- И. И. Мечникова Институт математики, экономики и механики Кафедра математического обеспечения, 900.66kb.
- Методические указания к изучению курса «История мифологии» для студентов 1 курса факультета, 420.44kb.
- Методические указания к выполнению курсовой работы по дисциплине «Основы научных исследований», 403.99kb.
- Отчет по самообследованию дополнительной профессиональной программы для получения дополнительной, 317.2kb.
- Методические указания к выполнению курсовой работы по дисциплине «Оценка качества продовольственного, 856.1kb.
3Абстрактные типы данных и их реализация. Классы
До сих пор мы сталкивались с конкретными типами данных, простыми или составными (массивы, записи, списки). Каждый тип данных характеризуется:
- набором допустимых значений;
- операциями, которые можно выполнять над данными этого типа;
- внутренним представлением данных этого типа в оперативной памяти.
В большинстве случаев для клиента, использующего тип данных, важны лишь первые два пункта. Например, при работе с данными вещественного типа обычно достаточно знать, что над ними можно производить арифметические операции, а также необходимо знать минимальное и максимальное положительное вещественное значение, представимое в памяти.
Абстрактный тип данных (АТД) – это тип данных, доступ к которым осуществляется только через некоторый набор действий (операций, подпрограмм). Этот набор действий называется интерфейсом абстрактного типа данных.
Чтобы абстрактный тип данных можно было использовать, необходимо дать реализацию всех операций, входящих в его интерфейс. Реализация при этом должна быть скрыта от клиента; данное правило называется принципом сокрытия реализации. Деление абстрактного типа данных на интерфейс и реализацию, а также сокрытие реализации, имеет несколько важных последствий. Во-первых, программист, использующий тип данных, может работать с ним только через его интерфейс, то есть вызывает только те операции, которые были предусмотрены разработчиком типа данных. Во-вторых, впоследствии разработчик абстрактного типа данных может поменять его реализацию без изменения интерфейса, что не затронет клиентские программы. Тем самым обеспечивается независимость пользователя типа данных от его разработчика.
Первый шаг реализации абстрактного типа данных – выбор его внутреннего представления. Если абстрактный тип данных достаточно сложен, то в качестве внутреннего представления выбирают некоторую структуру данных. Помимо структуры данных, для реализации АТД используются некоторые средства языка программирования, обеспечивающие отделение реализации от интерфейса и сокрытие реализации. В языке Паскаль такими языковыми средствами являются модули и классы.
Далее мы приведем простой и часто используемый абстрактный тип данных, называемый стеком. Будут даны две его реализации: с использованием массива и односвязного списка. В качестве языковых средств будут вначале использованы модули, а затем классы.
3.1АТД «Стек» и его реализация с помощью модуля
Стек – это абстрактный тип данных, состоящий из последовательности элементов, которые можно добавлять и извлекать из этой последовательности только с одного конца, называемого вершиной стека. Кроме того, можно проверять стек на пустоту.
Примером стека является колода карт, если для нее разрешено лишь добавлять или снимать карты с вершины колоды; действия с серединой колоды запрещены. Другой пример – магазин автомата, для которого также можно либо добавить патрон первым в магазин, либо убрать первый патрон из магазина.
Говорят, что стек реализует принцип LIFO – last in first out (последним пришел – первым вышел): элемент, положенный на стек последним, выходит из него первым.
Будем считать, что стек хранит элементы целого типа и составим его интерфейс:
procedure Push(i: integer);
function Pop: integer;
function Top: integer;
function IsEmpty: boolean;
Процедура Push кладет элемент i на вершину стека, функция Pop снимает элемент с вершины стека и возвращает его значение. Функция Top возвращает значение элемента на вершине стека, не снимая его (вместо имени Top часто используют также имя Peek). Функция IsEmpty возвращает логическое значение: пуст ли стек. Очевидно, при выполнении операций Pop и Top стек должен быть непустым, что контролируется вызовом процедуры Assert.
Реализация АТД «Стек» на базе массива
Реализуем АТД «Стек» в виде модуля. Модули являются удобным языковым средством для создания АТД, поскольку содержат секции интерфейса и реализации. Интерфейс стека поместим в интерфейсную секцию модуля, а реализацию – в секцию реализации модуля.
Элементы стека будем хранить в целочисленном массиве, индексируемом от нуля. Индекс верхнего элемента стека будем хранить в переменной t. Если t=-1, то это означает, что стек пуст. Поскольку стек реализован на базе массива фиксированного размера, то при выполнении операции Push он может переполниться. Эта особая ситуация контролируется при помощи вызова процедуры Assert.
Далее приводится текст модуля IntStack, реализующего стек целых чисел.
unit IntStack;
interface
procedure Push(i: integer);
function Pop: integer;
function Top: integer;
function IsEmpty: boolean;
implementation
const sz=1000;
var
a: array [0..sz-1] of integer;
t: integer;
procedure Push(i: integer);
begin
Inc(t);
Assert(t<=sz);
a[t]:=i;
end;
function Pop: integer;
begin
Assert(not IsEmpty);
Result:=a[t];
Dec(t);
end;
function Top: integer;
begin
Assert(not IsEmpty);
Result:=a[t]
end;
function IsEmpty: boolean;
begin
Result := t=-1
end;
initialization
t:=-1;
end;
Отметим, что массив a, предназначенный для хранения элементов стека, и переменная t описаны в секции реализации модуля, поэтому они не видны из других модулей. Тем самым, обеспечивается принцип сокрытия реализации, позволяющий работать с абстрактным типом данных только через его интерфейс.
Вычисление постфиксного выражения
Составим программу, вычисляющую значение выражения, записанного в постфиксной нотации. Для простоты договоримся, что в выражении могут присутствовать только знаки операций «+» и «*», целые однозначные числа и, возможно, пробелы. Например:
5 9 8 + 4 6 * * 7 + *
Пусть выражение хранится в строке. Для его вычисления воспользуемся стеком целых чисел. Если встречено число, то кладем его на стек, если же знак операции, то снимаем со стека два последних числа, производим над ними указанную операцию и кладем результат на стек. Например, обработка операции сложения имеет вид: Push(Pop+Pop).
Проследим работу алгоритма для строки, приведенной выше. С этой целью выведем содержимое стека до и после обработки знака операции:
5 9 8 +
5 17
5 17 4 6 *
5 17 24
5 17 24 *
5 408
5 408 7 +
5 415
5 415 *
2075 (результат)
Заметим, что при обработке знака операции в стеке должно быть как минимум два элемента, а после обработки знака операции количество элементов в стеке уменьшается на 1. После обработки всей строки результат лежит на вершине стека. Если после его снятия стек не пуст, значит, исходное выражение в постфиксной нотации являлось ошибочным.
Ниже приводится программа вычисления значения постфиксного выражения, использующая АТД «Стек», подключаемый в виде модуля IntStack:
uses SysUtils,IntStack;
var
a: string;
i: integer;
begin
read(a);
for i:=1 to Length(a) do
case a[i] of
'0'..'9': Push(StrToInt(a[i]));
'+': Push(Pop+Pop);
'*': Push(Pop*Pop);
end;
Assert(IsEmpty);
writeln(Pop);
end.
Несомненное достоинство данной реализации стека состоит в том, что она скрыта в модуле, и клиентская программа работает со стеком только через его интерфейс. Именно это обстоятельство позволяет нам поменять реализацию стека на более эффективную, не изменяя клиентскую программу.
Избавимся от возможной ошибки переполнения стека. Для этого заменим массив, использующийся для хранения элементов стека, односвязным списком.
Реализация АТД «Стек» на базе линейного списка
uses IntStack;
... // интерфейс остается тем же самым
implementation
type
PNode=Node;
Node=record
data: integer;
next: PNode;
end;
var head: PNode; // указатель на вершину стека
function NewNode(data: integer; next: PNode): PNode;
begin
New(Result);
Result.data:=data;
Result.next:=next;
end;
procedure Push(i: integer);
begin
head:=NewNode(i,head);
end;
function Pop: integer;
var v: PNode;
begin
Assert(not IsEmpty);
v:=head;
head:=head.next;
Result:=v.data;
Dispose(v);
end;
function Top: integer;
begin
Assert(not IsEmpty);
Result:=head.data;
end;
function IsEmpty: boolean;
begin
Result := head=nil;
end;
initialization
head:=nil;
finalization
while not IsEmpty do
Pop;
end.
Добавление элемента в стек и удаление элемента из стека осуществляются с использованием алгоритмов добавления и удаления из начала односвязного списка. Отметим еще раз, что клиентская программа в результате такой замены реализации стека не изменится.
Реализация стека с помощью модулей имеет один большой недостаток: в программе можно использовать только один стек. Таким образом, с помощью модуля нельзя создать полноценный тип данных, позволяющий описывать несколько переменных этого типа.
Для решения указанной проблемы в современных языках программирования введена особая языковая конструкция, называемая классом. Класс сочетает в себе свойства модуля и типа данных, позволяя описывать и использовать несколько переменных классового типа, называемых объектами. Использование классов при составлении программ называется программированием, ориентированным на объекты, или объектно-ориентированным программированием. Отметим также, что важной составной частью объектно-ориентированного программирования являются наследование классов и полиморфизм, с которыми мы познакомимся в дальнейшем.