ZX-Ревю 1992 №1-2 1991 г.

Beta Basic - Глава 3. Процедуры.


ГЛАВА 3. ПРОЦЕДУРЫ

Список используемых ключевых слов: DEF PROC, END PROC, LOCAL, DEFAULT, REF, READ LINE, LIST PROC и функция ITEM().

В этой главе мы кратко рассмотрим вопросы, связанные с применением процедур в БЕТА-БЕЙСИКе. Более подробную информацию Вы сможете получить далее, прочитав разделы относящиеся к каждому из указанных ключевых слов.

Использование процедур - весьма желанный прием для тех, кто программирует на БЕЙСИКе. Современные версии БЕЙСИКа в той или иной мере используют эту возможность и программисты встречают их с большим энтузиазмом. И, если Вы ими не пользуетесь, то наверное стоит пересмотреть свои взгляды, причем вовсе не потому, что это модно или потому, что это все рекомендуют, а просто так удобнее и легче писать и отлаживать программы.

БЕТА-БЕЙСИК 3.0 имеет одну из самых совершенных систем использования процедур среди других языков для домашних персональных компьютеров. Именно здесь Вы получаете наибольшую гибкость и эффективность программирования.

Основное преимущество использования процедур состоит в структурном программировании. Эта концепция предполагает, что Вы можете создать некоторый программный модуль, способный выполнять определенную работу и не оказывать никаких нежелательных побочных эффектов на остальную часть программы. Он не должен, например, изменять никакие переменные в программе, кроме тех, которые положено. После того, как этот блок программы вами написан и тщательно отлажен, Вы можете забыть о том, как он работает и из чего состоит. Когда Вы пишете новую программу и он Вам нужен, Вы просто пришьете его к тексту командой MERGE "", а используете - вызвав его по имени. Каждая процедура должна быть достаточно простой, чтобы быть вполне понятной. Если она у Вас получается громоздкой, сложной и непонятной, то стоит подумать о том, чтобы разделить ее на несколько логических частей и выразить каждую из этих частей своей процедурой. Процедура - это часть программы, имеющая свое имя и начинающаяся с оператора DEF PROC, после которого задается ее имя и имена тех переменных, с которыми она должна работать, а заканчивающаяся оператором END PROC.

Давайте рассмотрим простой, хотя и вполне бесполезный пример: 100 DEF PROC greet

110 PRINT "Hello" 120 END PROC

Попробуйте дать команду RUN и Вы увидите, что ничего не произойдет. Хоть процедура и часть программы, но работать в таком виде она не будет. Здесь мы записали только определение процедуры, то есть указали, что она должна делать. Выполнение процедуры происходит только после вызова ее по имени. Наша процедура имеет имя "greet". Попробуйте добавить строку 10 greet

Теперь, если Вы дадите команду RUN, в строке 10 будет запущена эта процедура и, в соответствии с ее определением, будет напечатано на экране слово Hello.

Все происходит точно так же, как в стандартном БЕЙСИКЕ с функциями. Там ведь тоже DEF FN игнорируется до тех пор, пока программа не встретит вызов функции оператором FN.

Вы можете столкнуться с проблемой того, как набрать слово greet в строке 10, ведь курсор находится в режиме "К", выше мы об этом упоминали:

- Вы можете нажать пробел и далее набирать greet по буквам;

- Вы можете дать команду KEYWORDS 4 и перейти в режим ввода всех ключевых слов по буквам;

- Вы можете использовать ключевое слово PROC и записать строку в виде:

10 PROC greet

Это совершенно то же самое, что и просто

10 greet

Использование ключевого слова PROC в данном случае просто дань привычке тех программистов, которые привыкли к такой записи по более ранним версиям БЕТА-БЕЙСИКА, хотя реально в нем больше нет никакой необходимости.

Далее мы будем говорить об использовании имени процедуры, как о вызове процедуры. Имя процедуры обязательно должно начинаться с буквы. Заканчивается имя процедуры пробелом, двоеточием, нажатием ENTER, операторами REF или DATA. Остальные символы, как правило, могут быть использованы в имени процедуры, но желательно, чтобы Вы привыкли использовать только буквы, цифры и символы подчеркивания - "_". Не имеет значения, какие буквы применяются строчные или прописные.

Определение процедуры начинается с DEF PROC и может быть расположено где угодно в программе. Его вполне можно располагать и до и после первого вызова процедуры. Важно только, чтобы оператор DEF PROC был первым оператором в строке. Определение процедуры может иметь сколько угодно строк и должно заканчиваться оператором END PROC.

Если Вы используете с одним DEF PROC несколько END PROC, то компьютер будет не в состоянии правильно "перепрыгнуть" через определение процедуры во время работы программы. В этом случае Вам придется самостоятельно позаботиться, чтобы он смог это сделать или размещать все лишние END PROC после оператора STOP. Изменим приведенный выше пример: 100 DEF PROC greet times 110 FOR n=1 TO times 120 PRINT "HELLO" 130 NEXT n 140 END FROC

В данном случае times - это параметр процедуры, он называется формальным параметром, поскольку не имеет пока никакого числового значения, а только указывает, как он используется при расчете процедуры. Раз он указан в определении процедуры, то он должен быть задан и при вызове процедуры. Теперь, когда вам надо будет исполнить процедуру greet, Вы зададите параметр times и заданное вами значение называется фактическим параметром - это число, которое фактически будет подставлено на место формального times при расчете процедуры.

Итак, формальные параметры - это просто имена, а фактические параметры - это числа, выражения или переменные (кроме тех случаев, когда используется REF, о чем см.

ниже).

Теперь, если Вы обратитесь к процедуре по имени, без задания параметра, например greet, то получите сообщение об ошибке "Variable not found" (переменная не найдена).

Если же Вы обратитесь к процедуре:

10 greet 5

Вы увидите что слово Hello будет пять раз напечатано на экране, т.к. фактический параметр "5" встал на место формального times.

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

Дело в том, что, как мы говорили, процедуры должны исполнять то, что записано в их определении и не должны оказывать нежелательных побочных воздействий на остальную часть программы. Рассмотрим нашу процедуру. Она манипулирует с двумя переменными times и n. Давайте теперь проверим, чему они равны после того, как процедура отработала. Дайте прямые команды: PRINT times PRINT n

Вы обнаружите, что times - не существует, а n - существует и имеет числовое значение.

Почему не существует times? Дело в том, что эта переменная введена нами в строке DEF PROC, что автоматически сделало ее локальной, то есть определенной только внутри процедуры. Когда процедура кончила работать, она удаляется из памяти и ее больше нет. И это хорошо, ведь если у Вас где-то еще в программе используется какая-нибудь переменная times, то ее значение могло бы быть изменено (испорчено), а теперь вам можно об этом не думать. А если у Вас до вызова процедуры уже была какая-то переменная times, то она тоже будет удалена? Нет, в момент вызова она будет запомнена, как глобальная переменная, а в процедуре будет создана локальная times. После работы процедуры локальная будет удалена, а глобальная - восстановлена. Хуже обстоит дело с переменной n, - она осталась существовать после работы процедуры и является глобальной, ведь она не входила в оператор DEF PROC. Ее значение может незаметно для Вас быть использовано где-то еще и привести к неприятным ошибкам. Для того, чтобы это не происходило, ее можно объявить локальной переменной внутри процедуры в принудительном порядке. Для этого служит оператор LOCAL.

Добавьте к нашей программе строку: 105 LOCAL n ,

а теперь добавьте к программе следующие строки, которые позволят вам убедиться, что после работы процедуры переменные times и n остались ненарушены:

10 LET n=1234 20 LET times=5678 30 greet 10 40 PRINT n,times

Процедура "greet" имела скорее учебное, чем практическое назначение, а вот более полезная процедура:

100 DEF PROC box x,y,width,height 130 PLOT x,y: DRAW width,0 140 DRAW 0,-height: DRAW -width,0 150 DRAW 0,height 160 END PROC

Процедура "box" предназначена для рисования прямоугольника и имеет четыре параметра: х, y - координаты левого верхнего угла, width - желаемая ширина, а height -высота. Так, команда box 100, 100, 10, 40 изобразит вблизи центра экрану прямоугольник, вытянутый по вертикали.

Теперь предположим, что мы хотим, чтобы у нас был квадрат, а поскольку у него высота равна ширине, то попробуем ее не указывать, например: box 100, 100, 50

Получим сообщение об ошибке, т.к. процедура имеет четыре формальных параметра, а мы им на смену подставили только три фактических. Это, однако, тоже можно

предусмотреть и предотвратить, для чего служит оператор default (по английски default -принятый "по умолчанию"). Запишем строку: 120 DEFAULT height=width

Эта строка буквально означает следующее: "Если параметр height не задан, то считать, что он равен параметру width".

Ну, а если Вы уж совсем ленивы, то можете не указывать и ширину квадрата, введя строку:

110 DEFAULT width=20

и у Вас и ширина и высота будут равны 20 пикселам. Используя запятые, можно опускать не только последние параметры, но и вообще любые.

box ,100,40,10

В этом вызове опущен параметр x. Разумеется, где-то в описании процедуры должен присутствовать оператор

DEFAULT x = ...

Передача параметров в виде ссылки

Итак, мы рассмотрели, как информация передается из главной части программы в процедуры с помощью параметров. Фактические параметры заменяют формальные, определенные в операторе DEF PROC. Но у нас может возникнуть необходимость не только передавать что-то в процедуру, но и, например, получать что-то от нее. Можно, конечно, написать процедуру, которая будет что-то рассчитывать, а результат расчета присваивать глобальной переменной x, из которой можно этот результат узнать после окончания работы процедуры и возврата в главную программу. Но это нарушит нашу договоренность о том, что процедура должна быть независимым модулем и не должна оказывать нежелательного влияния на другие участки программы (и на другие процедуры тоже). А если мы поступим таким образом с переменной х, то теперь должны будем все время помнить о том, что эту букву уже нигде нельзя использовать для иных целей, т.к. можно потерять то, что в этой переменной содержится. Желательно было бы уже при вызове процедуры и указании фактических параметров задать какую-то переменную, в которую надо поместить результат работы процедуры. Данная версия БЕЙСИКа, в отличие от многих аналогов, позволяет делать и это.

Обычно, при вызове процедуры в нее передаются параметры в виде значений (чисел), которые встают на место формальных параметров, но можно и передать просто имя переменной, без указания ее значения. Это называется передачей параметра, как ссылки, и делается добавлением оператора REF перед именем этой переменной в операторе DEF PROC. REF - это сокращение от английского слова reference (ссылка). Таким образом, переменная, которую Вы ставите при вызове процедуры в качестве фактического параметра, переименовывается в то имя, перед которым стоит REF, вычисляется в процедуре и возвращается под первоначальным именем в вызывающую программу или процедуру. В качестве демонстрации приведем процедуру SWOP, которая обменивает между собой содержимое двух строковых переменных. 200 DEF PROC SWOP REF а$, REF b$ 210 LOCAL t$

220 LET t$=a$: LET a$=b$: LET b$=t$ 230 END PROC

А вызывается она, например, так:

10 LET x$="hi":LET y$="goodbye" 20 SWOP x$,y$ 30 PRINT x$,y$

Без указания REF в определении функции и a$ и b$ тоже будут обмениваться своим содержимым в процедуре SWOP, но только именно внутри нее: глобального эффекта не будет, т.к. локальные a$ и b$ при выходе из процедуры будут утрачены. С использованием же REF эти переменные являются как бы временными именами, на которые ссылаются глобальные x$ и y$, поэтому изменения которым подверглись a$ и b$ в теле процедуры, отражаются и на x$ и на y$.

Языки программирования для микрокомпьютеров, использующие концепции

процедур, в большинстве случаев не позволяют использовать массивы в качестве параметров. БЕТА-БЕЙСИК разрешает это, но правда, требует, чтобы они передавались только как ссылки. Они переименовываются вместо того, чтобы просто копироваться в область локальных переменных процедуры. Этим достигается экономия памяти, ведь компьютеру не надо одновременно хранить один массив два раза. Если же вам на самом деле нужна локальная копия Вашего массива для каких-то временных манипуляций с ним, Вы можете внутри процедуры создать параллельный массив, объявив его как LOCAL, а затем скопировать в него содержание Вашего исходного массива, воспользовавшись для этого командой БЕТА-БЕЙСИКа COPY, о чем мы еще скажем ниже, когда будем ее рассматривать вместе с командой JOIN. Вот демонстрационный пример, в котором показано, как можно найти сумму элементов массива. 300 DEF PROC total REF a(),REF sum 310 LOCAL n 320 LET sum=0

330 FOR n=1 TO LENGTH (1,"a()") 340 LET sum = sum + a() 350 NEXT n 360 END PROC

За именем массива должны идти скобки, чтобы интерпретатор отличал имена массивов от обычных переменных с тем же именем. Перед "sum" стоит оператор REF, так что по окончании работы содержимое "sum" будет передано глобальной переменной. Функция LENGTH (), о которой мы еще будем говорить, определяет размер массива для того, чтобы процедура могла работать с массивами любой длины.

Теперь зададим сам массив, чтобы убедиться, что наша процедура работает нормально: 100 DIM t(10) 110 FOR n=1 ТО 10 120 LET t(n)=n 130 NEXT n

и добавим вызов нашей процедуры: 140 total t(), answer 150 PRINT answer

в итоге получим 55.

Передача параметров списком

Возможны варианты, когда вам вместо того, чтобы определять комбинацию параметров для процедуры, удобнее иметь дело со списком этих параметров, причем список может быть неопределенной длины. Чтобы это было возможным, в БЕТА-БЕЙСИКе 3.0 есть специальные средства.

Если в операторе DEF PROC использовать оператор DATA вместо обычного перечисления формальных параметров, то соответствующий ему оператор READ примет список фактических параметров, стоящих в вызове вашей процедуры. Чтобы эта возможность была по настоящему удобной, необходимо, чтобы можно было определить в процедуре есть ли еще параметры в списке, которые она не приняла. Для этого существует функция ITEM(). Она возвращает 0, если список исчерпан полностью, 1 если в списке есть не переданные параметры и следующий параметр - число, 2 - если следующий параметр -строковая переменная.

Приведен пример, который показывает, как можно организовать процедуру, которая просуммирует несколько заданных вами чисел. 100 DEF PROC SUM DATA 110 S = 0

120 DO UNTIL ITEM()=0

130 READ a

140 s = s+a

150 LOOP

160 PRINT sum

170 END PROC

Примененные в строках 120, 150 операторы DO UNTIL и LOOP - это удобная форма организации цикла (см. далее). В принципе вместо DO UNTIL можно было бы применить и DO WHILE (см. ниже).

Вызов этой процедуры можно выполнить, например так:

sum 1, 2, 3, 4 или с других количеством параметров:

sum 1, 2, х, у, z, 1256

Если Ваш список параметров состоит из строковых переменных, то в строке 130 надо было бы применять READ а$, а не READ a. А как быть, если список смешанный и содержит и числа и строки? В этом случае перед READ надо проверить, очередной параметр с помощью ITEM() и использовать либо тот вариант, либо другой. В строковых переменных при этом можно избежать использования кавычек, если вместо READ использовать READ LINE (см. далее).

Обратите внимание на то, что список фактических параметров, передаваемых через DATA и READ, исключает возможность им быть локальными, если Вы специально это не зададите.

Рекурсия

Процедура, которая вызывает саму себя, называется рекурсивной. Есть анекдот, что в одном из компьютерных словарей-справочников написали:

РЕКУРСИЯ см.РЕКУРСИЯ.

Рекурсия часто помогает очень элегантно избегать сложных программистских проблем. С другой стороны, это не самая быстро работающая структура. К тому же возможны большие расходы памяти. Ведь при каждом вызове процедурой самой себя формируется новый временный список локальных переменных и для них выделяется память. Кроме того, несмотря на внешнюю простоту такой структуры, ее тщательный разбор может вызвать у программиста легкое головокружение. В качестве примера мы приведем процедуру, которая рисует бриллиант, а затем еще один такой же, но поменьше. Внутри первого и еще один внутри второго и так далее, на минимальный размер наложено ограничение, иначе процесс мог бы продолжаться бесконечно. 100 DEF PROC diamond х,y,size,diff DEFAULT diff=15 PLOT x,y,-size DRAW -size,size DRAW size,size DRAW size,-size DRAW -size,-size 110 IF size >4 THEN

diamond x,y+size,size-diff diamond x,y-size,size-diff diamond x-size,y,size-diff diamond x+size,y,size-diff 130 END PROC

Вызвать эту процедуру можно, например, так: diamond 128,88,40

Ошибки

Если Вы попробуете вызвать процедуру, которую забыли задать, Вам дадут сообщение об ошибке W: "Missing DEF PROC". Если же Вы забудете закрыть процедуру с помощью END PROC - сообщение X: "No END PROC". Программа будет стараться во время работы "перепрыгнуть" через блок, в котором задается процедура и не сможет этого сделать.

Если при вызове задано больше фактических параметров, чем их есть в наличии в описании процедуры, - сообщение "Parameter error." Если тип формальных параметров не совпадает с типом реально установленных фактических параметров - "Nonsense in BASIC". Оператор END PROC может генерировать сообщение "Variable not found", если оказывается, что переменная, которую процедура должна передать в качестве выходного параметра, не существует.




СОДЕРЖАНИЕ:


  Оставте Ваш отзыв:

  НИК/ИМЯ
  ПОЧТА (шифруется)
  КОД



Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Hardware - описание музыкального сопроцессора AY-3-8910/8912.
Избранные мысли... - избранные мысли и максимы Франсуа де Ларошфуко.
Хиты - Описание программ из приложения: HEAVY METAL MOVER, TRANTOR, PENTIX, DIGITAL STUDIO COMPILER (LPT-Colox version).
Открытые письма Nemo №4.9
От авторов - Редакция.

В этот день...   23 ноября