Эта статья предназначена тем, кто уже изучил операторы БЕЙСИКа, однако ещё не успел приобрести достаточно опыта в написании программ. Приводимые здесь рекомендации и приёмы сохранят своё значение для многих ПЭВМ и диалектов БЕЙСИКа.

Б.Б. МАТВЕЕВ

СПАГЕТТИ В КОМПЬЮТЕРИЗАЦИИ

Обычно, прочтя за короткое время пособие по БЕЙСИКу, человек с техническим образованием восклицает: «Как всё просто и понятно!» Действительно, персональные ЭВМ и средства работы с ними с тем и создавались, чтобы их освоение не отнимало много времени от основных занятий владельца, кем бы он ни был по профессии - врачом или строителем, физиком или бухгалтером. Более того, если для данной ПЭВМ в изобилии имеются все необходимые программы, обеспечивающие потребности потенциального пользователя, то ему зачастую вовсе нет необходимости что-либо знать о БЕЙСИКе и программировании. Достаточно лишь освоить правила общения с конкретной, уже имеющейся в продаже программой.

Однако зачастую пользователь имеет не столь благоприятную ситуацию, и объективная необходимость сталкивает его с более или менее значительным объёмом программной работы.

Когда же программа начинает разбухать прямо-таки на глазах, когда пользователь начинает систематически сталкиваться со множеством ошибок, с непредсказуемым поведением программы в очевидных, казалось бы, ситуациях, прозвучавшее в начале работы «как просто» сменяется недоумением и досадой. Действительно, для создания сколько-нибудь серьёзных программ мало освоить операторы, составляющие язык. Необходимо ещё научиться грамотно их применять. Ситуация совершенно аналогична изучению иностранных языков, когда освоение словарного минимума и простых диалогов отнюдь не решает задачи полноценного общения. Итак, как знает любой программист, от формального знакомства с алгоритмическим языком до серьёзного умения работать на нём дистанция огромного размера. Постараемся показать это читателю на конкретных примерах.

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

Начнём с простого примера.

Пусть нам нужно организовать ввод фамилий авторов ссылок. Осуществим это с помощью следующей программы:

10 REM                ПРИМЕР 1.
15 REM          ВВОД ФАМИЛИЙ АВТОРОВ
20 REM          18 ФЕВРАЛЯ 1988 Г.
100 DIM FAM$(100)
110 REM ###### УЧАСТОК ВВОДА ######
115 K1=1
120 PRINT "НАЧИНАЕМ ВВОД ССЫЛОК"
122 PRINT " "
130 REM---
140 INPUT "ФАМИЛИЯ:";NAME$
150 IF NAME$="****" THEN GOTO 190
160 FAM$(K1)=NAMF$
170 K1=K1+1
180 GOTO 130
190 REM+++
195 NMAX=K1
200 PRINT "ВЫ ВВЕЛИ СЛЕДУЮЩИЙ СПИСОК ФАМИЛИЙ"
205 NMIN=NMAX-1
210 FOR K1=1 ТО NMIN STEP 1
220 PRINT FAM$(K1)
230 NEXT K1
240 PRINT " "
250 PRINT "СПИСОК ФАМИЛИЙ ВЫВЕДЕН."

Программа как таковая начинается здесь со строки 100, которой предшествует заголовок. Необходимо выработать привычку делать хотя бы небольшой, в две-три строки, комментарий в начале программы, чтобы, имея быстро растущее программное хозяйство, не тратить время, пытаясь по виду текста вспомнить его содержание. Можно смело утверждать, что при отсутствии комментариев, вернувшись к знакомой программе через пару недель, даже сам её автор только с усилием вспомнит, который именно из десятка вариантов текста он видит перед собой. Так что условимся резервировать номера строк, скажем, до 99 под заголовок программы и описания различных программных объектов.

В строке 120 выводится на экран начальная поясняющая надпись. Можно обойтись и без этого, но хорошим стилем, безусловно, будет, если программа выдаёт ясный и исчерпывающий набор сообщений. Оператор PRINT в следующей строке позволит сделать пропуск строки после надписи, чтобы улучшить восприятие текста на экране. Возможностью улучшить «читабельность» сообщений программы на экране не стоит пренебрегать. В строке 140 с клавиатуры вводится очередная фамилия и помещается в строковую переменную NAME$. Фамилий вводится несколько, и ЭВМ надо объяснить, когда же этот ввод будет закончен. Можно предварительно информировать ЭВМ о количестве вводимых фамилий и оформить этот процесс циклом FOR-NEXT, задав в нём необходимое число повторений. Однако если в процессе ввода пользователь решил ввести больше фамилий, чем предполагал заранее, или же решил вовсе прекратить ввод, то возникает проблема выхода из цикла до выполнения заданного числа повторений. Поэтому изберём вариант выхода по признаку окончания. Пусть таковым будут четыре звёздочки, хотя можно избрать и какой-либо другой признак, лишь бы он содержал больше одной литеры (во избежание случайного ввода) и состоял из малоупотребительных сочетаний символов.

Строка 150 сличает введённую с клавиатуры информацию и в случае совпадения с признаком окончания передаёт управление за пределы участка ввода. Если введённая строка не совпадает с признаком окончания, то можно запомнить её в одном из элементов массива FAM$, на который указывает переменная К1, начальное значение которой определено в строке 100. Запись в очередной элемент массива осуществляется в строке 160, после чего переменная NAME$ готова для принятия нового элемента ввода, только надо не забыть увеличить на единицу указатель К1, подготовив тем самым запись в очередной свободный элемент массива FAM$.

Участок ввода завершается строкой 180, передающей управление строке 130, очередному оператору ввода. Таким образом, программа будет располагать в массиве FAM$ все новые фамилии, пока мы не введём признак окончания.

Обратим внимание на строку 180. Употребление оператора передачи управления GOTO является, наверное, одним из наиболее остро дискутируемых приёмов программирования. И это вполне понятно. Множество переходов внутри программы способно совершенно запутать её смысл, породить трудные для обнаружения ошибки в логике её работы. При этом наиболее неприятными являются передачи управления «назад» по тексту программы. В нашем случае употребление GOTO оправдано тем, что участок ввода, по-видимому, не будет сильно разрастаться в длину, а логика его работы ясно понимается из его текста. Общей рекомендацией может быть очень умеренное употребление GOTO, особенно идущих «назад», в частности, преимущественное употребление цикла FOR-NEXT с большим заданным числом повторений и выходом по условию примерно так, как это сделано в строке 150.

Строка 130. Управление на неё передаётся «назад» от строки 180 оператором GOTO. Почему же переход осуществляется к комментарию, а не прямо к строке 140? Дело в том, что текст программы находится в процессе постоянного изменения. Допустим, вы захотите добавить какие-то операторы в участок ввода до оператора INPUT. Тогда, чтобы они находились внутри этого участка и использовались при каждом вводе, нам нужно будет при каждом таком добавлении поправлять строку 180, меняя адрес перехода на номер добавляемого оператора. Это чревато множеством недоразумений и лишней работы. Достаточно вспомнить, что вероятность внести ошибку при одном исправлении составляет 20% [1]. Лучше дать постоянную ссылку на строку с комментарием, пометив его, скажем, тремя минусами, что будет указывать на «неправильную» передачу управления «снизу». Теперь явно видно, что на строку 130 идёт откуда-то снизу передача управления, и её нельзя стирать или произвольно изменять её номер. При необходимости после неё можно добавлять в участок ввода другие строки и модифицировать их, менять их номера. Аналогичную роль играет строка 190. Она отмечена тремя плюсами как «правильная» передача управления «сверху вниз». Строка 195 позволяет запомнить последнее значение переменной ввода К1 в переменную NMAX для дальнейшего использования. В БЕЙСИКе все переменные «глобальны», т.е. областью их действия является вся программа, поэтому лучше искусственно «локализовать» текущие переменные в циклах и в местах, подобных рассмотренному участку ввода. При требовании сохранения за пределами цикла каких-либо их значений будем выделять специальные переменные. В данном случае нам необходимо сохранить номер первого незанятого элемента массива FAME$. Этот номер затем может неоднократно требоваться программе, и лучше его поместить в специальную переменную NMAX, в то время как «локальная» переменная К1 будет снова использована как переменная цикла в строках 220-240 для контрольного вывода на экран введённой перед этим информации. Настоятельно рекомендуется выводить для визуального контроля на экране введённые величины и строки.

Это - необходимый приём даже при надёжно работающей клавиатуре. Он называется «эхо-печать», т.е. вывод на экран информации, непосредственно перед этим введённой. Можно также распечатать введённый массив фамилий после окончания ввода, как мы и поступили. Если не принимать таких мер предосторожности, то мы не будем иметь никакой гарантии в достоверности введённой информации.

Как смог убедиться читатель из обширного комментария к такой, казалось бы, невзрачной программе, как приведённая в примере 1, создание надёжной, удобной в работе и модификации, так сказать, «непотопляемой» программы является не просто применением суммы знаний о языке, а результатом определённой технологии программирования. Рядовой пользователь ПЭВМ, не располагающий ни печатающим устройством для выдачи полного листинга программы, ни какими-либо средствами отладки, может видеть на экране лишь небольшой участок текста в два десятка строк. В таких условиях факторы надёжности, ясности, логичности, хорошей читаемости программы в громадной степени будут определять успех (или неуспех) всего замысла.

Конечно, пользователь, например БК-0010, привыкший тесниться в жёстких пределах, отведённых ему 16 К, может охарактеризовать введение многих независимых имён переменных, дополнительные строки с комментариями и т.д. как некоторую расточительность по отношению к доступной памяти.

Но это только кажущийся проигрыш. Написание за один этап плотного, компактного, высокоэффективного кода посильно весьма немногим. Можно согласиться, что экономия памяти или эффективность во многих случаях являются необходимыми и даже решающими качествами программы. Однако всё хорошо в своё время. Вот что пишет автор известной книги о технологии программирования преподаватель из Калифорнии Денни Ван Тассел [1, с. 117]: «Основной задачей программирования является создание правильных, а не эффективных программ.

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

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

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

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

10 REM              ПРИМЕР 2.
15 REM    ВВОД БИБЛИОГРАФИЧЕСКИХ ССЫЛОК
20 REM         18 ФЕВРАЛЯ 1988 Г.
100 DIM FAM$(100)
101 DIM TITLE$(100)
102 DIM VOLM$(100)
103 DIM NUM$(100)
104 DIM PAGE$(100)
105 DIM YEAR$(100)
106 DIM PUBL$(100)
107 REM---
110 REM ###### УЧАСТОК ВВОДА ######
115 K1=1
120 PRINT "НАЧИНАЕМ ВВОД ССЫЛОК"
122 PRINT " "
130 REM---
140 INPUT "ФАМИЛИЯ:";NAME$
150 IF NAME$="****" THEN GOTO 190
151 INPUT "ЗАГЛАВИЕ:";ТIТ$
152 INPUT "TOM:";VOLM$
153 INPUT "НОМЕР:";NUMER$
154 INPUT "СТРАНИЦА:";PAG$
155 INPUT "ГОД ИЗДАНИЯ:";YE$
156 INPUT "ИЗДАТЕЛЬСТВО:";PUB$
160 FAM$(K1)=NAME$
161 TITLE$(K1)=TIT$
162 VOL$(K1)=VOLM$
163 NUM$(K1)=NUMER$
164 PAGE$(K1)=PAG$
165 YEAR$(K1)=YE$
166 PUBL$(K1)=PUB$
170 K1=K1+1
180 GOTO 130
190 REM+++
195 NMAX=K1
200 PRINT "ВЫ ВВЕЛИ СЛЕДУЮЩИЙ СПИСОК ФАМИЛИЙ:"
205 NMIN=NMAX-1
210 FOR K2=1 ТО NMIN STEP 1
220 PRINT FAM$(K2)
230 NEXT K2
235 PRINT " "
240 PRINT "СПИСОК ФАМИЛИЙ ВЫВЕДЕН."
250 REM
260 REM ###### УЧАСТОК ПЕРЕКЛЮЧЕНИЯ РЕЖИМОВ ######
270 REM
280 PRINT " МЕНЮ РЕЖИМОВ РАБОТЫ "
290 PRINT " "
300 PRINT " 1. ПОИСК ФАМИЛИИ АВТОРА "
310 PRINT " 2. ВВОД БИБЛИОГРАФИИ "
330 PRINT " "
340 REM---
350 IF INKEY$="1" THEN GOTO 400
360 IF INKEY$="2" THEN GOTO 130
370 GOTO 340
400 REM+++
401 INPUT "ВВЕДИТЕ ФАМИЛИЮ ДЛЯ ПOИСКА:";NAME2$
402 PRINT "ФАМИЛИЯ ДЛЯ ПОИСКА: ";NAME2$
403 REM---
404 INPUT "НАЧАТЬ ПОИСК? (Y/N) ";NAME3$
405 IF NAME3$="Y" THEN GOTO 410
406 IF NAME3$="N" THEN GOTO 250
407 PRINT "НЕВЕРНЫЙ ОТВЕТ. ПОВТОРИТЕ ВВОД."
408 GOTO 403
410 REM+++
411 FOR K3 = 1 ТО NMIN
420 IF FАМ$(K3)=NAME2$ THEN GOTO 450
430 NEXT K3
440 PRINT "ФАМИЛИЯ ";NAME2$;" НЕ НАЙДЕНА"
445 GOTO 500
450 REM+++
451 PRINT "НАЙДЕНА ФАМИЛИЯ АВТОРА: ";FAM$(K3)
452 PRINT " НОМЕР ССЫЛКИ В СПИСКЕ ";K3
454 PRINT " ЗАГОЛОВОК: ";TITLE$(K3)
456 PRINT " ТОМ: ";VOL$(K3)
458 PRINT " НОМЕР: ";NUM$(K3)
460 PRINT " СТРАНИЦА: ";PAGE$(K3)
462 PRINT " ГОД: ";YEAR$(K3)
464 PRINT " ИЗДАТЕЛЬСТВО: ";PUBL$(K3)
500 END

Начало примера 2 во всем аналогично предыдущему тексту, с той лишь разницей, что теперь в соответствующие массивы вводится полный набор информации о библиографической ссылке. Условный оператор в строке 150 позволяет, как и раньше, прекратить ввод при получении признака окончания в переменной NAME$. После ввода и распечатки массива мы ввели участок «переключения» режимов. Программа, достигнув строки 340, начнёт «вращаться» по участку строк 340-370, пока не будет нажата клавиша 1 или клавиша 2. Чтобы пользователь не был введён в заблуждение кажущимся бездействием программы (хотя программа работает, на экране ничего не происходит), перед таким «переключателем» обязательно должна выдаваться на экран надпись- подсказка. Когда же режимов выбора станет больше, то все их необходимо перечислить в поясняющей надписи на экране. Такой способ выбора одного из режимов работы программы называется «выбор из меню». Поскольку участок строк 340-370 будет проходиться неопределённо большое число раз, цикл здесь неприемлем, и мы допустим «неправильный» GOTO, что можно мотивировать, как и в строках 130-180, краткостью участка текста и простотой его функционального назначения.

Выбрав в меню вариант 1, мы перейдём к участку программы, оформленному в виде цикла (строки 400-450), который позволяет «пройтись» по всему введённому файлу в поисках фамилии автора, совпадающей с введённой в строке 402. Мы применили здесь немедленную эхо-печать, поскольку даже единственная неверно введённая буква в фамилии будет причиной поиска и сравнения с неверным образцом. Зарезервируем пока строку 500 как самую последнюю в программе. Переход к ней теперь будет означать выход из программы.

Рассмотрим структуру программы в целом. Теперь можно убедиться, что стоило программе немного вырасти: как проявился целый ряд неудобств и даже ошибок. Например, ввести ссылки можно только один раз, искать фамилию тоже лишь однократно. Кроме того, к участку ввода теперь можно многократно обращаться из меню, и при каждом обращении ввод будет производиться снова и снова с первого элемента массивов, затирая тем самым прежде введённую информацию. Это - типичный пример неточного определения смысла переменных. Но лучше поздно, чем никогда. Мы ввели ранее переменную NMAX, указывающую на первый незанятый элемент массивов. Зафиксируем теперь это определение и запретим использование этого имени для других целей. Теперь это будет указатель, изменяющийся вместе с размером самого файла. Менять значение указателя можно будет только в тех участках программы, в которых будет производиться ввод или вычёркивание ссылок. Переменную NMAX теперь будем инициализировать явно в начале программы, а переменная участка ввода N перед каждым входом в него будет принимать значение первого свободного элемента массивов, а в конце ввода будет заносить обновлённое значение указателя из N обратно в NMAX

Однако нами ещё отнюдь не решены проблемы общего построения программы, её структуры. Теперь хорошо видно, что самый естественный для пользователя порядок работы должен начинаться с выдачи на экран основного меню (строки 250-330). Пользователь выбирает из меню нужный режим, работает в этом режиме, а по окончании возвращается обратно к меню для следующего выбора. Очевидно, что с самого начала программы надо было поместить участок набора режимов, управление с которого передавалось бы к конкретным режимам. В теперешнем же виде программы после её запуска мы оказываемся на участке ввода. Разумеется, формально это легко исправить, вставив строку 115 GOTO 250, а в комментарии строки 250 добавить «+++» как указание на ссылку «сверху». Однако, как метод такое «развязывание» нежелательно, ибо, будучи постоянно применяемо, приводит к появлению множества переходов вверх и вниз по тексту программы.

Такой способ решения проблем носит название «спагетти», поскольку выявить логику участка программы, запутанного множеством переходов, или модифицировать такой текст столь же трудно, как отделить от остальных одну из макаронин, перепутанных в тарелке. Поневоле допустив появление одной такой макаронины (GOTO 250), впредь постараемся быть предусмотрительнее. Ревнителям же абсолютной чистоты и структурности текста остаётся только ввести текст программы заново, изменив порядок операторов и аккуратно исправив номера строк в операторах перехода.

Из всего сказанного выше можно сделать вполне естественный вывод: если бы мы детально представили себе с самого начала работу программы и сформулировали её желаемые функции, переделок удалось бы избежать. Нарисуем теперь блок-схему структуры программы (рис. 1). Каждый из прямоугольников означает участок кода, выполняющий определённую функцию.

Мы предусмотрительно зарезервировали пустые прямоугольники под функции, которые могут появиться в дальнейшем. Из получившейся схемы явствует, что каждую из стрелок-связей на рис. 1 программно надо оформить с помощью оператора GOSUB. Пунктирные стрелки от нижнего ряда прямоугольников означают дальнейшие вызовы подпрограмм. В самом верху располагается программа-диспетчер, головная программа, вызывающая отдельные операции. Такой головной модуль обязательно существует при правильной организации программы. Он не должен быть большим: его основная функция - дать как бы план или конспект всей программы, с необходимыми комментариями и описаниями переменных и массивов в начале и последующими вызовами тех или иных подпрограмм.

Рис. 1.

Каждая из подпрограмм тоже выполняет определённую функцию, смысл которой может быть сформулирован одной короткой фразой. Если формулировка получается пространная, то необходимо разделить подпрограмму на несколько подпрограмм с более узким функциональным назначением. Текст одной подпрограммы должен содержать не более 60 строк (примерно 1 страница текста) [2] и должен быть построен так, чтобы за 10 мин знакомства с ним можно будет понять не менее 90% его содержания (правило 90/10) [1].

Условимся также и об именах переменных. Пусть, как и в примерах 1 и 2, имена типа K1, K2 и т.д. служат переменными циклов. Пока мы отнюдь не исчерпали ресурсов оперативной памяти, введём для разных циклов разные имена их переменных. Если необходимо запомнить отдельные значения этих переменных за пределами их циклов, то будем пользоваться для этой цели специально выделенными «глобальными» переменными (NMAX в примерах 1 и 2), инициализируя и комментируя их в начале программы. Так мы начали систематически применять приёмы структурного программирования. Разумеется, принятые в этой статье соглашения могут выглядеть несколько иначе, в зависимости от привычек и опыта конкретного программиста. При этом несовпадающие наборы рекомендаций будут преследовать одни и те же цели.

Рассмотрим следующий пример:

10 REM                 ПРИМЕР 3.
15 REM       СТРУКТУРИРОВАННАЯ ПРОГРАММА
20 REM          16 ФЕВРАЛЯ 1986 Г.
21 REM
22 REM NMAX - УКАЗАТЕЛЬ НА ПЕРВЫЙ СВОБОДНЫЙ ЭЛЕМЕНТ ФАЙЛА
23 NMAX = 1
24 REM NMIN - УКАЗАТЕЛЬ НА ПОСЛЕДНИЙ ЗАНЯТЫЙ ЭЛЕМЕНТ ФАЙЛА
27 NMIN = 0
100 DIM FAM$(100)
101 DIM TITLE$(100)
102 DIM VOL$(100)
103 DIN NUM$(100)
104 DIM PAGE$(100)
105 DIM YEAR$(100)
106 DIM PUBL$(100)
115 GOTO 250
120 REM///
121 REM ===ПОДПРОГРАММА ВВОДА ССЫЛОК===
122 K1=NMAX
123 PRINT " НАЧИНАЕМ ВВОД ССЫЛОК "
124 PRINT " "
130 REM---
140 INPUT "ФАМИЛИЯ:";NAME$
150 IF NAME$ = "****" THEN GOTO 190
151 INPUT "ЗАГЛАВИЕ:";ТIТ$
152 INPUT "ТОМ:";VOLM$
153 INPUT "НОМЕР:";NUMER$
154 INPUT "СТРАНИЦА:";PAG$
155 INPUT "ГОД ИЗДАНИЯ:";YE$
156 INPUT "ИЗДАТЕЛЬСТВО:";PUB$
160 FAM$(K1)=NAME$
161 TITLE$(K1)=TIT$
162 VOL$(K1)=VOLM$
163 NUM$(K1)=NUMER$
164 PAGE$(K1)=PAG$
165 YEAR$(K1)=YE$
166 PUBL$(K1)=PUB$
170 K1=K1+1
171 IF K1>=101 THEN GOTO 181
180 GOTO 130
181 REM+++
182 PRINT "ДОСТИГНУТ ПРЕДЕЛЬНЫЙ РАЗМЕР ФАЙЛА!"
190 REM+++
195 NMAX=K1
205 NMIN=NMAX-1
210 RETURN
250 REM+++
260 REM ###### УЧАСТОК ПЕРЕКЛЮЧЕНИЯ РЕЖИМОВ ######
270 REM
280 PRINT * МЕНЮ РЕЖИМОВ РАБОТЫ: "
290 PRINT " "
300 PRINT " 1. ПОИСК ФАМИЛИЯ АВТОРА "
310 PRINT " 2. ВВОД БИБЛИОГРАФИИ "
311 PRINT " 3. ПРОСМОТР БИБЛИОГРАФИИ "
312 PRINT " 4. ВЫХОД ИЗ ПРОГРАММЫ "
313 PRINT " 5. ЗАГРУЗКА ФАЙЛА С ДИСКА "
314 PRINT " 6. СОХРАНЕНИЕ ФАЙЛА НА ДИСК "
315 PRINT " 7. СОРТИРОВКА ПО ФАМИЛИЯМ "
316 PRINT " 8. УДАЛЕНИЕ ЗАПИСЕЙ "
317 PRINT " 9. КОРРЕКЦИЯ ЗАПИСЕЙ "
330 PRINT " "
340 REM---
350 IF INKEY$="1" THEN GOSUB 400
351 IF INKEY$="2" THEN GOSUB 120
352 IF INKEY$="3" THEN GOSUB 600
353 IF INKEY$="4" THEN GOTO 9999
354 IF INKEY$="5" THEN GOSUB 700
355 IF INKEY$="6" THEN GOSUB 800
356 IF INKEY$="7" THEN GOSUB 500
357 IF INKEY$="8" THEN GOSUB 900
358 IF INKEY$="9" THEN GOSUB 1000
370 GOTO 340
400 REM///
401 REM ===ПОДПРОГРАММА ПОИСКА ССЫЛКИ ПО ФАМИЛИИ АВТОРА===
402 INPUT "ВВЕДИТЕ ФАМИЛИЮ ДЛЯ ПОИСКА:";NAME2$
403 PRINT "ФАМИЛИЯ ДЛЯ ПОИСКА: ";NAME2$
404 REM 
405 INPUT "НАЧАТЬ ПОИСК? (Y/N) ";NAME3$
406 IF NAME3$="Y" THEN GOTO 410
407 IF NAME3$="N" THEN GOTO 250
408 PRINT "НЕВЕРНЫЙ ОТВЕТ. ПОВТОРИТЕ ВВОД."
409 GOTO 404
410 REM+++
411 FOR K3 = 1 ТО NMIN
420 IF FAM$(K3)=NAME2$ THEN GOTO 450
430 NEXT K3
440 PRINT "ФАМИЛИЯ ";NAME2$;" НЕ НАЙДЕНА"
445 GOTO 470
450 REM+++
451 PRINT "НАЙДЕНА ФАМИЛИЯ АВТОРА: ";FAM$(K3)
452 PRINT " НОМЕР ССЫЛКИ В СПИСКЕ ";K3
454 PRINT " ЗАГОЛОВОК: ";TITLE$(K3)
456 PRINT " ТОМ: ":VOL$(K3)
458 PRINT " НОМЕР: ";NUM$(K3)
460 PRINT " СТРАНИЦА: ";PAGE$(K3)
462 PRINT " ГОД: ";YEAR$(K3)
464 PRINT " ИЗДАТЕЛЬСТВО: ";PUBL$(K3)
470 RETURN
500 REM///
510 REM ===ПОДПРОГРАММА СОРТИРОВКИ ФАЙЛА БИБЛИОГРАФИИ ===
520 PRINT " ИЗВИНИТЕ, ЭТА ВОЗМОЖНОСТЬ ПОКА НЕ РЕАЛИЗОВАНА"
530 RETURN
600 REM///
610 REM ===ПОДПРОГРАММА ПРОСМОТРА БИБЛИОГРАФИИ===
611 PRINT "В ФАЙЛЕ НАХОДИТСЯ ";NMIN;" ЗАПИСЕЙ"
612 PRINT " "
615 INPUT "ВВЕДИТЕ НАЧАЛЬНЫЙ НОМЕР ЗАПИСИ ДЛЯ ПРОСМОТРА:";I1
617 INPUT "ВВЕДИТЕ КОНЕЧНЫЙ НОМЕР ЗАПИСИ ДЛЯ ПРОСМОТРА:";I2
620 FOR К4 = I1 ТО I2
630 PRINT "# " ;К4
640 PRINT FAM$(K4);TITLE$(К4);VOL$(К4);NUM$(K4);PAGE$(К4);YEAR$(К4)
650 PRINT PUBL$(K4)
660 NEXT К4
670 RETURN
700 RЕМ///
710 REM ===ПОДПРОГРАММА ЗАГРУЗКИ БИБЛИОГРАФИИ С ДИСКА===
720 PRINT " ИЗВИНИТЕ, ЭТА ВОЗМОЖНОСТЬ ПОКА НЕ РЕАЛИЗОВАНА"
730 RETURN
800 REM///
810 REM ===ПОДПРОГРАММА СОХРАНЕНИЯ БИБЛИОГРАФИИ НА ДИСКЕ==
820 PRINT " ИЗВИНИТЕ, ЭТА ВОЗМОЖНОСТЬ ПОКА НЕ РЕАЛИЗОВАНА"
880 RETURN
900 REM///
910 REM ===ПОДПРОГРАММА УДАЛЕНИЯ ЗАПИСЕЙ ИЗ ФАЙЛА===
920 PRINT " ИЗВИНИТЕ, ЭТА ВОЗМОЖНОСТЬ ПОКА НЕ РЕАЛИЗОВАНА"
930 RETURN
990 REM///
1000 REM ===ПОДПРОГРАММА МОДИФИКАЦИИ ЗАПИСЕЙ В ФАЙЛЕ===
1010 PRINT " ИЗВИНИТЕ, ЭТА ВОЗМОЖНОСТЬ ПОКА НЕ РЕАЛИЗОВАНА"
1020 RETURN
9999 REM+++ОКОНЧАНИЕ ПРОГРАММЫ

Здесь мы добавили операции ввода-вывода файла библиографии. Без записи на внешний носитель памяти созданный нами файл исчезнет при сбое питающего напряжения или при выключении ПЭВМ. Поэтому нужно периодически сбрасывать обновлённый файл на магнитофон или лучше на диск (если он имеется).

Предусмотрены также операции стирания и модификации отдельных записей файла, а также полезная сортировка по фамилиям авторов.

Когда используем ПЭВМ, мы обычно стеснены как в отношении оперативной, так и в отношении внешней памяти. Поэтому будем сохранять файл всегда в отсортированном по фамилиям авторов виде, а после удаления или добавления записей будем производить «принудительную» сортировку в памяти.

В меню на строках 280-230 мы зарезервировали все упомянутые выше операции. Чтобы у нас не было ошибок из- за несуществующих пока переходов на нереализованные подпрограммы, мы оставили в местах текста, предназначенных для этих подпрограмм, их «заготовки». Такая «заготовка», называемая ещё «затычкой» или «подыгрывающим модулем», хороша тем, что она даёт возможность последовательно реализовывать модуль за модулем, не нарушая задуманной структуры всей программы. Незнакомому же с программой или просто забывчивому пользователю «затычки» будут выдавать вежливый, ясный ответ. Читатель, наверное, уже обратил внимание, что первая строка в подпрограммах в согласии с принятыми раньше соглашениями - комментарий. Только теперь он помечен (как начало подпрограммы) тремя символами деления (косая черта). Будем отводить для начала подпрограмм строки с номерами, кратными сотне. Также в начале каждой подпрограммы отведём строку, кратко поясняющую функцию этой подпрограммы.

Отметим, что пришло в негодность наше соглашение о номере последней строки программы. Программа растёт, и нам пришлось увеличить зафиксированный ранее номер с 500 до 9999, а также отыскать и исправить указывающие на него ссылки. Это занятие, чреватое пропуском (недоисправлением) одной или нескольких ссылок, которые после удаления старой строки будут передавать управление уже несуществующей строке и прерывать задачу по ошибке. Такова цена удачных и неудачных соглашений о стандарте.

Принятый первоначально интервал между номерами строк тоже быстро исчерпал себя - кое-где номера следуют уже без перерыва. Имеются диалекты БЕЙСИКа (например, Acorn BBC), где можно использовать средство перенумерации строк (разумеется, с одновременными изменениями в соответствующих GOTO и GOSUB). В этом случае проблем не возникает. Если же такая готовая сервисная программа или команда языка отсутствует, возникает неприятная необходимость вручную перенумеровывать участки текста и изменять ссылки на них. В таких системах лучше с самого начала нумеровать строки через значительный интервал - 20 или даже 100. Подпрограммам уровня 2 (см. рис. 1) необходимо отвести участки номеров, кратные тысяче, чтобы собрать рядом в тексте не только модули второго уровня, а и логически с ними связанные подпрограммы следующих уровней. Такое расположение не обязательно, но удобно для последующей отладки.

Чтобы предотвратить в процессе ввода-вывода превышение максимальной длины массивов (определённой в строках 100-106), в подпрограмму ввода-вывода (строки 171-182) добавлены операторы, контролирующие эту ситуацию. Максимальный размер массивов выбирается в соответствии с размером оперативной памяти ПЭВМ и потребностью пользователя.

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

Обладатель Персональной ЭВМ с дисководом может отвести для файла три определённых имени на диске.

Вид операторов ввода-вывода будет несколько различаться в зависимости от типа ПЭВМ, однако структура соответствующих подпрограмм при этом не изменится. Например, для БЕЙСИКа - Acorn ВВС можно оформить ввод-вывод таким образом:

ПРИМЕР 4

700 REM///
710 REM ===ПОДПРОГРАММА ЧТЕНИЯ БИБЛИОГРАФИИ С ДИСКА===
720 INPUT "УКАЖИТЕ ИМЯ ФАЙЛА ДЛЯ ЧТЕНИЯ С ДИСКА";FILE$
725 INFILE=OPENIN(FILE$)
730 INPUT#INFILE,NMAX
735 FOR K2=1 TO NMIN
740 INPUT#INFILE;FAM$(K2);TITLE$(K2);VOL$(K2);NUM$(K2);PAGE$(K2)
742 INPUT#INFILE;YEAR$(K2);PUBL$(K2)
760 NEXT K2
770 REM ПРОКОНТРОЛИРОВАТЬ РЕЗУЛЬТАТЫ ЧТЕНИЯ С ДИСКА
775 CLOSE #INFILE
780 RETURN
800 REM///
810 REM ===ПОДПРОГРАММА СОХРАНЕНИЯ БИБЛИОГРАФИИ НА ДИСКЕ===
820 INPUT " УКАЖИТЕ ИМЯ ФАЙЛА ДЛЯ СОХРАНЕНИЯ НА ДИСКЕ: ";FILE$
825 OUTFILE = OPENOUT(FILE$)
830 PRINT #OUTFILE,NMAX,NMIN
835 FOP K2=1 TO NMIN
840 PRINT#OUTFILE;FAM$(K2);TITLE$(K2);VOL$(K2);NUM$(K2);PAGE$(K2)
842 PRINT#OUTFILE;YEAR$(K2);PUBL$(K2)
860 NEXT K2
870 REM ПРОКОНТРОЛИРОВАТЬ РЕЗУЛЬТАТЫ ЗАПИСИ НА ДИСК
875 CLOSE #OUTFILE
880 RETURN

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

Рассмотрим подпрограммы, реализующие операции 7, 8, 9 основного меню. Программа была структурирована нами, и теперь можно приводить только соответствующие подпрограммы, не повторяя остального текста. За пределами подпрограммы, возможно, будут добавляться только описания массивов и переменных (в отведённый участок в начале программы), а также каких-то новых функций в основном меню. Вот почему выгодно отводить участок строк в начале программы под описания, а не рассыпать определения по всему тексту. Сначала рассмотрим подпрограмму удаления ссылки:

ПРИМЕР 5.1

900 REM///
910 REM ===ПОДПРОГРАММА УДАЛЕНИЯ ЗАПИСИ===
915 INPUT "ВВЕДИТЕ НОМЕР УДАЛЯЕМОЙ ЗАПИСИ: ";K4
920 PRINT "НОМЕР УДАЛЯЕМОЙ ЗАПИСИ: ";K4
925 PRINT FAM$(K4);TITLE$(K4);VOL$(K4);NUM$(K4);PAGE$(K4);YEAR$(K4)
930 PRINT PUBL$(K4)
935 REM---
936 INPUT " УДАЛЯТЬ? (Y/N) ";NAME$
940 IF NAME$="N" THEN GOTO 990
945 IF NAME$="Y" THEN GOTO 960
950 PRINT " НЕВЕРНЫЙ ОТВЕТ! "
955 GOTO 935
960 REM+++
962 FAM$(K4)=FAM$(NMIN)
964 TITLE$(K4)=TITLE$(NMIN)
966 VOL$(K4)=VOL$(NMIN)
968 NUM$(K4)=NUM$(NMIN)
970 PAGE$(K4)=PAGE$(NMTN)
972 YEAR$(K4)=YEAR$(NMIN)
974 PUBL$(K4)=PUBL$(NMIN)
975 PRINT " ЗАПИСИ НОМЕР ";K4;" УДАЛЕНА"
976 NMIN=NMIN-1
978 NMAX=NMAX-1
990 REM+++
999 RETURN

Строки 900-999, согласно вызову из основного меню, отводятся под операцию стирания. Это - «горячая» операция, которая требует контроля в виде распечатки стираемой ссылки (920- 930). Убедившись, что для удаления выбрана необходимая запись, пользователь подтверждает её вычёркивание (935-955). Как и в операции поиска (строки 404-408), здесь воспринимаются только два ответа. При вводе чего-либо иного программа требует правильного ответа. Вычёркивание производится посредством перемещения последней записи файла на место удаляемой, чтобы в файле не образовывалось пустых мест. Вот и пригодилась переменная-указатель на последний занятый элемент массивов (NMIN). Перед завершением операции уменьшаются значения обоих указателей (NMIN и NMAX). Теперь можно выходить из подпрограммы.

Подпрограмма коррекции записи также выдаёт для контроля изменяемую запись (1002-1008), а затем меню для выбора конкретного элемента ссылки:

ПРИМЕР 5.2

1000 REM///
1001 REM ===ПОДПРОГРАММА КОРРЕКЦИИ ЗАПИСИ===
1002 INPUT "ВВЕДИТЕ НОМЕР КОРРЕКТИРУЕМОЙ ЗАПИСИ: ";K5
1004 PRINT "НОМЕР КОРРЕКТИРУЕМОЙ ЗАПИСИ: ";K5
1006 PRINT FAM$(K5);TITLE$(K5);VOL$(K5);NUM$(K5);PAGE$(K5);YEAR$(K5)
1008 PRINT PUBL$(K5)
1010 REM---
1012 PRINT " ВЫБЕРИТЕ ПОЛЕ ДЛЯ ИСПРАВЛЕНИЯ: "
1014 PRINT " ФАМИЛИЯ АВТОРА: 1 "
1018 PRINT " ЗАГОЛОВОК: 2 "
1020 PRINT " ТОМ: 5 "
1022 PRINT " НОМЕР: 4 "
1024 PRINT " СТРАНИЦА: 5 "
1026 PRINT " ГОД: 6 "
1028 PRINT И ИЗДАТЕЛЬСТВО: 7 "
1030 REM ---
1032 IF INKEY$<>"1" THEN GOTO 1036
1033 I=1
1034 NAME$=FAM$(K5)
1035 GOTO 1061
1036 IF INKEY$<>"2" THEN GOTO 1040
1037 I=2
1038 NAME$=TITLE$(K5)
1039 GOTO 1061
1040 IF INKEY$<>"3" THEN GOTO 1044
1041 I=3
1042 NAME$=VOL$(K5)
1043 GOTO 1061
1044 IF INKEY$<>"4" THEN GOTO 1048
1045 I=4
1046 NAME$=NUM$(K5)
1047 GOTO 1061
1048 IF INKEY$<>"5" THEN GOTO 1052
1049 I=5
1050 NAME$=PAGE$(K5)
1051 GOTO 1061
1052 IF INKEY$<>"6" THEN GOTO 1056
1053 I=6
1054 NAME$=YEAR$(K5)
1055 GOTO 1061
1056 IF INKEY$<>"7" THEN GOTO 1030
1057 I=7
1058 NAME$=PUBL$(K5)
1061 REM+++
1062 PRINT "ВЫ ВЫБРАЛИ ДЛЯ КОРРЕКЦИИ ПОЛЕ: ";NAME$
1064 INPUT "ВВЕДИТЕ ИСПРАВЛЕННОЕ ПОЛЕ: ";NAME2$
1065 PRINT "СТАРОЕ: ";NAME$
1066 PRINT " НОВОЕ: ";NAME2$
1067 REM---
1068 INPUT "ЗАНОСИТЬ ИСПРАВЛЕНИЕ? (Y/N) ";ANS$
1069 IF ANS$="N" THEN GOTO 1082
1070 IF ANS$="Y" THEN GOTO 1073
1071 PRINT " НЕВЕРНЫЙ ОТВЕТ! "
1072 GOTO 1067
1073 REM+++
1074 IF I=1 THEN FAM$(K5)=NAME2$
1075 IF I=2 THEN TITLE$(K5)=NAME2$
1076 IF I=3 THEN VOL$(K5)=NAME2$
1077 IF I=4 THEN NUM$(K5)=NAME2$
1078 IF I=5 THEN PAGE$(K5)=NAME2$
1079 IF I=6 THEN YFAR$(K5)=NAME2$
1080 IF I=7 THEN PUBL$(K5)=NAME2$
1081 PRINT "ИСПРАВЛЕНИЕ ПРОИЗВЕДЕНО"
1082 REM+++
1099 RETURN

Участок строк 1030-1061 заносит в переменную NAME$ элемент одного из массивов в зависимости от нажатой клавиши. Как и в прочих приведённых выше переключателях режимов с участием операторов INKEY$, программа «крутится» по строкам 1030-1061 до получения с клавиатуры одного из допустимых ответов. Строки 1062- 1072 организуют, как обычно, визуальную проверку введённой информации, а в строках 1074-1080 введённая во временную переменную NAME2$ информация записывается в указанный переменной I массив.

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

Нам осталось рассмотреть последнюю операцию: сортировку.

ПРИМЕР 5.3

300 REM///
301 REM ===ПОДПРОГРАММА СОРТИРОВКИ В АЛФ. ПОРЯДКЕ ПО ФАМИЛИИ ABTOPA===
305 J=NMIN
310 REM--
311 REM ЕСЛИ ВСЕ ОТСОРТИРОВАЛИ, ТО ИДТИ НА ВЫХОД
312 IF J<2 GOTO 342
313 REM ПРОСМОТР ПЕРВОЙ ПАРЫ
314 I = 1
315 REM---
316 REM ЕСЛИ ПРОСМОТРЕНЫ ВСЕ ПАРЫ, ТО ИДТИ НА СЛЕДУЮЩИЙ ПРОХОД
320 IF I>J-1 GOTO 335
321 REM ЕСЛИ ЭЛЕМЕНТЫ УЖЕ УПОРЯДОЧЕНЫ, ИХ ОБМЕН НЕ НУЖЕН
322 IF FAM$(I)<=FAM$(I+1) THEN GOTO 332
328 REM ОСУЩЕСТВИТЬ ОБМЕН ЭЛЕМЕНТАМИ
330 GOSUB 370
332 REM+++
333 I=I+1
334 GOTO 315
335 REM+++
340 J=J-1
341 GOTO 310
342 REM+++
343 PRINT " СОРТИРОВКА ОКОНЧЕНА"
344 RETURN
370 REM/// ОБМЕН МЕСТАМИ ЭЛЕМЕНТОВ МАССИВОВ
371 N$=FAM$(I)
372 FAM$(I)=FAM$(I+1)
373 FAM$(I+1)=N$
374 N$=TITLE$(I)
375 TITLE$(I)=TITLE$(I+1)
376 TITLE$(I+1)=N$
377 N$=VOL$(I)
378 VOL$(I)=VOL$(I+1)
379 VOL$(I+1)=N$
380 N$=NUM$(I)
381 NUM$(I)=NUM$(I+1)
382 NUM$(I+1)=N$
383 N$=PAGE$(I)
384 PAGE$(I)=PAGE$(I+1)
385 PAGE$(I+1)=N$
386 N$=YEAR$(I)
387 YEAR$(I)=YEAR$(I+1)
388 YEAR$(I+1)=N$
389 N$=PUBL$(I)
390 PUBL$(I)=PUBL$(I+1)
391 PUBL$(I+1)=N$
392 RETURN

Здесь применена самая простая из сортировок - сортировка методом «всплывающего пузырька». Название хорошо воспроизводит порядок работы алгоритма, просматривающего элементы файла попарно и меняющего местами элементы пары, если она расположена неупорядоченно. Поэтому одни элементы сортируемого списка как бы «всплывают» к его началу, а другие спускаются ближе к его концу. Если, например, надо упорядочить по возрастанию четыре числа, то цикл по переменным I и J (строки 310-341) будет исполняться так:

10

10

10

10

10

1

26

14

14

14

1

10

14

26

1

1

14

14

1

1

26

26

26

26

I=1

l=2

l=3

I=1

l=2

l=3

 

J=4

 

 

J=3

J=2

Строка 330 служит вызовом подпрограммы обмена местами элементов массивов (370-392) и использует при этом временную переменную NAME$.

К сожалению, подпрограмма сортировки, которую мы привели здесь и которую читатель может встретить почти в каждом англоязычном пособии по БЕЙСИКу, обладает существенным недостатком, а именно: она может гарантировать упорядочение только латинского алфавита. Причина заключается в следующем: действие строки 322, сравнивающей две строковые переменные, основано на побуквенном сопоставлении в сравниваемых переменных внутренних кодов ПЭВМ, которые всегда упорядочены для латинского и не всегда упорядочены для русского алфавита. Так, например, началу латинского алфавита (буквам А, В, С, D...) соответствуют коды ASCII 65, 66, 67, 68 и т.д. Тем самым будут справедливы «неравенства»: «А» < «В», «С» < «D», «BASIC» < «PASCAL», «12» < «15». Последовательность кодов для русского алфавита читатель может узнать из документации к своей ПЭВМ или же запустив такую программу:

ПРИМЕР 5.4

10 INPUT "ВВЕДИТЕ РУССКУЮ БУКВУ: ";A$
15 PRINT ASC(A$)
20 GOTO 10

Вводя в алфавитном порядке русские буквы, мы увидим на экране последовательность их кодов для данной ПЭВМ. Если эта последовательность не является монотонно возрастающей, придётся заменить строку 332 специальной программой сравнения строк, которая и заключит собой ряд примеров БЕЙСИКа в этой статье:

ПРИМЕР 5.5

40 REM ОПРЕДЕЛЕНИЕ ВСПОМОГАТЕЛЬНОГО МАССИВА ДЛЯ СОРТИРОВКИ
50 DIM ALF$(33)
52 DATA " ","А","Б","В","Г","Д","Е","Ж","3","И","Й","К","Л","М","Н"
54 DATA "О","П","Р”,"С","Т","У","Ф","Х","Ц","Ч","Ш","Щ","Ь","Ы","Э"
56 DATA "Ю","Я",":"
58 REM ОПРЕДЕЛЕНИЕ ПОРЯДКА БУКВ ДЛЯ СОРТИРОВКИ
60 FOR K6=1 ТО 33
62 READ ALF$(K6)
64 NEXT K6
300 REM///
301 REM ===ПОДПРОГРАММА СОРТИРОВКИ РУССКОГО И ЛАТИНСКОГО АЛФАВИТОВ===
305 J=NMIN
310 REM---
311 REM ЕСЛИ ВСЕ ОТСОРТИРОВАЛИ, ТО ИДТИ НА ВЫХОД
312 IF J<2 GOTO 342
313 REM ПРОСМОТР ПЕРВОЙ ПАРЫ
314 I=1
315 REM---
316 К=0
317 REM ЕСЛИ ПРОСМОТРЕНЫ ВСЕ ПАРЫ, ТО ИДТИ НА СЛЕДУЮЩИЙ ПРОХОД
320 IF I>J-1 GOTO 335
321 REM ЕСЛИ ЭЛЕМЕНТЫ УЖЕ УПОРЯДОЧЕНЫ, ТО I=0 И ОБМЕН ИМИ НЕ НУЖЕН
322 GOSUB 345
323 IF K=0 THEN GOTO 332
328 REM ОСУЩЕСТВИТЬ ОБМЕН ЭЛЕМЕНТОВ:
330 GOSUB 370
332 REM+++
333 I=I+1
334 GOTO 315
335 REM+++
340 J=J-1
341 GOTO 310
342 REM+++
343 PRINT "СОРТИРОВКА ОКОНЧЕНА"
344 RETURN
345 REM/// ПОДПРОГРАММА СРАВНЕНИЯ ДВУХ ЭЛЕМЕНТОВ МАССИВА
346 K=0
347 L$=MID$(FAM$(I),1,1)
348 M$=MID$(FAM$(I+1),1,1)
349 IF L$<>":" OR M$<>":" THEN GOTO 352
350 IF FAM$(I)>FAM$(I+1) THEN K=1
351 GOTO 368
352 REM+++ЦИКЛ СРАВНЕНИЯ ДВУХ СТРОК
353 MAX=LEN(FAM$(I))
354 FOR K7=1 ТО МАХ
355 L$=MLD$(FAM$(I),K7,1)
356 M$=MID$(FAM$(I+1),K7,1)
357 FOR K8=1 ТО 33
358 IF ALF$(K8)=L$ THEN IM=K8
359 IF ALF$(K8)=M$ THEN IP=K8
360 NEXT K8
361 IF IP>IM THEN GOTO 368
362 IF IP=IM THEN GOTO 365
363 IF IP<IM THEN K=1
364 GOTO 368
365 REM+++
366 NEXT K7
368 REM+++
369 RETURN

По начальным номерам строк можно видеть, что нам пришлось вставить в начало программы определение и заполнение нового массива, задающего порядок букв русского алфавита. Разумеется, в нашей власти определить и любой другой порядок следования, в зависимости от целей конкретной операции упорядочивания. Здесь мы определили двоеточие как последний символ нашей последовательности. С двоеточия будем начинать ввод всех иностранных фамилий. Таким образом, в файле будут следовать сначала русские фамилии, а затем иностранные.

В уже приводившейся программе сортировки определена новая переменная К. Это - переменная состояния сравнения, принимающая только два значения: 0 (элементы упорядочены, и менять их местами не надо) и 1 (элементы расположены с нарушением порядка, и их надо поменять местами). Переменные, определённые только для таких двух значений, обычно именуют «флагами», а относительно их состояний говорится: 0 - «флаг опущен», 1 - «флаг поднят». У нас изначально флажок опускается (строка 316) и поднимается, если в процессе сравнения двух строк обнаруживается, что они образуют «неупорядоченную» пару. Это будет сигналом для вызова программы перемены мест (строка 330). Строка 322 вызывает модифицированную подпрограмму сравнения. Если результатом работы этой программы будет K=0, то строка 323 отправит нас в обход вызова в строке 330 - перемена местами не требуется, и для сравнения будет выбрана следующая пара элементов массивов.

Подпрограмма сравнения (строки 345-369), к радости виртуозов экономии памяти, выполнена почти без комментариев для самостоятельного прочтения апологетами абсолютно плотного кода. Для всех же остальных предлагается такая схема работы подпрограммы (рис. 2).

Рис.2.

Оператор в строке 346 опрокидывает флаг в «упорядоченное» положение. В переменные L$ и M$ помещаются первые символы сравниваемых элементов массивов. Если (строка 349) хотя бы одна из фамилий не начинается с двоеточия (содержит русские буквы), надо начинать сравнивание по нашему алфавиту. Если же обе фамилии начались с двоеточия (латинские буквы), нам достаточно обычного сравнения в строке 350. Цикл сравнения кириллицы (строки 345-366) отделяет в переменные L$ и M$ очередные символы для сравнения, производящегося в строках 357-360. Указатели IM и IP устанавливаются (см. рис. схемы подпрограммы) при совпадении очередных сравниваемых литер с литерами алфавита в строках 52-56.

Строка 361 отсеивает случай упорядоченного следования пары и, оставив флаг К опущенным, уводит на выход. Строка 362 реагирует на равенство пары сравниваемых литер и отправляет на следующую попытку сравнения следующей пары литер. Строка 363 фиксирует найденное нарушение следования по алфавиту, поднимая флаг К, которым далее воспользуется подпрограмма сортировки в строке 323.

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

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

200 FOR M=1 TO...
250 GOSUB 500
300 NEXT M

МОДУЛЬ 1

500 REM ///
600 GOSUB 800

МОДУЛЬ 2

800 REM ///
810 FOR M=2 TO...
850 NEXT M

МОДУЛЬ 3

Существуют гораздо более быстрые (но занимающие больший участок текста) сортировки. Некоторые конструкции можно перепрограммировать более компактно (но и более трудно для прочтения). Однако общий вывод заключается в том, что программа построена с учётом требований структурного программирования, вследствие чего мы имеем реальную возможность прилагать дальнейшие усилия для её улучшения.

  1. Ван Tассeл Д. Стиль, разработка, эффективность, отладка и испытание программ. - M.: Мир, 1985.
  2. Mайep Г. Надёжность программного обеспечения. - M.: Мир, 1980.
  3. Керниган Б.В., Плоджер P.B. Элементы стиля программирования. - M.: Радио и связь, 1983.

 

Performed by © gid, 2012-2024.