IV.3. ФУНКЦИИ ПОЛЬЗОВАТЕЛЯ . ОПЕРАТОР DEF FN Язык MSX-BASIC предоставляет возможность программисту (пользователю) о п р е д е л я т ь в составляемой им программе одну или несколько "соб- ственных" функций с помощью специального оператора DEF FN, имеющего следу- ющий синтаксис: DEF FNα[ (а р г у м е н т [,а р г у м е н т] ...)] = β , где: DEF FN ("FuNction DEFinition"-"определение функции")-служебные слова; α - имя переменной, для которой выделяется память; а р г у м е н т - имя переменной, для которой память не выделяется (функции пользователя могут иметь не более 9 аргументов); β - выражение, имеющее тот же тип, что и α. Итак, оператор, определяющий функцию пользователя,начинается со служеб- ного слова DEF . В идентификаторе определяемой функции два первых символа (буквы FN) являются обязательными, а остальные (α) выбираются автором про- граммы по его желанию. А р г у м е н т ы перечисляются через запятую, и вся совокупность а р г у м е н т о в заключается в круглые скобки. Отме- тим,что функция пользователя может вообще не иметь аргументов. Каждый а р г у м е н т является ф о р м а л ь н ы м параметром:это означает, что он на самом деле "не существует" (память для него не выделя- ется!) и поэтому не совпадает с переменными, имеющими такое же имя в про- грамме. Далее, после символа присваивания "=" в записи оператора, определяюще- го функцию пользователя, следует выражение β,задающее алгоритм вычисления значения функции пользователя. Это есть "образец", по которому программа каждый раз вычисляет значение функции, если,конечно,это необходимо. В вы- ражении допустим вызов любой встроенной функции и вызовы других функций пользователя. Приведем примеры о п и с а н и я функций пользователя: Ф о р м а л ь н ы е параметры │ │ │ │ ▼ ▼ ▼ ▼ 1) 10 DEF FNC(X,Y)=SQR(X+SIN(Y)) Параметры (аргументы),не являющиеся формальными │ │ ▼ ▼ 2) 30 DEF FNBUS(Z)=A*Z+B*LOG(ABS(Z))+5467.453*FNC(4,0.7) ▲ ▲ ▲ ▲ │ │ │ │ Ф о р м а л ь н ы й параметр Функция пользователя Параметр (аргумент),не являющийся формальным │ ▼ 3) 100 DEF FNQU$(QU,ST)=MID$(OL$(QU),ST) ▲ ▲ ▲ ▲ │ │ │ │ Ф о р м а л ь н ы е параметры Отметим, что р е к у р с и я з а п р е щ е н а (и прямая, и косвен- ная!). Кстати, о понятии р е к у р с и и см. в разделе IV.4 . Посмотрите... NEW Ok 10 DEF FNY(X)=(X<1)*FNY(X) 20 PRINT FNY(3) run Out of memory in 20 Ok Напомним, что и с п о л н я е м ы м оператором мы называем оператор программы, определяющий конкретные действия, которые должны быть выполнены. Оператор DEF FN может стоять в любом месте программной строки.Однако,он, в отличие от оператора DATA,- и с п о л н я е м ы й оператор, т.е. функция пользователя должна быть определена до обращения к ней. Если же обращение к функции пользователя произведено раньше ее определения оператором DEF FN, то на экране дисплея появляется сообщение об ошибке: "Undefined user function in ..." ("Функция пользователя не определена в строке ..."). Поэтому, как правило,в начале программы для операторов DEFFN специально резервируется несколько программных строк. Если в ходе выполнения программы, необходимо использовать значение функ- ции пользователя, определенной в этой программе, при заданных значениях ее аргументов, то необходимо в ы з в а т ь ее, т.е. записать идентификатор функции в соответствующее выражение, заменив формальные параметры нужными значениями аргументов - ф а к т и ч е с к и м и параметрами (константами или выражениями). Приведем примеры в ы з о в а функции пользователя: a) 50 R=FNC(3.07,5.) ▲ ▲ │ │ ф а к т и ч е с к и е параметры 3.07 и 5. b) 90 A=10:B=2:D=3.3:C=FNBUS(5*D-7) ▲ │ ф а к т и ч е с к и й параметр 5*D-7 Если выражение β содержит программные объекты (имена переменных,элемен- тов массивов, функции), описанные в качестве формальных параметров в опе- раторе DEF FN, то их значения определяются из списка фактических парамет- ров, указываемых при вызове функции пользователя. Если выражение β содержит программные объекты,не входящие в список фор- мальных параметров, то берутся их "текущие" значения из программы (на мо- мент вызова функции пользователя). В каждом из таких случаев интерпретатор обратится к описанию (опреде- лению) соответствующей функции пользователя, "подставит" вместо ф о р - м а л ь н ы х параметров необходимые значения ф а к т и ч е с к и х па- раметров и произведет требуемые вычисления, после чего результат вычисле- ния значения функции возвращается в точку вызова функции. Другими словами, компьютер выполнит над значениями фактических аргумен- тов (параметров) все те действия,которые оператор DEF FNα выполняет над своими формальными аргументами. П р и м е р ы: ───────────── 1) Ok 10 DEF FNP(X)=X^2+X+1:S=0 ▲ ▲ ▲ │ │ │ Ф о р м а л ь н ы й параметр X 20 FOR K=1 TO 7 30 S=S+FNP(K) ▲ │ Ф а к т и ч е с к и й параметр K 40 NEXT K 50 PRINT S:END run 175 Ok Ф о р м а л ь н ы й параметр X 2) NEW │ │ Ok ▼ ▼ 10 DEF FNA(X)=X*7 20 INPUT"Сколько лет Вашей собаке";D 30 PRINT"Если бы собака была человеком,ей было бы";FNA(D);"лет!" ▲ │ Ф а к т и ч е с к и й параметр D run Сколько лет Вашей собаке? 4 Если бы собака была человеком,ей было бы 28 лет! Ok Аргумент,не являющийся формальным параметром 3) NEW │ Ok ▼ 10 DEF FNZ(A,B)=A^2+B^2+C ▲ ▲ ▲ ▲ │ │ │ │ Ф о р м а л ь н ы е параметры A и B 20 INPUT X,Y,C 30 PRINT FNZ(X,Y):END ▲ ▲ │ │ Ф а к т и ч е с к и е параметры X и Y run ? 2,-3,5 18 Ok 4) NEW 5)NEW Ok Ok 10 DEF FNS$(X$,K)=MID$(X$,K,1) 10 INPUT X:IF X>1 TH 15 DEF FNQ$(X$)=FNS$(X$,K)+"$" EN DEF FNY(X)=X^2 EL 20 INPUT X$,K:PRINT FNQ$(X$):END SE DEF FNY(X)=X^3 run run 20 ? FNY(X):END ? APR,2 ? betta,4 run run P$ t$ ? 2 ? -12 Ok Ok 4 -1728 Ok Ok 6) 10 'Функция FNODD$(N) помогает проверить,является ли целое число N нечетным:если значением функции является -1, то число N - нечетное, если же значением функции является 0, то число N - четное. 20 DEF FNODD(N)=RIGHT$(BIN$(N),1)="1" Отметим, что можно определить 4 различные функции пользователя,имеющие одинаковое имя, но различающиеся по типам. 7) NEW Ok 10 DEF FNQ%=1:DEF FNQ!=2:DEF FNQ=3:DEF FNQ$="4" 20 DEFINTQ:PRINT FNQ; 30 DEFSNGQ:PRINT FNQ; 40 DEFDBLQ:PRTNT FNQ;PRINT 50 PRINT FNQ%;FNQ!;FNQ;FNQ$ run ·1··2··3 ·1··2··3·4 Ok Этот факт приводит к удивительным последствиям: например,программа мо- жет модифицировать сама себя с помощью динамического изменения вызова фун- кции (несоответствие типов формальных и фактических параметров при обраще- нии к функции пользователя ──▶ ошибка ──▶ обработка прерывания по ошибке ("ловушка" ошибки) ──▶ изменение типа функции пользователя). 8) NEW Ok 5 ON ERROR GOTO 100 10 DEF FNY(X)=X^3 15 DEF FNY$(X$)=X$+"(Козьма Прутков)" 18 DEFSTR X 20 LINEINPUT X:PRINT FNY(X):END 100 IFERR=13 THEN DEFSTR Y:RESUME0'Код 13 имеет ошибка"Type mismat ch"! run Смотри в корень! Смотри в корень!(Козьма Прутков) Ok (см. раздел VIII.3.6). Поскольку выражение β, содержащее алгоритм вычисления значения функции пользователя, хранится в памяти компьютера как ч а с т ь п р о г р а м- м ы , то DEFFN - один из нескольких операторов, которые нельзя использо- вать в режиме прямого выполнения команд (это пример о п е р а т о р а ,ко- торый не является к о м а н д о й !). 9) Ok DEF FNR(X)=X^5:PRINT FNR(2) Illegal direct Ok Сообщение об ошибке "Illegal direct" означает, что в качестве команды непосредственного выполнения встречается оператор, недопустимый в этом ре- жиме. Учтите, что если Вы допустили ошибку во время определения функции, эта ошибка будет обнаружена только тогда,когда функция встретится в выражении. Компьютер будет указывать как на источник ошибки на ту строку текста, где использована неверно заданная функция, а не на ту строку , где эта функ- ция была определена! IV.4. ПОДПРОГРАММЫ Прежде всего отметим, что п о д п р о г р а м м ы - это специальным образом оформленные группы программных строк. Подпрограммы бывают двух ти- пов: с т а н д а р т н ы е и н е с т а н д а р т н ы е . Первые из них входят в состав математического обеспечения компьютера. Нам часто приходится неявным образом обращаться к ним,когда,например, вво- дим данные в память, печатаем результаты счета, вычисляем значения встро- енных функций и т.п. Н е с т а н д а р т н ы е подпрограммы составляются самим пользовате- лем и являются фактически фрагментами его программ. В виде подпрограмм целесообразно оформлять логически завершенные части алгоритма, имеющие самостоятельную ценность; кроме того,использование под- программ позволяет экономить время и место в том случае, когда нужно в од- ной и той же программе несколько раз выполнить какую-либо последователь- ность операторов.Вместо того,чтобы многократно переписывать эту последова- тельность, напишите единственный программный м о д у л ь, к которому Вы сможете обращаться всякий раз при необходимости. Подпрограмма, следовательно, играет в какой-то степени ту же роль, что в математике "теорема" или "лемма": когда нужно доказать результат в не- которой специальной области, например, в дифференциальной геометрии,вовсе не обращаются каждый раз к базовым аксиомам теории множеств, а опираются на теоремы, которые косвенно через несколько уровней абстракции основыва- ются на этих аксиомах. Общая форма записи оператора обращения к подпрограмме (оператора вызо- ва подпрограммы) следующая: GOSUB n где: GOSUB ("GO to SUBroutine"-"идти к подпрограмме") - служебное слово; n - номер первой строки подпрограммы, 0≤n≤65529. Разумеется, нежелательно, чтобы строка с номером n находилась внутри цикла! Использование несуществующего номера программной строки n вызывает по- явление на экране дисплея сообщения об ошибке вида: "Undefined line number" ("Н е о п р е д е л е н н о м е р с т р о к и"). Подпрограмма, как правило (!), завершается оператором RETURN где RETURN ("return"-"возврат") - служебное слово. Оператор GOSUB n используется для вызова подпрограммы - группы операто- ров, начинающихся программной строкой с номером n и заканчивающихся опе- ратором RETURN. Подпрограмма может начинаться с комментария и находиться в любом месте Вашей программы. Главной особенностью оператора GOSUB является то, что оператор RETURN возвращает управление оператору,стоящему за последним выполненным операто- ром GOSUB. Заметим,что оператор, которому возвращается управление, может находиться не на следующей, а на той же программной строке! Приведем пример: Ok Ok ┌──────────────────┐ 100 X=2 100 X=2 ▼ │ 110 GOSUB 200:X=3 ┌── 110 GOSUB 200:X=3 │ 120 GOSUB 200 │ ┌ 120 GOSUB 200 │ 125 X=4 │ │ 125 X=4 ◀─ ─ ─ ─ ─ ─ ─ ─ ┐ │ 140 END │ │ 140 END │ │ 200 PRINT X^X │ │ │ │ 210 RETURN │ └─ ─ ─ ─ ─ ─ ─ ┐ │ │ run │ ▼ │ │ 4 └─────────────▶ ┌───────────────┐ │ │ 27 │ 200 PRINT X^X │ │ │ Ok │ 210 RETURN │─┘─┘ └───────────────┘ Оператор GOSUB n может показаться очень похожим на оператор GOTO n . Существенная разница между ними в том,что оператор GOSUB как бы "посылает в командировку"- после выполнения операторов подпрограммы происходит воз- вращение назад. Основная программа │ Подпрограмма ┌───────────────────┐ ▼─────────▶┌────────────┐ │ GOSUB │ │ ││ │ │ ··· │ │ ││ │ │ GOSUB │ ▼ ││ │ │ ··· │ │ ││ │ │ END │ │ ││ RETURN │ └───────────────────┘ ▼ ▼└────────────┘ В с е переменные в подпрограмме являются г л о б а л ь н ы м и. Это означает,что "доступ" к их значениям возможен из л ю б о г о места Вашей основной программы. Глобальная переменная - переменная, областью существования которой яв- ляется вся программа. Локальная переменная - переменная с ограниченной об- ластью существования в программе. Образно говоря, переменные,имеющие одно и то же имя в основной (вызыва- ющей) программе и подпрограмме не "однофамильцы", а одно и то же лицо! При вызове подпрограммы значения переменных в основной программе не"за- мораживается", а могут быть изменены операторами подпрограммы! Без "замо- раживания" то и дело происходят пренеприятнейшие вещи. Представьте себе,что Вы подарили своему товарищу очень ценную для него подпрограмму и объяснили, как ею пользоваться. Разумеется,Вашему товарищу нет никакого дела до того, какие имена переменных Вы использовали в Вашей подпрограмме. Поэтому может случиться, что и в его основной программе бу- дут использоваться переменные с теми же именами, что и в подаренной Вами подпрограмме. Ясно, что эти переменные "неразличимы" для интерпретатора. Дальнейшее ужасно! Как говорят программисты, в MSX-BASIC постоянно присутствует "побочный эффект подпрограммы"! Следовательно, важно выделить конкретные имена переменных,используемых т о л ь к о в данной подпрограмме и применить их для передачи значений аргументов из основной программы и возвращения результатов работы подпро- граммы в основную программу. Впрочем, оператор RESTORE (см. раздел II.4.5.) предоставляет возмож- ность каждой подпрограмме иметь свои собственные (л о к а л ь н ы е) пере- менные. Необходимо просто при к а ж д о м обращении к ней выполнять опе- ратор RESTORE для установки указателя на начало данных подпрограммы. П р и м е р. ─────────── NEW Ok 10 DATA 5,2:'Данные основной программы 20 DATA 7,.5:'Данные подпрограммы 30 READ A,B:?A+B;:GOSUB 40:?A+B:END 40 RESTORE 20:READ A,B:?A+B;:RESTORE 10:READA,B:RETURN'A и B - л о к а л ь н ы е переменные подпрограммы! run ·7··7.5··7 Ok Наиболее распространенной ошибкой при использовании подпрограмм являет- ся их неполное отделение от основной программы. Основными способами защи- ты от указанной ошибки являются: 1) использование оператора END; например, если п а к е т подпрограмм (совокупность нескольких подпрограмм) начинается с программной строки 100, то строка основной программы 99 END предотвратит вход в подпрограмму, начинающуюся со строки 100; 2) использование в основной программе перед первой строкой подпрограм- мы оператора GOTO k ,где k - номер программной строки, расположенной за строкой, в которой расположен оператор RETURN данной подпрограммы (см.при- мер из раздела IV.4.1). Поэтому сформулируем важный практический вывод. ┌────────────────────────────────────────────────────────────┐ │ Помещайте подпрограммы или в с а м о м к о н ц е или │ │ в с а м о м н а ч а л е основной программы ! │ └────────────────────────────────────────────────────────────┘ При случайном(!) попадании в подпрограмму без использования оператора GOSUB она выполнится нормально,но компьютер выдаст сообщение об ошибке "RETURN without GOSUB" ("RETURN б е з GOSUB"). Однако, если Вы случайно забыли поставить оператор RETURN, то интер- претатор может иногда не заметить ошибку! Он будет тщетно искать "забытый" оператор RETURN до конца программы, и, не найдя, прекратит все вычисления (т.к.программа уже будет выполнена полностью). Сообщения об ошибке не бу- дет! Берегитесь! Учтите, что операторы, стоящие за оператором RETURN в той же програм- мной строке, н и к о г д а не выполняются, поэтому за оператором RETURN целесообразно помещать только комментарии. Например, 50 RETURN '──▶ или 50 RETURN ──▶ . Поговорим теперь о совместном использовании операторов FOR...NEXT и GOSUB n. Внутри цикла использовать оператор GOSUB, м о ж н о, но оператор NEXT должен быть выполнен только п о с л е выполнения оператора RETURN. В противном случае последует сообщение: "NEXT without FOR" ("NEXT б е з FOR"). П р и м е р. ─────────── NEW Ok 5 INPUT K,N:FOR I=1 TO N:GOSUB 100:PRINT I;:NEXTI:END 100 IF K>I THEN NEXTI:RETURN ELSE RETURN run run ? 2,6 ? 0,6 NEXT without FOR in 100 ·1··2··3··4··5··6 Ok Ok С другой стороны, если цикл FOR...NEXT используется в подпрограмме, то оператор RETURN, находящийся в цикле FOR...NEXT, позволяет выйти из цикла и вернуться в основную программу. Это наводит на мысль,что наиболее эффек- тивно размещать процедуру поиска элемента массива, обладающего требуе- мыми свойствами, в н у т р и подпрограммы. Как только искомый элемент бу- дет найден, выполнится выход из подпрограммы. Однако учтите, что в момент выполнения оператора RETURN [k] любой неза- конченный цикл FOR ... NEXT в подпрограмме заканчивается и часть стеково- го пространства, которое он (цикл) занимал, освобождается! П р и м е р. Вывести на экран дисплея первое нечетное число,встретивше- ─────────── еся в целочисленном массиве A(K). NEW Ok 10 DEFINT A:INPUT K:DIM A(K):DATA 2,1,4,7,8 30 FOR I=1 TO K:READ A(I):NEXTI:GOSUB 100:END 100 FOR J=1 TO K:IF VAL(RIGHT$(STR$(A(J)),1))MOD2=1THEN?A(J):RETURN EL SE NEXTJ 120 PRINT "нечетных элементов в массиве нет":RETURN run run ? 3 ? 5 1 1 Ok Ok При многократном выполнении выхода из подпрограмм с помощью операторов GOTO, ON GOTO или IF...THEN...ELSE (вместо оператора RETURN) в конце кон- цов на экране может появиться сообщение об ошибке: "Out of memory" ("Н е х в а т а е т п а м я т и") . Причина возникающей ошибки довольно своеобразна:дело в том, что интер- претатор отводит сравнительно небольшой участок динамической памяти для хранения списка адресов возврата из подпрограмм.Такой список организуется в виде стека и называется р а б о ч и м с т е к о м. Каждый раз при вы- зове подпрограммы в стек заносится соответствующий этому вызову а д р е с в о з в р а т а, занимающий 7 байтов. При выполнении оператора RETURN из стека извлекается адрес возврата, записанный последним, и происходит пере- дача управления по этому адресу, после чего он удаляется из стека. Опера- торы, подобные GOTO или IF...THEN...ELSE,осуществляют выход из подпрограм- мы, не затрагивая стека! Если такой выход из подпрограмм происходит часто, то адреса из стека не удаляются, а лишь добавляются при каждом новом вызо- ве подпрограммы, так что в результате размер стека может превысить величи- ну отведенного ему участка памяти. В итоге программа прекращает работу, и выдается сообщение об ошибке. П р и м е р. 10 GOSUB 20:END ─────────── 20 FOR I=1 TO 1:K=K+1:GOTO 10:NEXTI:RETURN run а теперь... print K Out of memory in 100 ·894 Ok Ok В программе может быть несколько операторов RETURN, относящихся к одно- му оператору GOSUB n . Отметим,что наличие оператора RETURN в некотором месте подпрограммы еще не означает фактического окончания подпрограммы в данном месте. П р и м е р. Написать программу,вычисляющую значение функции y=│ x+x²│ ─────────── в точке x=A(не применяя функции ABS()). NEW Ok 10 INPUT A:U=A+A^2:GOSUB 100:PRINT Z:END 100 'Подпрограмма вычисления │x│: аргумент U,результат Z 110 IF U>=0 THEN Z=U:RETURN ELSE Z=-U:RETURN run ? -4 12 Ok Подпрограмма может, в свою очередь, вызывать другую подпрограмму, та - другую и так далее. Глубина вложения (степень вложения) подпрограмм огра- ничивается лишь размерами стека. Наглядно это можно представить себе следующим образом. Пусть Вас из Куйбышева послали в Москву на повышение квалификации, а оттуда еще в Ленинград на курсы. По окончании этих курсов Вы возвращаетесь в Москву,за- канчиваете учебу там и только после этого возвращаетесь домой. Вложенные подпрограммы являются довольно мощным средством разработки и отладки больших программ. Используя принцип вложений, можно не только раз- бивать сложную программу на отдельные модули и для каждого из них писать свою подпрограмму, но и р а з д е л я т ь на м о д у л и любые подпро- граммы. Чем меньше будет каждый выделенный программный модуль,тем легче будет его программировать и отлаживать и тем больше вероятность его много- кратного использования в других программах! "Трудности, обусловленные бессистемным написанием (без использования мо- дульной структуры) большой и сложной программы,можно сравнить с трудностя- ми, возникающими при попытке съесть сразу ц е л и к о м весь батон кол- басы; в то же время, если разрезать колбасу на ломтики, то съесть ее не представит никакого труда" (Л.Пул). Приведем пример, иллюстрирующий вложение подпрограмм: NEW Ok 1' Программа вычисления суммы вида 2' k k k 3' S = x + x +...+ x , 4' 1 2 n 5' где x - корни линейного алгебраического уравнения n-ой степени 6' i 7'Алгоритм решения поставленной задачи основан на формулах Ньютона,при- веденных в книге: А.П.Мишина, И.В.Проскуряков "Высшая алгебра". М.:ГИФ- МЛ, 1962, гл.III, 3, с.245. 11 INPUT "Укажите степень многочлена";N:INPUT"Укажите k";K 15 DIM A(N) 'Описан массив коэффициентов уравнения! 18 PRINT"Вводите коэффициенты уравнения" 20 FOR I=0 TO N:INPUT A(I):NEXT 'Итак, массив А(N) введен! 40 DIM SIG(N):FOR I=1 TO N:SIG(I)=(-1)^I*A(I)/A(0):NEXTI 80 IF N>=K THEN DIM S(N):A1=N:A2=K:GOSUB 200 ELSE DIM S(K):A1=K:A2=N:G OSUB 300 110 PRINT S(K):END 200 '¤¤¤¤¤ Начало подпрограммы 1 ¤¤¤¤¤ 205 S(1)=SIG(1):FOR J=2 TO A2:S(J)=(-1)^(J+1)*J*SIG(J) 230 FOR I=1 TO J-1:S(J)=S(J)+(-1)^(J-I+1)*S(I)*SIG(J-I) 250 NEXTI:NEXTJ:RETURN '──▶ 300 '¤¤¤¤¤ Начало подпрограммы 2 ¤¤¤¤¤ 305 GOSUB 200 'Вот оно, в л о ж е н и е подпрограмм! 360 FOR J=N+1 TO K:S(J)=0 380 FOR I=1 TO N:S(J)=S(J)-(-1)^S(J-I)*SIG(I) 400 NEXT I,J:RETURN '──▶ run Укажите степень многочлена? 2 Укажите k? 2 Введите коэффициенты уравнения ? 1 ? 0 ? -1 2 Ok