Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
38
Добавлен:
23.03.2015
Размер:
884.22 Кб
Скачать

7.2. Абстракция данных

В этом разделе мы опишем некоторый подход для специфика­ции, использования и реализации абстракций данных при про­граммировании на языке Паскаль. Этот подход основывается сугубо на соглашениях по программированию. В языке CLU, наоборот, — компилятор обеспечивает непосредственную поддерж­ку этих абстракций. Данные соглашения разработаны в основном для того, чтобы показать, что мы можем реализовать абстракции данных, не разрабатывая повторно какие-либо модули, которые их используют, и что можем возвращать из функций абстрактные значения.

На рис. 7.1 показана спецификация абстракции данных для очереди целых чисел. В этой абстракции задается тип intqueue и набор операций, которые по соглашению допускаются для ра­боты с представлением этого типа. Имена всех операций начи­наются с префикса q. Будем следовать этому соглашению, чтобы избежать конфликтов с именами операций, используемых для дру­гих типов данных.

Отметим, что, несмотря на тот факт, что обе операции q-ap-pend и q-remfirst формально модифицируют очередь q, сама очередь q не передается, как некоторый тип var. Мы реализуем тип данных intqueue и другие абстрактные типы, используя ука­затели на неупорядоченный массив. Когда мы передаем в проце­дуру некоторый абстрактный объект, то, следовательно всегда передаем некоторую ссылку. Чтобы модифицировать абстрактный объект, модифицируем тот объект, на который указывает ссылка, а не саму ссылку. Таким образом, абстрактные объекты никогда не передаются, как параметры типа var.

Наше решение хранить абстрактные объекты в неупорядочен­ном массиве продиктовано тем, что надо реализовать типы, чьи объекты изменяют свой размер по мере работы программы. Мы настаиваем на том, чтобы представление было некоторой ссыл».

144 Глава 7

intqueue = data type is q_new, q.isempty, q_append,

q-remfirst

Описание

Типы данных intqueue используются для хранения значений типа целого числа. Элементы могут извлекаться из очереди или удаляться из нее только методом обратного магазинного типа («первым пришел — первым обслужен») Операции function q_new: intqueue

effects Возвращает новую очередь без элементов в ней. function qJsempty (q: intqueue): boolean

effects Возвращает значение true, если в очереди q нет элементов, в про­тивном случае возвращает значение false. procedure q .append (q; intqueue; e: integer) modifies q

effects Добавляет элемент e в конец очереди q. function q_rernfirst (q: intqueue): integer requires чтобы очередь q была не пустой, modifies q effects Возвращает элемент из начала очереди q и удаляет этот элемент из

очереди q. end intqueue

Рис. 7.1. Спецификация очереди целых чисел.

кой, чтобы гарантировать то, что абстрактные объекты могут быть возвращены при помощи функций. Если бы, например, пред­ставление было некоторой записью, содержащей в качестве своей компоненты некоторый указатель, то его нельзя было бы вернуть при помощи функции. И наконец, использование указателя га-раш нрует эффективность при передаче абстрактных объектов по значению.

По соглашению объявление типа представления всегда будет в такой форме:

type type: iai-ne == ^typename.rep;

где typename.rep является некоторым типом, который описывает «реальное» представление. Например, для типа intqueue мы ис­пользуем такой тип представления:

type

intqueue = ^intqueue.rep; intqueue. rep = '[•intqLleue.elem; intqueue_elem == record val: integer; next: intqueue. rep; end;

Следуя нашим соглашениям, просто объявить переменные не­которого абстрактного типа, например,

var q: intqueue;

Смысл этого объявления состоит в том, что в стеке отводится пространство для некоторой неинициализированной переменной q.

Использование языка Паскаль

Размер этого пространства такой, чтобы поместился некоторый указатель, как в языке CLLJ.

Когда очередь q объявлена, выделяется только указатель, и, следовательно, когда организуется выход из процедуры, в ко­торой объявлена очередь q, освобождается только этот указатель. Место для элементов очереди q выделяется в операциях типа intqueue при помощи явных обращений к примитиву new языка Паскаль. Поскольку в языке Паскаль сборка мусора автоматически не осуществляется, пространство, занятое этими элементами, должно быть явно освобождено той программой, в которой ис­пользуется данная очередь. Для этой цели в языке Паскаль имеется примитив dispose. Указатель задается как аргумент для этого примитива, он следует этому указателю и освобождает то пространство, на которое указывает данный указатель. Размер освобождаемого пространства зависит от типа указателя. Прими­тив dispose (q) будет освобождать пространство, занятое одним указателем типа intqueue.rep. Это означает, что вся память, занимаемая элементами, будет оставаться занятой, и это может привести к особенно неприятной ошибке в программе, называе­мой «утечкой памяти». Утечка памяти происходит тогда, когда не была освобождена память, которая должна быть освобождена. Единственным симптомом этой ошибки является то, что через некоторое время неупорядоченный массив постепенно исчерпы­вается. К тому времени, когда в программе кончится такое про­странство, может выполниться очень много команд от того места, где произошла сама утечка.

Чтобы гарантировать правильное освобождение пространсчЕа, для каждого типа дожна существовать некоторая дополнительная операция, которая называется destroy. Операция destroy исполь­зует один аргумент — некоторый объект данного типа, и она освобождает все пространство в неупорядоченном массиве, свя­занное с этим объектом. Значение данного объекта при возврате из операции destroy не определено. Приведем спецификацию процедуры q.destroy.

procedure q. destroy (q: intqueue) modilSes q

efiects В неупорядоченном массиве освобождается вся память, занимаемая очередью q.

По соглашению пользователи никогда не освобождают абст­рактные объекты непосредственно. Вмесго этого они обращаются к операции destroy для типа данного объекта. При этом согла­шении размер освобождаемого пространства находится под кон­тролем. При этом, однако, не гарантируется, что к освобожден­ному объекту не будет больше ссылок. Если такая ссылка сущест­вует, то после освобождения данного объекта она становится «повисающей ссылкой». Если делается попытка получить доступ

146 Глава 7

{Объявление типа представления для типа данных intqueue) type

intqueue = ^intqueiie_rep; intqueue_rep == ^г^иеие.^ет; intqueue_e!em == record val: integtr; next: intqtieue_rep; end;

{Начало реализации операций для типа данных intqueue} tunction q_new: intqueue; var q; intqueue; begin new (q); qf :== nil; q_new := q end {функция q_new};

function qJsempty (q: intqueue): boolean; begin qJsempty := (q^ = nil) end {функция qJsempty};

procedure q_append (q: intqueue; e: integer); var last_elem, elem: intqueue_rep; begin new (elem); ^ет^.уа) := e; elernf.next .'== nil; if qJsempty (q) then q^ := elem else begin last_elem :== qf; while last.elernf.next () nil do

last_elem :== last.elemf.next; last.elem^.next :== elem end;

end {процедура q_append}; tunction q_remfirst (q: intqueue): Integer; var oldq: infqueue_rep; begin

if qJsempty (q) (He удовлетворяются требования предложения requires} then failure (' К фрикции q_remfirst обратились с пустой очередью") else begin q.rernfirst :== q^.val; oldq :== qf;

qt ••= qTt-"ext:

dispose (oldq) {Освободить пространство, занимаемое удаленным элементом} end

end {функция q_rennfirst); procedure q_destroy (q: intqlieue) •var next.elem, old_elem: intqueue_rep; begin next.elem :•=. <\\', while next_elem ( ) nil do begin old_elem := next_elem; next_elem :=г next_elem•l•. next; dispose (old^elem); end; dispose (q) end {процедура q_destroy}; {Конец реализации операций типа данных intqueue}

Рис. 7,2. Реализация операций типа данных intqueue.

Использование языка Паскаль

Рис. 7.3. Очереди ql и q2 совместно используют одну и ту же очередь.

к памяти через повисающую ссылку, то может быть прочитана бессмысленная информация. Еще хуже модификации, сделанные через повисающие ссылки. Они могут нарушить непротиворечи­вость других объектов. Хотя при реализации па языке Паскаль можно обнаружить повисающие ссылки, такое обнаружение тру­доемко и поэтому обычно не делается. На рис. 7.2 приведена реализация типа данных intqueue, включая и операцию destroy.

По нашему соглашению следует, что каждый абстрактный тип, который представляется некоторым указателем, гарантирует, что присваивание абстрактных значений имеет тот же самый смысл, что и присваивание на языке CLU. Это не эквивалентно тому при­сваиванию, которое существует в языке Паскаль для встроенных в него типов. На языке Паскаль, например, присваивание мас­сива а 1: == а2 приводит к копированию массива а2. Последую­щие изменения в массиве а2 не влияют на массив а1. Если, с дру­гой стороны, мы напишем ql: = q2, где ql и q2 являются очере­дями целых чисел типа intqueue, то будет копироваться только указатель, а очереди ql и q2 будут совместно использовать оче­редь intqueue.

Важно, чтобы это совместное использование правильно рабо­тало. Последующие изменения в очереди q2 должны отражаться в очереди q), и наоборот. Мы достигаем этого, употребляя еще один уровень косвенного указания в представлении. Вместо того чтобы указывать непосредственно на первый элемент очереди, заголовки очередей ql и q2 указывают на некоторый указатель, который уже указывает на первый элемент очереди (рис. 7.3). Это позволяет нам удалять первый элемент из очереди и отражать это для обеих очередей ql и q2. Такое косвенное указание могло бы быть также сделано посредством некоторой записи, а не просто указателя. Например, предположим, что мы решили хранить размер очереди в представлении (это было бы полезным, если бы очереди целых чисел intqueue имели операцию size). Тогда бы мы задали следующее: intqueue_rep = record size: integer; first; lintqueue_ elem; end

148 Глава 7

Рис. 7.4. Очереди ql и q2 не используют совместно одну и ту же очередь.

а совместное использование все равно правильно бы paбo^aлo.

Часто бывает полезным включить в "тип некоторую функцию копирования, например функцию q_copy с определением, приве­денным в гл. 4. Функции копирования должны обеспечивать пол­ную неразделяемую копию аргументов. Они могут быть исполь­зованы всегда, когда мы хотим выполнить присвоение обычным семантическим правилом языка Паскаль. Например, мы могли бы написать ql: = q_copy (q2), а не ql: = q2. На рис. 7.4 показано отсутствие совместного использования в том случае, когда оче­реди ql присваивается значение функции q.copy (q2). Надо иметь в виду, однако, что эти функции копирования не будут вызываться компиляторами как часть неявного присваивания, например, при обращении по значению.

И наконец, отметим, что наш метод не препятствует тому, чтобы программы получали доступ к представлению вне реали­зации некоторого типа. По нашему соглашению мы просто счи­таем такой доступ не по правилам.

7.3. Полиморфные абстракции

Параметры типа языка CLU позволяют нам построить поли­морфные абстракции, т. е. абстракции, которые могут быть ис­пользованы для объектов различного типа, В языке Паскаль имеется некоторый встроенный полиморфизм. Генератор типа массивов, например, может быть использован для объявления массивов с различными границами и элементами различных ти­пов. Однако в языке Паскаль почти не обеспечивается определен­ный пользователем полиморфизм. Конечно, это не остановит нас перед проектированием полиморфных абстракций. Специфика­ция таких абстракций достаточно очевидна. Мы используем син­таксис, аналогичный тому, который использовался для специфи-

Использование. языка Паскаль

queue = data type retype: type] is q [etype]_new, q [etype]Jsempty, q [etype]_append, q [etype]_remiirst, q [etype].destroy

Описание

Очереди используются для хранения значений типа данных etype. Эле­менты могут извлекаться из очереди или удаляться из нее только методом обрат­ного магазинного типа («первым пришел — первым обслужен»).

procedure q ietype]_append (q: queue [etype]; e; etype) modifies q efiects Добавляет элемент e в конец очереди q.

end queue Рис. 7.5. Частичная спецификация параметрической счереди.

кации параметрических абстракций при реализации на языке CLU. Пример параметрической очереди приведен па рис. 7.5.

Поскольку язык Паскаль не обеспечивает реализацию пара­метрических абстракций, мы опять полагаемся па соглашение по программированию. Реализация параметрической очереди может выглядеть совсем как реализация inna данных intqueue. Объяв­ление типа представления приводится на рис. 7,6.

type

queue [etypeJ == I queue [etype ]_rep; queue [etype ]_rep == ]quew [etype]_eleiT]; queue [etype ]_e!em == record val: etype; next: queue [etype ]_rep; end;

Рис.. 7.6. Тип представления для реализации типа queue [ etype].

Эти объявления не верны в смысле языка Паскаль, поскольку они содержат такие неверные идентификаторы, как queue letype!. В реализацию этих операций мы вводим также синтаксические ошибки. Эти синтаксические ошибки предотвращают оттого, чтобы программисты случайно не использовали нереализованные пара­метрические абстракции. Реализация операций аналогична реали­зации, приведенной на рис. 7.2, за исключением того, что пре­фикс q заменяем на префикс queue [etype!, а идентификаторы intqueue и integer—соответственно на queue letype] и etype. Реализация операции q letypeLappend приведена на рис. 7.7.

Прежде чем использовать в программе реализацию типа дан­ных queue [etype], мы должны использовать редактор текстсв, чтобы заменить каждое вхождение текста queue [etype] на неко-чорый удобный незапрещенный идентификатор, например на intq. Кроме того, надо заменить все оставшиеся вхождения иден-тификачора etype на некоторый уже известный тип, например

150 Глава 7

procedure q fetypeJ .append (q: queue [etype); e: etype); var )ast_elem, elem: queue [etype ]_rep; begin new (elem); elem+.val := e; elernT.next := nil; if q fetypeLisempty (q) then <\\ := elem else beiin ]ast_e]em := q^; while last_eleml•.next () nil do

last_elem := iast.eleml.next; last.elernf.next := elem end

end {процедура q [etypeLappend};

Рис. 7.7. Реализация процедуры q [etype]. append.

type intq s= •l•intq_гep; intq.rep == •l•intq_elem^ intq_elem = record val: ihteger; next: intq_rep; end;

procedure intq.append (q: intq; e: integer); var last_elem, elem; irftq.rep; begin new (elem); elem•r.val := e; elernf.next :=: nil; if intqJsempty (q) then •l' := elem else begin last_elem :== q•r,^ while last_elemf.next () nil do last_elem ;= last_elem•l•. next; last.elernf.next :== elem end

:• end {процедура intq.append};

Рис. 7.8. Конкретная реализация типа данных queue [etypel»

на integer, и надо изменить имена операций, заменив префикс, например переименовав q [etype Lappend в intq.append. На рис. 7.8 показана конкретная реализация объявления типа, взя­того из рис. 7.8 g использованием реализации процедуры из рио. 7.7.

Соседние файлы в папке POSIBNIK