- •1. Введение
- •1.1. Декомпозиция и абстракция
- •1.2. Абстракция
- •1.2.1. Абстракция через параметризацию
- •1.2.2. Абстракция через спецификацию
- •1.2.3. Виды абстракций
- •3. Процедурная абстракция
- •3.2. Спецификации
- •3.3. Спецификации процедурных абстракций
- •3.4. Реализация процедур
- •3.5. Более обобщенные процедуры
- •3.6. Создание процедурных абстракций
- •3.7. Заключение
- •4. Абстракции данных
- •4.1. Спецификации для абстракций данных
- •4.2.1. Реализация на языке clu
- •4.2.2. Замечания по поводу операций up и down
- •4.3. Использование абстракций данных
- •4.4. Реализация полиномов
- •4.5. Пояснения для понимания реализаций
- •4.5.1. Функция абстракции
- •4.5.2. Инвариант представления
- •4.6.3. Сохранение инварианта представления
- •4.5.4. Изменяемые представления
- •4.6. Параметризованные абстракции данных
- •4.7. Списки
- •4.8. Упорядоченные списки
4.2.1. Реализация на языке clu
Хотя, конечно, возможно реализовать типы на языках, которые специально не приспособлены для этой цели, более удобно, если тип может быть реализован как отдельный программный модуль. Язык CLU предоставляет такую программную единицу. Эта единица называется кластер. (CLU, отметим, — это первые три буквы слова cluster.) Кластер включает в себя следующее.
1. Заголовок, в котором придаются имена реализуемому типу и операциям. Этот заголовок похож на заголовок в спецификации.
2. Определение представления, выбранного в данной реализации.
3. Реализации примитивных операций. Дополнительные программы (процедуры и итераторы) также могут быть включены в кластер, но обращаться извне можно только к тем, которым приданы имена в заголовке.
Общий вид кластера представлен на рис. 4.4. dnarne = cluster is % список имен операций re? = и здесь дается описание представления
% здесь помещаются реализации операций % если необходимо, здесь могут быть представлены % вспомогательные программы
end dnarne Рис. 4.4. Общий вид кластера.
Для представления наборов целых чисел с помощью массивов, например, мы указываем
гер = array tint ]
Эта строка выполняет две функции) информирует компилятор CLU о том, что в этом кластере представлением является массив целых чисел и что в теле кластера обозначение гер может использоваться в качестве аббревиатуры для этого представленного массивом типа.
Внутри кластера должны быть доступны как абстрактный тип, так и тип представления. Более того, должна иметься возможность перехода от одного типа к другому. Например, операция insert получает в качестве аргумента набор целых чисел, но для реализации insert необходимо использовать массив, который представляет этот набор.
Возможность перехода от абстрактного типа к типу представления и обратно в языке CLU обеспечивают ^две специальные операции: up и down. Операция up использует в качестве аргумента объект представления и в качестве результата выдает абстрактный объект, а операция down осуществляет обратное действие. Для того чтобы обеспечить преобразование абстрактного типа в тип представления и обратно, каждый кластер имеет свою собственную версию операций up и down. Эти операции автоматически определяются компилятором CLU. Например, в кластере процедуры intset компилятор порождает операции up и down со следующими заголовками:
up = proc (a: array tint 1) returns (intset) down == proc (s: intset) returns (array [int])
Операции up и down могут использоваться только в кластерах и всегда осуществляют переход от абстрактного типа к типу представления и обратно только для кластера, в котором они появились. Поэтому операции up и down не могут отрицательно повлиять на контроль типов языка CLU.
Хотя операции up и down могут появиться, где угодно, среди операций кластера, они в основном используются в одном из двух случаев. В первом случае, когда операция использует в качестве аргумента абстрактный объект, для преобразования этого объекта в тип представления используется операция down. Во втором случае, когда операция возвращает заново созданный абстрактный объект, сразу же перед возвратом для преобразования объекта представления в абстрактный тип используется операция up. Для удобства работы язык CLU предоставляет специально зарезервированное слово cvt. Слово cvt может использоваться как тип аргумента или результата в заголовках операций кластеров. Использование его как типа аргумента указывает, что фактический аргумент — абстрактного типа, неформальный — типа представления и что поэтому к фактическому аргументу сразу же после вызова должна быть неявно применена операция down и результирующий объект представления должен быть присвоен формальному аргументу. Когда cvt используется как тип результата, это означает, что результирующий объект — абстрактного типа, но возвращаемый объект—типа представления и что поэтому прямо перед возвратом к возвращаемому объекту неявно должна быть применена операция up. Слово cvt не добавляет никакой функциональности, которая не была бы обеспечена операциями up и down. Оно обеспечивает только удобство, которое возникает из возможности довольно просто описывать общие случаи.
Теперь мы можем обратиться к представленной на рис. 4.5 реализации процедуры intset.
intset == cluster is create, insert, delete, member, size, choose rep = array [int]
create = proc ( ) returns (cvt) return (rep$new ( )) end create
insert = proc (s: intset, x: int)
if — member (s, x) then rep$addh (down (s), x) end end insert
delete = proc (s: cvt, x: int) j: int :== getind (s, x) if j <= rep$high (s) then s [j] := rep$top (s) % top возвращает s [high (s)]
rep$remh (s) end end delete
member = proc (s: cvt, x: int) returns (bool) return (getind (s, x) <= rep$high (s)) end member
size ^ proc (s: cvt) returns (int^ return (rep$size (s)) end size
72 Глава 4
choose = proc (s: cvt) returns (int)
return (rep$bottom (s)) % bottom возвращает s [low (s) ] end choose
getind = proc (s: rep, x: int) returns (int) i: int : = rep$low (s) while i (== rep$high (s) do if x = s \i} then return (i) end i:=i+ I end return (i) end getind
end intset Рис. 4.5. Реализация типа данных intset.
Прежде всего обратим внимание на реализацию операции create. Заголовок здесь следующий:
create = proc ( ) returns (cvt)
Следовательно, возвращается абстрактный объект, т. е. набор целых чисел. По оператору return должен быть возвращен массив, но к этому объекту перед возвращением применяется операция up. Обратимся теперь к реализации операции insert. В этом слу-лае cvt не используется. Заголовок здесь следующий:
insert = proc (s: intset, х: int)
Следовательно, заданный пользователем абстрактный объект intset не подвергается операции down.
Операция insert не возвращает никакого результата; вместо этого она модифицирует свой аргумент intset. В данной реализации каждый элемент набора встречается в представляющем этот набор массиве только один раз; следовательно, операция insert не может просто добавить целое число х в массив. Она должна прежде проверить, не содержится ли уже х в массиве. Для этой цели используется операция member. Аргументом этой операции является набор целых чисел, поэтому в заголовке insert не используется cvt. Заметим, что в обращении к операции member префикс «intset$» отсутствует; этот префикс не требуется, если операция вызывается внутри своего собственного кластера.
Если оказывается, что х не является членом s, к s явно применяется операция down и при помощи операции addh х добавляется в массив. Если х уже принадлежит s, массив не модифицируется. Заголовок операции delete следующий:
delete = proc (s: cvt, х: int)
В этом случае к переданному в качестве аргумента абстрактному объекту intset немедленно применяется операция down и внутри операции delete s будет иметь тип array [int].
Операция delete должна прежде всего выяснить, принадлежит ли х s. Для этого используется внутренняя процедура getind. Если х есть в массиве, процедура getind возвращает индекс, соответствующий расположению х, в противном случае возвращается число, большее верхней границы массива. Так как процедура getind не указана в заголовке кластера, она является внутренней для этого кластера, т. е. может быть вызвана только из этого кластера.
Если х принадлежит s, операция delete просто записывает верхний элемент массива в позицию х и затем уничтожает верхний элемент, используя операцию remh. При этом операция remh возвращает удаляемый элемент как результат, однако в нашем случае этот ненужный результат просто уничтожается.
Операция choose позволяет возвратить произвольный элемент s. Следовательно, при реализации этой операции мы можем действовать, как нам удобно. Один удобный выбор — это первый элемент массива, другой, тоже удобный, — последний элемент. Конечно, это приводит к тому, что операция choose возвращает обратившемуся к ней различные объекты, но, так как в спецификации choose говорится, что выбор произвольный, не следует рассчитывать на возврат каких-то определенных объектов. Отметим, что в согласии со спецификацией операция choose не изменяет переданного ей в качестве аргумента набора intset.
