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

6.2.1. Реализация итераторов

Конструкция iter языка CLU аналогична конструкции proo. Она имеет заголовок, похожий на заголовок спецификации итера­тора. В теле этой конструкции для получения следующего эле­мента используется предложение yield. Однако выполнение опе­рации iter на этом не закапчивается. Наоборот, она готова вы­давать дополнительные элементы, если вызвавшая ее процедура в этом заинтересована, В этом случае она продолжает свое выпол­нение с предложения, следующего за предложением yield, а ее локальные переменные имеют те же самые значения, как и до выполнения предложения yield.

На рис. 6.3 показана некоторая реализация итератора •ele­ments для наборов intset. Здесь мы предполагаем, что массивы представляются в том же виде, как и на рис. 4.5 и 5.5. Когда операция elements продолжает свое выполнение после предложе­ния yield, ее переменная i является индексом выданного перед этим элемента. Операция elements затем продолжает работать и выдает следующий элемент, а заканчивает работу тогда, когда будут выданы все элементы.

гер == array [int.] elements == iter (s: cvt) yields (int) i; int :== rep$low (s) while true do yield (s fi])

except when bounds: return end i := i + I end end elements Рис. 6.3. Операция elements в наборе intset.

6.2.2. Использование итераторов

Для итераторов можно использовать оператор for, имеющийся в языке CLU. Этот оператор определяет некоторые переменные цикла. Если эти переменные являются локальными для цикла, форма такого оператора следующая:

for deeLlist in iter.call do body end

Если переменные не являются локальными, то используется такая форма:

for idn_list in iter. call do body end

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

На рис. 6.4 показана третья реализация процедуры setsum. Каждый раз, когда выполняется тело цикла, в переменную цикла е устанавливается другой элемент данного набора. Этот элемент добавляется в переменную sum, и затем тело цикла заканчивается, вызывая продолжение выполнения итератора. Когда будут вы­даны все элементы, операция elements завершается и вызывает окончание работы оператора for. Затем процедура setsum выдает вычисленную сумму.

setsum == proc (s: intset) returns (int) sum; int :== 0 for е: int in intset$elements (s) do sum := sum + jg_ end return (sum) end setsum

Рис. 6.4. Использование итератора.

Итератор и цикл for, который его использует, передают управ­ление друг другу. Сперва вызывается итератор. Он выдает в ка­честве результата тому, кто его использует, некоторый элемент, но сам не завершает работу. Вместо этого он возвращает управле­ние тому, кто его использует. Когда использующая его процедура готова получить следующий элемент, она возобновляет выпол­нение итератора.

На рис. 6.5 показана трассировка выполнения процедуры sum е i рс В setsum рс В elements

После первой выдачи После второй выдачи После третьей выдачи Окончание

О

4

5 12

начало тела начало тела начало тела оператор return

Рис. 6.5. Трассировка выполнения процедуры setsum.

после yield после yield после yield

132 Глава 6

бстракция итерации

setsum, когда к ней обращаются с набором intset jl, 4, 71, пред­полагая, что представляющий этот набор массив содержит эти элементы в порядке 4, 1, 7, На этом рисунке показаны значения локальных в процедуре setsum переменных sum и е и локальной в операции elements переменной i сразу же после выдачи элемента. Показано также значение переменной sum после окончания цикла в процедуре setsum. В этом случае значения переменных i и е не показаны, поскольку обращение к операции elements и тело цикла завершилось. После выдачи каждого элемента счетчик команд (рс) в процедуре setsum находится в начале тела опера­тора for, а счетчик команд для операции elements находится на операторе, следующем за оператором yield, г. е. на операторе присваивания для переменной i.

На рис. 6.6 показан второй пример использования операции elements — поиск некоторого отрицательного элемента. Если вы­данный элемент отрицателен, то процедура find. neg сразу же выдает результат. При этом возврате оператор for, а значит, и операция elements завершают работу до того, как будут выданы другие целые числа в наборе intset.

find.neg= pi-ос (s: intset) returns (int) signal (notJouiid) for x: int in intsel$e!ernents (s) do if x < 0 then return (x) end er.d signal not „found end find.neg

Рис. 6.6. Использование итератора для поиска.

К итераторам можно обращаться только из операторов for, и из каждого оператора for можно обращаться только к одному итератору. Однако операторы for могут быть вложенными. Кроме того, итераторы могут быть реализованы при помощи операторов for и их реализация может быть рекурсивной. Некоторые такие примеры приводятся в разд. 6.3.

6.2.3. Встроенные итераторы

В языке CLU имеется ряд операций итераторов для встроен­ных типов. Для целых чисел имеется операция intSfrom.to,

from. to == iter(x, у: int) yields (int)

effects Выдает целые числа между x и у включительно в порядке следо­вания.

Имеется также операция int$from_to.by, чей третий аргумент задает величину, на которую увеличивается переменная х на каждой итерации. Для массивов (и последовательностей) имеется два итератора — операция elements, которая выдает элементы массива (или последовательности) от нижней границы до верхней

границы, и операция indexes, которая выдает индексы массива (или последовательности) от нижней границы до верхней границы. И наконец, строки имеют итератор chars, который выдает символы строки от первого до последнего.

Примеры

В этом разделе приводится несколько примеров итераторов. Первый пример, приведенный на рис. 6.7, — это простой «фильтр». Он называется фильтром потому, что удаляет некоторые элементы, выданные другим итератором, и выдает только остальные эле­менты в процедуру, которая к нему обращается.

filter = iter (s: intset, els: itertype (intset) yields (int),

pred: proctype (int) returns (bool)) yields (int) requires чтобы s не модифицировался в теле цикла. effects Выдает в произвольном порядке все элементы, выданные опе­рацией els (s), которые удовлетворяют условию, заданному в pred',

iter (s: intset, els: itertype (intset) yields (int),

pred: proctype (int) returns (bool)) yields (iiit) for е: int in els (s) do if pred (e) then yield (е) end end end filter

Рис. 6.7. Фильтр.

Например, если операция odd выдает значение true, только если ее целый аргумент является нечетным числом, то фильтр

filter (s, intset^elements, odd)

выдает нечетные числа из набора s. В качестве второго примера добавим операцию

smalLto.big к упорядоченному списку olist.

small_to.big= iter (s, olist [t]) yields (t) requires чтобы набор s не модифицировался в теле цикла. effects Выдает элементы набора s в возрастающем порядке, определяемом при помощи операции t$lt, причем каждый элемент только один раз.

Эта операция могла бы заменить операцию least, описанную ранее. Реализация показана на рис. 6.8. Она рекурсивна — сперва операция small, to_big обращается рекурсивно сама к себе для любого поддерева, затем она выдает значение в узле, и далее она обращается рекурсивно сама к себе для правого поддерева. Заметим, между прочим, что операция smalLto_big является некоторой абстракцией через параметризацию. Она наследует тип параметра t из упорядоченного списка olist.

134 Глава 6

примером рис. 6.9:

является итератор permuta-

node== record [val: t, left, right: olist [t]] rep = variant [some: node, empty: null] small _to_big= Her (s: cvt) yields (t) tagcase s tag empty: return tag some (n: node): for e; t in small_to_big (n.left) do % выдать элементы левого поддерева yield (e) end yield (n.val) for e: t in small_to_big (n.right) do % выдать элементы правого поддерева yield (e) end end end small_to_big Рис. 6.8. Операция small, to_ big для упорядоченного списка.

Нашим следующим tions, приведенный на

si = sequence (int) permutations = iter (seq: si) yields (si)

% если последовательность состоит из одного числа, то оно выдается, и ор­ганизуется выход if si$size (seq) = I then yield (seq) else for index: int in si$indexes (seq) do % Создать некоторую последовательность, в которой опущено значение

с этим индексом val: int;=seq [index] prefix: si :== si$subseq (seq, I, index — 1) suffix: si :=si$subseq (seq, index+ I, si$size (seq)) % Присоединить перестановки последовательности к этому значению

и выдать

for perm: si in permutations (prefix Ц suffix) do % Операция II является операцией конкатенации yield (si$addl (perm, val)) end % конец для perm end % конец для index end % конец для if end permutations Рис. 6.9. Итератор permutations.

permutations' iter (seq: sequence [int]) yields (sequence [int]) effects Выдает в произвольном порядке все последовательности, по­строенные при помощи перестановок элементов в наборе seq.

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

\ракция итерации

_полняются перестановки этой последовательности, обращаясь рекурсивно к самому себе. Затем п-ный элемент добавляется ^ к началу каждой перестановки последовательности и выдается результирующая последовательность.

Нашим последним примером является итератор allprimes, приведенный на рис. 6.10.

ai = array [int] allprimes = iter ( ) yields (int) yield (2) % выдается первое простое число primes: ai :== ai$new ( ) x: int := I while true do

x: = x+2 % следующее рассматриваемое число for p: int in ai$elements (primes) do if x//p == 0 then exit not_found end

% x—не простое число

if piiip^x then break end % x — простое число end except when not_found: continue end % имеем некоторое простое число yield (x) ai$addh (primes, x) end end primes Рис. 6.10. Итератор allprimes.

allprimes =: iter ( ) yields (int) effects Выдаются простые числа в возрастающем порядке.

В нашей реализации все найденные до некоторого момента простые числа Хранятся в массиве primes. В цикле while полу­чаются нечетные числа. Те из них, которые кратны ранее найден­ным простым числам, отбрасываются в цикле for. Мы останавли­ваем цикл for, как только достигаем некоторого простого числа, чей квадрат больше текущего рассматриваемого числа, поскольку это рассматриваемое число не может, по-видимому, делиться на большее простое число.

6.4. Вопросы проектирования

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

Тип может иметь несколько итераторов. Например, упорядо­ченные списки olist могли бы иметь такую операцию:

big.to.small = iter (s: olist [t]) yields (t) requires чтобы набор s не модифицировался в теле цикла. effects Выдает элементы набора s в порядке уменьшения, как это опре­деляется при помощи операции t$lt, причем каждый элемент только один раз.

136 Глава 6

Зта операция дополняла бы операцию sma!l_to_big, обсу­ждавшуюся выше.

Для изменяемых совокупностей мы постоянно требовали, чтобы совокупность, по которой происходит итерация, не изме­нялась в теле цикла. Если мы опустим это требование, итератор должен работать по известным правилам даже в том случае, когда сделаны модификации. Например, предположим, что целое число п удалено из набора intset, когда работает операция elements. Должно ли число п выдаваться по операции elements?

Один подход состоит в том, что требуется, чтобы итератор выдавал элементы, содержащиеся в его аргументе совокупности в момент обращения, даже если модификации происходят позднее. Поведение заданного таким образом итератора хорошо опреде­лено, но его реализация, вероятно, будет неэффективной. Напри­мер, если бы итератор elements был реализован таким образом, то при его реализации пришлось бы при первом к нему обращении копировать весь набор intset — как раз то, против чего мы про­тестовали при рассмотрении операции eLseg. Поскольку при подходе с ограничениями На тело цикла эти неэффективности исчезают, в большинстве случаев этот подход будет предпочти­тельным. Связанный с этим вопрос касается того, может ли ите­ратор сам модифицировать совокупность. По общему соглашению такие модификации должны избегаться.

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

for t: task in task. queue$al!_ tasks (queue) do

% выполняется операция t. % если операция t генерирует новую задачу nt, % тогда ее надо поставить в очередь, выполнив % операцию taskqueue$enq (queue, nt) end

Абстракция итерации

Когда выполняемая задача генерирует другую задачу, мы просто помещаем ее в очередь, чтобы она позднее выполнялась. Итератор alLtasks в соответствующее время представит ее на выполнение. Однако такие примеры редки, обычно совокупность не изменяется ни в теле цикла, ни итератором.

6.5. Обзор главы

В этой главе рассматривается проблема полноты типов дан" ных, которые являются совокупностью объектов. Поскольку совокупность, вообще говоря, используется для выполнения некоторого действия над ее злементами, нам необходим некоторый способ доступа ко всем элементам. Этот способ должен быть эффек-

тивным в смысле времени и пространства, удобным для исполь­зования и не должен разрушать данную совокупность. Кроме того, он должен обеспечивать абстракцию через спецификацию.

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

Итераторы эффективны для исполнения. Выдача из итератора аналогична обращению к процедуре. К телу цикла for «обра­щаются» из итератора. Возобновление работы итератора анало­гично возврату из процедуры. Тело цикла «возвращает» управ­ление в итератор. Следовательно, затраты на использование ите­раторов состоят максимум из одного обращения к процедуре на выполнение тела цикла. Такие затраты могут быть меньше из-за оптимизаций компилятора.

Итераторы полезны сами по себе, как это имело место в приме­рах permutations и primes. Однако их основным применением яв­ляются операции над типами данных. В конце книги мы рассмо­трим другие примеры их использования.

Дополнительная литература

Atkinson Russel R., Barbara H. Liskov, Robert \V. Scheifler, 1978. Aspects of implementing CLU. Proceedings of the ACM 1978 Annual Conference, pp. 123— 129.

Упражнения

6.1. Задайте некоторую процедуру is.prirne, в кочорой определяечся, про­стое ли это целое число, и за^ем реализуйте ее, используя итератор allprirnes (рис. 6.10).

6.2. Реализуйте для упорядоченного списка olist итератор big.to_small, который был задан в разд. 6.4.

6.3. Для структуры poly (рис. 4.3), которая выдает все ненулевые термы, было бы полезно иметь итератор terms. Задайте спецификацию для этого итера­тора. Должен ли он быть некоторой операцией структуры poly?

6.4. Реализуйте итератор terms, заданный в упражнении 6.3, или в в"де операции структуры poly, или в другом виде (по вашему усмотрению), зачем используйте итератор terms для того, чтобы реализовать следующее:

diff = proc (р : poly) returns (poly)

effects Возвращается структура poly, которая является результатом дифференцирования набора р.

6.5. Задайте спецификацию абстракции stack, которая обеспечивает доступ к ее элементам в порядке «последним пришел — первым вышел». Стеки изменяемы. Отметим, что для того, чтобы стеки были полезны для каких-либо приложений (например, чтобы реализовать тип в упражнении 6.6.), должен быть некоторый способ доступа ко всем элементам стека. Мы могли бы задать некоторый итератор,

138 Глава 6

symtab=data type [etype: type] is create, enter.scope, leave_scope, add.id, lookup, depth

Описание

Таблица syrntab является некоторой таблицей символов, разработанной для использования а компиляторе с языка программирования с блочной струк­турой. Она предоставляет операции для входа в область действия и для выхода из нее, а также операции для добавления и поиска информации об идентифика­торах. Таблицы sybtab изменяемы.

Операции

create =•• proc ( ) returns (syrntab) etfects Возвращает некоторый новый пустой элемент syrntab, который

готов воспринимать информацию о самой внешней области действия. enter _scope = proc (s: syrntab)

modifies s

effects Модифицирует набор s так, что он подготовлен для восприятия информации о новой области действия.

leave_scope = proc (s: syrntab) signals (outermost _scope)

modifies s

etfects Если набор s установлен на сам^ю внешнюю область действия, то signals выдает outerrnost_scopc. В прогивном случае из набора s уда­ляется информация о самой внутренней области действия. Затем набор s позиционируется так, чтобы воспринимать информацию об области действия, которая непосредственно содержит удаленную область дей­ствия.

add_id = proc (s: syrntab, id: sti ing, info: etype)

signals (duplicate) modifies s

effects Если строка id уже определена в самой внутренней области дей­ствия в наборе s, то signals выдает duplicate. В противном случае к самой внутренней области действия набора s добавляется строка id с соот­ветствующей информацией info.

lookup == proc (s: syrntab, id: string) returns (etype)

signals (not .defined)

effects. Если строка id не определена ни в какой области действия на­бора s, то signals выдает not_defined. В противном случае возвращается информация, связанная с набором s. Если набор s определен в несколь­ких областях действия, выдается информация из самой внутренней области действия sthx областей действия. depth == proc (s: syrntab) returns (int)

effects Возвращает число областей действия в наборе s. end syrnlab

Рис. 6.11. Спецификация блочно-структурированной таблицы символов.

который выдает элементы стека от самого последнего элемента, помещенного в стек, до самого первого, или мы могли бы задать некоторую операцию fetch, которая возвращает 1-й элемент для i от 1 до числа элементов в стеке. Какой выбор лучше?

6.6. На рис. 6.11 задана некоторая спецификация абстракции блочно-структурированной таблицы символов, которая обеспечивает операции, хорошо подходящие для компилирования программ, написанных на блочно-структури­рованном языке. Реализуйте эту абстракцию и дайте инвариантное представление и функцию абстракции. (Указание: используйте стеки (см. упражнение 6.5) и карты (см. упражнение 5.5). Добавьте дополнительные операции для стека и карты, если необходимо, и задайте спецификации для любых таких добавленных операций.)

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

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

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

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

140 Глава 7

Мы предполагаем только незначительное знакомство читателя с языком Паскаль и будем пояснять необычные свойства этого языка по мере их использования. В настоящее время исполь­зуется много диалектов языка Паскаль, В основном мы пытались ограничиться некоторым подмножеством языка Паскаль, которое является общим для большинства диалектов. Однако часть при­меров, приведенных в этой главе, нельзя откомпилировать на некоторых диалектах языка Паскаль. Все наши программы были откомпилированы и просчитаны в системе Турбо-Паскаль.

7.1. Абстракции процедур и функций

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

Между процедурами языка CLU и операциями языка Паскаль имеется несколько важных различий. Наиболее существенными являются следующие: 1) операции языка Паскаль могут читать и модифицировать переменные, которые являются глобальными по отношению к вызванной операции; 2) формальные параметры языка Паскаль объявляются или типом var, или по значению. В языке CLU механизмы передачи аргументов совершенно раз­личные; 3) функции языка Паскаль могут вернуть только один аргумент, и этот аргумент должен принадлежать к довольно ограниченному набору типов; 4) в языке Паскаль нет механизма обработки исключительных ситуаций.

7.1.1. Глобальные переменные

Существуют ситуации, в которых глобальные переменные по­лезны. Больше всего это заметно тогда, когда надо сохранить некоторое состояние между вызовами процедуры. (Во многих дру­гих языках программирования это может быть использовано с применением собственных переменных. Описание собственных переменных языка CLU приводится в разд. А.8.5 приложения.)

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

7.1.2. Передача параметров и возврат значений

В языке Паскаль имеется два механизма передачи параме­тров — параметры с типом var передаются по ссылке, а пара­метры не с типом var — по значению. При вызове по значению

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

фактический аргумент является некоторым выражением (нап­ример, х + у). Такое выражение вычисляется, и его значение копируется в формальную переменную данной операции. При вызове по значению надо отметить два важных момента. Во-первых, модификация формального параметра не влияет на фактический параметр. Непонимание этого является частым источником ошибок в программах на языке Паскаль, Во-вторых, вызов по значению связан с накладными расходами для больших значений (например, массивов), поскольку надо выполнять их копирование.

При вызове по ссылке фактический параметр должен быть пере­менной или «псевдопеременной» (такой, как элемент массива). фактический параметр не может быть выражением. Формальный параметр можно представлять просто как другое имя фактиче­ского параметра, т. е. использование формального параметра в вызываемой операции эквивалентно использованию фактиче­ского параметра в вызывающей программе.

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

7.1.3. Обработка исключительных ситуаций

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

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

procedure failure (s: error, msg)

modifies Выходной поток на основном устройстве вывода. effects Печатает следующую строку на основном устройстве вывода: "Сбой. Программа окончилась из-за:" + s, а затем выполнение программы останавливается.

142 Глава 7

(Мы используем знак плюс для обозначения конкатенации строк. Эта операция обозначается одинаково в версиях языка Паскаль, которые поддерживают строки.) К процедуре failure обращаются тогда, когда в языке CLU явно или неявно будет сигнализировано состояние сбоя failure. Аргумент при обращении должен, конечно, идентифицировать проблему и операцию, в ко­торой она произошла. Обычно тип error.msg будет объявляться как некоторый тип строк. Если в используемом языке Паскаль строки не поддерживаются, аргумент может быть представлен как упакованный массив символов.

search ==ргос^а: int_array, х: int) returns (i: int)

signals (not- in)

effects Если переменная х находится в массиве а, то возвращается такой индекс i, что а [i] == х, в противном случае сигнализируется состояние not- in.

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

function searchi (var a: int. array; х: integer; var i: integer):

search I - exceptions modifies i

effects Если переменная х находится в массиве а, то индекс i устанавли­вается таким, что а [i] =; х, и возвращается значение ok; в противном случае возвращается значение not-in.

где searchl-exceptions является вычисляемым типом type searchLexceptions == (ok, not-in)

Отметим, что функция searchi имеет третий аргумент — параметр i с типом var. Эта переменная используется для получения ре­зультата в нормальном случае.

По соглашению все обращения к этой функции мы вставляем в предложение case следующим образом:

case searchi (а, х, i) of ok:a[i):=a[i]+l; not-in: write («Элемент не найден») end

Предложение case должно иметь ветвь, соответствующую каждому значению, которое может быть возвращено функцией. (Если возможны только две альтернативы, как в случае с функцией searchi, результат мор бы иметь булевский тип, а не вычисляе­мый тип, и оператор if мог бы использоваться вместо предложе­ния case.)

Этот подход имеет несколько недостатков. Неудобно, что функция searchi модифицирует параметр с типом var вместо выдачи

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

индекса, поскольку мы не сможем тогда ее использовать в таких выражениях, как a [search I (a, x)L Более того, функция searchi не так надежна при использовании, как процедура search. На­пример, предположим, что мы решили добавить еще одно значе­ние в тип searchi-exceptions. В соответствии с нашими соглаше­ниями надо добавить еще одну ветвь к каждому предложению case, в котором имеется обращение к функции searchi. Если, од­нако, это не будет сделано, то не возникнет ни ошибки во время компиляции, ни ошибок во время счета. (В языке Паскаль, если переключатель в предложении case не соответствует никакой из ветвей, программа продолжает выполнение g предложения, сле­дующего за предложением case.)

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