================================================================= ПРОГРАММА-СПРАВОЧНИК по книге Г.Шилдта Р А Б О Т А С ТУРБО ПАСКАЛЕМ (главы 6-11, приложения) г. Москва, 1990 г. ================================================================= Оглавление Работа с Турбо Паскалем #2/2 = 1 = ГЛАВА 6. ШИФРОВАНИЕ И СЖАТИЕ ДАННЫХ.............................4 КРАТКАЯ ИСТОРИЯ КРИПТОГРАФИИ....................................4 ТИПЫ ШИФРОВ.....................................................5 ШИФРЫ ЗАМЕНЫ....................................................7 ШИФРЫ ПЕРЕСТАНОВКИ.............................................16 ШИФРЫ ПОБИТОВОЙ ОБРАБОТКИ......................................22 СЖАТИЕ ДАННЫХ..................................................27 Восемь в семь..................................................27 Шестнадцатибуквенный алфавит...................................30 ДЕШИФРАЦИЯ.....................................................36 ГЛАВА 7. ГЕНЕРАЦИЯ СЛУЧАЙНЫХ ЧИСЕЛ И МОДЕЛИРОВАНИЕ.............40 ГЕНЕРАТОРЫ СЛУЧАЙНЫХ ЧИСЕЛ.....................................41 Определение качества генераторов...............................44 Использование нескольких генераторов...........................50 МОДЕЛИРОВАНИЕ..................................................54 Моделирование очередей обслуживания............................55 Управление портфелем акций методом случайного поиска...........64 ГЛАВА 8. СИНТАКСИЧЕСКИЙ РАЗБОР И ВЫЧИСЛЕНИЕ ВЫРАЖЕНИЙ..........68 Разбор выражения...............................................71 СИНТАКСИЧЕСКИЙ РАЗБОР..........................................75 ПРОСТАЯ ПРОГРАММА СИНТАКСИЧЕСКОГО РАЗБОРА ВЫРАЖЕНИЙ............77 Добавление переменных к синтаксическому анализатору............87 ПРОВЕРКА СИНТАКСИСА В ПРОГРАММЕ РЕКУРСИВНОГО НИСХОДЯЩЕГО СИНТАКСИЧЕСКОГО РАЗБОРА.......................................96 ГЛАВА 9. ИНСТРУМЕНТАРИЙ БАЗ ДАННЫХ ТУРБО ПАСКАЛЯ...............97 TURBO ACCESS...................................................97 Необходимые файлы..............................................98 Файлы данных и индексные файлы.................................98 Константы В-дерева в Turbo Access..............................99 Зарезервированные имена переменных и коды ошибок...............99 ClearKey......................................................101 DeleteKey.....................................................101 DeleteRec.....................................................101 FileLen.......................................................102 FindKey.......................................................102 GetRec........................................................102 InitIndex.....................................................102 MakeFile MakeIndex............................................102 NextKey.......................................................103 OpenFile и OpenIndex..........................................103 PrevKey.......................................................104 PutRec........................................................104 SearchKey.....................................................104 UsedRec.......................................................104 ПРИМЕР ПРОСТОГО ПОЧТОВОГО СПИСКА..............................106 ПРИМЕР ПРОГРАММЫ ИНВЕНТАРИЗАЦИИ...............................117 TurboSort.....................................................122 InP, OutP и Less..............................................122 Работа с Турбо Паскалем #2/2 = 2 = GINST.........................................................124 ГЛАВА 10. ГРАФИЧЕСКИЙ ИСТРУМЕНТАРИЙ ТУРБО ПАСКАЛЯ.............126 ТРЕБОВАНИЯ К АППАРАТНОМУ ОБЕСПЕЧЕНИЮ..........................127 СИСТЕМЫ КООРДИНАТ.............................................127 ТРЕБОВАНИЯ ПО ИНИЦИАЛИЗАЦИИ...................................129 БАЗОВЫЕ ГРАФИЧЕСКИЕ ЭЛЕМЕНТЫ..................................130 ГРАФИЧЕСКИЕ ТЕКСТОВЫЕ ПРОЦЕДУРЫ...............................133 Машинно-независимый набор символов............................133 DrawText и DrawTextW..........................................134 ОКНА..........................................................135 Увеличение масштаба изображения...............................137 ГИСТОГРАММЫ И КРУГОВЫЕ ДИАГРАММЫ..............................140 РИСОВАНИЕ ГРАФИКОВ............................................146 ГЛАВА 11. ЭФФЕКТИВНОСТЬ, МОБИЛЬНОСТЬ И ОТЛАДКА................147 ЭФФЕКТИВНОСТЬ.................................................147 Предотвращение дублирования кода..............................147 ЭФФЕКТИВНОСТЬ.................................................148 Предотвращение дублирования кода..............................149 Использование процедур и функций..............................151 Предложение Case против цепочки if/then/elso..................153 МОБИЛЬНОСТЬ ПРОГРАММ..........................................154 Применение констант...........................................154 Зависимость от операционной системы...........................155 Расширение Турбо Паскаля......................................156 ОТЛАДКА.......................................................157 Проблема указателей...........................................157 Переопределение встроенных процедур и функций.................160 Неожиданные синтаксические ошибки.............................162 Ошибки if/then/if/else........................................164 Забывание о параметрах var в процедурах и функциях............164 Общие соображения по отладке..................................166 ЗАКЛЮЧИТЕЛЬНЫЕ ЗАМЕЧАНИЯ......................................167 ПРИЛОЖЕНИЕ А. ПРЕОБРАЗОВАНИЕ ПРОГРАММ С ЯЗЫКОВ СИ И БЕЙСИК В ТУРБО ПАСКАЛЬ..............................................168 Сравнение Си и Турбо Паскаля..................................171 Преобразование циклов Си в циклы Турбо Паскаля................173 Пример трансляции.............................................175 Использование компьютера для помощи в преобразовании из Си в Турбо Паскаль..............................................178 ПРЕОБРАЗОВАНИЕ ИЗ БЕЙСИКА В ТУРБО ПАСКАЛЬ.....................188 Преобразование циклов Бейсика в циклы Турбо Паскаля...........188 Преобразование IF/THEN/ELSE...................................191 Создание подпрограмм Турбо Паскаля из программ Бейсика........193 Избавление от глобальных переменных...........................195 ЗАКЛЮЧИТЕЛЬНЫЕ СООБРАЖЕНИЯ ПО ТРАНСЛЯЦИИ......................196 ПРИЛОЖЕНИЕ В. РАЗЛИЧИЯ МЕЖДУ ТУРБО ПАСКАЛЕМ И СТАНДАРТНЫМ ПАСКАЛЕМ.....................................................197 НОВЫЕ КЛЮЧЕВЫЕ СЛОВА..........................................198 Работа с Турбо Паскалем #2/2 = 3 = absolute......................................................198 external......................................................198 implementation................................................199 inline........................................................199 interface.....................................................200 interrupt.....................................................200 shr и shl.....................................................200 Тип string....................................................201 uses..........................................................202 XOR...........................................................202 НОВЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ.....................................203 Экранные и графические процедуры..............................203 Строковая функция.............................................203 Процедуры взаимодействия с операционной системой..............204 ДОПОЛНИТЕЛЬНЫЕ РАЗЛИЧИЯ МЕЖДУ ТУРБО ПАСКАЛЕМ И СТАНДАРТНЫМ ПАСКАЛЕМ.....................................................205 ПРИЛОЖЕНИЕ С. ИСПОЛЬЗОВАНИЕ БЛОКОВ ТУРБО ПАСКАЛЯ.............206 СОЗДАНИЕ БЛОКА................................................206 ИСПОЛЬЗОВАНИЕ БЛОКОВ..........................................208 КОРОТКИЙ ПРИМЕР...............................................209 ПРИМЕР СТЕКА..................................................212 Работа с Турбо Паскалем #2/2 = 4 = ГЛАВА 6. ШИФРОВАНИЕ И СЖАТИЕ ДАННЫХ ----------------------------------------------------------------- Любители компьютеров и программирования получают удовольст- вие от игры с кодами и шифрами. Возможно, это объясняется тем, что в основе всех кодов лежат алгоритмы. Или тем, что эти люди просто имеют склонность к шифрованной информации, которую боль- шинство не может понять. Все программисты видимо балдеют, когда не программист смотрит на листинг программы и говорит что-нибудь вроде: "Боже, как это сложно". Поэтому и написание программ назы- вается "кодированием". С криптографией тесно связано сжатие данных. Сжатие данных означает упаковку информации в меньший объем, нежели обычно используется. Так как сжатие данных может играть роль в криптог- рафии и в нем применяется много тех же самых принципов, что и в криптографии, то эта тема включена в данную главу. Криптография в вычислительной технике важна по двум основным причинам. Наиболее очевидная - это необходимость поддерживать секретность уязвимых данных в системе с разделением ресурсов. Хо- тя защита с помощь паролей является достаточной для многих ситуа- ций, особо важные секретные файлы обычно кодируются для обеспече- ния высокого уровня защищенности. Второе использование кодирова- ния в компьютерах - это передача данных. Так как процедуры коди- рования являются довольно сложными, они обычно выполняются компь- ютером. Сжатие данных в общем случае применяется для увеличения ем- кости различных запоминающих устройств. Хотя стоимость запоминаю- щих устройств быстро падает в последние годы, иногда еще необхо- димо умещать большую информацию в меньший объем. КРАТКАЯ ИСТОРИЯ КРИПТОГРАФИИ ----------------------------------------------------------------- Хотя никто не знает, когда появилась тайнопись, но глиняная табличка, сделанная приблизительно 1500 лет до нашей эры, содер- жит один из самых ранних ее примеров. Она содержит закодированную формулу изготовления глазури для покрытия сосудов. Греки применя- ли коды по крайней мере с 475 года до нашей эры, а высшие слои в Риме использовали простые шифры в период царствования Юлия Цеза- ря. В начале нашей эры интерес к криптографии (также, как и к другим интеллектуальным занятиям) упал; единственными, кто иногда применял ее, были монахи. С наступлением эпохи возрождения искусство криптографии стало расцветать. Во времена Луи ХIV во Франции для правительственных сообщений использовалось шифрова- Работа с Турбо Паскалем #2/2 = 5 = ние, основанное на 587 произвольно набранных ключах. В ХIX веке два фактора способствовали развитию криптографии. Первым фактором были истории Эдгара Алана По такие, как "Золотой жук", в которых фигурируют закодированные сообщения и которые волновали воображение многих читателей. Вторым фактором явилось изобретение телеграфа и азбуки Морзе. Азбука Морзе была первым двоичным представлением (точка и тире) алфавита, которое получило широкое распространение. В первую мировую войну в ряде стран были разработаны "шифро- вальные машины", которые позволяют легко кодировать и декодиро- вать текст, используя сложный шифр. С этого момента история крип- тография становится историей дешифрации кодов. До того, как для кодирования и декодирования стали использо- ваться механические устройства, сложные шифры применялись не часто, так как требовали много времени и сил для кодирования и декодирования. Поэтому большинство кодов можно было расшифровать за относительно короткий промежуток времени. Однако, дешифрация стала гораздо более сложной, когда стали применяться шифровальные машины. Хотя современные компьютеры могли бы расшифровать эти ко- ды относительно легко, но даже компьютеры не могут приблизиться к выдающемуся таланту Герберта Ядлея, который до сих пор считается самым выдающимся дешифровальщиком всех времен. Он расшифровал в 1915 году в свое свободное время дипломатический код США, а затем в 1922 году дипломатический код Японии, хотя он даже не знал японского языка. Во время второй мировой войны главный метод дешифровки кодов основывался на краже неприятельской дешифровальной машины, таким образом можно было избежать утомительного процесса расшифровки кодов. Фактически обладание службой Аллеса германской шифроваль- ной машиной, что было не известно Германии, способствовало в оп- ределенной степени исходу войны. С приходом компьютеров, особенно многопользовательских, не- обходимость в засекречивании информации и в недешифруемых кодах стала еще более острой. Необходимо не только защищать файлы, но и управлять доступом собственно к компьютеру. Было разработано мно- жество методов шифрования файлов данных и алгоритм DES (Стандарт шифрования данных), принятый национальным бюро по стандартам, считается недоступным для расшифровки. Однако, DES труден для ре- ализации и подходит не для всех случаев. ТИПЫ ШИФРОВ ----------------------------------------------------------------- Большинство традиционных методов кодирования относится к од- Работа с Турбо Паскалем #2/2 = 6 = ному из двух основных типов: замена и перестановка. В шифрах за- мены один символ заменяется другим, но порядок следования симво- лов в сообщении не изменяется. В шифрах перестановки в соот- ветствии с некоторым правилом перемешиваются символы сообщения. Эти типы кодов могут быть любого уровня сложности и даже могут быть применены совместно. Цифровые компьютеры привнесли третий основной тип шифрования, называемый битовой обработкой, в котором по некоторому алгоритму изменяется машинное представление данных. Все три метода могут использовать ключ. Ключ - это строка символов, необходимая для дешифрования сообщения. Не путайте ключ с методом. Знание ключа не дает возможности дешифровать сообще- ние, необходимое также знать алгоритм шифрования. С другой сторо- ны знание метода шифрования без ключа также не дает такой возмож- ности; необходимо знать и метод и ключ. В данной главе описываются машинные методы, которые основаны на базовых методах кодирования текстовых файлов. Будет проде- монстрирован ряд коротких программ, которые осуществляют кодиро- вание и декодирование текстовых файлов. За одним исключением все эти программы осуществляют выполнение функций как кодирования, так и декодирования: функция декодирования всегда является обрат- ной по отношению к кодированию, используемому для получения шиф- рованного текста. Работа с Турбо Паскалем #2/2 = 7 = ШИФРЫ ЗАМЕНЫ ----------------------------------------------------------------- Один из простейших шифров замены представляет собой смещен- ный на определенное число позиций алфавит. Например, если каждая буква была смещена на три позиции, то алфавит abcdefghijklmnopqrstuvwxyz превращается в defghijklmnopqrstuvwxyzabc Отметим, что буквы abc выдвинулись и добавились в конец. Для того, чтобы закодировать сообщение, используя данный метод, вы просто подставляете символы сдвинутого алфавита вместо реальных символов. Например, сообщение meet me at sunset превращается в phhw ph dw vxqvhw Программа, показанная далее, позволяет зашифровать любое текстовое сообщение, используя любое по вашему выбору смещение: { шифр простой замены } program subst; type str80 = string[80]; var inf, outf: str80; start: integer; ch: char; procedure code (inf, outf: str80; start: integer); var infile, outfile: file of char; ch: char; t: integer; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); Работа с Турбо Паскалем #2/2 = 8 = while not eof(infile) do begin Read(infile, ch); ch : = upcase(ch); if (ch>='A') and (ch<='Z') then begin t := ord(ch)+start; if t>ord('Z') then t := t-26; ch := chr(t); end; Write(outfile, ch); end; WriteLn('файл закодирован'); close(infile); close(outfile); end; procedure decode(inf, outf: str80; start: integer); var infile, outfile: file of char; ch: char; t: integer; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); while not eof(infile) do begin read(infile, ch); ch := upcase(ch); if (ch>='A') and (ch<='Z') then begin t := ord(ch)-start; if t в произвольную строку, которая содержит все буквы алфавита, нап- ример qazwsxedcrfvtgbyhnujm ikolp Вы можете пожелать узнать, значительно ли улучшается стой- кость шифрования при использовании перемешанного варианта алфави- та по сравнению с вариантом простого смещения. Ответ: да. Дело в том, что существует 26! вариантов перестановок алфавита, а с про- белом число вариантов достигает 27!. Факториал числа представляет собой произведение всех чисел, не превосходящих данного. Напри- мер, 6! равно 6х5х4х3х2х1, то есть 720. Следовательно 26! очень большое число. Программа, представленная далее, реализует улучшенный шифр замены, использующий перемешанный алфавит, показанный выше. Если вы закодируете сообщение meet me at sunset используя программу реализации улучшенного шифра замены, полу- чится строка Работа с Турбо Паскалем #2/2 = 10 = tssjptspqjpumgusj которую значительно труднее дешифровать. { улучшенный шифр замены, который использует перемешанный алфавит } program subs1; type str80 = string[80]; var inf, outf: str80; alphabet,sub: str80; ch: char; { данная функция возвращает индекс в алфавите замены } function find(alphabet: str80; ch: char): integer; var t: integer; begin find := -1; { код ошибки } for t := 1 to 27 do if ch=alphabet[t] then find := t; end; {find} {данная функция возвращает TRUE истина, если с - это буква алфавита } function isalpha(ch: char): boolean; begin isalpha:=(upcase(ch)>='A') and (upcase(ch)<='Z'); end; {isalpha} procedure code(inf, outf: str80); var infile, outfile: file of char; ch: char; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); while not eof(infile) do begin Read(infile, ch); ch:=upcase(ch); Работа с Турбо Паскалем #2/2 = 11 = if isalpha(ch) or (ch=' ') then begin ch:=sub[find(alphabet, ch)]; { найти замену } end; Write(outfile, ch); end; WriteLn('файл закодирован'); close(infile); close(outfile); end; {code} procedure decode(inf, outf: str80); var infile, outfile: file of char; ch: char; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); while not eof(infile) do begin Read(infile, ch); ch:=upcase(ch); if isalpha(ch) or (ch=' ') then ch:=alphabet[find(sub,ch)]; {замена снова на реальный алфавит } Write(outfile, ch); end; WriteLn('файл декодирован'); close(infile); close(outfile); end; {decode} begin alphabet := 'ABCDEFGHIJKLMNOPQRSTUVWXYZ '; sub := 'CAZWSXEDCRFVTGBYHNUJM IKOLP'; Write('введите имя входного файла : '); ReadLn(inf); Write('введите имя выходного файла : '); ReadLn(outf); Write('кодировать или декодировать (C or D): '); ReadLn(ch); if upcase(ch)='C' then code(inf, outf) else if upcase(ch)='D' then decode(inf, outf); end. Работа с Турбо Паскалем #2/2 = 12 = Хотя дешифрация рассматривается далее в этой главе, вам сле- дует знать, что даже такой улучшенный код замены может быть срав- нительно легко дешифрован, используя частотные таблицы английско- го языка, в которых дана статистическая информация по использова- нию каждой буквы алфавита. Как вы можете легко заметить, глядя на зашифрованное сообщение, "s" почти наверняка является буквой "е", наиболее часто встречающейся буквой английского алфавита, а "р" - пробелом. Оставшаяся часть сообщения может быть дешифрована быст- ро и легко. Чем больше шифрованное сообщение, тем легче расшифровать его с помощью частотных таблиц. Для того, чтобы осложнить успех де- шифровальщика, применяющего частотные таблицы, вы можете исполь- зовать шифр множественной замены. Одна и та же буква в исходном сообщении не обязательно преобразуется в одну букву шифрованного текста. Один из возможных методов реализации шифра множественной замены состоит в добавлении второго перемешанного алфавита и пе- реключении с одного алфавита на другой при встрече в тексте про- бела. Пусть вторым перемешанным алфавитом будет следующий: poi uytrewqasdfghjklmnbvcxz Тогда, используя данный подход, сообщение meet me at sunset будет зашифровано в вид tssj su qj kmdkul Чтобы понаблюдать, как это выполняется, запишите упорядочен- ный алфавит и два перемешанных алфавита один под другим, как по- казано ниже алфавит: abcdefghijklmnopqrstuvwxyz<пробел> замена: qazwsxedcrfvtgbyhnujm ikolp замена2: poi uytrewqasdfghjklmnbvcxz В начале работы программы используется первый алфавит. Это означает, что "meet" кодируется в "tssj". Когда встречается пер- вый пробел происходит переключение на второй алфавит, приводящее к кодированию слова "me" в "su". Следующий пробел заставляет программу использовать снова первый алфавит. Этот процесс продол- жается до тех пор, пока не будет закодировано все сообщение. Данная программа создает шифр множественной замены: { шифр множественной замены } program subs3; Работа с Турбо Паскалем #2/2 = 13 = type str80=string[80]; var inf, outf: str80; alphabet, sub, sub2: str80; ch: char; {данная функция возвращает индекс в алфавите подстановки } function find(alphabet: str80; ch: char): integer; var t: integer; begin find:= -1; { код ошибки } for t:= 1 to 27 do if ch=alphabet[t] then find:= t; end; {find} {This function returns TRUE if ch is a letter of the alphabet.} function isalpha(ch: char): boolean; begin isalpha:= (upcase(ch)>='A') and (upcase(ch)<='Z'); end; {is alpha} procedure code(inf, outf: str80); var infile, outfile: file of char; ch: char; change: boolean; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); change := TRUE; while not eof(infile) do begin Read(infile,ch); ch := upcase(ch); { переключение алфавитов при каждом пробеле } if ch=' ' then change := not change; if isalpha(ch) then Работа с Турбо Паскалем #2/2 = 14 = begin if change then ch:=sub[find(alphabet,ch)] else ch:=sub2[find(alphabet,ch)]; end; Write(outfile, ch); end; WriteLn('файл закодирован '); close(infile); close(outfile); end; {code} procedure decode(inf, outf: str80); var infile, outfile: file of char; ch: char; change: boolean; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); change := TRUE; while not eof(infile) do begin Read(infile, ch); ch := upcase(ch); if ch=' ' then change := not change; if isalpha(ch) then begin if change then ch:=alphabet[find(sub, ch)] {find substitution} else ch:=alphabet[find(sub2, ch)]; {second sub} end; Write(outfile, ch); end; WriteLn('файл декодирован '); close(infile); close(outfile); end; begin alphabet:='ABCDEFGHIJKLMNOPQRSTUVWXYZ '; sub :='QAZWSXEDCRFVTGBYHNUJM IKOLP'; {алфавит #1} sub2 :='POI UYTREWQASDFGHJKLMNBVCXZ'; {алфавит #2} Работа с Турбо Паскалем #2/2 = 15 = Write('введите имя входного файла : '); ReadLn(inf); Write('введите имя выходного файла : '); ReadLn(outf); Write('кодировать или декодировать (C or D): '); ReadLn(ch); if upcase(ch)='C' then code(inf, outf) else if upcase(ch)='D' then decode(inf, outf); end. Использование шифров множественной замены существенно зат- рудняет дешифрацию кода с применением частотных таблиц, изза то- го, что разные буквы в разные моменты времени отображаются в одни и те же символы. Если вы поразмыслите над этой проблемой, то най- дете, наверное, методы использования различных перемешанных алфа- витов и более сложные процедуры переключения их, которые дадут одинаковую частоту использования букв в зашифрованном тексте. В этом случае частотные таблицы окажутся бесполезными при дешифра- ции кода. Работа с Турбо Паскалем #2/2 = 16 = ШИФРЫ ПЕРЕСТАНОВКИ ----------------------------------------------------------------- Одним из ранних вариантов шифра перестановки был разработан стандартами в 475 году до нашей эры. Он использовал устройство в виде длинной узкой ленты, которая накручивалась на цилиндр и на которой сообщение писалось поперек. Лента затем разматывалась и поставлялась получателю, который имел точно такой же цилиндр. Те- оретически возможно прочитать ленту без цилиндра, так как буквы все-таки сохраняют порядок следования. Однако, на практике данный метод остается только возможным, так как необходимо перепробовать большое количество различных размеров цилиндров прежде, чем сооб- щение начинает вырисовываться. Вы можете создать машинную версию такого шифра, поместив исходное текстовое сообщение в матрицу одним способом и выведя его другим способом. Для того, чтобы сделать это, для хранения кодируемого сообщения используется одновременная строка, но сооб- щение записывается в дисковый файл в виде матрицы. В нашем вари- анте исходный текст, представляющий собой строку длиной 100 байт, записывается на диск, как матрица 5х20. Однако, вы могли бы использовать матрицу любой другой размерности. Так как сообщение помещается в матрицу фиксированного размера, то существует веро- ятность того, что не все элементы матрицы будут использованы. Это делает необходимым инициализацию матрицы перед помещением в нее исходного текста. На практике лучше инициализировать матрицу про- извольными символами, однако, для простоты используется символ "#". Если вы поместите сообщение meet me at sunset в матрицу, она будет выглядеть следующим образом │ m │ e │ e │ t │ │ ├───┼───┼───┼───┼───┤ │ m │ e │ │ a │ t │ ├───┼───┼───┼───┼───┤ │ │ s │ u │ n │ s │ ├───┼───┼───┼───┼───┤ │ e │ t │ # │ # │ # │ ├───┼───┼───┼───┼───┤ │ # │ # │ # │ # │ # │ Если вы затем осуществите запись матрицы по столбцам, то сообщение будет выглядеть следующим образом: Работа с Турбо Паскалем #2/2 = 17 = mm e...eest...e u...tan... ts где точки обозначают соответствующее количество символов "#". Для декодирования сообщения заполняются столбцы матрицы. Затем матри- ца может быть отображена в нормальном порядке. Программа Skytale использует данный метод для кодирования и декодирования сообще- ний: {шифр skytale} { примечание: наибольшее сообщение, которое может быть за- кодировано, состоит из 100 байт } program skytale; type str100 = string[100]; str80 = string[80]; var inf, outf:str80; sky: str100; t: integer; ch: char; procedure code(inf, outf: str80); var infile, outfile: file of char; ch: char; t, t2: integer; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); t := 1; { считывание текстового файла, как одномерной матрицы } while (not eof(infile)) and (t<=100) do begin Read(infile, sky[t]); t := t+1; end; { запись в матрицу размера 5х20 } for t := 1 to 5 do for t2 := 0 to 19 do Write(outfile, sky[t+(t2*5)]); WriteLn('файл закодирован'); Работа с Турбо Паскалем #2/2 = 18 = close(infile); close(outfile); end; {code} procedure decode(inf, outf: str80); var infile, outfile: file of char; ch: char; t, t2: integer; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); { считывание матрицы размером 5х20 } for t := 1 to 5 do for t2 := 0 to 19 do Read(infile, sky[t+(t2*5)]); { вывод в качестве строки } for t := 1 to 100 do Write(outfile, sky[t]); WriteLn('файл декодирован'); close(infile); close(outfile); end; {decode} begin { заполнение символов "#" } for t := 1 to 100 do sky[t] := '#'; Write('введите имя входного файла: '); ReadLn(inf); Write('введите имя выходного файла: '); ReadLn(outf); Write('кодировать или декодировать (C or D): '); ReadLn(ch); if upcase(ch)='C' then code(inf, outf) else if upcase(ch)='D' then decode(inf, outf); end. Существуют другие методы получения перемешанных сообщений. Один метод, особенно подходящий для компьютера, использует пе- рестановку букв внутри сообщения с некоторым алгоритмом. Следую- щая программа перемешивает буквы: {шифр перемешивания. Длина сообщения не должна превышать 100 символов } Работа с Турбо Паскалем #2/2 = 19 = program transpose; type str100 = string[100]; str80 = string[80]; var inf, outf: str80; message: str100; ch: char; t: integer; procedure code(inf, outf: str80); var infile, outfile: file of char; temp: char; t, t2: integer; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); t := 1; while (not eof(infile)) and (t<=100) do begin Read(infile, message[t]); t := t+1; end; message[t-1] := {удаление знака конца файла } { теперь перемешиваются символы } for t2 := 0 to 4 do for t := 1 to 4 do begin temp := message[t+t2*20]; message[t+t2*20] := message[t+10+t2*20]; message[t+10+t2*20] := temp; end; {now write it out} for t := 1 to 100 do Write(outfile, message[t]); WriteLn('файл закодирован'); close(infile); close(outfile); end; {code} procedure decode(inf, outf: str80); Работа с Турбо Паскалем #2/2 = 20 = var infile, outfile: file of char; temp: char; t, t2: integer; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); t := 1; while (not eof(infile)) and (t<=100) do begin Read(infile, message[t]); t := t+1; end; message[t-1] := '#'; {удаление знака конца файла } {теперь перемешиваются символы } for t2 := 0 to 4 do for t := 1 to 4 do begin temp := message[t+t2*20]; message[t+t2*20] := message[t+10+t2*20]; message[t+10+t2*20] := temp; end; { теперь осуществляем вывод } for t := 1 to 100 do Write(outfile, message[t]); WriteLn('файл декодирован'); close(infile); close(outfile); end; {decoded} begin for t := 1 to 100 do message[t] := '#'; Write('введите имя входного файла : '); ReadLn(inf); Write('введите имя выходного файла : '); ReadLn(outf); Write('кодировать или декодировать (C or D): '); ReadLn(ch); if upcase(ch)='C' then code(inf, outf) else if upcase(ch)='D' then decode(inf, outf); end. Работа с Турбо Паскалем #2/2 = 21 = Хотя коды перестановки могут быть эффективными, алгоритмы, если требуется высокий уровень секретности, становятся очень сложными. Работа с Турбо Паскалем #2/2 = 22 = ШИФРЫ ПОБИТОВОЙ ОБРАБОТКИ ----------------------------------------------------------------- Цифровые компьютеры положили начало новому методу шифрова- ния, основывающемуся на обработке бит, составляющих символы исходного текста. Хотя побитовая обработка является вариацией шифра замены, концепции, методы и возможности отличаются столь значительно, что он должен рассматриваться, как отдельный вид шифра. Шифры побитовой обработки хорошо подходят для применения в компьютерах, так как они используют операции, легко выполняемые компьютерами. В общем случае шифры побитовой обработки применимы только для файлов компьютеров и не могут быть использованы для создания печатной копии, так как побитовая обработка ведет к порождению непечатных знаков. По этой причине предполагается, что любой файл, зашифрованный методами побитовой обработки, будут оста- ваться компьютерными файлами. Шифры побитовой обработки преобразуют исходную информацию в шифрограмму, изменяя исходную битовую комбинацию каждого символа с помощью одного или нескольких следующих логических операторов: AND OR NOT XOR Турбо Паскаль является одним из лучших языков для реализации шифров побитовой обработки, так как он поддерживает данные опера- торы для использования над типом данных bite. Когда данный опера- тор применяется к байтовым переменным, операция осуществляется на байт-байтовой основе, легко выполняя изменения состояний битов в байте. Простейший и наименее стойкий шифр использует только опера- тор NOT (напомним, что оператор NOT вызывает инверсию каждого би- та в байте: 1 переходит в 0, а 0 в 1). Следовательно, байт, ин- вертированный дважды, равен исходному. Следующая программа, назы- ваемая Complement (дополнение), шифрует любой текстовый файл ин- вертированием битов внутри каждого символа. Так как Турбо Паскаль строг в отношении типов переменных, в программе следует использо- вать переменные типа byte (байтовые), а не char (символьные), чтобы можно было применить операторы побитовой обработки. { шифр дополнения до 1 } program complement; type str80 = string[80]; Работа с Турбо Паскалем #2/2 = 23 = var inf,out: str80; ch: char; t: integer; procedure code(inf, outf: str80); var infile, outfile: file of byte; ch: byte; begin assing(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); while not eof(infile) do begin Read(infile, ch); ch := not ch; Write(outfile, ch); end; WriteLn('файл закодирован'); close(infile); close(outfile); end; {code} procedure decode(inf, outf: str80); var infile, outfile: file of byte; ch: byte; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); while not eof(infile) do begin Read(infile, ch); ch := not ch; Write(outfile, ch); end; WriteLn('файл декодирован'); close(infile); close(outfile); end; {decoded} Работа с Турбо Паскалем #2/2 = 24 = begin Write('введите имя входного файла: '); ReadLn(inf); Write('введите имя выходного файла: '); ReadLn(outf); Write('кодировать или декодировать (C or D): '); ReadLn(ch); if upcase(ch)='C' then code(inf, outf) else if upcase(ch)='D' then decode(inf,outf); end. Трудно показать, как будет выглядеть шифрованное сообщение, так как побитовая обработка, применяемая здесь, в общем случае порождает непечатные символы. Попробуйте шифр на вашем компьютере и проанализируйте полученный файл; он будет выглядеть вполне засекреченным. Существует два недостатка в данной простой схеме шифрования. Во-первых, программа шифрования не требует ключа для декодирова- ния, следовательно, любой имеющий доступ к программе может деко- дировать файл. Во-вторых, и возможно это более важно, данный ме- тод мог бы разгадать любой опытный программист. В улучшенном методе кодирования с помощью побитовой обработ- ки используется оператор XOR. Оператор XOR имеет следующую табли- цу истинности: XOR │ 0 │ 1 ────┼────┼──── 0 │ 0 │ 1 ────┼────┼──── 1 │ 1 │ 0 Результат операции XOR равен TRUE (истина) тогда и только тогда, когда один оператор равен TRUE, а второй - false (ложь). Это дает XOR уникальные свойства: если выполнить операцию XOR над двумя байтами, один из которых называется ключем, а затем взять результат и ключ и снова применить к ним операцию XOR, то в ре- зультате получим исходный байт, как показано ниже: 1101 1001 XOR 0101 0011 /ключ/ ----------- 1000 1010 равны 1000 1010 XOR 0101 1001 /ключ/ ----------- Работа с Турбо Паскалем #2/2 = 25 = 1101 1001 Будучи примененными для кодирования файлов, данный процесс разрешает две проблемы, присущие методу, основанному на операции инвертирования. Во-первых, так как применяется ключ, наличие только программы декодирования не позволяет дешифровать файл; во-вторых, из-за того, что использование ключа делает каждый файл своеобразным, то он будет очевиден тем, кто изучал только вы- числительную технику. Ключ не обязательно должен быть только однобайтовым. Напри- мер, вы могли бы использовать несколько символов и применять их последовательно на одном файле. Однако, в приведенной ниже прог- рамме для простоты используется однобайтовый ключ. { шифр на основе операции XOR с ключем } program xor_wiht_key; type str80 = string[80]; var inf, outf: str80; key: byte; ch: char; t: integer; procedure code(inf, outf: str80; key: byte); var infile, outfile: file of byte; ch: byte; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); while not eof(infile) do begin Read(infile, ch); ch := key xor ch; Write(outfile, ch); end; WriteLn('файл закодирован'); close(infile); close(outfile); end; {code} Работа с Турбо Паскалем #2/2 = 26 = procedure decode(inf, outf: str80; key: byte); var infile, outfile: file of byte; ch: byte; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); while not eof(infile) do begin Read(infile, ch); ch := key xor ch; Write(outfile, ch); end; WriteLn('файл декодирован'); close(infile); close(outfile); end; {decode} begin Write('введите имя входного файла: '); ReadLn(inf); Write('введите имя выходного файла; '); ReadLn(outf); Write(' введите односимвольный ключ : '); ReadLn(ch); key := ord(ch); Write('кодировать или декодировать (C or D): '); ReadLn(ch); if upcase(ch)='C' then code(inf, outf, key) else if upcase(ch)='D' then decode(inf, outf, key); end. Работа с Турбо Паскалем #2/2 = 27 = СЖАТИЕ ДАННЫХ ----------------------------------------------------------------- Методы сжатия данных позволяют заданное количество информа- ции упаковать в меньший объем. Они часто используются в вычисли- тельных системах для увеличения ресурсов памяти за счет снижения необходимых объемов, для снижения времени передачи информации (особенно по телефонному каналу), для повышения уровня секрет- ности. Хотя существует много схем сжатия данных, мы рассмотрим только две из них. Первая - это битовое сжатие, в котором более одного символа запоминается в одном байте, а вторая - это уничто- жение символов, при котором осуществляется удаление символов из файла. Восемь в семь ----------------------------------------------------------------- Большинство современных компьютеров используют размеры бай- та, которые являются степенью двойки. Заглавные и строчные буквы и знаки пунктуации составляют приблизительно 63 кодов, что требу- ет 6 бит для представления байта (6-битовый байт может принимать значения от 0 до 63). Однако, большинство компьютеров использует 8-битовый байт; таким образом 25% байта тратится зря в текстовых файлах. Следовательно, можно упаковать 4 символа и 3 байта. Единственная проблема состоит в том, что в коде ASCII существует более 63 различных символов. Это означает, что некоторые необхо- димые символы требуют по крайней мере 7 бит. Можно использовать представление не в коде ASCII, но это нежелательно. Самый простой вариант - это упаковка 8 символов в 7 байт, основывающийся на том факте, что ни буквы, ни знаки пунктуации не используют восьмого бита в байте. Следовательно, вы можете использовать восьмой бит каждого из семи байт для запоминания восьмого символа. Данный ме- тод экономит 12,5% памяти. Однако, многие компьютеры, включая IBM PC, используют весь 8-битовый байт для представления специальных и графических символов. Кроме того, некоторые текстовые процессо- ры используют восьмой бит для задания команд текстовой обработки. Следовательно, использование данного типа упаковки данных возмож- но только с файлами типа ASCII, которые не используют восьмого бита. Для демонстрации того, как это происходит, рассмотрим следу- ющие 8 символов, представленные как 8-битовые байты: байт 1 0111 0101 байт 2 0111 1101 байт 3 0010 0011 байт 4 0101 0110 Работа с Турбо Паскалем #2/2 = 28 = байт 5 0001 0000 байт 6 0110 1101 байт 7 0010 1010 байт 8 0111 1001 Как вы видите, восьмой байт всегда равен 0. Так происходит всегда, кроме случая, когда восьмой бит используется для контроля четности. Самый простой способ сжатия 8 символов в 7 байт состоит в том, чтобы распределить 7 значащих бит байта 1 в семь неисполь- зуемых восьмых битовых позиций байтов 2-8. Семь оставшихся байт будут выглядеть следующим образом: байт 2 1111 1101 байт 3 1010 0011 байт 4 1101 0110 байт 5 0001 0000 байт 6 1110 1101 байт 7 0010 1010 байт 8 1111 1001 байт 1 - читать вниз Для восстановления байта 1 вы собираете вместе восьмые биты каждого из 7 байт. Данный метод сжимает любой текстовый файл на 1/8 или на 12,5%. Это весьма существенная экономия. Например, если вы пере- давали исходный текст вашей любимой программы другу по телефонной линии на большое расстояние, то вы сократите расходы на 12,5% (напомним, что объектный и исполнительные коды программы требуют всех 8 бит). Следующая программа осуществляет сжатие текстового файла, используя описанный метод: { данная программа сжимает 8 байт в семь } program compress_file; type str80 = string[80]; var inf, outf: str80; ch: char; t: integer; procedure compress(inf, outf: str80); var infile, outfile: file of byte; ch, ch2: byte; Работа с Турбо Паскалем #2/2 = 29 = done: boolean; begin assign(infile, inf); reset(infile); assign(outfile, outf); rewrite(outfile); done := FALSE; repeat Read(infile, ch); if eof(infile) then done := TRUE else begin ch:=ch shl 1; {выдвижение свободного бита} for t := 0 to 6 do begin if eof(infile) then begin ch2 := 0; done := TRUE; end else Read(infile, ch2); ch2:=ch2 and 127; {сброс старшего бита } ch2:=ch2 or ((ch shl t) and 128); {pack bits} Write(outfile, ch2); end; end; {else} until done; WriteLn('file compressed'); 7 close(infile); close(outfile); end; {compress} procedure decompress(inf, outf: str80); var infile, outfile:file of byte; ch, ch2: byte; s: array[1..7] of byte; done: boolean; begin assign(infile, inf); reset(infile); assign(outfile,outf); rewrite(outfile); done := FALSE; Работа с Турбо Паскалем #2/2 = 30 = repeat ch := 0; for t := 1 to 7 do begin if eof(infile) then done := TRUE else begin Read(infile, ch2); s[t] := ch2 and 127; {сброс старшего бита} ch2 := ch2 and 128; {очистка младших битов} ch2 := ch2 shr t; {распаковка} ch := ch or ch2; {встраивание восьмого байта} end; end; Write(outfile, ch); for t := 1 to 7 do Write(outfile, s[t]); until done; WriteLn('file decompressed'); close(infile); close(outfile); end; {decompress} begin Write('введите имя входного файла: '); ReadLn(inf); Write('введите имя выходного файла: '); ReadLn(outf); Write('сжатие или распаковка (C or D): '); ReadLn(ch); if upcase(ch)='C' then compress(inf, outf) else if upcase(ch)='D' then decompress(inf,outf); end. Данная программа достаточно сложна, так как различные биты должны быть сдвинуты циклически. Если вы помните, что надо сде- лать с первым из каждых восьми байт то легче понять программу. Для того, чтобы программа работала правильно, длина сжимаемого файла должна быть кратна 8. Это означает, что очень короткие фай- лы (меньше 64 символов) будут длинее, чем несжатая версия. Одна- ко, на длинных файлах этого не произойдет. Шестнадцатибуквенный алфавит ----------------------------------------------------------------- Хотя это не подходит для всех ситуаций, интерес представляет Работа с Турбо Паскалем #2/2 = 31 = метод сжатия данных, в котором уничтожаются ненужные буквы из слова, то есть слово сокращается. Сжатие данных происходит за счет того, что неиспользуемые символы не запоминаются. Экономия пространства за счет сокращений довольно распространена, напри- мер, "Mr" используется вместо "Mister". Вместо применения общеп- ринятых сокращений в описываемом в данном разделе методе осущест- вляется автоматическое удаление различных букв из сообщения. Для реализации этого необходимы "минимальный алфавит. Минимальным на- зывается такой алфавит, из которого исключены редко используемые буквы и оставлены только те, которые необходимы для составления большинства слов или для избежания неоднозначности. Следователь- но, любой символ, который не входит в минимальный алфавит, будет удален из слова, в котором он появился. Предметом выбора является минимальный алфавит. В данном разделе используются 14 наиболее часто встречающихся букв плюс символы пробела и возврата каретки. Автоматизация процесса сокращения требует, чтобы вы знали, какие буквы в алфавите используются наиболее часто, чтобы можно было составить минимальный алфавит. Теоретически вы могли бы подсчитать буквы в каждом слове словаря; однако, у различных лю- дей словарные смеси отличаются, поэтому частотная диаграмма, построенная только на словах английского языка, не может отражать действительной частоты использования букв. В качестве альтернати- вы вы можете подсчитать частоты использования букв в данной главе и использовать их в качестве основы для составления вашего мини- мального алфавита. Для реализации этого вы могли бы использовать следующую простую программу. Данная программа пропускает все зна- ки пунктуации, исключая точку, запятую и пробел. {данная программа подсчитывает число символов каждого типа в файле} program countchars; type str80 = string[80]; var inf: str80; t: integer; alpha: array[0..25] of integer; space, period, comma: integer; { данная функция возвращает TRUE, если ch является буквой алфавита } function isalpha(ch: char): boolean; begin isalpha:=(upcase(ch)>='A') and (upcase(ch)<='Z'); end; {isalpha} Работа с Турбо Паскалем #2/2 = 32 = { подсчет числа встреч каждого символа в файле } procedure count(inf: str80); var infile: file of char; ch: char; begin assign(infile, inf); reset(infile); while not eof(infile) do begin Read(infile, ch); ch := upcase(ch); if isalpha(ch) then alp a[ord(ch)-ord('A')] := alpha[ord(ch)-ord('A')]+1 else case ch of ' ': space := space+1; '.': period := period+1; ',': comma := comma+1; end; end; close(infile); end; {count} begin Write('введите имя входного файла: '); ReadLn(inf); for t := 0 to 25 do alpha[t] := 0; space := 0; comma := 0; period := 0; count(inf); for t := 0 to 25 do WriteLn(chr(t+ord('A')), ': ', alpha[t]); WriteLn('space:', space); WriteLn('period:', period); WriteLn('comma:', comma); end. После прогона данной программы с текстом данной главы вы по- лучите следующую таблицу частот: A 2525 P 697 B 532 Q 62 C 838 R 1656 D 1145 S 1672 E 3326 T 3082 F 828 U 869 Работа с Турбо Паскалем #2/2 = 33 = G 529 V 376 H 1086 W 370 I 2242 X 178 J 39 Y 356 K 94 Z 20 L 1103 M 1140 Space 1 5710 N 2164 Period 2 234 O 1767 Comma 3 513 1 - пробел; 2 - точка; 3 - запятая. Данные частоты использования букв хорошо согласуются со стандартной смесью английского языка, а некоторое отклонение объ- ясняется повторяющимся использованием ключевых слов Турбо Паскаля в программах. Для того, чтобы добиться значительного сжатия данных, вы должны существенно урезать алфавит за счет наименее часто исполь- зуемых букв. Хотя существует много вариантов минимального алфави- та, в данной главе в него включены 14 наиболее часто используемых букв и пробел, которые составляют 85% всех символов данной главы. Так как символ возврата каретки необходим для предотвращения раз- рывов слов, он также должен быть включен в алфавит. Таким обра- зом, минимальный алфавит будет следующим: A B D E H I L M N O R S T U <пробел><возврат каретки> Следующая программа удаляет все символы, кроме выбранных. Программа записывает комбинацию возврат каретки/перевод строки, если она присутствует. Это делает вывод читабельным. { программа сжатия и удаления символов } program compres2; type str80 = string[80]; var inf, outf: str80; ch: char; t: integer; procedure comp2(inf, outf: str80); var infile, outfile: file of char; ch: char; done: boolean; begin assign(infile, inf); Работа с Турбо Паскалем #2/2 = 34 = reset(infile); assign(outfile, outf); rewrite(outfile); done := FALSE; repeat if not eof(infile) then begin Read(infile, ch); ch := upcase(ch); if pos(ch,'ABCDEJILMNORSTU')<>0 then Write(outfile,ch); if ord(ch)=13 then Write(outfile, ch); {cr} if ord(ch)=10 then Write(outfile, ch); {lf} end else done := TRUE; until done; WriteLn('файл сжат '); close(infile); close(outfile); end; {compress} begin Write('введите имя входного файла:'); ReadLn(inf); Write('введите имя выходного файла: '); ReadLn(outf); comp2(inf, outf); end. 8Программа использует встроенную функцию Pos для определения того, входит ли считанный символ в минимальный алфавит. Роs возв- ращает 0, если не входит, и номер позиции символа в алфавите, если входит. Если вы примените программу к следующему сообщению Attention High Command: Attack successul. Please send additional supplies and fresh troops. This is essential to maintain our foolhold. General Frashier сжатое сообщение будет следующим ATTENTOIN I COMMAND ATTAC SUCCESSUL LEASE SEND ADDITIONAL SULEIS AND RES TROOS TIS Работа с Турбо Паскалем #2/2 = 35 = IS ESSENTIAL TO MAINTAIN OUR OOTOLD ENERAL RASIER Как вы видите, сообщение является довольно читабельным, хотя некоторая неоднозначность присутствует. Неоднозначность является главным недостатком данного метода. Однако, если вы знакомы со словарем писавшего сообщение, то возможно выберите лучший мини- мальный алфавит, который снимет часть неясностей и неоднознач- ностей. Исходное сообщение имеет длину 168 байт, а упакованное сооб- щение - 142 байта, следовательно, экономия составляет приблизи- тельно 16%. Если к данному сообщению применить и удаление символов и би- товое сжатие, то сообщение сократится приблизительно на 28%. Нап- ример, если бы вы были капитаном подводной лодки и хотели послать сообщение в штаб, но не желали бы выдать ваше местоположение, то вы могли бы захотеть сжать сообщение, используя оба метода, чтобы оно было как можно короче. Как метод битового сжатия, так и метод удаления символов используются в криптографии. Битовое сжатие само по себе шифрует информацию и делает декодирование более трудным. Метод удаления символов при применении его до шифрования имеет одно преимущест- во: он изменяет частоту использования символов в исходном тексте. Работа с Турбо Паскалем #2/2 = 36 = ДЕШИФРАЦИЯ ----------------------------------------------------------------- Глава о криптографии была бы не полной без краткого обзора дешифрации. Искусство дешифрации - это в сущности метод проб и ошибок. Без применения цифрового компьютера благодаря исчерпываю- щему анализу могут быть расколоты относительно простые шифры. Од- нако, более сложные коды либо не могут быть расшифрованы, либо требуют методов и ресурсов, которых не существует. Для простоты в данном разделе сосредоточимся на дешифрации наиболее простых ко- дов. Если вы желаете расшифровать сообщение, которое было зашиф- ровано с помощью метода простой замены со смещенным алфавитом, то вы должны попробовать все 26 возможных смещения, чтобы выяснить, какое из них подходит. Программа для реализации этого показана ниже: { программа дешифрования кода для шифра простой замены. сообщения не могут быть длинее 1000 символов } program brksub; type str80 = string[80]; var inf: str80; message: array[1..1000] of char;{взять входное сообщение} ch: char; { данная функция возвращает TRUE, если ch является буквой алфавита } function isalpha(ch: char): boolean; begin isalpha := (upcase(ch)>='A') and (upcase(ch)<='X'); end; {is alpha} procedure break(inf: str80); var infile: file of char; ch: char; done: boolean; sub, t, t2, l: integer; begin assign(infile, inf); reset(infile); Работа с Турбо Паскалем #2/2 = 37 = done := FALSE; l := 1; repeat Read(infile, message[l]); message[l] := upcase(message[l]); l := l+1; until eof(infile); l := l-1; { удалить знак конца файла } t := 0; sub := -1; { попробовать каждое возможное смещение } repeat for t2 := 1 to l do begin ch := message[t2]; if isalpha(ch) then begin ch := chr(ord(ch)+t); if ch>'Z' then ch := chr(ord(ch)-26); end; Write(ch); end; WriteLn; WriteLn('декодирован? Y/N): '); ReadLn(ch); if upcase(ch)='Y' then sub := t; t := t+1; until (t=26) or (upcase(ch)='Y'); if sub<> -1 then Write('offset is ', sub); close(infile); end; {break} begin Write('введите имя входного файла: '); ReadLn(inf); break(inf); end. С незначительными вариациями вы можете применить данную программу для дешифрации шифров, которые используют произвольный алфавит. В данном случае подставляется вводимый вручную алфавит, как показано в данной программе: {программа дешифрации кода для шифров подстановки с произ- вольным алфавитом program beksub2; type Работа с Турбо Паскалем #2/2 = 38 = str80 = string[80]; var inf: str80; sub: string[26]; message:array[1..1000] of char; ch: char; { ввод входного сообщения } { данная функция возвращает TRUE, если ch является буквой алфавита } function isalpha(ch: char): boolean; begin isalpha := (upcase(ch)>='A') and (upcase(ch)<='Z'); end; { is alpha } procedure break2(inf: str80); var infile: file of char; ch: char; done: boolean; t, l: integer; begin assign(infile, inf); reset(infile); done := FALSE; l := 1; repeat Read(infile, message[l]); message[l] := upcase(message[l]); l := l+1; until eof(infile); l := l-1; {очистка признака конца файла } repeat Write('введите алфавит замены: '); ReadLn(sub); for t := 1 to l do begin ch := message[t]; if isalpha(ch) then ch := sub[ord(ch)-ord('A')]; Write(ch); end; WriteLn; Работа с Турбо Паскалем #2/2 = 39 = WriteLn('декодирован ? (Y/N): '); ReadLn(ch); if upcase(ch)='Y' then done:=TRUE; until done; WriteLn('алфавит подстановки : ', sub); close(infile); end; {besub2} begin Write('введите имя входного файла: '); ReadLn(inf); break2(inf); end. В отличие от шифров замены шифры перестановки и манипуляции битами труднее для дешифрации методами проб и ошибок. Если вы должны расшифровать более сложный код, то желаем удачи. Работа с Турбо Паскалем #2/2 = 40 = ГЛАВА 7. ГЕНЕРАЦИЯ СЛУЧАЙНЫХ ЧИСЕЛ И МОДЕЛИРОВАНИЕ ----------------------------------------------------------------- Последовательности случайных чисел используются в программи- ровании в самых разнообразных случаях, начиная с моделирования (это наиболее частое применение) и кончая играми и другим развле- кательным программным обеспечением. Турбо Паскаль содержит встро- енную функцию, называемую Random, которая генерирует случайные числа. Как вы увидите в данной главе, Random - это превосходный генератор случайных чисел, но для некоторых применений вам может потребоваться два или более различных генераторов для обеспечения различных наборов случайных чисел для различных задач. Кроме то- го, при моделировании требуется ассиметричный или дисбалансный генератор случайных чисел, который порождает последовательность, смещенную к одному из концов. Первая часть данной главы посвящена построению генераторов случайных чисел и оценке их качества. Во второй части данной главы показывается, как вы можете ис- пользовать случайные числа при моделировании реальных ситуаций на двух примерах. В примерах иллюстрируются фундаментальные основы программ моделирования. Работа с Турбо Паскалем #2/2 = 41 = ГЕНЕРАТОРЫ СЛУЧАЙНЫХ ЧИСЕЛ ----------------------------------------------------------------- Технически термин "генератор случайных чисел" - это абсурд; числа само по себе не являются случайными. Например, 100 - это случайное число? А 25? Что в действительности означает этот тер- мин, так это то, что создается последовательность чисел, появляю- щихся случайным образом. Это порождает более сложный вопрос: что такое последовательность случайных чисел? Единственно правильный ответ: последовательность случайных чисел _ это последователь- ность, в которой все элементы являются несвязанными. Это опреде- ление приводит к такому парадоксу, что любая последовательность может быть как случайной, так и неслучайной в зависимости от то- го, как эта последовательность получена. Например, следующая строка чисел 1 2 3 4 5 6 7 8 9 0 была получена печатанием верхней строки клавиатуры по порядку, таким образом последовательность конечно не может рассматриваться как сгенерированная случайным образом. Но как быть, если вы полу- чите ту же самую последовательность, вынимая пронумерованный тен- нисные шары из боченка. В данном случае это уже случайным образом сгенерированная последовательность. Данный пример показывает, что случайность последовательности зависит от того, как она была по- лучена, а не от нее самой. Помните, что последовательность чисел, сгенерированная компьютером, является детерминированной: каждое число, кроме пер- вого, зависит от предшествующих чисел. Технически это означает, что компьютером может быть сгенерирована только квазислучайная последовательность чисел. Однако, этого достаточно для большинс- тва задач и в данной книге такие последовательности для простоты будут называться случайными. В общем случае считается хорошо, когда числа в последова- тельности случайных чисел распределены равномерно (не путайте это с нормальным распределением или колоколообразной кривой). При равномерном распределении все события равновероятны так, что ди- аграмма равномерного распределения стремится к прямой горизон- тальной линии, а не к кривой. До широкого распространения компьютеров всякий раз, когда необходимы были случайные числа, они получались либо бросанием игральных костей, либо выниманием пронумерованных шаров из ящика. В 1955 году фирма RAND опубликовала таблицу из 1 миллиона случай- ных чисел, полученных с помощью вычислительной машины. На ранней стадии развития вычислительной техники было разработано много ме- тодов генерации случайных чисел, но большинство из них не нашло применения. Один очень интересный метод был разработан Джоном фон Нейма- ном; его часто называют среднеквадратичным. В данном методе пре- дыдущее случайное число возводится в квадрат, а затем из резуль- Работа с Турбо Паскалем #2/2 = 42 = тата выделяются средние цифры. Например, если вы создаете числа из трех цифр, а предыдущее число было 121, то возведение в квад- рат дает результат 14641. Выделение трех средних цифр дает следу- ющее случайное число 464. Недостатком данного метода является то, что он имеет очень короткий период повторения, называемый циклом. По данной причине данный метод сегодня не используется. В настоящий момент наиболее часто применяется метод генера- ции случайных чисел, основывающийся на использовании уравнения R = (aR +c)modm n+1 n при выполнении следующих условий R>0 a>0 c>0 m>R , a и c Отметим, что R - это предыдущее число, а R - следующее. Дан- ный метод иногда называют линейным сравнительным методом. Формула так проста, что вы можете подумать, что генерировать случайные числа просто. Однако, это ловушка: насколько хорошо работает дан- ная формула, очень сильно зависит от значения а, с и m. Выбор значений иногда в большей степени искусство, нежели наука. Су- ществуют сложные правила, которые могут помочь вам выбрать значе- ния; однако, мы рассмотрим лишь несколько простых правил и приме- ров. Модуль (m) должен быть довольно большим, так как он опреде- ляет область случайных чисел. Операция взятия по модулю порождает остаток от деления числа на модуль. Следовательно, 10 по модулю 4 равно 2. Таким образом, если модуль равен 12, то формулой порож- даются числа от 0 до 11, а если модуль равен 21425, то порождают- ся числа от 0 до 21424. Выбор множителя а и приращения с является очень сложной задачей. В общем случае множитель может быть до- вольно большим, а приращение - маленьким. Множество попыток и проверок необходимо, чтобы создать хороший генератор. В качестве первого примера здесь приведен один из наиболее часто используемых генераторов случайных чисел. Уравнение, пока- занное в Rаn1 используется как основа для генератора случайных чисел в ряде популярных языков. var a1: integer; { установка до первого вызова Ran1 } function Ran1: real; var t: real; begin Работа с Турбо Паскалем #2/2 = 43 = t := (a1*32749+3) mod 32749; a1 := Trunc(t); Ran1 := Abs(t/32749); end; {Rea1} Данная функция имеет три главные особенности. Во-первых, случайные числа в действительности являются целыми, хотя функция возвращает действительные числа. Данный метод работает с целыми числами, но генераторы случайных чисел, как это принято, должны возвращать числа в пределах от 0 до 1, что означает, что это должны быть числа с плавающей запятой. Во-вторых, начальное значение задается через глобальную пе- ременную а1. До первого вызова Ran1 переменная а1 должна быть ус- тановлена в 1. В-третьих, в Ran1 случайные числа делятся на модуль прежде, чем они будут возвращены функцией, для того, чтобы числа лежали в области от 0 до 1. Если вы поинтересуетесь значением глобальной переменной а1 до возврата строки, то оно должно лежать в области от 0 до 32748. Следовательно, когда а1 делится на 32749, получен- ное число будет больше или равно 0 и меньше 1. Многие генераторы случайных чисел не применимы, так как они порождают не равномерное распределение или имеют короткий цикл повторения. Даже когда эти недостатки не очень бросаются в глаза, они могут породить смешанный результат, если такой генератор ис- пользуется снова и снова. Решение заключается в том, чтобы соз- дать различные генераторы и применять их индивидуально или сов- местно для получения более качественных последовательностей случайных чисел. Применение нескольких генераторов может сгладить распределение последовательности за счет уменьшения малых смеше- ний отдельных генераторов. Далее приведена функция генерирования случайных чисел, называемая Ran2, которая порождает хорошее расп- ределение: var a2:integer; { установить в значение 203 до первого вызова Ran2 } function Ran2: real; var t: real; begin t := (a2*10001+3) mod 17417; a2 := Trunc(t); Ran2 := Abc(t/17417); end; {Ran2} Оба этих генератора случайных чисел порождают хорошие после- довательности случайных чисел. Тем не менее остаются вопросы: достаточно ли "случайной" является последовательность? Хороши ли данные генераторы? Работа с Турбо Паскалем #2/2 = 44 = Определение качества генераторов ----------------------------------------------------------------- Вы можете применить различные тексты для определения случай- ности последовательности чисел. Ни один из тестов не скажет, что последовательность является случайной, однако, он скажет, если она не является таковой. Тесты могут выявить не случайные после- довательности, но, если тест не нашел недостатков, это не означа- ет, что данная последовательность действительно случайная. Тесты, однако, повышают уверенность в генераторе случайных чисел, кото- рый породил последовательность. Теперь мы кратко рассмотрим нес- колько простых способов тестирования последовательностей. Для начала рассмотрим способ определения того, насколько близко распределение чисел в последовательности соответствует ожидаемому. Например, вы пытаетесь генерировать последователь- ность случайных чисел от 0 до 9. Вероятность появления каждой цифры равна 1/10. Пусть была сгенерирована следующая последова- тельность 9 1 8 2 4 6 3 7 5 2 9 0 4 2 4 7 8 6 2 Если вы подсчитаете число появлений каждой цифры, то получи- те результат Цифры Число появлений 0 1 1 1 2 4 3 1 4 3 5 1 6 2 7 2 8 3 9 2 Далее вам следует ответить самому себе на вопрос, достаточно ли похоже данное распределение на ожидаемое вами. Помните: если генератор случайных чисел хороший, он генери- рует последовательности случайно. В истинно случайном варианте все последовательности возможны. Действительно, как какая-то пос- ледовательность может быть не случайной, если любая последова- тельность возможна? Просто некоторые последовательности менее по- хожи на то, какой должна быть случайная последовательность, чем другие. Вы можете определить вероятность того, что данная после- довательность является случайной, используя критерий хи-квадрат. В критерии хи-квадрат ожидаемое количество вычитается из Работа с Турбо Паскалем #2/2 = 45 = наблюдаемого количества встреч числа в сгенерированной последова- тельности. Этот результат называется V. Вы можете использовать V для нахождения процента в таблице значений хи-квадрат. Этот про- цент определяет вероятность того, что была порождена случайная последовательность. Малая таблица хи-квадрат приведена на рис.7-1; вы можете найти полные таблицы в большинстве книг по статистике ─────────────────────────────────────────────────────────── p=99% p=95% p=75% p=50% p=25% p=5% n=5 0.5543 1.1455 2.675 4.351 6.626 11.07 n=10 2.558 3.940 6.737 9.342 12.55 18.31 n=15 5.229 7.261 11.04 14.34 18.25 25.00 n=20 8.260 10.85 15.45 19.34 23.83 31.41 n=30 14.95 18.49 24.48 29.34 34.80 43.77 ──────────────────────────────────────────────────────────── Рис.7-1. Выбранные значения хи-квадрат. Для определения вероятности того, что последовательность не случайная, найдите строку в таблице, показанной на рис.7-1, с числом элементов последовательности; в данном случае это 20. За- тем ищите число по строке, которое больше V. В данном случае это колонка 1. Это означает, что существует вероятность 99% того, что пример из 20 элементов будет иметь V больше 8,260. С другой сто- роны это означает, что существует вероятность только в 1% того, что проверяемая последовательность была сгенерирована случайным образом. Чтобы пройти через критерий хи-квадрат, вероятность V должна снизиться до уровня от 25% до 75%. Однако, вы можете противопоставить этому выводу вопрос: Так как все последовательности возможны, как может данная последова- тельность иметь только однопроцентный шанс быть законной? Ответ такой: это всего лишь вероятность. Фактически, если вы применяете критерий хи-квадрат, вам следует получить несколько различных последовательностей и усредненный результат, чтобы избежать от- вержения хорошего генератора случайных чисел. Любая единичная последовательность может быть отвергнута, но ряд различных после- довательностей после усреднения должен давать хороший результат. С другой стороны, последовательность может пройти через кри- терий хи-квадрат и не быть случайной. Например, последователь- ность 1 3 5 7 9 1 3 5 7 9 пройдет критерий хи-квадрат, но она выглядит не очень случайной. В данном случае сгенерирован пробег по диапазону значений. Пробег - это просто возрастающая или убывающая последовательность чисел с произвольным интервалом. В данном случае каждая группа из пяти цифр представляет собой возрастающую последовательность и как та- ковая не может считаться случайной последовательностью. Можно Работа с Турбо Паскалем #2/2 = 46 = создать тест для обнаружения такой ситуации, но это выходит за рамки данной книги. Другой характеристикой, подлежащей оценке, является длина периода: то есть, как много чисел может быть сгенерировано до на- чала повторения последовательности. Все машинные генераторы слу- чайных чисел всегда генерировали повторяющуюся последователь- ность. Чем длинее период, тем лучше генератор. Даже если частота чисел внутри периода распределена равномерно, числа не образуют случайную серию, так как действительно случайная серия не может достаточно повторяться. В общем случае период в несколько тысяч чисел удовлетворяет большинству применений. Тест для выяснения периода может быть разработан. Различные другие тесты могут быть применены для определения качества генератора случайных чисел. Наверное можно написать больше программ для проверки генераторов случайных чисел, чем создать самих генераторов. Рассмотрим еще один тест, который поз- волит вам проверить генератор случайных чисел "визуально", ис- пользуя диаграмму для демонстрации характеристик сгенерированной последовательности. В идеале диаграмма должна основываться на частоте каждого числа. Однако, так как генератор может порождать тысячи различных чисел, это не выполнимо. Вместо этого будут создаваться диаграм- мы, сгруппированные до десяти цифрам. Например, так как порождае- мые числа лежат в области от 0 до 1, число 0.9365783 будет вклю- чено в группу 9, а число 0.34523445 будет включено в группу 3. Это означает, что диаграмма вывода случайных чисел имеет 10 ли- ний, каждая из которых представляет число попаданий в группу. Программа также выводит среднее значение последовательности, ко- торое может быть использовано для обнаружения смешения. Как и все другие программы данной главы следующая программа выполняется только на персональном компьютере IBM PC, который имеет адаптер цветного графического дисплея. Разработанные ранее функции Ran1 и Ran2, а также встроенная функция Турбо Паскаля Random, продемонс- трированы рядом для сравнения. { программа, которая сравнивает три генератора случайных чи- сел } program RanGenerator; uses Graph; const COUNT = 1000; var freg1, freg2, freg3: array[0..9] of integer; a2, a1: integer; Работа с Турбо Паскалем #2/2 = 47 = f, f2, f3: real; r, r2, r3: real; y, x: integer; GraphDriver, GraphMode: integer; {отображение графического вывода} procedure Display; var t : integer; begin for t := 0 to 9 do begin Line(t*10, 180, t*10, 180-freg1[t]); Line(t*10+110, 180, t*10+110, 180-freg2[t]); Line(t*10+220, 180, t*10+220, 180-freg3[t]); end; end; {Display} function Ran1: real; var t: real; begin t := (a1*32749+3) mod 32749; a1 := Trunc(t); Ran1 := Abs(t/32749); end; {Ran1} function Ran2: real; var t: real; begin t := (a2*10001+3) mod 17417; a2 := Trunc(t); Ran2 := Abs(t/17417); end; {Ran2} begin { переключение на графический режим, используя режим 4 CGA/EGA } GraphDriver := CGA; GraphMode := CGAC1; InitGraph(GraphDriver, GraphMode, ''); SetColor(2); SetLineStyle(SolidLn, 0, NormWidth); OutTextXy(80, 10, 'Comparison of Random'); OutTextXy(96, 20, 'Number Generators'); Работа с Турбо Паскалем #2/2 = 48 = { прорисовать базовые линии } Line(0, 180, 90, 180); Line(110, 180, 200, 180); Line(220, 180, 310, 180); OutTextXy(30, 190, 'Random Ran1 Ran2'); {инициализация переменных генераторов случайных чисел } a1:=1; a2:=203; f := 0; f2 := 0; f3 := 0; for x := 0 to 9 do begin { инициализация матриц частот } freg1[x] := 0; freg2[x] := 0; freg3[x] := 0; end; for x := 1 to COUNT do begin r:=Random; { взять случайное число } f:=f+r; { прибавить для вычисления среднего } y:=Trunc(r*10);{ преобразовать в целое число от 0 до 9 } freg1[y]:=freg1[y]+1;{ увеличить счетчик частоты } r2 := Ran1; { взять случайное число } f2:=f2+r2; { прибавить для вычисления среднег } y:=Trunc(r2*10);{ преобразовать в целое число от 0 до 9} freg2[y]:=freg2[y]+1;{ увеличить счетчик частоты } r3 := Ran2; { взять случайное число } f3:=f3+r3; { прибавить для вычисления среднего } y:=Trunc(r3*10);{ преобразовать в целое число от 0 до 9} freg3[y]:=freg3[y]+1;{увеличить счетчик частоты } Display; { отобразить счетчики частот } end; ReadLn; RestoreCrtMode; WriteLn('mean of Random is: ', f/COUNT); WriteLn('mean of Ran1 is: ', f2/COUNT); WriteLn('mean of Ran2 is: ', f3/COUNT); end. В данной программе каждая функция генерирует 1000 чисел и на основе этого создаются таблицы частот. Процедура Display рисует все три матрицы частот на экране после генерации каждого случай- ного числа так, что вы можете наблюдать рост частот. На рис.7-2 Работа с Турбо Паскалем #2/2 = 49 = показан вывод каждого генератора случайных чисел после генерации 1000 чисел. Средние значения равны 0,489932 у Ran1, 0,4858311 y Ran2 и 0,494014 у Random. Это приемлемо. ──────────────────────────────────────────────────────────── Сравнение генераторов случайных чисел │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─┴─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┴─┘ Random Ran1 Ran2 ─────────────────────────────────────────────────────────── Рис.7-2. Вывод из программы отображения работы генераторов случайных чисел. Для эффективного использования программы вы должны наблюдать как за формой диаграммы, так и за динамикой роста, чтобы выявить короткие повторяющиеся циклы. Например, Ran2 генерирует значи- тельно меньше чисел в области от 0,9 до 0,999999, чем Random и Ran1. Конечно данный текст не является всеобъемлющим, но он помо- жет вам понять способ, которым генератор порождает числа, и уско- рит процесс анализа генераторов. Работа с Турбо Паскалем #2/2 = 50 = Использование нескольких генераторов ----------------------------------------------------------------- Один простой метод, который улучшает свойства случайных пос- ледовательностей, порождаемых тремя генераторами, заключается в комбинировании их под управлением одной главной функции. Данная функция выбирает между двумя из них, основываясь на результате третьей. С помощью этого метода вы можете получить очень длинный период и уменьшить влияние циклов и смещений. Функция, называемая CombRandom, показанная здесь, осуществляет комбинирование выводов генераторов Ran1, Ran2, Random: {данная функция использует три генератора для возврата одного случайного числа } function CombRandom: real; var f: real; begin f := Ran2; if f>0.5 thenCombRandom := Random else CombRandom := Ran1; { случайный выбор генератора } end; {CombRandom} Результат Ran2 используется для того, чтобы решить, Ran1 или Random выдаст значение главной функции CombRandom. При таком ме- тоде период главной функции равен или больше суммы периодов Random и Ran1. Таким образом, данный метод делает возможным по- рождение последовательности с очень длинным периодом. Можно легко изменять смесь Random и Ran1 изменением константы в операторе if, чтобы получить желаемое вами распределение между этими двумя ге- нераторами. Кроме того, вы можете добавить дополнительные генера- торы и осуществлять выбор между ними для получения еще более длинного периода. Далее следует программа для отображения диаграммы CompRandom и ее среднего значения. На рис.7-3 показана финальная диаграмма после генерации 1000 чисел. Среднее значение CombRandom равно 0,493361. { данная программа демонстрирует комбинированный вывод трех генераторов случайных чисел } program MultiRandom; uses Graph; const Работа с Турбо Паскалем #2/2 = 51 = COUNT=1000; var freg: array[0..9] of integer; a2,a1: integer; f, r: real; y, x: integer; GraphDriver, GraphMode: integer; { отображение графического представления работы генераторов } procedure Display; var t: integer; begin for t := 0 to 9 do Line(t*10+110, 180, t*10+110, 180-freg[t]); end; {Display} function Ran1: real; var t: real; begin t := (a1*32749+3) mod 32749; a1 := Trunc(t); Ran1 := Abs(t/32749); end; {Ran1} function Ran2: real; var t: real; begin t := (a2*10001+3) mod 17417; a2 := Trunc(t); Ran2 := Abs(t/17417); end; {Ran2} {данная функция использует три генератора для возврата одного случайного числа } function CombRandom real; var t: real; begin f := Ran2; if f>0.5 then CombRandom := Random Работа с Турбо Паскалем #2/2 = 52 = else CombRandom := Ran1; {случайный выбор генератора } end; {CombRandom} begin { переключение на графический режим, используя режим 4 CGA/EGA } GraphDriver := CGA; GraphMode := CGAC1; InitGraph(GraphDriver, GraphMode, ''); SetColor(2); SetLineStyle(SolidLn, 0, NormWidth); OutTextXy(48, 10, 'вывод, полученный комбинированием '); OutTextXy(40,20, 'три генератора случайных чисел '); Line(110, 180, 200, 180); a1:=1; a2:=203; {инициализация переменных для генераторов случайных чисел } f := 0; for x:=0 to 9 do freg[x]:=0; {инициализация матрицы частот} for x := 1 to COUNT do begin r:=CombRandom; { взять случайное число } f:=f+1; { прибавить для вычисления среднего } y:=Trunc(r*10);{ преобразовать в целое число от 0 до 9} freg[y]:=freg[y]+1;{ увеличить счетчик частоты } Display; end; ReadLn; RestoreCrtMode; WriteLn('Среднее случайное число равно : ', f/COUNT); end. ───────────────────────────────────────────────────── Вывод, полученный комбинированием трех генераторов случай- ных чисел. │ │ │ │ │ │ ││││││││││ ││││││││││ ││││││││││ ││││││││││ ││││││││││ └┴┴┴┴┴┴┴┴┘ ────────────────────────────────────────────────────── Работа с Турбо Паскалем #2/2 = 53 = Рис.7-3. Финальное отображение функции CombRandom: Работа с Турбо Паскалем #2/2 = 54 = МОДЕЛИРОВАНИЕ ----------------------------------------------------------------- Оставшаяся часть данной главы посвящена применениям генера- торов случайных чисел для моделирования на компьютерах. Промоде- лировать можно все; успех моделирования зависит главным образом от того, насколько хорошо программист понял ситуацию, которую на- до моделировать. Так как реальные ситуации часто имеют тысячи пе- ременных, многие вещи с трудом поддаются моделированию. Однако, существуют ситуации, которые очень хорошо подходят для моделиро- вания. Моделирование важно по двум причинам. Во-первых, оно позво- ляет вам изменять параметры моделирования для проверки и наблюде- ния возможных результатов в то время, как в реальности такие экс- перименты могут быть и дорогими и опасными. Например, моделирование атомной электростанции может быть использовано для проверки влияния различных типов отказов без какой-либо опаснос- ти. Во-вторых, моделирование позволяет нам создавать ситуации, которые не могут произойти в реальной жизни. Например, психологи могут захотеть изучить влияние непрерывного роста интеллекта мыши до человеческого, чтобы определить, когда мышь будет проходить лабиринт наиболее быстро. Хотя это не может быть сделано в реаль- ной жизни, моделирование может помочь проникновению в природу со- отношения интеллекта и инстинкта. Далее разберем первый из двух примеров, в которых используются генераторы случайных чисел. Работа с Турбо Паскалем #2/2 = 55 = Моделирование очередей обслуживания ----------------------------------------------------------------- В первом примере моделируется обслуживание в бакалейной лав- ке. Предположим, что лавка открыта 10 часов в день с пиковыми ча- сами с 12 до 13 и с 17 до 18 часов. Период с 12 до 13 часов имеет нагрузку в два раза, а с 17 до 18 - в три раза больше обычной. При моделировании один генератор "порождает" покупателей, второй генератор определяет время обслуживания покупателя, а третий ре- шает, в какую очередь пойдет покупатель. Цель моделирования сос- тоит в том, чтобы помочь управляющему найти оптимальное число очередей, которые должны работать в обычный день при условии, что число людей в очереди в любое время не превышало бы 10 и кассиры не ожидали бы покупателей. Ключ к данному типу моделирования состоит в создании многих процессов. Хотя Турбо Паскаль непосредственно не поддерживает па- раллельности, вы можете моделировать с помощью множества процес- сов или с помощью главной программы с циклами. Далее показана программа с ее глобальными данными для моделирования очередей без поддержки процедур и функций: var gueues, count: array[0..9] of integer; open: array[0..9] of boolean; cust, time: integer; a1, a2: integer; y, x: integer; change: boolean; GraphDiver, GraphMode: integer; begin {переключение на графику, используя режим 4 CGA/EGA} GraphDriver := CGA; GraphMode := CGAC1; InitGraph(GraphDriver, GraphMode, ''); SetColor(2); SetLineStyle(SolidLn, 0, NormWidth); a1:=1; a2:=203; {инициализация переменных генератора случайных чисел} change := FALSE; cust := 0; time := 0; for x:=0 to 9 do begin gueues[x]:=0; {инициализация очереди } open[x]:=FALSE; { нет покупателей или очередей в начале дня } Работа с Турбо Паскалем #2/2 = 56 = count[x]:=0; {счетчик очереди } end; OutTextXy(155, 190, '1 10'); OutTextXy(8,190,'Check-out lines: '); { теперь начинается день открытием первой очереди } open[0] := TRUE; repeat AddCust; AddQueue; Display; CheckOut; Display; if (time>30) and (time<50) then AddCust; if (time>70) and (nime<80) then begin AddCust; AddCust; end; time := time+1; until time>100; { конец дня } ReadLn; RestoreCrtMode; end. Элемент Graph.P включен, чтобы программа могла использовать графические функции. Главный цикл управляет всем моделированием: repeat AddCust; AddQueue; Display; CheckOut; Display; if (time>30) and (time<50) then AddCust; if (time>70) and (time<80) then begin AddCust; AddCust; end; time := time+1; until time>100; { конец дня } Функция AddCust использует либо Ran1, либо Ran2 для генера- ции числа покупателей, встающих в очереди при каждом запросе. Работа с Турбо Паскалем #2/2 = 57 = Функция AddQuece используется для помещения покупателей в очереди в соответствии с результатом Ran2, а также открывает новые очере- ди, если все существующие переполнены. Функция Display отображает программу моделирования. Checkout использует Ran2 для назначения каждого покупателя в очередь с соответствующим увеличением счет- чика очереди; каждый вызов уменьшает счетчик на 1. Когда счетчик покупателей равен 0, очередь становится пустой. Переменная time (время) изменяет интенсивность, с которой генерируются покупатели, для того, чтобы отследить часовые пики. Каждый проход по циклу представляет одну десятую часа. На рис.7-4, 7-5 и 7-6 показаны состояния очередей, когда time=28, time=60 и time=88, что соответствует нормальному време- ни, концу первого пика и концу второго пика, соответственно. От- метим, что в конце второго пика требуется максимум пять очередей, Если моделирующая программа написана правильно, то в бакалейной лавке оставшиеся пять очередей не нужны. gueue 1: 10 time: 28 gueue 2: 8 gueue 3: 0 gueue 4: 0 gueue 5: 0 gueue 6: 0 gueue 7: 0 gueue 8: 0 gueue 9: 0 gueue 10: 0 Очередь │ │ │ │ │ │ │ │ │ │ │ │ │ │ 1 10 Рис.7-4. Состояние очередей, когда time=28: gueue 1: 6 time: 60 gueue 2: 8 gueue 3: 8 gueue 4: 1 gueue 5: 0 gueue 6: 0 gueue 7: 0 Работа с Турбо Паскалем #2/2 = 58 = gueue 8: 0 gueue 9: 0 gueue 10: 0 Очередь │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 1 10 Рис.7-5. Состояние очередей, когда time=60: gueue 1: 8 time: 80 gueue 2: 9 gueue 3: 6 gueue 4: 6 gueue 5: 7 gueue 6: 0 gueue 7: 0 gueue 8: 0 gueue 9: 0 gueue 10: 0 Очередь │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 1 10 Рис.7-6. Состояние очередей, когда time=88: Вы можете непосредственно управлять различными переменными в программе. Во-первых, вы можете изменить путь и число прибывающих покупателей. Вы также можете изменить функцию AddCost, чтобы она возвращала число покупателей в пиковые часы с большим или меньшим постепенным увеличением или уменьшением. Программа предполагает, что покупатели случайным образом выбирают, в какую очередь им встать. Такой подход справедлив для одних покупателей, а другие будут выбирать самую короткую очередь. Вы можете учесть это, из- Работа с Турбо Паскалем #2/2 = 59 = менив функцию AddQueue так, чтобы она в некоторых случаях помеща- ла покупателей в самую короткую очередь, а в некоторых - случай- ным образом. При моделировании не учитываются такие случайности, как упавшая булка или буйный покупатель в очереди, которые вызы- вают временные остановки очереди. Целиком программа выглядит следующим образом: program simulation; {моделирование очередей в бакалейной лавке } uses Graph; var gueues, count: array[0..9] of integer; open: array[0..9] of boolean; cust, time: integer; a1, a2: integer; y,x: integer; change: boolean; GraphDriver, GraphMode: integer; function Ran1: real; var t: real; begin t := (a1*32749+3) mod 32749; a1 := Trunc(t); Ran1 := Abs(t/32749); end; {Ran1} function Ran2: real; var t: real; begin t := (a2*10001+3) mod 17417; a2 := Trunc(t); Ran2 := Abs(t/17417); end; {Ran2} function CombRandom: real; {random selection of generators} 2 var f: real; begin f := Ran2; if f>0.5 then CombRandom := Random else CombRandom:=Ran1;{случайный выбор генераторов} Работа с Турбо Паскалем #2/2 = 60 = end; {CombRandom} { добавление покупателей в очередь } procedure AddCust; var f, r: real; begin if change then f:=Random {переключение между двумя } else f := Ran2; {генераторами } if f>0.5 then if f>0.6 then cust:=cust+1 {добавить одного покупателя} else if f>0.7 then cust:=cust+2 {два покупателя} else if f<0.8 then cust:=cust+3 {три покупателя } else cust := cust+4; {четыре покупателя } end; {AddCust} { обслуживание покупателя } Procedure CheckOut; var t: integer; begin for t := 0 to 9 do begin if gueues[t]<>0 then begin {получить время обслуживания } while count[t]=0 do count[t]:=Trunc(Ran1+5); {новый покупатель требует времени обслуживания } count[t]:=count[t]-1; if count[t]=0 then gueues[t]:=gueues[t]-1; {удаление покупателя} end; if gueues[t]=0 then open[t]:=FALSE;{закрытие очереди} end; end; {CheckOut} {возвращается TRUE, если очередь переполнена } function AllFull: Boolean; var t: integer; begin AllFull := TRUE; for t := 0 to 9 do if (gueues[t]<10) and open[t] then AllFull:=FALSE; end; {AllFull} Работа с Турбо Паскалем #2/2 = 61 = {данная процедура вводит новые очереди } procedure AddQueue; var t, line: integer; done: Boolean; begin done := FALSE; while cust<>0 do begin if AllFull then begin t:=0; repeat if not open[t] then begin open[t]:=TRUE; done:=TRUE; end; t:=T+1; until done or (t=9); end else begin Line:=Trunc(Ran2*10); if open[line] and (gueues[line]<10) then begin gueues[line]:=gueues[line]+1; cust:=cust-1; end; end; if AllFull and open[9] then cust:=0; {all full} end; end; {AddQueue} {очистить символы длины, начиная с позиции Х, У } procedure ClrVed(x,y,len: integer); var i: integer; s: string[80]; begin for i := 1 to len do s := concat(Chr(219), Chr(219)); SetColor(0); OutTextXy(x, y, s); SetColor(2); Работа с Турбо Паскалем #2/2 = 62 = end; {ClrVid} {отображение экрана результатов моделирования очереди } procedure Display; var t: integer; value: string[80]; begin cirVid(170, 10, 3); str(time, value); OutTextXy(120, 10, 'Time: '); OutTextXy(170, 10, value); for t := 0 to 9 do begin {erase old line} SetColor(0); Line((t*10)+160, 180, (t*10)+160, 180); {нарисовать новое состояние моделирования } SetColor(2); Circle((t*10)+160, 180, 3); Line((t*10)+160, 180, (t*10)+160, 180-gueues[t]*10); {дать также текстовый вывод } OutTextXy(8, t*10, 'gueue'); str(t+1, value); value := concat(value, ':'); OutTextXy(56, t*10, value); ClrVid(80, t*10, 3); str(gueues[t], value); OutTextXy(80, t*10, value); end; end; {Display} begin {переключение на графику, используя режим 4 CGA/EGA } GraphDriver := CGA; GraphMode := CGAC1; InitGraph(GraphDriver, GraphMode, ''); SetColor(2); SetLineStyle(SolidLn, 0, NormWidth); a1:=1; a2:=203; {инициализация переменных генератора случайных чисел } change:=FALSE; cust:=0; time:=0; Работа с Турбо Паскалем #2/2 = 63 = for x := 0 to 9 do begin gueues[x]:=0; {инициализировать очереди } open[x]:=FALSE;{нет покупателей или очередей в начале дня } count[x]:=0; {счетчик очереди } end; OutTextXy(155, 190, '1 10'); OutTextXy(8, 190, 'Check-out lines; '); {теперь начинается день открытием первого пункта обслуживания } open[0]:=TRUE; repeat AddCust; AddQueue; Display; CheckOut; Display; if (time>30) and (time<50) then AddCust; if (time>70) and (time<80) then begin AddCust; AddCust; end; time:=time+1; until time>100; { конец дня } ReadLn; RestoreCrtMode; end. Работа с Турбо Паскалем #2/2 = 64 = Управление портфелем акций методом случайного поиска ----------------------------------------------------------------- Искусство управления портфелем акций в общем случае основы- вается на различных теориях и предположениях о многих факторах, некоторые из которых не могут быть легко поняты. Существуют стра- тегии купли и продажи, основанные на статистическом анализе стои- мости акций, индексе РЕ, цены на золото и даже лунного цикла. Вы можете подумать, что фондовая биржа слишком сложна для моделирования; она имеет слишком много переменных и слишком много неизвестных; ей присущи широкие колебания по времени и плавные изменения в зависимости от других параметров. Однако, задача сама по себе имеет решение: так как рынок такой сложный, его можно рассматривать как совокупность случайно происходящих событий. Это означает, что вы можете моделировать поведение фондовой биржы, как серию не связанных событий, носящих случайный характер. Такой метод моделирования называется методом случайного поиска управле- ния портфелем акций. Вы можете руководствоваться методом случай- ного поиска, так как он не хуже других. Прежде, чем продолжать, предупредим: метод случайного поиска дискредитирован профессиональными бизнесменами. Он представляется здесь исключительно в познавательных целях, а не для того, чтобы помочь вам принять решение о ваших вложениях. Для реализации метода случайного поиска, во-первых, выберите десять компаний из журнала "Уол Стрит Джоурнел" некоторым случай- ным методом. После того, как вы выберите десять компаний, введите их имена в программу моделирования случайного поиска так, чтобы она могла сообщить вам, как поступить с ее акциями. Программа может сообщить вам пять вариантов поведения по от- ношению к акциям данной компании: - продать; - купить; - продать не на долго; - купить на прибыль; - держать не делать ничего. Операции продажи, купли и держа- ния акций объяснять не надо. Продажа не на долго - это продажа акций, которые вскоре, как вы надеетесь, сможете купить по дешев- ке. Такая продажа - это способ делать деньги, когда курс акций начинает падать. Когда вы покупаете на прибыль, вы используете деньги прибыли для финансирования части стоимости акций, которые вы покупаете. Это приносит деньги в случае игры на повышении кур- са акций. Далее показана программа случайного поиска. Встроенная функ- ция KeyPressed проверяет статус клавиатуры и ожидает нажатия кла- виши. Это позволяет вам использовать последовательность, порожда- емую генератором случайных чисел в случайный момент, в сущности порождая случайно выбранное значение, что предотвращает выдачу программой одинаковых рекомендаций Работа с Турбо Паскалем #2/2 = 65 = {программа управления портфелем акций методом случайного поиска} program RandomWalk; uses Crt; type str80 = string[80]; action = (buy, sell, hold, short, margin); var t: integer; stock: array[1..10] of str80; ch: char; act: action; f: real; { ввод имен компаний } procedure Enter; var t: integer; begin for t := 1 to 10 do begin Write('Введите имена компаний : '); ReadLn(stock[t]); end; end; {Enter} {возврат очередного курса акций } function NextAction: action; var f: real; begin NextAction := hold; case Trunc(Random*10) of 1: NextAction := sell; 2: NextAction := buy; 3: NextAction := short; 4: NextAction := margin end; end; {NextAction} begin Write('Подождать, а затем нажать любую клавишу '); Работа с Турбо Паскалем #2/2 = 66 = repeat f := Random; {Randomize the generator} until KeyPressed; ch := ReadKey; WriteLn; enter; repeat for t := 1 to 10 do begin act := NextAction; if Length(stock[t])>0 then begin Write(stock[t], ': '); case act of buy: WriteLn('Кyпить'); sell: WriteLn('Продать'); short: Write('Продать не на долго'); margin: WriteLn('Купить на прибыль '); hold: WriteLn('Держать '); end; end; end; Write('Снова (Y/N) '); ch := ReadKey; WriteLn; until UpCase(ch)='N'; end. Программа требует, чтобы вы интерпретировали ее инструкции следующим образом: Инструкция Интерпретация Купить Купить столько указанных акций, сколько вы можете себе позволить без занимания Продать Продать все акции, если они у вас есть. Далее случайным образом выбрать новую компанию для вложения ваших денег Продать не на долго Продать 100 акций указанной компании, даже если их у вас нет, в надежде, что вы в будущем сможете купить их по низкой цене Купить на прибыль Занять деньги для покупки акций указанной компании Держать Не делать ничего Например, вы запустили данную программу, используя фиктивные Работа с Турбо Паскалем #2/2 = 67 = имена компаний Com1-Com10, совет первого дня может выглядеть сле- дующим образом: Сом1: продать Сом2: купить Сом3: купить на прибыль Сом4: продать не на долго Сом5: держать Сом6: держать Сом7: держать Сом8: купить Сом9: держать Сом10: продать не на долго Совет на второй день мог бы быть таким: Сом1: держать Сом2: держать Сом3: продать Сом4: продать не на долго Сом5: держать Сом6: держать Сом7: купить Сом8: купить на прибыль Сом9: держать Сом10: продать Помните, что, так как программа ждет, когда вы нажмете кла- вишу, ваш вывод будет отличаться от показанного здесь. Вы можете предпочесть запускать программу каждую неделю или месяц, а не каждый день. Программу можно свободно менять любыми путями. Например, вы могли бы захотеть иметь программу, которая выдает количество ак- ций для купли или продажи в зависимости от имеющихся у вас сво- бодных средств. Снова напомним, что данная программа является только забавой и не рекомендуется для решения по настоящим вкла- дам. Однако, интересно создать такую программу и проследить за ее эффективностью. Работа с Турбо Паскалем #2/2 = 68 = ГЛАВА 8. СИНТАКСИЧЕСКИЙ РАЗБОР И ВЫЧИСЛЕНИЕ ВЫРАЖЕНИЙ ----------------------------------------------------------------- Как вы напишите программу, которая будет брать в качестве входа строку, содержащую численное выражение, например, 10-5*3, и возвращает ответ, который в данном случае равен -5? Если "высшее духовенство" еще существует среди программистов, она должна быть составлена теми немногими, кто знает как это делается. Хотя каж- дый, кто пользуется компьютером, мистифицирован способом, которым компиляторы и интерпретаторы языков высокого уровня преобразуют такие сложные выражения, как 10*3 - (4+COUNT)/12 в команды, кото- рые может выполнять компьютер. Процесс преобразования называется синтаксическим разбором. Синтаксический разбор - это основа всех компиляторов и интерпретаторов с языков высокого уровня. Некото- рые программисты знают, как написать программу синтаксического разбора; эта область программирования доступна только для немно- гих посвященных. Однако, такого быть не должно. Синтаксический разбор на са- мом деле является довольно прозрачным и похож на другие проблемы программирования. В некоторых отношениях он даже легче, так как имеет дело со строгими синтаксическими правилами. В данной главе рассматривается метод рекурсивного нисходящего разбора, а также все вспомогательные процедуры, которые позволяют вычислить слож- ные численные выражения. Все эти процедуры будут помещены в один файл, который вы будете использовать всякий раз, когда это пона- добиться. После того, как вы научитесь пользоваться данным фай- лом, вы можете развить и модифицировать его в соответствии с ва- шими нуждами и присоединить себя к "высшему духовенству". ВЫРАЖЕНИЯ ----------------------------------------------------------------- Хотя выражения могут включать информацию всех типов, мы бу- дем рассматривать только один тип: численные выражения. Для целей данной статьи предположим, что численные выражения могут вклю- чать: - числа; - операторы +,-,/,*,^, и =; - скобки; - переменные. Символ ^ обозначает возведение в степень как в языке Бейсик, а символ = - оператор присваивания. Все эти элементы подчиняются правилам алгебры, с которыми вы знакомы. Несколько примеров выра- жений: 10-8 (100-5)*14/6 Работа с Турбо Паскалем #2/2 = 69 = а+в-с 10^5 а=10-в Примем следующее старшинство операторов: старшие: ^ */ + - младшие: = Оператору присваивания предшествуют вычисления слева и спра- ва. В примерах данной главы приняты следующие предположения: Все переменные являются однобуквенными, что означает, что возможно 26 переменных. Все числа имеют тип integer (целые), хотя вы могли бы легко написать процедуры для работы с числами с плавающей запя- той. Наконец, только минимальное количество проверок на ошибки включено в процедуры, чтобы сохранить логическую ясность и не су- етиться. Попытайтесь вычислить следующее простое выражение: 10-2*3 Это выражение имеет значение 4. Хотя вы легко бы написали программу, которая вычисляет данное специфическое выражение, но гораздо интереснее создать программу, которая будет давать пра- вильный ответ для любого выражения. Во-первых, предположим, что вы применяете процедуру, аналогичную следующей: a:=взять первый операнд while(операнды присутствуют) do begin op:=взять оператор; b:=взять второй операнд; a:=a op b end; Согласно данной процедуре вы могли бы взять первый операнд, оператор и второй операнд; выполнить операцию; затем взять следу- ющий оператор и операнд, если таковые имеются; выполнить эту опе- рацию и так далее. Если вы пользуетесь этим базовым методом, то выражение 10-2*3 даст результат 24(8*3) вместо правильного ответа 4, так как процедура пренебрегает старшинством операторов. Вы не можете брать операнды в другом порядке, кроме как слева направо, а умножение должно выполняться до вычитания. Начинающий может по- думать, что преодолеть данный момент просто; иногда в очень огра- ниченных случаях это так, но проблема становится действительно Работа с Турбо Паскалем #2/2 = 70 = сложной, когда добавляются скобки, возведения в степень, перемен- ные и вызовы функций. Хотя существует несколько методов написания процедур, кото- рые вычисляют выражения подобного сорта, вы изучите один, который является самым распространенным и простым (некоторые другие мето- ды, использующиеся для написания программ грамматического разбо- ра, применяют сложные таблицы, которые требуют программ для их генерации). Метод, который вы будете изучать, называется рекур- сивным нисходящим синтаксическим разбором; в данной главе вы уви- дите, как он оправдывает свое название. Работа с Турбо Паскалем #2/2 = 71 = Разбор выражения ----------------------------------------------------------------- Прежде, чем вы сможете развернуть синтаксический разбор, ко- торый вычисляет выражения, вы должны для облегчения выделить эле- менты выражения. Например, в данном выражении А*В-(w+10) вы должны быть способны выделить операнды A,B,W и 10, скобки и операторы *, + и -. В данном случае вам нужна процедура, которая возвращает каждый элемент выражения в отдельности. Данная проце- дура также должна пропускать пробелы и табуляции и обнаруживать конец выражения. Формально каждый элемент выражения называется лексемой (lcken). Следовательно, функция, которая возвращает оче- редную лексему выражения, называется CetToken. Для хранения выра- жения нужна глобальная переменная. Данная символьная переменная называется prog. Переменная prog является глобальной, так как она должна устанавливаться между вызовами CetToken и должна позволять другим функциям использовать ее. В дополнении к prog используется глобальная целая переменная t в качестве индекса по prog, позво- ляя CetToken продвигаться по выражению по одной лексеме за раз. CetToken предполагает, что prog заканчивается символом $. Вы должны убедиться и в данном случае, что это так в виду того, что символ $ сигнализирует о конце выражения. Помимо получения лексемы вам необходимо также знать ее тип. Для синтаксического разбора, рассматриваемого в данной главе, достаточно трех типов: VARIABLE (переменная),NUMDER (число) и DELIMITER (разделитель), где тип DELIMITER используется для опе- раторов и скобок. Далее показана функция CetToken с необходимыми глобальными переменными и функциями поддержки: type str80 = string[80]; TType = (DELIMITER,VARIABLE,NUMBER); var token, prog: str80; TokType: TType; code, t: integer; result: real; {данная функция возвращает TRUE, если ch является буквой алфавита} function IsAlpha(ch: char): boolean; begin IsAlpha:= (UpCase(ch)>='A') and (UpCase(ch)<='Z'); end; {IsAlpha} Работа с Турбо Паскалем #2/2 = 72 = {данная функция возвращает TRUE, если ch является символом новой строки, табуляции или пробелом } function IsWhite(ch: char): boolean; begin IsWhite: = (ch=' ') or (ch=chr(9)) or (ch=chr(13)); end; {IsWhite} {данная функция возвращает TRUE, если ch является разделителем} function IsDelim(ch: char): boolean; begin if pos(ch, ' +-/*%^=()S')<>0 then IsDelim: = TRUE end; {IsDelim} {данная функция возвращает TRUE, если ch - цифра от 0 до 9} function IsDigit(ch: char): boolean; begin IsDigit: = (ch>='0') and (ch<='9'); end; {IsDigit} {GotToken считывает следующую лексему из входного потока} procedure GetToken; var temp: str80; begin token: = ''; {пустая строка } while(IsWhite(prog[t])) do t:=t+1; {пропустить предшествующие пробелы} if prog[t]='S' then token: = 'S'; if pos(prog[t], '+-*/%^=()')<>0 then begin TokType: = DELIMITER; token: = prog[t]; {является оператором } t: = t+1; end else if IsAlpha(prog[t]) then begin While(not IsDelim(prog[t])) do begin token: = concat(token, prog[t]); { построить лексемы } t: = t+1; end; TokType: = VARIABLE; end else if IsDigit(prog[t]) then begin Работа с Турбо Паскалем #2/2 = 73 = while(not IsDelim[t])) do begin token: = concat(token,prog[t]); { построить число } t: = t+1; TokType: = NUMBER; end; end; end;{GetToken} Прежде, чем данная процедура может быть использована, гло- бальная переменная t должна быть установлена в 1. Помните, что эта переменная используется для индексирования по строке prog, которая содержит входное выражение. При входе в CetToken процеду- ра проверяет, не равен ли очередной символ $, который указывает на конец строки выражения. Предваряющие пробелы пропускаются. Хо- тя пробелы могут быть добавлены в выражение для читабельности, при синтаксическом разборе они игнорируются. После пропуска пробелов prog(t) указывает на число, перемен- ную, оператор или $, если хвостовые пробелы завершают выражение. Если следующий символ является оператором, то этот символ возвра- щается как строка в глобальной переменной token и тип DELIMITER помещается в TokType. Если этот следующий символ является буквой, то считается, что это переменная; символ возвращается, как строка в token; TokType принимает значение VARIABLE. Если следующий сим- вол является числом, то целое число возвращается как строка в token с типом NUMBER. Наконец, если следующий символ отсутствует, вы можете считать, что это найден конец выражения, и token прини- мает значение $. Чтобы сохранить логическую ясность данной функции, опреде- ленное количество проверок на ошибки опущено и сделан ряд допуще- ний. Например, некоторые неопознанные символы отбрасываются. Кро- ме того, в данной версии переменные могут быть любой длины, но первым символом должна быть буква. Однако, вы легко можете моди- фицировать CelToken, чтобы допустить применение символьных строк, чисел с плавающей запятой или чего-либо еще. Для того, чтобы понять, как работает CetToken, рассмотрим, что она возвращает на каждом шаге для выражения А+100-(В*С)/2. Лексема Тип лексемы A VARIABLE переменная + DELIMITER ограничитель 100 NUMBER число - DELIMITER ограничитель ( DELIMITER ограничитель B VARIABLE переменная * DELIMITER ограничитель C VARIABLE переменная Работа с Турбо Паскалем #2/2 = 74 = ) DELIMITER ограничитель / DELIMITER ограничитель 2 NUMBER число $ $ Работа с Турбо Паскалем #2/2 = 75 = СИНТАКСИЧЕСКИЙ РАЗБОР ----------------------------------------------------------------- Напомним, что существует целый ряд методов синтаксического разбора и вычисления выражений. Для целей данной статьи предполо- жим, что выражения являются рекурсивными структурными данными, которые определяются в терминах самих себя. Если вы ограничитесь использованием в выражениях только операторов +, -, *, / и ско- бок, то сможете сказать, что все выражения могут быть определены в терминах следующих правил: выражение=>терм[+терм][-терм] терм=>множитель[*множитель][/множитель] множитель=>переменная, число или (выражение) где любая часть может быть пустой. Квадратные скобки обозначают необязательность, а => - порождение. Фактически правила являются правилами порождения выражений. Предшествование операторов подра- зумевается способом порождения выражений. Выражение 10+5*В состоит из двух термов: 10 и 5*В. Однако, включает три множителя: 10, 5 и В. Этими множителями являются два числа и одна перемен- ная. С другой стороны выражение 14*(7-С) имеет два терма 10 и (7-С), один из которых является числом, а другая дочерним выражением. Дочернее выражение распадается на од- но число и одну переменную. Данный процесс формирует основу для рекурсивного нисходящего синтаксического разбора, который представляет собой набор общих рекурсивных процедур, носящих цепочный характер. На каждом соот- ветствующем шаге синтаксический разбор может выполнять заданные операции в алгебраически правильной последовательности. Для при- мера рассмотрим синтаксический разбор входного выражения 9/3-(100 +56) и выполнение операций по шагам. Шаг 1. Взять первую лексему: 9/3 Шаг 2. Взять оба множителя и выполнить операцию деления. В результате получается 3. Шаг 3. Взять вторую лексему: (100+56). В данной точке вы должны рекурсивно проанализировать второе выражение. Шаг 4. Взять оба числа и сложить. В результате получается 156. Шаг 5. Возвратиться из рекурсивного вызова и вычесть 156 из 3, что дает ответ - 153. Если вы немного запутались, не беспокойтесь. Это сложная концеп- ция. Нужно усвоить два момента о данном рекурсивном взгляде на выражения: во-первых, предшествование операторов является неявным Работа с Турбо Паскалем #2/2 = 76 = при заданных правилах порождения выражений; во-вторых, данный ме- тод синтаксического разбора и вычисления выражений очень похож на то, как это делается вручную. Работа с Турбо Паскалем #2/2 = 77 = ПРОСТАЯ ПРОГРАММА СИНТАКСИЧЕСКОГО РАЗБОРА ВЫРАЖЕНИЙ ----------------------------------------------------------------- В оставшейся части данной главы рассматриваются две програм- мы синтаксического разбора. Первая осуществляет синтаксический разбор и вычисление константных выражений. Это синтаксический разбор самого простого вида. Второй синтаксический анализатор принимает до 26 задаваемых пользователем переменных от А до Z. Далее целиком приводится простая версия программы рекурсив- ного нисходящего синтаксического разбора для целых выражений. Она принимает те же самые глобальные данные, что и процедура GetToken, показанная ранее. {********** синтаксический анализатор выражений *************} procedure Level2(var result: real); forward; procedure Level3(var result: real); forward; procedure Level4(var result: real); forward; procedure Level5(var result: real); forvard; procedure Level6(var result: real); forward; procedure Primitve(var result: real); forward; {это точка входа в синтаксический анализатор } procedure GetExp(var result: real); begin GetToken; if length(token)<>0 then Level2(result) else Serror(3); end; {GetExp} {процесс + или - } procedure Level2; var op: char; hold: real; begin Level3(result); op: = token[1]; while((op='+') or (op='-') do begin GetToken; Level3(hold); arith(op, result, hold); op: = token[1] end; end; {Level2} Работа с Турбо Паскалем #2/2 = 78 = {процесс * или \ } procedure Level3; var op: char; hold: real; begin Level4(result); op: = token[1]; while ((op '*') or (op='/')) do begin GetToken; Level4(hold); arith(op, result, hold); op: = token[1]; end; end; {Level3} {процесс ^ (возведение в степень)} procedure Level4; var hold: real; begin Level5(result); if token[1]='^' then begin GetToken; Level4(hold); arith('^', result, hold); {exponents} end; end; {Level4} {процесс унарного оператора} procedure Level5; var op: char; begin op: = ' '; if((TokType=DELIMITER) and ((token[1]='+') or (token[1]='-'))) then begin {unary plus or minus} op:= token[1]; GetToken; end; Level6(result); Работа с Турбо Паскалем #2/2 = 79 = if op='-' then result: = -result; end; {Level5} {процесс скобок } procedure Level6; begin if(token[1]='(') and (TokType=DELIMITER) then begin {Parenthesized expression} GetToken; Level2(result); if token[1]<>')'then Serror(2);{скобки не сбалансированы} GetToken; end else Primitive(result); end; {Level6} { найти значение числа} procedure Primitive; begin if TokType=DELIMITER then val(token, result, code) else Serror(1); GetToken; end. Программа, как показано, может принимать операторы +,-,* и /, а также возведение в степень (^), унарный минус и скобки. Она имеет шесть уровней, а также функцию Primilive, которая возвраща- ет значение целого числа. Команда foevard необходима, так как не- которые из этих процедур взаимно рекурсивны, следовательно, не возможно определить все процедуры до обращения к ним. В дополнение к синтаксическому анализатору существует нес- колько процедур для специальных целей: Serror, которая сообщает о синтаксических ошибках, Pwr и Arith, которые выполняют различные арифметические операции. Эти подпрограммы показаны далее: {отображение сообщений об ошибках } Procedure Serror(i: integer); begin case i of 1: WriteLn('синтаксическая ошибка '); 2: WriteLn('несбалансированные скобки'); 3: WriteLn('выражение отсутствует '); end; end; {Serror} {возведение в степень } function Pwr(a, b: real): real; Работа с Турбо Паскалем #2/2 = 80 = var t: integer; temp: real; begin if a=0 then Pwr: = 1 else begin temp: = a; for t: = trunc(b) cownto 2 do a: = a*temp; Pwr: = a; end; end; {данная функция выполняет заданные арифметические операции} procedure Arith(op: char; var result, operand: real); begin case op of '+': result: = result+operand; '-': result: = result-operand; '*': result: = result*operand; '/': result: = result/operand; '^': result: = result^operand; end; end; {Arith} Как показано ранее, в двух глобальных переменных token и TokType возвращаются очередная лексема и ее тип, а строка prog содержит выражение. Далее представлен синтаксический анализатор с процедурами поддержки, а также простые программы, которые могут быть исполь- зованы для демонстрации синтаксического анализатора. { Данная программа демонстрирует работу синтаксического анализатора. Она не принимает переменных и поддерживает числа только типа real (действительные)} program parser; type str80 = string[80]; TType = (DELIMITER, VARIABLE, NUMBER); var token, prog: str80; TokType: TType; code, t: integer; result: real; {данная функция возвращает TRUE, если ch является буквой алфавита} Работа с Турбо Паскалем #2/2 = 81 = function IsAlpha(ch: char): boolean; begin IsAlpha:= (UpCase(ch)>='A') and (UpCase(ch)<='Z'); end; {IsAlpha} {данная функция возвращает TRUE, если ch является символом новой строки, табуляции или пробелом } function IsWhite(ch: char): boolean; begin IsWhite: = (ch=' ') or (ch=chr(9)) or (ch=chr(13)); end; {IsWhite} {данная функция возвращает TRUE, если ch является разделителем} function IsDelim(ch: char): boolean; begin if pos(ch, ' +-/*%^=()S')<>0 then IsDelim: = TRUE end; {IsDelim} {данная функция возвращает TRUE, если ch - цифра от 0 до 9} function IsDigit(ch: char): boolean; begin IsDigit: = (ch>='0') and (ch<='9'); end; {IsDigit} {GotToken считывает следующую лексему из входного потока} procedure GetToken; var temp: str80; begin token: = ''; {пустая строка } while(IsWhite(prog[t])) do t:=t+1; {пропустить предшествующие пробелы} if prog[t]='S' then token: = 'S'; if pos(prog[t], '+-*/%^=()')<>0 then begin TokType: = DELIMITER; token: = prog[t]; {является оператором } t: = t+1; end else if IsAlpha(prog[t]) then begin While(not IsDelim(prog[t])) do begin token: = concat(token, prog[t]); { построить лексемы } t: = t+1; Работа с Турбо Паскалем #2/2 = 82 = end; TokType: = VARIABLE; end else if IsDigit(prog[t]) then begin while(not IsDelim[t])) do begin token: = concat(token,prog[t]); { построить число } t: = t+1; TokType: = NUMBER; end; end; end; {GetToken} {отображение сообщений об ошибках } Procedure Serror(i: integer); begin case i of 1: WriteLn('синтаксическая ошибка '); 2: WriteLn('несбалансированные скобки'); 3: WriteLn('выражение отсутствует '); end; end; {Serror} {возведение в степень } function Pwr(a, b: real): real; var t: integer; temp: real; begin if a=0 then Pwr: = 1 else begin temp: = a; for t: = trunc(b) cownto 2 do a: = a*temp; Pwr: = a; end; end; {данная функция выполняет заданные арифметические операции} procedure Arith(op: char; var result, operand: real); begin case op of '+': result: = result+operand; '-': result: = result-operand; '*': result: = result*operand; '/': result: = result/operand; '^': result: = result^operand; end; Работа с Турбо Паскалем #2/2 = 83 = end; {Arith} {********** синтаксический анализатор выражений *************} procedure Level2(var result: real); forward; procedure Level3(var result: real); forward; procedure Level4(var result: real); forward; procedure Level5(var result: real); forward; procedure Level6(var result: real); forward; procedure Primitive(var result: real); forward; {это точка входа в синтаксический анализатор } procedure GetExp(var result: real); begin GetToken; if Length(token)<>0 then Level2(result) else Serror(3); end; {GetExp} {процесс + или - } procedure Level2; var op: char; hold: real; begin Level3(result); op:= token[1]; while(op='+') or (op='-') do begin GetToken; Level3(hold); arith(op, result, hold); op: = token[1]; end; end; {Level2} {процесс * или \ } procedure Level3; var op: char; hold: real; begin Level4(result); Работа с Турбо Паскалем #2/2 = 84 = op: = token[1]; while (op='*') or (op='/') do begin GetToken; Level4(hold); arith(op,result,hold); op: = token[1]; end; end; {Level3} {процесс ^ (возведение в степень)} procedure Level4; var hold: real; begin Level5(result); if token[1]='^' then begin GetToken; Level4(hold); arith('^',result,hold); {exponents} end; end; {Level4} {процесс унарного оператора} procedure Level5; var op: char; begin op: = ' '; if((TokType=DELIMITER) and ((token[1]='+') or (token[1]='-'))) then begin op: = token[1], GetToken; end; Level6(result); if op='-' then result: = -result; end; {Level5} {процесс скобок } procedure Level6; begin if(token[1]='(') and (TokType=DELIMITER) then begin {заключенное в скобки выражение } Работа с Турбо Паскалем #2/2 = 85 = GetToken; Level2(result); if token[1]<>')'then Serror(2);{несбалансированные скобки} GetToken; end else Primitive(result); end; {Level6} { найти значение числа } procedure Primitive; begin if TokType=NUMBER then val(token, result, code) else Serror(1); GetToken; end; begin {nain} repeat t:=1; {инициализировать счетчик лексем } Write('Введите выражение: '); ReadLn(prog); prog:=concat(prog, '$'); if(prog<>'quit$') then begin GetExp(result); writeLn(result); end; until prog='quit$' end. Программа позволяет вам ввести численное выражение, для ко- торого она вычислит ответ. Для выхода из программы напечатайте QUIT. Для понимания того, как синтаксический анализатор вычисляет выражение, поработайте со следующим выражением, которое после приема содержится в prog: 10-3*2 Когда вызывается GetExp (начальная процедура в синтакси- ческом анализаторе), она берет первую лексему и, если она явля- ется пустой, печатает сообщение no expression present (выражение отсутствует) перед возвратом. Если лексема присутствует, то вызы- вается level2 (level1 будет добавлена к синтаксическому анализа- Работа с Турбо Паскалем #2/2 = 86 = тору позднее, когда будет добавлен оператор назначения, а сейчас она не нужна). Лексема теперь содержит число 10. level2 вызывает level3, а level3 вызывает level4, которая в свою очередь вызывает level5. level5 проверяет, является ли лексема унарным оператором + или -; в данном случае это не так и, поэтому, вызывается level6. level6 рекурсивно вызывает level2 в случае заключенного в скобки выраже- ния или вызывает Primitive для нахождения целого значения. Нако- нец, когда Primitive выполнится и значение 10 поместится в reselt, процедура GetToken получит следующую лексему. Затем функ- ции начнут возвращать управление назад по цепочке. Теперь лексема - это оператор "-" и функции возвратятся до level2. Следующий шаг очень важен. Так как лексемой является опера- тор "-", она сохраняется и GetToken получает новую лексему 3; нисходящая вниз цепочка начинается вновь. Снова вызывается Primitive, целое число 3 возвращается в result и считывается лек- сема *. В данном случае управление возвращается к Level3, где считывается финальная лексема 2. В этой точке совершается первая арифметическая операция умножения 2 на 3. Этот результат затем возвращается к level2 и выполняется вычитание, которое дает ре- зультат 4. Хотя процесс сначала может показаться запутанным, вы должны проработать другие примеры для самопроверки. Вы могли бы использовать этот синтаксический анализатор, как настольный калькулятор. Вы также могли бы использовать его в ба- зах данных и других не сложных случаях. Прежде, чем можно было бы применить его в языках и сложных калькуляторах, синтаксический анализатор должен быть способен поддерживать переменные, которые являются предметом следующего раздела. Работа с Турбо Паскалем #2/2 = 87 = Добавление переменных к синтаксическому анализатору ----------------------------------------------------------------- Все языки программирования и многие калькуляторы используют переменные для запоминания значений, которые потребуются позднее. Простой синтаксический анализатор из предыдущего раздела должен быть расширен обработкой переменных прежде, чем его можно будет использовать для этой цели. Во-первых, вам нужны собственно переменные. Так как синтак- сический анализатор ограничен использованием только целых выраже- ний, то вы также можете использовать переменные только целого ти- па. Синтаксический анализатор будет распознавать только переменные от А до Z, хотя вы могли бы избавиться от этого огра- ничения, если бы захотели. Каждая переменная использует ячейку матрицы из 26 элементов. Следовательно, нам нужно добавить varsi array[0..25] of real; { 26 переменных } Однако, перед тем, как начать использовать эти переменные, вам следует установить их в 0. Вам также нужна процедура для нахождения значения данной пе- ременной. Так как вы используете буквы от А до Z в качестве имен переменных, вы можете легко индексировать по матрице vars на ос- нове имени переменной. Далее представлена функция FindVar: function FinVar(s: str80): real; var t: integer; begin FinVar:=vars[ord(Upcase(s[1]))-ord('A')]; end; {FindVar} Как вы видите, данная функция может принимать имена переменных любой длины, но значащей в имени является только первая буква. Вы можете модифицировать данную функцию, чтобы она удовлетворяла ва- шим нуждам. Вы должны также модифицировать функция primitive, чтобы она трактовала и числа и переменные, как примитив, следующим образом procedure Primitive; begin if TokType=NUMBER then val(token, result, code) else if TokType=VARIABLE then result:=FindVar(token) else Serror(1); GetToken; end. Работа с Турбо Паскалем #2/2 = 88 = Все это необходимо, чтобы синтаксический анализатор мог пра- вильно использовать переменные; однако, отсутствует способ прис- ваивания значений переменным. Вы можете назначить значения пере- менным вне синтаксического анализатора, но, так как можно трактовать=, как оператор присваивания, вы имеете возможность сделать его частью синтаксического анализатора различными метода- ми. Один из них состоит в том, чтобы добавить level1 к синтакси- ческому анализатору, как показано далее: { процесс предложения присваивания } procedure Level1; var hold: real; temp: Type; slot: integer; TempToken: str80; begin if TokType=VARIABLE then begin {сохранить старую лексему} TempToken:=token; temp:=TokType; slot:=ord(Upcase(token[1]))-ord)'A'); GetToken; {проверить,существует ли = для присваивания } if token[1]<>'"' then begin PutBack; {заменить лексему } {восстановить старую лексему} token := TempToken; TokType := temp; Level2(result); end else begin GetToken; Level2(result); vars[slot] := result; end; end {if} else Level2(result); end; {Level1} Когда переменная встречается как первая лексема в выражении, она может быть либо целью выражения, как в А=В*10 либо просто частью выражения А-123 В level1, чтобы знать, что из себя представляет переменная, должен выпол- Работа с Турбо Паскалем #2/2 = 89 = няться просмотр вперед. Просмотр вперед - это процесс, сохраняю- щий текущую лексему и затем получающий следующую для анализа. В данном случае, если существующая лексема - это =, то вы знаете, что будет выполняться присваивание, и выполняются соответствующие процедуры. Если это не =, то лексема будет возвращена назад в строку выражения, а предыдущая лексема должна быть восстановлена. Вы можете сделать это с помощью процедуры Putbacl, которая просто уменьшает индекс t. Как вы можете видеть, "просмотр вперед" тре- бует определенного времени и его следует в общем случае избегать, исключая случаи, когда он абсолютно необходим. Далее представлены целиком расширенный синтаксический анали- затор, вспомогательные функции и главная программа: {данная программа демонстрирует синтаксический анализатор который допускает применение переменных } program parser2; type str80 = string[80]; TType = (DELIMITER, VARIABLE, NUMBER); var token, prog: str80; TokType: TType; code, t: integer; result: real; vars: array[0..25] of real; {26 переменных} {данная функция возвращает TRUE, если ch является буквой алфавита} function IsAlpha(ch: char): boolean; begin IsAlpha:= (UpCase(ch)>='A') and (UpCase(ch)<='Z'); end; {IsAlpha} {данная функция возвращает TRUE, если ch является символом новой строки, табуляции или пробелом } function IsWhite(ch: char): boolean; begin IsWhite: = (ch=' ') or (ch=chr(9)) or (ch=chr(13)); end; {IsWhite} {данная функция возвращает TRUE, если ch является разделителем} function IsDelim(ch: char): boolean; begin if pos(ch, ' +-/*%^=()S')<>0 then IsDelim: = TRUE end; {IsDelim} Работа с Турбо Паскалем #2/2 = 90 = {данная функция возвращает TRUE, если ch - цифра от 0 до 9} function IsDigit(ch: char): boolean; begin IsDigit: = (ch>='0') and (ch<='9'); end; {IsDigit} {GotToken считывает следующую лексему из входного потока} procedure GetToken; var temp: str80; begin token: = ''; {пустая строка } while(IsWhite(prog[t])) do t:=t+1; {пропустить предшествующие пробелы} if prog[t]='S' then token: = 'S'; if pos(prog[t], '+-*/%^=()')<>0 then begin TokType: = DELIMITER; token: = prog[t]; {является оператором } t: = t+1; end else if IsAlpha(prog[t]) then begin While(not IsDelim(prog[t])) do begin token: = concat(token, prog[t]); { построить лексемы } t: = t+1; end; TokType: = VARIABLE; end else if IsDigit(prog[t]) then begin while(not IsDelim[t])) do begin token: = concat(token,prog[t]); { построить число } t: = t+1; TokType: = NUMBER; end; end; end; {GetToken} { PutBack возвращает лексему во входной поток } procedure PutBack; begin t := t-length(token); end; {PutBack} Работа с Турбо Паскалем #2/2 = 91 = {отображение сообщений об ошибках } Procedure Serror(i: integer); begin case i of 1: WriteLn('синтаксическая ошибка '); 2: WriteLn('несбалансированные скобки'); 3: WriteLn('выражение отсутствует '); end; end; {Serror} {возведение в степень } function Pwr(a, b: real): real; var t: integer; temp: real; begin if a=0 then Pwr: = 1 else begin temp: = a; for t: = trunc(b) cownto 2 do a: = a*temp; Pwr: = a; end; end; {данная функция выполняет заданные арифметические операции} procedure Arith(op: char; var result, operand: real); begin case op of '+': result: = result+operand; '-': result: = result-operand; '*': result: = result*operand; '/': result: = result/operand; '^': result: = result^operand; end; end; {Arith} {FindVar возвращает значение переменной} function FindVar(s: str80): real; var t: integer; begin FindVar:=vars[ord(Upcase(s[1]))-ord('A')]; end; {FindVar} {********** синтаксический анализатор выражений *************} {**** with variables and assignment *******} procedure Level2(var result: real); forward; Работа с Турбо Паскалем #2/2 = 92 = procedure Level1(var result: real); forward; procedure Level3(var result: real); forward; procedure Level4(var result: real); forward; procedure Level5(var result: real); forward; procedure Level6(var result: real); forward; procedure Primitive(var result: real); forward; {это точка входа в синтаксический анализатор } procedure GetExp(var result: real); begin GetToken; if Length(token)<>0 then Level1(result) else Serror(3); end; {GetExp} { процесс предложения присваивания } procedure Level1; var hold: real; temp: Type; slot: integer; TempToken: str80; begin if TokType=VARIABLE then begin {сохранить старую лексему} TempToken:=token; temp:=TokType; slot:=ord(Upcase(token[1]))-ord)'A'); GetToken; {проверить,существует ли = для присваивания } if token[1]<>'"' then begin PutBack; {заменить лексему } {восстановить старую лексему} token := TempToken; TokType := temp; Level2(result); end else begin GetToken; Level2(result); vars[slot] := result; end; end {if} Работа с Турбо Паскалем #2/2 = 93 = else Level2(result); end; {Level1} {процесс + или - } procedure Level2; var op: char; hold: real; begin Level3(result); op := token[1]; while(op='+') or (op='-') do begin GetToken; Level3(hold); arith(op, result, hold); op := token[1] end; end; {Level2} {процесс * или \ } procedure Level3; var op: char; hold: real; begin Level4(result); op := token[1]; while ((op='*') or (op='/')) do begin GetToken; Level4(hold); arith(op, result, hold); op := token[1]; end; end; {Level3} {процесс ^ (возведение в степень)} procedure Level4; var hold: real; begin Level5(result); if token[1]='^' then begin GetToken; Работа с Турбо Паскалем #2/2 = 94 = Level4(hold); arith('^', result, hold); end; end; {Level4} {процесс унарного оператора} procedure Level5; var op: char; begin op := ' '; if ((TokType=DELIMITER) and ((token[1]='+') or (token[1]='-'))) then begin op := token[1]; GetToken; end; Level6(result); if op='-' then result := -result; end; {Level5} {процесс скобок } procedure Level6; begin if(token[1]='(') and (TokType=DELIMITER) then begin {заключенное в скобки выражение} GetToken; Level2(result); if token[1]<>')' then Serror(2); {скобки не сбалансированы} GetToken; end else Primitive(result); end; {Level6} procedure Primitive; begin if TokType=NUMBER then val(token, result, code) else if TokType=VARIABLE then result := FindVar(token) else Serror(1); GetToken; end; begin {главная} for t:=0 to 25 do vars[t]:=0; {инициализировать Работа с Турбо Паскалем #2/2 = 95 = переменные} repeat t := 1; Write('Введите выражение: '); 38 ReadLn(prog); prog := concat(prog, '$'); if(prog<>'quit$') then begin GetExp(result); writeLn(result); end; until prog='quit$'; end. Теперь при расширенном синтаксическом анализаторе вы можете ввести такие выражения, как А=10/4 А-В С=А*(Р-21) и они будут вычислены правильно. Работа с Турбо Паскалем #2/2 = 96 = ПРОВЕРКА СИНТАКСИСА В ПРОГРАММЕ РЕКУРСИВНОГО НИСХОДЯЩЕГО СИНТАКСИЧЕСКОГО РАЗБОРА ----------------------------------------------------------------- При синтаксическом разборе выражений синтаксическая ошибка - это ситуация, в которой входное выражение не удовлетворяет точным правилам, предъявляемым синтаксическим анализатором. В большинс- тве случаев синтаксические ошибки происходят по вине человека обычно при печатании. Например, следующие выражения не могут быть правильно разобраны описанным синтаксическим анализатором: 10**8 ((10-5)*9) /8 Первое выражение имеет два оператора подряд, во втором присутс- твуют несбалансированные скобки, а третье начинается с оператора деления. Так как синтаксические ошибки могут запутать синтакси- ческий анализатор и привести к ошибочным результатам, очень важна защита от них. При изучении программы синтаксического разбора вы возможно обратили внимание на функцию Serror, которая вызывается в различ- ных ситуациях. В отличие от других методов синтаксического разбо- ра при рекурсивном нисходящем синтаксическом разборе проверка синтаксиса осуществляется очень легко, так как в большинстве слу- чаев ошибки обнаруживаются в модулях Primitive, FindVar или level6, где проверяются скобки. Проверка синтаксиса имеет здесь только одну задачу: синтаксический анализатор не должен аварийно завершить при синтаксической ошибке. Это может вызвать генерацию множественных сообщений об ошибках. Чтобы добавить полное восстановление от ошибок, вы должны добавить глобальные переменные, которые проверяются на каждом уровне. Переменные сначала должны быть установлены в FALSE, а лю- бое обращение к Serror должно устанавливать их в TRUE, заставляя синтаксический анализатор преждевременно прекращать одну функцию. При таком построении программы может так случиться, что бу- дут выданы множественные сообщения об ошибках. Если в одних слу- чаях это будет раздражать, то в других будет полезным, так как дает возможность обнаружить несколько ошибок. В общем случае, од- нако, вы захотите усилить проверку синтаксиса перед тем, как ис- пользовать ее в коммерческой программе. Работа с Турбо Паскалем #2/2 = 97 = ГЛАВА 9. ИНСТРУМЕНТАРИЙ БАЗ ДАННЫХ ТУРБО ПАСКАЛЯ ----------------------------------------------------------------- Мощным средством, существующим в Турбо Паскале, является ин- струментарий баз данных. Он содержит процедуры, которые поддержи- вают операции в базах данных типа В-дерева, сортировки и установ- ку терминалов оконечных пользователей. Они называются Turbo Access, Turbosort и GINST. В данной главе рассматривается каждая из них и делается ударение на процедурах баз данных. Примеры, данные в этой главе, отражают инструментарий баз данных, предназначенный для использования с Турбо Паскалем версии 3. Инструментарий баз данных, предназначенный для Турбо Паскаля версии 4 и последующих, существенно отличается от предназначенных для более ранних версий. Наиболее важное отличие заключается в том, что процедуры инструментария версии 4 содержатся в блоках, которые связываются с вашей программой, как это необходимо, вмес- то включения с использованием директивы компилятора SI, как в ин- струментарии версии 3. TURBO ACCESS ----------------------------------------------------------------- Процедуры баз данных TURBO ACCESS реализуют полную файловую структуру типа В-дерева. В-дерево названо по имени его изобрета- теля R.Bayer. Оно отличается от обычного двоичного дерева тем, что каждый корневой узел может иметь более двух детей, как пока- зано на рис.9-1. Из-за организации В-дерева файлы, размещающиеся на диске, могут быть найдены очень быстро. Хотя реализация В-де- рева может быть довольно трудной, вам не надо понимать, как функ- ции используют процедуры инструментария - фирма Borland сделала за вас всю трудную работу. ┌────┐ / -----└────┘ -----\ / | \ ┌────┐ ┌────┐ ┌────┐ / └────┘ └────┘ └────┘ / | / \ | \ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ Рис.9-1. Пример В-дерева Работа с Турбо Паскалем #2/2 = 98 = Необходимые файлы ----------------------------------------------------------------- Прежде, чем вы сможете использовать любую процедуру инстру- ментария, вы должны иметь в наличие следующие файлы: Имя файла Функция ACCESS.BOX Основной файл данных и индексов для инициализации и установки ADDKEY.BOX Добавляет ключи к индексным файлам DELKEY.BOX Удаляет ключи из индексных файлов GETKEY.BOX Находит ключи Отметим, что инструментарий баз данных поставляется с двумя версиями базовых процедур ввода/вывода: одна для версии 2 Турбо Паскаля, другая для версии 3 Турбо Паскаля. Они называются ACCESS.BOX и ACCESS3.BOX, соответственно. Вы должны скопировать соответствующий файл в ACCESS.BOX для того, чтобы включить соот- ветствующие процедуры, как показано в примерах в данной главе. С этого момента будем полагать, что процедуры ввода/вывода баз дан- ных находятся в файле ACCESS.BOX. В дополнение к этим файлам вам еще нужна программа SETCONST. PAS, которая прилагается к инструментарию для вычисления несколь- ких констант, необходимых для процедур В-дерева. Файлы данных и индексные файлы ----------------------------------------------------------------- В Turbo Access файл данных используется для хранения запи- сей, которые содержат запоминаемую вами информацию. Каждый файл данных, созданный и поддерживаемый Turbo Access, может содержать до 65 536 записей. Однако, в данное число входит и первая запись, которая зарезервирована для использования Turbo Access. Таким об- разом вы в действительности можете запомнить 65535 записей. Хотя длина каждой записи может быть до 65536 байт, это не рекомендует- ся. Наименьшая допустимая длина записи - 8 байт. Все переменные файлов данных в Turbo Access должны быть декларированы как тип DataFile, который задан в ACCESS.BOX. Индексный файл содержит ключи, которые используются для идентификации специфических записей и номеров записей, связанных с ключем. Каждый индексный файл может содержать до 65535 ключей. Индексный файл обеспечивает быстрый способ определения местополо- жения специфических записей в файле данных. Важно понять, что все ключи должны быть строками. Все переменные индексных файлов в Turbo Access должны быть декларированы как IndexFile. Работа с Турбо Паскалем #2/2 = 99 = Константы В-дерева в Turbo Access ----------------------------------------------------------------- Шесть специальных констант должно быть задано любой програм- мой, которая использует систему Turbo Access. Эти константы уп- равляют такими спецификациями, как высота дерева и число узлов, подсоединенных к каждому корню. Имеются следующие константы: MaxDataRecSize MaxKeyLen PageSize Order PageStackSize MaxHeight Значение данных констант лучше всего вычислять с помощью программы SETCONST.PAS. Используя данную программу вы должны ввести длину записи данных, которые вы будете запоминать, и длину ключа, используемого для индексирования каждой записи. Мы расс- мотрим использование SETCONST.PAS далее в данной главе. Зарезервированные имена переменных и коды ошибок ----------------------------------------------------------------- Turbo Access использует глобальные переменные, имена которых начинаются с буквы "ТА". Хотя вы также можете объявить перемен- ные, которые начинаются с "ТА", но это не рекомендуется. Это мо- жет привести к сообщениям об ошибках во время компиляции, указы- вающим на дублирование имен идентификаторов. Если ошибка ввода/вывода происходит, когда вы используете процедуры Turbo Access, отображается один из кодов, показанных в таблице 9-1. Работа с Турбо Паскалем #2/2 = 100 = Таблица 9-1 Коды ошибок ввода/вывода Turbo Access ─────────────────────────────────────────────────────────────── Код Смысл ─────────────────────────────────────────────────────────────── 1 Неожидаемая длина записи 2 Несуществующий файл 3 Директория переполнена 4 Файл не открыт 5 Файл не открыт для ввода 6 Файл не открыт для вывода 7 Преждевременный EOF(конец файла) 8 Ошибка записи 9 Ошибка формата числа 10 Преждевременный конец файла 11 Размер файла превысил 65535 байт 12 Преждевременный конец файла 13 Ошибка записи 14 Попытка поиска за концом файла 15 Файл не открыт 16 Недопустимая операция 17 Недопустимое использование непосредственного режима 18 Недопустимое использование присваивания стандартным файлам 144 Неожидаемая длина записи 145 Попытка поиска за концом файла 153 Преждевременный конец файла 240 Ошибка записи 243 Слишком много открытых файлов ─────────────────────────────────────────────────────────────── В тексте отсутствуют страницы 288, 289 (прим.переводчика) procedure AddRec(var dfile: DataFile, var RecNum, var buffer); Процедура AddRec добавляет записи, содержащиеся в buffer, к файлу данных dfile и возвращает номер записи, в которой осущест- влено запоминание, в RecNum переменная buffer не имеет типа, но она должна содержать правильную запись. При успешном выполнении ОК содержит TRUE; в противном случае она равна FALSE. RecNum час- то используется в последующем вызове AddKey. Работа с Турбо Паскалем #2/2 = 101 = ClearKey ----------------------------------------------------------------- Процедура ClearKey используется для сброса указателя индекса индексного файла в начало файла. Она объявляется следующим обра- зом: procedure ClearKey(var ifile: IndexFile); CloseFile и CloseIndex Данные процедуры используются для закрытия файла данных и индексного файла, соответственно. Они объявляются следующим обра- зом: procedure CloseFile(var dfile: DataFile); procedure CloseIndex(var ifile: IndexFile); Вы должны закрыть все файлы, которые были открыты процедурами Turbo Access. Если этого не делать, то могут быть потеряны данные или разрушен индексный файл. DeleteKey ----------------------------------------------------------------- Данная процедура удаляет ключ из индексного файла. Она объ- является следующим образом: procedure DeleteKey(var ifile: Indexfile, var RecNum: integer, var key); Если в файле допускаются дублированные ключи, то RecNum должна содержать номер записи данных, связанной с ключем, который должен быть удален. Если операция завершилась успешно, то ОК равно TRUE, в противном случае - FALSE. DeleteRec ----------------------------------------------------------------- Процедура DeleteRec удаляет указанную запись из файла дан- ных. Она объявляется следующим образом: procedure DeleteRec(var dfile. DataFile,var RecNum:integer); Данная процедура осуществляет удаление записи с номером RecNum. Если операция выполняется успешно, ОК равно TRUE, в противном случае - FALSE. Уничтожаемая запись удаляется из файла логически, но она остается в файле физически и помещается в связанный список уничтоженных записей для возможного последующего использования. Важно, чтобы вы никогда не пытались уничтожить уже уничтоженную запись. Делая так, можно разрушить связанный список уничтоженных записей. Работа с Турбо Паскалем #2/2 = 102 = FileLen ----------------------------------------------------------------- Функция FileLen возвращает число записей в файле данных. Она объявляется следующим образом: function FileLen(var dfile: DataFile): integer; Помните, что все файлы Turbo Access используют первую запись для внутренних целей так, что число записей, действительно содержа- щих ваши данные, меньше на 1 возвращаемого данной функцией. FindKey ----------------------------------------------------------------- Данная процедура определяет местоположение заданного ключа в индексном файле и возвращает соответствующий номер записи файла данных. Она объявляется следующим образом: procedure FindKey(var ifile: IndexFile,var RecNum:integer, var key); Если key является ключем в заданном файле, то RecNum устанавлива- ется на запись, связанную с ключем в файле данных. Если операция выполнилась успешно, то ОК устанавливается в значение TRUE, в противном случае - в FALSE. GetRec ----------------------------------------------------------------- Процедура GetRee осуществляет считывание записи информации с заданным номером из файла данных. Она объявляется следующим обра- зом: procedure GetRec(var dfile: DataFile, var RecNum: integer, var buffer); Запись с номером RecNum считывается в buffer, который должен по размеру соответствовать записи. InitIndex ----------------------------------------------------------------- Данная процедура использует для инициализации таблицы, ис- пользуемой Turbo Access для установки индексных файлов. Она не имеет параметров. Данная процедура должна вызываться однажды в начале вашей программы до вызова других процедур Turbo Access. MakeFile MakeIndex ----------------------------------------------------------------- Данные процедуры используются для создания файлов данных и Работа с Турбо Паскалем #2/2 = 103 = индексных файлов Turbo Access. Они объявляются следующим образом: procedure MakeFile(var dfile: DataFile, fname: string[14] RecLen: integer); procedure MakeIndex(var ifile: IndexFile, fname: string[14], KeyLen,DupKeys: integer); Параметр RecLen в MakeFile должен содержать значение длины дан- ных, которые будут запоминаться. Лучший способ сделать это - ис- пользование SizeOf вместо подсчета байт. Параметр KeyLen в MakeIndex должен содержать значение длины ключа. Если DopKey ус- тановлен в 0, то дублирование ключей не допускается, а если в 1, то допускается. Успешное выполнение операции устанавливает ОК в TRUE, в противном случае - в FALSE. NextKey ----------------------------------------------------------------- Данная процедура возвращает соответствующий номер записи файла данных, связанный со следующим ключем в индексном файле. Она объявляется следующим образом: procedure NextKey(var ifile: IndexFile, var RecNum: integer, var key); После обращения к процедуре RecNum будет содержать номер за- писи в файле данных, соответствующей следующему ключу в индексном файле. Переменной key будет присвоено значение следующего ключа. При успешном обращении к NextKey ОК устанавливается в TRUE, в противном случае - в FALSE. OpenFile и OpenIndex ----------------------------------------------------------------- Данные процедуры используются для открытия существующих фай- ла данных и индексного файла, соответственно. Они объявляются следующим образом: procedure OpenFile(var dfile: DataFile, fname: string[14], RecLen: integer); procedure OpenIndex(var ifile:IndexFile,fname: string[14], KeyLen, DupKeys: integer); Параметр RecLen в OpenFile должен быть установлен в значение дли- ны данных, которые будут запоминаться. Лучший способ сделать это состоит в том, чтобы применить SizeOf вместо подсчета байт. Пара- метр RecLen в OpenIndex должен быть установлен в значение длины Работа с Турбо Паскалем #2/2 = 104 = ключа. Если параметр DupKey установлен в 0, дублирование ключей не допускается, а если 1, то разрешается. При успешном выполнении данных процедур ОК устанавливается в TRUE, в противном случае - в FALSE. PrevKey ----------------------------------------------------------------- Данная процедура используется для возврата номера записи в файле данных, связанной с предыдущим ключем в индексном файле. Она объявляется следующим образом: procedure PrevKey(var ifile: IndexFile, var RecNum: integer, var key); После обращения к процедуре параметр RecNum будет содержать номер записи в файле данных, соответствующей предыдущему ключу в ин- дексном файле. Переменной key будет присвоено значение предыдуще- го ключа. При успешном выполнении данной процедуры ОК устанавли- вается в TRUE, в противном случае - в FALSE. PutRec ----------------------------------------------------------------- Данная процедура помещает запись информации с заданным номе- ром в файл данных. Она объявляется следующим образом: procedure PutRec(var dfile: DataFile, var RecNum:integer, var buffer); Информация из buffer помещается в запись с номером RecNum. Пере- менная buffer должна иметь размер соответствующий длине записи. SearchKey ----------------------------------------------------------------- Данная процедура используется для поиска в индексном файле первого ключа, который не меньше ключа, заданного в вызове проце- дуры. Она объявляется следующим образом: procedurte SearchKey(var ifile: IndexFile, var RecNum: integer, var key); При успешном выполнении процедура возвращает номер записи в файле данных, связанной с ключем в RecNum и устанавливает ОК в TRUE. В противном случае ОК устанавливается в FALSE. UsedRec ----------------------------------------------------------------- Данная процедура возвращает число записей в файле данных, Работа с Турбо Паскалем #2/2 = 105 = содержащих истинные данные. Уничтоженные записи не подсчитывают- ся. Процедура объявляется следующим образом: function UsedRecs(var dfile: DataFile): integer; Работа с Турбо Паскалем #2/2 = 106 = ПРИМЕР ПРОСТОГО ПОЧТОВОГО СПИСКА ----------------------------------------------------------------- В качестве примера использования Turbo Access будет заново с применением его возможностей написана программа ведения простого почтового списка, разработанная в главе 2, которая использовала связанные ссылки. Во-первых, нам необходимо найти размер записи, используемой для хранения адресной информации, а затем вы приме- ните программу SETCONST для вычисления соответствующих значений шести констант, требующихся для процедур Turbo Access. Так как Turbo Access использует первые два байта в каждой записи в качестве флага индикации удаления, необходимо добавить поле целой переменной к исходной адресной записи. Кроме того, по- ля "предыдущий" и "следующий" больше не используются. Результиру- ющая запись выглядит следующим образом: type address = record status: integer; { используется Turbo Access } name: string[30]; street: string[40]; city: string[20]; state: string[2]; zip: string[9]; end; Длина в байтах поля adress, которая найдена, используя SizeOf, равна 108. Вам нужно будет данное число при выполнении программы SETCONST.PAS. Программа SETCONST.PAS определяет значения констант, необхо- димых для Turbo Access. При запуске программы появляется экран, аналогичный показанному на рис.9-2, со значениями, принимаемыми по умолчанию. Руководство пользователя по инструментарию баз дан- ных говорит, что для большинства применений вам необходимо изме- нить только длину записи и длину ключа, чтобы подстроиться под ваши особенности. Необходимо также изменить число записей, запо- минаемых в базе данных, если оно превышает 10000. Размер страницы и размер стека страниц изменять не надо. Длина записи adress рав- на 108, так что сначала надо ввести это значение. В данном приме- ре поле name будет использоваться в качестве ключа, поэтому зна- чение 30 вводится как длина ключа. Клавишу RETURN нажать для оставшихся полей. Когда информация введена, SETCONT.PAS отобража- ет экран, показанный на рис.9 -3. При выходе из SETCONST.PAS вы можете автоматически создать соответствующую декларацию const. Декларация для программы Работа с Турбо Паскалем #2/2 = 107 = ** 1 - рабочий лист задания констант Turbo Access, версия 1.10А ** размер записи данных (байт) 200 200 длина строки ключа (символов) 10 размер базы данных (записей) 10000 размер страницы (ключей) 24 размер стека страниц (страниц) 10 плотность (процент используемых элементов в средней странице) 50% 75% 100% общее количество страниц индексного файла память, используемая для стека страниц (байт) размер страницы индексного файла (байт) размер индексного файла (байт) размер файла данных (байт) порядок максимальная высота среднее количество просмотров, необходимое для нахождения ключа среднее количество просмотров, удовлетворяемое стеком страниц среднее количество просмотров на диске для нахождения ключа нажмите ESC для завершения программы Рис.9-2. Начальный экран SETCONST.PAS ** Turbo Access constant determination worksheet, Version 1.10A Data record size (bytes) 108 Key string Iength (characters) 30 Size of the database (records) 10000 Page size (keys) 24 Page stack size (pages) 10 Density (Percent of Items in use per average Page)50% 75% 100% Total Index file pages 834 556 417 Memory used for page stack (bytes) 8430 8430 8430 Index file page size (bytes) 843 843 843 Index file size (bytes) Data file size (bytes) Order 12 12 12 MaxHeight 4 4 3 Average searches needed to find a key 3.71 3.19 2.90 Average searches satisfied by page stack 1.75 1.50 1.38 Average disk searches needed to find a key 1.96 1.69 1.52 ESC to end program Работа с Турбо Паскалем #2/2 = 108 = Рис.9-3. Экран SETCONST.PAS с вычисленными значениями: почтового списка показаны ниже. Комментарии даны автором. Const {данные константы сгенерированы программой SETCONST.PAS, предоставляемой инструментарием баз данных. } MaxDataRecSize = 108; MaxKeyLen = 30; PageSize = 24; Order = 12; PageStackSize = 10; MaxHeight = 4; данные константы сгенерированы программой SETCONST.PAS, предос- тавляемой инструментарием баз данных. С данной информацией и включенными необходимыми файлами Turbo Access первая часть программы почтового списка выглядит следующим образом: program db_example; Const {данные константы сгенерированы программой SETCONST.PAS, предоставляемой инструментарием баз данных. } MaxDataRecSize = 108; MaxKeyLen = 30; PageSize = 24; Order = 12; PageStackSize = 10; MaxHeight = 4; type address = record status: integer; {используется Turbo Access } name: string[30]; street: string[40]; city: string[20]; state: string[2]; zip: string[9]; end; {следующие файлы содержат процедуры баз данных} {$i access.box} {основные процедуры баз данных} {$i addkey.box} {добавить элементы } {$i delkey.box} {удалить элементы } {$i getkey.box} {поиск по дереву } var dbfile: DataFile; ifile: IndexFile; done: boolean; В основном тексте программы, показанном далее, сначала ини- циализируется таблица индексов с помощью процедуры InitIndex. Да- лее либо открываются, либо создаются соответствующие файлы данных и индексный. Основной цикл программы аналогичен тому, который разработан в главе 2, и позволяет пользователю выбрать различные опции. При завершении файлы данных и индексный закрываются. begin InitIndex; OpenFile(dbfile, 'mail.lst', SizeOf(address)); if not OK then begin WriteLn('creating new data file'); MakeFile(dbfile, 'mail.lst', SizeOf(address)); end; OpenIndex(ifile, 'mail.ndx', 30, 0); if not OK then begin WriteLn('creating new index file'); MakeIndex(ifile, 'mail.ndx', 30, 0); end; done:=false; repeat case MenuSelect of '1': Enter; '2': Remove; '3': ListAll; '4': Search; '5': Update; '6': done:=true; end; until done; CloseFile(dbfile); CloseIndex(ifile); end. Отметим, что больше не необходимо явно загружать и сохранять почтовый список: процедуры Turbo Access объявляют файлы автомати- чески. Процедура Enter, показанная далее, вводит адресную информа- цию, запоминает ее в файле данных и помещает ключ в индексный файл. Отметим, что поле status каждой записи установлено в 0. По соглашению Turbo Access использует 0 для обозначения активной за- писи, а не нулевое значение для обозначения уничтоженных элемен- тов. Работа с Турбо Паскалем #2/2 = 110 = {добавить адрес к списку} procedure Enter; var done: boolean; recnum: integer; Работа с Турбо Паскалем #2/2 = 109 = temp: string[30]; info: address; begin done:=FALSE; repeat Write('Enter name:'); Read(info.name); WriteLn; if Length(info.name)=0 then done:=TRUE else begin Write('Enter street: '); Read(info.street); WriteLn; Write('Enter city: '); Read(info.city); WriteLn; Write('Enter state: '); Read(info.state); WriteLn; Write('Enter zip: '); Read(info.zip); WriteLn; info.status:=0; {сделать активной } FindKey(ifile, recnum, info.name); if not OK then {убедитесь, что нет дублированных ключей } begin AddRec(dbfile, recnum, info); AddKey(ifile, recnum, info.name); end else WriteLn('Duplicate key ignored'); end; until done; end; {Enter} Как вы видите, данная процедура осуществляет проверку на дублирование ключей. Так как дублированные имена не допускаются, процедура, во-первых, проверяет, соответствует ли новый ключ ка- кому-либо уже существующему в файле. Если это так, то он игнори- руется. Порядок вызова AddRec и AddKey критичен, так как перемен- ная RecNum должна быть сначала установлена процедурой AddRec и затем запомнена процедурой AddKey (Помните, что номер записи фай- ла данных связан с ключем в индексном файле и используется для последующего нахождения данных). Процедура ListAll рассчитывает все содержимое почтового списка: procedure ListAll; Работа с Турбо Паскалем #2/2 = 111 = var info: address; len, recnum: integer; begin len: = filelen(dofile) -1; for recnum:=1 to len do begin GetRec(dbfile, recnum, info); {display if not deleter} if info.status = 0 then display(info); end; end; {ListAll} Процедура FileLen возвращает число записей активных или уничтоженных в файле данных, включая первую запись, которая заре- зервирована для использования Turbo Access. Следовательно, дейс- твительное число пользовательских записей на единицу меньше. Кро- ме того, из-за того, что некоторые записи могут быть уничтожены, необходимо проверять поле status до отображения информации. Поиск определенного адреса включает в себя поиск ключа в ин- дексном файле с помощью процедуры FindKey. Когда ключ найден, возвращается соответствующий номер записи в файле данных и он ис- пользуется процедурой GetRec для получения нужной информации. Процедура Search, показанная далее реализует данный подход: {найти заданный элемент} procedure Search; var name: string[30]; recnum: integer; info: address; begin Write('Enter name: '); ReadLn(name); {найти ключ,если он существует} FindKey(ifile, recnum, name); if OK then { если найден } begin GetRec(dbfile, recnum, info); {display if not deleter} if info.status = 0 then Display(info); end else WriteLn('not found'); end; {Search} Наконец, модификация существующей записи предполагает, что вы должны сначала найти запись, считать ее, модифицировать и за- писать обратно в файл данных. Процедура Update иллюстрирует прос- той метод обновления, в котором пользователь должен ввести заново всю информацию. Более сложные подходы требуют перевода только из- Работа с Турбо Паскалем #2/2 = 112 = меняемых полей. { изменение адреса в списке, исключая поле имени } procedure Update; var done: boolean; recnum: integer; temp: string[30]; info: address; begin Write('Введите имя: '); Read(info.name); WriteLn; FindKey(ifile, recnum, info.name); if OK then begin Write('Введите улицу: )'; Read(info.street); WriteLn; Write('Введите город: '); Read(info.city); WriteLn; Write('Введите штат: '); Read(info.state); WriteLn; Write('Введите индекс: '); Read(info.zip); WriteLn; info.status:=0; {сделать активной} PutRec(dbfile, recnum, info); end else WriteLn('ключ не найден'); end; {Update} Целиком программа, использующая Turbo Access для работы с файлами, выглядит следующим образом: program db_example; Const {данные константы сгенерированы программой SETCONST.PAS, предоставляемой инструментарием баз данных. } MaxDataRecSize = 108; MaxKeyLen = 30; PageSize = 24; Order = 12; PageStackSize = 10; MaxHeight = 4; type address = record status: integer; {используется Turbo Access } name: string[30]; street: string[40]; city: string[20]; state: string[2]; Работа с Турбо Паскалем #2/2 = 113 = zip: string[9]; end; {следующие файлы содержат процедуры баз данных} {$i access.box} {основные процедуры баз данных} {$i addkey.box} {добавить элементы } {$i delkey.box} {удалить элементы } {$i getkey.box} {поиск по дереву } var dbfile: DataFile; ifile: IndexFile; done: boolean; function MenuSelect:char; {возврат пользовательского выбора } var ch:char; begin WriteLn('1. '); WriteLn('2. Удалить имя '); WriteLn('3. Отобразить список'); WriteLn('4. Обновление '); WriteLn('5. Поиск по имени '); WriteLn('6. Выход '); repeat WriteLn; Write('Введите ваш выбор:'); Read(ch); ch:=UpCase(ch); WriteLn; until (ch>='1') and (ch<='6'); MenuSelect:=ch; end; {MenuSelect} {добавить адрес к списку} procedure Enter; var done: boolean; recnum: integer; temp: string[30]; info: address; begin done:=FALSE; repeat Write('Введите имя: '); Read(info.name); WriteLn; if Length(info.name)=0 then done:=TRUE else Работа с Турбо Паскалем #2/2 = 114 = begin Write('Введите улицу: '); Read(info.street); WriteLn; Write('Введите город: '); Read(info.city); WriteLn; Write('Введите штат: '); Read(info.state); WriteLn; Write('Введите индекс: '); Read(info.zip); WriteLn; info.status:=0; {сделать активной} FindKey(ifile, recnum, info.name); if not OK then {убедитесь, что нет дублированных ключей } begin AddRec(dbfile, recnum, info); AddKey(ifile, recnum, info.name); end else WriteLn('Дублированный ключ игнорирован'); end; until done; end; {Enter} { изменение адреса в списке, исключая поле имени } procedure Update; var done: boolean; recnum: integer; temp: string[30]; info: address; begin Write('Введите имя: '); Read(info.name); WriteLn; FindKey(ifile, recnum, info.name); if OK then begin Write('Введите улицу: '); Read(info.street); WriteLn; Write('Введите город: '); Read(info.city); WriteLn; Write('Введите штат: '); Read(info.state); WriteLn; Write('Введите индекс: '); Read(info.zip); WriteLn; info.status:=0; {сделать активной} PutRec(dbfile, recnum, info); end else WriteLn('ключ не найден'); end; {Update} {удалить адрес из списка } Работа с Турбо Паскалем #2/2 = 115 = procedure Remove; var recnum: integer; name: string[30]; info: address; begin Write('Введите имя для удаления : '); Read(name); WriteLn; FindKey(ifile, recnum, name); if OK then begin DeleteRec(dbfile, recnum); DeleteKey(ifile, recnum, name); end else WriteLn('Не найдено'); end; {Remove} procedure Display(info: address); begin WriteLn(info.name); WriteLn(info.street); WriteLn(info.city); WriteLn(info.state); WriteLn(info.zip); WriteLn; end; {Display} procedure ListAll; var info: address; len, recnum: integer; begin len := fileLen(dbfile) -1; for recnum:=1 to len do begin GetRec(dbfile, recnum, info); {отобразить, если не уничтожен} if info.status = 0 then display(info); end; end; {ListAll} {Найти заданный элемент } procedure Search; var name: string[30]; recnum: integer; info: address; begin Write('Введите имя: '); ReadLn(name); Работа с Турбо Паскалем #2/2 = 116 = {найти ключ, если существует} FindKey(ifile, recnum, name); if OK then begin GetRec(dbfile, recnum, info); {отобразить, если не уничтожен} if info.status = 0 then Display(info); end else WriteLn('не найден'); end; {Search} begin InitIndex; OpenFile(dbfile, 'mail.lst', SizeOf(address)); if not OK then begin WriteLn('Cоздание нового файла'); MakeFile(dbfile, 'mail.lst', SizeOf(address)); end; OpenIndex(ifile, 'mail.ndx', 30, 0); if not OK then begin WriteLn('Cоздание нового файла '); MakeIndex(ifile, 'mail.ndx', 30, 0); end; done:=false; repeat case MenuSelect of '1': Enter; '2': Remove; '3': ListAll; '4': Search; '5': Update; '6': done:=true; end; until done; CloseFile(dbfile); CloseIndex(ifile); end. Работа с Турбо Паскалем #2/2 = 117 = ПРИМЕР ПРОГРАММЫ ИНВЕНТАРИЗАЦИИ ----------------------------------------------------------------- Для демонстрации того, как легко создать новые прикладные программы при наличии базового набора процедур, рассмотрим прог- рамму инвентаризации. Запись, используемая для хранения информа- ции, выглядит следующим образом type inv = record status: integer; name: string[30]; descript := string[40]; guantity: integer; cost: real; end; Длина ее, найденная с помощью SizeOf, равна 83. Используя данную длину и длину ключа, равную 30, программа SETCONST.PAS создает определение констант Const MaxDataRecSize = 82; MaxKeyLen = 30; PageSize = 24; Order = 12; PageStackSize = 10; MaxHeight = 4; Другие изменения, необходимые для преобразования процедур ведения почтового списка в процедуры инвентаризации, заключаются только в изменениях предложений печати. Целиком программа инвен- таризации выглядит следующим образом: program inventory; Const { данные константы генерируются программой SETCONST.PAS предоставляемой инструментарием баз данных } MaxDataRecSize = 82; MaxKeyLen = 30; PageSize = 24; Order = 12; PageStackSize = 10; MaxHeight = 4; type inv = record status: integer; name: string[30]; descript: string[40]; Работа с Турбо Паскалем #2/2 = 118 = guantity: integer; cost: real; end; {следующие файлы содержат процедуры баз данных} {$i access.box} {основные процедуры баз данных} {$i addkey.box} {добавить элементы } {$i delkey.box} {удалить элементы } {$i getkey.box} {поиск по дереву } var dbfile: DataFile; ifile: IndexFile; done: boolean; function MenuSelect:char; {возврат пользовательского выбора } var ch:char; begin WriteLn('1. Введите элемент '); WriteLn('2. Удалить элемент '); WriteLn('3. Отобразить инвентарный список'); WriteLn('4. Поиск элементов '); WriteLn('5. Обновление '); WriteLn('6. Выход '); repeat WriteLn; Write('Введите ваш выбор: '); Read(ch); ch:=UpCase(ch); WriteLn; until (ch>='1') and (ch<='6'); MenuSelect:=ch; end; {MenuSelect} {добавить элемент к списку} procedure Enter; var done: boolean; recnum: integer; temp: string[30]; info: inv; begin done:=FALSE; repeat Write('Введите имя элемента: '); Read(info.name); WriteLn; Работа с Турбо Паскалем #2/2 = 119 = if Length(info.name)=0 then dont:=TRUE else begin Write('Введите описание: '); Read(info.descript); WriteLn; Write('Введите количество: '); Read(info.guantity); WriteLn; Write('Введите стоимость: '); Read(info.cost); WriteLn; info.status:=0; { сделать активной } FindKey(ifile, recnum, info.name); if not OK then begin AddRec(dbfile, recnum, info); AddKey(ifile, recnum, info.name}; end else WriteLn('дублированный ключ игнорирован'); end; until done; end; {Enter} {изменение элемента в списке с сохранением поля имени} procedure Update; var done: boolean; recnum: integer; temp: string[30]; info: inv; begin Write('Enter item name: '); Read(info.name); WriteLn; FindKey(ifile, recnum, info.name); if OK then begin Write('Введите описание: '); Read(info.descript); WriteLn; Write('Введите количество: '); Read(info.guantity); WriteLn; Write('Введите стоимость: '); Read(info.cost); WriteLn; info.status:=0; info.status:=0; {сделать активной} PutRec(dbfile, recnum, info); end else WriteLn('ключ не найден'); end; {Update} {удалить элемент из инвентарного списка} procedure Remove; Работа с Турбо Паскалем #2/2 = 120 = var recnum: integer; name: string[30]; begin Write('Введите имя уничтожаемого элемента: '); Read(name); WriteLn; FindKey(ifile, recnum, name); if OK then begin DeleteRec(dbfile, recnum); DeleteKey(ifile, recnum, name); end else WriteLn('Не найдено'); end; {Remove} procedure Display(info: inv); begin WriteLn('Item name: ',info.name); WriteLn('Description: ',info.descript); WriteLn('Quantity on hand: ',info.quantity); WriteLn('Initial cost: ',info.cost:10:2); WriteLn; end; {Display} procedure ListAll; var info: inv; len, recnum: integer; begin len := filelen(dbfile) -1; for recnum:=1 to len do begin Getrec(dbfile, recnum, info); if info.status = 0 then display(info); end; end; {ListAll} {поиск элемента} procedure Search; var name: string[30]; recnum: integer; info: inv; begin Write('Введите имя элемента: '); ReadLn(name); {найти ключ, если он существует} Работа с Турбо Паскалем #2/2 = 121 = FindKey(ifile, recnum, name); if OK then {если найден} begin GetRec(dbfile, recnum, info); if info.status = 0 then Display(info); end else WriteLn('не найден'); end; {Search} begin InitIndex; OpenFile(dbfile, 'inv.lst', SizeOf(inv)); if not OK then begin WriteLn('Cоздание нового файла'); MakeFile(dbfile, 'inv.lst', SizeOf(inv)); end; OpenIndex(ifile, 'inv.ndx', 30, 0); if not OK then begin WriteLn('Cоздание нового файла'); MakeIndex(ifile, 'inv.ndx', 30, 0); end; done:=false; repeat case MenuSelect of '1': Enter; '2': Remove; '3': ListAll; '4': Search; '5': Update; '6': done:=true; end; until done; CloseFile(dbfile); CloseIndex(ifile); end. Программа ведения почтового списка и данная программа имеют один базовый скелет. Он может быть модифицирован для различных ситуаций использования баз данных. Работа с Турбо Паскалем #2/2 = 122 = TurboSort ----------------------------------------------------------------- Инструментарий баз данных предоставляет функцию TurboSort, которая является разновидностью QuickSort. Она может быть исполь- зована для сортировки любого типа данных, если их длина равна по меньшей мере двум байтам. QuickSort используется, так как это са- мая быстрая сортировка в большинстве ситуаций, как вы видели в Главе 1. TusboSort находится в файле SORT.BOX, который должен быть включен в каждую программу, использующую ее. TurboSort объ- является следующим образом function TurboSort(ItemSize: integer): integer; ItemSize - это размер данных, которые будут сортироваться; он должен быть вычислен с помощью SizeOf. Значение, возвращаемое TurboSort, интерпретируется, как показано в таблице 9-3. TurboSort может сортировать до 32767 элементов. Хотя функция будет пытаться сортировать их в оперативной памяти для скорости, она будет использовать временный файл на диске, когда это необхо- димо. Таблица 9-3 Коды возврата TurboSort ────────────────────────────────────────────────────────────── Значение Смысл ────────────────────────────────────────────────────────────── 0 Сортировка выполнена успешно 3 В компьютере нет достаточной памяти 8 Длина элемента меньше 2 9 Более 32767 входных элементов для сортировки 10 Ошибка записи на диск 11 Ошибка чтения с диска 12 Нет возможности создать временный файл на диске ────────────────────────────────────────────────────────────── InP, OutP и Less ----------------------------------------------------------------- TusboSort имеет три фазы работы: ввод данных, которые будут сортироваться, сортировка данных и вывод отсортированных данных. Для того, чтобы помочь TurboSort выполнить свою работу, вы должны создать три процедуры, называемые InP, OutP, Less. Данные проце- дуры декларируются как forward с помощью SORT.BOX, но вы должны создать действительную реализацию. Процедура InP используется для передачи TurboSort данных, которые должны сортироваться, по одному элементу за раз. Действи- тельная передача выполняется, используя SortRelease, которая так- же определена в SORT.BOX. Она объявляется следующим образом procedure SortRelease(item); 1 Так как тип параметра item не задан, могут сортироваться данные Работа с Турбо Паскалем #2/2 = 123 = любого типа. Простая процедура InP, которая считывает десять це- лых, введенных с клавиатуры, и передает их TurboSort для сорти- ровки, показана ниже: procedure InP; var f: integer; begin for i:=1 to 10 do ReadLn(data[i]); for i:=1 to 10 do SortRelease(data[i]); end; {InP} Процедура OutP используется для поэлементного чтения отсор- тированных данных из TurboSort с помощью SortReturn, определенной в SORT.BOX. SortReturn объявляется следующим образом: procedure SortReturn(item); Так как тип параметра item не задан, то могут быть возвращены данные любого типа. Процедура OutP может не обладать информацией о том, сколько элементов данных будет возвращаться, поэтому функ- ция SorEOS может быть использована для проверки на конец данных. Следующая простая процедура OutP может быть использована с целыми числами, генерируемыми процедурой InP, показанной ранее: procedure OutP; var data: integer; begin repeat SortReturn(data); write(data,' '); until SortEOS; end; {OutP} Функция Less является наиболее важной из трех рассматривае- мых, так как она вызывается каждый раз, когда сравниваются два элемента. Функция Less возвращает TRUE, если первый аргумент меньше второго. Less декларирована в SORT.BOX, как имеющая два параметра, называемые Х и У, которые вы должны перекрыть с двумя логическими переменными, имеющими тот же самый тип, что и сорти- рованные данные. Перекрытие реализуется с помощью команды alsolute. Например, данная версия Less может быть использована для сравнения двух целых переменных: function Less; var first: char absolute X; second: char absolute Y; begin Less := first < second; end; {Less} В качестве примера связи этих процедур рассмотрим короткую программу, которая считывает десять целых числе, сортирует их и отображает отсортированный список: Работа с Турбо Паскалем #2/2 = 124 = program simple_sort; var data: array [1..10] of integer; result: integer; {$i sort.box} {read in the sort routines} procedure InP; var f: integer; begin for i:=1 to 10 do ReadLn(dara[i]); for i:=1 to 10 do SortRelease(data[i]); end; {InP} function Less; var first: char absolute X; second: char absolute Y; begin Less := first < second; end; {Less} procedure OutP; var data: integer; begin repeat SortReturn(data); write(data, ' '); until SortEOS; end; {OutP} begin result:=TurboSort(sizeOf(integer)); writeLn('sort result; ', result); end. GINST ----------------------------------------------------------------- Программа GINST, предоставляемая инструментарием баз данных, позволяет вам создать программу подготовки терминалов, которая дает возможность пользователям устанавливать свои программы на Турбо Паскале на своих компьютерах. Существует много типов компь- ютеров, терминалов и прочего; программа GINST позволяет вам соз- Работа с Турбо Паскалем #2/2 = 125 = дать программы на Турбо Паскале, которые могут быть установлены в различных условиях. Как таковая данная программа не будет расс- матриваться. Однако, интересующиеся читатели могут обратиться к руководству пользователя инструментария баз данных. Работа с Турбо Паскалем #2/2 = 126 = ГЛАВА 10. ГРАФИЧЕСКИЙ ИСТРУМЕНТАРИЙ ТУРБО ПАСКАЛЯ ----------------------------------------------------------------- Графический инструментарий Турбо Паскаля является необяза- тельным добавляемым для расширения Турбо Паскаля средством, кото- рое представляет большой и разнообразный набор графических проце- дур. Эти процедуры делятся на пять основных категорий: - базовые графические элементы точки, линии, квадраты, круги; - тексты; - окна; - гистограммы; - графики. Количество различных процедур, функций и опций делает невоз- можным рассмотрение всего графического инструментария в данной главе (Руководство по графическому инструментарию имеет объем 256 страниц). Однако, будет дан обзор средств каждой категории с при- мерами. Примеры, данные в этой главе, отражают графический инстру- ментарий, предназначенный для версии 3 Турбо Паскаля. Графический инструментарий, предназначенный для версии 4 и последующих, зна- чительно отличается от предназначенного для более ранних версий. Наиболее существенное отличие инструментария, предназначенного для версии 4, состоит в том, что процедуры содержаться в блоках, которые связываются с вашей программой, а не включаются в нее с помощью директивы компилятора SI, как в версии 3 инструментария. Работа с Турбо Паскалем #2/2 = 127 = ТРЕБОВАНИЯ К АППАРАТНОМУ ОБЕСПЕЧЕНИЮ ----------------------------------------------------------------- Для персонального компьютера IBM PC и совместимых с ним про- цедуры графического инструментария требуют, чтобы у вас в системе был один из следующих графических аппаратов: - CGA (цветной графический адаптер); - EGA (расширенный графический адаптер); - монохромный графический адаптер Hercules. Вы можете настроить вашу копию графического инструментария в со- ответствии с инструкцией в руководстве пользователя графического инструментария для конкретного типа графического адаптера вашей системы. Кроме того, для функционирования инструментария в систе- ме необходима память по крайней мере 192 Кбайта. СИСТЕМЫ КООРДИНАТ ----------------------------------------------------------------- Все графические процедуры в инструментарии используют две отдельные координатные системы. Первая называется абсолютной ко- ординатной системой, а вторая - планетной координатной системой. Абсолютная координатная система определяется используемым вами графическим адаптером. Она представляется числом пикселей в горизонтальном и вертикальном направлениях (Пиксель - это наи- меньшая адресуемая точка на экране). Например, CGA в режиме 6 имеет 640 пикселов в ширину и 200 в высоту. Процедуры инструмен- тария используют координатную систему Х-Y, где ось Х представляет горизонтальное направление, а ось Y - вертикальное. По соглашению верхний левый угол экрана имеет координаты (0,0) и в случае конт- роллера CGA левый нижний угол имеет координаты (639,200). Хотя вы можете использовать абсолютную координатную систему, это делается редко из-за больших преимуществ планетной координатной системы. Планетная координатная система задается процедурой DefineWold, которая используется для определения начальной и ко- нечной точек координатной системы. Например, строка DefineWord(1, 0, 0, 1000, 1000); определяет координатную систему для планеты номер 1. Она делает 0,0 левым верхним углом и 1000,1000 правым нижним. Когда это сде- лано все графические процедуры в инструментарии будут настроены на координатную систему. Это позволяет создавать графические программы без учета того, какое графическое оборудование будет использоваться, таким образом давая большую независимость от ап- паратуры. Та же самая программа может быть скомпилирована для ра- боты либо с контроллером EGA в режиме 640х350, либо с CGA с раз- решением 640х200 без изменений. Работа с Турбо Паскалем #2/2 = 128 = Для использования планетной координатной системы требуется трехступенчатый процесс. Во-первых, вы должны осуществить задание параметров планеты с помощью DefineWold. Во-вторых, вы должны выбрать одну из предварительно заданных планет с помощью SelectWold. Наконец, вы должны выбрать окно в планете с помощью SelectWindom. Процедура объявляется следующим образом: procedure DefineWorld(WorlidNum:integer,StartX,StartY,EndX, EndY: real); procedure SelectWorid(WoridNum: integer); procedure SelectWindom(WindomNum: integer); WorldNum - это номер планеты, а WindomNum - номер окна. StartX и StartY определяют координаты верхнего левого угла, а EndX и EndY - правого нижнего угла. Окно, которое вы выбрали, должно иметь тот же номер, что и номер планеты. Следующий фрагмент кода определяет две планеты и выбирает первую из них в качестве текущей среды: DefineWorld(1, 0, 0, 1000, 1000); DefineWorld(2, 0, 0, 2000, 2000); SelectWorld(1); SelectWorld(2); После данной последовательности процедуры инструментария бу- дет работать в координатном пространстве 1000х1000. Следующим преимуществом планетной координатной системы явля- ется то, что она позволяет увеличивать масштаб изображения. Это реализуется уменьшением координат планеты при неизменности всех других параметров. Примеры этого будут даны позднее. Работа с Турбо Паскалем #2/2 = 129 = ТРЕБОВАНИЯ ПО ИНИЦИАЛИЗАЦИИ ----------------------------------------------------------------- Файлы typed.sys, graphix.sys, kernel.sys должны быть включе- ны в любую программу, которая использует графический инструмента- рий. Порядок включения важен и должен быть следующим: {Si typedef.sys} {Si graphix.sys} {Si kernel.sys} Прежде, чем можно будет использовать какую-либо процедуру инстру- ментария, должна быть вызвана процедура InitGraphic для инициали- зации графической системы. После завершения использования графики вы должны вызвать процедуру LeaveGraphic для сброса экрана в при- нимаемый по умолчанию текстовый режим. Работа с Турбо Паскалем #2/2 = 130 = БАЗОВЫЕ ГРАФИЧЕСКИЕ ЭЛЕМЕНТЫ ----------------------------------------------------------------- Графический инструментарий представляет процедуры реализации базовых графических элементов, которые на первый взгляд дублируют аналогичные процедуры, имеющиеся в Турбо Паскале. Однако, это не так. Процедуры инструментария могут работать в планетной коорди- натной системе, тогда как соответствующие процедуры Турбо Паскаля не могут. Процедуры реализации базовых графических процедур опи- саны в таблице 10-1. Таблица 10-1 Процедуры реализации базовых графических элементов ───────────────────────────────────────────────────────────────── Имя Функция ───────────────────────────────────────────────────────────────── DrawPoint Нарисовать точку в заданном месте DrawLine Нарисовать линию в заданном месте DrawSguare Нарисовать квадрат в заданном месте DrawCircle Нарисовать круг в заданном месте DrawCircleSegment Нарисовать круг в заданном месте DrawCircleDirect Нарисовать круг, используя абсолютные координаты SetAspect Установить коэффициент взгляда для процедур реализации круга GetAspect Возврат к текущему коэффициенту взгляда ───────────────────────────────────────────────────────────────── Следующая простая программа выбирает планету и окно и рисует круги, квадраты и линию. Результаты показаны на рис.10-1. program simple_graphics; {Si typedef.sys} {Si graphix.sys} {Si kernel.sys} var radius: real; i:integer; begin InitGraphic; DefineWorld(1, 0, 0, 1000, 1000); SelectWorld(1); SelectWindom(1); DrawBorder; {установить границу вокруг окна } SetAspect(1); Работа с Турбо Паскалем #2/2 = 131 = radius: = 0.05; for i: = 1 to 10 do begin DrawCircle(500,500,radius); radius: = radius + 0.2; end; repeat until KeyPressed; ReadLn; DrawSguare(100, 100, 900, 900, false); DrawSguare(400, 400, 600, 600, false); repeat until KeyPressed; ReadLn; DrawLine(0, 0, 1000, 1000); repeat until KeyPressed; LeaveGraphic; end. Процедура SetAspect используется для установки коэффициента взгляда, который определяет способ рисования круга. Любое значе- ние, отличное от 1, будет порождать элипс вместо круга. Процедура DrawBorder помещает границу вокруг активного окна. Работа с Турбо Паскалем #2/2 = 132 = ┌─────────────────────────────────────────────────┐ │ ┌───────────────────────────────────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───────────────────────────────────────────┘ │ └─────────────────────────────────────────────────┘ Рис.10-1. Круги, квадраты и линии в области 1000х1000 Чтобы понять влияние планетных координат, изменим предложе- ние DefineWold и запустим программу снова. Окончательное отобра- жение будет выглядеть, как показано на рис.10-2. Отметим, что не все круги влезли в область 2000х2000. Когда что-нибудь не влезает в область, процедуры инструментария автоматически "отстригают" его по краю. ┌──────────────────────────────────────────────────┐ │ ┌────────────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────────────────────────────────────────────────┘ Рис.10-2. Круги, квадраты и линия в области 2000х2000 Работа с Турбо Паскалем #2/2 = 133 = ГРАФИЧЕСКИЕ ТЕКСТОВЫЕ ПРОЦЕДУРЫ ----------------------------------------------------------------- Графический инструментарий представляет два метода отображения текста на экране графического дисплея. Первый метод основывается на использовании стандартных процедур ввода/вывода Write и WriteLn, которые отображают машинно зависимый набор символов. Этот набор определяется аппаратным обеспечением компьютера и со- держит типы символов, которые вы обычно используете. Однако, инс- трументарий также позволяет вам отображать машинно независимые символы, используя процедуры DrawText и DrawTextW, которые отоб- ражают буквы переменного размера на графическом экране или в ок- не. Машинно независимые процедуры наиболее интересны. Машинно-независимый набор символов ----------------------------------------------------------------- Каждый машинно-независимый символ конструируется, используя матрицу 4х6 пикселов. Например, буква "Е" конструируется, как это показано на рис.10-3. Так как машинно-независимый набор символов создается с помощью графического инструментария, то можно изме- нять размер букв, используя масштабирующий коэффициент. ───────────────────────────────────────── ┌──┬──┬──┬──┐ ├──┼──┼──┼──┤ ├──┼──┼──┼──┤ ├──┼──┼──┼──┤ ├──┼──┼──┼──┤ ├──┼──┼──┼──┤ └──┴──┴──┴──┘ ──────────────────────────────────────── Рис.10-3. Построение буквы "Е" в матрице 4х6 пикселов Работа с Турбо Паскалем #2/2 = 134 = DrawText и DrawTextW ----------------------------------------------------------------- Процедуры DrawText и DrawTextW объявляются следующим образом: procedure DrawText(X, Y, Scale: integer, Msg: WrkString); procedure DrawTextW(X, Y, Scale: integer, Msg: WrkString); Данные процедуры выводят сообщение Msg, начиная с координат X, Y в масштабе, заданном параметром Scale. WrkString декларируется инструментарием, как строка максимальной длины. Однако, вы можете использовать любой тип, который пожелаете. Простая программа, показанная далее, отображает примеры пер- вых шести размеров шрифта. program text_graphics; {Si typedef.sys} {Si graphix.sys} {Si kernel.sys} var i:integer; begin InitGraphic; DefineWorld(1, 0, 0, 1000, 1000); SelectWorld(1); SelectWindom(1); DrawBorder; for i: = 1 to 6 do begin DrawTextW(10, i*140, i, 'Это тест') end; repeat until KeyPressed; LeaveGraphic; end. Главным преимуществом применения машинно-независимых симво- лов является то, что их размер может быть изменен для удовлетво- рения специфическим нуждам вашей программы. Работа с Турбо Паскалем #2/2 = 135 = ОКНА ----------------------------------------------------------------- Графический инструментарий позволяет вам создавать и обраба- тывать одно или несколько окон. Каждое окно может быть связано со своей отдельной планетной координатной системой. Кроме того, окно может иметь заголовок и быть заключенным в границы. Две основные процедуры используются для работы с окнами: DefincWindow и SelectWindow. Они декларируются следующим образом: procedure DefineWindow(WindomNum, X1, Y1, X2, Y2: integer); procedure SelectWindow(WindomNum: integer); WindoNum - это номер окна. В процедуре DefineWindow параметры X1, Y1 определяют местоположение верхнего левого угла, а X2, Y2 - правого нижнего угла. Одним необычным аспектом процедуры DefineWindow является измерение координаты Х единицами, равными 8 пикселам. Следовательно, предложение Defin_Window(1, 0, 0, 10, 10); задает окно, которое имеет 10 пикселов высоты и 80 пикселов шири- ны (такой подход используется, так как все окна должны быть вы- равнены в памяти отображения на границу байта). Для связи планеты с окном необходимо придерживаться следую- щей последовательности: 1. выбрать планету; 2. выбрать окно. Для отображения сообщения в заголовке вы должны сначала связать заголовок с окном, а затем заголовок в соответствие "включено". Для реализации этого используйте процедуры DefineHeader и SetHeaderOn, которые декларируют следующим образом: procedure DefineHeader(WindowNum: integer, Msg: WrkString); procedure SetHeaderOn; Вызов DrawBorder помещает границу вокруг активного окна. Данная процедура не имеет параметров. Следующая программа иллюстрирует правильный порядок вызова различных процедур работы с окном для того, чтобы активизировать окно с границей и заголовком. Ее вывод показан на рис.10-5. program One_Window; {Si typedef.sys} {Si graphix.sys} {Si kernel.sys} var i:integer; begin InitGraphic; DefineWorld(1, 0, 0, 1000, 1000); DefineWindow(1, 20, 20, 40, 100); Работа с Турбо Паскалем #2/2 = 136 = DefineHeader(1, 'Заголовок'); SetHeaderOn; SekectWorld(1); SekectWindow(1); DrawBorder; repeat until KeyPressed; LeaveGraphic; end. ────────────────────────────────────────────────── ┌────────────────┐ └────────────────┘ ┌────────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────┘ ─────────────────────────────────────────────────── Рис.10-5. Простое окно с границей и заголовком Работа с Турбо Паскалем #2/2 = 137 = Увеличение масштаба изображения ----------------------------------------------------------------- Созданием окон с различными координатами вы можете добиться эффекта увеличения масштаба графического отображения. Рассмотрим следующее предложение для рисования линии: DrawLine(0, 0, 100, 100); Если активная планета, в которой рисуется данная линия, задана следующим образом DefineWorld(1, 0, 0, 100, 100); то линия будет идти диагонально из одного угла в другой. Однако, если планета будет задана, как DefineWorld(1, 0, 0, 200, 200); то та же самая линия будет проходить половину дистанции из одного угла в другой. Графические объекты будут всегда заполнять экран в пропорции к системе координат планеты. В большей планете объект будет выглядеть меньше, а в меньшей будет выглядеть больше. Следующая программа создает эффект увеличения масштаба изоб- ражения в углу квадрата. Вывод программы показан на рис.10-6. ─────────────────────────────────────────────────────── ┌─────────────┐ └─────────────┘ ┌─────────────┐ │┌──┐ │ ││ │ ├────────────┐ │└──┘ ├────────────┘ │ ├────────────┐ │ │ ┌─────┐ │ │ │ │ │ ├────────────┐ └─────────────┤ │ │ ├────────────┘ │ └─────┘ ├────────────┐ │ │ ┌─────────┤ │ │ │ │ └────────────┤ │ │ │ │ │ │ │ │ │ │ └──┴─────────┘ Рис.10-6. Эффект увеличения масштаба изображения за счет ис- пользования окна program Windows; {Si typedef.sys} {Si graphix.sys} {Si kernel.sys} Работа с Турбо Паскалем #2/2 = 138 = {Si windows.sys} var i: integer; procedure SetUpWindow; begin DefineWindow(1, 0, 0, 20, 100); DefineHeader(1, 'Sguare'); DefineWorld(1, 0, 0, 400, 400); SelectWorld(1); SelectWindow(1); SetHeaderOn; SetBackground(0); DrawBorder; DefineWindow(2, 20, 40, 40, 140); DefineHeader(2, 'Zoom in a little'); DefineWorld(2, 0, 0, 200, 200); SelectWorld(2); SekectWindow(2); SetHeaderOn; SetBackground(0); DrawBorder; DefineWindow(3, 40, 80, 60, 180); DefineHeader(3, 'Ah...much clearer'); DefineWorld(3, 0, 0, 100, 100); SekectWorld(3); SelectWindow(3); SetHeaderOn; SetBackground(0); DrawBorder; end; begin InitGraphic; SetUpWindow; {появиться для увеличения в углу} for i: = 1 to 3 do begin SelectWorld(1); SelectWindow(1); DrawSguare(10, 10, 120, 120, false); DrawLine(10, 10, 20, 20); end; Работа с Турбо Паскалем #2/2 = 139 = repeat until KeyPressed; LeaveGraphic; end. Работа с Турбо Паскалем #2/2 = 140 = ГИСТОГРАММЫ И КРУГОВЫЕ ДИАГРАММЫ ----------------------------------------------------------------- Графический инструментарий предоставляет процедуры, позволя- ющие вам нарисовать гистограммы и круговые диаграммы. Существуют две процедуры для реализации круговых диаграмм: DrawPolarPie и DrawCartPie. Они отличаются способом задания местоположения и ра- диуса круговой диаграммы: в полярных или декартовых координатах. Легче пользоваться процедурой DrawPolarPie. Она задается следую- щим образом procedure DrawPolarPie(X, Y, Radius, Theta, Inner, Outer: real; Info: PieArray; Num, Option, TextSize: integer); X,Y - координаты центра, radius - радиус круговой диаграммы и Theta - угол (в градусах) первого сегмента. Параметр Inner и Outer определяют длину линии метки указателя. Info - это матрица типа PieArray, которая определяется следующим образом: type PieType = record Area: real; Text: wrkstring; end; PieArray = array[1..MaxPiesGlb] of PieType; Параметр Num должен определять число сегментов, а Option устанав- ливается в соответствии со следующей таблицей: Значение Option Смысл 0 Нет меток 1 Только текстовые метки 2 Метка из текста и значений 3 Метки только из значений Наконец, параметр TextSize определяет размер машинно-неза- висимых символов, используемых в метках. Программисты, которые используют процедуру DrawPolarPie, должны также включить следую- щие файлы в указанном порядке: {Si circsegm.hgh} {Si pie.hgh} Для использования DrawPolarPie вы должны сначала загрузить 0 значения сегментов в Info.Area и метки, связанные с этими сегмен- тами в Info.Text. Наконец, вы присваиваете соответствующие значе- Работа с Турбо Паскалем #2/2 = 141 = ния остальным параметрам и рисуете круговую диаграмму. Процедура, следующая далее, рисует программу, показанную на рис.10-7. procedure PlotPie; var Radius, Theta, InRad, OuterRad: real; Mode, Size: integer; Products: PieArray; begin DefineWindow(1, 0, 0, 50, 100); DefineHeader(1, 'Exports in Billions'); DefineWorld(1, 0, 0, 1000, 1000); SelectWorld(1); SelectWindow(1); SetHeaderOn; DrawBorder; Products[1].Text: = 'Wheat S'; Products[2].Text: = 'Corn S'; Products[3].Text: = 'Manu. Goods S'; Products[4].Text: = 'Electronics S'; Products[5].Text: = 'Misc. S'; Products[1].Area: = 15; Products[2].Area: = 12.4; Products[3].Area: = 7.34; Products[4].Area: = -24; {вытолкнуть} Products[5].Area: = 16; Radius: = 125; Theta: = 60; SetAspect(1.0); InRad: = 0.85; DuterRad: = 1.5; Mode: = 2; {установить рисование обеих меток} Size: = 1; {установить малый размер текста} DrawPolarPie(500, 500, Radius, Theta, InRad, OuterRad, Products, 5, Mode, Size); end; {PlotPie} Для того, чтобы нарисовать гистограмму, требуется использо- вать процедуру DrawHistogram, которая декларируется следующим об- разом: Работа с Турбо Паскалем #2/2 = 142 = procedure DrawHictogram(Info: PlotArray; Num: integer; Hatch: Boolean; HatchStyle:integer); Параметр Info содержит значение, связанное с каждой полосой. ─────────────────────────────────────────────────────── ┌───────────────────────────┐ └───────────────────────────┘ ┌───────────────────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ └───────────────────────────┘ ┌───────────────────┐ └───────────────────┘ ┌───────────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ └───────────────────┘ Рис.10-7. Круговая диаграмма и гистограмма Это двумерная матрица, где для любого Info[i,1] зарезервиро- ван для внутреннего использования, а Info[i,2] должен содержать значение полосы для i. Если параметр Hatch равен TRUE, то каждая полоса заштриховывается. Параметр HatchStyle определяет частоту штрихования; значение 1 задает самую частую штриховку. Нельзя ри- совать метки непосредственно используя процедуру DrawHistogram. Вы должны включить следующие файлы в порядке, показанном да- лее, в любую программу, которая использует DrawHistogram: {Si hatch.hgh} {Si histogrm.hgh} Процедура, показанная далее, иллюстрирует использование DrawHistogram. Она порождает гистограмму, показанную на рис.10-7. procedure PlotBar; var Products: PlotArray; begin DefineWindow(2, 40, 110, 70, 180); DefineHeader(2, 'Exports in Billions'); Работа с Турбо Паскалем #2/2 = 143 = DefineWorld(2, 0, 0, 30, 35); SelectWorld(2); SelectWindow(2); SetHeaderOn; SetBackground(0); DrawBorder; Products[1,2]: = 15; Products[2,2]: = 12.4; Products[3,2]: = 7.34; Products[4,2]: = 24; Products[5,2]: = 16; DrawHistogram(Products, 5, true, 5); DrawTextW(1, 2, 1, 'Wheat Corn Manu. Elec. Misc.'); DrawTextW(1, 7, 1, 'S15 S12.4 S7.34 S24 S16'); end; {PlotBar} Отметим, что метки естественным образом включаются с помощью процедуры DrawTextW. Для вашего удобства гистограмма и круговая диаграмма показа- ны далее в одной программе: program Presentation_graphics; {Si typedef.sys} {Si graphix.sys} {Si kernel.sys} {Si circsegm.hgh} {Si pie.hgh} {Si hatch.hgh} {Si histogrm.hgh} procedure PlotPie; var Radius, Theta, InRad, OuterRad: real; Mode, Size: integer; Products: PieArray; begin ClearScreen; SetColorWhite; DefineWindom(1, 0, 0, 50, 100); DefineHeader(1, 'Exports in Billions'); DefineWorld(1, 0, 0, 1000, 1000); SelectWorld(1); SelectWindom(1); SetHeaderOn; Работа с Турбо Паскалем #2/2 = 144 = SetBackground(0); DrawBorder; Products[1].Text:='Wheat S'; Products[2].Text:='Corn S'; Products[3].Text:='Manu. Goods S'; Products[4].Text:='Electronics S'; Products[5].Text:='Mics. S'; Products[1].Area:=15; Products[2].Area:=12.4; Products[3].Area:=7.34; Products[4].Area:=-24; {вытолкнуть} Products[5].Area:=16; Radius: = 125; Theta: = 60; SetAspect(1.0); InRad: = 0.85; OuterRad: = 1.5; Mode:=2; {установить рисование обеих меток} Size:=1; { установить малый размер текста } DrawPolarPie(500, 500, Radius, Thete, InRad, OuterRad,Products, 5,Mode, Size); end; {PlotPie} procedure PlotBar; var Products: PlotArray; begin DefineWindow(2, 40, 110, 70, 180); DefineHeader(2, 'Exports in Billions'); DefineWorld(2, 0, 0, 30, 35); 1 SelectWorld(2); SelectWindow(2); SetHeaderOn; SetBackground(0); DrawBorder; Products[1,2]: = 15; Products[2,2]: = 12.4; Products[3,2]: = 7.34; Products[4,2]: = 24; Products[5,2]: = 16; DrawHistogram(Products, 5, true, 5); Работа с Турбо Паскалем #2/2 = 145 = DrawTextW(1, 2, 1, 'Wheat Corn Manu. Elet. Mias.'); DarwTextW(1, 7, 1, S15 S12.4 S7.34 S24 S16'); end; {PlotBar} begin InitGraphic; PlotPie; PlotBar; repeat untei KeyPressed; LeaveGraphic; end. Работа с Турбо Паскалем #2/2 = 146 = РИСОВАНИЕ ГРАФИКОВ ----------------------------------------------------------------- Графический инструментарий содержит прекрасный набор проце- дур рисования графиков и сглаживания. Далее будут рассмотрены две процедуры DrawPoly и Spline. Процедура DrawPoly используется для рисования на экране лю- бой произвольной кривой по ее конечным точкам. Она объявляется следующим образом: procedure DrawPole(Info: PlotArray;Start, End, Code, Scale, Line: integer); Info - это матрица рисования, которая содержит координаты каждой рисуемой точки. Start и End - это матрица индексов первой и пос- ледней точки. Параметр Code определяет символ, который будет ис- пользоваться для рисования точки. Он имеет следующие значения: Код Смысл 0 Используется линия между точками 1 + 2 х Так как точки генерируются с помощью генератора случайных чисел Random, каждый прогон процедуры порождает особую кривую. Работа с Турбо Паскалем #2/2 = 147 = ГЛАВА 11. ЭФФЕКТИВНОСТЬ, МОБИЛЬНОСТЬ И ОТЛАДКА ----------------------------------------------------------------- Способность писать программы, которые эффективно используют системные ресурсы, переносимы на другие компьютеры и свободны от ошибок - это признак профессионального программиста. В данной главе рассматриваются некоторые методы, с помощью которых дости- гается эффективность, мобильность и отладка программ. ЭФФЕКТИВНОСТЬ ----------------------------------------------------------------- В применении к программам термин эффективность относится ли- бо к использованию ресурсов системы, либо скорости выполнения, либо к тому и другому. К ресурсам системы относится оперативная память, дисковое пространство, устройства, то есть то, что может выделяться и использоваться. Суждение о том, является программа эффективной или нет, субъективно, оно зависит от ситуации. Расс- мотрим программу, которая при выполнении использует 147 Кбайт оперативной памяти, 2 Мбайта дискового пространства и затрачивает в среднем 70 минут. Если это короткая программа, выполняющаяся на персональном компьютере Apple 2, то по всей видимости не очень эффективна. Однако, если это программа, выполняющаяся на супер- компьютере Cray, то вероятно, она эффективна. С другой стороны, когда вы добиваетесь эффективности по ка- кому-либо одному параметру, часто при этом ухудшаются другие по- казатели. Например, достижение более быстрого выполнения програм- мы ведет к ее увеличению, если вы используете линейную программу вместо вызовов функций для увеличения скорости. Кроме того, дос- тижение более эффективного использования дискового пространства за счет упаковки данных замедляет доступ к диску. В свете этих проблем вы можете поставить вопрос. а как эф- фективность вообще может обсуждаться. В действительности сущест- вует несколько методик программирования, которые всегда дают эф- фективные программы или по крайней мере более эффективные, чем в других случаях. Существует также ряд методов, которые делают программу как быстрее, так и меньше. Предотвращение дублирования кода ----------------------------------------------------------------- Даже самые лучшие программисты пишут иногда избыточные коды. Избыточный код не ссылается на код, который может быть выделен в подпрограмму; даже не очень опытные программисты понимают это. Кроме того, к избыточности относится и не необходимое дублирова- ние аналогичных предложений внутри процедуры. Чтобы получить луч- Работа с Турбо Паскалем #2/2 = 148 = шее представление о том, что такое избыточный код, рассмотрим следующий фрагмент: Способность писать программы, которые эффективно используют системные ресурсы, переносимы на другие компьютеры и свободны от ошибок - это признак профессионального программиста. В данной главе рассматриваются некоторые методы, с помощью которых дости- гается эффективность, мобильность и отладка программ. ЭФФЕКТИВНОСТЬ ----------------------------------------------------------------- В применении к программам термин эффективность относится ли- бо к использованию ресурсов системы, либо скорости выполнения, либо к тому и другому. К ресурсам системы относится оперативная память, дисковое пространство, устройства, то есть то, что может выделяться и использоваться. Суждение о том, является программа эффективной или нет, субъективно, оно зависит от ситуации. Расс- мотрим программу, которая при выполнении использует 147 Кбайт оперативной памяти, 2 Мбайта дискового пространства и затрачивает в среднем 70 минут. Если это короткая программа, выполняющаяся на персональном компьютере Apple 2, то по всей видимости не очень эффективна. Однако, если это программа, выполняющаяся на супер- компьютере Cray, то вероятно, она эффективна. С другой стороны, когда вы добиваетесь эффективности по ка- кому-либо одному параметру, часто при этом ухудшаются другие по- казатели. Например, достижение более быстрого выполнения програм- мы ведет к ее увеличению, если вы используете линейную программу вместо вызовов функций для увеличения скорости. Кроме того, дос- тижение более эффективного использования дискового пространства за счет упаковки данных замедляет доступ к диску. В свете этих проблем вы можете поставить вопрос. а как эф- фективность вообще может обсуждаться. В действительности сущест- вует несколько методик программирования, которые всегда дают эф- фективные программы или по крайней мере более эффективные, чем в других случаях. Существует также ряд методов, которые делают программу как быстрее, так и меньше. Работа с Турбо Паскалем #2/2 = 149 = Предотвращение дублирования кода ----------------------------------------------------------------- Даже самые лучшие программисты пишут иногда избыточные коды. Избыточный код не ссылается на код, который может быть выделен в подпрограмму; даже не очень опытные программисты понимают это. Кроме того, к избыточности относится и не необходимое дублирова- ние аналогичных предложений внутри процедуры. Чтобы получить луч- шее представление о том, что такое избыточный код, рассмотрим следующий фрагмент: Read(a); Read(y); if a<10 then WriteLn('Недопустимый ввод'); if Length(y)=0 then WriteLn('Недопустимый ввод'); В данном случае предложение WriteLn('Недопустимый ввод') встречается дважды. Однако, это не необходимо, так как фрагмент может быть переписан следующим образом Read(a); Read(y); if (a<10 or (Length(y)=0) then WriteLn('Недопустимый ввод'); В таком варианте код не только короче, но и будет в действи- тельности выполняться быстрее, так как выполняется только одно предложение if/then вместо двух. Данный пример возможно не встретится в реальной программе, так как избыточные предложения являются соседними и их легко об- наружить. Однако, так как избыточные предложения часто разнесены в программе, то такие коды встречаются в большинстве программ. Избыточность иногда является следствием методов, выбранных для кодирования процедур. Например, далее представлены два метода кодирования функции, которая осуществляет поиск заданного слова в матрице строк: type str80 = string[80]; StrArray = array [1..100] of str80; function StrSearch1(str: StrArray; word: str80): boolean; { правильный, неизбыточный код } var t: integer; begin StrSearch1 := FALSE; for t := 1 to 100 do if str[t]=word then StrSearch1 := TRUE; end; Работа с Турбо Паскалем #2/2 = 150 = Function StrSeach2(str: StrArray; word: str80): boolean; {неправильный, избыточный код } var t: integer; begin t :=1; StrSearch := FALSE; if str[t]=word then StrSearch2 := TRUE else begin t := 2; while(t<=100) do begin if str[t]=word then StrSearch2 := TRUE; t := t+1; end; end; end; При втором методе не только дублируются предложения сравне- ния if/then, но также имеются два предложения присваивания (t:=1 и t:=2). Первая версия работает быстрее и требует значительно меньше памяти. Коротко говоря, избыточный код может быть следствием либо неряшливости при программировании, либо неверного выбора метода реализации процедуры. Работа с Турбо Паскалем #2/2 = 151 = Использование процедур и функций ----------------------------------------------------------------- Всегда помните, что использование процедур и функций с ло- кальными переменными составляет основу структурного программиро- вания. Процедуры и функции являются строительными блоками прог- рамм на Турбо Паскале и составляют самое главное положительное качество данного языка. Вам следует знать несколько особенностей функций Турбо Паскаля, которые влияют на размер и скорость выпол- нения вашего кода. Во-первых, Турбо Паскаль является стеково-ориентированным языком: все локальные переменные и параметры используют стек для промежуточного запоминания. При вызове функции адрес возврата вызвавшей процедуры также помещается в стек. Это позволяет прог- рамме осуществить возврат в точку, из которой был вызов. Когда функция возвращает управление, данный адрес и все локальные пере- менные и параметры должны быть удалены из стека. Процесс заталки- вания данной информации в стек называется последовательностью вы- зова, а процесс выталкивания информации из стека - последовательностью возврата. Эти последовательности требуют оп- ределенного времени и иногда довольно большого. Чтобы понять, как вызов функции может замедлить вашу прог- рамму, рассмотрим два примера: Версия 1 Версия 2 for x:=1 to 100 do for x:=1 to 100 t:=compute(x); t:=Abs(Sin(q)/100/3.1416); function compute(q: integer): real; var t:real; begin compute:=Abs(Sin(q)/100/3.1416); end; Хотя каждый цикл выполняет одну и ту же функцию, версия 2 гораздо быстрее, так как использование непосредственного кода устраняет задержки, связанные с последовательностями вызова и возврата. Для понимания того, сколько времени тратится, рассмот- рим следующий код на псевдоассемблере, который демонстрирует в теории последовательности вызова и возврата для функции compute: ; последовательность вызова move A, x ; поместить значение х в аккумулятор push A call compute ; инструкция вызова помещает адрес ; возврата в стек Работа с Турбо Паскалем #2/2 = 152 = ; последовательность возврата ; значение возврата функции должно быть помещено в ; регистр - мы используем В move B, stack-1 ; доставить значение во временное t return ; возврат к вызвавшей процедуре ; вызвавшая процедура затем выполняет следующие действия Использование функции compute внутри цикла ведет к тому, что последовательности вызова и возврата будут выполнены 100 раз. Ес- ли вы хотите написать быстрый код, то использование данной функ- ции внутри цикла - это не самый лучший подход. Теперь вы можете подумать, что следует писать программы, ко- торые состоят из нескольких больших процедур и которые, следова- тельно, будут работать быстрее. В большинстве случаев, однако, небольшие различия во времени выполнения не важны, а вот потеря структуры будет ощутимой. Но это другая проблема. Замена вызовов подпрограмм, которые используются различными процедурами, на не- посредстевнные коды сделает вашу программу очень большой, так как один и тот же код будет дублироваться большее количество раз. Помните, что подпрограммы были выдуманы главным образом как средство повышения эффективности использования памяти. Положение таково, что убыстрение программы ведет к ее увеличению, а умень- шение программы ведет к ее замедлению. Использовать непосредс- твенный код вместо вызовов функций следует только тогда, когда скорость имеет абсолютный приоритет. В противном случае рекомен- дуется повсеместное применение процедур и функций. Работа с Турбо Паскалем #2/2 = 153 = Предложение Case против цепочки if/then/elso ----------------------------------------------------------------- Следующие фрагменты кодов функционально эквивалентны. Одна- ко, один является более эффективным, чем другой. Можете ли вы сказать какой? case ch of if ch='a' then f1(ch) 'a': f1(ch); else if ch='b' then f2(ch) 'b': f2(ch); else if ch='c' then f3(ch) 'c': f3(ch); else if ch='d' then f4(ch) 'd': f4(ch); end; Левый фрагмент кода гораздо более эффективен, чем первый, так как в общем случае предложение case порождает более компакт- ный и быстрый объектный код, нежели серия предложений if/then/else. Цепочка if/then/else важна, так как она позволяет вам выпол- нить переходы по множеству ветвей с анализом данных различных ти- пов, что не может быть сделано с помощью предложения case. Одна- ко, если вы используете скалярные данные целые, действтительные числа, символьные данные и перечисления, то следует применять предложение case. Работа с Турбо Паскалем #2/2 = 154 = МОБИЛЬНОСТЬ ПРОГРАММ ----------------------------------------------------------------- Довольно частое явление - это перенос программы, написанной на одной машине, на другую, которая отличается процессором, опе- рационной системой или тем и другим. Данный процесс может быть и очень простым и крайне трудным в зависимости от того, как была написана программа. Программа мобильна, если она может быть легко перенесена. Программа переносится трудно, если она содержит много машинно-зависимых вещей: фрагментов кодов, которые работают толь- ко со специфической операционной системой или процессором. Турбо Паскаль допускает перенесение кода между всеми его версиями,но все *таки требует внимания к деталям и часто при этом теряется эффективность из-за отличий в операционных системах. Перенесение кода, написанного с использованием какого-либо компилятора Паскаля, в Турбо Паскаль может вызвать проблемы из-за применений различных наборов расширений. Обратная задача также проблематична: если были использованы расширения Турбо Паскаля, написанный код должен быть модифицирован при использовании друго- го компилятора. В данном разделе рассматривается несколько специфических об- ластей и даются некоторые решения. Вы также увидите, как писать программы на языке Паскаль, чтобы они были мобильными. Применение констант ----------------------------------------------------------------- Возможно самый простой способ сделать программу мобильной состоит в том, чтобы ввести каждое системно- и процессорно-зави- симое "магическое" число в декларацию констант. У данным "маги- ческим числам" относятся размер записи прямого доступа, специаль- ные команды экрана и клавиатуры, информация по распределению памяти и другие данные, которые могут сильно измениться при пере- несении программ. Если вы выделите эти числа в декларацию конс- тант они станут понятными для человека, осуществляющего перенесе- ние, и облегчит их редактирование. Например, далее приводятся две декларации матриц и две про- цедуры, которые обращаются к ним. В первом варианте размерности матриц являются жестко заданными, а во втором _ помещаются в дек- ларацию const. {первая версия} var count: array[1..100] of integer; procedure f1; var Работа с Турбо Паскалем #2/2 = 155 = t: integer; begin for t := 1 to 100 do count[t] := t; end; {вторая версия} const MAX = 100; var count: array[1..MAX] of integer; procedure f2; var t: integer; begin for t := 1 to MAX do coun[t] := t; end; Вторая версия лучше в том случае, если вы хотите переносить данную программу на машину, которая, например, допускает больший размер матрицы. В данном случае необходимо изменить только МАХ и все ссылки на МАХ будут автоматически скорректированы. Данную версию не только легко модифицировать, но она также позволяет из- бежать большого количества ошибок редактирования. Помните, что в реальной программе будет возможно много ссылок на МАХ, поэтому выигрыш в мобильности часто вполне весомый. Зависимость от операционной системы ----------------------------------------------------------------- Практически все коммерческие программы имеют коды, которые являются специфическими по отношению к операционной системе. Нап- ример, программа может использовать экранную память персонального компьютера IBM PC для быстрого переключения экрана или специаль- ные графические команды, применимые в данной операционной систе- ме. Некоторые особенности, связанные с операционной системой, не- обходимы для быстрых коммерческих программ. Однако, нет причин делать код более зависимым, чем это необходимо. Когда вы должны использовать системные вызовы для доступа к операционной системе, лучше сделать это в одной главной процедуре так, чтобы только ее надо было изменять при переводе в другую операционную систему, оставив все остальные без изменения. Напри- мер, если системный вывод необходим для очистки экрана и помеще- ния курсора в точку с координатами X,Y, то вы могли бы создать главную процедуру, аналогичную OpSysCall, показанной далее: Работа с Турбо Паскалем #2/2 = 156 = {интерфейс с операционной системой} procedure OpSysCall(op, x, y: integer); begin case op if 1: ClearScreen; 2: ClearEOL; 3: CotoXY(x,y); end; Хотя эти вызовы стандартны для всех версий Турбо Паскаля, если вы будете переносить программу на другой компилятор Паскаля, то вы поймете ценность такого метода взаимодействия. Должен быть изменен только код, который формирует действительные функции, за- висимые от операционной системы, оставляя нетронутым общий интер- фейс. Расширение Турбо Паскаля ----------------------------------------------------------------- Если вы осуществляете перенос из Паскаля в Турбо Паскаль, расширения и улучшения Турбо Паскаля могут сделать работу легче и даже повысить эффективность программы. Например, расширение string значительно облегчает работу со строками. Однако, если вы переносите код из Турбо Паскаля в другой Паскаль, то вы почти наверняка должны удалить все расширения и улучшения из вашей программы. Наихудшей задачей будет замена всех переменных типа string на матрицы символов. Но это только первый шаг, вы также потеряете все процедуры, которые поддерживают стро- ки, такие как Copy, Concat и Pos. Если ваша программа использует такие процедуры, то в новой версии вы должны создать их сами. В общем случае, если вы до написания программы знаете, что будете переносить ее в другой Паскаль, то следует избегать приме- нения расширений и улучшений Турбо Паскаля. Работа с Турбо Паскалем #2/2 = 157 = ОТЛАДКА ----------------------------------------------------------------- Перефразируя Томаса Эдисона можно сказать, что программиро- вание - это на 10% вдохновение и на 90% отладка. Хорошие програм- мисты являются хорошими отладчиками. Если вы и имеете большой опыт отладки, вам надо следить за различными типами мелочей, ко- торые имеют место при использовании Турбо Паскаля. Проблема указателей ----------------------------------------------------------------- Наиболее часто ошибки в Турбо Паскале связаны с неправильным употреблением указателей. Проблемы указателей делятся на две ос- новные категории: недоразумения с действиями над указателями и случайное применение недопустимых указателей. Для разрешения проблем первого типа вы должны разобраться с указателями в языке Паскаль; для разрешения проблем второго типа вы должны всегда проверять допустимость указателей перед их применением. Следующая программа иллюстрирует типичную ошибку с указате- лем, которую допустил программист: program WRONL; {данная программа имеет ошибку} type pntr = ^obiect; obiect = record x: char; y: integer; name:string[80]; end; var p: pntr; begin p^.name := 'tom'; p^.x := 'g'; p^.y := 100; end. Данная программа может аварийно завершиться, так как указа- тель р не принимает значение, используя New. Указатель р содержит неизвестное произвольное число, которое может указывать куда угодно в памяти компьютера. Это совсем не то, что вы хотели. Для исправления этой программы вы должны добавить строку Работа с Турбо Паскалем #2/2 = 158 = New(p); до первого использования р. "Дикий" указатель крайне трудно выследить. Если вы осущест- вляете присваивание указателю значение переменной, которая не со- держит недопустимый адрес, ваша программа некоторое время может работать правильно, а крах наступит после. Статистически, чем меньше ваша программа, тем больше вероятность того, что она будет работать правильно даже со сбившимся указателем, так как исполь- зуется меньше памяти. По мере роста вашей програмы ошибка стано- вится более вероятной. Во-вторых, наиболее коварные проблемы могут возникнуть, ког- да используются указатели: вы можете выйти за пределы памяти в течение вызова New во время выполнения. Это вызывает ошибку во время выполнения и оно прекращается. К счастью в Турбо Паскале есть специальная встроенная функция MemAvail так, что вы можете избежать этих проблем. MemAvail возвращает число свободных байт слева от неупорядоченного массива. В версиях, предшествующих 4.0, MemAvail возвращает число параграфов свободной памяти. Следова- тельно, чтобы сделать программу полностью правильной, проверяйте наличие свободной памяти перед ее выполнением. Чтобы сделать это, вы должны знать число байт, необходимое для каждого типа данных, которые вы размещаете. Однако, это число может изменяться в зави- симости от процессора и операционной системы, поэтому используйте функцию SizeOf, которая возвращает число байт, требующихся для переменной. Измененная программа выглядит следующим образом: program RIOHL; {это верная программа} type pntr = ^object; object = record x: char; y: integer; name:string[80]; end; var p: pntr; begin if MaxAvail>=SizeOf(object) then begin {свободная память} New(p); p^.name := 'tom'; p^.x := 'g'; p^y := 100; Работа с Турбо Паскалем #2/2 = 159 = end; end. Одним из указаний на проблему с указателем является то, что ошибка ведет к неустойчивости в поведении. Ваша программа может работать один раз правильно, а другой раз неправильно. Иногда другие переменные будут содержать мусор без видимых причин. Если такое происходит, проверьте ваши указатели. Хотя указатели являются вещью хлопотной, они также являются одним из самых мощных и полезных аспектов языка Паскаль. Приложи- те усилия заранее, чтобы научиться правильно ими пользоваться. Работа с Турбо Паскалем #2/2 = 160 = Переопределение встроенных процедур и функций ----------------------------------------------------------------- Хотя Турбо Паскаль не допускает переопределения ключевых слов, которые входят в язык, он позволяет вам переопределять сло- ва, которые отсылают к стандартным процедурам и функциям. Прог- раммисты иногда думают, что это интересно по многим причинам; од- нако, это может привести только к неприятностям. Далее дается пример проблемы, вызванной переопределением встроенной процедуры: program WRONG; {данная программа не вернa} var t: integer; units_processed: integer; procedure WriteLn(t: integer); begin write(t, ' ', 'байт свободной памяти в неупорядоченном массиве'); end; begin {вычислить объем свободной памяти в неупорядоченном массиве} WriteLn(NemAvail); . . . WriteLn(units_processed); end. Как показано в данном примере, программист переопределяет стандартную процедуру WriteLn, но забыв, что программа также ис- пользовала данную процедуру для печати числа обработанных элемен- тов. Вместо этого сообщение о свободной памяти будет отображаться дважды, так как переопределение WriteLn заменено встроенным опре- делением. Хотя в предыдущем примере заметить ошибку легко, самые глав- ные проблемы возникают тогда, когда встроенные функции и процеду- ры переопределены, но не используются непосредственно в программе в данном месте. Позже, когда программа будет модифицирована, пе- рераспределенная функция ил процедура рассматривается, как если бы она была встроенной, как показано в предыдущем примере: {проверить окно, оставшееся в глобальной матрице count } function MemAvail: boolean; var t: integer; Работа с Турбо Паскалем #2/2 = 161 = begin MemAvail := FALSE; for t := 1 to MAX do if count[t]=0 then MemAvail:=TRUE; end; Переопределение MemAvail хорошо, пока выполняется данный пример. Проблема возникает, если матрица count превращается позд- нее из глобальной переменной в динамическую переменную, которая выделяется из неупорядоченного массива. В данном случае, если вы пытаетесь использовать MemAvail для просмотра того, осталось ли достаточно свободного пространства, ваша программа выйдет из строя. Единственный способ избежать таких проблем заключается в том, чтобы никогда не иметь процедур и функций, написанных вами, с именами как у встроенных функций. Если вы не уверены, присоеди- ните ваши инициалы в начало имени, например, HSMemAvail вместо MemAvail. Работа с Турбо Паскалем #2/2 = 162 = Неожиданные синтаксические ошибки ----------------------------------------------------------------- Иногда вы будете встречаться с синтаксическими ошибками, ко- торые трудно понять или даже распознать, как ошибки. В частности, необнаруживаемая ошибка произойдет, когда вы пытаетесь скомпили- ровать данный код: program Errors; {данная программа не компилируется} var s: string[80]; procedure F1(x: string[80]); begin WriteLn(s); end; begin ReadLn(s); F1(s); end. Если вы попытаетесь скомпилировать эту программу, то увидите следующее сообщение об ошибке: Error 89: ")" expected /ошибка 89: ожидалась ")"/ После нажатия клавиши ESC, вы обнаружите, что Турбо Паскаль указывает на строку procedure F1(x:string[80]); с курсором в позиции, показанной стрелкой. Это недостаток Турбо Паскаля? Нет. Турбо Паскаль не может использовать тип string в вызовах процедур и функций. Вы должны явно декларировать свой тип и применить его. В данном примере вы, во-первых, декларируете тип, называемый str80 с помощью следующего предложения: type str80 = string[80]; Затем вы используете заново созданный тип str80, как тип па- раметра для функции F1. Правильная программа выглядит следующим образом: program CorrectecError; {данная программа будет скомпилирована} type str80 = string[80]; Работа с Турбо Паскалем #2/2 = 163 = var s := str80; procedure F1(x: str80); begin WriteLn(x); end; begin ReadLn(s); F1(s); end. Другая, вносящая путаницу, синтаксическая ошибка порождается следующей программой: program Error; {данная программа не будет скомпилирована} procedure F2; var t: integer; begin for t := 1 to 10 do WriteLn('hi there'); end begin F2; end. Ошибка здесь состоит в том, что точка с запятой отсутствует после end в процедуре F2. Однако, Турбо Паскаль укажет на ошибку в следующей строке. В данной простой программе легко обнаружить ошибку. Однако, в некоторых ситуациях вам пришлось бы потрудить- ся, чтобы найти то место, где пропущена точка с запятой. Работа с Турбо Паскалем #2/2 = 164 = Ошибки if/then/if/else ----------------------------------------------------------------- Даже очень опытные программисты могут допустить ошибку в конструкции if/then/if/else. Например, уверены ли вы, что следую- щий код работает правильно if count<100 then if count>50 then F1 else F2; Не шутите с соответствующим форматированием. Предложение else не ассоциируется с первым if, а только со вторым if. Помни- те, что else всегда ассоциируется с ближайшим if. В данном приме- ре вместо выполнения F2, когда count больше 100, Турбо Паскаль не делает ничего. Кроме того, F2 будет выполняться только, если count меньше 100 и - если меньше 50. Вы увидите это когда код правильно отформатирован: if count<100 then if count>50 then F1 else F2; Если вы хотите просто выполнить F2, когда count больше 100, вам надо было бы использовать конструкцию begin/end, как показано далее: if count<100 then begin if count>50 then F1; end else F2; Забывание о параметрах var в процедурах и функциях ----------------------------------------------------------------- Иногда в пылу программирования легко забыть, что, если про- цедура или функция изменяет свои аргументы, они должны быть опре- делены, как параметры типа var. Забывание этого может вызвать причудливые результаты и потребовать часов на отладку. Например, обсудим неправильную программу: program Error;{ данная программа не верна } var t: integer; procedure F1(x: integer); Работа с Турбо Паскалем #2/2 = 165 = begin Write('Введите значение: '); ReadLn(x); end; begin F1(t); {получить значение t} writeLn('t имеет значение: ', t); end. Данная программа не работает, так как значение назначается только локальной переменной х, а затем F1 возвращает управление и t не модифицируется. Чтобы сделать эту программу работающей, вы должны объявить х внутри F1, как параметр var. Это будет озна- чать, что переменная t будет модифицирована. Правильная программа выглядит следующим образом: program Fixed; {данная программа верна} var t: integer; procedure F1(var x: integer); begin Write('Введите значение: '); ReadLn(x); end; begin F1(t); {получить значение t} writeLn('t имеет значение: ', t); end. Хотя эту простую программу легко исправить, когда такая ошибка произойдет в большой программе, она может оказаться одной из наиболее трудных для нахождения. Работа с Турбо Паскалем #2/2 = 166 = Общие соображения по отладке ----------------------------------------------------------------- Каждый может иметь свой подход к программированию и отладке. Однако, доказано, что одни методы лучше других. В случае отладки возрастающее тестирование считается наиболее дешевым и эффектив- ным по времени методом, даже если кажется, что оно замедляет про- цесс разработки. Инкрементное тестирование - это простой процесс, всегда име- ющий рабочий код. Как только можно будет запустить кусок вашей программы, вам следует сделать это, проверяя эту секцию пол- ностью. По мере расширения вашей программы продолжайте проверять новые секции. При таком методе вы можете быть уверены, что любая возможная ошибка находится в малой области кода. Теория инкрементного тестирования основывается в основном на вероятности и областях. Каждый раз, как вы добавляете длину, вы удваиваете области. Следовательно, по мере роста вашей программы образуется и рост областей, в которых вы должны осуществлять по- иск ошибки. При отладке вы как программист хотите иметь дело с как можно меньшей областью. С помощью данного метода вы можете отделить область, которая уже проверена от всего кода и, следова- тельно, уменьшить область, которая может содержать ошибки. Работа с Турбо Паскалем #2/2 = 167 = ЗАКЛЮЧИТЕЛЬНЫЕ ЗАМЕЧАНИЯ ----------------------------------------------------------------- В данной книге были рассмотрены различные алгоритмы и методы и некоторые из них детально. Помните, что теория вычислительных систем является наукой как теоретической, так и импирической. Хо- тя довольно легко порой увидеть, почему один алгоритм лучше, чем другой, трудно сказать, как делать хорошие программы. Когда речь идет об эффективности, мобильности и отладке, экспериментирование иногда дает информацию более легко, чем теоретические изыскания. Программирование - это как наука, так и искусство. Это нау- ка, так как вы должны знать логику и понимать, как работает алго- ритм; это искусство, так как вы создаете законченное целое - программу. Как программист вы имеете одну из лучших профессий на свете: вы ходите по грани между наукой и искусством и берете луч- шее из того и другого. Работа с Турбо Паскалем #2/2 = 168 = ПРИЛОЖЕНИЕ А. ПРЕОБРАЗОВАНИЕ ПРОГРАММ С ЯЗЫКОВ СИ И БЕЙСИК В ТУРБО ПАСКАЛЬ ----------------------------------------------------------------- Часто программисты тратят массу своего времени на преобразо- вание программ из одного языка в другой. Вы можете найти данный процесс и простым и трудным в зависимости от метода, который вы использовали для трансляции, и от того, насколько хорошо вы знае- те оба языка. В данном приложении описываются методы, которые по- могут вам преобразовать программы, написанные на языках Си и Бей- сик (включая Турбо Си и Турбо Бейсик), в Турбо Паскаль. Почему кто-то может захотеть транслировать программу, напи- санную на одном языке, в другой язык? Одна из причин - это удобс- тво сопровождения: программа, написанная не на структурированном языке, аналогичном Бейсику, трудна для сопровождения и развития. Другими причинами являются скорость и эффективность: Турбо Пас- каль является очень эффективным языком и некоторые важные задачи транслируются в Турбо Паскаль для повышения производительности. Третья причина - это практичность: полезно иметь все программы на одном языке; при этом изначально вы можете применить компиляторы различных языков. Возможно вы сочтете нужным транслировать прог- рамму в Турбо Паскаль по каким-либо из приведенных причин. Си и Бейсик были выбраны из почти сотни языков программиро- вания, так как они популярны среди пользователей микрокомпьютеров и находятся на противоположных концах спектра языков программиро- вания. Си - это структурированный язык, который во многом похож на Турбо Паскаль, а Бейсик является неструктурированным языком и ничем не схож с Турбо Паскалем. Хотя в данном приложении нельзя подробно описать трансляцию этих языков, будут рассмотрены наибо- лее важные проблемы этого. Вы должны быть уже знакомы либо с язы- ком Си, либо с Бейсиком; данное приложение не ставит целью обу- чить вас какому-либо из этих языков. ПРЕОБРАЗОВАНИЕ ИЗ СИ В ТУРБО ПАСКАЛЬ ----------------------------------------------------------------- Си и Турбо Паскаль во многом похожи, особенно в структурах управления и в использовании автономных подпрограмм с локальными переменными. Это делает возможным выполнение трансляции "один в один", которая представляет собой процесс подстановки ключевых слов и функций Турбо Паскаля вместо эквивалентных ключевых слов в Си. При трансляции "один в один" вы можете применить компьютер для помощи в данном процессе; простая программа трансляции будет разработана далее в этой главе. Хотя Си и Турбо Паскаль похожи, существует несколько важных различий между ними. Во-первых, Си не использует строгой проверки типов в отличие от Турбо Паскаля. Следовательно, некоторые типы Работа с Турбо Паскалем #2/2 = 169 = процедур, написанные на Си, должны быть модифицированы, чтобы согласовать типы всех операндов. Например, в Си символьные и це- лые переменные могут быть свободно перемешаны; в Турбо Паскале они не могут смешиваться без применения различных функций преоб- разования типов (Однако, помните, что строгая проверка типов не делает язык лучше или хуже. Это только изменяет взгляд програм- миста на задачу). Второе и более важное различие заключается в том, что Си формально не является блочно структурированным, тогда как Турбо Паскаль является. Термин блочно структурированный обозначает спо- собность языка создавать логически связанные единицы кода, кото- рые могут рассматриваться как целое. Термин также означает, что процедуры могут иметь процедуры внутри себя, что будет известно только для них. Хотя Си часто называют блочно структурированным, так как позволяет легко создавать блоки кодов, он не допускает создания функций внутри других функций. Например, следующий код в Си требует двух функций: A() { printf("starting A\n"); B(); } B() { printf("inside function B\n"); } Однако, когда этот код транслируется в Турбо Паскаль, он будет выглядеть следующим образом: procedure A; var x:integer; procedure B; begin WriteLn('inside proc b'); end; begin WriteLn('starting A'); B; end; Здесь процедура В задана внутри процедуры А. Это означает, что процедура В известна только процедуре А и может быть использована только процедурой А. За пределами процедуры А может быть опреде- Работа с Турбо Паскалем #2/2 = 170 = лена другая процедура В. Третье различие между Си и Турбо Паскалем заключается в том, что в Си ссылки вперед на функции не завершены и фактически широ- ко используются. Но в Паскале функции и процедуры должны быть объявлены до их использования. По стандарту Паскаля на функции и процедуры это означает, что, если вы должны сослаться на функцию или процедуру до ее декларации, необходимо использовать деклара- цию forward. Работа с Турбо Паскалем #2/2 = 171 = Сравнение Си и Турбо Паскаля ----------------------------------------------------------------- На рис.А-1 сравниваются ключевые слова и операторы Турбо Паскаля и Си. Как вы можете видеть, многие ключевые слова Турбо Паскаля не имеют эквивалента в Си. Это, в частности, объясняется тем фактом, что Турбо Паскаль использует ключевые слова в местах, где Си использует операторы для реализации тех же самых действий: ─────────────────────────────────────────────────────────── Turbo Pascal Си Turbo Pascal Си and && mod % array nil (иногда\0) begin { not ! case switch of copst or ║ div / packed do procedure downto program else else record struct end } repeat do file sel forward extern(on occasion) then for for type function to goto goto until while(как в do/while) if if var in while while label with ──────────────────────────────────────────────────────────── Рис.А-1. Сравнение ключевых слов Турбо Паскаля и Си: В дополнение к ключевым словам Турбо Паскаль имеет различные встроенные стандартные идентефикаторы, которые могут быть исполь- зованы непосредственно в программе. Эти идентефикаторы могут быть функциями (как WriterLn) или глобальными переменными (как MaxInt), которые содержат информацию о состоянии системы. Кроме того, Турбо Паскаль использует стандартные идентефикаторы для за- дания таких типов данных как real, integer, boolean и character. На рис.А-2 показаны наиболее часто используемые идентефика- торы Си с их эквивалентами в Паскале. В дополнение к ним любые другие эквиваленты встроенных функций Паскаля можно найти в стан- дартной библиотеке Си; однако, они могут изменяться в зависимости от компилятора. Работа с Турбо Паскалем #2/2 = 172 = ───────────────────────────────────────────────────── Си Turbo Pascal char er int boolean char byte char char EOF EOF O FALSE flush flush integer integer scanf() and olhers Read or ReadLn float real anv one-zero value TRUE printf() Write or WriteLn ──────────────────────────────────────────────────── Рис.А-2. Стандартные идентефикаторы Турбо Паскаля и их экви- валенты в Си. Язык Си также отличается от Турбо Паскаля и операторами. На рис.А-3 показаны операторы Си и их эквиваленты в Турбо Паскале. ──────────────────────────────────────────────────── Си Turbo Pascal Meaning + + сложение - - вычитание * * умножение / / деление / div целочисленное деление % mod модуль ^ возведение в степень = := присваивание == = равно как условие < < меньше чем > > больше чем >= >= больше или равно <= <= меньше или равно != <> не равно ────────────────────────────────────────────────────── Рис.А-3. Операторы Си и их эквиваленты в Турбо Паскале Работа с Турбо Паскалем #2/2 = 173 = Преобразование циклов Си в циклы Турбо Паскаля ----------------------------------------------------------------- Так как циклы управления программой являются основополагаю- щими в большинстве программ, в данном разделе сравниваются циклы в Си и Турбо Паскале. В языке Си существует три встроенных цикла: for, while и do/while. Цикл for в Си имеет следующую общую форму: for(начальная установка; условие; приращение) предложение for в Си гораздо более общее предложение: чем for/do в Паскале: условие проверки не должно быть значением цели, как в Турбо Пас- кале; более того, им может быть любое булевское выражение. Су- ществует также механизм сообщения вам, работает цикл положительно или отрицательно, так как в Си не используются Турбо Паскалевские условия to и downto. Другое отличие состоит в том, что части на- чальной установки и приращения могут быть сложными составными, чему нет аналога в Турбо Паскале. Однако, не смотря на эти разли- чия цикл for языка Си будет часто иметь ту же форму, что и стан- дартный for/do в Паскале, что делает трансляцию очень простой. Например, цикл for в Си for(x=10;x<=100;++x)printf("%d\n",x); может быть транслирован в Паскаль следующим образом for x:=10 to 100 do WriteLn(x); Предложения while в Си и while/do в Паскале практически оди- наковы. Однако, do/while в Си и repeat/until в Турбо Паскале тре- буют, чтобы вы использовали различные ключевые слова и "обрат- ную" проверку цикла. Это происходит потому, что цикл do/while в Си выполняется до тех пор, пока условие цикла выполняется, тогда как циклы until в Турбо Паскале выполняются до тех пор, пока не выполнится условие. Простая трансляция обоих типов циклов показа- на далее Си Pascal ------------------------------------------------- while(x<5) while x<5 do { begin x=gefnum(); Read(x); printf("%d\n", x); WriteLn(x); } end; do { repeat x=getnum(); Read(x); Работа с Турбо Паскалем #2/2 = 174 = printf("%d\n"; x); WriteLn(x); } while(x<=5); until x>9; Будьте внимательны при трансляции do/while в repeat/until: вы должны изменить условие на противоположное. Приведем несколько философских соображений относительно соотношения между do/while и repeat/until. Конструкцию do/while можно считать позитивной, так как цикл выполняется до тех пор, пока условие не станет истинным; repeat/until можно назвать негативным предложением цикла, так как цикл выполняется до тех пор, пока условие ложно. Это наводит на мысль, что выбор цикла для использования основывается на том, яв- ляетесь ли вы оптимистом или пессимистом - но это еще не показа- но. Работа с Турбо Паскалем #2/2 = 175 = Пример трансляции ----------------------------------------------------------------- Чтобы почувствовать процесс трансляции, проследим шаги пре- образования программы на языке Си в Турбо Паскаль. В качестве об- разца программы на Си возьмем следующую: float qwerty; main() { qwerty=0; printf("%f", qwerty); printf("hello there\n"); F2(25); printf("%f\n", F1(10)); printf("%2.4f\n", qwerty): } F2(x) int x; { printf("%d", x*2); } float F1(w); float w; { qwerty = 23.34; return w/3.1415; } В данной программе декларированы три функции (Учтите, что все подпрограммы в Си являются функциями, независимо от того, используются или нет возвращаемые значения). Первый шаг трансля- ции данной программы в Турбо Паскаль состоит в том, что опреде- лить, какие функции на Си останутся функциями в Турбо Паскале то есть, какие будут возвращать значения и какие будут процедурами. Вы можете определить это, анализируя ключевое слово retutn в Си. Если оно присутствует в функции, вы можете предположить, что значение должно возвращаться. Единственной функцией, использующей return, является F1. Следовательно, в Турбо Паскале эквивалентом F1 будет: function F1(w: real): real; begin F1 := w/3.1415; qwerty := 23.34 end; Работа с Турбо Паскалем #2/2 = 176 = Функция F2 не возвращает значения, поэтому она становится процедурой в Турбо Паскале. procedure F2(x: integer); begin WriteLn(x*2); end; Далее функция main должна быть преобразована в следующий программный код на Турбо Паскале: begin qwerty := 0; WriteLn(qwerty); WriteLn('hello there'); F2(25); WriteLn(F1(10)); WriteLn(qwerty:2:4); end. Наконец, вы должны объявить глобальную переменную qwerty как real и добавить заголовок программы. Когда вы сделаете это и сое- дините куски вместе, программа на Турбо Паскале, транслированная из Си, будет выглядеть следующим образом: program test (input,output); var qwerty: real; procedure F2(x: integer); begin WriteLn(x*2); end; function F1(w:real): real; begin F1 := w/3.1415; qwerty := 23.34; end; Работа с Турбо Паскалем #2/2 = 177 = begin qwerty := 0; WriteLn(qwerty); WriteLn('hello there'); F2(25); WriteLn(F1(10)); WriteLn(qwerty:2:4); end. Работа с Турбо Паскалем #2/2 = 178 = Использование компьютера для помощи в преобразовании из Си в Турбо Паскаль ----------------------------------------------------------------- Можно создать компьютерную программу, которая будет прини- мать исходный код на одном языке и выдавать на другой. Лучшим способом сделать это является реализация полного синтаксического анализатора для исходного языка, но вместо машинного кода он дол- жен выдавать код на нужном языке. Время от времени вы можете най- ти рекламу таких продуктов в компьютерных журналах и их высокая стоимость отражает сложность задачи. Менее чистолюбивый подход заключается в создании простой программы для помощи вашим усилиям по преобразованию программы выполнением некоторых простых задач трансляции. Данная "компь- ютерная помощь" может действительно сделать работу по трансляции значительно легче. Такой транслятор принимает в качестве входа программу на ис- ходном языке и автоматически выполняет все трансляции "один в один" в нужный язык, оставляя наиболее трудные аспекты преобразо- вания для вас. Например, для того, чтобы присвоить переменной count значение 10 в языке Си, вам следовало бы написать count=10; В Турбо Паскале предложение является точно таким же за иск- лючением того, что двоеточие предшествует знаку "=". Следователь- но, транслятор может изменить предложение присваивания "=" в Си на ":=" в Турбо Паскале. Другим примером является оператор цикла while в Си: ключевое слово while используется тем же самым обра- зом в Турбо Паскале. Однако, в Си и Турбо Паскале обращение к дисковым файлам происходит различным образом и не существует легкого способа ав- томатического выполнения такого преобразования. Кроме того, пре- образование конструкции do/while языка Си в repeat/until Турбо Паскаля не легко автоматизировать. Следовательно, эти сложные преобразования остаются за вами. Далее представлены шаги создания транслятора из Си в Турбо Паскаль. Во-первых, такому транслятору необходима функция, кото- рая возвращает по одной лексеме из программы на языке Си. Функция GetToken, разработанная в главе 8, может быть модифицирована для этого, как показано далее: {считывание лексемы из входной строки} procedure GetToken; var temp:str80; begin Работа с Турбо Паскалем #2/2 = 179 = token:=''; {пустая строка} While(Iswhite(prog[t])) do t:=t+1;{пропустить предшествующие пробелы} if prog[t]='S' then token:='S'; if (pos(prog[t],'.#:;,+│<>&^-*/!%^=(){}')<>0) or (prog[t]=chr(39)) then begin if (prog[t]='{') or (prog[t]='}') then begin TokType:=NAME; if prog[t]='{' then begin token:='begin'; indent:=indent+1; end else begin token:='end'; indent:=indent-1; end; end else begin TokType:=DELIMITER; token:=prog[t]; {это оператор} end; t:=t+1; end else if IsAlpha(prog[t]) then begin While(not IsDelim(prog[t])) do begin token:=concat(token,prog[t]); {построить лексему} t:=t+1; end; TokType:=NAME; end else if IsDigit(prog[t]) then begin While(not IsDelim(prog[t])) do begin token:=concat(token,prog[t]); {построить число} t:=t+1; TokType:=NUMBER; end; end else if prog[t]='"' then {это строка} begin t:=t+1; token:=chr(39); { a ' } while prog[t]<>'"' do Работа с Турбо Паскалем #2/2 = 180 = begin token:=concat(token, prog[t]); t:=t+1; TokType:=STR; end; t:=t+1; {пройти за закрывающие кавычки} token:=concat(token,chr(39)); end; end; {GetToken} Символы { и } в Си преобразуются в паскалевские эквиваленты begin и end внутри GetToken для того, чтобы упростить другие части программы. Вторым шагом в создании транслятора является разработка про- цедуры, которая транслирует элементы языка Си в их эквиваленты в Турбо Паскале. Функция translate, показанная далее, является не лучшей реализацией такой процедуры, но она подходит для целей трансляции {трансляция программы на Си в Турбо Паскаль} procedure Translate; begin case token[1] of '-': token:='not'; '=': {просмотр вперед для обнаружения двойного символа равно} begin SetToken; if token='=' then token:='==' else begin PutBack; {восстановить лексему в потоке} token:=':='; {присваивание} end; end; '!': {просмотр вперед для обнаружения <>} begin GetToken; if token='=' then token:='<>' else begin PutBack; token:='not'; end; end; '%': token:='mod'; Работа с Турбо Паскалем #2/2 = 181 = '│': begin GetToken; if token<>'│' then PutBack; {нет двойного ИЛИ} token:='or'; end; '&': begin GetToken; if token<>'&' then PutBack; {нет двойного И} token:='and' end; '^': token:='xor'; end; if token='switch' then token:='case' else if token='struct' then token='record' else if token='int' then token='integer' else if token='tloat' then token='real' else if token='printf' then token='Write' else if token='extern' then token='forward' else if token='case' then token=''; end; {Translate} Отметим, что в большинстве случаев ключевые слова и операто- ры языка Си просто передаются на выход, так как они точно такие же в Турбо Паскале. ОДнако, в случае циклов while и do/while в Си нельзя при таком методе решить, нужен вам цикл while/do или repeat/until в Паскале. Далее приведена вся программа трансляции: {простой преобразователь из Си в Турбо Паскаль} program СToPascal; type str80 = string[80]; TType = (DELIMITER, NAME, NUMBER,STR); var inname, outname, token, prog: string[255]; TokType: TType; infile, outfile: text; indent, t: integer; {данная функция возвращает TRUE, если ch является буквой алфавита} function IsAlpha(ch:char): bolean; begin IsAlpha:=(UpCase(ch)>='A') and (UpCase(ch)<='Z'); end; {IsAlpha} Работа с Турбо Паскалем #2/2 = 182 = {данная функция возвращает TRUE, если ch является символом новой строки, табуляции или пробелом } function IsWhite(ch:char): bolean; begin IsWhite:=(ch=' ') or (ch=chr(13)); end; {IsWhite} {данная функция возвращает TRUE, если ch является разделителем} function IsDelim(ch:char): boolean; begin if ch=chr(29) then IsDelim:=TRUE { a ' } else if pos(ch,'#:.,;<>│&-+/*%^=!()${}')<>0 then IsDelim:=TRUE else IsDelim:=FALSE; end; {IsDelim} {данная функция возвращает TRUE, если ch - цифра от 0 до 9} function IsDigit(ch:char): bolean; 5 begin IsDigit:=(ch>='0') and (ch<='9'); end; {IsDigit} {GotToken считывает следующую лексему из входного потока} procedure GetToken; var temp: str80; begin token := ''; {пустая строка } while(IsWhite(prog[t])) do t:=t+1; {пропустить предшествующие пробелы} if prog[t]='$' then token:='$'; if (pos(prog[t],'.#:;,+│<>&^-*/!%^"(){}')<>0) or (prog[t]=chr(39)) then begin if (prog[t]='{') or (prog[t]='}') then begin TokType:=NAME; if prog[t]='{' then begin token:='begin'; indent:=indent+1; end else begin token:='end'; indent:=indent-1; end; Работа с Турбо Паскалем #2/2 = 183 = end else begin TokType:=DELIMITER; token:=prog[t]; {является оператором } end; t := t+1; end else if IsAlpha(prog[t]) then begin While(not IsDelim(prog[t])) do begin token:=concat(token,prog[t]);{ построить лексемы } t:=t+1; end; TokType:=NAME; end else if IsDigit(prog[t]) then begin While(not IsDelim(prog[t])) do begin token:=concat(token,prog[t]); { построить число } t:=t+1; TokType:=NUMBER; end; end else if prog[t]='"' then begin t:=t+1; token:=chr(39); { a ' } while prog[t]<>'"' do begin token:=concat(token,prog[t]); t:=t+1; TokType:=STR; end; t:=t+1; {пройти на закрывающие кавычки} token:=concat(token,chr(39)); end; end; {GetToken} procedure PutBack; {поместить назад неиспользованную лексему} begin t:=t-length(token); end; {PutBack} {транслировать программу с языка Си в Турбо Паскаль} procedure Translate; Работа с Турбо Паскалем #2/2 = 184 = begin case token[1] of '-': token:='not'; '=': {просмотр вперед для поиска двойного символа равно} begin GetToken; if token='=' then token:='==' else begin 17 PutBack; {восстановление лексемы} token:=':='; end; end; '!': {просмотр вперед для поиска <>} begin GetToken; if token='=' then token:='<>' else begin PutBack; token:='not'; end; end; '%': token:='mod'; '│': begin GetToken; if token<>'│' then PutBack; { не двойное ИЛИ} token:='or'; end; '&': begin GetToken; if token<>'&' then PutBack; { не двойное И} token:='and' end; '^': token:='xor'; end; if token='swich' then token:='case' else if token='stiwet' then token:='record' else if token='int' then token:='integer' else if token='float' then token:='real' else if token='printf' then token:='forward' else if token='case' then token:=''; end; {Translate} {преобразование в Паскаль} procedure convert; var count:integer; Работа с Турбо Паскалем #2/2 = 185 = begin GetToken; for count:=1 to indent do Write(outfile,' '); while token<>'$' do begin case TokType of STR: Write(outfile,token); NAME: begin Translate; Write(outfile,token,' '); end; DELIMITER: begin Translate; Write(outfile,token); end; NUMBER: Write(outfile,token); end; GetToken; end; token:=''; WriteLn(outfile,token); end; {convert} begin {main} Write('Введите имя входного файла: '); ReadLn(inname); Write('Введите имя выходного файла: '); ReadLn(outfile); Assign(infile,inname); Assign(outfile,outname); Reset(infile); Rewrite(outfile); indent:=0; {счетчик абзацев для каждого BEGIN и END} while not EOF(infile) do begin t:=1; {сбрасывать индекс каждый раз} ReadLn(infile,prog); prog:=concat(prog,'&'); convert; end; token:='.'; WriteLn(outfile,token); close(infile); close(outfile); end. Работа с Турбо Паскалем #2/2 = 186 = В данной программе глобальная переменная indent используется для автоматической реализации структурированного расположения ко- да с добавлением двух пробелов при каждом операторе begin и уда- лением двух пробелов при каждом end. Это позволяет соответствую- щим образом отформатировать псевдопаскалевский вывод. В сущности программа для оказания помощи в преобразовании из Си в Турбо Паскаль считывает последовательно код языка Си, берет по одной лексеме, выполняет преобразование и записывает вариант Турбо Паскаля. Чтобы посмотреть, как эта простая программа может выполнять трансляцию из Си в Турбо Паскаль, пропустите следующий исходный код на Си через транслятор: main() { int t, a; t= getnum(); if(t=10) then process(t); else { a = t-100; print("%d", a); } } process(x) int x; { int count; for(count=0; count; count++) printf("this is x*%d: %d\n, count, x*count); } После того, как вы пропустите его через транслирующую прог- рамму, получится следующий псевдопаскалевский выход: main () begin integer t, a: t :=getnum (); if (t:=10) then process (t ); else begin a:-t -100; print ('%d',a); end end Работа с Турбо Паскалем #2/2 = 187 = process (x) integer x; begin integer count; for (count:=0; count; count++) Write ('this is x*%d: %d\n, count, x*count); end Как вы можете видеть, это не код Турбо Паскаля, но вы имеете печать и разрядку. Все, что от вас требуется, это отредактировать данные строки. Работа с Турбо Паскалем #2/2 = 188 = ПРЕОБРАЗОВАНИЕ ИЗ БЕЙСИКА В ТУРБО ПАСКАЛЬ ----------------------------------------------------------------- Задача преобразования из Бейсика в Турбо Паскаль значительно более трудная, чем преобразование из Си в Турбо Паскаль. Бейсик не является структурированным языком и имеет мало общего с Турбо Паскалем, что означает отсутствие полного набора управляющих структур и, что более важно, автономных программ с локальными пе- ременными. Задача трансляции очень сложна; она требует обширных знаний как Бейсика, так и Турбо Паскаля и понимания работы прог- раммы, так как в сущности вы переписываете программу заново в Турбо Паскале и используете версию на Бейсике, как руководство. Из-за сложности задачи в данном разделе показывается несколько особо сложных моментов трансляции и дается ряд советов. Преобразование циклов Бейсика в циклы Турбо Паскаля ----------------------------------------------------------------- Цикл FOR/NEXT является единственной формой управления цикла- ми во многих версиях Бейсика. Общая форма цикла FOR/NEXT похожа на цикл for/do в Турбо Паскале: существует начальное значение и значение цели; но в отличие от опции STER в Бейсике в Турбо Пас- кале допускается приращение 1 и -1. Цикл for/do в Турбо Паскале является гораздо более изощренным и гибким, чем цикл FOR/NEXT в Бейсике, так как он допускает использование любых скалярных типов для управления циклами. Вы должны переписывать все циклы FOR/NEXT, использующие опцию STER, в цикл while/do или repeat/until Паскаля. Однако, при последующих обсуждениях приме- ров используются циклы Бейсика, в которых опция STER не применя- ется. Например, цикл FOR/NEXT Бейсика 10 FOR x=1 TO 100 20 PRINR X 30 NEXT транслируется в Турбо Паскаль следующим образом for x := 1 to 100 do WriteLn(x); Как вы видите, преобразование представляет собой подстановку один в один. При преобразовании необходимо убедиться, что пере- менная управления циклом не модифицируется внутри цикла. Много вариантов Бейсика позволяет изменять переменную управления внутри цикла, как показано ниже 10 FOR COUNT=10 TO 0 STER -1 20 INPUT A 30 PRINT A*COUNT Работа с Турбо Паскалем #2/2 = 189 = 40 IF A = 100 THEN COUNT = 0 50 NEXT Предложение IF/THEN в строке 40 может вызвать более ранний выход из цикла. Чтобы правильно транслировать данный код в Турбо Паскаль, вы должны допустить данный условный переход. Хотя в версии 4 Паскаля допускается такая модификация переменной управ- ления циклом внутри цикла, в более ранних версиях это не так. После того, как предложение for/do скомпилировано, число раз вы- полнения цикла будет фиксировано, даже если вы измените значение переменной управления внутри цикла. Следовательно, вы должны пе- рекодировать циклы FOR/NEXT такого типа либо в while/ do, либо в repeat/until Турбо Паскаля. В некоторых вариантах Бейсика существует цикл WHILE/WEND. В таких случаях вам следует использовать цикл while/do Турбо Паска- ля и трансляция будет прямой. Если в Бейсике, которым вы пользуе- тесь, нет цикла WHILE/WEND, ваша работа существенно осложнится, так как вы должны будете распознавать циклы, которые используют предложения GOTO. Это относится также к случаю, когда в Бейсике заложен цикл типа repeat/until. Трансляция данных типов затрудни- тельна, так как вы должны действительно понимать, как программа работает, чтобы распознать цикл и транслировать его в одну из встроенных структур управления циклами Турбо Паскаля. После обнаружения в Бейсике циклов с внутренним предложением GOTO легко сказать, в какой цикл следует его транслировать. Пом- ните, что цикл repeat/until в Турбо Паскале всегда выполняется хотя бы один раз, так как условие проверяется в конце цикла, тог- да как цикл while/do может выполняться и не выполняться, так как его условие проверяется в начале цикла. Следовательно, вы должны тщательно просматривать каждый такой цикл в Бейсике, чтобы опре- делить, где осуществляется проверка. Например, код Бейсика 100 S = S+1 200 Q = S/3.1415 300 PRINT Q; 400 IF S < 100 THEN GOTO 100 скрывает цикл repeat/until: он всегда выполняется по крайней мере один раз. После выполнения строки 100, строки с 200 по 400 будут выполняться также. Если S меньше 100, управление в программе возвращается к строке 100. В Турбо Паскале этот код выглядел бы следующим образом: repeat s := s+1; q := q/3.1415; Write(q); until s=100; Работа с Турбо Паскалем #2/2 = 190 = В следующем примере на языке Бейсик проверка выполняется в начале цикла: 10 A = 1 20 IF A>100 THEN GOTO DO 30 PRINT A 40 INPUT B 50 A = A+B 60 GOTO 20 80 PRINT "DONE" Это требует использования в Турбо Паскале цикла while/do: a := 1; while a<=100 do begin WriteLn(a); ReadLn(b); a := a+b; end; Избегайте помещения любых инициализаций внутри цикла. В дан- ном примере предложение а:=1 должно быть вне цикла, так как это начальное условие и оно не должно относится к циклу. Работа с Турбо Паскалем #2/2 = 191 = Преобразование IF/THEN/ELSE ----------------------------------------------------------------- В большинстве вариантов Бейсика существует только одностро- ковые предложения IF/THEN/ELSE. Это означает, что, когда блок предложений должен быть выполнен на основе результата IF, следует применять предложения GOTO или GOSUB. Вы должны распознавать та- кие ситуации, так как вы захотите при трансляции структурировать код в соответствующее предложение (if/then или if/then/else) Тур- бо Паскаля. В качестве примера рассмотрим фрагмент кода на языке Бейсик: 120 IF T<500 THEN GOTO 500 130 Y=W 140 T=10 150 INPUT A . . . 500 REM RESUME DISK READS Чтобы выполнить блок IF в программе на языке Бейсик, условие IF должно быть изменено на противоположное: целью IF не должно быть условие, которое вызывает вхождение в блок IF, а должно быть условием, вызывающим переход. Это одна из наихудших проблем в Бейсике. Использование GOSUB в качестве цели в IF или ELSE значи- тельно упрощает проблему, но не снимает ее полностью. Если бы фрагмент кода на Бейсике был непосредственно транслирован в Турбо Паскаль, было бы получено следующее if t<500 then {не делать ничего} else begin y := w; t := 10; ReadLn(a); end; {возобновить считывание с диска} Вы теперь можете видеть проблему: целью if является пустое предложение. Единственный метод разрешения проблемы состоит в пе- резаписи условия if таким образом, что, если оно истинно, осу- ществляется вход в блок кода. Фрагмент кода становится следующим: if t>=500 then begin y := w; t := 10; Работа с Турбо Паскалем #2/2 = 192 = ReadLn(a); end; { возобновить считывание с диска.} Теперь вы видите, что код таков, какой пишет на Турбо Паска- ле. Работа с Турбо Паскалем #2/2 = 193 = Создание подпрограмм Турбо Паскаля из программ Бейсика ----------------------------------------------------------------- Одной из причин трудности трансляции из Бейсика в Турбо Пас- каль является то, что стандартный Бейсик не поддерживает автоном- ных подпрограмм с локальными переменными. Это означает, что трансляция литералов программы на Бейсике в Турбо Паскаль порож- дает очень большую главную программу без подпрограмм. Это пере- черкивает многое из того, что побуждает проводить трансляцию. Лучше всего при трансляции создавать программу на Турбо Пас- кале с очень маленькой главной программой и большим количеством подпрограмм, но, чтобы сделать это, требуется знание программы. Однако, далее будет дан ряд правил, которые помогут вам. Во-первых, вам следует сделать из всех- процедур GOSUB подп- рограммы. Кроме того, ищите аналогичные функции, в которых изме- няются только переменные, и сводите их в одну подпрограмму с па- раметрами. Например, следующая программа на Бейсике имеет две процедуры: первая начинается со строки 100, а вторая - со строки 200. 10 A=10 20 B=20 30 GOSUB 100 40 PRINT A, B 50 C=20 60 D=30 70 GOSUB 200 80 PRINT C, D 90 END 100 A = A+B 110 B = A/B 120 RETURN 200 C = C*D 210 D = C/D 220 RETURN Обе подпрограммы выполняют одинаковые функции, а отличаются набором переменных. Соответствующая трансляция этой программы в Турбо Паскаль порождает только одну функцию, которая использует параметры, чтобы избежать двух специальных функций: program x; var a, b, c, d: integer; procedure f1(var x, y: integer); begin x := x*y; Работа с Турбо Паскалем #2/2 = 194 = y := x div y; end; begin a := 10; b := 10; f1(a, b); WriteLn(a, ' ', b); c := 20; d := 30; f1(c, d); WriteLn(c, ' ', d); end. Эта трансляция в Турбо Паскаль передает читателю смысл кода более точно, чем это делает версия на Бейсике, так как в послед- нем случае предполагается, что действительно существуют две от- дельные функции. Следующее правило состоит в том, что из всех повторяющихся кодов следует делать функции. В программе на Бейсике несколько одинаковых строк могут повторяться. Программисты делают так для убыстрения кода. Так как Турбо Паскаль является компилируемым языком, использование функции вместе непосредственного кода имеет очень слабое отрицательное влияние на скорость выполнения, а улучшение понятности и структуры программы превосходит любые по- тери в скорости. Работа с Турбо Паскалем #2/2 = 195 = Избавление от глобальных переменных ----------------------------------------------------------------- В Бейсике все переменные являются глобальными: они известны в пределах всей программы и могут быть модифицированы где угодно в программе. В процессе трансляции пытайтесь преобразовать как можно больше этих глобальных переменных в локальных, так как это делает программу более гибкой и устойчивой к ошибкам. При большем количестве глобальных переменных больше вероятность возникновения побочных эффектов. Иногда трудно понять, когда делать переменную локальной для подпрограммы. Самый простой выбор - это счетчики управления в ко- роткой секции кода. Например, в следующем коде 10 FOR x=1 TO 10 20 PRINT X 30 NEXT переменная х используется только для управления циклом FOR/NEXT и может, следовательно, быть переделана в локальную переменную внутри подпрограммы. Другой тип переменных, которые являются кандидатами стать локальными, - это промежуточные переменные. Промежуточные пере- менные содержат результаты промежуточных вычислений. Промежуточ- ные переменные часто размазываются по программе и их трудно рас- познать. Например, переменная С12, показанная далее, содержит промежуточные результаты вычислений: 10 INPUT A, B 20 GOSUB 100 30 PRINT C12 40 END 100 C12 = A*B 110 C12 = C12 / 0.142 120 RETURN Тот же самый код в Турбо Паскале с С12 в качестве локальной переменной будет следующим program x; var a, b: real; function f2(x, y: real): real; var C12:real; begin C12 := c*b; Работа с Турбо Паскалем #2/2 = 196 = C12 := C12/0.142; C12 := C12; end; begin ReadLn(a, b); WriteLnC12(a, b); end. Помните, лучше иметь как можно меньше глобальных переменных, поэтому важно найти хороших кандидатов в локальные переменные. ЗАКЛЮЧИТЕЛЬНЫЕ СООБРАЖЕНИЯ ПО ТРАНСЛЯЦИИ ----------------------------------------------------------------- Хотя трансляция программ наверное наиболее скучная из всех задач программирования, она одна из наиболее часто встречающихся. Правильный подход состоит в том, чтобы, во-первых, понять принци- пы работы транслируемой программы. Когда вы поймете, как работает программа, ее легче перекодировать; вы будете знать, правильно ли работает новая версия программы. Кроме того, когда вы знаете транслируемую программу, работа становится более интересной, так как это уже не просто процесс замены символов. Работа с Турбо Паскалем #2/2 = 197 = ПРИЛОЖЕНИЕ В. РАЗЛИЧИЯ МЕЖДУ ТУРБО ПАСКАЛЕМ И СТАНДАРТНЫМ ПАСКАЛЕМ ----------------------------------------------------------------- В данном приложении обсуждаются некоторые наиболее важные различия между Турбо Паскалем и стандартным Паскалем. Решение расширить язык обычно принимается по серьезным соображениям, так как каждое расширение снижает мобильность программ и, следова- тельно, делает язык нестандартным. Обычно расширения делаются только из-за того, что стандартная форма языка имеет недостаток в крайне необходимых функциях. Причиной необходимости расширения стандартного Паскаля является отсутствие нескольких необходимых для программиста вещей и ожидающихся от языка таких, как строки, абсолютная адресация и средства связывания. Следует помнить, что Паскаль был введен, как учебный язык; фирма Борланд сделала его универсальным языком. Все расширения можно разбить на две категории: дополнитель- ные средства языка (ключевые слова) и дополнительные встроенные процедуры и функции. Мы рассмотрим сначала предложения, а затем некоторые наиболее важные процедуры и функции. Данное приложение заканчивается рассмотрением некоторых вещей, которые Турбо Пас- каль не поддерживает, а стандартный Паскаль поддерживает. Работа с Турбо Паскалем #2/2 = 198 = НОВЫЕ КЛЮЧЕВЫЕ СЛОВА ----------------------------------------------------------------- Следующие ключевые слова добавлены к стандартному Паскалю для создания Турбо Паскаля. absolute ----------------------------------------------------------------- Одним из наиболее важных расширений в Турбо Паскале является модификатор absolute. В стандартном Паскале у вас нет средств разместить переменную в памяти по заданному адресу. Это свойство не было нужно в учебных ситуациях, но в условиях разработки ре- ального программного обеспечения оно может быть существенным. Чтобы понять необходимость этого, представим, что вы хотите напи- сать программу, которая будет помещать информацию в разделяемую область памяти такую, как видео память. Чтобы сделать это, должно быть возможно задать, что переменная разделяется по заданному ад- ресу. Чтобы объявить, что переменная размещается по заданному ад- ресу, вы просто добавляете модификатор absolute в предложение декларации переменной после объявления типа. Для персонального компьютера IBM PC и совместимых с ним, адрес должен быть задан в форме сегмент:смещение; для других машин - в их стандартной фор- ме. Для IBM PC следующее предложение будет декларировать перемен- ную типа и ее адрес (сегмент 0, смещение 9000): count : real absolute 0:9000; Помните, вы должны быть очень внимательны при декларации переменных, чтобы избежать краха вашей программы или операционной системы. Вы также можете использовать модификатор absolute для того, чтобы две переменные стали разделять одно и то же адресное прост- ранство. Чтобы сделать это используйте имя переменной, которая будет разделять пространство, как приемник модификатора absolute. В следующем примере test и count будут разделять одно пространс- тво: test: integer; count: integer absolute test; external ----------------------------------------------------------------- Программы в стандартном Паскале не имеют возможности исполь- зовать функции или процедуры, которые написаны не на Паскале. Например, такое специальное устройство, как синтезатор речи, должно управляться специальным модулем на языке ассемблера. В Турбо Паскале решение такой проблемы состоит в том, чтобы позво- Работа с Турбо Паскалем #2/2 = 199 = лить внешним процедурам быть связанными с программой на Паскале. Это реализуется с помощью модификатора external и директивы ком- пилятора SL. Модификатор external осуществляет информирование программы на Паскале о том, что заданная функция или процедура является процедурой на языке ассемблера и что файл, который содержит ее, должен быть связан с программой на Турбо Паскале. Внешняя прог- рамма без тела должна быть декларирована в вашей программе на Паскале; нужен только заголовок для задания параметров, если та- ковые имеются. Например, для декларирования процедуры, называемой speech, с одним внешним параметром string вы могли бы написать procedure speech(word:string); external; Функция декларируется точно также. implementation ----------------------------------------------------------------- Ключевое слово implementation было добавлено в версию 4 Тур- бо Паскаля и используется для раздельной компиляции программных модулей. Детали описаны в Приложении А. inline ----------------------------------------------------------------- Турбо Паскаль позволяет вам вставить команды на языке ас- семблера непосредственно в ваш исходный код на Паскале. Это может оказаться очень полезным как для взаимодействия со специальными устройствами, так и для написания очень быстрых процедур, которые используют средства поддержки Турбо Паскаля. Машинный код, который вы хотите вставить в вашу программу, должен идти после ключевого слова inline и быть заключен в круг- лые скобки. Каждый байт или слово отделяются обратным слешем. Простая арифметика может быть реализована с помощью знаков плюс и минус. Символ "*" определяет ячейку, на которую указывает счетчик адреса. Весь код вводится в виде чисел, то есть вы не можете ис- пользовать мнемоник как в ассемблере. Так как inline - это пред- ложение, оно оканчивается точкой с запятой. Например, inline ($C9/$E900); будет вводить три байта в вашу программу: $C9, $E9 и 0. Работа с Турбо Паскалем #2/2 = 200 = interface ----------------------------------------------------------------- Данное ключевое слово было добавлено в версии 4 Турбо Паска- ля для поддержки раздельной компиляции программных модулей и их связывания. Детали описаны в Приложении С. interrupt ----------------------------------------------------------------- Директива interrupt, добавленная к версии 4 Турбо Паскаля, сообщает компилятору, что процедура будет использоваться для об- работки прерываний. shr и shl ----------------------------------------------------------------- Ключевое слово shr обозначает сдвиг вправо, а shl - влево. Эти операции могут быть выполнены только над переменными целого типа. Они вызывают сдвиг битов в операнде на заданное число пози- ций влево или вправо. Этот тип операций применяется при написании специфических для системы кодов и драйверов устройств. Общая фор- ма данных операций следующая: <целое выражение> shr <число> <целое выражение> shl <число> где <число> должно быть в области от 1 до 15. Чтобы пронаблюдать выполнение операции, рассмотрим пример. Ниже дано двоичное представление целого числа, называемого count, значение которого равно 8. count: 0000 1000 После выполнения предложения count:=count shr 1; count будет иметь значение 4 и будет выглядеть следующим образом: count: 0000 0100 так как все биты были сдвинуты вправо на одну позицию. После выполнения count:=count shl 3; count будет иметь значение 32 и выглядеть следующим образом Работа с Турбо Паскалем #2/2 = 201 = count: 0010 0000 Как вы заметили, сдвиг вправо - это деление на 2, а сдвиг влево - это умножение на 2. Сдвиг влево и вправо часто использу- ются, как быстрые способы умножения и деления на числа, являющие- ся степенью 2. Тип string ----------------------------------------------------------------- В стандартном Паскале нет простых средств для работы с сим- вольными строками. В Турбо Паскаль был добавлен специальный тип символьных матриц, называемый string, который позволяет вам легко манипулировать со строками. Строки должны иметь длину от 1 до 256 символов. Например, для объявления строки с максимальной длиной 20 вы должны написать: test_string: string[20]; В отличие от матрицы символов, которая должна иметь фиксиро- ванную длину, переменная типа string может иметь любую длину большую 0 и меньшую или равную заданной максимальной длине. Работа с Турбо Паскалем #2/2 = 202 = uses ----------------------------------------------------------------- Ключевое слово uses добавлено в версии 4 Турбо Паскаля и ис- пользуется для помощи в обеспечении поддержки раздельной компиля- ции кусков программы и их связывания. Детали описаны в Приложении 6. XOR ----------------------------------------------------------------- XOR - это оператор, который может быть применен к операндам типа integer и boolean. В случае операндов типа integer он вызы- вает побитное выполнение операции исключающего ИЛИ над двумя це- лыми числами. Для операндов типа boolean определяется результат TRUE/FALSE (истина/ложь). В случае операции над целыми числами каждый бит результата устанавливается в соответствии с таблицей истинности XOR │ 0 │ 1 ────┼───┼── 0 │ 0 │ 1 ────┼───┼── 1 │ 1 │ 0 То есть бит результата устанавливается в 1 тогда и только тогда, когда бит одного операнда равен 1, а другого - 0. Например, 1011 0010 XOR 0110 1001 ───────── 1101 1011 В случае булевских операндов результат операции определяется следующей таблицей истинности: XOR │ F │ T ────┼───┼── F │ F │ T ────┼───┼── T │ T │ F То есть, результат операции исключающего или истинен (TRUE) тогда и только тогда, когда только один из операндов истинен. В случае, когда Х и Y являются булевскими переменными и Х равна TRUE, а Y - FALSE, результат операции X XOR Y будет равен TRUE. Работа с Турбо Паскалем #2/2 = 203 = НОВЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ ----------------------------------------------------------------- Турбо Паскаль имеет внушительный список процедур и функций, которые существенно облегчают жизнь программиста. Они как правило относятся к трем областям: управление экраном, работа со строками и взаимодействие с операционной системой. Некоторые наиболее час- то используемые из них будут коротко здесь рассмотрены. Экранные и графические процедуры ----------------------------------------------------------------- К основным экранным процедурам относятся следующие: Процедура Действие CrtEol Очистить до конца строки CrtExit Послать строку сброса терминала CrtInit Послать строку инициализации терминала CrtScr Очистить экран DelLine Удалить до конца строки GotoXY Позиционировать курсор по заданным координатам X,Y InsLine Вставить пустую строку в текущую позицию курсора LowVideo Слабая яркость изображения NormVileo Нормальная яркость изображения В версии 4 Турбо Паскаля эти процедуры запомнены в блоке Crt. В дополнении к экранным функциям, описанным выше, Турбо Пас- каль для персонального компьютера IBM PC включает полный набор процедур цветной графики, процедур реализации окон, которые могут быть использованы для придания вашим программам хорошего профес- сионального вида. В версии 4 Турбо Паскаля они содержаться в бло- ке Graph или Graph3. Строковая функция ----------------------------------------------------------------- Как уже говорилось ранее, одним из наиболее существенных расширений Турбо Паскаля являются переменные типа string. Для поддержки этого нового типа данных в Турбо Паскаль включен ряд специальных строковых процедур, которых нет в стандартном Паска- ле. Список их приведен далее. Процедура Действие Delete Удалить подстроку, заданную начальной позицией и длиной Insert Вставить одну строку в другую, начиная с заданной позиции Работа с Турбо Паскалем #2/2 = 204 = Str Преобразовать целое или действительное число в строку Val Преобразовать строку в действительное или целое число Функция Действие Copy Возвращает копию подстроки Concat Возвращает конкатенацию двух строковых параметров Length Возвращает длину строки Pos Возвращает начальную позицию подстроки в строке Процедуры взаимодействия с операционной системой ----------------------------------------------------------------- Турбо Паскаль имеет специальные процедуры, которые позволяют программе на Паскале вызывать процедуры операционной системы. Для операционной системы CP/М-80 существуют две процедуры, называемые Bdos и Bios, которые позволяют обратиться к операционной системе СР/М. Для IBM PC и операционной системы MSDOS процедура называет- ся MsDOS. В основном они работают одинаково. Через эти функции операционной системе передаются номер функции и параметры. В вер- сии 4 Турбо Паскаля эти функции находятся в блоке DOS. Работа с Турбо Паскалем #2/2 = 205 = ДОПОЛНИТЕЛЬНЫЕ РАЗЛИЧИЯ МЕЖДУ ТУРБО ПАСКАЛЕМ И СТАНДАРТНЫМ ПАСКАЛЕМ ----------------------------------------------------------------- Кроме расширений и добавлений к стандартному Паскалю Турбо Паскаль отличается от стандарта еще в ряде моментов. Если вы име- ете опыт программирования на Паскале, но новичок в Турбо Паскале, важно запомнить следующие различия. Процедура new, которая применяется для распределения динами- ческих переменных для использования с указателями, не будет при- нимать спецификации различных записей. Это можно обойти, если не- обходимо, использовав процедуру getmem. Оператор goto не должен задавать переход за пределы блока. То есть и метка и оператор goto должны находиться в одном блоке. Только для пользователей операционной системы СР/М-80: Рекурсивные подпрограммы не должны использовать параметров типа var. Функции get и put не реализованы: read и while были расшире- ны для выполнения дискового ввода/вывода. Ключевое слово packed может быть использовано, но оно не вы- зовет никакого действия. Кроме того, функции pack и unpack не ре- ализованы. Процедура page не реализована. Функции и процедуры не могут быть использованы в качестве параметров подпрограмм. Это редко используемое свойство стандарт- ного Паскаля часто вызывает путаницу. Работа с Турбо Паскалем #2/2 = 206 = ПРИЛОЖЕНИЕ С. ИСПОЛЬЗОВАНИЕ БЛОКОВ ТУРБО ПАСКАЛЯ ----------------------------------------------------------------- Одним из наиболее важных добавлений Турбо Паскаля, представ- ляемым версией 4, является блок (unit). Блок дает следующие четы- ре важных улучшения: 1. Программный код может быть теперь больше одного сегмента (64К). 2. Могут быть созданы библиотеки связанных процедур. 3. Большие программы могут быть разбиты на меньшие, связан- ные куски. 4. Реализация подпрограммы может быть скрыта от остальных частей программы. В данном приложении показывается, как создавать блоки и об- ращаться к ним из других частей программы. Если вы новичок в вер- сии 4, вы возможно захотите тщательно изучить приложение. Однако, оно не является заменой материала, данного в Справочном руководс- тве по Турбо Паскалю. СОЗДАНИЕ БЛОКА ----------------------------------------------------------------- Общая форма блока следующая: 1 unit <имя>; interface <общедоступные декларации переменных, процедур констант, типов и функций > implementation <приватные декларации и код> begin <необязательный код инициализации блока > end. Давайте коротко рассмотрим каждую часть. Имя блока должно быть тем же, что и имя файла, в котором со- держится блок. Программа использует имя для обращения к блоку. Секция interface блока используется для декларации общедос- тупных символов блока таких, как имена процедур и функций, пере- менные, константы и типы. Все, что декларировано в данном разделе блока, будет доступно любой программе, использующей блок. Помни- те, что в секции interface вы только декларируете символы, а не обеспечиваете процедуры или другие коды. Секция implemention используется для двух целей. Во-первых, здесь помещается весь действительный код. Во-вторых, здесь декла- рируются символы для использования в пределах блока и недоступные из других частей программы. Наконец, блок может предоставлять код инициализации, который Работа с Турбо Паскалем #2/2 = 207 = может быть использован для установки начальных значений или сос- тояний всего в блоке. Этот код инициализации помещается между предложениями begin и end, которые соответствуют этим предложени- ям в файле program. Если вы знаете язык Модула-2, то блок аналогичен тому, что в Модуле-2 называется module с соответствующими разделами definilion и implementation. Работа с Турбо Паскалем #2/2 = 208 = ИСПОЛЬЗОВАНИЕ БЛОКОВ ----------------------------------------------------------------- Для того, чтобы одна часть программы имела доступ к блоку, требуется использовать предложение uses. Предложение uses имеет следующую общую форму: uses <список блоков, разделенный запятыми>; Например, для доступа к процедуре в стандартном блоке Crt, в ва- шу программу необходимо включить предложение uses Crt; Помните, что, если один блок вызывает процедуры из другого блока, оба блока должны быть включены в список uses. Если блок В ссылается на блок А, то предложение uses должно быть включено в любую программу, использующую блок В: uses A,B; Код инициализации, если он существует, выполняется до выпол- нения любого кода в программе, которая использует блок. Когда включены два или более блоков, код инициализации выполняется по порядку, заданному в списке. Работа с Турбо Паскалем #2/2 = 209 = КОРОТКИЙ ПРИМЕР ----------------------------------------------------------------- Короткий блок, показанный ниже и называемый MyUnit, реализу- ет функцию, называемую InputInt, которая отображает строку подс- казки и возвращает целое число, введенное пользователем. unit MyUnit; interface {объявляет общедоступные символы} procedure InputInt(prompt: string; var i: integer); Implementation {собственно реализация символов} procedure InputInt; begin Write(prompt); Read(i); end; begin WriteLn('помещайте код инициализации здесь'); end. Для компиляции данного блока убедитесь сначала, что файл, содержащий его, называется MYUNIT.PAS, и затем скомпилировать его как если бы это была программа на Турбо Паскале. Турбо Паскаль скомпилирует блок, но не будет пытаться связать его или запустить на выполнение, так как распознает, что блок - это не вся програм- ма. Короткая программа, показанная далее, использует MyUnit: program UnitTest; uses MyUnit; var x: integer; begin InputInt('введите число от 0 до 32000: ',x); if (x mod 2)=0 then WriteLn('even') else WriteLn('odd'); end. Вы можете скомпилировать данную программу, как любую другую программу на Турбо Паскале, так как Турбо Паскаль автоматически осуществит связывание данного кода с кодом блока MyUnit. Вы можете иметь общедоступные символы, которые не являются Работа с Турбо Паскалем #2/2 = 210 = ни именами функций, ни именами процедур. Например, далее предс- тавлена улучшенная версия MyUnit, которая проверяет, умещается ли сообщение подсказки в одной строке. Если строка слишком длинная, то булевская переменная TooLong принимает значение TRUE, в про- тивном случае FALSE. unit MyUnit; interface {объявляет общедоступные символы} procedure InputInt(prompt: string; var i: integer); var TooLong: boolean; implementation procedure InputInt; begin if Length(prompt) > 75 then Toolong: = true else begin Write(prompt); Read(i); TooLong: = false; end; end; begin WriteLn('Помещайте код инициализации здесь'); end. Далее показана короткая программа, которая использует данный блок: program UnitTest; uses MyUnit; var x: integer; s: string; begin WriteLn('Введите подсказку'); ReadLn(s); InputInt(s, x); if not TooLong then if (x mod 2) = 0 then WriteLn('even') Работа с Турбо Паскалем #2/2 = 211 = else WriteLn('odd') else 2 WriteLn('Подсказка слишком длинная'); end. Работа с Турбо Паскалем #2/2 = 212 = ПРИМЕР СТЕКА ----------------------------------------------------------------- Чтобы дать вам лучшее представление о достоинствах блоков, разработаем блок, который содержит все необходимые коды для рабо- ты со стеком. Преимущество помещения стековых процедур в блоки заключается в том, что как матрица, используемая для хранения стека, так и переменная, используемая в качестве индекса по дан- ной матрице, объявляется в разделе implementation блока, таким образом осуществляется защита их от модификации другими частями программы. Таким образом стековые процедуры образуют "черный ящик", доступ в который сильно контролируется. Такой подход помо- гает избежать ошибок, которые вызываются непредвиденной модифика- цией стека другими частями программы. Блок stack показан далее. unit stack; interface procedure push(i: integer); function pop: integer; const MAX = 100; var StkError: boolean; implementation var stk: array[1..MAX] of integer; tos: integer; {содержит указатель на текущую вершину стека} procedure push; begin if tos < MAX then begin inc(tos); stk[tos]: = i; StkError: = false; end else StkError: = true; end; function pop; begin Работа с Турбо Паскалем #2/2 = 213 = if tos > 1 then begin pop: = stk[tos]; dec(tos); StkError: = false; end else begin StkError: = true; pop: = 0; end; end; {initialize tos} begin tos: = 1; WriteLn('Cтековые процедуры инициализированы'); end. Как вы можете видеть, общедоступными символами являются име- на подпрограмм posh и pop, переменной StkError и константы MAX. Переменная StkError устанавливается в значение FALSE, когда опе- рации push и рор выполняются успешно, в противном случае она ус- танавливается в значение TRUE. Матрица stk и индекс tos предназ- начены для внутреннего использования, так как они объявлены в разделе implementation блока. Следовательно, любая программа, ко- торая использует стековые процедуры, может поместить в стек и из- влечь из него целое число, проверить состояние переменной StkError и обратиться к константе МАХ, чтобы узнать размер стека. Однако, другие части программы не могут изменить содержимое мат- рицы stk и значение tos. Стек в действительности размещается в stk и доступ к нему осуществляется с помощью индексирующей пере- менной tos. Короткая программа, показанная далее, использует блок stack. program StkTest; uses stack; var i: integer; begin WriteLn('максимальная глубина стека равна' MAX-1); repeat Write('введите целое число (0 для выхода)'); Работа с Турбо Паскалем #2/2 = 214 = ReadLn(i); push(i); until (i=0) or (StkError); WriteLn('это содержимое стека'); repeat WriteLn(pop); until StkError; end. Данная программа позволяет ввести в стек до 99 целых чисел. Когда вы введете 0 или стек переполнится, программа отобразит со- держимое стека. Отметим, что при использовании стековых процедур в программе, не существует других способов обращения к стеку, кроме процедур push и рор.