Text
                    

Язык Паскаль в иллюстрациях
Ronald ^lcock The right of the University of Cambridge to print and sell all manner of books vias granted by Henr) Vlll in 1534 The University has printed and published continuously since 1584 CAMBRIDGE UNIVERSITY PRESS CAMBRIDGE NEW YORK NEW ROCHELLE MELBOURNE SYDNEY
Доналд ^\лкок явь-гк Перевод с английского А.Ю. Медникова под редакцией А. Б. Ходулёва Москва «Мир» 1991
ББК 32.973 А 50 УДК 681.3 Алкок Д. А 50 Язык Паскаль в иллюстрациях: Пер. с англ. - М.: Мир, 1991. - 192 с., ил. ISBN 5-03-001292-3 Книга английского специалиста, в которой в наглядной и оригинальной форме представлен стандарт языка Паскаль, имеющего реализации практически на всех современных ЭВМ. Изложение рассчитано на изучение языка. Для программистов разной квалификации. А 1702070000-104 041(01)-91 139-90 ББК 32.973 Редакция литературы по математическим наукам Учебное издание Доналд Алкок ЯЗЫК ПАСКАЛЬ В ИЛЛЮСТРАЦИЯХ Заведующий редакцией академик В. И. Арнольд. Зам. зав. редакцией А. С. Попов. Ст. научный редактор М. В. Хатунцева. Художник Ю. Урманчиев. Художник-график Ю. Н.Соустин. Художественный редактор В. И. Шаповалов. Корректор Т. М. Подгорная. Оригинал-макет подготовлен на персональном компьютере и отпечатан на лазерном принтере издательства "Мир". Подписано к печати 19.02.91. Формат 70X100 1/16. Бумага офсетная, Гарнитура литературная. Печать офсетная. Объем 6.00 бум. л. Усл. печ. л. 15,6.Уч.-изд. л. 16,65. Усл. кр.-отт. 31,57. Изд.№ 1/6498. Тираж 50 000 экз.Зак. 458. Цена 4р.60к. Издательство «Мир> .В/О «Совэкспорткнига» Госкомитета СССР по печати. 129820, Москва, 1-й Рижский пер., 2. Можайский полиграфкомбинат В/О «Совэкспорткнига» Госкомитета СССР по печати. 143200, Можайск, ул. Мира, 93. ISBN 5-03-001292-3 (русск.) ISBN 0-521-33695-3 (англ.) © Cambridge University Press 1987. © перевод на русский язык, Медников А.Ю., 1991
в?аадкл© ВТИЕ ®а?эз©^ (®/ткрыв эту книгу, не сразу понимаешь, что держишь в руках учебник по языку программирования Паскаль. На одной странице - какие-то падающие пирамидки из кубиков, на другой - переплетение стрелочек, на третьей - вроде бы програм- ма, но по ней почему-то ползает множество жучков. Все это - выразительные средства, делающие чтение книги не только полезным, но и по возможности простым и интересным занятием. Упомянутые жучки, в частности, указывают ошибочные места в программе. Этот символ происходит из омонимии английского слова bug, означающего одновременно и жука и ошибку в программе. Заде один немаловажный момент - расположение материала по страницам. Автор позаботился о том, чтобы каждый разворот представлял собой некоторый сравнительно обособленный фрагмент изложения. Читателю не придется бесконечно перелистывать страницы, сравнивая, скажем, начало коротенькой программы с ее продолжением на обороте. Благодаря широкому использованию наглядных образов и тщательно продуманной форме изложения автору удалось весьма компактно изложить стандартный Паскаль в полном объеме. [д|еобычный стиль книги создал существенные трудности при ее переводе. Первая проблема - переводить ли имена в программах. Большинство компиляторов с Пас- каля не позволяют использовать в именах русские буквы, поэтому было принято решение сохранить в программах английские имена, с тем чтобы читатель мог взять любую программу из книги и выполнить ее на своем компьютере. В необхо- димых случаях в программы добавлены комментарии на русском языке. Вместе с тем, практически все компиляторы позволяют использовать русские буквы (если, конечно, они есть на клавиатуре компьютера) в текстах, предназначенных для печати или обработки. Соответственно, такие тексты, как правило, переводи- лись, так что программы общаются с пользователем на русском языке. \|тобы сохранить компоновку материала, перевод выполнялся «страница в стра- ницу». Каждый разворот содержит ровно тот же материал, что и английский ори- гинал. Ради сохранения целостности разворотов примечания редактора перевода пришлось поместить в конце каждой главы. Они касаются в основном отличий версии Турбо Паскаль, наиболее распространенной на персональных компьютерах в СССР, от версии Acornsoft ISO Паскаль, на которую ориентировался автор. Типографский набор не подходит для издания перевода по тем же причинам, что и для оригинала. Не обладая, однако, терпением, необходимым для подготовки манускрипта, мы остановили выбор на компьютерных издательских системах, от которых отказался автор (см. его предисловие). При подготовке на персональ- ном компьютере оригинал-макета этой книги использовались компьютерные шриф- ты, разработанные в Институте прикладной математики им. М.В. Келдыша АН СССР. ц^нигу ввиду ее краткости и «занимательности» можно рекомендовать начинающим программистам - тем, кто уже имел дело с компьютером, но еще не поднаторел в изучении многотомных руководств по программному обеспечению, а также и тем, кто только вступает в мир компьютеров. При изучении книги желательно использовать компьютер, оснащенный каким-либо (годится любой) компилятором с Паскаля, с тем чтобы выполнять упражнения не на бумаге, а по-настоящему. ^]зык Паскаль - простой и стройный - весьма удачен как средство обучения программированию; именно для этой цели он и предназначался его создателем - Н. Виртом. (Впоследствии язык Паскаль нашел широкое применение и для созда- ния серьезных производственных программ.) Следует, однако, иметь в виду, что автор почти не касается общих вопросов программирования, сосредоточиваясь на изложении собственно языка. Для углубленного изучения программирования следует обратиться к дополнительной литературе. Ходулёв 5
НИЕ [Предисловие редактора ПЕРЕВОДА 5 Предисловие 8 Uq Принципы и ЧТО ТАКОЕ ПРОГРАММА 12 ПЕРВОЕ ЗНАКОМСТВО С ПАСКАЛЕМ 14 ВВОД ПРОГРАММЫ 15 КОМПИЛЯЦИЯ 16 ШАГИ ВЫПОЛНЕНИЯ 17 УПРАЖНЕНИЯ 18 ©СНОВНЫЕ ЭЛЕМЕНТЫ 19 ПУНКТУАЦИЯ 20 ПЕРЕМЕННЫЕ 22 КОНСТАНТЫ 22 СТАНДАРТНЫЕ ТИПЫ 23 ВЫРАЖЕНИЯ 24 ЗАЕМ (ПРИМЕР) 25 УСЛОВИЯ 26 ПОЛЯ 26 ГЕОМЕТРИЧЕСКИЕ ФИГУРЫ (ПРИМЕР) 27 ЦИКЛЫ 28 БЫЛАЯ СЛАВА (ПРИМЕР) 29 СИНУС (ПРИМЕР) 29 УПРАЖНЕНИЯ 30 Do (Синтаксис 31 СТИЛЬ НАПИСАНИЯ 32 ОБОЗНАЧЕНИЯ 33 ЭЛЕМЕНТЫ 34 КОМПОНЕНТЫ 35 СИНТАКСИС ВЫРАЖЕНИЯ 36 СИНТАКСИС ОПЕРАТОРА 37 СИНТАКСИС ПРОГРАММЫ 38 СИНТАКСИС ТИПА 39 ПРИМЕЧАНИЯ РЕДАКТОРА 40 Арифметика 41 ОПЕРАЦИИ 42 РАЗМЕР И ТОЧНОСТЬ 44 КОМПАРАТОРЫ 45 АРИФМЕТИЧЕСКИЕ ФУНКЦИИ 46 ТРИГОНОМЕТРИЧЕСКИЕ ФУНКЦИИ 47 ФУНКЦИИ ПРЕОБРАЗОВАНИЯ 48 ЛОГИЧЕСКИЕ ФУНКЦИИ 49 ФУНКЦИИ НАД ДИСКРЕТНЫМИ ТИПАМИ 50 ПРИМЕЧАНИЯ РЕДАКТОРА 52 & 7 ПРАВЛЕНИЕ - 53 БЛОК-СХЕМЫ 54 ОПЕРАТОР IF-THEN-ELSE 56 ЦИКЛ FOR 57 ЦИКЛ REPEAT 58 ЦИКЛ WHILE 58 ФИЛЬТР (ПРИМЕР) 59 ОПЕРАТОР CASE 60 АВТОМАТНЫЙ РАСПОЗНА- ВАТЕЛЬ (ПРИМЕР) 61 УПРАЖНЕНИЯ 62 ПРИМЕЧАНИЯ РЕДАКТОРА 62 Функции И ПРОЦЕДУРЫ 63 ОПРЕДЕЛЕНИЕ ФУНКЦИИ 64 ПРИМЕРЫ ФУНКЦИЙ 66 РЕКУРСИЯ 67 ПРОЦЕДУРЫ 68 СЛУЧАЙНЫЕ ЧИСЛА 70 СНОВА ЗАЕМ (ПРИМЕР) 72 ИМЕНА ФУНКЦИЙ КАК ПАРАМЕТРЫ 73 ССЫЛКИ ВПЕРЕД 74 ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ 75 ПОБОЧНЫЕ ЭФФЕКТЫ 76 ПРАВИЛА ВИДИМОСТИ 77 УПРАЖНЕНИЯ 78 ПРИМЕЧАНИЯ РЕДАКТОРА 78 ^ИПЫ И МНОЖЕСТВА 79 СТАНДАРТНЫЕ ТИПЫ 80 ОПРЕДЕЛЕНИЕ ТИПОВ 81 ПЕРЕЧИСЛЯЕМЫЕ ТИПЫ 82 ИНТЕРВАЛЬНЫЕ ТИПЫ 83 ТИП МНОЖЕСТВ И ПЕРЕМЕННЫЕ ТИПА МНОЖЕСТВО 84 КОНСТРУКТОРЫ МНОЖЕСТВ И ОПЕРАЦИИ НАД МНОЖЕСТВАМИ 85 ФИЛБТР2 (ПРИМЕР) 86 МУ-У-У (ПРИМЕР) 87 УПРАЖНЕНИЯ 88 ПРИМЕЧАНИЯ РЕДАКТОРА 88 (Do б^АССИВЫ И СТРОКИ 89 ЗНАКОМСТВО С МАССИВАМИ 90 СИНТАКСИС ОБЪЯВЛЕНИЯ МАССИВОВ 91 ПЛОЩАДЬ МНОГОУГОЛЬ- НИКА (ПРИМЕР) 92 ПРОВОДА (ПРИМЕР) 93 СОРТИРОВКА МЕТОДОМ ПУЗЫРЬКА (ПРИМЕР) 94 6
БЫСТРАЯ СОРТИРОВКА (ПРИМЕР) 96 УПАКОВКА 98 ЗНАКОМСТВО СО СТРОКАМИ 99 ФОКУС (ПРИМЕР) 100 СИСТЕМЫ СЧИСЛЕНИЯ (ПРИМЕР) 102 УМНОЖЕНИЕ МАТРИЦ (ПРИМЕР) 105 НАСТРАИВАЕМЫЕ ПАРАМЕТРЫ-МАССИВЫ 106 УПРАЖНЕНИЯ 108 ПРИМЕЧАНИЯ РЕДАКТОРА 108 Записи юэ знакомство с ЗАПИСЯМИ 110 СИНТАКСИС ЗАПИСЕЙ 111 ПЕРСОНАЛЬНЫЕ ЗАПИСИ (ПРИМЕР) 112 ОПЕРАТОР WITH 116 ЧТО ТАКОЕ ВАРИАНТЫ 118 УПРАЖНЕНИЯ 120 ПРИМЕЧАНИЯ РЕДАКТОРА 120 Ц(0)оС{рАйЛЫ 121 ЧТО ТАКОЕ ФАЙЛЫ 122 ОТКРЫТИЕ ФАЙЛОВ 124 ТЕКСТОВЫЕ ФАЙЛЫ 125 ПРОЦЕДУРЫ WRITE И WR1TELN ДЛЯ ТЕКСТОВЫХ ФАЙЛОВ 126 ПРОЦЕДУРА PAGE ДЛЯ ТЕКСТОВЫХ ФАЙЛОВ 126 ПРОЦЕДУРЫ READ И READLN ДЛЯ ТЕКСТОВЫХ ФАЙЛОВ 127 БЕЗОПАСНОЕ ЧТЕНИЕ 128 ПРОЦЕДУРА GRAB ДЛЯ БЕЗОПАСНОГО ЧТЕНИЯ 130 ДВОИЧНЫЕ ФАЙЛЫ, ПРОЦЕДУРЫ PUT И GET 134 СЖАТИЕ (ПРИМЕР) 136 СВОЙСТВА ФАЙЛОВ, СВОДКА 137 УПРАЖНЕНИЯ 138 ПРИМЕЧАНИЯ РЕДАКТОРА 138 ИИоСОнТЕРАКТИВНЫй ВВОД 139 ДИАЛОГ 140 ПРОБЛЕМА ЗАГЛЯДЫ- ВАНИЯ ВПЕРЕД 141 ПРОБЛЕМА БУФЕРА 142 ПРОБЛЕМА КОНЦА ФАЙЛА (EOF) 143 ПРИМЕЧАНИЯ РЕДАКТОРА 144 и^О^ИНАМИЧЕСКАЯ ПАМЯТЬ 145 ДИНАМИЧЕСКАЯ ПАМЯТЬ 146 ПРОЦЕДУРЫ NEW И DISPOSE 148 СТЕКИ И ОЧЕРЕДИ 150 ОБРАТНАЯ ПОЛЬСКАЯ НОТАЦИЯ 152 RAMDdlWn (ПРИМЕР) 154 ПРОСТЫЕ ЦЕПИ 155 КРАТЧАЙШИЙ ПУТЬ (ПРИМЕР) 156 КОЛЬЦА 160 АСТРА (ПРИМЕР) 162 ДВОИЧНЫЕ ДЕРЕВЬЯ 164 ОБЕЗЬЯНЬЯ СОРТИРОВКА (ПРИМЕР) 166 УПРАЖНЕНИЯ 168 ПРИМЕЧАНИЯ РЕДАКТОРА 168 t/Зо^ИНАМИЧЕСКИЕ СТРОКИ 169 ПРОГРАММЫ ОБРАБОТКИ СТРОК 170 READSTRING 172 WRITESTRING 172 MIDDLE 173 CONCAT 174 COMPARE 175 INSTR 176 PEEK 176 POKE 177 ОБРАТНЫЙ СЛЕНГ (ПРИМЕР) 178 ТЕХНИКА ХЕШИРОВАНИЯ (ПРИМЕР) 180 HASHER (ПРИМЕР) 182 Литература 184 (Краткая справка 185 СТАНДАРТНЫЕ ПРОЦЕДУРЫ 185 СТАНДАРТНЫЕ ФУНКЦИИ 186 СИНТАКСИС 187 Указатель юо 7
йКИЕ ^|зык программирования Паскаль был разработан профессором Цюрихского Федерального технологического института (ЕТН) Никлаусом Виртом. Предвари- тельное описание появилось в 1968 году. С тех пор Паскаль становился все более и более популярен, причем не только как язык для обучения принципам программирования, но и как средство создания достаточно сложного программного обеспечения. 1ДЗ этой книге дано полное описание версии языка, определенной стандартом BS6192: Спецификация языка программирования Паскаль. Этот стандарт разрабатывался так, чтобы обеспечить совместимость со стандартом ISO 7185 Международного Института Стандартов. Чтобы не уходить от действительности, я выполнил программы, приведенные в этой книге, в трех системах: • ISO Паскаль фирмы Acornsoft • Pro Паскаль фирмы Prospero 9 Турбо Паскаль фирмы Borland International ой стиль изложения - картинки. Гораздо больше можно сказать рисунком: list[hashp ЫЩ I нежели сотней слов о хеш-адресах, указателях, записях и связанных списках. Но я думал также и о выборе правильных слов, имея в виду простоту и краткость. Материал скомпонован так, чтобы каждый разворот из двух страниц содержал законченный фрагмент. В результате вам не нужно переворачивать страницы, встретив, например, ссылку из текста на диаграммы. При такой компоновке, а диаграммы здесь являются столь же важным элементом, как и сам текст, слова необходимо размещать строго на своем месте. Это одна из причин использования рукописного текста - в таких условиях просто легче пользовать- ся пером, нежели типографским набором. (Современный автор, использующий текстовые процессоры и автоматизированный набор, - все это заметно упрощает процесс подготовки и компоновки текстового материала, - поддается соблазну и думает: «Как бы мне изложить эту идею без рисунка?», тогда как на самом деле ему следовало бы задаться вопросом: «Как мне заменить все эти скучные слова одним рисунком?».) (^одержание книги излагается в стиле руководства по языку программирования. В главе 1 приводится пример для начинающих - демонстрируется сама идея хранимой программы. В гл. 2 дается краткий обзор элементов программирования (переменные, стандартные типы, выражения, условия и циклы). У тех, кто писал программы на других языках, все это не должно вызвать затруднений. Материала этих двух глав вполне достаточно для того, чтобы все остальные возможности Паскаля излагать при помощи законченных программ. 8
[j\iaBa 3 - короткая, но - важная. В ней вводится система обозначений, которая используется на протяжении всей книги для описания синтаксиса операторов и элементов Паскаля. Эта нотация является смесью формы Бэкуса-Наура и диаграмм, имеющих вид железнодорожных путей. Я полагаю, что такая структура хорошо воспринимается зрительно, не теряя в строгости. Ср]ачиная с гл. 4, каждая возможность Паскаля вводится в контексте работаю- щей программы. Более длинные программы служат не только для демонстрации средств Паскаля, но также и для иллюстрации фундаментальных понятий программирования, к которым, например, относятся быстрая сортировка, рекурсия, кольца, двоичные деревья и хеширование. ^1\ольше всего мне пришлось ломать голову над проблемой интерактивного ввода. Паскаль был разработан во времена перфокарт и магнитных лент. Логика операторов WRITE и READ не позволяла программам запрашивать ввод данных с клавиатуры. Теперь такая возможность организации диалога доступна - читатели этой книги, вероятно, будут выполнять примеры из книги в интерактивном режиме. К сожалению, в различных версиях языка Паскаль эта проблема решена по-разному. Поэтому в моих примерах используется наипростейшая организация ввода и указываются те места, где читателю, работающему с интерактивной системой, следует поставить запросы, облегчающие использование программы. Вопросам, которые могут возникнуть при попытке интерактивного использования Паскаля, я посвятил короткую гл. И. Зсли на первых порах пунктуация программ, написанных на Паскале покажется Вам излишней, и вы обнаружите, что Вас уводит вправо, то не отчаивайтесь, скоро вы к этому привыкнете. Когда вы дойдете до записей, вновь засияет солнце. Добравшись до указателей (и научившись собирать цепочки, стеки, кольца w деревья), Вы почувствуете свою преданность Паскалю. Лекарства от любви к Паскалю еще не найдено. Доналд Алкок Ноябрь 1986 9
благодарности {•/та книга должна была писаться вместе с соавторами, сначала с Колин Дэй, затем с Ричардом Кайтом. Однако все попытки к совместной работе разбились о сугубо индивидуальную природу рукописного труда. Так или иначе, настоящая книга, вероятно, удалась. Моя сердечная благодарность им обоим. благодарю также Пола Шеринга из компании Euro Computer Systems Ltd. за предоставленную мне возможность работать на компьютерах его фирмы и за оказанную помощь при выполнении программ в системах Pro Паскаль и Турбо Паскаль. Первоначально я разрабатывал программы в системе Acorn ISO Паскаль. 3 заключение я выражаю благодарность моему старшему сыну Эндрю за разра- ботку программы, которую я использовал при составлении и сортировке предметного указателя к этой книге. 10
и ЧТО ТАКОЕ ПРОГРАММА ПЕРВОЕ ЗНАКОМСТВО С ВВОД ПРОГРАММЫ компиляция ШАГИ ВЫПОЛНЕНИЯ ПАСКАЛЕМ
w© «в DWiWM ЕСЛИ ВЫ ХОТЬ НЕМНОГО ЗНАКОМЫ С ПРОГРАММИРОВАНИЕМ. ПЕРЕВЕРНИТЕ СТРАНИЦУ <М1а ляру поручили покрасить крышу и стенки этого бака для бензина. Каким образом он сможет определить, сколько ему потребуется банок с краской, если у него нет под рукой компьютера? 236.0' [Jпроизводитель красок утверждает, что емкости одной банки достаточно для покраски поверхности площадью 236.0 [£5спомним, 4jo площадь круга вычисляется по формуле яг2 (где г - радиус круга) или яд /4 (где d - диаметр круга). Вспомним также, что длина окружности равна яё. Таким образом, маляр может вычислить площадь крыши (top) = 3.14 * 35.02 : 4 = 961.63 площадь стенки (wall) - 3.14 * 35.0 * 8.5 - 934.15 (Ллощадь поверхности , которую надо покрасить, равна сумме двух площадей, подсчитаных выше. Разделив величину площади на емкость одной банки, маляр получит необходимое ему количество банок: банки (pots) = ( 961.63 + 934.15 ) : 236 = 8.03 ^-[]исло с дробной частью называется вещественным (real). Разумеется,нельзя купить часть банки с краской. Поэтому количество банок следует округлить до ближайшего большего целого числа (integer). Для этого отбросим дробную часть и прибавим 1 полные банки (fullpots) = 8((jp S +1=9 решение ^□сли при других размерах количество банок получилось бы равным 8.00 вместо 8.03, то в ответе все равно было бы 9. Это не вполне точно, но маляр, вероятно, будет чувствовать себя уверенней в такой ситуации, чем если бы у него было всего 8 банок. Теперь представим себе, что маляр захотел решить эту задачу в общем виде, т. е. сделать так, чтобы при возникновении подобной задачи еще раз потребовалось бы лишь ввести несколько чисел и «нажать кнопку», чтобы получить ответ. 3 diameter аведем несколько небольших коробочек, в которых будут *—1~ |—* храниться числа. Коробочки ______ будут снабжены именами. f________________ height Содержимое коробочек в разных --------—' ~~Т" задачах будет различным. coverage 12
не забыть про коробочки, где будут храниться точные результаты: Р‘ промежу- top wall —11— pots fullpots ЛЬК для чтения Списку инстру ии Выглядит он так: □приближенное значение п будем хранить в специальной коробочке. Это значение одно и то же вне зависимости от размеров бака или емкости банки ~ поэтому коробочка на замке. называемому программой ~ можно дать имя. ЪапертМе коробочк1 открытые ] коробочки 4 ооъявам именам гсех используемых} коробочек Ч '""ри укажем типы ( хранящихся^ чисел у PROGRAM painter (введем (INPUT) данные, выведем (OUTPUT) результат); константы (CONSTANTS) pi « 3.14 { можно использовать, но не изменять }; переменные (VARIABLES) tj diameter, height, coverage, j top, wall, pots, fullpots; все коробочки содержат вещественные \ числа (REAL), за исключением fullpots, предназначенной для целых (INTEGER). Начало инструкций ☆ прочесть исходные данные: записать в коробочки diameter, height, coverage соответствующие числа; ☆ в коробочку top поместить результат умножения содержимого коробочки pi на квадрат содержимого коробочки diameter, деленный на 4\ ☆ в коробочку wall поместить результат перемножения содержи- мого коробочек pi, diameter, height; ☆ в коробочку pots поместить сумму чисел из коробочек top и wall, поделенную на число из коробочки coverage; ☆ в коробочку full pots поместить число с отброшенной дробной частью из коробочки pots, сложенное с 1; ☆ Напечатать сообщение маляру (’ВАМ НЕОБХОДИМО \напечатать число из коробочки fullpots,’ БАНОК* ); Конец инструкций. jQlycTb строка исходных данных состоит из чисел __ ™ 23б:о | . ели вы поработаете за компьютер и сами проделаете все инструкции программы, то результатом будет записка для | ВАМ* НЕОБХОДИМО 9 БАНОК............... f^jpuxue исходные данные, естественно, приведут к другому результату. В этом заключается суть программы ~ программа - это обобщенные вычисления. 13
Q©9 ПЕЙ 8идв©ба@а0® © йммж\ ПЕРЕВОД ПРОГРАММЫ, ПРИВЕДЕННОЙ НА ПРЕДЫДУЩЕЙ СТРАНИЦЕ Инструкции на предыдущей странице слишком многословны, и поэтому их нельзя использовать как программу для компьютера. Однако эти инструкции можно без потери информации переписать на Паскале. SJacTO повторяющееся словосочетание «содержимое коробочки* можно безболезненно опустить. Например, третья инструкция программы будет теперь выглядеть так * в коробочку wall поместить результат вычисления: pi умножить на diameter умножить на height; вместо словосочетания «в такую-то коробочку поместить результат вычисления:* будем писать просто название коробочки с символом * где читается как «принимает значение». Теперь заменим слова «умножить*, «сложить», «вычесть», «поделить» на знаки ♦, Третья инструкция станет совсем короткой * wall pi ♦ diameter * height и будет читаться как «wall принимает значение pi умножить на diameter умножить на height». объявления INTEGER; разделители) (ин^рукц^ 3 Паскале существуют и другие сокращения, речь о которых пойдет позже, как и о некоторых важных правилах пунктуации. Однако уже сейчас мы можем записать нашу программу на Паскале._______ PROGRAM painter (INPUT, OUTPUT); CONST pi - 3.14; VAR diameter, height, coverage, top, wall, pots: REAL; fullpots: BEGIN READ (diameter, height, coverage); - top pi ♦ SQR(diameter) / 4.0; wall pi * diameter * height; pots (top + wall) / coverage; fullpots TRUNC(pots) + 1; WRITE (’ВАМ НЕОБХОДИМО’, fullpots, END. &>QR( ) и TRUNC( ) - функции из & набора функций, заранее пре- ^^^^^\дусмотренного в Паскале, ’ БАНОК’) Объявления и инструкции этой программы в точности соответствуют объявлениям и инструкциям программы на обычном языке. (^мысл появления прописных и строчных букв скоро будет разъяснен. 14
зз©& тамжад ПАСКАЛЬ СТАНДАРТИЗОВАН, КЛАВИАТУРЫ КОМПЬЮТЕРОВ РАЗЛИЧАЮТСЯ Ииже других похожи нарисована клавиатура обычного домашнего компьютера. Клавиатуры персональных компьютеров и терминалов систем с разделением времени на эту. пробела ныдвшэ 3 правой части клавиатуры обязательно есть кнопка, на которой написано enter, return, ввод или нарисовано ЮТ . Это - кнопка перехода на новую строку. Любая клавиатура имеет клавиши от А до Z, цифры от 0 до 9, точку, запятую, двоеточие и точку с запятой, а также знаки арифметических операций /, которые используются в нашей программе. *» [Йеред разному вводом программы необходимо загрузить редактор и это делается по- в разных системах. Если используется Acornsoft Паскаль на компьютере ВВС модели В, то надо набрать EDIT и нажать llreturQ В Pro Паскале используется встроенный редактор аналогичный Word Star, в Турбо Паскале надо нажать кнопку 1ТЁ1. СДаходясь в редакторе, безбоязненно вводите текст программы: у вас всегда есть возможность стереть или исправить неверно введенный символ. На большинстве компьютеров это делается кнопкой IldeteteJ или или ____ При вводе программы, однако, необходимо внимательно следить за пунктуацией в программе, которая в языке Паскаль довольно неочёвидна. возможности редакторов могут сильно отличаться. Редактор Турбо Паскаля основан на текстовом процессоре Word Star. Вначале любой редактор немного страшен, но с появлением опыта работы даже самый «страшный» редактор становится не так уж плох. Терпение и настойчивость. [/Различием между прописными и строчными буквами можно пренебречь: вводите программу либо с нажатой, либо с отжатой кнопкой 1фр^Д$с1{||. Единственное место в программе, где размер букв может оказаться важен - это строка write (’Вам необходимо’, fullpots,’ банок’) где слова внутри апострофов в точности воспроизводятся при выводе результата. [^водя программу, имейте в виду, что компьютер не выполняет инструкций программы и вовсе может не знать, что вводится программа на Паскале; для компьютера это просто файл. Можно вводить даже «Гори, гори, моя звезда» на португальском - компьютер будет невозмутим. 15
в© Г®ЛЙ 1ЛЯШЖ1 И ПРОБЛЕМА ВВОДА ДАННЫХ С КЛАВИАТУРЫ □□рограмму на Паскале в отличие от программы на Бейсике, которая запускается командой RUN, надо предварительно скомпилировать. Компиляция означает перевод исходной программы с языка Паскаль в объектную программу - на язык компьютера. При запуске программы, вычисления производятся по программе в объектном коде, а не по исходной программе. ЦЦосле компиляции имеются две версии программы: одна на Паскале, другая на языке компьютера (или близком к нему). Если посмотреть на объектную программу, то на экране будет просто тарабарщина. □Паскаль выполняется быстрее Бейсика, потому что объектная программа на языке близком к языку компьютера (или непосредственно в командах компьютера) выполняется весьма эффективно, в то время как инструкции программы на Бейсике интерпретируются в исходном виде. Платой за выигрыш в скорости выполнения скомпилированной программы служат неизбежные затраты времени на компиляцию и связанные с этим неудобства. Правда, в большинстве систем предусмотрена возможность сохранения объектных программ, а значит и повторного их выполнения без рекомпиляции. На следующей странице показан случай, когда копия скомпилированной программы сохраняется на диске. <^)тапы от ввода программы до ее выполнения изображены напротив. Слева написаны команды, которые надо набрать на клавиатуре, чтобы выполнить очередной шаг. Эти команды зависят от системы, с которой вы работаете: выписанные команды вымышлены, однако они довольно типичны. MYSOURCE и MYOBJECT - имена исходной и объектной программ, придуманные программистом. □□оследний шаг - выполнение программы. Здесь предполагается ввод данных (input) с клавиатуры и вывод результатов (output) на экран. Это довольно распространенная схема ввода-вывода данных, стандартная в Паскале, но, разумеется, не единственная. Язык был разработан еще тогда, когда файлы хранились на магнитной ленте, ввод осуществлялся с перфокарт, а вывод - на печатающее устройство. Современные компиляторы позволяют выводить сообщения Паскаля на экран и вводить данные с клавиатуры. Если вы располагаете подобным компилятором, у вас не будет особых затруднений при выполнении примеров из этой книги. Однако, если ваша программа задает вопросы после получения ответов, загляните в гл. И, где проливается свет на возможные трудности и пути их преодоления. Вероятно, ваш компьютер не работает в интерактивном режиме ~ в этом случае вы все исходные данные придется хранить на диске, а схема пакетной обработки показана на рисунке. же сможете выполнить примеры, но не вводить с клавиатуры. Такая GO - ВЫПОЛНЕНИЕ INPUT на диске вам необходим* 9 банок ЛЬТА данные из файла «П АКЕТНЫЙ 16
НЕ'НИ КОМАНДЫ НЕ СТАНДАРТИЗОВАНЫ команды операционной системы на разных компьютерах могут отличаться, однако процесс, который изображен ниже, весьма типичен: □Предполагается, что программа в процессе выполнения запрашивает данные с клавиатуры. Если же исходные данные будут вводится из файла на диске, то их необходимо ввести, отредактировать и записать на диск так же, как и саму программу. LOAD PASCAL - ЗАГРУЗКА КОМПИЛЯТОРА Ъ Турбо Паскале\_ и аналогичных сис-\_ темах этот шаг выпол няется автоматически < часто эти шаги объединяются}^ в ~ run myobjecv 35 8.5 236 GCT^ ВЫПОЛНЕНИЕ COMPILE MYSOURCE, MYOBJECT - КОМПИЛЯЦИЯ (о^едактированныи' \ текст программы t*' с Паскаля одимо к ввод прямо с клавиатур объектна программ сообщения компилятора гг LOAD MYOBJECT - ЗАГРУЗКА 35 ва объектная^ nfWjipaMMi 17
Iq 1уыполните на компьютере программу о покраске. Это упражнение заставит вас воспользоваться редактором и компилятором. Знакомство с неизвестной системой всегда непростое дело: возможно это упражнение - самое трудное во всей книге. 18
©щадя ЫЕ эожга пунктуация ПЕРЕМЕННЫЕ КОНСТАНТЫ СТАНДАРТНЫЕ ТИПЫ ВЫРАЖЕНИЯ ЗАЕМ (ПРИМЕР) УСЛОВИЯ ПОЛЯ ГЕОМЕТРИЧЕСКИЕ ФИГУРЫ (ПРИМЕР) ЦИКЛЫ БЫЛАЯ СЛАВА (ПРИМЕР) СИНУС (ПРИМЕР)
СИНТАКСИС ОПРЕДЕЛЕН В СЛЕДУЮЩЕЙ ГЛАВЕ, СЕЙЧАС - ОБЩИЕ ПОЛОЖЕНИЯ Типичная программа на Паскале имеет следующую структуру: аголовок завершается точкой с запятой. В любом объявлении каждый список завершается точкой с запятой. Операторы отделены один от другого точкой с запятой. \£>лова BEGIN и END не являются операторами - они служат знаками пунктуации. Слово BEGIN выступает в качестве левой, a END - правой скобки. Так как они сами - знаки пунктуации, то точка с запятой после BEGIN и перед END не обязательна. В программах на Паскале слова BEGIN и END используются преимущественно для образования составных операторов. Составной оператор может быть использован в любом месте, где мог бы быть использован простой оператор. Пример составного оператора: loss (’Ура Г) (’Увы...’); IF profit > THEN WRITE ELSE WRITE точки с запято отсутствуют; внутри оператора в качестве знаков пунктуации слова IF, THEN, ELSE выступа 20
(®/ператоры разделены знаками пунктуации, поэтому расположение программы на странице с точки зрения компилятора значения не имеет. Вполне достаточно придерживаться двух правил: • Не писать слова вместе^^_________________________ PROGRAA^pa i n t er( IN PUT, OUTPUT); j CONSTpi-3.14; | • He разрывать'словр^^обелами или переходом на новую строку: PRO’GRAM painter ( INPUT^OUT^' PUT); CONST Qji = 3.1Д; (пробелы, котороые //ечтомечены жучками, разрешены) Ly остальном компилятору все равно, как будет расположена программа, однако, это совсем не безразлично для программиста. Польза отступов, проясняющих структуру программы, видна из вступительного примера. В этой книге не предлагается никаких специфических правил отступов; все принципы излагаются на примерах. Если же примеры из этой книги выполняются в системе, где есть автоматическое форматирование отступов, то внешне программы, вероятно, будут отличаться от моих. Взгляды на выбор отступов весьма различны, но все согласны в одном - отступы должны делать структуру программы максимально наглядной. (Загляните вперед, на с. 27, и вы увидите там программу с широким использованием отступов.) Кулова PROGRAM, CONST, VAR, BEGIN, END (и еще три десятка, знакомство с ними - впереди) называются зарезервированными словами. Зарезервированные слова нельзя расширять: , CONSTANT^ и сокращать: PROG 'painting ( INPUT, OUTPUT ); можно либо прописные, либо строчные буквы, либо те и другие книге используются и те и другие. О причинах этого речь [yjcпользовать вместе. В этой пойдет чуть позже. PROGRAM J>AINTER(INPUT,OUTPUT)] [ program painter(input,output); | Program PAINTER(INput,OUTput); днако в строках разница между прописными и строчными буквами существует: WRITEf ВАМ НЕОБХОДИМО’,fullpots,* БАНОК’jtfrj WRITE(’ Вам необходимо’,fullpots,’ Банок’) I ВАМ НЕОБХОДИМО 9 БАНОК; Вам необходимо 9 Банок [^Цмена задавайте такой длины, какая вам больше нравится. При этом только убедитесь что первые восемь символов во всех именах различаются. Так, например, некоторые компиляторы воспримут имена NUMBEROFMEN и NUMBEROFWOMEN как одинаковые. 21
пе;р;еже!Ннв'ге Незапертые маленькие коробочки из вступительного примера называются переменными. Простая переменная - это некоторая коробочка, имеющая имя и значение. НА ПРИМЕРЕ ПРОСТОЙ ПЕРЕМЕННОЙ ИЛЛЮСТРИРУЕТСЯ ОБЩЕЕ ПОНЯТИЕ ПЕРЕМЕННЫХ результате объявления ее в разделе VAR. 0 компьютере переменная создается в 1 Объявление одновременно специфицирует имя переменной и тип ее значения. Типы переменных обсуждаются напротив. VAR diameter: REAL; \*>имвол, состоящий из двоеточия и знака равенства ~ произносится как «принимает значение» ~ и указывает на то, что значение ( обычно результат вычисления выражения ) должно быть помещено в коробочку. wall pi*diameter*height; присваивание значения переменной «ппинимае значение» Lyo вступительном примере значения переменных оставались постоянными; переменным были присвоены определенные числа, которые не менялись. Однако в этой программе можно обойтись меньшим числом переменных. В предлагаемой версии используется несколько присваиваний переменной х: константа задается константа типа ЗапеРтая маленькая коробочка из вводного примера называется константой. Константы создаются в результате объявления их в разделе CONST. Тип констант автоматически, исходя из формы ее значения. Например, pi REAL Это определяется десятичной точкой в числе 3.14. CONST pi - 3.14; обоатитевним ие. а не 22
а «на INTEGER, REAL, CHAR, BOOLEAN Прелые (INTEGER) числа включают положительные, отрицательные и нуль: все константы типа INTEGER все переменные типа INTEGER • выражение, присваиваемое целой переменной, должно принимать целое значение; поэтому деление вида i/j использовать нельзя, см. ниже. нет десятичной точк CONST dozen-12, deer- -lz; VAR i, j, к : INTEGER; is— dozen*decr+6; j:- TRUNC(3.14)+7; runa'REAL нет десятичной очки результат Зеществецнные числа (тип REAL) - это числа с дробной частью. Вещественное число может быть отрицательным, нулем или все константы типа REAL должны положительным: есятичная точка важна [CONST "pf-ЗЛ415926; couple-2.tf; все переменные типа REAL должны быть объявлены так | VAR х, у, г : REAL; • выражение, присваиваемое вещественной переменной, может принимать целое или вещественное значение; перед присваиванием целые значения xs- pi/180.0; У! Z: автом. преоораз. isdnt. в^геа или автоматически преобразуются в вещественные. (В выражении допускается одновременное использование целых и вещественных членов, последствия этого описаны на следующей странице.) Дитеры (тип CHAR) - это буквы, цифры и символы; тип CHAR подразумевает одиночную литеру. • все константы типа CHAR при объявлении надо заключать в апострофы CONST р-’А’; q-’*’; г-'б*; | • все переменные типа CHAR должны быть объявлены так VAR a,b,c : CHAR; | • литеры можно сравнивать, результат будет логического типа. Литеры IF (р>а) AND (с>’Х’) THEN | т.д., ’0’<Т, Т<*2’ и т.д. сравниваются на основе их порядковых номеров: ’А’<’В’, ’В’<’С’ и Логические значения (тип BOOLEAN) - Паскале false «меньше чем> true.) это false (ложь) и true (истина). (В логические константы уже предусмотрены и нет нужды в их объявлении программистом логические переменные должны быть объявлены так FALSE 4 TRUE VAR ok, alive: BOOLEAN; • Логическое выражение должно приво- диться к значению true или false IF a-b THEN ok:- TRUE; IF alive AND ok THEN WRITE(’Great’) 23
СТАРШИНСТВО. СКОБКИ МЕНЯЮТ ПОРЯДОК ДЕЙСТВИЙ. ТИПЫ: INTEGER, REAL, BOOLEAN СДспользование арифметических выражений в операторах присваивания проиллюстрировано во вводном примере. Вот два из них: присваивание целого) порядок вычислений. Если бы в первом присваивание вещественного pots (top + wall)/ coverage; fullpots := TRUNC(pots) + 1 ; (^кобки обеспечивают необходимый примере скобки были опущены: pots ;= top + wall / coverage; то сначала было бы выполнено деление арифметических выражениях: выше * / ниже +- ---- 3° втором из приведенных примеров г переменной. Функция TRUNC( ) дает це , приоритет которого выше. Приоритет в U* / ^имеют^равный приорите^ f+^u^"ТТмею^равныit приорйтет\ гроизводится присваивание значения целой лый результат, а число 1 записано без десятичной точки; таким образом, оба слагаемых в сумме дают целое значение. Вообще, когда все члены выражения - целые, само выражение принимает целое значение. 4/ сформулированного выше правила существует важное исключение: деление (с использованием знака «/») всегда дает вещественный результат: б.о / 2 —» 3.25^^ (веществе нное)\ I 6/2 —» 3.0 (вещественное)^ еление нацело ~ нахождение частного и остатка ~ может быть выполнено при помощи операций DIV и MOD, речь о которых пойдет позже. Зыражение может включать в себя и целые и вещественные члены. Наличие хотя бы одного вещественного члена или знака «/> приводит к тому, что значение результата будет вещественным. Функции TRUNC( ) и ROUND( ) могут быть использованы для преобразования вещественного числа в целое. С^ункция SQR( ) возводит значение аргумента (записанного внутри скобок) в квадрат. В Паскале нет оператора возведения в произвольную степень (подобно- го t в Бейсике). Возведение в степень здесь осуществляется с использованием логарифмов - это показано напротив. Нематематики должны принять на веру, что вместо AtX в Бейсике, на Паскале можно написать EXP(LN(A)*X). |]^ому-то это может показаться странным, но знаки >, <= и т.д. тоже могут быть использованы как операции. Например, 1 > 2 имеет значение false, а 1 + 2 = 3- true. Выражения, содержащие подобные операции, принимают логические значения и называются логическими выражениями или еще условиями. В состав логических выражений могут входить логические операции NOT (не), AND (и), OR (или), а также члены, типа CHAR: ok :» (1=2) OR (ch >= A’); I IF ok THEN 24
34W\ ПРИМЕР ДЛЯ ИЛЛЮСТРАЦИИ АРИФМЕТИЧЕСКИХ ВЫРАЖЕНИЙ, ВОЗВЕДЕНИЯ В СТЕПЕНЬ, ПЕЧАТИ ВЕЩЕСТВЕННОГО РЕЗУЛЬТАТА ^!/\есячная выплата m по займу в s фунтов на п лет под процент р вычисляется по формуле: m = sr^ + О" 12((l+r)n- 1) где г = р -5- 100 >десь представлена программа вычисления m по известным значениям s, р и и. PROGRAM loans( INPUT, OUTPUT ); VAR m, BEGIN. s.p,_ n, r, a~j_REAL ; —v—. ЁЕсли^вашУП асколь*^ интерактивный, вдю вставьте READ(s, р, и); г := р/100; а := ЕХР( LN(1 + m := (s*r*a)/(12*(a-l)); m :» TRUNC(100*m + 0.5)/100;< WRITELN; WRITELN; -a WRITEf 'Взято £’, s:4:2); WRITEf под’,р:5:2,’%’); WRITEf на’,п:5:2,’лет’); WRITELN; WRITEf Месячная выплата £*,m:5:2); WRITELN; WRITEf Общая прибыль равна £’,m*n*12-s:5:2); WRITELN END.___________________________________________________ сюда подходящую под- :казк\ г)* и); [р \ч ерез логарифм) (округление. ^до^п^нниУ ^^^кажоый Х^новую строка сумма займа) процент срок, выплаты (лет) л □ P * ЮО ] □ (Ьг)п / | ^выплата ) WRIT1 ндми^ает :Ъ:^означает поле из 5 позиций с 2 5 позициями под У дробную часть \ V - »6‘М Al 2 3 4 5/ а т с 5 после выполнения программы будет выглядеть так: 14.5 10 исходные данные 99.99 Взято £99.99 под 14.5% на 10 лет Месячная выплата £ 1.63 Общая прибыль равна £95.61 Осли ваша версия Паскаля поддерживает интерактивную работу, то, чтобы на экране появился запрос необходимых данных, перед оператором READ вставьте еще оператор WRITE. □Приведенная выше программа вовсе не проверяет исходные данные. Если пользователь программы введет неверное число (например, букву о вместо числа 0), то программа не сработает. Большинство программ из этой книги столь же уязвимы в этом отношении. Причина такой уязвимости в том, что тщательная проверка данных заметно удлинит программы ~ цель которых - компактная иллюстрация различных аспектов программирования. Читателю оставляется в качестве упражнения сделать программы дружественными и устойчивыми к ошибкам. 25
РЕЗУЛЬТАТ ЛОГИЧЕСКОГО ВЫРАЖЕНИЯ ПОЗВОЛЯЕТ ВЫБРАТЬ ХОД ВЫЧИСЛЕНИЙ зависимости от результата программа может выполнять различные действия. Вот элементарный пример: IF stock<12 THEN WRITELNfЗакажите больше’); true /’закажите больше/ ^^огическое выражение stock < 12 может принять значение true или false. Если stock < 12 принимает значение false, то оператор WRITELN не выполняется: управление будет просто передано следующему оператору. количество операторов, входящих в оператор IF и выполняемых в зависимости от значения логического выражения, не ограничено, 4/словия, проиллюстрированные здесь, есть просто сравнение двух величин. Допускаются и более сложные условия, включающие логические операции AND (й), OR (или), NOT (не). Например, READ(key); IF key - ’у’ THEN BEGIN эти операторы выполняются, если ответом является 0 IreturnI f END ELSE BEGIN (initiab-’Е’) AND (initialc’L*) где initial - переменная типа CHAR, в которой записана начальная буква фамилии. Результат true будет означать, что в телефонной книге фамилия находится между Е и К. эти операторы выполняются, если ответ отличен от □ fRETURNI^ END; эти операторы выполняются послед независимо от ответа Tv V4"—' keen (х - у) OR (z >- 3); IF NOT keen THEN ------------ ^Значение логического выражения можно присвоить логической перемен- ной и лишь затем проанализировать. ПОЛЯ ДЛЯ ПЕЧАТИ ЧИСЕЛ, СТРОК И ДЕСЯТИЧНЫХ ДРОБЕЙ ‘ ^"Пирина поля и количество дробных десятичных разрядов указываются после двоеточия - это показано ниже: t 1 о v****v. г: i 123; п- 123.456; WRITELN( 1:8)'^^^ширКна^ол^ WRITELN( -г: ^2^ ^иГслд^поз1^ — WRlTELNCStringW^ v ь 7 X (только оля вещественных t ^4 печатаемый S текст прижима-^ ется к правому ^л^краюу— ^^дкр^глен^ Задавая ширину поля выражением, можно рисовать кривые, но об этом чуть позже. 26
ИСПОЛЬЗОВАНИЕ УСЛОВИЙ И ПЕЧАТЬ В ЗАДАННОМ ПОЛЕ начало) да буква Т ? (£^десь приведена блок-схема программы, позволяющей вычислять площади геометрических фигур: прямоугольника, треугольника, круга. Прочитать начальную букву фигуры: П, Т или ----Г ...Т ----{буква П ?> a rea-Vs(s-a )( s-b )( s-c ) Напечатать areqf* апечатать сообщения об ошибкё/- Стоп вычислить area-bd вычислить агеа-п вычислить s-(a+b+c)/2 30т программа, которая соответствует этой блок-схеме: PROGRAM shapes(INPUT, OUTPUT); CONST pi - 3.1415926; VAR letter: CHAR; s,area,a,b,c,d: REAL; ok: BOOLEAN; BEGIN ok:- TRUE; READ(letter); IF (letter—’П’) OR (letter-’n’) THEN BEGIN READ(b,d); area:- b*d END ELSE ELSE IF (letter-’T’) OR (letter-’т’) THEN BEGIN READ(a,b,c); s:- 0.5*(a+b+c); area:- SQRT(s*(s-a)*(s-b)*(s-c)) END IF (letter-’K’) OR (letter-’к’) THEN BEGIN READ(d); area:- pi*SQR(d)/4 END ok:- FALSE; печать числа в поле из 8 позиций с двумя знаками после десятичной точки ELSE __________, IF .ok THEN WRITEf Площадь равна’, area: 8:2) x, ELSE АМР1ТЕ(’Должно быть П, T или К’) END. f .......... 12345678 27
щижьа ФУНДАМЕНТАЛЬНОЕ ПОНЯТИЕ В ЛЮБОМ ЯЗЫКЕ ПРОГРАММИРОВАНИЯ (ДЛдожно заставить программу выполнять некоторую последовательность инструкций несколько раз подряд: PROGRAM xmas( OUTPUT ); VAR humbug: INTEGER; BEGIN ^FOR humbug s 1 TO 3 DO Q___WRITELNf Мы желаем Вам веселого Рождества’);' WRITELNfH счастливого Нового года’); END. —V— _________ Этот оператор выполняется один раз, когда цикл завершен Ч.Ие употребляется вот такая организация цикла: PROGRAM tables( INPUT, OUTPUT); VAR valu, product, multiplier : INTEGER; BEGIN READ(valu); FOR multipliers! TO 10 DO / BEGIN products multiplier*valu; \ WRITELN(muliplier:2,’*’,valu:2,’=’,product:4) 'END END. Этот оператор V— ^выполняется, когда humbug равно /, когда humbug равно 2 и когда humbug равно Замечание: ШР1ТЕЬН(’Что-нибудь’) эквивалентно WRITEf Что-нибудь’); J TWRITELN BEGIN и END - этоу'«скобки»' в которые заключен составной оператор, I 4 следующий за DO / [^дсли результат этих простых программ сразу не очевиден для вас, то перед дальнейшим чтением выполните их на компьютере. Циклы - основа программирования. ППикл FOR называется «конечным», потому что число повторений определено до начала цикла. Цикл REPEAT таковым не является. Часть между внешними BEGIN и END в последней из приведенных программ можно заменить следующим фрагментом: что-нибудь столь серьезное, что они вообще не выполнятся). Однако сущест- вует цикл, в котором проверка на выполнение осуществляется в начале цикла и, если эта проверка не проходит, то цикл пропускается: READ(valu); multipliers!; WHILE multiplier <= 10 DO 4 BEGIN productsmultiplier*valu; \ WRITELN(muliplier:2,’*’,valu:2,’=’,product:4) \ multipliersmultiplier+!; END Ал [Примеры уместного j использования цикла WHILE даны позже (пропускается, когда multi plier>10 28
ПРОГРАММА ДЛЯ ИЛЛЮСТРАЦИИ ЦИКЛОВ НА ПРИМЕРЕ ЗВЕЗДНОПОЛОСАТОГО ФЛАГА (1912г.) одной на программа 3 1912 году Американский флаг «Былая слава» имел 48 звезд (по союзный штат) и 13 полос (по одной на колонию). Нижеследующая печатает грубое приближние «Былой славы» 1912 г. Сегодня больше штатов, а, следовательно, и звезд больше. PROGRAM glory( OUTPUT); VAR row, col : BEGIN /^FOR col:=l TO О WRITELN; FOR rOW:=l TO / BEGIN FOR 4 IF INTEGER; подчеркивай^ 19 DO WRITE(*_5_*); ТО 19 DO (col<9) OR (row<7) THEN WRITEf* ’) ELSE WRITE(’__ WRITELN; END END. C01:=l 13 DO ПРОГРАММА ПЕЧАТИ СИНУСОИДАЛЬНОЙ КРИВОЙ, ИСПОЛЬЗУЮЩАЯ ЦИКЛ И ПЕРЕМЕННУЮ ШИРИНУ ПОЛЯ □предлагаемая программа печатает график функции sin(x). График размещается на экране таким образом, чтобы колебания происходили относительно середины экрана. Вся хитрость такой/печати - использование для задания ширины поля выражения. Ширина поля изменяется от строки к строке; в каждом строке звездочка прижимается к правому краю поля. PROGRAM sinuous(OUTPUT); CONST offset=20; scale=18; degreestep=8; VAR i: INTEGER; k: REAL; эти три значения рассчитаны на ТВ монитор; подберите их так, чтобы они подходили к о&орудованию^/^^ BEGIN k:= degreestep ♦ 3.1415926 / 180; TOR i:»0 TO MAXINT DO V—WRITELN(’*’:ROUND(off^et+scale*SIN(k*i))) радианы и^градусов 0,1,2,... градусы END. ширина поля- ажение 29
ЖНЕНИ IQ >Q >Q выполните программу о займах loans. Поэкспериментируйте с различными исходными данными. Если вы введете нулевое значение процента, то программа не сработает. Включите в программу проверку такой возможности и обеспечьте для этого случая печать правильных результатов. Если ваш Паскаль допускает интерактивный ввод, предусмотрите в сообщения пользователю о необходимых исходных данных. программе решению новой признак остановки Зыполните программу о геометрических фигурах shapes. Усовершенствуйте программу - сделайте после печати результата возврат к задачи. Пусть программа воспринимает букву Z как (т.е. будут распознаваться буквы П, Т, К и Z). выполните программу построения графика sinuous. затухающих колебаний, задав вместо y»sin х функцию Постройте график y=sin х / exp х . 30
3 вин СТИЛЬ НАПИСАНИЯ ОБОЗНАЧЕНИЯ ЭЛЕМЕНТЫ КОМПОНЕНТЫ СИНТАКСИС ВЫРАЖЕНИЯ СИНТАКСИС ОПЕРАТОРА СИНТАКСИС ПРОГРАММЫ СИНТАКСИС ТИПА
_ Л _ ЗАРЕЗЕРВИРОВАННЫЕ СЛОВА, СТИЛЬ НЖСЕЛИЙЯ ПРЕД0П₽ЕДЕЛЕ™™аи““и: придумывает программист <®)братите внимание на разницу в стилях написания самой первой программы; здесь она приводится еще раз: PROGRAM painting(INPUT,OUTPUT); CONST pi = 3.14; VAR diameter, height, coverage, top, wall,pots : REAL; fullpots : INTEGER; BEGIN READ( diameter, height, coverage); top:= pi ♦ SQR( diameter)/4.0; walb= pi * diameter * height; pots-= ( top + wall )/coverage; fullpots:= TRUNC( pots ) + 1; WRITE(*BaM необходимо*,fullpots,* банок*) END. [компьютер, работая с программой на Паскале, не различает прописные и строчные буквы. Исключением здесь являются буквы внутри апострофов. Таким образом, программа может быть целиком набрана прописными буквами: "'‘ProgramTainti ng( i n put, output); CONST PI = 3.14; _______________________________. или целиком строчными: var diameter, height, coverage, top, wall,pots : real; или прописными и строчными буквами вместе: __________ Г...TuiTPots:- TRUNQPots) t l; I Розница существенна в промежутке между апострофами: Ш ем не менее во вводном примере ~ как и во всей книге в дальнейшем ~ используются три стиля написания с тем, чтобы выделить три типа слов в Паскале: • PROGRAM, CONST, VAR, BEGIN,... - зарезервированные слова, которые ведут себя подобно знакам пунктуации. Каждое такое слово в Паскале имеет свое определенное значение • INPUT, REAL, READ, TRUNC,... - предопределенные имена; эти имена указывают на возможности, предоставляемые Паскалем для объявления файлов (INPUT), типов (REAL) или вызова полезных функций (WRITE( ), TRUNC( )). Однако программист вправе игнорировать подобные возможности и использовать эти имена для других целей • painting, pi, diameter, height,... - имена, которые составляет программист с целью идентификации переменных, констант, процедур и прочих объектов, которые еще появятся. Программу проще понять, если смысл имени или слова ясен из его написания. 32
©0©зт НЕНИЯ ДЛЯ ОПИСАНИЯ ФОРМ ЗАПИСИ ОБЪЯВЛЕНИЙ И ОПЕРАТОРОВ ПАСКАЛЯ Чг^орму написания и пунктуацию Паскаля помогают определить схематические обозначения. Обозначения, которые описаны ниже, - смесь двух общепринятых систем: диаграмм, похожих на железнодорожные пути, которые используются в нескольких книгах по Паскалю, и формы Бэкуса-Наура (БНФ), которая используется в определении Паскаля в стандарте ISO. Железнодорожные диаграммы зрительно могут показаться сложными при разборе сколько-нибудь нетривиальной конструкции; БНФ хороша для формального определения, но не столь удобна для быстрых справок или общей оценки синтаксической структуры. Представленная ниже система обозначений предназначена для быстрых справок и общих оценок практически без потери строгости. курсив Курсивные буквы используются в названиях определяемых объектов: цифра, оператор, выражение и так далее как и в БНФ означает «определено как...» БУКВЫ Эти литеры означают сами себя; в определениях они понимаются & + ( * / как есть. Буквы по желанию можно заменять на строчные: А на 012 и т.д. а, В на b, С на с и т.д. Вертикальные скобки, содержащие несколько строк, позволяют выбирать только одну строку имя, пер конст файла типа фун проц Эта стрелка говорит, что объект(ы), над которым она нарисована, не обязателен (может быть пропущен) Эта стрелка разрешает возврат ~ таким образом, можно выбрать еще один объект из вертикальных скобок или тот же самый Круг (или колбаска) содержит разделитель, который необходимо использовать при возврате к другому объекту. Отсутствие кружка означает отсутствие разделителя Подпись у имени означает, что именуется этим именем; будь то переменная, константа, файл, тип, функция или процедура. (Этот механизм уже из области семантики, а не синтаксиса.) Этот символ располагается перед примером вместо слова «например» [Некоторые слова, используемые в нижеследующих определениях, отличаются от тех, что используются в стандартных работах по Паскалю. В частности, я использую слово имя вместо идентификатор, терм вместо множитель, и мне не нужно слово, обозначающее слагаемое. Я использую слово компаратор вместо операции сравнения. 33 2-458
СИНТАКСИСА ПАСКАЛЯ* буква, цифра, символ, пробел, операция, компаратор буква А В С D Е F G Н I J К L М N О Р Q R S Т и V W X Y Z цифра О 1 2 3 4 5 6 7 8 9 'строчные V------- буквы эквивалентны прописным. НапримерГ^ div a DIV. Исключением^ являются цепочки литер. символ * t внимание, что апостроф и фигурные скобки {’} отсутствуют в определении символа. Они явно фигурируют в диаграммах 3 цепочках или комментариях могут также использоваться и другие имеющиеся на клавиатуре такие как £, I или $. символы, Добавьте сюда строчные и русские буквы, если их допускает компилятор. клавиша нажатая пробела^ один раз пробел Пробелы важны в цепочках и ком- ментариях. Переход на новую строку в цепочках и комментариях не 1) •) допустим. операция * DIV MOD AND '/операции, старшие по Тприоритету компаратор OR у операции, младшие по4 (приоритету, приоритет/p ниже, чем г у любой и, Коопераций Г IN 3 выражении 3+4*5 умножение (♦) вы- полняется перед сложением (+), потому что приоритет умножения выше. Чтобы изменить порядок операций, исполь- зуйте скобки, например, (3+4)*5. Зыражение 5-3-2 истинно, потому что оно рассматривается как (5—3)—2, а не как 5—(3-2). Другими словами, компаратор имеет более низкий приоритет. См. примечания редактора перевода в конце главы - Прим. ред. 34
ЙШ&О буква цифра имя буква СИНТАКСИСА ПАСКАЛЯ' имя, цифры, число, константа, переменная, цепочка, комментарий ► X ► H2SO4 ► h2so4 цифры ► 6 ► 0123444 переменная имя ► к ► аггау[6,2*к] ► person, age ► ptrt ► array[6][2*k] ► ’Вам необходимо’ ► ’£’ комментарий { буква цифра символ » пробел { ► {Комментарий программиста} ► (* Это тоже комментарий *) комментарий воспринимается как один пробел и может быть вставлен всюду, где допустим пробел. 35 2*
ИН И ЛОГИЧЕСКОГО ВЫРА- ЖЕНИЯ, НАЗЫВАЕМОГО ТАКЖЕ УСЛОВИЕМ «<Е)лементы» и «компоненты» синтаксиса Паскаля теперь могут быть объединены в определении выражения. Во вводном примере фигурирует несколько выражений, следующие два из которых типичны: (top + wall)/coverage; pi ♦ SQR(diameter)/4.0; ^^ыражение содержит один или более членов. Члены связаны между ками и операциями. Членом может быть имя переменной (например, ссылка на функцию (например, TRUNC(pots)) или что-нибудь перечисленного ниже. еще из собой скоб- top), или ( выражение ) NOT член имя конст цепочка перемени ая^. — имяфун ( ^выражение^ ) число NIL TRUE 6.75ЕЗ NIL ’kpg’ p6t epsilon TRUNC(pots) (a+b) 6750 [2*а..З*Ь] [ ] NOT TRUE (®)пределив операциями член, можно определить выражения как набор членов, объединенных и компараторами: выражение терм компаратор ► (pi SQR(diameter)/4) + (pi*diameter*height) УТТобг операцииЯЬ ^ЫизкийХ^ (высоког^приоритета^ (^приоритет* ЗыРажение> содержащее один или более логический член, называется логическим < в этом примере благодаряХ*—*^ приоритету скобки необязательны ! компараторов или единственный выражением или условием. ожно TRUE ^истинно. 3 операторах WRITE и WRITELN может выражения: также использоваться нестандартная форма нестандартное выражение : выражение . выражение ► WRITE(x:8:2) ► WRITELN(’*’: ROUND(offset+scale*SIN(k*i))) 36
«ЖЕ 0ЛЕ 0AS?(cY!>/A некоторые формы еще ILT W НЕ БЫЛИ ПРЕДСТАВЛЕНЫ иже приводится определение оператора. Некоторые из операторов упоминаются здесь впервые. REPEAT k оператор л UNTIL условие и WHILE условие DO оператор FOR имяпер = выРажение ТО DOWNTO выражение DO оператор ; END оператор присваивания ~ с меткой составной итератор оператор IF 100: area;= p*SQR(diameter)/4 BEGIN IF a>b BEGIN temp;=a; a:=b; b:=temp END THEN BEGIN temp:=a; a==b; bMemp END ; t:=a; ; a:=b; b: = t;.END пустые операторы 37
30т определение программы по принципу сверху вниз: программа PROGRAM имяС имяфа^ла )> блок „ ЧИТАЯ ПЕРВЫЙ РАЗ, 1 ЭГГИ ДВЕ СТРАНИЦЫ 1 МОЖНО ПРОПУСТИТЬ заметьте л Здесь: блок LABEL ^ци^ры; '"CONST имя - константа^ ^TYPE ^иля^тип^Г*' ""VAr” v имя~~тип~^ { продолжается на следующей строке } { продолжается } { продолжается } { продолжается } FUNCTION имя параметры : имя^,^п г г типа PROCEDURE имя параметры ; блок ; BEGIN к оператор , END { продолжается } Т»е: параметры * имЯтипа FUNCTION имя и ) мятипа {процедуры ( как параметрьачь Lфункций и r-^pPROCEDURE Г процедур1 имя ''параметры 38
Гейн ШЖ ЖИЛ для полноты ОПРЕДЕЛЕНИЯ ПРОГРАММЫ ПО ПРИНЦИПУ СВЕРХУ ВНИЗ Зот определение типа по принципу сверху вниз: тип имя типа дискретный Ъимя типа ХРАСКЕ[Г SET OF дискретный ARRAY [ дискретный ] OF тип ► REAL ► 0..6 ► tREAL ► PACKED SET 2) OF 0..6 ► ARRAY[m,0..6] OF REAL RECORD 'па/ш 'вариант^ T^END FILE OF тип ► RECORD a,b,c: REAL; i: 0..6 END ► FILE OF INTEGER дискретный :: = ► INTEGER константа.. константа ► (I,thou,thee,we,you,they) ► 0..6 ► ’A’..*Z* ► I..we и поля имя : тип ► nr, age: INTEGER; status: CHAR И этом завершается определение синтаксиса стандартного Паскаля (ISO). 39
(c. 34) Во всех известных мне версиях Паскаля допускается переход на новую строку внутри комментариев. 2) (с. 39) В конструкциях SET OF дискретный и ARRAY [дискретный] OF тип в качестве дискретный нельзя использовать тип INTEGER. 40
6 ОПЕРАЦИИ РАЗМЕР И ТОЧНОСТЬ КОМПАРАТОРЫ АРИФМЕТИЧЕСКИЕ ФУНКЦИИ ТРИГОНОМЕТРИЧЕСКИЕ ФУНКЦИИ ФУНКЦИИ ПРЕОБРАЗОВАНИЯ ЛОГИЧЕСКИЕ ФУНКЦИИ ФУНКЦИИ НАД ДИСКРЕТНЫМИ ТИПАМИ
ОПЕР- * / DIV MOD + - AND OR удобства, синтаксис операций здесь приведен еще раз. СДспользование представленных опера- ций поясняется на данном развороте. Если использование скобок не кажет- ся очевидным, то лучше вернуться к с. 36 - там изложен синтаксис выражений. IJUpH отсутствии скобок выражения вычисляются слева направо, сначала выполняются операции с высоким приоритетом, затем - с низким приоритетом. Для указания любого нужного порядка вычислений можно ставить скобки. Например, члены а*Ь/с и (а*Ь)/с будут вычисляться одинаково, а в члене а*(Ь/с) скобки влекут изменение порядка вычислений. © перации DIV и MOD предназначены для деления нацело; они позволяют OD сокращение^от modulo частное получить соответственно целое частное и остаток: WRITELN( 17 DIV 5, 17 MOD 5 ) положительных значений i и j выполняются следующие соотношения: (i DIV j) • j + (I MOD j) - i WR1TELN( (17 DIV 5)*5 + (17 MOD 5)) | { При неположительных значениях ситуация несколько слояшее. Второй операнд у операции МОР, например, не может быть отрицательным: WRITELN( 17 MOD " I Error _ 5) Допустимые сочетания приведены ниже: WRITELNI 17 DIV 5, 17 MOD 5 ); WRITELNI 17 DIV (-5)); WR1TELN( -17 DIV 5, -17 MOD 5 ); WRITELN( -17 DIV (-5)) 2 -2 3 -3 -3 3 Первый операнд может быть меньше по абсолютному значению: WRITELN( 5 DIV 17, 5 MOD 17 ); WRITELN( 5 DIV (-17)); WRITELN( -5 DIV 17, -5 MOD 17 ); WRITEtN( -5 DIV (-17)) Дели же делитель равен нулю или один из сообщение об ошибке: WRITELN( 17 DIV 0 *=*< WR1TELN( 17.0 ^OD 5 ' операндов - не целое, то появляется Error Error 42
(®)перации + и - могут использоваться как ’унарные’ операции (проще говоря, как знаки) перед целыми или вещественными выражениями: WRITELN( -2, +2*3 ); -2 6 WRITELN( -2.0:4:1 ). Иг// результат операций ♦, + и - будет целый, если оба операнда - целые: 6 5-1 гда - WRITELN( 2*3, 2+3, 2-3 ) и вещественный, если хотя бы один или оба WRITELN( 2.0*3:4:l ); WRITELN( 2+3.0:4:1 ) ^/L Операция / дает вещественный результат ~ даже если оба операнда являются целыми: WRITELN( 6/2:4:1 ); WRITELN( 6.0/2:4:1 ) Делитель не может быть равен нулю: WR1TELN( 6/0 4:1 ) ©перации AND и OR, применяемые к логическим операндам, дают логический результат. Если операнды не принадлежат логическому типу, сообщение об ошибке: то появляется WRITELN( 1 OR ^^2 );G^funoM CHAR WRITELN( ’A’ AND<^B^ утиспользование только V- лкпмпаратпрпв например, ’A’<’B’ Ъггог Error определяет результат применения операций 00|ижеследующая таблица истиности AND и OR к логическим операндам: AND второй true операнд false операнд true x/ X первый false X X OR второй true операнд false операнд true \/ у/ первый false у/ X удалее приведены некоторые примеры логических выражений. Обратите внимание на то, как оператор WRITELN печатает логические результаты словами. Эти слова могут быть записаны прописными или строчными буквами в зависимости от конкретной системы: WRITELN( TRUE AND TRUE , TRUE AND FALSE ); WRIT₽LN( FALSE AND TRUE , FALSE AND FALSE ); WRITELN( TRUE OR TRUE , TRUE OR FALSE ); WRITELN( FALSE OR TRUE , FALSE OR FALSE ); WRITELN((((l-2) OR (1+2-3) OR (1>2)) AND (2+3-5)) true false true true true 43
разлдар и ЦЕЛЫХ И ВЕЩЕСТВЕННЫХ I /Целое может быть положительным, нулем MAXINT хранит копию наибольшего целого, храниться. или отрицательным. Константа с именем которое может передаваться или MAXIN 32767 PROGRAM findout(OUTPUT); BEGIN WRITE(MAXINT) END. Определите чему оно равно у вас выполнив эту маленькую программу Значение 32767 - типично для систем, где целые хранятся как 16-разрядные слова. В случае 32-разрядного слова максимальное целое обычно равно 2147483647. 121сли в программе результат промежуточных вычислений целого выражения превы- шает величину MAXINT, то появляется сообщение об ошибке. Иногда этого можно избежать, добавив скобки, например, заменив i*j DIV к на i*(j DIV k). J оЛотя диапазон допустимых целых простирается от -MAXINT до MAXINT, вы, возможно, обнаружите, что и значение «-(MAXINT+1)» не приводит к ошибке. Это объясняется тем, что при традиционной кодировке ^целых в слове из п битов можно записать числа в диапазоне от -2П до 2n -1 (несимметричном относительно нуля). вещественное число может быть отрицательным, нулем или положительным. Его наибольшее абсолютное значение - 10 , точность, обычно, - 6 или 7 десятичных значащих цифр. В таких системах наибольшее положительное или отрицательное число будет около ± 100 000 000 000 000 000 000 000 000 000 000 000 000 Наименьшее положительное или отрицательное число будет около ± 0.000 000 000 000 000 000 000 000 000 000 000 000 01 Число 1 000 000 (в случае вышеуказанной точности) еще будет отличаться от числа 1 000 001, но не от 1 000 000.1 . ^Оисла хранятся в виде двоичных цифр (битов), а вовсе не в десятичном виде; в этом - неизбежная неопределенность двух предыдущих абзацев. Ниже диапазоны вещественных чисел представлен наглядно: 44
ИЛИ «ОПЕРАЦИИ СРАВНЕНИЯ*. ЛОГИЧЕСКИЙ РЕЗУЛЬТАТ ИЗ СОВМЕСТИМЫХ ОПЕРАНДОВ Удесь воспроизведен для удобства синтаксис компаратора. Смысл символов отвечает их написанию: например, >= означает «Больше или равно». Приоритет любого компаратора ниже чем приоритет любой операции. 4Jto6bi подчеркнуть разницу между операцией и компаратором, приведем еще раз и синтаксис выражения'. выражение = + ‘г*член-р компаратор + - Коопераций/ - ^(операций/ Сравнивать можно члены совместимых типов, при этом результатом будет логическое значение: - ......... WRITELN( 2>1, 2.0>1.0, TRUE>FALSE, ’AVB’fcfy ще RUE TRUE TRUE TRU1 венный способ сравне-^ гния значений типа СНА1 вещественные и целые члены с одинаковыми значениями взаимозаменяемы: WRITELNf 2>1.0. 2.0>1 ) Синтаксическая диаграмма выражения допускает только один компаратор. Однако, выражение в скобках - это член. Таким образом, включая еще компаратор, можно строить более сложные выражения: 45отя тип множество вводится в гл. 7, ниже для полноты изложения, представлены операции над множествами. «Друзья» и «знакомые» - это имена множеств, «приятель»- имя одного элемента множества. друзья = знакомые О <= >= IN друзья о знакомые истинно, если все друзья - знакомые, и все знакомые одновременно друзья (множества совпадают) истинно, если среди друзей не все знакомые или среди знакомых не все друзья (различные множества) друзья <= знакомые истинно, если все друзья - знакомые друзья >- знакомые истинно, если все знакомые - друзья йриятель IN друзья истинно, если приятель - друг МОТ(приятель IN друзья) истинно, если приятель не является другом 45
ЖЕ 3 функциях всегда использовались аргументы: х ABS( у ) ^ф^кция^ В Паскале же предпочтительнее термин параметра Параметры, которые используются ниже, - фактические параметры. Позднее мы определим формальные параметры. ПАРАМЕТР! Следующие две функции можно применять к целым параметрам, и в этом случае они возвращают целый результат. Этим функциям можно также передавать вещественный параметр, получая вещественный результат. выражение ) абсолютное (т.е, положительное) значение параметра WR1TELN( ABS(-2), ABS(O), ABS(2)); WRITELN(ABS(-2.0):4:l,ABS(0.0):4:l,ABS(2.0):4:l) выражение ) квадрат параметра WRITELN(SQR(-2),SQR(0),SQR(2)); WRITELN(SQR(-2.0):4:l, SQR(0.0): 4:1, SQR(2.0): 4:1) (®/стальные арифметические функции воспринимают целый или вещественный параметр; результат в любом случае будет вещественным: выражение ) квадратный корень WRITELN(SQRT(16):4:LlSQRT(0.64):4:l,SQRT(0):4:l); WRlTELN(SQRTH6))*j£ выражение ) натуральный логарифм WRITELN(LN(1):4:1.LN(2.718282):4:1.LN(7.5):4:1); WRITELN(LN(Q),qJ|^ LN(-l)o^C > выражение ) экспонента WRITELN(EXP(0):4:l,EXP(l):8:5,EXP(2.014903):4:l); WR1TELN(EXP(-1):7:4,EXP(LN(1OO):6:1) Г! IP, . . Д.Д57.9.. , 1QQA 46
ежкшяя ЙЭиже определяются тригонометрические функции. Аргумент любой из них может у* № быть целым или вещественным; результат в любом случае - вещественный. у радиан V = (МО/it) CONST PI-3.1415926; WRITELN( SIN(-PI/6):4:1); WRITELN( SIN(0):4:l); WRITELN( SIN(PI/2):4:1) аж» выражение ) АРКТАНГЕНС « угол в радианах, тангенс которого равен ...» arctan(p/a) - а радиан а WRITELN( ARCTAN(1E35):8:5); WRITELN( ARCTAN(0):4:l); WRITELN( ARCTAN(-1):8:5) 47
®PS©0P4\3©3^WSJ ИЗ ВЕЩЕСТВЕН- HUI \J D ULi/lDlil ц^огда целое значение присваивается вещественной переменной, оно автоматически преобразуется в веществен- ный тип и никакие функции для этого не требуются. Такое преобразование типов называется неявным. Обратного неявного преобразования нет: будет ошибкой пытаться присваивать переменной целого типа вещественный результат. VAR х,у : REAL; i,j : INTEGER; преобразуется в 3.0 10 в 10.0 х:= 2*3+4^преобразование' из у:= 3 j:= 2.0*3+4^!^’ j:=9/3«>I Щи|еред присваиванием целой переменной вещественного значения это значение следует преобразовать к целому типу отбрасыванием дробной части или округ- лением. Для этих целей служат функции TRUNQ ) и ROUND( ) соответственно. преобразует вещественное в целый тип, отбрасывая дробную часть WRITELN(TRUNQ3.1), TRUNQ3.8)); WRITELN(TRUNQ-3.1), TRUNC(-3.8)); WRITELN(TRUNQ3.0), TRUNQ-3.0)) 3 -3 -3 3 -3 3 (вещественное) выражение ) преобразует вещественное в целый округляя до ближайшего целого WRITELN(ROUND(3.1), ROUND(3.8)); WRITELN(ROUND(-3.1), ROUND(-3.8)); WRITELN(ROUND(3.0), ROUND(-3.0)) WRITELN(ROUND(3.5), ROUND(-3.5)); тип, i/десь возможны недоразумения. Пусть вещественная переменная х имеет значение 3.499999. Если это значение напечатать с использованием оператора WRITE(x:8:5), то получится 3.50000, в то время как WRITE(ROUND(x)) даст 3, а не 4. Это затруднение можно обойти при помощи небольшой поправки, например WRITE(ROUND(x+0.000001)) (в предположении, что значение переменной х заведомо положительное). [Дрименять функции TRUNQ ) и ROUND( ) к параметрам целого типа нельзя: WRITELN( TRUNQ3));<HJ WRITELN( ROUND(3))oS Error Error 48
ВОЗВРАЩАЮТ ЗНАЧЕНИЕ TRUE или FALSE С^ункция ODD( ) используется для проверки четности или нечетности результата целого выражения. выражение ) возвращает TRUE, если параметр - нечетный ~ в противном случае возвращает FALSE WRITELN(ODD(3), ODD(2), WRITELN(ODD(-3), ODD(-! WRlTELN(QDD(3.0))a^( ( (Следующие функции служат для определения конца строки или конца файла соответственно. Функция EOLN используется только с текстовыми файлами, которые организованы как строки символов. Файлы описываются в гл. 10, однако, приведенной ниже информации вполне достаточно, чтобы воспользоваться функцией EOLN. Функцию EOF не следует использовать при вводе данных с клавиатуры; этот вопрос обсуждается в гл. И. возвращает TRUE, если была прочитана последняя литера текущей строки имяфайла) WHILE NOT EOLN DO A BEGIN WHILE NOT EOLN DO BEGIN READ(aJ; WRITE(a:5:l) END WRITELN(i:3) END__________ означает EOLN(INPUT) вод Ввод 13 13.0 11 12 13 пробелы npoj ти 12 целый тип вещественный 11.0 12.0 имяфайла WHILE NOT EOF(f) DO BEGIN WHILE NOT EOLN(f) DO f BEGIN L READ(ch); WRITE(ch) END; WRITELN END___________ WHILE NOT EOF(g) DO I BEGIN \ -READ(ch); WRlTE(ch) END ведет к ошибке) возвращает TRUE, если была прочитана последняя литера файла (попытка дальнейшего стения Признак конца строки О читается как пробел вводимый файл f пробелы имеют О значение при * чтении типа CHAR e e e о 8 пробелы имеют значение при чтении типа CHAR вводимый файл g Признак конца^строки читается^как пробел 49
©ЭДЖШЖ] (НАД ПОЗИЦИЯ В ПОРЯДКЕ ВОЗРАСТАНИЯ теа LyyKBbi от ’А* до ’Z’ следуют в возрастающем порядке, иными словами, каждая буква имеет порядковое значение, соответствующее ее месту в алфавите. Это порядковое значение может быть получено посредством функции ORD( ): ©й®< выражение ) WRITELN( ORD(T), ORD(’J’)) возвращает порядковый номер литеры ~ или значения другого дискретного типа 209 од sen D1C (Порядковый номер литеры зависит от компьютера; для персональных и домашних компьютеров общепринятым является код ASCII. Но, независимо от используемого кода, порядковые значения букв следуют по возрастанию: ORD(’A’) < ORD(’B’) < ORD(’C’)... < ORD(’Z’) хотя ORD(’Z*)-ORD(*A*) и не обязательно равно 25. Не все компьютеры работают со строчными буквами, но если они их «понимают», то ORD(’a’) < ORD(’b’) < ORD(’c’)... < ORD(’z’) (Определенной связи между прописными и соответствующими строчными буквами нет, но можно без опасений полагаться на то, что ORD(’a’)-ORD(’A’) имеет то же значение, что и ORD(*z*)-ORD(*Z’). J (Независимо от используемого кода, порядковые значения цифр также расположены по возрастанию: ORD(’O’) < ORD(T) < ORDC2’)... < ORD(’9’) и, более того, порядковые значения соседних цифр отличаются на 1; так, ORD(’9’)-ORD(*0’)- 9. Отсюда следует, что численное значение цифры d (типа CHAR) может быть получено как value:» ORD(d) - ORD(’O’) (Пожалуй, нет такой версии Паскаля, где бы ORD(’O’) возвращала нуль.) Паскаль поддерживает типы CHAR, INTEGER и т.п. В дополнение к ним программист вправе определить и другие типы путем перечисления последовательности констант: TYPE тип’ за^аннЬ1й перечислением^ days - (mon,tue,wed,thu,fri,sat,sun); ZiSKrV1 рассматриваетсяна с. 82 константы типа, заданного перечислением, имеют порядковые значения, отсчитываемые от нуля. Например, ORD(mon) возвращает 0, ORD(sun) возвращает 6; mon<sun. ^Jnn BOOLEAN - перечисляемый тип, который автоматически задается как TYPE BOOLEAN - (FALSE,TRUE); следовательно, ORD(FALSE) дает 0, ORD(TRUE) дает 1; FALSE < TRUE. 50
i для ORD( ) является функция CHR( ): выражение ) возвращает литеру, порядковое значение которой задается параметром ~ неправильное значение влечет ошибку WR[lbLN(C^ \VRITEJLN(^ \yRITELN(CHR(1000)J _____ --- КГу вероятно, недо-\ >^\пустимое значение* EBCDIC/ 1 J Error [Порядковые значения ~ даже если они существуют ~ редко бывают нужны сами по себе. Часто достаточно знать следующий или предыдущий элемент в установленном порядке. Для этой цели служат функции SUCC( ) и PRED( ): возвращает элемент, следующий за тем, который указан в качестве параметра WRITELN(SUCCfA’), SUCC(’O’), SUCC(O)); WRITELN(SUCC(FALSE)); WRITELN(SUCC(’Z’ ))^(^^u^eei^ ____________________ сл едующ его) выражение ) В1 TRUE Error возвращает элемент, предшествующий тому, который указан в качестве параметра WRITELN(PRED(’Z’), PRED(’9’), PRED(9)); WRITELN(PRED(TRUE)k WRITELN(PRED(’A’)pT//A^r^^ee7^ " _\ ( предшествующего^/ Y8 8 FALSE Error £ути две функции можно использовать для определения следующих и предшествующих элементов для типа, заданного перечислением. Возьмем тип days, определенный напротив: PRED(sun) возвращает sat, SUCC(mon) возвращает tue Однако было бы неверно писать WRITELN(PRED(sun)), поскольку элементы перечисляемого типа нельзя читать или печатать ~ что, конечно, снижает выгоду от использования таких типов. Наилучшее приближение к WRITELN(PRED(sun)) - это оператор WRITELN(ORD(PRED(sun))), печатающий число 5 (порядковое значение элемента sat). С^ункцию SUCC( ) удобно использовать для управления циклом: i:-0; REPEAT i:-SUCC(i); операторы PtJNTlLi-To 51
'«там шжэм (с. 42) Выражение 17 MOD -5 является ошибочным (в некоторых компиляторах) не потому, что операция MOD не допускает отрицательных аргументов, а потому, что синтаксис Паскаля запрещает располагать подряд две операции. В равной мере будет ошибочным выражение 17 ♦ -5. Если же записать выражение так: 17 MOD (-5), то сообщения об ошибке не будет и в результате получится число 2. Впрочем, версия Турбо Паскаль допускает и выражение 17 MOD -5 , и 17 * -5 . 2 ) (с. 42) Обратите внимание на тонкий момент: в каком порядке выполняются < операции в выражениях типа -17 D1V 5 ? Операция «-» формально имеет более низкий приоритет, чем «*», следовательно, выражение -17 DIV 5 должно трактоваться, как -(17 DIV 5). Именно так работает компилятор на больших ЭВМ серии ЕС. Вместе с тем, в версии Турбо Паскаль приоритет унарного минуса (т.е. минуса, перед которым нет операнда) считается выше приоритета любой операции, поэтому то же выражение обрабатывается как (-17) DIV 5 . Оба варианта дают одинаковый результат, разница между ними может проявиться в случае переполнения. з) (с. 44) Замена i * j DIV к на i ♦ (j DIV k) не только уменьшает риск переполнения, но и изменяет результат выражения, следовательно, такая замена далеко не всегда допустима. (с. 50) К сожалению, русские буквы, даже если они есть на компьютере, вовсе не обязательно расположены по алфавиту. Самая лучшая в этом смысле ситуация - на ПЭВМ ЕС-1840 при работе в операционной системе М86, поставляемой заводом изготовителем. Здесь русские буквы располо- жены по алфавиту, без промежутков и разница между прописными и соответ- ствующим строчными буквами постоянна. На той же ПЭВМ и других, совмес- тимых с IBM PC, при работе в системе MS DOS обычно используется коди- ровка {и не одна), в которой русские буквы (кроме ё) расположены по алфавиту, но разность строчных и прописных букв неодинакова для разных букв. На больших же компьютерах серии ЕС порядок русских букв (А, Б, Ц, Д, Е, Ф, Г, X,...) совсем не алфавитный. 5) (с. 51) В коде ASCII PREDfA’), как и SUCC(’Z’), определены: PRED(*A’)=*@*; SUCC(’Z’)=’[’ 52
БЛОК-СХЕМЫ ОПЕРАТОР IF-THEN-ELSE ЦИКЛ FOR ЦИКЛ REPEAT ЦИКЛ WHILE ФИЛЬТР (ПРИМЕР) ОПЕРАТОР CASE АВТОМАТНЫЙ РАСПОЗНАВАТЕЛЬ (ПРИМЕР)
ОПЕРАТОР IF-THEN-ELSE, ЦИКЛ FOR, ЦИКЛ REPEAT, ЦИКЛ WHILE, ОПЕРАТОР CASE, ОПЕРАТОР GOTO цуольшинство управляющих операторов уже использовались в примерах из предыдущих глав. В этой главе даются точные определения и обсуждаются особенности. Только эти операторы способны изменить порядок выполнения программы, без них управление передается от оператора к оператору последовательно. всех |а этом развороте на рисунках в виде блок-схем изображено функционирование управляющих операторов. true 1г условие THEN оператор ELSE оператор выполнить оператор, который следует за THEN выполнить оператор, который следует за ELSE путь на случай отсутствия ELSE I OR имя пер ТО DOWNTO ЕРЕАТ оператор UNTIL условие DO оператор вычислить первое выражение, запомнить результат как «начальное значение» । вычислить второе выражение, запомнить результат как «конечное значение» — I" "~Г--- уйач. значение > кон. значение? \да \ (< в случае DOWNTO)}-----------> присвоить начальное значение переменной цикла [выполнить оператор увеличить переменную цикла на 1 (уменьшить в случае DOWNTO) WHILE условие DO оператор ______________ роверить условие временная цикла > кон. значение? \ да (< в случае DOWNTO) временная цикла становится неопределенной выполнить оператор 54
CASE^ выражение END константа вычислить выражение ( далее результат будет называться <значением») да совместимые константы CASE i OF 0: WRITE(^b’); l,3,5:WRITE(’He4eT>); 2,4,6:WRITE(’4eT’) END 'значение равно какой либо \ ^константе в первом списке?/ выполнить первый оператор 'значение равно какой-либо константе во втором списке?. да выполнить второй оператор и т.д. и т.д. значение равно какой-либо онстанте в последнем списке?, да выполнить последний оператор 13 стандарте не указывается, что же будет, если значение выражения не совпадет ни с одной из констант. (В неко- торых версиях Паскаля для отслеживания подобных ситуаций предусмотрено ключевое слово OTHERWISE, однако это - нестандартная возможность.1J) ©W© GOTO метка передать управление оператору, который помечен указанной меткой ыполнить помеченный оператор ______ 3 некоторых версиях Паскаля GOTO и помеченный оператор должны обязательно находится в одном программном блоке. выполнить операторы, которые следуют за помеченным оператором ©ператор GOTO полезен в интерактивных системах для исправления ошибок пользователя ~ тема, обсуждение которой выходит за рамки этой книги. Стандартным Паскалем не предусмотрен выход какой-то способ, конечно, можно изобрести: ПАСКАЛЮ НЕДОСТАЕТ ОПЕРАТОРА EXIT из середины цикла Хотя операторы! | {проверить условие^—drue -операторы^ [REPEAT^ операторы! [iKNOT ^oaueJTHENj операторы^ одинаковые UNTIL условие не считая GOTO 55
ОПЕРАТОР 35?-wgs3-gasg датаксис оператора: IF условие THEN оператор г ELSE оператор ► IF profit > loss THEN WRITE(*ypa!’) ► IF profit > loss THEN WRITE(’ypal’) ELSE WRITE(’yBbi...’) ► IF initiab’k’ AND initial’s’ THEN WRITE(’Cmotph в каталоге между L и R*) 1^>огда условие вычислено и его значение оказалось true, выполняется оператор, следующий за THEN ~ оператор, следующий за ELSE, игнорируется. Если, наоборот, значение условия -оказалось false, то игнорируется оператор, который следует за THEN ~ а выполнен будет оператор, следующий за ELSE (если такой имеется). Эсли вычисление условия не дает ни true ни false, то выдается сообщение об ошибке. (®)ператор, следующий за THEN или ELSE, может быть составным оператором (т.е. иметь вид BEGIN.......END). Нет никаких ограничений на число или сложность операторов, входящих в составной оператор. [будьте внимательны при использовании вложенных операторов IF. Предпочтительнее пользоваться схемой ELSE-IF, нежели THEN-IF, заставляющей «хранить в уме» соответствующие ELSE. Злоупотребление THEN-IF обычно заканчивается неприятным нагромождением закрывающих ELSE: бщее правило таково: каждый ELSE относится к ближайшему предшествующему IF, еще не имеющему парного ELSE. 56
ЦИКЛ (Синтаксис оператора FOR: ИСПОЛЬЗУЕТСЯ, ЕСЛИ ВЫ МОЖЕТЕ УКАЗАТЬ В ЗАГОЛОВКЕ ЧИСЛО ПОВТОРЕНИИ выражение DO оператор FOR имя := выражение ТО имя (переменной, Лцикла/ ► FOR humbug— 1 ТО 3 DO WRITELN(*Mh желаем Вам веселого Рождества’); WRITELN(’H счастливого Нового года’) ► FOR m:= 12 DOWNTO 2 DO WRITELN(m:3,’ человек,’); WRITELN(*1 человек и его собака пошли косить луг*) DOWNTO [беременная цикла может быть любого упорядоченного типа (обычно - типа INTEGER и ни в коем случае не REAL). Оба выражения должны быть* того же типа, что и переменная цикла. юк-схему со с. 54 неплохо подкрепить примерами, которые и приведены ниже. (®)ба выражения вычисляются перед выполнением операторов цикла; впоследствии они не перевычисляются. Если эти выражения отвечают невозможной последовательности, то цикл вообще не выполняется: I FOR i:= 2 ТО 1 DO WRITE(’Po6KHft’) Ничего не печатается сообщения об ошибке нет [Невозможно выскочить за «финишную черту», которая установлена в самом начале: finish:=3; FOR i =3 TO fin 4 BEGIN ( finish:=finish+l \ WRITELN(finish 'END [всякое изменение переменной цикла является ошибкой. Среди таких некор- ректных изменений - присваивание переменной цикла и чтение в нее значений: FOR i = 1 TQ,3 DO 3 DO ШР1ТЕ(’Боже мой!’) [ДДудет неверно делать какие-либо предположения о значении переменной цикла FOR по выходе из цикла (если только выход не через GOTO): _______ FOR i— 1 TO 3 DO —WRITE(i:4); WRITELN; WRITE(i:4)^T* 2 3 может быть что угодно 57
Если при определенных условиях цикл должен быть пропущен, то следует предусмотреть специальные меры ~ скажем, воспользоваться оператором IF. В такой ситуации лучше использовать цикл WHILE. ЦИКЛ шит (Синтаксис оператора WHILE: WHILE условие DO оператор n:-3; WHILE n>0 DO BEGIN n.-PRED(n); WRITELN(n) END; следует из блок-схемы, проверка условия производится перед выполнением оператора, что позволяет пропустить цикл в случае невыполнения условия (в отличие от цикла REPEAT ) ципичное использование цикла WHILE - при копировании текстовых файлов. Текстовый файл - это файл, организованный в строки и состоящий из некоторых элементов, разделенных пробелами, как описано на с. 125. VAR ch: CHAR; WHILE NOT eOF(f) DO i BEGIN / WHILE NOT EOLN(f) DO i BEGIN T READ(f,ch); \ WRITE(ch) \ 'END; \ WRITELN 4 END; сТак можно с ^скопировать^ текстовый в файл о о Так можно скопировать текстовый файл ом примере опирование на экран о о о [}Qe используйте гл, 10 и И. EOF при вводе с клавиатуры. Подробнее об этом пойдет речь в 58
НА ПРИМЕРЕ ПРОГРАММЫ ДЛЯ ЧТЕНИЯ ИЗ ТЕКСТА МАЛЕНЬКИХ ЧИСЕЛ ИЛЛЮСТРИРУЮТСЯ ЦИКЛЫ REPEAT И WHILE ^Цтобы прочитать числа из приведенного ниже файла недостаточно лишь операторов READ - встречающиеся на пути слова и знаки пунктуации препятствуют этому. Программа filter предназначена для считывания именно чисел и отсеивания прочих данных. Здесь изображен файл исходных данных. Его следует вводить не нажимая клавишу [rEWkN| до самой последней точки. За 6 месяцев, если повезет, у меня будет 350 фунтов +46.47 дохода -8.12 налоги. Этого должно хватить для приобретения домашнего компьютера МК2 с памятью 32К. 6.00 350.00 46.47 -8.12 2.00 32.00 §)то - выходной файл, который должна создать программа из изображенного выше входного файла. вот и программа, выполняющая эту работу: пробе PROGRAM filter(INPUT,OUTPUT); VAR ch,sgn: CHAR; fraction: INTEGER; number: REAL; BEGIN ch:-*r’’; WHILE NOT E N DO y BEGIN f number;-0.0; fraction:-0; / sgn:-ch; READ(ch); / IF (ch>-’0’) AND (ch<-’9’) / THEN BEGIN { если цифра } REPEAT / REPEAT 1 number-10*number+ORD(ch)-ORD(’0’); I fraction:-fraction*10; \ IF NOT EOLN THEN READ(ch); UNTIL((ch<’0’) OR (ch>’9’) OR EOLN; IF (ch-’.’) AND NOT EOLN THEN BEGIN READ(ch); fraction:-!; \ END UNTIL((ch<’0’) OR (ch>’9’) OR EOLN; IF fraction>0 THEN number-number/fraction; IF sgn=’-’ THEN number—number; WRITELN(number:8:2); END { если цифра } { WHILE NOT EOLN } г недостаток: если в числег^ более одной десятичной точки, то учтена будет только последняя; например, 12.3.4 даст 123.4 без \ сообщения об ошибке J END END. 4\юбой фрагмент программы, имеющий дело с вводом, трудно сделать абсолютно неуязвимым ввиду огромного множества потенциально необходимых проверок. В этом отношении приведенная программа особенно плоха. Лучшая версия этой программы, в которой использованы пока не введенные в рассмотрение возможности Паскаля, приводится на с. 86. 59
ОПЕРАТОР <&ass (Синтаксис оператора CASE: ♦ CASE выражение OF константа/, оператор, END ► CASE digit OF 0: WRITELN(’E^b’); 1,3,5,7,9: WRITELNfнечет’); 2,4,6,8: WRITELN(’qeT’); END необязательно ► CASE ch OF ’O’: WRITELN(’h^b’); ’l’,’3’,’5’,’7’,’9’:WRITELN(’HeqeT’); *2*,*4’,*6’,*8*:WRITELN(’qeT’); END (выражение может давать значение это INTEGER или CHAR и ни в коем случае REAL. Выражение и константы должны принадлежать одному типу. г^\ействие оператора CASE определено блок-схемой на с. 55. Как только обна- руживается совпадение, выполняется соответствующий оператор; ни один из других операторов не выполняется. Если совпадений нет вообще, то результат непредсказуем. Поэтому будьте внимательны и постарайтесь предусмотреть все значения, которые может принять выражение (что не всегда просто). ^!/\ожно использовать и вложенные операторы CASE, это удобно, например, при реализации автоматных распознавателей, которые дают способ наглядной записи алгоритмов распознавания текстов Представленная таблица предназначена для перевода римских чисел, составленных из цифр X, V, I.______________________ любого упорядоченного типа, как правило, ИМ ВОЛ ’X’ состоя н ие^^^ •y T г—1 п:=10; state:=2 n:=5; state:=3 n:=l; state*. =6 <—и 2 n:=n+10; state:=2 n:=n+5; state:=3 n: = n+l; state: =6 3“ ok:=FALSE ok:=FALSE n:=n+l; state: =4 4 ok:-FALSE ok:=FALSE n:=n+l; state: =5 5“ ok:-FALSE ” ’ ok:=FALSE n:=n+l; state:=7 6“ n:=n+8; state;=7 n:=n+3; state: =7 n:=n+l; state: =5 7 ok;=FALSE ok:=FALSE ok:=FALSE 1^\ля расшифровки XIV начинаем с состояния 1,' как указано стрелкой. Первый символ - *Х*, поэтому смотрим столбец ’X’ и находим n:=10; state:=2. Итак, полагаем п равным 10 и сдвигаем стрелку на вторую строку. Теперь смотрим столбец, Значение и, таким образом, становится 10+1=11. Сдвигаем стрелку к строке 6. Теперь в столбце ’V* находим п:=п+3; state:=7. Значение и становится равным 11+3=14. Сдвигаем стрелку на строку 7 и замечаем, что любая следующая цифра ’X*, ’V’ или *Г будет теперь ошибкой (например, XlVXSf §)та таблица позволяет декодировать римскую запись чисел, содержащих любое количество цифр X (в начале) и цифры V, I, записанные по обычным правилам: I, II, III, IV, V, VI, VII, VIII, IX, X, XI и т.д. Вместе с тем такое число как IIII, будет воспринято как ошибочное и переменная ок примет значение FALSE. Для работы с цифрами М, D, С и L таблицу можно расширить. определяемый вторым символом, т.е. *1’, и находим n:-n+l; state:=6. 60
ИЛЛЮСТРИРУЕТ ВЛОЖЕННЫЕ ОПЕРАТОРЫ CASE PROGRAM roman( INPUT, OUTPUT); VAR n,state: INTEGER; symbol: CHAR; ok: BOOLEAN; BEGIN { программа } см. гл. 7 писать: IN [ X , V , 1’1, Приятнее IF symbol state:=l; ok:=TRUE; n:=0; WHILE NOT EOLN DO BEGIN READ(symbol); IF ((symbol=’X’) OR (symbol=’V’) OR (symbol=’I*) THEN CASE state OF 1: CASE symbol OF ’X’: BEGIN n:=10; state:=2 END; ’V’: BEGIN n:=5; state: =3 END; 2: T: BEGIN n:=l; state:=6 ! END; CASE symbol OF END ’X’: BEGIN n:=10; state: =2 END; ’V’: BEGIN n:=n+5; state: =3 END; 3: ’Г: BEGIN n:=n+l; END; CASE symbol OF ’X’,’V’: ok-=FALSE; state: =6 END 4: ’Г: BEGIN n.-n+l; END; CASE symbol OF ’X*,’V’: ok:=FALSE; state: =4 END 5: ’Г: BEGIN rr=n+l; END; CASE symbol OF ’X’,’V’: ok:=FALSE; state:=5 END 6: ’Г: BEGIN n:=n+l; END; CASE symbol OF state:=7 END ’X’: BEGIN n;=n+8; state: =7 END; ’V’: BEGIN n:=n+3; state:=7 END; 7: ’Г: BEGIN n:=n+l; END; ok:=FALSE; state: =5 END { CASE state } END ELSE BEGIN (декодированное IF ok ^NiUQA^L в плХ THEN WRITELN(n?^)2^±i6' ELSE WRITELN(’PECCAVISTI’); state;=l; ok:=TRUE END { ELSE } ND { WHILE NOT } END. { программы } Замечание: перед нажатием следует ввести точку или пробел XIV XIVX XX. 14 PECCAVISTI&p^ 20 61
UQ Ьуыполните программу roman. Дополните программу так, чтобы она справлялась с цифрами: М - 1000, D - 500, С - 100, L - 50 . Если ваш Паскаль допускает интерактивную работу, то включите в программу сообщения-подсказки для удобства пользователя. 15 (с. 55) В версии Турбо Паскаль в операторе CASE можно использовать слово ELSE, после которого записывается оператор, исполняемый, если значение выражения не совпадает ни с одной из меток. 62
(Ь ШШ И ОПРЕДЕЛЕНИЕ ФУНКЦИИ ПРИМЕРЫ ФУНКЦИЙ РЕКУРСИЯ ПРОЦЕДУРЫ СЛУЧАЙНЫЕ ЧИСЛА СНОВА ЗАЕМ (ПРИМЕР) ИМЕНА ФУНКЦИЙ КАК ПАРАМЕТРЫ ССЫЛКИ ВПЕРЕД ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ ПОБОЧНЫЕ ЭФФЕКТЫ ПРАВИЛА ВИДИМОСТИ
ОПИЕДОЕ ШЖВ © I ОПРЕДЕЛЯЙТЕ СВОИ 'СОБСТВЕННЫЕ ФУНКЦИИ □иаскаль сам по себе не предоставляет функции, вычисляющей площадь круга по фактическому параметру - диаметру круга. диаметр Однако такую функцию легко определить: a:=CIRCLE(6.5); WRITELN(a:8:2) 33.69 параметр должен принадлежать FUNCTION circle( d: REAL): REAL; CONST pi=3.1415926; BEGIN circle:» pi ♦ SQR( d )/4.0; END; функция возвратит результат типа REAL типу REAL прйсвоитб имени функции' результа в Паскале результатом может быть только одно значение Теперь функцию circle( ) (или CIRCLE( )) можно использовать в программе. точно так же, как ранее использовались функции SQR( ) или TRUNC( ). 3 первой строке определения функции параметр d как бы говорит «Делай то, что делается со мной, но используй значение, которое будет на моем месте». В примере в начале этой страницы на место d ставится число 6.5, которое, таким образом, возводится в квадрат, затем результат умножается на 3.1415926 и делится на 4.0. Параметр d - это формальный параметр, тогда как число 6.5 - фактический параметр. В программе, обращающейся к функции circle( ), можно использовать имя d как имя переменной (или любого другого объекта), не опасаясь помех со стороны функции. d:=-99; a:=circle(6.5); WRITELN(a,d:8:2) [1Циже приведен синтаксис определения функции (пока без учета случая, когда параметры сами являются функциями или процедурами): программе. Синтаксис блока определен на с. 38; эта схема всего лишь иллюстрирует расположение определений функций и процедур в программе. пределения функций и процедур могут в свою очередь сами содержать вложенные в них определения функций и процедур. © 64
Оп]иже приведена функция, вычисляющая площадь прямоугольника; ее параметрами являются длины сторон: FUNCTION rectangle( b,d: REAL): REAL; b BEGIN rectangle ;= b*d END; d J вот похожая функция для вычисления площади тругольника: FUNCTION triangle( a,b,c: REAL): REAL; VAR x: REAL; BEGIN x; = (a+b+c)/2; triangle SQRT(x*(x-a)*(x-tj)*(x-c)) END Ц ь r 1 Зсе эти ТРИ Функции (circle( ), rectangle( ), triangle( )) могут быть вызваны из следующей программы, которая представляет собой переработку программы со с. 27. PROGRAM shapes2(lNPUT, OUTPUT); VAR letter: CHAR; a,x,y: REAL; здесь поместит?три фу нкцил^Г любом порядке BEGIN REPEAT / READ(letter); f CASE letter OF ’в*, ’B’ : a=-0; TI 3.5 2 Площадь: 7.00 К 6.5 Площадь: 33.69 T 3 4 5 Площадь: 6.00 Все Площадь: 0.00 / ’п’, ЧТ : BEGIN READLN(x,y); a:=rectangle(x,y) END V, Т’ : BEGIN READLN(x,y,z); a:=triangle(x,y,z) END к’, К’ : BEGIN READLN(x); a:=circle(x) END I END; { CASE letter } \ WRITELNfПлощадь: \a:8:2) UNTIL (letter = ’q’) OR (letter = ’Q’) END. Обратите внимание на то, что функции вызываются с фактическими параметрами х, у, z, тогда как формальные параметры в их определениях - a, b, с, d. Переменная а в основной программе никак не связана с формальным параметром а в функции triangle( , , ). Точно так же отсутствует связь переменной х в основной программе и локальной переменной х в функции trianglef , , ). Подробнее об этом. - несколько позже. । Определенные здесь функции различаются по числу параметров. В функциях может быть любое фиксированное число параметров; определить функцию с । переменным числом параметров (например, как в случае READ(a), READ(a,b), ' READ(a,b,c)) нельзя - такой возможностью пользуется только сам Паскаль. i 3 приведенных примерах все типы - REAL, однако, допускается и любое сочетание: например mixfun(a: REAL; b: INTEGER; c: CHAR): BOOLEAN; 65 3-458
ПИИТУНЕ шеи ДЛЯ ИЛЛЮСТРАЦИИ ОПРЕДЕЛЕНИЙ ФУНКЦИИ Ly Паскале нет функции, для вычисления кубического корня. Вот ее определение: FUNCTION cubrt(x: REAL): REAL; VAR old, noo: REAL; BEGIN IF x-0 THEN cubrt-0 ELSE BEGIN okh-l; ^REPEAT Г noo:-x/SQR(old); olds-(noo+old)/2 UNTIL ABS(x/(noo*noo*noo)-l)<lE-6 ; cubrb- noo; END END; { функции }________________________ выход, когда noo) cubrt(-27) возвращает -3 cubrt(O) возвращает О cubrtf2/) возврашаегЗ Программирующие на Бейсике и сожалеющие об отсутствии функции SQN( ), могут определить ее либо <в лоб»: FUNCTION BEGIN IF x>0 IF x<0 END; sgn(x: REAL): INTEGER; THEN sgns-1 ELSE THEN sgn:—1 ELSE sgn;-0 возвращает 1, если x > 0 , возвращает -1, если x < О возвращает 0, если х - 0\ либо хитрее:___________________________________ ____________________________ FUNCTION sgn(x: REAL): INTEGER; /Сработает потомуГчто^^ BEGIN sgn:-QRD(x>0) - QRD(x<0) END; \^ORD(TRUE)-h ORDfFALSE)^ 3 Паскале нет функции TAN( ) (тангенс угла, измеренного в радианах). Вот ее определение: FUNCTION tan(x: REAL): REAL; BEGIN tan:* sin(x)/cos(x) END; (Далее приведены функции для вычисления арксинуса (угла в радианах, синус которого равен ... ) и арккосинуса: since - x/1 tan a - x/vl-x ±n/2 +7Г, когда x^O /-х tan а - - arctan(vl-x /х) FUNCTION arcsin(x: REAL): REAL; BEGIN IF ABS(x)-l THEN arcsin:-x*l.5707963 ELSE arcsin:*ARCTAN(x/SQRT(l-SQR(x))) END; +71/2 FUNCTION arccos(x: REAL): REAL; BEGIN IF x-0 THEN arccos:-1.5707963 ELSE arccos:-ARCTAN(SQRT(l-SQR(x))/x) + 3.1415926*ORD(x<0) END; ' (Д^ункции arcsin( ) и arccos( ) рассматриваются также на с. 78. 66
ИЕК ОПРЕДЕЛЕНИЕ РЕКУРСИВНОЙ ФУНКЦИИ ПОМОГАЕТ ПОНЯТЬ ИДЕЮ РЕКУРСИИ [наибольший общий делитель (НОД) чисел 1470 й 693 - это 21. Другими словами, 21 - наибольшее число, на которое и 1470 и 693 делятся без остатка. Чтобы убедиться в этом, разложим оба числа на простые сомножители: 1470 - 2 х/а)х 5 х/9х 7 693 - Су х 3 хСх х И и выделим пары общих сомножителей ~ в данном случае это пары чисел 3 и 7. Наибольший общий делитель - это произведение совпадающих сомножителей; в данном случае это 3*7*21. Ьуолее изящный метод поиска НОД - деления 1470 на 693: 1470 MOD 693 - 84 алгоритм Евклида. Найдем остаток от Так как этот остаток не равен нулю, повторим то же действие, подставив вместо первого числа второе, а вместо второго - остаток: 693 MOD 84 - 21 Этот остаток также не нуль, поэтому еще одно деление: 84 MOD 21-0 Теперь остаток - нуль, следовательно, НОД равен 21. Вот и отлично. (Следующая функция на Паскале использует метод Эвклида: FUNCTION VAR rem: BEGIN rem n IF rem-0 END; ncf(n,m: INTEGER): INTEGER; INTEGER; (рекурсивное обращен, MOD m; THEN hcf:-m ELSE hcf:-hcf(m,rem) работает правильно^ как для n*m так и \*>разу ясно, как вычисляется hcf(84,21): остаток rem будет равен нулю и функция возвратит число 21. При обращении hcf(1470,693) rem будет равен 84, и, следовательно, функция вызовет сама себя как hcf(693,84). Теперь уже rem примет значение 21, и функция вызовет себя еще раз в виде hcf(84,21). Таким образом, при каждом обращении Паскаль как бы создает новую копию функции — i :-W(1470,693V]\ ------ 2 FUNCTION ЬсЦДТй^З) VAR remj^/woeoJ^-x BEGIN < rem;-14ZD—MQD 693 -84 1 ^hcf:-(icf(693,84)>—> END FUNCTION Ь^(Ц7^693) VAR rem;^^новая^. begin rem: -693 MOD 84 -2j/ —hcf:- Cgcf(84,21I>-^ end 2J FUNCTION hcf(1470,693) VAR гет\^4^новая\>. BEGIN rem:-84 MOD21-0^ ^hcb- 21 ' END (Способность функции обращаться к себе самой называется рекурсией. Подробнее о рекурсии говорится в этой и следующих главах. 67 3*
i^WbO И ОТЛИЧИЕ ПАРАМЕТРОВ-ЗНАЧЕНИЙ ОТ ПАРАМЕТРОВ-ПЕРЕМЕННЫХ ц^огда какая-либо часть программы используется более одного раза, то вовсе не обязательно повторять текст; эту часть можно оформить в виде процедуры, дав этой ей имя и вызывая каждый раз, когда необходимо выполнить эту часть программы. Вот простейший пример: процедура печати двух целых чисел в обратном порядке: PROCEDURE reversed,b: INTEGER); BEGIN WRITELN(b:3,a:3) ..ЕЕФ;______________________________________________________ |з основной программы эта процедура может быть вызвана так: фактические параметры могут быть константами (4,5), выражениями (4*х,5*у) или именами переменных (х,у). Каждый раз при вызове процедуры reverse( , ) вычисляются фактические параметры, и их значения подставляются на место формальных параметров а и Ь. По этой причине а и b называются параметрами-значениями. [Предположим, что вместо поменять местами значения процедура для этого совсем печати значений в обратном порядке, необходимо двух переменных целого типа. Приведенная ниже непригодна: PROCEDURE swop(a,b: INTEGER); VAR tempry: INTEGER; BEGIN tempry: =a; a:=b; b: END; =tempry (Предположим, процедура вызывается следующим образом, причем значения переменных х и у равны соответственно 1 и 100: I swop(x,y) | г^\алее произойдет вот что: значение 1 будет помещено в а, 100 - в Ь\ затем значения а и b поменяются; после чего произойдет возврат в программу. Переменные х и у останутся нетронутыми. Процедура работает только лишь со значениями своих параметров; обращения swop(4,5) или swop(4*x,5*y) равным образом не дадут результата. в том, чтобы описать параметры как параметры-переменные. Если перед параметра записать слово VAR, то процедура получит доступ к цуыход именем переменной в вызывающей программе: PROCEDURE swop( VAR a,b: INTEGER); / теперь вы сможете изменить VAR £ значения переменных, tempry: INTEGER; относящихся к вызывающей BEGIN ч! программе ж. tern pry: =а; a:»b; b:=tempry END; 68
Теперь, вызывая процедуру, получаем: х«=1; у.-ЮО; swop(x,y); WRITELN(x,y) 100 1 [Юопросту говоря, пишите VAR перед теми параметрами, значения которых должны быть изменены процедурой. 0олее строго, наличие VAR в заголовке процедуры означает прямую связь с вызывающей программой. Оператор а:=Ь в процедуре означает х==у в вызывающей программе (речь идет о последнем примере). На жаргоне: параметры-переменные передаются по адресу или по ссылке, тогда как параметры-значения передаются по значению ~ процедура для хранения каждого переданного значения создает локальную переменную. Зызь1вать процедуру с параметрами-значениями так, как показано ниже - swop(4,5);oS»^ swop(4*x,5*y) с»* бессмысленно. Вызов процедуры имеет смысл, только если оба параметра - имена переменных, значения которых следует поменять местами. £Ддесь возможно недоразумение: раздел VAR в процедуре служит для определения локальных для данной процедуры пременных, тогда как слово VAR в заголовке процедуры означает ссылку на нелокальные переменные: PROCEDURE swop(VAR a,b: INTEGER); VAR tempry: INTEGER;\&C^>O^ r J ^нелокальные BEGIN tempry: =a; a:=b; b^=tempry локальная PROCEDURE reversed, b: INTEGER); begin WRITELN(b,a) END; ("переменные) END; (^^алее приведен синтаксис определения процедуры (пока не учитываются параметры, являющиеся в свою очередь именами функций). (Элемент блок имеет структуру программы внутри программы. Синтаксис блока детально изложен на с. 38 <Х/та схема просто иллюстрирует расположение определений функций и процедур в программе. Определения функций и процедур могут в свою очередь сами содержать вложенные определения функций и процедур. PROGRAM / CONST VAR определения Функций и л едур BEGlfT^^4^^ основная\ программа} end; 69
МИЙ АД ФУНКЦИЯ, ВОЗВРАЩАЮЩАЯ ЗНА- ЧЕНИЕ И МЕНЯЮЩАЯ ПАРАМЕТР Рассмотрим следующую функцию: FUNCTION next(VAR seed: INTEGER): INTEGER; CONST multiplier-37; increment-3; cycle-64; BEGIN next:-seed; seed:=(multiplier*seed+increment) MOD cycle END; обратите внимание\ на VAR в заголовке^ необычный прием fS для функции Будучи вызвана с параметром $, содержащим число 16 s:-16; WRITE(next(s)) ~~| эта функция должна, очевидно, возвратить число 16. Вместе с тем, возвращая 16, функция изменяет значение, хранящееся в переменной seed, на 19. Если функцию вызвать снова, с полученным значением $, то она возвратит число 19 и изменит значение s на 2. Продолжая вызывать функцию next( ), получим определенную последовательность целых чисел, начинающуюся с исходного значения $: Замечательным свойством этой последовательности является то, что каждое значение от 0 до 63 встречается в. ней ровно один раз. Более того, шестьдесят пятый вызов функции next( ) даст число 16 и начнется новый цикл. Другими словами, начиная с любого желаемого целого, функция генерирует фиксированную перестановку чисел от 0 до 63. ^акой прием используется преимущественно для генерации «случайных» чисел (правильнее их называть псевдослучайными - чтобы подчеркнуть их предсказу- емость). Цикл из 64 чисел, конечно, слишком мал; Грогоно (см. литературу) предлагает константы для генерации перестановки чисел от 0 до 65 535; CONST multiplier-25173; increment-13849; cycle-65536; йодбор констант с нужными свойствами - нетривиальная задача. Чтобы получить приведенные выше значения 37 и 3 для цикла из 64 чисел мне пришлось изрядно поэкспериментировать с простыми числами.1 1Йриведенная выше функция возвращает значение и изменяет значение параметра. Такой способ действий применяется нечасто. В большинстве функций нет необходимости изменять параметры, и, следовательно, нет смысла использовать слово VAR в заголовке. 70
3 задачах моделирования, а также в играх часто удобнее использовать случайные дроби в интервале от 0 до 1, нежели случайные целые. Для получения дробей нужно слегка измененить функцию: FUNCTION rnd(VAR seed: INTEGER): REAL; CONST multiplier-25173; increment-13849; cycle-1 BEGIN rnd:=seed/cycle; добавлено^ деление л seed:-(multiplier*seed+ increm^nt) MOUc^cle^ END; тСбястантч Грогоног 0лкгпа<1.0 §)та функция не будет работать, если значение MAxInT меньше, чем 231-1. Тем не менее, следующая остроумная модификация программы генерирует цикда из 32 768 дробей даже тогда, когда MAXINT имеет значение всего лишь 2-1 (32 767): FUNCTION rnd(VAR seed: INTEGER): REAL; VAR a,b,c,d: INTEGER; BEGIN rnd:-seed/32767; a:-seed DIV 256; b:-seed MOD 256; c:-((b*93) MOD 256) + 13; d:-(b*26)+((b*93) DIV 256)+(a*93)+(c DIV 256)+27; seed:-((d MOD 128)*256)+(c MOD 256) END; Следующая программа моделирует процесс бросания пары игральных костей. Ее цель - показать, насколько выгоднее ставить на 7, чем на любую другую сумму очков. (Применив массивы ~ см. гл. 8 ~ можно сделать программу существенно проще.) PROGRAM bones(OUTPUT); VAR score, throws,seed,a, b, c, d, e, f, g, h, i, j, k: INTEGER; (^нэЗаУвста вьтё^^рвукГ^^сию^Гпд^^ BEGlfT^— seed.-O; a;-0; b;-0; c:-0; d;-0; e:-0; f:-0; ---- g:-0; h:-0; i:-0; j:-0; k:-0; 6*гпй($еей)меняетс&\ FOR throws:»! TO 3600 DO 0T 0*0 0o nO4TU 6.0J BEGIN { броски } score:-TRUNC(l+6*rnd(seed))+TRUNC(l+6*rnd(seed)); CASE score OF 2: a:-a+l; 3: b:-b+l; 4: c:=c+l; 5: d:-d+l; 6: e:-e+l; 7: f-f+1; 12: k:-k+l; 11: j:-j+l; 10: i:-i+l; 9: h:=h+l; 8: g;=g+l; END { CASE } END; {FOR throws } WRITELN(2,'3,4,5,6,7,8,9,10,ll,12); WRITELN(a,b,c,d,e,f,g,h, i, j, k) END. 100 200 300 400 500 600 500 400 300 200 100 z 3 4 5 6 7 8 9 10 11 1 89 194 298 396 523 558 532 418 298 202 92 авните с «идеальным^ответом одберит одх&дя щи ьР'форм а\ с учетом устройства вывода; например а:4,Ь:4,с:4 и т.д. J Результат примерно симметричен относительно 7. Вдохновляет сопоставление результата с «идеальным»; загляните на с. 78 - там предлагается более объемный тест. 71
ПРОГРАММА ИЛЛЮСТРИРУЕТ ОПРЕДЕЛЕНИЕ ПРОЦЕДУРЫ [JlJporpaMMa на с. 25 вычисляет месяч- ную выплату по ссуде s выданной на п лет под процент р. Сложнее однако, ответить на обратный вопрос: под какой процент выдана ссуда величиной которая гасится месячными выплатами величиной т в течение п лет. sr(l + r)n 12[(l+r)n - 1] где г = р v 100 4Jto6h решить приведенное выше уравнение относительно г можно воспользовать- ся методом проб и ошибок. Возьмем какое-нибудь г, подставим в формулу и вычислим ml. Если ml совпало с т, то выбор г был верным. Если ml оказалось меньше, значит г было выбрано слишком малым, поэтому увеличим г, умножив его на m/ml, и попробуем еще раз. Если же ml больше чем нужно, значит г было выбрано слишком большим, поэтому снова умножим его на m/ml, на сей раз чтобы уменьшить, и вновь вычислим т. Короче говоря^ если разница между т и ml велнка, то умножаем г на m/ml и повторяем вычисления. Рано или поздно значение г окажется достаточно близким к точному решению уравнения. ^)тот метод хорошо работает лишь до тех пор, пока увеличение искомой величины обусловливает увеличение (или уменьшение) результата. Если же результат колеблется или имеется разрыв, как например, банкротство; то этот метод не годится. иД|иже приведена программа: PROGRAM loanrate(INPUT, OUTPUT); VAR s,m,ml,г,percent: .REAL; n: INTEGER; PROCEDURE formula(VAR m: REAL; n: INTEGER; VAR a: REAL; BEGIN a:=EXP(LN(l+r)*n); m;=s*r*a/(12*(a-l*)) END; s,r: REAL); BEGIN Если ваш Паскаль интерактивный, вставьте READ(s,m,n); r-0.1; ^REPEAT V f formula(ml/n7s7r); \ r:-r*m/ml 'IHMTII ARC/m сюда необходимые сообщения начальный предпсг^ ож^тельный процент. 'UNTIL ABS(m/ml-l) <1E-6l_^_-^--------- percent ~r*100; ^^^округлять^удет WRIT ELN/ WRITELNfОбщая суммаГ^ТзТ^УГ^4------ WRITELN(’Mecfl4Han выплата: £’,m:4:2); WRITELNf Число лет: ’,n:4); WRITELN; WRITELNfПроцент: *,percent:4:2,’%’) END. округлено до сотых Процент: 14.32% 9.99 1.63 10 Общая сумма: £99.99 Месячная выплата: £1.63 Число лет: 10 72
ГЛУБОКИЙ ВДОХ... Здесь приведены операторы программы, вычис- ляющей приращения географических координат точек на земной поверхности по известным азимуту и расстоянию, пройденному от предыдущей точки (траверсу). 1 долгота (easting) азимут (bearing) BEGIN nort hi ng:-0; easting;=0;< WHILE NOT EOFtf BEGIN начало координат точк им функции ход но ну Jure точке 1 READLN(f, bearing, distalic^ nort h i ng: -nort hi ng+project i on(beari ng, di stance, dosine); east i ng: «east i ng+project ion(beari ng, distance, si ne); WRITELN(northing:10:2,easting:10:2) END { while } END. { program } Зот определение функции projection( , , ): имя функци не^мож параметром-переменной FUNCTION projection(bng,dist: REAL; ^FUNCTION rat io(x:REAL): REAL): REAL; BEGIN project ion:-dist*ratio(bng); С определяет TpeTuu^"^\^^ END; формальный параметр) вот как определяются функции, имена которых используются в качестве фактических параметров функции projection( , , ): FUNCTION sine(b: REAL): REAL; BEGIN sine:- SIN(3.1415926*b/180) END; FUNCTION cosine(b:REAL):REAL; BEGIN cosine:- COS(3.1415926*b/180) END; (®)братите внимание на определение третьего формального projection( , , ): параметра функции фактическим должна быть деленная пользователем FUNCTION ratio( х : Real ): REA1 ^зтстфункция^ k должна иметь\~^ параметр типа REAL) параметролс^^ функция, опре\ функция должна) возвращать результат типа REAi Единственная роль имени х - указать, что здесь должен быть параметр; тем самым синтаксис функции-параметра согласуется с определением функции. ЕД)ля полноты картины ниже приведено начало программы:____________________________ PROGRAM traverse(f,OUTPUT); VAR nort hi ng, easting: REAL; __ _______ _______ _____ ,я^здесьследуег/поместить определения функции; затем основную программу)*^ ТГо, что здесь описано, будет работать далеко не во всех Паскаль-системах. Многие компиляторы не позволяют использовать имена функций в качестве параметров, и я не могу утверждать, что осуждаю их за это. Единственная известная мне задача, где параметры-функции действительно нужны - это интегрирование. ... ВЫДОХ! 73
В ПРОЦЕССЕ КОМПИЛЯЦИИ (Ф/бъявления констант и переменных в любом блоке располагаются перед скобками BEGIN и END, которые заключают в себе собственно операторы. Вследствие этого компилятору никогда не приходится иметь дело с оператором, содержащим конс- танты и переменные, о которых он (компилятор) еще не знает. Появление необъ- явленной константы или переменной вызовет во время компиляции сообщение об ошибке. Це же рассуждения справедивы и в отношении подпрограмм (т.е. функций и процедур). Если компилятор встречает вызов подпрограммы, о которой он не знает, то появляется сообщение об ошибке. Следить за правильным порядком следования определений - обязанность программиста. PROGRAM demo( IN PUT, OUTPUT); VAR a,b,c: REAL; PROCEDURE ring(VAR area.circumf: REAL; diam: REAL); BEGIN circumf;-3.14* *diam; c area.--circle(diam) END в этом месте компилятор еще не знает о функции circlet } FUNCTION circled: REAL): REAL; BEGIN circ!e:-3.14*SQR(d)/4 END; \®)чевидный выход - поменять порядок строк так, чтобы функция circle( ) была определена перед процедурой ring( , , ). Однако можно обойтись и меньшей кровью (перестановка строк в реальных программах, значительно более длинных, чем наши простенькие примеры, может быть весьма серьезной операцией): • оставьте злополучную подпрограмму на своем месте, лишь упростите ее заголовок, вычеркнув все параметры •' вставьте полный заголовок там, где ему надлежит быть, ~ т.е. перед подпрограммой, которая его вызывает. • после полного заголовка добавьте предопределенное слово FORWARD25: PROGRAM demo( IN PUT, OUTPUT); VAR a,b,c: REAL; FUNCTION circle(d:REAL):REAL; FORWARD, вставьте гюлный заголовок^ перед всеми подпрограммами, < вызывающими эту функцию что определение еледиет PROCEEDURE ring(VAR area,circumf: BEGIN circumf!-3.1415926*diam; area:-circle(diam) END; REAL; diam: REAL); FUNCTION circle; BEGIN circle:-3.1415926*SQR(d)/4.0 END; оставьте от заголовка только имя сам у n сопрограмм у не трогайте [JlJoMHMO определения подпрограмм ссылки вперед используются в Паскале только для указателей в списках. Это описано в гл. 12. 74
СОЗДАЮТСЯ ЗАНОВО ПРИ КАЖДОМ ОБРАЩЕНИИ, ИСЧЕЗАЮТ ПРИ ВОЗВРАТЕ [(Приведенные ниже примеры использовались на с. 69, чтобы показать различия между локальными и нелокальными переменными в процедуре: PROCEDURE swop(VAR a,b;JNTEGER); PROCEDURE reverse(a,fy : INTEGER); VAR tempry: I NTEGER?^^ejJo^x BEGIN BEGIN Х^кальные) WRITELN(b,a) / 'локальные^ tempryi=a; a:=b; b==tempry END; END, ^переменны^/ (Дэокальные' переменные создаются при вызове процедуры. Затем текущие значения параметров-значений копируются в соответствующие локальные переменные. Например, вызов: _________- . _____ а и числа 5 в повлечет копирование числа 4 в локальную переменную с именем локальную переменную с именем Ь. [(После этого начинает выполняться процедура. При завершении, ние возвращается в вызвавшую программу, локальные переменные содержимое теряется навсегда. Но пока управление не возвращено программе, локальные переменные сохраняются. Это очень важно рекурсивных подпрограмм, что видно уже из банального примера о «факториале»: когда управле- забываются, их вызывающей в случае го экземпляра функции. В некоторый момент выполнения изображенной выше прог- раммы будет одновременно существовать четыре различных копии переменной п. (®)бъявлять переменную п, как это было сделано выше (VAR п) - вовсе не обяза- тельно. Параметры-значения автоматически объявляются локальными переменными: FUNCTION factorial(n: BEGIN IF n=l THEN facto ELSE integer): INTEGER; .параметры-значения - уже локальные переменные 75
ИХ ЛУЧШЕ ИЗБЕГАТЬ, НО ИНОГДА ОНИ МОГУТ БЫТЬ ПОЛЕЗНЫМИ (Следующий генератор случайных чисел - альтернатива программе со с. 71: Динамическая картинка FUNCTION randi; BEGIN randi.=seed/(65536-l); seed:=(25173*seed + 13849) MOD 65536 IB .................................... Функцию можно вызвать, скажем, так:_________ seed: =20; t hrow: 41+5*randi )+(l+5*randi) ^)тот фрагмент программы выполняется как задумано, потому что компьютер, работая внутри функции randi, способен видеть переменную с именем seed. Более того, функция randi может изменить значение, хранящееся в переменной seed. Вызываемая подпрограмма способна видеть свою вызывающую программу, но не наоборот. локальные а,Ь локальные а, локальная Динамическая картинка ц>огда подпрограмма обращается к переменной с именем а, она подразумевает локальную переменную а. Если локальной переменной с именем а нет, то взгляд обращается в вызвавшую подпрограмму (возможно, в свою же рекурсивную копию) в поис- ках локальной переменной с именем а в этой под- программе. Если и там нет локальной переменной а, то смотрим еще дальше наружу... ТГот же принцип применяется ко всем именованным объектам: переменным, константам, функциям, процедурам, файлам и типам. []^огда подпрограмма изменяет значение переменной, объявленной вне этой подпрограммы, говорят, что подпрограмма имеет ' “ " имеет побочный эффект; она изменяет значение объявлена вне функции randi. побочный эффект. Функция randi переменной seed, которая [Добочные эффекты часто появляются случайно. Многократно используя переменные с короткими именами вроде а,Ь,с и программист имеет потенциальный источник неприятностей. Некоторые книги по Паскалю, с целью предотвратить такую опасность, ратуют за использование длинных имен переменных. забывая объявить их локально, маленьких программах, вероятно, проще всего делать все переменные глобальными. При использовании множеств (они описываются в следующей главе), возможно, единственный разумный путь - сделать глобальными все переменные типа множество. В длинных программах, возможно, имеет смысл определить несколько глобальных переменных, чтобы обращаться к ним из процедур. Однако, если побочные эффекты используются бесконтрольно или небрежно, то это уже - плохой стиль. IjQa противоположной странице приведена структура типичной программы. Чтобы подчеркнуть вложенность подпрограмм, они заключены в рамки. Надписи поясня- ют, какие переменные доступны на данном уровне вложения. Выделены и те пере- менные, которые могут вызвать побочные эффекты. Обратите внимание на то, что сама программа выступает в качестве подпрограммы (отличающейся нестандартным заголовком, определяющим файлы INPUT и OUTPUT, и нестандартной концовкой), вложенной в «Паскаль-оболочку». 76
ГАГЕГй В'И'Д'ИМ 1(с¥£{Й’РГ| «СТАТИЧЕСКИЙ» РИСУНОК LJ UU ВЛОЖЕННОЙ ПРОГРАММЫ ПАСКАЛЬ-ОБОЛ 04 КА: стандартные файлы (INPUT, OUTPUT), типы (REAL и т.д функции (SQR() и т.д.), процедуры (WRITE() и т.д.), константы (TRUE,FALSER PROGRAM twigs(INPUT, OUTPUT); VAR a,b,c: REAL; PROCEDURE lining(p,q: REAL; VAR x.y: REAL); VAR a,b: REAL; ☆ ☆ ☆ ☆ PROCEDURE chick(p: REAL; VAR x: REAL); I VAR a d* REAL- ____ - ^*><*^4^**^^*— nrptki __ ./«гигл»л\ f возможны ПОООЧНЫЛ В EG IN xX в этих операторах вы можете\Х ^/ЬАркты ^ТГсгЙльЗювать a,d,p, относящиеся к chick —*/ЧТ> - использовать Ь, относящееся к lining, с, относящееся I использовать х для возврата результата при выходе из chick 7?Х I использовать у lining JLL вызвать chick или lining рекурсивно использовать все файлы, типы, функции, процедуры, константы Паскаля ft ☆ "ft ☆ ☆ ☆ PROCEDURE egg(p: REAL; VAR x: REAL); J | depiki a,e REAL, ---- /^вО^МОЖНЫПОбОЧН^ ^BEGIN^^в этих операторах вы можете ^ТйТю)ГьзойатьГо?е,р, относящиеся к egg использовать Ь, относящееся к lining, с, относя щеесяет^Кй^Х. I использовать х для возврата результата при выходе из egg 1 использовать у " " " " " " " " " lining вызвать chick (для вызова chick из egg надо использовать FORWARD^ вызвать egg или lining рекурсивно ^использовать все файлы, типы, функции, процедуры, константы Паскаля ft ft ft ft ft BEGIN { lining 7o»ptqt относящиеся к lining ИСП0ЛБ30 использовать с, относящееся к twigs использовать х,у для возврата вызывать chick, egg вызывать lining рекурсивно использовать все файлы, типы, возможны побочные эффек. результата при выходег из limn функции, процедуры, константы Паскаля BEGIN { twigs }^/вглавной программ&'вы можете\_________ Т'^и^Тю/ГбЗОватХ^о, с, относящиеся к twigs х ____ '☆ вызвать lining ^^использовать все файлы, типы, функции, процедуры, константы Паскал: 77
нения q г^Уля оценки диапазона результатов, выдаваемых функциями arcsin( ) и arccos( ), которые определены на с. 66, напишите программу построения таблицы результатов при значениях параметров в интервале от -1 до 1 с шагом 0.1. Основой программы может быть, к примеру, такой оператор: FOR п:—10 ТО 10 DO WRITELN(n/10:6:2,arcsin(n/10):6:2,arccos(n/10):6:2) выполните на компьютере программу bones, приведенную на с. 71. Если вы располагаете свободным компьютерным временем, увеличьте число бросаний игральной кости с 3600 до 32768. Посмотрите, станет ли результат ближе к «идеальному». выполните на компьютере программу loanrate, (с. 72). Как и предыдущая программа loan, эта программа не работает, в случае нулевого процента. Исправьте этот дефект. Если ваша Паскаль-система допускает интерактив- ный ввод, предусмотрите в программе сообщения для ее пользователя обо всех требуемых данных. НЖ 1 (с. 70) Теоретический анализ этого и других методов получения случайных чисел можно найти в книге: Д. Кнут. Искусство программирования для ЭВМ. т.2 Получисленные алгоритмы, М.: «Мир», 1977. 2 (с. 74) Слово FORWARD является не предопределенным, а зарезервированным словом в языке Паскаль. Это означает, что его нельзя переопределить в программе, в отличие, скажем, от предопределенного слова REAL. з ) (с. 77) В примере программы twigs переменная у не является локальной по отношению к внутренним процедурам, поэтому использование ее для возврата результата - тоже побочный эффект. 78
у СТАНДАРТНЫЕ ТИПЫ ОПРЕДЕЛЕНИЕ ТИПОВ ПЕРЕЧИСЛЯЕМЫЕ ТИПЫ ИНТЕРВАЛЬНЫЕ ТИПЫ ТИП МНОЖЕСТВ И ПЕРЕМЕННЫЕ ТИПА МНОЖЕСТВО КОНСТРУКТОРЫ МНОЖЕСТВ И ОПЕРАЦИИ НАД МНОЖЕСТВАМИ ФИЛБТР2 (ПРИМЕР) МУ-У-У (ПРИМЕР)
REAL, INTEGER, CHAR, BOOLEAN ~ СВОДКА - ^онстанты стандартных типов могут быть определены в разделе CONST любого блока. Объявлять тип каждой константы не надо: он узнается по форме написания: CONST константа CONST pi=3.14; increment^; star=>/; лдесятичнаяЛочка) следовательно, у "рг - типа REAL ) 'мет десятичной) точки, значит^ Т тип INTEGER] 7апострофьс\—^ следовательно, ’— * - типа CHAR. или из сопоставления с некоторой ранее определенной константой: p=pi; stella=star; verily=TRUE; decrement-^ increment; .... —1 ................ —....выражения недопустимый (nредел сложности x=-uJ"—z IJ ип каждой переменной должен быть объявлен в разделе VAR того блока, в котором эта переменная будет использована: [J ип каждого параметра должен быть объявлен в заголовке процедуры или функции. FUNCTION mix(r:REAL; i:INTEGER, с:CHAR):BOOLEAN; VAR s:REAL; j:INTEGER; lettenCHAR; ok:BOOLEAN ^Арифметические действия над стандартными типами рассмотрены в гл. 4; частности, описано смешение типов REAL и INTEGER в выражении и преобра- зование результата из одного типа в другой. выражения, использующие тип CHAR или операции NOT, AND, OR приводят к результату типа BOOLEAN. приведенные ниже правила касаются порядковых значений: • Целое имеет порядковое значение, равное себе (ORD(6) - это 6), а, Следовательно, имеет предшествующее и последующее ( PRREDI6) - это 5, SUCC(6) - это 7). • Величина типа REAL не имеет порядкового значения. • Порядковые значения величин типа CHAR таковы, что ORD(’A* *)<ORD(’B’) и т.д.; ORD(T)-ORD(’0’) равно 1, ORD(’2’)-ORD(T)=2 и т.д. • При написании условия неявно используется функция ORD( ); таким образом, ORD(’I’)<ORD(’J’) может быть упрощено до *Г<*Л*. Однако напомню еще раз, что SUCC(4’) - это не обязательно ’J’ и не обязательно ORD(’J’)-ORD(*r) равно 1. 80
ПЕРЕЧИСЛЯЕМОГО, ИНТЕРВАЛЬНОГО И ТИПА МНОЖЕСТВО программиста есть возможность определить свои отличные от четырех стандартных типов. Для этого типов соответствующего блока. Раздел определения между разделами CONST и VAR, что иллюстрируется собственные простые типы, служит раздел определения типов (TYPE) располагается ниже на этой странице. Здесь Дан синтаксис раздела TYPE (опущены структурные типы, которые рассматриваются, начиная со следующей главы). Три типа имеют соответственно названия: перечисляемые типы, интервальные типы и типы - множества. Зот пример перечисляемого и двух интервальных типов: ""type u* 4Я = KOh SEI ; 1станта.. константа Г OFl имя типа () константа.. константа^ PROGRAM dodo( IN PUT, OUTPUT); CONST pi=3.14; TYPE daytype= (mon,tue,wed,thu,fri,sat,sun); weekdaytype= mon..fri; dicetype= 2..12 перечисляемый тип VAR х: REAL; ЗпослеДствии имена daytype, weekdaytype, dicetype могут быть использованы для опре- деления переменных наравне с именами REAL, INTEGER, CHAR и BOOLEN. j^===^ ^1/\ожно поступить иначе, убрав определение типа из раздела TYPE и включив его в раздел VAR: today: daytype; throw,score: dicetype; PROCEDURE egg(VAR d:daytype); PROGRAM dodo(INPUT, OUTPUT); CONST pi=3.14; TYPE daytype= (mon,tue,wed,thu,fri,sat,sun); VAR x: REAL; --- today: mon..fri; определения типов, V----* th row, sco re: 2.. 12 перенесены в раздел VAI но в заголовках функций и процедур такая свобода не допустима: PROCEDURE egg(VAR d: daytype); /^^типпараметра должен^-— —....... ...Г. '^^^Ъбыть определен в разделе TYPE^ (Леречисления и интервалы полезны при проверке правильности программ - благодаря им обеспечивается автоматическая проверка диапазона изменения переменных: WHILE throw >= score DO si mulate(throw, score); CASE today OF mon,tue,wed,thu,fri: WRITEf Работай')', sat,sun: WRITEfИграй') END { CASE } выдается сообщение о! ошибке, если хоть одш переменная выйдет Т~ за границы 81
GO иже даны определения двух перечисляемых типов и соответствующих переменных: TYPE VAR days» (mon, t ue, wed, t hu, f ri, sat «sunk.—----------x— status-(wedded^nwed)£2/x33^cbwcno4b3oea«o слово wedded» а~нё\ wed, поскольку в перечислении все имена должны быть различными^/ today, tomorrow: days Зы не можете считывать или печатать значения перечисляемого типа: READ(today, tomorrow);: WRITE(fri, today) Вы можете присваивать значения переменным перечисляемого типа: today-=mon; tomorrow:-today но только в том случае, когда переменная и значение относятся к разным перечислениям: today:-unwed; К перечисляемому типу неприменимы арифметические действия: today:-sat + sun^ константы перечисляемого типа имеют порядковые значения, начинающиеся с нуля: WRITELN(ORD(mon),ORD(tue),ORD(sun)); | Следовательно, можно вычислять предшествующие и последующие элементы: Однако первая константа не имеет предшествующего элемента, а последняя - последующего: today -PRED(mon); tomorrow:-SUCC(sun); В логических выражениях для всех элементов значения, можно не писать ORD( ):_________ IF ORD(today)>ORD(mon) THEN sayso; IF today>mon THEN sayso Паскаля, имеющих порядковые зти^два операторам работают одинаково ^ип BOOLEAN - перечисляемый тип, определение которого автоматически присутствует в Паскаль-программе: TYPE BOOLEAN - (FALSE,TRUE) откуда заключаем, что ORD(FALSE) есть нуль, ORD(TRUE) - 1 и FALSE < TRUE. 82
ИНТЕ .03ЛЛ2ЙЙЕ там интервалы из константа .. константа! ПЕРЕЧИСЛЕНИЙ, (нижняя \ (вё'рхНяя'^ ЦЕЛЫХ и ЛИТЕР цД граница^ от определения переменных нескольких интервальных типов TYPE daytype- (mon,tue,wed,thu,fri,sat,sun); VAR weekday: mon.. f ri; _ t hrow, score: 2.. 12 ^^^^^нтервал 7una IHTEG^) m us ke t eer: 1.. 3; Z * ~ grade: ’A’.. * D* интёрвал^тйпсР^НАР^ niTmrHmrnMMTniiriiiiuririirnnminiBiM .. 'Оги 1 inn n i,i»i'7>-.,a</'>x jr\ ’x^^ax а„Р.а»ЬнЫЙ тип может быть определен на основе любого типа, имеющего порядковое значение. Тем самым исключаются интервалы типа REAL: VAR price - (1.99..5.99) Reaf) | [/Лнтервалы перечислений подчинены тем же самым ограничениям, что и сам перечисляемый тип. Так, элементы типа mon..fri не могут читаться или печататься, к ним нельзя применять арифметические действия, их нельзя присваивать переменным никаких других типов, кроме mon..fri и daytype ( где daytype - базовый тип, по отношению к которому mon..fri - интервал и они, следовательно, совместимы). [^онстанты любого интервального типа имеют порядковые значения, совпадающие с их порядковыми значениями в базовом типе. Так, в интервале sat..sun, базовый тип которого - daytype, значения ORD(sat) и ORD(sun) будут, соответственно, 5 и 6, но не 0 и 1. Зел и базовый тип интервала - INTEGER, то значения такого интервального типа могут обрабатываться как целые числа. Такая обработка включает чтение, печать и целую арифметику: READ(throw); score: -SQR(throw); Write(score) Более того, можно смешивать значения из различных интервалов: Вместе с тем всякий раз перед изменением значения переменной (посредством присваивания, чтения и т.п.) проверяются ее границы. В этой автоматической проверке объявленных границ и есть смысл интервалов. В результате програм- мист может не отвлекаться на частые проверки вида IF (score>12) OR (score<2) THEN WRITEfscore лежит вне границ’). В хорошо написанных программах вы скорее увидите VAR score: 2.. 12 нежели VAR score: INTEGER. IЛели базовый тип интервала - тип CHAR, то значения из интервала могут обрабатываться как литеры, в том числе их можно читать, печатать и использовать в логических выражениях: READ(grade); IF grade <- ’В’ THEN WRITEfXopomo сделано!’) ELSE WRITEf Надо поработать’) 83
тип 3 общих словах, множество - это набор элементов одинакового типа. В Паскале вы можете создавать и именовать множества для отслеживания элементов любого упорядоченного типа ( исключая тип REAL и структурные типы). «Отслеживание» здесь означает сохранение информации о наличии или отсутствии каждого элемента. 0 следующем примере сначала определяется перечисляемый тип, а затем - тип множества. Базовым типом является тип daytype: TYPE daytype= (mon,tue,wed,thu,fri,sat,sun); dayset - SET OF daytype а здесь определены две переменные, хранящие множества дней по описанной ниже схеме: VAR washdays, bathdays: dayset; 0 какой-либо момент выполнения программы эти две переменные могут выглядеть, например, так: ЙИ 1111 н t. eXmon wed тпЧ г. e.fmon thu frh Видно, что информация, содержащаяся в переменной типа множество, включает по одному логическому значению ( присутствует или нет) для каждого возможного элемента множества. |^>ак при определении перечислений или интервалов, здесь также допускаются сокращения за счет перенесения определения типа в раздел VAR: TYPE daytype= (mon, tue, wed, thu, fri,sat,sun); washdays,bathdays: SET OF daytype можно и вовсе опустить раздел TYPE: _________________ _ ___ washdays/bafhdays: SET OF (mon,tue,wed,thu,fri,sat,sun); 0 следующем разделе VAR определены несколько переменных типа множество: VAR washdays,bathdays: SET OF (mon, tue,wed, thu,fri,sat,sun); teaset: SET OF CHAR> letters: SET OF ’A’..’Z’ digits: SET OF dice: SET OF 2.. 12 полное множествд^лите^ зависит от системы*-/'"* dice digit для некоторых) > систем SET OFN INTEGER слишко. ч велико изображено заполненным изображено пустым 84
[^онструктор множества определяет которое затем можно присвоить переменной, или как-либо рбработать, или и то и другое вместе. Конструктор множества можно рассматривать как константу типа множество. множество ~ ► [2*3..3*3,5+6,5] ► [5,6,7,8,9^11] < । одина-\ i ковые множеству ^/\ножество можно сделать пустым: die dice: =[2*3.. 3*3,5+6^ 5д (Объединение двух множеств обозначается знаком все эти элементы законн в интервале dice:=[]; СЗли присвоит такое значение: dice:» [2..5] + [4..6]; [/Пересечение обозначается звездочкой: . плюс: dice:- [2..51 * [4..61; die разность двух- множеств обозначается знаком минус: dice:» [2..5] - [4..6]; dice:» [4..6] - [2..5]; dic< X'l z 1 2V6/ 4 4/3’ dice- i ♦ ^!/\ножества можно сравнивать. Использование компараторов IN, >=, <-, = применительно к переменным типа множество или конструкторам позволяет строить логические выражения. ринадлежность отдельного элемента множеству может быть выявлена при помощи операции IN: WRITELN(6 IN [4..6],6 IN [2..5]);|t^ TRUE FALS IN ртя выяснения, содержит ли одно множество ругое, используем >= TRUE FALS WRITELN([2..12] >= [3..5]);|QK WRITELN([3..5] >= [2.. 12]); pin выяснения, содержится ли одно множество в другом, используем <= WRITELN([2..12] <= [3..5]); WRITELN([3..5] <= [2.. 12]); FALSE TRUE ^*\ля проверки совпадения двух множеств используем = или о WRITELN([3..5] = [5,4,3]); Jbj WRITELN([3..5] <> [4,5,3]); TRUE FALSE 85
ИЛЛЮСТРИРУЕТ ПРОЦЕДУРЫ, КОТОРЫЕ ВЫЗЫВАЮТ ДРУГ ДРУГА, ПЕРЕЧИСЛЯЕМЫЕ ТИПЫ И КОНСТРУКТОРЫ МНОЖЕСТВ [Программа filter (на с. 59) считывает входной файл, и печатает в выходной файл все распознанные числа. Представленная ниже программа имеет то же назначение. Она несколько длиннее предыдущей версии, хотя, возможно, понятнее, поскольку она более прямолинейна. Процедуры используются наипростейшим образом; они работают только с глобальными переменными: PROGRAM filter2(INPUT,OUTPUT); VAR state: (ignoring,pending,reading); fraction: 0..MAXINT; ch: CHAR; positive: BOOLEAN; number: REAL; PROCEDURE initialize; { начальная установка } BEGIN state:-ignoring; positive:- TRUE; number-0; fractiom-O END; PROCEDURE display; BEGIN IF fraction>0 THEN number IF NOT positive THEN number WRITELN(number:10:2); initialize END; PROCEDURE accumulate; { накапливать число BEGIN number-10*number + ORD(ch)-ORD(’0’); fraction:-10*fraction; state:-reading END; PROCEDURE negate; BEGIN positive:-FALSE; END; BEGIN { программа } initialize; WHILE NOT EOLN DO BEGIN { WHILE } A READ(ch); ' CASE state OF ignoring: IF ch-’-’ счисляемый { затем начальная установка } number/fraction; -number; { и установить ожидание state:—pendi ng и установить чтение } ^/попробуйте программу с данными) приведенными \на с. гнор. } state"** symbol IN [0..9] ♦ » прочие игнорировать: accumulate в и перейти игнор. negate и ожид. игнор. ждать: к чтению initialize 1 читать: f гас.:—1 display и initialize ждать } читать } THEN negate ELSE IF ch IN [’O’..’9’] THEN accumulate; pending: IF jh IN [’O’..’9’] accumulate; initialize; THEN ELSE reading: IF ch-’.’ THEN ELSE \ END { CASE } END; { WHILE } IF state-reading THEN END. _____ fraction:-! IF ch IN [’O’..’9’] THEN accumulate; ELSE display недостаток:любая V литера работает как признак конца числа, например, $-8-9* даст: -8.00 / ><9.00/—Z display чтобы не упустить число V в самом конце^ файла 86
ИГРА ДЛЯ ИЛЛЮСТРАЦИИ ИНТЕРВАЛЬНОГО ТИПА И МАНИПУЛЯЦИИ НАД МНОЖЕСТВАМИ компьютер задумывает четырехзначное число, не содержащее двух одинаковых цифр. Вы набираете свое число и компьютер сообщает количество быков (точно угаданных цифр) и количество коров (цифр, которые есть в задуманном числе, но на другом месте). Например, пусть задуманное число 5734, а вы набрали 0755. Результат будет 1 бык и 2 коровы. Игра продолжается до тех пор, пока вы не получите четыре быка. PROGRAM mooo( IN PUT, OUTPUT); TYPE playtype = ’O’..’9’; seedtype = 0..65535; scoretype = 0..4; VAR pool, target: SET OF a,b,c,d: playtype; seed: seedtype; bulls,cows: scoretype; playtype; pool например target у 6, а не 5,—> поэтому результат всегда меньше. !. О FUNCTION random: REAL; BEGIN 42 random: =seed/65536; seed;=(25173*seed + 13849) MOD 65536; I END; { random } FUNCTION unique: playtype; VAR ch: CHAR; BEGIN /REPEAT f ch:=CHR(TRUNC(10*random)+ORD(’0’); UNTIL ch IN pqoI;^^— unique:=ch; зностьм н ож&ств') poo 1:=poo 1 -[ch ]; 4 t a rge t:«t a rge t +fch ] < END; { unique )ение множеств)- fPROCEDURE try(thisone: CHAR)?4—T VAR ch: CHAR; BEGIN READ(ch); IF ch IN target THEN IF ch=thisone THEN bulls:=SUCC(bulls) ELSE cows:»SUCC(cows) END; { try } BEGIN { программа } WRITE(’3aflaftTe случайное число,’); WRITELNf затем о READLN(seed); pool:=[’0’..’9’]; target:=[]; a:=unique; b:=unique; c:=unique; REPEAT f bulls:=0; cows:=0; I try(a); try(b); try(c); try(d); \ WRITELN(’BbiKOB: ’,bulls:!,’; коров: ’.cows:l); \ READLN UNTIL bulls=4; END. { программа } возвращает случайное > число в интервале \ ( 0.0 - random < 1.0 ) (переместить случайную \ цифру из ’pool’ в У* s'* прочитать *— (следующую цифру и, если необходимо, у* увеличить число V I быков^илц^крррв) пустое множество d:=unique; о мп за дум ывает число лЬсй ^Задайте случайное 35109 1234 Быков: 0; 5678 Быков; 1; 5990 Быков: 2; коров: 1 коров: О коров: О 87
Iq (^Эополните программу filter2 на с. 86 так, чтобы она могла обрабатывать числа, записаные в «научном» формате, где «Е» означает «умножить на 10 в степени»: |ля этого потребуется расширить таблицу переходов. So [^опробуйте игру МУ-У-У со с. 87. Усовершенствуйте игру. Для этого модифицируйте программу так, чтобы она: предлагала сыграть еще раз, после того как игра закончилась • останавливала игру и считала ее выигранной компьютером, если задуманное число не было правильно угадано после десяти попыток • отдельно подсчитывала число выигрышей игрока и компьютера и выводила счет на экран. (с. 87) Пример работы программы тооо в правом нижнем углу страницы вымышленный. Как можно установить ~ даже без помощи компьютера ~ не существует числа из четырех различных цифр, для которого программа тооо даст ответы, показанные в примере. Действительно, из первых двух ответов видно, что среди цифр 1, 2, 3, 4, 5, 6, 7, 8 есть всего две цифры задуманного числа; но всего в числе - 4 различные цифры, значит, обе оставшиеся цифры - 0 и 9 - обязательно входят в задуманное число. Но тогда, рассматривая последние три цифры 9, 9, 0 вопроса 5990 программа тооо каждую из них посчитает коровой или быком (при наличии в вопросе совпадающих цифр программа тооо действует не вполне логично), следовательно, сумма быков и коров в третьем ответе должна быть не меньше трех.(Фактически, при указанном начальном значении случайного числа, программа задумывает число 5920.) 88
и (gWOEEJ ЗНАКОМСТВО С МАССИВАМИ СИНТАКСИС ОБЪЯВЛЕНИЯ МАССИВОВ ПЛОЩАДЬ МНОГОУГОЛЬНИКА (ПРИМЕР) ПРОВОДА (ПРИМЕР) СОРТИРОВКА МЕТОДОМ ПУЗЫРЬКА (ПРИМЕР) БЫСТРАЯ СОРТИРОВКА (ПРИМЕР) УПАКОВКА ЗНАКОМСТВО СО СТРОКАМИ ФОКУС (ПРИМЕР) СИСТЕМЫ СЧИСЛЕНИЯ (ПРИМЕР) УМНОЖЕНИЕ МАТРИЦ (ПРИМЕР) НАСТРАИВАЕМЫЕ ПАРАМЕТРЫ-МАССИВЫ
(S шетм ПРЯМОУГОЛЬНЫЙ МАССИВ КОРОБОЧЕК КАКОГО-ЛИБО ОДНОГО ТИПА ^1\о сих пор переменные стандартного типа изображались отдельными маленькими коробочками: VAR x: REAL; i,j: INTEGER; alive: BOOLEAN; cyfer: CHAR; это относится и к переменным перечисляемого и интервального типов? 'простыеХ-^ переменные\ стандартных] ч* типов TYPE daytype - (mon,tue,wed,thu,fri,sat,sun); VAR today: daytype; workday: mon..fri; throw: 2.. 12; ] workday £ throw простые\_ . , переменные\ перечисляемого t? интервального^ типов .' вместе с тем, имеется также возможность объявлять переменные, которые являются массивами таких маленьких коробочек: TYPE daytype - (mon,tue,wed,thu,fri,sat,sun); session - (morn,aft,eve); VAR vector: ARRAY[1..3] OF REAL; roster: ARRAY[mon..fri,session] OF BOOLEAN; vector[l] vector[2] vectorf3] массивы^к rosterfmon, 16.5 rosterftue, rosterfwed, rosterfthu, rosterffri, morn] aft] eve] V X £~кдмпонента rosterfthu, eve} ^^дм n онентаК^}-^ (обозначается vector[3] ^/\a лень кие коробочки массива называются компонентами; в квадратных скобках стоят индексы. Базовый тип массива - это тип маленьких коробочек, из которых составлен массив (в каждом массиве все компоненты одного типа). юмпоненты можно обрабатывать так же, как переменные базового типа: vector[2]:-i6.5; READ(vector[3]); " rosterftue, aft]:-TRUE; WRITE(roster[tue,aft]); roster[wed,eve]:- NOT rosterftue,aft]; (®/днако использование компонент массива в качестве обычных переменных не дает никакой выгоды. Массивы ценны тем, что индексы могут быть переменными или выражениями, обеспечивая доступ к последовательным компонентам. Взгляните на следующий фрагмент: | rFOR day:- mon ТО tue DO j f FOR time:- morn TO eve DO j rosterfday,time]:-FALSE; I FOR i:- 1 TO 3 DO vector[i]:=0; записываем FALSE" во вс компоненты массива roster и 0 во все компоненты массива vector Предполагается, что предварительно в разделе VAR объявлено i: 1..3, day: mon..fri и time: morn..eve . iLJ a,iveD 90
№ЯВ^ЕНИЯ ©ив: л\., ссивы, изображенные напротив, могли быть объявлены после предварительного определения соответствующих типов: TYPE daytype - (mon,tue,wed,thu,fri,sat,sun);«tf£3r=^^l session - (morn,aft,eve); 1 vectortype - ARRAY[1..3] OF REAL; rostertype - ARRAY[mon..fri,session] OF BOOLEAN^ перечисляемые) ^типЫ'^У'''^ ^^^~~^/Уипы\ массивовi VAR vector: vectortype; roster: rostertype; переменные-массивы/^определенны^ в терминах типов массивов синтаксис типа массива: ^должно быть имя 'упорядоченного типа, 'packed ARRAY [ мя типа имя ) ] OF тип. константа.. константа ^/специфицирует\^ /любой базовый тип} J (тип запись иди > массив не исключаются) щнтаксис обращения к компоненте массива: \^/бработка массивов производится путем изменения индексов компонент, как показано напротив. Есть, однако, одно важное исключение: копия всего содержимого массива может быть присвоена другому массиву того же типа одной операцией: имя имя^. y^UM^MO^CUe^Y ПРИСВАИВАНИЕ /массивау^ того же типа) ЦЕЛИКОМ Здесь слова «того же типа» означают тип с таким же именем. Тип, имеющий такую же спецификацию, но другое имя, не подходит: TYPE at у ре - ARRAY[1..3] OF REAL; VAR a,b: atype; c: ARRAY[1..3] OF REAL; (Tl^nb^c^odSfl^) ^а^/одинаковая\^ a:-b и менем/ a:^(спецификация^ [/Исключением тут является тип 'РАСКЕ& ARRAY[ ] OF CHAR, для которого в некоторых версиях Паскаля эквивалентность не обязательна. Измените в последних примерах REAL на CHAR, и запись а:»с станет корректной. массив, такой как массив roster, на самом деле является массивом массивов. Нижеследующий синтаксис вполне правомерен, но слишком неуклюж: TYPE rostertype - ARRAY[mon..fri] OF ARRAY [session] OF BOOLEAN; roster[day][t i me]: «FALSE 91
ПРИМЕР, ИЛЛЮСТРИРУЮЩИЙ ИСПОЛЬЗОВАНИЕ ОДНОМЕРНЫХ МАССИВОВ [Досмотрите на схему справа: Заштрихованная площадь А.. дается выражением: ' А</ - 2<Х/У/ - Х/УР - - |(2*3 - 2.5*1) - 1.75 12 3 4 5 Tfa же формула может быть использована для вычисления площади на рисунке слева. Но здесь площадь оказывается отрицательной'. А(/= 2<Х(У/ - х/уР = j(3*2.5 - 5*4) - -6.25 Ч^/ормулу можно применять к последовательным сторонам многоугольника. Сумма площадей треугольников даст изображенную здесь площадь §сли многоугольник замкнут, как показано слева, то сумма площадей даст площадь, заключенную внутри него. (Ограниченная контуром область должна лежать слева от каждой стрелки; стороны фигуры не должны пересекаться, как, например, у восьмерки. едующая программа вводит координаты граничных точек и вычисляет площадь многоугольника: PROGRAM polygon(INPUT, OUTPUT); TYPE поставьте сюда" требуемый spantype - 1..30; VAR i,j,n: spantype; area: REAL; x,y: ARRAYfspantype] OF REAL; наибольший размер задачи ,15 BEGIN инициала 1-3; если но если. ация то 1=2, то и т.д., i=n, то еслйУваш Паскаль интерактивный, вставьте: WRITELN (’Число вершин?*); READLN(n); jfFOR i:=l ТО n DO <READLN(x[i],y[i]); агеа;=О; FOR i:-l TO ri / BEGIN [ j:-(i MOD n) + 1; \ area:-area+0.5*(x[i]*y[j]-x[j]*y[i]) \ END; WRITELNfПлощадь равна \агеа:8:2) END. 7 0 8 8 3 7 7 0 исло вершин? 0 \ 6 I 1 I 15 \ 12 8 4 8 \ Площадь равна 53.01 92
РАБОТА С МАССИВАМИ КАК С ВЕКТОРАМИ - И СОВСЕМ НЕМНОГО МАТЕМАТИКИ kijea силовых кабеля а и ?, если наложить эти схемы, кажутся до страшного близки других другу. Каково же наименьшее расстояние между а и о? тригонометрическое решение будет очень громоздким; _здесь нас выручит векторная алгебра. Представим а и ? как векторы: а = (9-4)1 + (16-8)1 + (17-0)1 I = (10-6)1 + (11-3)1 + (15-5)1 2Qx векторное произведение ах?- это вектор, перпендикулярный и а и ? ^Промасштабируйте его, поделив на его же длину |ах?| и вы получите единичный вектор, параллельный вектору а х ?: и = axt + | ах ? | £ озьмите вектор с, соединяющий любую точку векта^а а с любой точкой вектора Здесь изображен один из них; он соединяет конец а с концом К: с = (10-9)1 + (11-16)1 + (15-17)1 (кратчайшее расстояние d между а и ? дается проекцией ~с на и (т.е. скалярным произведением с и и), которая равна: d = с • и PROGRAM cables(INPUT,OUTPUT); TYPE vector = ARRAY[1..3] OF REAL; VAR coord a,b,c,u: vector; d, length: REAL; coord: ARRAY[1..12] OF REAL; i: 1..12; г 2 з: 4 BEGIN C^FOR FOR f BEGIN a[' \ b[i v cn: END; чтение данных i==l TO 12 DO READ(coord[i]); i;=l TO 3 DO - coord[i]; - coord[6+i]; - coord[3+i] армирование а, Б, с u[l u’2}= a u[3]:= a =coord[3+i =coord =coord ’9+i 9+f 2]*b[3] - b[2]*a[3]; 3 *b 1 - a 1 *b 3 ; 4]*b[2] - b[l]*a[2]; и = ах 6 ’1-] 2: 3' a a 2 '3 Ь2 6 7: :8: 9 [io: [n: [12 a Z A 7 length;=SQRT(SQR(u[l])+SQR(u[2])+ SQR(u[3])); ^FOR i:=l TO 3 DO u[i]:=u[i]/length; d:=c[l]*u[l]+c[2]*u[2]+ c[3]*u[3]; WRITELN(*KpaT4aftmee расстояние’, d:6:2) END. [Г [2: [з: c П -5» -2 1£L 9 Ifi. XL 3 5_ 10 JU- 15 1 2 з: начало a конец а начало ? конец b IZ £_ 10 Г 2 :з: u .716 -656. .239 а х 3 93
заклейменная одним из моих РЕЦЕНЗЕНТОВ КАК «НЕПРЕ- лир? г\ ВЗОИДЕННАЯ В СВОЕЙ неэффективности» ЗьГГй, Существует много методов сортировки (упорядочения) содержимого массива. Ниже описан несложный метод пузырька. ^[]тобы разобраться в нем, возьмем список из букв. «Пометим» первую и следующую за ней буквы. Если порядок букв правильный, то так их и оставим и передвинем метку вниз на одну строчку. Если порядок букв неверный, то поменяем их местами и также сдвинем метку вниз. Закончим процесс, не доходя одной строчки до конца списка, элементом за пределами списка. с тем чтобы предотвратить Вот картинка, поясняющая сравнение с метод: [1] правильно [3] оставляем [4] [5] IL CD Р’ неверно меняем 21 А’ ГГ 2‘ !3‘ ‘4' !5! LBL 'Z'неверно ’Р’| XI ’А’ меняем !з !4] *] Lb: ’Z’ х: ’А’ неверно меняем :&] ’A’ самый тяже^ ^лый элемент^ 5] LZT i 2 букву, осталось отсортировать список букв, лежащих поступим так же, как и с полным списком. Другими «4/топив» самую тяжелую сверху. С этим списком мы словами, обратимся к уже известной процедуре рекурсивно. [^олее аккуратный подход к сортировке - поставить в соответствие . сортируемому массиву элементов массив указателей (pointers): Теперь закончится, указатели, а не сами элементы. Когда сортировка можно переставлять массивы будут выглядеть так: т 2 !з! А' 5" .В? -А -*(Г >[2' ~^з: ~>(4: -Ч5: ГВ’ IDLj {например, letters[pointers[4]] - lAj указатели буквы всего несколько букв, то этот подход не дает Зсли цель - отсортировать особых выгод. Но в реальных условиях с каждым сортируемым элементом может быть связано значительное количество информации, и гораздо проще двигать указатели нежели всю информацию, на которую они указывают. 94
Зот полная программа сортировки букв: PROGRAM bubbles(INPUT,OUTPUT); TYPE sizetype - 0..30; VAR pointers: ARRAY [sizetype] OF sizetype; letters: ARRAY [sizetype] OF CHAR; key: CHAR; n,i: sizetype; PROCEDURE swop(VAR p,q: sizetype); VAR tempry: sizetype; BEGIN temprys-p; p:-q; qs-tempry END; PROCEDURE sort(first;last: sizetype); VAR i: sizetype; sorted: BOOLEAN; ) BEGIN { sort } ' IF first < iast THEN BEGIN < sorted:- TRUE; ) FOR i:-l TO last-1 DO / .BEGIN f IF letters[pointers[i]]>letters[pointers[i+l]] , / THEN ) BEGIN ' I swop(pointers[i],pointers[i+l]); \ sorted:-FALSE; \ END { if letters } -------- 'END; { for i } IF NOT sorted THEN sort(first, last-1) END { if first < last } END- { sort } ПРОЦЕДУРА СОРТИРОВКИ рекурсивный вызов BEGIN { bubbles } READLN(n); FOR i:-l TO n DO /BEGIN READLN(letters[i]); \ pointers[i]:-i 'END; sort(l,n); WRITELN; /FOR i:-l TO n DO ^~WRITE(letters[pointers[i]]) END. { bubbles } есливаш Паскальинтерактивный) вставьте: WRITELN(’Число букв?’)^ прочитать последовательновсе буквы и установить указатели, ABCDZ Число букв? 5 В z D C A сортировать буквы ^отя метод пузырька неэффективен при сортировке запутанного списка, список, в котором всего одно или два значения стоят не на месте, сортируется очень быстро. 95
ПРИМЕР ДЛЯ ИЛЛЮСТРАЦИИ УД РЕКУРСИИ И ЕЩЕ ОДНОГО МЕТОДА СОРТИРОВКИ (/СЛДетод, называемый быстрой сортировкой, был придуман, профессором Ч. Хоаром. Ниже приведена интерпретация этого метода, которая скорее иллюстрирует принципы, нежели прдеставляет собой рабочий продукт. Юзьмем для сортировки несколько чисел: 7 3 12 9 2 14 13 8 ^Установим указатели i и /, как это показано, на концы списка. Будем двигать / по направлению к I. Если число, на которое указывает /, больше, чем то, на которое указывает t, то передвигаем / на один шаг. Теперь / указывает на меньшее число, чем I. Поэтому меняем местами эти два числа и сами указатели: ---- [Продолжаем двигать / по направлению к i (теперь это будет движение направо, а не налево). Если / указывает на число меньшее, чем то, что относится к i, то передвигаем / на шаг вправо. (Обратите внимание, что условие движения / изменилось на обратное.) 13 Т) Теперь / указывает на число, большее, чем то, что относится к I. Меняем числа, указатели, направление и условие, как уже делали раньше: (3 2^7 9^42 14^3 8) [Продолжаем в том же духе, когда нужно переключаясь, пока / не встретится с Г. GnJa этом этапе можно утверждать, что все числа слева от числа с указателем i не превосходят, а все числа справа - не меньше этого числа. Другими словами, отмеченное число находится на своем законном месте. Тем не менее, числа слева от i не отсортированы; не отсортированы и числа правее i. Однако уже описана процедура размещения одного элемента, которая разделяет группу на две; теперь остается только отсортировать группы справа и слева от I, начав сортировку по схеме, которая подробно изложена выше. 96
ieio можно изобразить так: 12 14- 13 в-третьих, сортируем это 3 8 2^7 ^9 i IF /' во-первых, «сортируем» ________I один элемент'-- \ во-вторых, [сортируем это рекурсия применима тогда, когда задача может быть сведена к такой же задаче или таким же задачам меньшего размера. Когда размер задачи станет достаточно малым, в рекурсивной процедуре должно быть, разумеется, предусмотрено завершение. В случае сортировки это должно произойти тогда, когда процедура вызывается для сортировки одного элемента. [д]иже приведена процедура быстрой сортировки, которую можно использовать вместо процедуры сортировки методом пузырька на предшествующем развороте: если firstclast, то сортировать нечего Z ПОДСТАВЬТЕ^ ЭТУ ПРОЦЕДУРУ В ПРОГРАММУ Н Г ПРЕДЫДУЩЕМ* I РАЗВОРОТЕ J PROCEDURE sort(first, last: sizetype); VAR i,j: sizetype; jstep: -1..1; condition: BOOLEAN; BEGIN IF firstclast THEN BEGIN i: first; j— last; jstep:—1; condition:» TRUE; .REPEAT f IF condition=(letters[pointers[i]]>letters[pointers[j]]) " / THEN _________________________________________ / BEGIN ^ествр?Игерес7а новкаэл e ментов^ I swop(poi nters[ i], poi swop(i,j); —^^^естан^кс^указателейХ-^^ I jstep: = -jstep: ^^^^Q^Me^^u^^npa^eHi^ \ condition:» NOT condit \ END; \ j=- j+jstp ' UNTIL j-i; sort(f irst, i-1); sort(i+l, last) END { if first<last END; { sort } рекурсивные вызов выход^если сортировать нечего (®)братите внимание, как переключается условие между <» и >. Логическое выражение letters[pointers[i]]>letters[pointers[j]] принимает значение true или false. Это значение сравнивается с тем значением, которое хранится в переменной condition. Это значение, посредством NOT, переключается- между true и false. каждый раз, когда процедура вызывает себя, компьютер должен запомнить значения, параметров и локальных переменных для возможного использования их при возврате. Это было проиллюстрировано на простом примере рекурсии на с. 75. В примере выше, переменные jstep и condition можно было бы сделать глобальными и, таким образом, сэкономить память. Но поступать так в столь маленьких задачах, как приведенные в этой книжке, было бы глупо. 97 4-458
КОМПРОМИСС МЕЖДУ быстродействием и ОБЪЕМОМ ПАМЯТИ (НЕКОТОРЫЕ КОМПИЛЯТОРЫ УПАКОВЫВАЮТ АВТОМАТИЧЕСКИ) [^^ля представления логического значения требуется один бит [] (т.е. двоичная цифра); литера обычно требует четыре бита | । 11 |; целое - 16 или 32 бита lit.....I । । । । 1 । । I । ..I । । । । । । । I. Однако, в компьютере единица хранения - - это слово. Размер этого слова зависит от марки и модели компьютера, обычно это - 32 бита. Отсюда следует, что хранение логических значений и литер (возможно, даже целых) по одному в слове - расточительство памяти. СЗ Паскале слово PACKED в определении массива (или записи) дает компилятору право упаковать информацию более плотно, чем один элемент на слово. Например, строчка: PACKED ARRAY [1..32768] OF BOOLEAN если, конечно, компилятор упакует компоненты этого массива до тридцати двух в слове, позволит, быть может, достьчь чего то, что в другом случае было бы невозможно. (Некоторые современные компиляторы выполняют упаковку автоматически.) [Щена за сэкономленную память - пониженная скорость обращения к памяти во время выполнения. [дЗаланс между быстродействием и памятью в некоторых системах может достигаться выборочной упаковкой: скажем, обрабатывается неупакованный массив, затем для хранения его содержимое копируется в упакованный массив. я этих целей в Паскале имеются процедуры РАСК и UNPACK. [Щиже приведены два типичных обращения к этим стандартным процедурам: VAR prolix: ARRAY[1..1000] OF CHAR; pith: PACKED ARRAY[1..1000] OF CHAR; PACK(prol ix, 1, pi thk UNPACK(pith,prolix,!); 98
(S© В НЕКОТОРЫХ ВЕРСИЯХ П ИМЕЕТСЯ СПЕЦИАЛЬНЫЙ СТРОКОВЫЙ ТИП Строковая константа состоит из букв, заключенных в апострофы. Если в строку нужно включить апостроф, то ои должен быть записан в виде пары апострофов: WRITELNfOohl ’, ’It”s coldl’) ~~| у^боЫ It’s coldl/ строковую переменную в стандартном Паскале выделяется PACKED ARRAYf] OF CHAR: VAR shiver: PACKED ARRAY[1..1O] OF CHAR shiver | , , .,.....I 1 10 (Строковые константы можно присваивать строковым переменным: shiver *lt”s cold!’; WRITELNCOoh! *, shiver) Ooh It’s cold Gn]o в стандартном Паскале присваивание допустимо, только если количество литер константы совпадает с размером упакованного массива: shiver ’Ooh!’;^^ shiver ’Ooh!!!!!*; { o.k. } МНОГИХ СОВРЕМЕННЫХ' КОМПИЛЯТОРАХ НЕ ТРЕБУЕТСЯ ^СОВПАДЕНИЯ ДЛИН (Строки можно сравнивать, для этого надо, чтобы число литер в них совпадало. Можно использовать любой компаратор (-, о, >- и т.д.): WRITELN(shiver - ’Ooh!!!!!’); WRlTELN(shiver > ’Ooh!’); Error TRUE Сравнение выполняется на основе порядковых значений. Литеры двух строк сопоставляются начиная с левого края, пока не встретится отличие. Строка, в которой первая несовпадающая литера имеет большее порядковое значение, считается большей. Отсутствие неравных литер означает равенство строк: ’a* < *b* true^> ORD(!a*)<ORD(>tf) *abcz* < ’abda* <ORD(’c*)<ORDCd*) ’abcdef’-’abcdef’ zVlrieX,—х ( нет отличий) [Дорядок литер в интервалах от ’0’ до *9’, от определяется Паскалем; в остальном порядковые значения литер зависят от символьного кода конкретной системы, это, как правило, - код ASCII. ’А’ до ’Z’ и от ’а’ до ’z’ [возможна обработка отдельных литер строки: /г-FOR i-1 ТО 5 DO shiver[i+5];-shiver[i]; WRITELN(shiver) Однако не все версии Паскаля допускают использование компонент упакованного пассива в качестве параметров процедур (см. напротив). Например, WRlTE(shiver[i]), возможно, придется заменить на ch:-shiver[i]; WRlTE(ch). ^)тих довольно ограниченных средств все же достаточно для создания набора мощных процедур строковой обработки, что демонстрируется в гл. 13. 99 4*
<SX§XX’2Z<g ИЛЛЮСТРАЦИЯ ОБРАБОТКИ СТРОК КАК МАССИВОВ 4/дивите своих друзей. Запишите пример на умножение длинных чисел, вроде этого, а затем начинайте записывать ответ, цифру за цифрой справа налево выполняя все действия в уме. Gjj)oKyc состоит в том, чтобы мысленно пере- вернуть нижнее число и постепенно сдвигать его влево под верхним числом. На каждом шаге перемножьте цифры, лежащие друг под другом и сложите результаты.Запишите последнюю цифру, а остальное запомните для последующего шага. Справа изображен весь процесс целиком. ^Отобы понять, как это все работает, рассмотрим 7x8 = 40 число как полином по степеням 10. На шаге результаты перемножения лежащих каждое каждом одна над другой цифр относятся к одной степени 10. Более того, эти числа - все с данной степенью 10 (и еще, конечно, предыдущего шага). 6 X 102 перенос от 4 х 10 7 х 101 5 х 10° 7x9 = 63 107' 10 5x3 = 15 7x8 = 56 6x9 _ пишем^ХЁ, запоминаем - 91813 8 х 9 х 10° 15 х 102 3 х 102 54 х 102 101 + 56 х 102 это, например, - все коэффициенты, при [Программа, изображенная напротив, автомати- зирует описанный выше метод умножения. Эта программа может справится с любой разумной длиной сомножителей. Надо только подобрать константы termlimit и prodlimit. В приведенном 7x3 = 21 6x8 = 48 100
PROGRAM parlour(INPUT, OUTPUT); CONST termlimi1=20; procUimit=40; TYPE termspan = 0..termlimit; prodspan = 0.. prodl i mi t; termtype = PACKED ARRAY [termspan] OF CHAR; prodtype = PACKED ARRAY [prodspan] OF CHAR; VAR a,b: termtype; c: prodtype; sum,offset: INTEGER; na,nb: termspan; i,k: prodspan; PROCEDURE backhand(VAR x: termtype; VAR count: termspan); VAR i: INTEGER; buffer: termtype; BEGIN Процедура backhand делает три действия: i:=0; REPEAT / READ(buffer[i]); ( i:=SUCC(i) 'UNTIL (buffer[i-l]=’*’) OR (buffer[i-l]=’=’); count=i-2; FOR i:=0 TO count DO x[i]:=buffer[count-i]; END; { backhand } BEGIN { parlour } (i) считывает число в буфер: (и) считает цифры начиная с 0 (Hi) переворачивает цифры в х[] backhand(a,na); backhand(b,nb); sum:=0; offset;=ORD(’0’); сдвиги, как описано рядом FOR k:=0 TO na+nb DO / BEGIN Г #FOR i =0 TO к DO ( IF (i<=na) AND ((k-i)<=nb) I THEN L --------sum:=sum+(ORD(a[i])-offset)*(ORD(b[k-i])-offset); \ c[k]:=CHR(sum MOD 10 + offset); \ sum:=sum DIV 10 4 END; отбросим последний перенос c[fia+nb+l]:=CHR(sum + offset); IF sum=0 THEN i:=na+nb ELSE i:=na+nb+l; FOR k:=l DOWNTO 0 DO WRITE(c[k]); WRITELN нули спереди END. { parlour } 101
(Ж'йТШЭД Й'ЗЖЛЗЯЖ] EaEK^°MBApcASI лит°ЕкР С$ак было отмечено на предыдущей странице, десятичное число (основание 10) - полином по степеням десятки. Аналогично, шестнадцатеричное число (основание 16) - полином по степеням шестнадцати, восьмеричное число (основание 8) - полином по степеням восьми и т.д. Вообще, число в системе счисления с основанием b - это полином по степеням b и для представления таких чисел необходимо b цифр. Для цифр больше 9 используются прописные буквы; букв от А до V хватит для работы с основаниями вплоть до 32. 0 1 2 |3 4 5 6 7 двоичное ' восьмерично# десятичное 8 9 А В С D Е F 10 GH1JKLMNOPQRSTUV 16 31 шестнадцатиричное ' основание 32 3 следующей программе литеры от ’0’ до ’V’ хранятся в строковой константе refconst, которая присваивается упакованному массиву литер с именем refstring. Этот массив используется двояко. По заданной литере, представ- ляющей цифру (скажем, шестнадцатеричную), методом последовательного поиска может быть найдено соответствующее ей числовое значение - при совпадении индекс массива указывает на порядковое значение. Наоборот, используя порядковое значение как индекс массива, можно извлечь соответствующую литеру без всякого поиска. IjlJa этих идеях основаны процедуры find и outdigit соответственно. К сожалению, некоторые версии Паскаля запрещают присваивать строковые константы массиву с типом '□иНГ* PACKED ARRAY [0. 31] OF CHAR требуя, чтобы нижняя граница была всегда единицей, например [1..32]. Сам индекс массива, таким образом, не может считаться порядковым значением и должен быть уменьшен на 1. Не слишком изящно. IjOporpaMMa предназначена для того, чтобы считывать число, записанное в одной системе счисления, и печатать это число в записи по другому основанию. Например, если программе задать то она преобразует 112D из шестнадцатиричной формы в восьмеричную и выдаст число 10455. ^начала программа отыскивает порядковые значения цифр D,2,1,1 и вычисляет полином по степеням 16: 13x16° + 2x16* + 1х162 + 1х163 - 4397 Поиск осуществляется процедурой find, а полином вычисляется в процедуре decimal. Заметьте, что если в диапазоне данного основания найти соответствие не удается, то find возвращает -1. Если же процедура decimal получает -1 от find, то в основную программу процедура decimal возвращает нуль. 102
PROGRAM bases(INPUT, OUTPUT); в основной программе refconst CONST присваивается массиву ref string stri nglength-32; x—-------------------- refconst-’0123456789ABCDEFGHIJKLMNOPQRSTUV’; TYPE stringrange - 1..stringlength; stringtype - PACKED ARRAY [stringrange] OF CHAR; basetype - 2..32; number - 0..MAXINT; VAR instring,outstring,refstring: stringtype; inlength,outlength: stringrange; ch: CHAR; dec,i: number; basenow,baserequired: basetype; продолжение на обороте ength: stringrange; base: basetype: INTEGER; основания поиск не ведется FUNCTION find(ch: CHAR; base: basetype): INTEGER; VAR found: BOOLEAN; i: number; BEGIN h-1; REPEAT THEN find:—i—l ELSE find:—1 END; VAR BEGIN BEGIN digit:-find(stnng[i],base); IF digitcO THEN silly:-TRUE ELSE BEGIN END END; ELSE decimah-n; END; found.-- (ch - refstring[i]); IF NOT found THEN i SUCQi) UNTIL found OR (bbase); IF found заграницей данного FUNCTION dec i ma 1 (st n ng:st ri ngt уpe; digit,power: INTEGER; n: number; i: stringrange; silly: BOOLEAN; i:-0; silly:- FALSE; power: 1; FOR i:-length DOWNTO 1 DO например, если основание - 16, то «степень» (power) принимает n:-n+digit*power, power- power*base KF silly THEN decimab-O; значения 1, 16, 162 ,163 103
бшязта еткшжи 1^\ля преобразования промежуточного десятичного результата к числу, выраженному в системе с новым основа- нием, программа использует деление на новое основание и запоминает остатки. Остатки - это порядковые значения цифр результата, записанные в обратной последовательности. ( ПРОДОЛЖЕНИЕ ) 4397 : 8 OCT. 5 “ST?) : 8 OCT. 5 “55 : 8 OCT. 4 “S : 8 OCT. 0 T : 8 OCT. 1 TF [У1ифры результата извлекаются из мас- сива литер по порядковым значениям. Результат 10455, очевидно, совсем не требует обращения к массиву. Но при переводе к основанию, скажем, 32, порядковые значения были бы 4, 9, 13. Соответствующий поиск в массиве даст число 49D. 4397 : 32 Т37 : 32 ост. 13 а ост. 9 ост. 4 □преобразование к требуемому основанию выполняется процедурой out digit. Чтобы справиться с обратным порядком вычисления цифр, используется рекурсия. 10455 выво 10455 8 16 112D 13 дает D VAR m: number; c: CHAR; BEGIN m:= n DIV base; refstring[l+(n MOD base)]; IF m<>0 THEN outdigit(m,base); WRITE(c) END; PROCEDURE outdigit(n: number; base: basetype); «поиск» цифры, например, 8 дает 8; рекурсия для вывода цифр в обратном порядке BEGIN { программа } refstring:= refconst; i:=l; REPEAT f READ(ch); I instring[i]:=ch; \ i:=SUCC(i) UNTIL ch=* inlength:=i-2; READLN(basenow, baserequired); dec:=decimal(instring, inlength, basenow); outdigit(dec, baserequired); WRITELN : 32 А END. 104
ЯГйи ИЛЛЮСТРИРУЕТ ДВУМЕРНЫЕ МАССИВЫ В КАЧЕСТВЕ МАТРИЦ ^jjpn продавца продают четыре вида товаров. Количество продаваемого сведено в таблицу / ей [1, [2, [3, товар 1] 2] 5 2 0 10 3 5 2 5 20 0 0 0 каждого товара и 1] 2] [1. [2. [3. И. 1.20 0.50 2.80 0.40 5.00 1.00 _2Ж_ таблице В представлены цена коммиссионные, получаемые от продажи. (Заученные от продажи деньги подсчитываются так: цена комис- сионные полученные комиссионные так: ей со [1 5*1.50 + [2 3*1.50 + [3 20*1.50 + 2*2.80 + 5*2.80 + 0*2.80 + 0*5.00 +10*2.00 = 2*5.00 + 5*2.00 = 0*5.00 + 0*2.00 = 33.10 38.50 30.00 [1 [2 [3 5*0.20 + 2*0.40 + 3*0.20 + 5*0.40 + 20*0.20 + 0*0.40 + 0*1.00 +10*0.50 = 6.80 2*1.00 + 5*0.50 0*1.00 7.10 + 0*0.50 =. 4.00 С/ ти вычисления называются умножением смотрятся записи: матриц и лучше такой ВОТ А[1, А[2, А[3, 1] ' 5 3 20 2] 2 5 0 3] о 2 0 4] 10' 5 0 столбцов число быть таким же должны как 2] 1] 2] 1] B[l, В[2, В[3, В[4, 1.50 0.20' C[l, 33.10 6.80 2.80 0.40 = С[2, 38.50 7.10 5.00 1.00 с[3, 30.00 4.00 2.00 0.50 а результат имеет V. столько строк, сколько} у А, и столько столбцов^} Следующая программа вводит матрицы А и В, перемножает эти матрицы и затем печатает их произведение - матрицу С: ° о число строк В A В о Е О Е * в А PROGRAM sales(INPUT,OUTPUT); TYPE atype = ARRAY[1..3,1..4] OF INTEGER; btype = ARRAY[1..4,1..2] OF REAL; ctype = ARRAY[1..3,1..2] OF REAL; VAR a: atype; b: btype; c: ctype; n,i,j,k: INTEGER; BEGIN FOR n:=l TO 3 DO READLN(a[n, 1], a[n, 2], a[n, 3], a[n, 4]); FOR n:=l TO 4 DO READLN(b[n,l],b[n,2]); OR i:=l TO 2 DO FOR j =l TO 3 DO BEGIN /•TOR k:=l TO 4 DO k—c[j» i]:“c[j> i] + a[j, k]*b[k, i]; END; FOR n:=l TO 3 DO WRITELN(c[n,l]:8:2,c[n,2]:8:2) END. попробуйте эту программу со значениями А и В, что —А даны сверху 105
@ивъ1 ГЛУБОКИЙ ВДОХ ... Зсли умножение матриц выделить в процедуру, то программа с предыдущей страницы могла бы быть записана несколько иначе: PROCEDURE matmul(VAR p: atype; VAR q: btype; VAR r: ctype); VAR i,j,k: INTEGER; BEGIN FOR i:-l TO 2 DO 1 FOR j-1 TO 3 DO f BEGIN \I z^FOR k:-l TO 4 + p[j,k]*q[k,i]; END END; DO V«r массивы р и q в этой процедуре не изменяются, тем не менее параметры, являющиеся массивами, всегда лучше делать, параметрами-переменными. В противном г' случае при каждом обращении к подпро-/ грамме массивы будут копироваться!/-^ лавная программа тогда упростится: BEGIN { программа } FOR п:-1 ТО 3 DO RE ADLN(a[n, 1], a[n, 2], a[n, 3], a[n, 4]); FOR n:-l TO 4 DO READLN(b[n,l],b[n,2]); matmul(a, b, с); вызов процедуры* для массивов а,Ь,с. FOR п:-1 ТО 3 DO WRITELN(c[n,l]:8:2,c[n,2]:8:2) END. 3 такой программе необходимо, чтобы пределы изменения i, j и k в циклах FOR процедуры matmul соответствовали размерам массивов типов atype, btype и ctype, которые объявлены в разделе TYPE основной программы: TYPE atype - ARRAY[1..3,1..4] OF INTEGER; btype - ARRAY[1..4,1..2] OF REAL; ctype - ARRAY[1..3,1..2] OF REAL Ио что делать, если обстоятельства вынуждают программиста изменить размер- ность этих массивов? Придется циклах FOR процедуры matmul, размерностям, а это - источник изменить и диапазон изменения i, j и k в чтобы установить соответствие новым возможных неприятностей. Частично эта проблема решена в Паскале BS6192. Суть идеи: используемые в качестве параметров массивы (такие как р, q и г в процедуре matmul) объявлять как настраиваемые массивы. Настраиваемый массив способен совмещаться по размеру и типу компонент с массивом, объявленном во внешнем блоке ~ обычно в разделе TYPE основной программы. О том, что массив - настраиваемый, программист сообщает компилятору с Паскаля, специфицируя настраиваемые параметры-массивы. В начале следующей страницы процедура matmul переписана с использованием настраиваемых параметров-массивов. 106
запятдя.а точка с запятой. ' PROCEDURE matmul(VAR р: ARRAY[1.. гр: INTEGER;!., ср: INTEGER]OF INTEGER; VAR q: ARRAY[l,.cp:INTEGER;l..cq:INTEGER]OF REAL; yAR r: ARRAY[l..rp:INTEGER;!..cq:INTEGER]OF REAL,); VAR ------ i,j,k: INTEGER; BEGIN FOR i:-l TO cq DO FOR j;-l TO rp DO BEGIN r[j,i]:-O; Л’FOR k:-l TO cp ^rtjJWju] + p[j»k]*q[k,i]; END END; ^^тонастраиваемый параметр-массив^— массив г должен совмещаться по размерности^ типу компонент и упаковке с любым массивом, Ч передаваемым в качестве фактического/^ параметра DO Зызов процедуры matmul остается неизменным: matmul( a, b, c); цак как же matmul узнает значения cq, гр и ср? В этом и есть изюминка. Настраиваемые параметры-массивы передают в процедуру matmul достаточно информации, чтобы matmul могла подглядеть в вызывающей программе объявления этих массивов. Вот картинка для массива р, когда программа вызывает matmul с liJ случае совместимости массивов р и а (оба - двумерные, у обоих компоненты типа INTEGER, оба - неупакованные) каждое имя, такое как гр, сопоставляется со значением размерности, например 3. [д]астраиваемее параметры-массивы не обеспечивают динамических границ массивов, они позволяют только автоматически устанавливать соответсвие с фиксированной размерностью из исходного объявления типа. Сложная возможность для достижения малого4-. Лишь немногие версии Паскаля допускают настраивае- мые параметры-массивы. (Динамические границы массива в ограниченных пределах можно промоделировать объявлением массивов большего размера и заданием параметров текущей размерности. Идея поясняется следующим фрагментом программы: TYPE atype - ARRAY[1..2O,1..2O]: увеличенная размерность PTOC^URE^jnatmul(p^ atype£ q: Ь^УР6»^ ^type; |J{lj^NTEGJ:R); matmul( a, b, c, 2, 3, размеры как параметры [растраиваемые параметры-массивы позволили бы сообщить процедуре matmul лишь то, что максимально возможные размеры равны 20. ВЫДОХ..,! 107
hmi Iq [доведите программу bubbles с более существенным, нежели 30, размером константы sizetype ~ скажем 100 или 150. Затем измерьте время сортировки: • когда входная последовательность задана случайными нажатиями клавиш без какой-либо системы • когда входная последовательность в основном отсортирована: AAAABBCCCCCCDDE... но какая-либо буква, выпадает из последовательности: ...EZFFFGGGG... Повторите предыдущее упражнение, используя вместо сортировки методом пузырька быструю сортировку (с. 97). Какие выводы можно сделать из результатов? (q 3зяв за основу программу bases, разработайте специальные процедуры для: • преобразования чисел из шестнадцатеричной формы записи в десятичную • преобразования чисел из десятичной формы записи в шестнадцатеричную Избавившись от чрезмерной общности в программе bases, вы в конечном итоге получите две коротких, элегантных и полезных процедуры. Ъ Осли вы знакомы с матричной алгеброй, разработайте набор процедур, подобных процедуре matmul, для сложения, транспонирования и (крепкий орешек) обращения матриц. Используйте параметры для передачи текущей размерности как предлагается в конце предыдущей страницы. (с. 98) Для хранения литеры в подавляющем большинстве компьютеров отводится 8 бит; изредка используется 6-битовый код (БЭСМ-6, код TEXT); но никогда - 4 бит. 2) (с. 98) В версии Турбо Паскаль нет никаких ограничений на использование компонент упакованных структур в качестве параметров процедур и функций ~ по очень простой причине: этот компилятор игнорирует слово PACKED. 3) (с. 99) В версии Турбо Паскаль допускается сравнение упакованных массивов литер разной длины и, кроме того., имеется специальный стро- ковый тип, позволяющий присваивать одной переменной последовательности литер разной длины. 4) (с. 107) То принципиально новое, что обеспечивают настраиваемые массивы-параметры - это возможность с помощью одной подпрограммы обрабатывать массивы разных размеров. 108
ф з&шсм ЗНАКОМСТВО С ЗАПИСЯМИ СИНТАКСИС ЗАПИСЕЙ ПЕРСОНАЛЬНЫЕ ЗАПИСИ (ПРИМЕР) ОПЕРАТОР WITH ЧТО ТАКОЕ ВАРИАНТЫ
3G»©m,iJ8© (S 34W5SW1 ЛИШЬ РАЗ УВИДЕВ ПРЕЛЕСТЬ ЗАПИСИ ... то время как массив - объединение компонент объединение компонент, вообще говоря, различного типа. Сравните следующий тип массива: одинакового типа, запись TYPE nametype = PACKED ARRAY[1..10] OF CHAR; infotype = ARRAY[1..3] OF nametype; с этим вот типом записи: typ£ nametype = PACKED ARRAY[1..1O] OF CHAR; detailtype = RECORD surname, forename: nametype; age: 18..65; grade: (jr,sr,exec) разного типа |Деременная может содержать массив целиком; VAR a,b: infotype; точно так же она может содержать целиком а[Г а[2: а[3‘ r. surname переменная VAR . q,r: detailtype; запись'. еменная q в массивах г. forename ne □.surname □.forename □ •age □.grade □Похожим образом адресуются компоненты - квадратных скобках): г. grade переменная г посредством индексов (в a[i]:= ’Wilberforc*; b[2]*.= a[l]; a[i] I WU.be.r.f.Qrc' bf2] IWi.l.he.rfДгд а в записях - посредством имени поля (после точки): q.surname:= ’Wilberforc’; q.age— 22; q. grade— jr; r. forename: = q. surname; q.surname r. forename s~^"T^7grade | \Vi.l.b.e.r.f,o.r,c.l [j{]a рисунках показаны записи, в состав которых входят компоненты различных типов, в том числе упакованные массивы. С другой стороны, записи сами могут являться компонентами массива. Единственное ограничение на смешение типов в массивах и записях касается массивов: в каждом конкретном массиве все компоненты должны быть одного типа. Пример массива записей^^-^^-— ---- -------------- —...................-—^C^people теперь^ __..............................массив из 10Ш записей VAR people: ARRAY[1..100] OF detailtype 110
г]ДУля работы с массивами используются индексы', аналогом индексов в случае записей являются имена полей. Подобно массивам, правило покомпонентной обработки имеет важное исключение: для копирования всего содержимого одной записи в другую запись одинакового типа достаточно одной операции: /7/То имя имя'^Зимязшгис^ ПРИСВАИВАНИЕ ("того же типа] ЦЕЛИКОМ «Тот же» тип здесь означает тип с совпадающим именем', только совпадения спецификации недостаточно. Похожее требование относительно присваивания массивов целиком встречалось на с. 91. (^лово PACKED перед RECORD имеет тот же смысл, что и перед ARRAY. Упако- ванная запись занимает меньше места, нежели соответствующая неупакованная. Расплатой за это служит замедление обращения к компонентам во время выполнения программы. Процедуры РАСК и UNPACK (с. 98) применимы только к массивам, но не к записям. [3 каждом конкретном типе записи ~ включая все вложенные записи ~ имена полей должны различаться . Однако в различных типах записей допускаются совпадающие имена полей: TYPE TYPE rectypel - RECORD z,y,XQviREAL; a: 1. END; rectype2 ~ RECORD'S^* u,v,w7xTREAL; b: (eeny,meany) END; s' нет nepe-\ сечения имен} олейу^^^ rectype3 - RECORD n: CHAR; v,w,x: END; rectype4 RECOR ^“x: CH END; e поле c именем x в типе rectype4 вступило в про- тиворечи 3; 111
ПЕЙ ШИ ПРИМЕР ИЛЛЮСТРИ- РУЕТ ИСПОЛЬЗОВАНИЕ ЗАПИСЕЙ {^)та программа запрашивает фамилию, имя, возраст и звание сотрудника. Каждый ответ заканчивайте нажатием на клавишу RETURN. Когда записей больше не останется, Лбце?(Д/Н): Д Фамилия? (<= 10 букв): HAIG Имя? (<= 10 букв): JOHN Возраст? (от 18 до 65): 40 программа отсортирует все имеющиеся записи по каждому из четырех признаков: • фамилия (алфавитный порядок) • имя (алфавитный порядок) • возраст (в порядке возрастания ) Звание? (JR,SR,EXEC): EXEC Еще? (Д/Н): Д Фамилия? (<= 10 букв): DAVIS Имя? (<= 10 букв): SAMUEL Возраст? (от 18 до 65): 64 Звание? (JR,SR,EXEC): JR Еще? (Д/Н): Н ВВОД • звание (в порядке возрастания порядкового значения: JR,SR,EXEC) РЕЗУЛЬТАТ rDAVIS SAMUEL 64 Junior \ HAIG JOHN 40 Executive HAIG JOHN 40 Executive DAVIS SAMUEL 64 Junior ♦♦♦♦ HAIG JOHN 40 Executive DAVIS SAMUEL 64 Junior DAVIS SAMUEL 64 Junior HAIG JOHN 40 Executive Jho'-* фами* Члии ) [Проверки данных сведены к мини- муму. Звание, отличное от JR, SR или ЕХЕС, воспринимается как JR; УпоХ-^ ответ на «Еще?», отличный от Д, имени} воспринимается как Н; прочие ошибки (например, имя длиннее 10 букв) обнаруживаются Паскаль-процессором. возрасЛ \ryJ'S ЦЦример, который здесь приведен, подразумевает использование интерактивного Паскаль-процессора. Ряд возможных затруднений, обус- ловленных интерактивным вводом, обсуждается в гл. И . Г по] зва- нию Ь1}опустимая длина имени и допустимое число записей, из соображений удобства модификации, задаются константами. Запись о сотруднике (персональная запись) уже обсуждалась; ее тип воспроизведен в программе.С полыми этой записи (surname, forename, age и grade) ассоциированы элементы перечисляемого ключевого типа (lastname, firstname, decrepitude и clout), который используется в процедуре сортировки для выделения соответствующего признака сортировки. Персональные записи хранятся в массиве а, соответствующие указатели - в массиве р. Указатели используются для сортировки, принцип которой описан на с. 94. Зот объявления: PROGRAM personel(INPUT,OUTPUT); CONST namelength = 10; listlength = 30; space = ’ ’; TYPE nametype = PACKED ARRAY[1.. namelength] OF CHAR; detaitype = RECORD surname, forename: nametype; age: 18..65; grade: (jr,sr,exec) END indextype = 0.. listlength; keytype = (lastname, firstname,decrepitude,clout); ordertype = (gt,eq); detailtype .surname .forename .age . grade 112
VAR a: ARRAYfindextype] OF detail type; p: ARRAYfindextype] OF indextype; key: keytype; count: indextype; массив ерсональных казате пи (А) главная программ; призна счетчик IJ лавная программа приведена на следующем развороте. Главная программа (А) вызывает процедуру ввода (В), затем вызывает про- цедуру сортировки (С) и процедуру вывода (D) ~ каждую четыре раза. Для . ввода строки литер процедура ввода (В) вызывает специальную процедуру (Е); она также вызывает функцию (F) ИЕРАРХИЯ (В) ввод ' (С) сортировка (Е) прием Z1___ (F) порядок (D) печать у так V— обозначены* ^{.вызовы ) для проверки двух строк на равенство. Процедура сортировки (С) также использует функцию (F), чтобы выяснить, какая из двух строк <больше>. Во избежание использования директивы FORWARD эти подпрограммы должны располагаться так, чтобы (Е) и (F) предшествовали (В), а (F) предшествовала (С). Основная программа (А) должна располагаться последней. г^алее приведены все эти подпрограммы. Процедура (Е) принимает данные с клавиатуры: PROCEDURE accept(VAR linebuf: nametype); { Прием } VAR i: 0..namelength; ch: CHAR; .BEGIN C^—FOR i;»l TO namelength (^-REPEAT READ(linebuf[ll) i;=l; Vc WHILE NOT EOLN DO ( /BEGIN _ v DO linebuffi]— space; UNTIL linebuf[l]ospace; терзая литера) Vy. ^linebuf [ 1 заполнение буфера i ^-^CTpoKu пробелами, пробелы в начале" ^^игнорируются/ \ READHinebuffi]); 'END; READLN END; I продолжать c linebuf[%2y ункция (F) сравнивает строки на равенство или на больше-меньше: FUNCTION order(c: ordertype; a,b: nametype): BOOLEAN; { Сравнение } VAR i: 0..namelength; cl, c2, nidi: CHAR; BEGIN невидимая литера'с меныиим\ i.=0; null-» CHR(0); \ порядковым значением, чем у J REPEAT i:-i+l; ^fegHCTQHTaj-'S IF a[i]=space THEN cl:- null fcLSE cl-a[i]; IF b[i]-space THEN c2- null ELSE c2-b[i]; UNTIL ((i-namelength) OR (cloc2)) OR f(cl-null) AND (c2-null)); CASE c OF gt: order- (cl>c2); eq: orders (cl-c2) END { CASE } END; _ _ _ 113
( ПРОДОЛЖЕНИЕ ) [Процедура сортировки (С) использует описанный ранее метод пузырька, который теперь приспособлен для работы с различными ключами сортировки. Каждый ключ подразумевает свой критерий сортировки. Разветвление по ключу осуществляется оператором CASE, структура которого напоминает структуру персональной записи. числовой ключ алфавитные ключи орядковый ключ PROCEDURE sort(n: indextype; k: keytype); { Сортировка } VAR s,sorted: BOOLEAN; i,tempry: indextype; BEGIN IF n>l THEN BEGIN sorted: = TRUE; FOR i:=l TO n-1 DO ------------ BEGIN CASE k OF lastname: s:»order(gt,a[p[i]]. surname, a[p[i+l]]. surname); firstname: s:«order(gt,a[p[i]]. forename, a[p[i+l]]. forename); decrepitude: s:=a[p[i]].age>a[p[i+l]].age; clout: s:=ORD(a[p[i]].grade)>ORD(a[p[i+l]]. grade) ND; { CASE } ___ s IF s THEN ^/[spaeHO TRUE или FALSE. BEGIN ^4.- - - sorted: = FALSE; tempry:= p[i]; p[i]-p[i+l]; p[i+ll:= tempry END END; { FOR i } IF NOT sorted THEN sort(n-l.k) END { IF n>l } END; _________ выбирает, по какому из\ четырех ключей сортировать если TRUE, то меняем ^указатели рекурсивное обращение [^^альше следует бесхитростный текст процедуры (D): PROCEDURE list(n: indextype); { Печать } VAR i: indextype; BEGIN FOR i =1 TO n DO BEGIN { FOR i } WRITE(a[p[i WRITE(a[p[i WRITE(a[p[i CASE a[p[iJ \jr: WRlTELN(’Junior’); S sr: WRITELN(’Senior’); exec: WRITELNfExecutive’); END { CASE } END { FOR i} END; ]. surname, space); ]. forename, space); ]. age: 3, space); .grade OF ]. surname ]. forename 'V ]age ч. ]. grade ПРИМЕР] не забывайте, что значения перечисляемого типа нельзя выводить на печать поэтому здесь используется оператор CASE/ ЕЖЕЕЕЕк HQs ГАН... ~ г 114
1^1даже несмотря на отсутствие проверок, написание процедуры (В) является наиболее утомительным. Таковы процедуры ввода в любом языке. Зсли ваша программа икает, запрашивая данные, которые уже были введены (см. гл. И), то можно убрать все запросы и создать файл с исходными данными. Посмотрите в вашем руководстве, как следует создавать, редактировать и сохранять файл данных для последующего его чтения в программе на Паскале. PROCEDURE inputter(VAR n: indextype); { Ввод } VAR indicator: CHAR; string: nametype; buffer: detailtype; BEGIN ns-O; REPEAT WRITEfEuie? (Д/Н): READLN(indicator); IF indicator - ’Д’ THEN BEGIN n:-n+l; p[n]s-n; L:-* 12 * P P P P 1 2 3 4 indicator string | 18..65 buffer, surname I buffer, forename ГГ77 buffer.agel I buffer, grade | $Р1ТЕ(’Фамилия? (<-10 букв): ’); accept(string); buffer, surnames-st ring; WRITE(’Hmh? (<-10 букв): ’); accept(string); buffer.forename:-string; WRITEf Возраст? (от 18 до 65): ’); READLN(buffer.age); WRITEfЗвание? (JR,SR,EXEC): f); buffer, grades-jr; accept(string); IF order IF ordei a[n]s-buffer END ELSE ;r(eq,string,’EXEC ir(eq,string,*SR IF indicator-’H’ UNTIL indicatoro’fl’; END; { inputter } ’)THEN buffer.grade:-exec; )THEN buffer, grades -sr; присваивание компоненте a[n] за cu «buffer THEN WRITELNf Нормальное завершение’) ELSE WRITELNf Ненормальное завершение’); Главная программа (А) - простая: BEGIN inputter(count); FOR key:— lastname TO clout DO /ГЛАВНАЯ I ПРОГРАММА j BEGIN f sort(count,key); list(count); \ WRITELNf****’); x END END. { PROGRAM } ключ сортировки пробегает в цикле все четыреполя записи 115
©ИЗР^‘й'©(? «50783 ЭКОНОМИТ ВРЕМЯ и ЧЕРНИЛА \$/братите внимание на повторяющийся элемент a[p[i]]r в тексте процедуры на с. 114. Строчки отличаются, главным образом, именем поля, которое следует за точкой. имена полей ) WRITE(a[p[i]].surname; ) WRITE(a[p[i]]. forename J WRITE(a[p[i]].age j^CASE a[p[i]].grade (®)ператор WITH приписывает единожды указанное имя записи (до точки) к последующим именам полей с тем, чтобы операторы, как, например изображенные сверху, можно было сократить до возможно меньших размеров. Вот снова эти же операторы, но уже в законченном виде и с использованием WITH. \ WITH a[p[i]J I BEGIN слово WITH относится ко всем именам DO WRITE(surname, space); ) WRITE(f orename, space )\ WRITE(age, space); CASE (grade) OF полей внутри составного оператора, следующего за WITH... DO END { WITH } (Синтаксис оператора WITH: WITH переменная^ DO оператор где Г...................—ZZZZZZZ— 1 Х'' I переменная := имя [^выражение ] заметьте, что — здесь не означает X повторения, как в Ч 7 циклах FOR и WHILEj .имя прим ер: а[рПШ-^-—v— . fieldj см., гл.-12 / ' СДиже приведен раздел объявлений, который потребуется в программе напротив. Эта программа демонстрирует использование оператора WITH применительно к 116
[Дервый из приведенных ниже примеров показывает, что оператор WITH может достигать любого уровня вложения. (Имеет ли вложение «уровни»? Быть может, лучше было бы говорить о «слоях», но «уровни» - общепринятый термин.) PROGRAM nestingtf^^ (зд^сь поместите о^ъявления^ТУРЕ BEGIN nest, fieldl. field2.field3:= TRUE; WITH nest, fieldl. field2 DO WRITELN(field3); WITH nest.fieldl DO WRITELN(field2.field3); WITH nest DO WRITELN(fieldl.field2.field3) END. пример иллюстрирует вложенные операторы WITH, отражающие структуру вложенных записей: PROGRAM nes^r^(OUTPUT)^___^^_-_^^^ Сзд^ръпоместитеобъявления TYP^u VAI BEGIN nest, fieldl. field2. field3;= TRUE; WITH nest DO WITH fieldl DO WITH field2 DO WRITELN(field3) END. Третий пример призван проиллюстрировать использование запятых вместо точек. Такие обозначения позволяют указывать более одного типа записи. Этому, однако, может помешать то, что компилятор не всегда будет знать, к какой записи относится каждое поле (вспомните, что в разных записях возможны одноименные поля). Запятые - не более чем альтернатива точкам. Сравните следующую программу с программой вверху страницы. PROGRAM nestjnj^ (здесь поМестит^объявлыия ТУР^ц УА^ BEGIN^ nest.fieldl.field2.field3: = TRUE; WITH nest,fieldl,field2 DO WRITELN(field3); WITH nest, fieldl DO WRITELN(field2.field3); WITH nest DO WRITELN(f ieldl. field2.field3) END. Запятая имеет смысл только после WITH; не пытайтесь писать: WITH nest DO WRITELN(fieldlJield2,field3); и не переставляйте поля после WITH: WITH field2,nest,field^^ WRITELN(field3); 117
w© иш©а wiiwa экономия ПРОСТРАНСТВА Рассмотрим программу для управления транспортными ресурсами, рассчитанную на случай забастовки железнодорожников и водителей автобусов. Наличие транспортных средств можно описать такой записью: PROGRAM carshareflNPULOUTBVT); TYPE Ttetuex modetype - (foot, pushbike/ motorbikeT~ca/-); gotype - RECORD surname: PACKED ARRAY[1..1O] OF CHAR; initial: CHAR; mode: modetype; year: 1900.. 1990; sidecar: BOOLEAN; mpg: REAL; seats: 1..6; END; фамилия инициал вид год коляска расх. гор. \места^. VAR person: gotype; people ARRAY[1..100] OF gotype; i: 1..100; ... _ .surname I ....... .initial □_____ . mode I I .уеагП .sidecar □_ .mpgCZJ f ж. seats □ / запись надо заполнять внимательно, потому что некоторые поля не всегда имеют смысл; например, пешеход не расходует горючее и у него нет мест для ' пассажиров. Значение поля mode в каждом случае определяет набор существенных полей. Таким образом, для заполнения или печати записей естественно использовать оператор CASE. Например: _____ ___ _____________ этот для оператор WRITE - общий всех видов транспорта в’, year); WITH peoplefi] DO BEGIN WRITELN(initial,surname:ll); CASE mode OF /^^^пустой^ foot, pushbike: ; z/JL. motorbi ke: BEGIN WRITEf Мотоцикл сделан обычный) IF sidecar THEN WRITELNfl место в коляске’) оператор) ELSE WRITELNfCHfleHbe сзади’) CASEr^ END; t motorbike } car: WRITELN(year:4,mpg:4:l,’ салон Ha’,seats-1:2) ^END { CASE mode } END; { WITH } велики, так, как Цакое решение, однако, не свободно от проблем: размер каждой записи должен быть достаточным для хранения всех вообще возможных полей. Память расходуется попусту. В реальных программах потери могут быть очень Поэтому Паскаль позволяет варьировать структуры от записи к записи на этом рисунке: данном случае .mode), позволяющее .surname .initial .mode □ Lear.. I □ LlQQlJ surname .initial .mode .year •mpg : surname .initial .mode .sidecar Jrsurname I .initial •mode I pushbike I .bikeyear [motorbike] Изменяющаяся часть записи называется вариантом. Вариант всегда располагается в конце. Поле (в различать варианты, называется полем признака. 118
1^^ля описания вариантов применяется специальный оператор. Его имя CASE, хотя он и отличается от управляющего оператора с тем же именем. Однако вполне очевидно и сходство между двумя операторами. Приведем новое определение типа gotype: TYPE modetype - gotype - RECORD surname: PACKED ARRAY[1..1O] initial: CHAR; —x^r^CASE mode: modetype OF OF CHAR; определена sidecar: BOOLEAN); у CASE нет закрывающего \END j foot, pushbike: (); К motorbike: (bikeyear: 1900.. 1990; car: (year: 1900..1990;mpgtREAL;seats: 1..6 END { RECORD Г ------------------- ------ r заметьте, что вместо «year* использовано имя «bikeyear*, < чтобы сделать все имена полей. Л в записи различными Зыше» определен тип записи, который нарисован во всех возможных видах в конце страницы напротив. (®)бращение к новой записи не упростилось; оно даже стало сложнее, теперь в ней используются различные компоненты для хранения года ния (при модификации программы напротив измените WRITEf Мотоцикл year) на WRITEfМотоцикл сделан в’,bikeyear)). Оператор CASE по-прежнему необходим, чтобы пешеходам и велосипедистам не пришлось перевозить пассажиров. (Синтаксис варианта рекурсивно определяется следующим образом: пбтому что изготовле- сделан в’, CASE имя: имя OF константа : ( поля вариант ) типа \ г z где -поля имя : тип внимание, у CASE нет закрывающего END. Так как вариант должен что вариант и вся запись закрываются одним следовать . в конце, считается, общим словом END. Заметьте> чт0 конструкция ( элементов.. Такая пустая пара используется в примере выше), открывает возможность появления еще CASE, позволяя описывать вложенные варианты. А так как поля в любом варианте могут опускаться, то отсюда вытекает, что правило о следовании варианта в конце не накладывает никаких ограничений на сложность. Шропуск 'имени? подразумевает, отсутствие поля признака, служащего для распознавания вариантов. Такая запись называется свободным объединением (в отличие от размеченного объединения при наличии поля признака). Свободное объединение допускает хранение элемента, например, под видом литеры с последующим извлечением его как целого числа ~ аналогично и для других случаев «эквивалентности» типов. Свободное объединение, придуманное для чтения по указателям (смешно) предложено Грогоно одновременно с соответ- ствующими предостережениями против такой практики. Смотри библиографию. поля вариант ) допускает отсутствие обоих скобок означает пустое определение полей (что и С другой стороны, присутствие варианта 119
жж ЁНИ Выполните программу personel. Модернизируйте программу, определив более реалистичные записи. Напишите процедуру быстрой сортировки, чтобы заменить ею процедуру сортировки методом пузырька на с. 114.Сортирует ли она записи сколько-нибудь быстрее? (Объем данных в этом упражнении так невелик, что ни одна процедура сортировки не имеет преимуществ над другими. Лучшей поэтому будет наипростейшая процедура.) (с. 111) Стандарт Паскаля допускает такое пересечение имен, как в примере справа внизу. Действительно, оно не приводит ни к каким неоднозначностям: если переменная г имеет тип rectype4, то поле х на ее верхнем уровне обозначается как г.х, тогда как поле с тем же именем во вложенной записи будет обозначено г.р.х . 2 ) (с. 117) В перечислении записей между WITH и DO запятая совсем не эквивалентна точке. Запятая служит сокращением для вложенных операторов WITH. Оператор WITH a,b DO... эквивалентен WITH a DO WITH b DO ... , таким образом, во внутреннем операторе можно использовать компоненты обеих записей. При этом запись b вовсе не обязательно должна быть полем в записи а. Оператор WITH a.b DO «присоединяет» к внутреннему оператору только одну запись - а. b . 120
и© OMIOJ ЧТО ТАКОЕ ФАЙЛЫ ОТКРЫТИЕ ФАЙЛОВ ТЕКСТОВЫЕ ФАЙЛЫ ПРОЦЕДУРЫ WRITE И WR1TELN ДЛЯ ТЕКСТОВЫХ ФАЙЛОВ ПРОЦЕДУРА PAGE ДЛЯ ТЕКСТОВЫХ ФАЙЛОВ ПРОЦЕДУРЫ READ И READLN ДЛЯ ТЕКСТОВЫХ ФАЙЛОВ БЕЗОПАСНОЕ ЧТЕНИЕ ПРОЦЕДУРА GRAB ДЛЯ БЕЗОПАСНОГО ЧТЕНИЯ ДВОИЧНЫЕ ФАЙЛЫ, ПРОЦЕДУРЫ PUT И GET СЖАТИЕ (ПРИМЕР) СВОЙСТВА ФАЙЛОВ, СВОДКА
<W© 74W3 ФАЙЛЭД СРЕДСТВО связи МЕЖДУ ПРОГРАММАМИ С^айл с именем OUTPUT уже встречался. Имя OUTPUT, будучи опущено в операторе WRITE (или WRITELN), подразумевается, но при желании его можно явно указать OUTPUT (текстовый файл; это понятие будет определено позже ) PROGRAM squares(OUTPUT); VAR i: INTEGER; BEGIN ^^существенно vFOR i:-l TO 4 DO JL_WRITELN(OUTPUT, i, sQR(i)) END* z» айл с именем INPUT также уже встречался. Аналогично имя INPUT подразуме- вается в процедурах READ, READLN и функциях EOF, EOLN, но может быть и явно указано: /.OUTPUT (текстовый J I g INPUT PROGRAM anysquares(INPUT,OUTPUT);____ VAR i,j,k: INTEGER; BEGIN ^^существенны i WRITELN(OUTPUT, ’диапазон: *)>— READLN(INPUT, i, TOR i -j TO k DO<J=н еобязател ьноJ V^WRITELN(OUTPUT, END. ^результаты работы можно отправить и в другие файлы, не только в файл с именем OUTPUT. Каждый такой файл должен быть указан в операторе PROGRAM, а его тип объявлен в разделе VAR. Однако файл OUTPUT должен быть указан всегда, хотя бы в качестве канала для сообщений ~ в частности, сообщений Паскаль-процессора об ошибках: ^OUTPUT my file 'Ь для ерезаписи PROGRAM filesquares(OUTPUT,myfile); VAR i: INTEGER; myfile: TEXT; BEGIN REWRlTE(myfile); FOR i:-l TO 4 DO тигглфайлау - текстовый, CwRITELN(myf i le ,7, SQRG))? WRITELN(OUTPUT, ’О. END. UM/ файл. т'ЪмяХ f файла1* 'существенно (3 качестве источника данных могут использоваться файлы, отличные от файла с именем INPUT. Каждый такой_файл должен быть указан в операторе PROGRAM, а его тип объявлен в разделе VAR: myfile yourfile OUTPUT ткрыть для чтения PROGRAM filecubes(OUTPUT,myfile,yourfile); VAR i,j: INTEGER; myfile,yourfile: TEXT; BEGIN RESET(myfile); REWRITE(yourfile); Z WHILE NOT EOF(myfile) DO 14 BEGIN I READLN(myfile,i,j); \ WRITELN(yourfile,i,i*j) 'END; WRITELN(OUTPUT,’o.k.) END. 7Tun\ файла Одновременно могут быть открыты несколько файлов; с другой стороны, в ходе выполнения одной программы один и тот же файл может быть открыт для записи и впоследствии установлен на чтение. 122
кх/братите внимание, что файлы myfile и yourfile перед записью должны быть открыты процедурой REWRITE, а перед чтением - процедурой RESET. Однако для открытия специальных файлов с именами OUTPUT и INPUT использовать REWRITE и RESET нельзя (эти файлы открываются автоматически). Ошибкой также будет попытка открыть уже открытый файл. Ly ISO Паскале все файлы - последовательные. Открытый на запись файл изначально является пустым, он содержит лишь маркер конца файла. Каждый оператор WRITE или WRITELN осуществляет добавление новой информации, после чего маркер сдвигается к новому концу файла. Оператор WRITELN (в отличие от WRITE), перед тем как вернуть управление, добавляет в файл еще и литеру конца строки. 7Г~ ~Ъ~ ► eof < REWRITE(f); |>>WRITE(f,’a’,’b’); И go/ f* ' (пусто [JJo смыслу, маркер конца файла - это следующая доступная компо- нента, в которую будет помещен следующий элемент (если он есть). Открытый для чтения файл имеет «окно>, расположенное над первой компонен- той. Выполнение первого оператора READ приводит к считыванию элемента в окне, после чего окно сдвигается к следующей компоненте и так далее. Оператор READLN (в отличие от READ), перед тем как вернуть управление, сдвигает окно за ближайшую литеру конца строки. Если такой литеры нет, то окно устанавливается на маркер конца файла. RESET f); READ(f, i); READLN(f,j); READ(f,k); READ(f,l) а ► а < ► пропускает) вплоть до/ следующего) Оконца y S строки} a ffenepb*^ \EOLN(f)\ юозвратит/ \ true f в a fiOLNCfh возвратит] \falsej (eofo\ \truey^J b ь □Qi b ~T~ с с с |V c c eoln eoln eolnQ eoln eoln ~ёГ d If d S’- eoln eoln eoln] I eoln eoln ► eof eof •eof И DLN(f^\ содержит^ пию *b’ пропущено) но - на '&'{ eof eof $ез£льта& RESET: S • окно -) \ на 'aj /результат^ \>READ(f,i)>\ Ге 1 содержит} у копию ОКНО - p-Z дка 'b'-J ( REA. (• i < V ко Ге ’c’ OK /^е^ульТат'Х \READ(f,k)T\ (9 k содержит) । k копию 1 Ze окно - < . на eoln J /peSy. \REA r9 I i < np (9 OK на ль?ат\ содержит} обел1}У но - J : eof Г (®/ператоры READ(f,i), READLN(f,j), изображенные выше, можно объединить в один оператор - READLN(f,i,j). Вообще: READLN(f, р, q, г,...) = READ(f, р); READ(f, q); READ(f, г);... READLN(f) WRITELN(f, p, q, г,...) = WRITE(f, p); WRITE(f, q); WRITE(f, r);... WRITELN(f) ^\итера конца строки имеет рассмотренный выше специальный смысл только в текстовых файлах наподобие тех, что изображены напротив. Текстовый файл в соответствие с названием состоит из слов и чисел, которые разделены пробе- лами и объединены в строки. Ниже рассматриваются файлы другого вида - двоичные. 3 следующей главе речь, пойдет об изменениях логики работы с файлами в случае интерактивного ввода. 123
ПОДРОБНЕЕ О ПРОЦЕДУРАХ КС W I\1 I С. П KCOD1 аждый файл, используемый в программе на Паскале для чтения или записи, должен быть указан в операторе PROGRAM: PROGRAM имяпрог ( имяф ) ; ► PROGRAM myprog(OUTPUT, mydata, шудищр); [J ип каждого файла должен быть объявлен в разделе VAR главной программы. Синтаксис объявления приведен ниже. Определение FILE OF REAL упреждает события, это - двоичный файл; речь о них - впереди. иУвключ айте^юда^ТайлыУ^Тгме^ \ами INPUT или OUTPUT, которые' по умолчанию определены как файлы типа TEXT./— ► VAR mydata: TEXT; mydump: FILE OF REAL Запись в файл, если это не файл OUTPUT, может Вестись только после того, как этот файл открыт процедурой REWRITE: REWRITE( /гляАддла ^^тема^у Ф f возможны расширения ► REWRITE( mydump ) rHtr исполняйте r REWRITE j с файлом •< ^OUTPUT ЧДтение при помощи операторов READ или READLN из файла, если это не файл INPUT, может производиться только после того, как этот файл открыт процедурой RESET: RESET( и*яфайла ► RESET( jnydata ) в^некдторых системах^ возможны расширения. 'не'исполнЯйте RESETч с файлом jrAsi < INPUT^ роцедуры WRITE, WRITELN, READ и READLN подробно обсуждаются на следующем развороте. [Приведенные выше определения применимы как к текстовым, так и к двоичным файлам. Двоичные файлы будут рассматриваться несколько позже. 124
сжали! ПОСМОТРИТЕ В СВОЕМ РУКОВОДСТВЕ, КАК ВВОДИТЬ, РЕДАКТИРОВАТЬ И СОХРАНЯТЬ ФАЙЛЫ Смайлы с именами INPUT и OUTPUT - текстовые файлы (файлы типа TEXT). Файлы, указаные программистом, также могут быть объявлены как текстовые. PROGRAM(INPUT, OUTPUT, hisf ile, herf ile); VAR hisfile,herfile: TEXT; текстовые файлы, v- — v объявленные программистом Т/екстовый файл состоит из литер в коде ASCII ~ или из литер того кода, который используется на вашем компьютере ~ таким образом, напечатанный текстовый файл можно воспринять зрительно: [/екстовый файл организован в виде строчек элементов, элементы разделены пробелами. Программа на Паскале, предназначенная для чтения такого файла, может считывать файл по одной литере используя только оператор READ(f,ch) (ch - типа CHAR). С другой стороны, в операторе READ можно использовать несколько параметров; каждый параметр того же типа, как предполагаемый очередной элемент файла. Например, оператор READLN(file,i,x,y) правильно прочитает первую строку текстового файла, который изображен выше. Здесь t типа INTEGER, х,у - типа REAL. (Отличительным свойством текстовых файлов является автоматическое преобразование элементов из внутренней формы представления в символьную в процедуре WRITE и из символьной во внутреннюю в процедуре READ. Это преобразование выполняется в соответствии с типом используемых параметров. Если соответствия типов нет, то чтение прерывается, поэтому безопаснее отказаться от автоматического преобразования и считывать данные по одной литере. Соответствующая процедура ввода приведена на с. 128-133. Текстовые файлы могут создаваться процедурой WRITE, это было показано на с. 122. Текстовые файлы можно также вводить с клавиатуры и сохранять на диске. Как это делается, зависит от вашей системы; посмотрите свое руководство. Обычно файл вводится под управлением «строчного редактора» или «экранного редактора». Редактор позволяет вводить, исправлять, вставлять и удалять текст. Когда файл введен и отредактирован, он может быть сохранен на диске для последующего использования в качестве файла INPUT в программе на Паскале. Для этого используется команда наподобие: SAVE ’INPUT’ (возмож^ [^\ля многих компиляторов недостаточно указать файлы в операторе PROGRAM; необходимо еще связать эти имена с именами файлов, которые понимает опера- ционная система. Для этих целей в Pro Паскале и Turbo Паскале используется процедура ASSIGN; в Acornsoft ISO Паскале - расширенные процедуры RESET и REWRITE: ^ucT0a^4od4gXj\SSIGN(mydata2,’MYDAT2.TX’) для\_ ^Ласкаля RESET(mydata2,’MYDAT2.TX’) (операционной системы) 125
ПРОЦЕДУРЫ ж® и для iTiEK Q'Bb’J* СООБЩАЕМ ПОДРОБНОСТИ (^интаксис процедур WRITE и WRITELN: только если выра- жение _типа~ REAL WRITEf имяфайлов выражение : ширина WRITELN ( имяфайла^ выражение : ширина : точность^ ) ^имяфайГа только если выра- жение типа. REAL ► WRITE(a,b,c) ► WRITELN(a,b,c) ► WRITE(myfile,a,b,c) ► WRITELN(myfile) ► WRITE(’*’:36*COS(x)) ► WRITE [Дервый выполняемый оператор WRITE или WRITELN располагает первое поле вывода в начале выходного файла. (Поле - последовательность смежных литерных позиций, в которые записывается выводимый элемент ~ сдвинутый к правому краю). Следующие поля, порождаемые той же или следующими процедурами WRITE и WRITELN, добавляются в выходной файл последовательно и вплотную друг к другу. Дели ширина поля вещественного или целого типов не указана, то используется некоторое стандартное значение, зависящее от системы (обычно это 14 пози- ций). Не указанная точность (число знаков после десятичной точки) для поля типа REAL подразумевает вывод в «научном» формате; например, значение -0.000123456 представляется в виде -1.23456Е-04. Число значащих цифр, которые печатаются перед Е, зависит от системы (обычно 6 или 9). Для строк не заданная ширина подразумевает ширину, равную числу считая открывающего и закрывающего апострофов (3 для ’abc’)*. Для элемента типа PACKED ARRAY[l..n] OF CHAR при отсутствии ширины используется п. Для логического элемента подразумеваемая ширина, зависит от системы (обычно 4 для true и 5 для false). Если заданное значение ширины слишком мало, чтобы вместить соответствующий элемент, то поле растягивается [Процедура WRITELN (в отличие от WRITE) после записи автоматически добавляет литеру конца строки. Процедура параметров также добавляет в файл литеру конца строки. литер в строке, не вправо. последнего параметра WRITELN без Шри вызове этой стандартной процедуры в указанный или подразумеваемый выходной файл посылается признак конца страницы (можно использовать, только если система распознает этот признак). 126
ПРОЦЕДУРЫ и СООБЩАЕМ ПОДРОБНОСТИ (Синтаксис процедур READ и READLN: READf READLN имяфайл^ переменная, ) в файле предполагается строка литер, которую можно прео& разовать к типу переменной: CHAR, INTEGER, REAL < имяфайла переменная < имяфайла > ► READ(a,b,c) ► READ(myfile,a,b,c) ► READLN(a,b,c) ► READLN(myfile,a,b,c) Iдели параметр имеет тип CHAR, то считывается одна литера в окне. Если это - литера конца строки, то она читается так, как если бы это был пробел. Тем не менее ее все же можно отличить от пробела, потому что когда в окне - литера конца строки ~ и ни в каком другом случае ~ функция EOLN для этого файла возвратит значение true. После успешного считывания литеры в окне, окно сдвигается к ближайшей следующей литере. Если эта литера оказалась маркером конца файла, то функция EOF (от End Of File - конец файла), будучи вызвана для этого файла, возвратит значение true. Функция EOF возвращает значение true, если только в окне - маркер конца файла. Попытка прочитать маркер конца файла ведет к ошибке. [дели параметр процедуры READ или READLN имеет тип INTEGER или REAL, то окно сдвигается вперед, пропуская пробелы и литеры конца строки, до тех пор, пока не встретится первый значащий символ новой строки (или же поиск не закончит- ся аварийно на маркере конца файла). Строка (если она правильно записана) преобразуется в элемент стандартного типа, согласующегося с соответствующим параметром. (Инструкция READ(x), например, сработает неверно для строки 1.5, если переменная х - типа INTEGER.) После успешного прочтения строки окно устанавливается над следующей за этой строкой литерой. Эта следующая литера может быть пробелом или литерой конца строки; в последнем случае функция EOLN, будучи вызвана, возвратит значение true (a EOF - false). 00|осле успешного прочтения последнего параметра процедурой READLN (в отличие от READ) окно пропускает все литеры, оставшиеся в данной строке файла, и останавливается, оказавшись над первой литерой следующей строки. Эта первая литера может оказаться маркером конца файла. В этом случае функция EOF возвратит значение true. То же относится к использованию процедуры READLN без параметров. концептуально, окно для текстовых файлов имеет гибкие рамки. Большую часть времени оно просматривает одну литеру. Однако, когда встречается строка литер, определяющая число, окно «растягивав т с я >, с тем, чтобы охватить все литеры в этой строке. В этом - отличие от окон двоичных файлов', окна для лих могут иметь сложную структуру, но не являются гибкими. Двоичные файлы описываются ниже. 127
вэз©иа<ая©а ЭТО - ПРОБЛЕМА В БОЛЬШИНСТВЕ ПОПУЛЯРНЫХ ЯЗЫКОВ ».м .р««я » временя приходя,» _ь .форм.™».. би.якя,- преимущество которых сказывается разве что в некотором облегчении жизни программиста: Вес । । |»| и кг • Размеры X , 11 . . . , , . , Порядковый номер । , , । । , । (£/днако, процедуру ввода сложных наборов данных весьма желательно сделать более гибкой. Можно, например, ______________ придумать некоторый «проблемно-ориентированный язык» , вП<отором"*смысл Y । । i*i । । см следующего числа или группы чисел указывается ключевыми словами: ВЕС 16.75 biz-r—V—I ПОРЯД,54321, ВЕС,1675 РАЗМЕРЫ X 2 Y 3.62 ТЛкодинаковые\3^ РАЗМЕРНОСТЬ ПОРЯДКОВЫЙ 54321 _ Y 3.62, X 2 [^п^лнсе^стве^ ^что в программе, разработанной для чтения форматных данных программист оставляет проверку данных Паскалю, например используя для чтения первого элемента приведенного выше бланка оператор READ(INPUT,weight). Однако, если пользователь такой программы по ошибке введет, скажем, 16.7S вместо числа 16.75, то Паскаль выдаст сообщение о неверном числе /* и программа остановится. Для программы, считывающей более чем пару чисел, такой подход неприемлем. IЕдинственный способ не утратить контроль над программой - это читать данные по одной литере и конструировать числа или ключевые слова и находить ошибки пользователя в самой программе. В Паскале -есть лишь одна безопасная процедура чтения - REAQ(file,ch) с предварительной проверкой конца файла. □ели эти рассуждения представили вам Паскаль в дурном свете, то не сомневайтесь - ряд других известных языков в смысле работы с вводом ничуть не лучше. Язык Фортран предлагает ряд привлекательных описателей ввода (см. мою книгу Illustrating Fortran, C.U.P., 1982, гл. 10), и тем не менее единственный практичный из них - тот, что считывает одну литеру. Несколько лучше обстоят дела в тех версиях Бейсика, где есть оператор «ON ERROR...», потому что этот оператор в случае ошибки при чтении позволяет вернуть управление - но такой подход явно неуклюж. (^писанная ниже процедура сконструирована так, чтобы продолжать работу, какая бы глупость не встретилась во входном файле. Называется процедура grab. ^1\ля использования процедуры grab просто вызывайте ее, когда потребуется следующий элемент. Проверки конца файла перед вызовом делать не надо. Считается, что каждый элемент заканчивается пробелом, признаком новой строки или концом файлу. Процедура возвращает запись, описывающую все аспекты прочитанного элемента. Процедура grab различает четыре типа элементов: • *имя\ имя начинается с буквы и содержит только буквы и цифры. Знача- щими являются только четьГре первые литеры (РАЗМЕРНОСТЬ=РАЗМЕРЫ). • число} число может быть записано с десятичной точкой или без нее. Процедура отличает эти две формы записи числа. • nogood\ строка литер, которая не является ни именем, ни числом (например, +R6). • tisn’t} пустой элемент, означающий конец файла; (любое последующее обращение к grab даст тот же результат). 128
Здесь изображен формат записи, — которую возвращает процедура. Эта запись выглядит громоздкой, но на самом деле очень проста в обращении. Пусть, к примеру, программист ожидает, что следующим элементом^^процедуры входного файла будет число. Обращение может выглядеть так: grab это тип записи для . stri ngl. j । хл-J CZZJ .length» 1 1..72 72 .пгГ 7» REAL .intf —1 INTEGER • nomCZZZI grab(it); IF it. tisnumber THEN remember:=it.nr ELSE complain(it); вещественное целое имя ошибка конец .tisnumberD^ .tisintegerQ I .tisname □ I .tisnogoodD । .tisnt Oj логические «флаги» Здесь предполагается, что complain - процедура. диагностики. Таким образом, если элемент окажется не числом, а чем-то другим, то диагностическая процедура сможет точно указать, где ошибка ( IF it.tisnt THEN ... IF it.tisnogood THEN ...) и, обратившись к компоненте it.string, сможет точно определить, что ввел пользователь. Ьуозможно, программист задействует операторы WITH it DO ... и, таким образом, упростит ссылки иа запись: IF tisnumber THEN ... IF tisnogood THEN ... . 3В°Д числа вроде 12345 повлечет за собой установку в положение true обоих флагов - флага tisnumber и флага tisinteger. Затем в поле .пг будет помещено значение 12345.0, а в поле .int - значение 12345 . Однако, за вводом 12345000000 последует установка в true только tisnumber, потому что (в стандартном компиляторе) это число превосходит MAXINT. □уиже логика процедуры grab представлена таблицей состояний. Использование таких таблиц описано на с. 60. \пИТер; СОСТОЯНИЯХ > G 1] G 2] ’О’. .’9’ 3] ’A’..’Z’ ’a’./z 4] О 5] другие 6] Q , пробел, нов. строка ф[1. ф[2 sign:=- nr:=digit(ch) =>[3. nom[i]:=ch ф[7. ф[7. =>[1. [2, чР с>[7. ^дей^твие =>[7. [7^ ф[7. tisnumber:=TRUE; nr:=sign*nr; [3, «=>[7. => [7. nr:=10*nr+digit(c ф[3. (Э h) =>[7. & frac:= =t>[4. <ф[7. if nr<MAXINT then- tisinteger:=TRUE и int:=sign*TRUNC(nr) [4, ф[7. ф[7. frac:=10*frac; _ nr:=nr+digit(ch)/ <=>[7. frac; =t>[7. «=>[7. tisnogood:=TRUE [5, =>[7. => [7. а =>[7/ =>[7. ф[7. t i sn u m be r: =TRU Eiqji nr:=sign*nr 4^ [6, ф[7. ф[7, i:=i+1; nom[i]:=c h; =>[6J(3> =>[7. Ф[7. tisname:=TRUE [7. => [7. ф[7. =>[7. => [7. =0[7, Ф[7. tisnogood:=TRUE различные действия в этой таблице представлены номерами в маленьких * облачках, например 7 . Изменение состояния отмечается широкой стрелкой, например qA [7,. Сама таблица хранится как массив tablefl..7,1..7] (см. следующую страницу), а значение каждой компоненты представляет собой код 100 ♦ действие + состояние Эта таблица формируется в компьютере при помощи файла. вида 129 5-458
ИМЕНИЕ ПРОЦЕДУРА ® Ц&ЗДИ ( ЗАПИСЬ ) ОБЕСПЕЧИВАЕТ (®)сновная программа начинается с задания констант. Значение Stringlength должно быть равным максимально возможной длине вводимых строк (в случае, если пользователь вообще забудет ввести пробелы или запятые). В константе Namelength надо задать число значащих символов в имени - обычно это четыре литеры. Константы Minord и Maxord - порядковые значения первой и последней литеры в имеющемся наборе литер. Для кода ASCII - это 32 и 127. В случае работы с кодом EBCDIC или каким-либо другим кодом измените эти значения. ------------------------------------------------------------------------------- PROGRAM saferead(INPUT,OUTPUT,!); CONST stringlength-72; namelength-4; minord-32; maxord-127; TYPE stringtype - PACKED ARRAY[1.. st ringlength] OF CHAR; nametype - PACKED ARRAYfl..namelength] OF CHAR; lookuptype - ARRAYfminord..maxord] OF 1..7; tabletype - ARRAY[1..7,1..7] OF 1..1200; intype - RECORD string: stringtype; length: 0..stringlength; nr: REAL; int: INTEGER; nom: nametype; tisnumbef.tisinteger.tisname, tisnogood,tisnt: BOOLEAN; END; lookup[32] lookup[33] lookup[34] T 6 6 np< lookup[46] | lookup[57] | |[co[| c ’9’ lookup[66] г ’B’ (Тгримеры) VAR it: intype; lookup: lookuptype; table: tabletype; INTEGER; f: _ ^Аассивы lookup и table необходимо проинициализировать. Массив lookup предназначен для определения номера столбца в таблице table, который соответствует прочитанной литере. Например, если в ch находится литера ’9*. то lookup[ORD(ch)] сразу даст 3. Аналогично, если ch содержит то lookup[ORD(ch)J будет 5. Инициализация производится специальной процедурой, которая должна быть вызвана только один раз: перед первым обращением к grab. Вот эта процедура:8' PROCEDURE initialization(VAR 1: lookuptype; VAR c: CHAR; i,j: 1..7; k: minord..maxord; BEGIN ф- FOR ki- mlnord TO maxord DO l[k]:—6; l[ORD(’+’)]i“l; irORDC-’N-a; O- FOR ci- *0’ TO V DO irORDCc’)ls-3; 4>- C^—-FOR ci- ’A’ TO ’Z’ DO l[ORDCc’)ji-4; > ф—FOR ci- ’a’ TO ’z’ DO ItORDfc’XM; l[ORD(’.’)l!-5; > rORDC,’)]:-?; 1[ORD(’ ’)ji-7; r признак конца строка как пробел* VAR t: tabletype); заполняем шестерками^ потом некоторые компов ненты переустановим^ читает 130
\®/бъявление VAR в основной программе содержит объявление файла типа TEXT: «f: ТЕХТ>. Запись и последующее чтение этого файла избавляет от необходи- мости записывать сорок девять отдельных присваиваний: t[l,l]:-002; t[l,2]:-102; t[l,3];-203; и т.д. (13Сли ваш компилятор Паскаля допускает «врёменные» файлы, то можно перенести все ссылки на f из основной программы в раздел VAR процедуры инициализации ~ единственное место, где используется f.) REWRITE(f);<s4 открытие файла f на записы WRITE(f, 002, 102, 203, 1006, 007, 007, 001); WRITE(f, 007, 007, 203, 007, 007, 007, 300); WRITE(f, 007, 007, 403, 007, 504, 007, 300); WRITE(f, 007, 007, 605, 007, 007, 007, 700); WRITE(f, 007, 007, 605, 007, 007, 007, 800); WRlTE(f, 007, 007, 1106, 1106, 007, 007, 900); WRITE(f, 007, 007, 007, 007, 007, 007, 700k RESET(f); x ^£установ№ a FOR i:-l ГО 7 DO /^^ключ: 50* (сравните этуХ^ таблицу с табли^ цей на с. 129 S V f FOR j:-l ТО 7 DO READ(f,t[i,j]); END; { initialization } vcC>[6y-> подразумевает выход. действий новое 7 SzK состояние С0|иже показано начало процедуры grab. Сюдй включено определение локальной функции для нахождения целого значения литеры. Например, digitC6*) возвращает 6. PROCEDURE grab( VAR rec: intype); VAR i: 1..stringlength; sign: state: 0..7; ch: CHAR; action: 0..11; frac: INTEGER; FUNCTION digit(c: CHAR): INTEGER; BEGIN digit:- ORD(ch) - ORD(’(T); END; BEGIN { grab ) WITH rec DO BEGIN { WITH rec } tisnumben- FALSE; tisintegen- FALSE; tisname:- FALSE; tisnogood;- FALSE; length:- 0; stat:—1; sign:—1; О FOR i:-l TO stringleneth DO strinrfi O'FOR i:-l TO namelength DO nom[i}- tisnt: установить во все ~‘\^флаги falsej FALSE?*^^^^ заполнение приемной строки пробелами продолжение на следующей странице 131 5*
( ЗАПИСЬ ) ПРОДОЛЖЕНИЕ [^\алее приведена логика содержательной части процедуры grab: проверка] t конца J [ файла) в процедуре grab - это единственный оператор READ столбец 7 - для разделителе^/ DIV 100; конец файла в состоянии (state) L означает, что grab ничего не ) ементг извлекает ... пустой REPEAT {для каждой цифры} IF EOF(INPUT) THEN BEGIN № action:=table[state,7] tisnt:» (state = 1) END ELSE BEGIN READ(INPUT,ch); length:=length+l; string[length]:=ch; action:=table[state,lookup[ORD(ch)] DIV 100; state:=table[state,lookup[ORD(ch)] MOD 100; END; { END ~ ' CASE action OF 0: 1: 2: 3: заполнение^ ^стро^и/* IF } действие^ и новое^ состояние ip^ з^чение было^+ц \лы sign:---1; С nr:= digit(ch); BEGIN IF nr <= MAXINT^-< THEN BEGIN x tisintegen=TRUE; int:» sign*TRUNC(nr) END; tisnumber:» TRUE; nn= sign*nr END; nr:» 10*nr + digit(ch); 1; Hj nr может быть преобразовано целое функцией TRUNQ ) только^ "^'-^Лесли оно не превосходит^^ MAXINT^- 4: 5: frac: 6: BEGIN frac:= 10*frac; < nr:= nr + digit(ch)/frac; END; 7: tisnogood:» TRUE; 8: BEGIN построение целого в nr после десятичной точки\ делим очередную цифру на 10, 100, 1000 и т. д^ 9: 10: И: tisnumber- TRUE; nr- sign*nr END; tisname:» TRUE; nom[l]:= ch; BEGIN первая литер IF i<= namelength THEN nomTi]:» ch END; END { CASE ]^^нюр^лам^е^авершение\ UNTIL (state » 0) ORTisnf^^^>=<2> END END; { procedure grab } построение noml дли^ы ngmelengti ^ встретился конец файла и никакогс^лемента^переднимЛ* 132
приведенная ниже главная программа служит лишь для демонстрации процедуры grab: BEGIN { главная программа } i и i t ial izat ion( lookup, table); REPEAT grab(it); WITH it DO BEGIN IF tisnumber THEN WRITELN(nr); IF tisinteger THEN WRITELN(int); IF tisname THEN WRITELN(nom); tisnogood THEN FOR i =l TO length DO IF гиц ь=1 iu lengi < WRITE(string[i]); x WRITELN END { WITH it } UNTIL it.tisnt END. { program } с программой, например, так: двоичных файлах рассказывается на следующей странице. Файл с именем f из предыдущего примера было бы лучше сделать двоичным файлом. Для соответствующих изменений программы поставьте вместо объявления f: TEXT в разделе VAR главной программы объявление f: FILE OF INTEGER. □Проверка конца файла (EOF) в начале предыдущей страницы рассчитана на неинтерактивную работу, однако, эта проверка не помешает и при интерактивной работе. Функция EOF будет возвращать значение false, Пока с клавиатуры не послан специальный сигнал (в Турбо Паскале это И ). Если у Вас возникнут неполадки при работе процедуры grab, загляните для воодушевления в гл. 11. 133
ПРОЦЕДУРЫ W© »©3 ^3 СИМНЫЕ (3/лемент текстового файла преобразуется из цепочки литер во внутреннюю форму при выполнении процедуры READ; из внутренней формы в цепочку литер - при выполнении процедуры WRITE. В двоичном файле в отличие от текстового информация хранится во внутренней (двоичной) форме. Двоичные файлы имеют ряд преимуществ в сравнении с текстовыми. Отсутствие необходимости в преобразовании позволяет их считывать и записывать с большей скоростью. Они также компактнее текстовых файлов и лишены ошибок округления, которые возникают при преобразовании во внутреннюю форму и обратно. Недостаток двоичных файлов в том, что даже если напечатать двоичный файл, то нельзя будет понять, что в нем содержится (исключением является FILE OF CHAR). 1^\воичные файлы полезны для врёменного хранения информации в процессе выполнения. Обычно в конце работы программы, отслужив свое, такие файлы могут быть уничтожены. Однако иногда необходимо сохранять огромные объемы промежуточных данных, полученных в одной программе, для последующего их использования в другой программе. Двоичные файлы - компактные и точные - идеальны для этих целей. Ч^хайл в Паскале - это переменная. Посмотрите на последнюю строчку раздела VAR на с. 130, она воспроизведена ниже: i: INTEGER; f: переменная типа TEXT типа TEXT точно таким же образом, Видно, что файл f объявлен как переменная как объявлена переменная i типа INTEGER. Вообще файлы могут быть любого типа; все файлы, за исключением файлов типа TEXT - двоичные. 3 следующем примере каждая компонента файла binfile является записью того же типа, что использовались в программе о персональных записях на с. 112. .surname Г“ forename [77 TYPE nametype - PACKED ARRAY[1..1O] OF CHAR; detailtype - RECORD surname, forename: nametype; age 18.. 65; grade: (jr,sr,exec) END; VAR binfile: FILE OF detailtype; вверху схематически нарисована одна незаполненная компонента файла binfile. Файл может содержать любое количество таких записей, какое необходимо для работы программы. (®/бъявление любого файла сопровождается неявным объявлением еще одной переменной - переменной-окна, относящейся к этому файлу. Имя окна такое же, как и имя файла; только, как показано, добавляется t: Вся связь с файлом binfile осуществляется через окно с именем binfilei, которое иногда также назы- вается буфером или буферной переменной файла. binf ilet. surname | .| bi nfi let, forename Г7Т77 binfilet.age I | binfilet.grade| | 134
(Синтаксис файлового типа определяется следующим образом: тип :: - TEXT , fACKEfih FILE OF тип 4 СЗКпИск^ ► TEXT ► PACKED FILE OF CHAR ► FILE OF detailtype 43Г- GnJe путайте FILE OF CHAR с текстовым файлом. Автоматическое прямое и обратное преобразование цепочек литер ~ а привилегия исключительно текстовых файлов, применять процедуры WRITELN и READLN. Запись в двоичный файл состоит, вообще должно быть записано, следует присвоить переменной-окну; (2) для сдвига вперед рамки окна и записи нового конца файла вызывается процедура PUT: также обнаружение конца строки ~ Только к текстовым файлам можно говоря, из двух этапов: (1) то, что стои^фаил', в окне виден конец файла перемещает окно файла REWRITE(binfile); WITH binfilet D BEGIN surname:» ’VENTANA* forename:»’ABIERTA’ age:» 21; grade:» sr END; PUT(binfile£ на новый конец .[□осле проверки конца файла, чтение из файла также состоит 1) прочесть содержимое окна; (2) используя процедуру GET, окна вперед к следующей компоненте (или к компоненты нет): RESET(binfile); IF NOT EOF(binfileT THEN BEGIN WITH binfile DO BEGIN s:- surname; f:- forename a:- age; g:» grade; END; GET(binfil END; концу файла, если из двух этапов: сдвинуть рамку следующей окно устанавлй* вается на первую, компоненту окно на сле- semeew else 32 exec FVEWTANA 'AB1ERTA Ц21 ьг “VENTANA — abierta 2! Sr SOTHEOWE"" ELSE 32 exec дующую компонен (или конец файла) Процедуры PUT и GET - это процедуры «нижнего уровня». Процедуры WRITE и READ могут быть описаны в терминах PUT и GET ~ и переменных-окон ~ следующим образом: WRITE(filename,item) 5 filename!:- item; PUT(filename); READ (filename,item) » item:- filename!; GET(filename); 3 Турбо Паскале процедуры PUT и GET не определены; вместо этого расширены процедуры WRITE и READ. 135
(SS3A КИЕ ПРИМЕР ИЛЛЮСТРИРУЕТ ОДНОВРЕМЕННУЮ РАБОТУ С ТЕКСТОВЫМИ И ДВОИЧНЫМИ ФАЙЛАМИ i/та программа разработана для чтения текстового файла и записи соответству- ющего двоичного файла. Таким способом достигается сжатие хранимой информации. Допустим, что текстовый файл был верифицирован другой программой, так что при вводе уже не будет надобности делать проверку формы и законченности. Пусть известно, что форма файла в точности такая, как здесь нарисовано: температура в J X СОЛНЕЧНОЙ КОМНАТЕ День Месяц темп, в полдень отметки 2 4 17 cnl со] сп] ш 1 цЛ цД ej ej ej 2.5 -10 -15.5 .-Хрл.одн^ .. MQP.Q3 . . ^ №РР!.!.!. . PROGRAM compressor text in,binaryout, OUTPUT); CONST monthchars = 3; remchars = 8; сообщения TYPE monthtype = PACKED ARRAY[1..monthchars] OF CHAR; remtype = PACKED ARRAY[1..remchars] grouptype = RECORD day: 1..31; month: monthtype; temp: REL; remarc: remtype END; VAR text in: TEXT; binaryout: FILE OF grouptype; count: INTEGER; i: 1..monthchars; OF CHAR; J 1.. remchars; bynaryout BEGIN count:=0; RESET(textin); REWRITE(binaryout); WITH binaryoutt DO WHILE NOT EOF(textin) DO .BEGIN X READ(textin,day); / FOR i:=l TO monthchars DO — READ(textin,month[i]); READ(textin, temp); CFOR j:=l TO remchars DO — READ(textin,remark[j]); \ counts count + 1; \ READLN(textin); \ PUT(binaryout); XEND { WHILE } { end WITH } WRITELN(OUTPUT,‘Обработано count/ строк данных’) END. окно binaryoutt.day | | binaryout t. month | , , | binaryoutt. temp | . binaryoutt. remark PUT 136
®3©ЙСТВА \ тип X. ФАЙЛА СВОЙСТВОМ ТЕКСТОВЫЕ ФАЙЛЫ ^packed" FILE OF CHAR (не текст, файлы ) Прочие типы (примеры: фай- лы массивов, записей сме- шаного типа) стандартные текст, файлы: INPUT, OUTPUT Файлы, определен- ные прог- раммистом Включение имен файлов в оператор PROGRAM INPUT - нео- бязателен, OUTPUT надо включать хотя бы для сообще- ний об ошибках В общем случае файл должен быть упомянут в операторе PROGRAM. (Некоторые компиляторы допускают «временные» файлы, которые не указываются в операторе PROGRAM) Определение файла как переменной в разделе VAR По умолчанию, типа TEXT В общем случае переменную-файл следует объявить в разделе VAR главной программы (или, если компилятор допускает временные файлы, то в локальном разделе VAR) RESET и REWRITE Не следует явно употреблять RESET и REWRITE Для чтения файл должен быть открыт процедурой RESET(imc* файла). Для записи файл прежде должен быть открыт процедурой REWRITE(«n<^ файла) Операторы ввода READ, READLN и GET: если 1-й параметр опущен - используется INPUT READ, READLN и GET: подра- зумеваемых парам, нет READ и GET, но не READLN Преобразова- ния при вводе Каждая цепочка литер автома- тически, в соответствии с типом параметра, преобразуется к типу CHAR, REAL или INTEGER Двоичный код файла преоб- разуется только в эле- менты типа CHAR Двоичный код файла преобра- зуется в REAL, INTEGER, CHAR в зависимости от типа файло- вой переменной Операторы, используемые для вывода WRITE,WRITELN, PUT и PAGE (некоторые компиляторы с Паскаля не допускают PUT) WRITE и PUT, но не WRITELN Преобразо- вания при выводе Элементы типа CHAR, INTEGER, BOOLEAN, а также PACKED ARRAY OF CHAR преобразуются в печатаемые цепочки литер Элементы типа CHAR преобра- зуются в дво- ичный код вы- ходного файла Элементы всех типов преобра- зуются в дво- ичный код вы- ходного файла EOLN Литера конца строки прочиты- вается как пробел. EOLN( ), когда в окне литера конца строки, возвращает true Конец строки не обнаруживает- ся; функция EOLN( ) исполь- зуется только применительно к текстовым файлам EOLN означает - EOLN(INPUT) Нет подразум, параметров EOF Если в окне файла - маркер конца файла, то функция EOF возвращает true; во всех остальных случаях - false EOF означает - EOF(INPUT) У EOF( ) подразумеваемых параметров нет При интерактивном вводе сиг- нал EOF зависит от системы Интерактивный ввод невозможен 137
WJ?£S3 ПЕНИ Iq [выполните программу saferead. Попробуйте с клавиатуры «вывести- из строя» эту программу. Это не должно получиться. При каждой попытке ошибочная строка будет отображаться на экране для проверки. io [Измените программу personel со с. 112 так, чтобы она записывала массив персональных записей в форме двоичного файла. Файл следует записать после прочтения всех данных, но до их сортировки. Кроме того, вставьте в программу чтение этого файла перед вводом каждого нового пакета записей. Обладая такими возможностями, программа будет выглядеть как простенькая управляющая система. Jq 3°зьмите любую программу из предыдущих глав (например, программу loanrate со с. 72) и замените ее примитивные операторы ввода обращением к процедуре grab. Если ваш Паскаль допускает интерактивные программы, добавьте соответствующие запросы и диагностику ошибок с тем, чтобы придать программе определенную дружественность по отношению к ее потенциальному пользователю. 1 (с. 123) В версии Турбо Паскаль чтение из текстового файла в состоянии конца строки возвращает не пробел, а специальную литеру - признак конца строки. 2 ) (с. 130) Процедура инициализации опирается на то, что коды всех строчных букв, равно как и коды всех прописных букв, располагаются подряд. Если это свойство не выполнено, то процедуру придется усложнить. 138
по HHfflER |Т|ИВНЬТЙ з@©д ДИАЛОГ ПРОБЛЕМА ЗАГЛЯДЫВАНИЯ ВПЕРЕД ПРОБЛЕМА БУФЕРА ПРОБЛЕМА КОНЦА ФАЙЛА (EOF)
ДИДА©1Г ПАСКАЛЬ ПРОЕКТИРОВАЛСЯ БЕЗ РАСЧЕТА НА ДИАЛОГ 1JПользователи многих современных программ работают в режиме диалога. Программа выдает на экран вопросы и подсказки, пользователь отвечает, набирая ответы на клавиатуре. Ответы зависят от уже полученных результатов. Если бы от пользователя требовалось сообщить всю информацию заранее, то результат, возможно, был бы иным. Другими словами, пользователь и программа приближаются к результату рука об руку, взаимодействуя друг с другом. Идея диалога сейчас является общепринятой, хотя в истории вычислительной техники она сформировалась относительно недавно. ^]зык Паскаль был спроектирован до того, как диалог стал общепринятым. Язык разрабатывался в те времена, когда программисты набивали программы на перфокартах и оставляли колоду карт оператору компьютера, который загружал ее в устройство чтения перфокарт. Данные также набивались на перфокартах и вручались оператору. Обе колоды впоследствии возвращались программисту завернутыми в «нотную бумагу» печатающего устройства с результатами (или же печальной диагностикой). Операторы обычно ожидали, пока для загрузки не накопится несколько таких программ. Поэтому такой режим обработки был назван «пакетным режимом». □Процедура ввода READ в Паскале была спроектирована в расчете на пользователя, работающего в пакетном режиме. Логика работы процедуры READ с перфокарточным файлом следующая: (1) считать с текущей перфокарты специфицированный элемент или элементы, затем (2) заглянуть вперед, чтобы увидеть, есть ли еще литера на текущей перфокарте; если нет, то сделать результатом функции EOLN - true. Такая логика дает программисту возможность перед каждым вызовом процедуры READ писать: IF NOT EOLN THEN ... ^^огика чтения целой строки (READLN) - такая же: (1) считать специфициро- ванный элемент или элементы с текущей перфокарты, игнорируя при этом все оставшиеся позиции; затем (2) заглянуть вперед, чтобы увидеть, есть ли еще перфокарта; если нет, то сделать результатом функции EOF значение true. В результате программист имеет возможность перед каждым вызовом процедуры READLN писать: IF NOT EOF THEN ... (®)днако, заглядывать вперед при интерактивном вводе - это абсурд; программа не может знать, что еще предполагает ввести ее пользователь. Таким образом, в силу того что ввод осуществляется человеком, отвечающим на запросы, логику процедур READ и READLN необходимо модифицировать. Распространенным подходом к решению этой проблемы (Acornsoft: ISO Паскаль, Prospero: Pro Паскаль) является «отложенный ввод-вывод», который означает, что подглядывание вперед откладывается до тех пор, пока программа не сделает следующего запроса с клавиатуры ~ например, посредством READ или EOF. Иная техника (Borland: Турбо Паскаль) - воспринимать >в качестве результата заглядывания вперед текущую литеру с клавиатуры \ И тот и другой подходы решают проблему заглядывания вперед, которая обсуждается напротив. Об остальных проблемах речь пойдет после. 140
ДЕЛАЕТ НЕВОЗМОЖНОЙ ИНТЕРАКТИВНУЮ РАБОТУ [Лринцип заглядывания вперед, обсуждавшийся в общих странице, и обуславливает заминки при интерактивной (при работе с файлом INPUT - неявная) помещает окно чертах на предыдущей работе. Процедура RESET на первый элемент файла; при последующем чтении процедурой READ или READLN содержимое окна копиру- ется и затем окно передвигается к следующей литере или за ближайший конец строки соответственно. Это фундаментальный принцип Паскаля, поэтому стоит PROGRAM hiccups(INPUT,OUTPUT); VAR a,b: CHAR; BEGIN WRITELNf первая буква’); READLN(a); WRITELNf вторая буква’); READLN(b); WRITELN(a.b.’l’); END. исследовать, что же произойдет, если попытаться выполнить эту маленькую прог- рамму ~ скомпилировав ее традиционным компилятором с Паскаля ~ в интерактивном режиме. первая буква ыполнение начинается с оператора WRITELNf первая буква’); затем выполняется оператор READLN(a). Здесь программа набрано и ждет, пока будет что-либо нажата клавиша RETURN. |аберем Н и нажмем клавишу RETURN. [h]|return| первая буква н у у (®)ператор READLN(a) считывает Н, однако, не удовлетворяется, пока не увидит первую литеру следующей строки. Таким образом, мы «зависли». Очевидный выход - ввести первую литеру следующей строки. [RETURN | [Д}се еще висим! В большинстве систем программа не получает данных, пока не нажата клавиша RETURN. Нажмем ее. КАК RETURN первая буква Д вторая буква А ДА! Теперь оператор READLN(a) завершается и управление передается оператору WRITELNfвторая буква’) и далее на READLN(b). Оператор READLN(b) считывает У, однако ждет первой литеры следующей строки. Опять висим! (Следующей строки нет. Тем не менее надо ввести что-нибудь. Что угодно! ЦЭаконец, завершается оператор READLN(b), так что управление переходит на оператор WRITELN(a,b,’!’) и затем на конец программы. Не блестяще! компиляторы с Паскаля вроде упомянутых на предыдущей странице, не вызывают заминок. Результат будет точно таким, как можно ожидать из текста программьь. Иначе говоря, таким, как здесь нарисовано . 141
Пирь лат 3^z ИЕЙ ЕСЛИ ВАШ ПАСКАЛЬ РАБОТАЕТ ТАК ЖЕ, НЕ ПИШИТЕ ИНТЕРАКТИВНЫХ ПРОГРАММ 1у те времена, когда под словом «файл» подразумевался «файл на магнитной ленте», обычным делом было использование Паскаль-процессором буферов ввода- вывода. Буфер - это область в памяти. Литеры, посланные в выходной файл, сперва обязательно попадали в буфер. И только когда буфер заполнялся, его содержимое копировалось на магнитную ленту. Этот же подход использовался и при вводе. Такая буферизация просто необходима при работе с файлами на маг- нитной ленте, она полезна при работе с дисками, однако если «файлом» явля- ется человек, вводящий данные с клавиатуры, то буферизация - это катастрофа. Ниже, на примере простой программы, иллюстрируются происходящие события. PROGRAM flush(INPUT,OUTPUT); VAR a,b: CHAR; BEGIN WRITELNfпервая буква’); READ(a); WRITELNf вторая буква’); READ(b); WRITELN(a,b,’!’); END. Co] I пробел] ГЛ I RETURN-! выполнение начинается с оператора WRITELNfпервая буква’). Слова ’первая буква’, несомненно, печатаются, однако печатаются в выходной буфер ~ имеющий достаточный объем, чтобы его содержимое еще не копировалось на экран. На экране ничего нет, а программа ждет. Оп]аберем строку данных и нажмем клавишу RETURN. Данные появятся на экране, ио это еще не означает, что программа получила их. Зсли больше ничего не происходит, то это значит, что данные попали во входной буфер и, пока буфер не заполнится или пока с клавиатуры не будет послан сигнал конца файла, данные из буфера не будут выбраны. (Как послать сигнал конца файла - зависит от системы.) [Предположим, что при вводе используется всего лишь буфер строки, который активизируется клавишей RETURN. Это означает, что оператор READ(a) выпол- нится; оператор WRITELNf вторая буква’) пошлет слова ’вторая буква’ в буфер вывода; оператор READ(b) выполнится; оператор WRlTELN(a,b,’!’) пошлет слово ’0X1’ в буфер вывода. конце концов управление достигает завершающего END. и выходной буфер копируется на экран. Нрли ваши программы ведут себя подобным образом, то это означает, что Паскаль-компилятор не рассчитан на работу с интерактивными программами. Программы для такого компилятора должны ориентироваться на ввод данных из дискового файла. компиляторам Pro Паскаль, Турбо Паскаль и Acornsoft Паскаль не присущи трудности, речь о которых шла выше; с их помощью можно компилировать интерактивные программы. 142
НЕ ИСПОЛЬЗУЙТЕ ОПЕРАТОР WHILE NOT EOF В ИНТЕРАКТИВНЫХ ПРОГРАММАХ (Справа - стандартная в Паскале схема для неинтерактивного ввода. Однако, что означает EOF в интерактивной программе? Обычно сигнал конца файла посылается с клавиатуры в виде специальной литеры, которая зависит от настройки системы. Пример сигнала конца одновременное нажатие клавиш №У PROGRAM pardon(INPUT, OUTPUT); VAR i: INTEGER BEGIN WHILE NOT EOF(INPUT) DO /BEGIN [ WRITELNf число:’); 1 READLN(INPUT, i); \ WRITELNfудвоенное -’,2*i) END END. 11 число: WHILE NOT EOF(f) THEN BEGIN • прочитать и обработать информацию в окне, затем • сдвинуть окно к —^следующему элементу . ----------------------- (*5десь показано, что же будет, если вы используете этот прием в интерактивной программе. работа начинается с проверки WHILE NOT EOF(INPUT), где программа ждет. Еще ничего не было набрано, поэтому функции EOF нечего проверять. (Оператор READLN тут ни при чем, потому что управление до него еще не дошло.) Поможем программе, введя первое число. И достаточно для проверки конца Функция EOF(INPUT) возвращает false исла файла, и, таким образом, управление передается на оператор WRITELNfчисло:’) и далее на READLN(INPUT,i). Оператор READLN(lNPUT,i) считывает ’11*, но не завершается, заглянет вперед (об этом см. также страницы). пока не в конце возвращает И число: 12 удвоенное - 22 11 число: 12 удвоенное - 22 число: _ еперь оператор READLN(INPUT,!) первое число И, управление переходит на WRITELNfудвоенное -’,2*i); этот оператор печатает 22. Затем цикл начнется сначала с WRITELNf число:’) и затем READLN( INPUT,!). Оператор READLN(INPUT,!) считывает число 12 и ждет следующей возможности заглянуть вперед. ^?аким образом, программа будет продолжать печатать решение предыдущей задачи, затем запрашивать число, которое ей уже было дано. СД так до тех пор, пока не будет нажата комбинация клавиш, посылающая сигнал конца файла в вашей конкретной системе (здесь она обозначена как ). удвоенное^, 24 «отложенным вводом» резуль- таты выглядят чуть разумнее, но все же остаются «не в фазе».'*' QQe используйте WHILE NOT EOF в интерактивных программах. 143
ПИИ тажиа РЭДЖй'ФРА (с. 140) Способ действий в версии 5 системы Турбо Паскаль скорее отвечает «отложенному вводу-вы воду», чем использованию текущей литеры. Версия 3 в этом смысле несколько отличается от версии 5. 2 ) (с. 141) Проблему заглядывания вперед вполне можно решить программным путем. Основная идея - не использовать процедуру READLN для чтения данных, а вызывать ее непосредственно перед чтением данных из новой строки. Например, программу hiccups можно модифицировать следующим образом: PROGRAM nohiccups(INPUT, OUTPUT); VAR a,b: CHAR; BEGIN WRITELNfпервая буква*); READ(a); WRITELN(* вторая буква*); READLN; READ(b); WRITELN(a,b,’!’); END. Эта программа будет работать правильно (как показано внизу с. 141) при использовании компилятора с заглядыванием вперед (но неправильно .в случае современного компилятора типа Турбо Паскаль). 144
ДИНАМИЧЕСКАЯ ПАМЯТЬ ПРОЦЕДУРЫ NEW И DISPOSE СТЕКИ И ОЧЕРЕДИ ОБРАТНАЯ ПОЛЬСКАЯ НОТАЦИЯ RAJIOdnon (ПРИМЕР) ПРОСТЫЕ ЦЕПИ КРАТЧАЙШИЙ ПУТЬ (ПРИМЕР) КОЛЬЦА АСТРА (ПРИМЕР) ДВОИЧНЫЕ ДЕРЕВЬЯ ОБЕЗЬЯНЬЯ СОРТИРОВКА (ПРИМЕР)
СДдея указателя уже встречалась в контексте сортировки массива, Лучше переставлять указатели, нежели компоненты, на которые те указывают. Указателями были целые числа, лежащие в интервале изменения индексов массива. п Л m rvm ВВОДЯТСЯ УКАЗАТЕЛИ и ДИНАМИЧЕСКИЕ и ЗАПИСИ FOR i:-l ТО ^DO^WRITELN(a[o[im гули есть возможность разместить сортируемые элементы в обычном массиве, то указателями на эти элементы, как показано выше, могут быть целые числа. Однако ввиду негибкости массивов использование этой структуры не всегда удобно. В ателье проката, скажем, никогда не хранится по одному свадебному платью или выходному костюму для каждого из зарегистрированыых клиентов, поскольку невероятно, чтобы все они в один день венчались или созывали гостей. По тем же причинам непрактично объявлять каждую переменную-массив массивом с наибольшими допустимыми размерами. Руководствуясь принципом «больше овец - меньше козлов» Паскаль запасает «кучу» коробочек для хранения данных. По мере поступления данных коробочки достаются из кучи и собираются в записи. Если, к примеру, первым элементом считываемых данных является отсчет температуры, то для хранения значения изготовляется контейнер типа REAL Если следующий элемент содержит сложную персональную запись, то взятые из кучи коробочки собираются в контейнер соответствующего типа. Если запись более не требуется, то от ее контейнера можно избавиться, вернув составляю- щие его коробочки обратно в кучу. Такие записи в силу того, что они появляются и исчезают, называются динамическими записями. Теперь посмотрим на динамическую запись по аналогии с файлом. Вспомните, каждый файл связан с — файловой переменной в виде окна. Если файл назы- (текуще* вается f, то к окну можно обратиться, используя к окно£ ft. Другими словами, окно не имеет своего имени: ссылка на него производится через имя файла, который представляет собой цепь таких окон ADAMS.! PHILBEAN файл. чу казате Р Liownley! [J очно также, с каждой динамической записью связан указатель. Если указатель называется р, то ссылка на динамическую запись делается посредством pt. Другими словами, динамическая запись не имеет своего имени: на нее можно сослаться по имени любого указателя, который указывает непосредственно на эту запись. Создав в каждой записи компоненту, содержащую указатель на другую запись, можно построить «цепь». Ссылка на элементы в текущей записи делается тем же способом, что и ссылка на элементы в текущем окне'. _____________________________ > —ъ WRITELN(ft.initial); I а | WRITELNfptinitial); I /Zb J [Приведенные здесь указатели - не целого типа. Они имеют специальный тип - указательный (напечатать указатель, чтобы посмотреть на что он похож, нельзя). Теперь определим синтаксис объявления указательного типа: 146
^aUma^^ типа допускаются) моделью/ УКАЗАТЕЛЬНЫЙ! ► TYPE x pointertype - fpersontype (Сравните приведенный выше синтаксис указательного типа с синтаксисом файлового типа: PACKED FILE OF тип ФАЙЛОВЫЙ^ ► TYPE TYPE persontype^ (RECORD^^^4* > surname: PACKED ARRAY[1..10]OF CH (initial: CHAR; " r age: 18..65 filetype - FILE Заметим, что слова FILE OF (ФАЙЛ ИЗ) соответствуют не словам УКАЗАТЕЛЬ НА (как можно было бы предположить), а всего лишь стрелке вверх. В этом смысле стрелка вверх должна произноситься как «указатель на» и пониматься как сокращение этого словосочетания. (Сравнивая синтаксис, заметим, что за словами FILE OF может следовать имя типа или полное определение типа. В этом примере можно убрать имя persontype, написав сразу после FILE OF определение записи. Напротив, в случае указателей такое сокращение недопустимо: элемент после стрелки вверх должен быть именем уже определенного типа. OF (personiypi poi ntertype - t] —.....Собязат£лы ►ersonty] to имяЛ если данная запись сама 4/ казатель на запись содержит указатель на такими указателями - объявления: полезнее всего использовать, другую запись. Простейшая структура данных, связанная это изображенная напротив «цепь». Здесь необходимы два surname: PACKED ARRAY[1..1O] OF CHAfi initial: CHAR; age: 18..65 ersontype - RECORD next: pointertype; oiriiertype - J person type^ ’ [^акое объявление поместить первым? Если сначала объявить persontype, то будет ссылка вперед на pointertype. С другой стороны, объявленный сперва тип pointertype будет ссылаться вперед на тип persontype. Не гоняйтесь за двумя зайцами, сперва объявляйте то, что содержит стрелку вверх. Ссылки вперед из указате лей разрешены; это вынужденное исключение из правила, запрещающего ссылаться на элементы, определение которых появится позже. £^ав имена одному или нескольким указательным типам, можно объявить переменную-укззгтълъ. Это делается обычным способом в разделе VAR. В примере на следующем развороте демонстрируется объявление переменных-указателей с именами top и р; обе эти переменные принадлежат типу pointertype. (Существует одна стандартная константа указательного типа, которая не нуждается в объявлении (способов объявления указателей-констант не существует). Стандартный указатель-константа называется NIL. При обработке указателей она аналогична нулю; ее можно использовать для пометки конца цепи, что и демонстрируется на следующих двух страницах._____________ стандартная константа ^^ука^ате^но^тч^п^ 147
ПРОЦЕДУРЫ SW и И ОРГАНИЗАЦИЯ СВЯЗАННОГО СПИСКА (ЦЕПИ) ^]тобы понять, что проще всего начать и программа создает top же делает программа, приведенная на следующей странице, объяснение с середины. Пользователь вводит ’А’, затем ’В* Теперь пользователь собирается ввести ’С и изображенному выше списку. Это присоединение (которые уже выполнены для букв А и В): присоединить эту букву к происходит в четыре этапа (i) (Н) создается новая запись, на которую указывает р. Это достигается вызовом стандартной процедуры NEW: NEW(p); в запись помещаются данные, например READLN(pt.data); (iii) указатель из переменной top копируется в новую запись. Теперь новая запись возглавляет старую цепь (наравне с top) pt. next:» top; (iv) указатель из переменной р копируется в top. Теперь top (наравне с р) возглавляет удлиненную цепь: top:= р; 3 результате получится: top шаг, если конец цепи ^-Цтобы отсоединить от цепи первую запись, требуется только можно пожертвовать маленьким кусочком памяти (i) указатель обреченной записи копируется в top. Теперь top указывает на следующую запись: top:» topt.next; всего один (как правило, можно): С В А (®/днако если мы не можем позволить себе Летучих Голландцев, то их остовы можно вернуть в кучу для повторного использования. Для этого следует: (i) указать обреченную запись; (ii) исключить ее, как показано выше; (iii) вызвать стандартную процедуру DISPOSE. Три шага вместо одного: р.- top; top:- topt.next; DISPOSE(p) top[ Р[ отдана буква 'С исчезла; указатель р - неопределен. Теперь 148
Щз предыдущего объяснения ясно видно, что запись, которая подключается последней - исключается первой. Следовательно, образ присоединения звена к цепи и отсоединения от нее можно заменить на другой - добавление в верх £1^ля работы с программой, которая написана ниже, необходимо: чтобы втолкнуть букву в стек - ввести +L (или плюс любую букву); чтобы вытолкнуть из стека - ввести один знак минус (в начале строки); чтобы остановить программу - ввести звездочку (в начале строки). PROGRAM stack(INPUT, OUTPUT); TYPE pointertype - t recordtype; record type = RECORD next: pointertype; letter: CHAR END; VAR top,p: pointertype; ch: CHAR; BEGIN < --- top:- NIL; REPEAT i READ(ch); / IF ch IN / THEN / CASE гинициализаци. пустого отека ch OF BEGIN { Вталкивание NEW(p); READLN(pt. letter); pt.next:» top; top:- p END ' END UNTIL ch - END. Т вытолкнута L вытолкнута выталкивать нечего и т.д. стандартная процедура W имяуказателя BEGIN { Выталкиванщ^4 IF top <> NIL прорер^а: не пустл ли ст$к? THEN BEGIN WRITELN(topt.letter,* вытолкнута’); P:= top; top:— topt.next; ISPOSE(p) END ELSE WRITELNfвыталкивать нечего’) END r— { CASE } стандартная процедура DISPOSE( имяиказателя ) l/AUJU/ 149
ИСПОЛЬЗОВАНИЕ РЕКУРСИИ ПРИ ОБХОДЕ СВЯЗАННОГО СПИСКА ЦЦрограмма на предыдущей странице была по возможности упрощена с тем, чтобы показать без лишнего тумана механизм исключения записей из головы цепи и подключения к ней. В программе, приведенной ниже, используется та же техника, лишь выделены отдельные процедуры и функции, вызываемые следующим образом: к push(ptr,ch) ch.-- pop(ptr) ^мроме того, добавлены еще две вспомогательные подпрограммы (утилиты), которые, не изменяя простой структуры стека, вталкивают элемент в стек снизу и выталкивают элемент из стека снизу соответственно: pushtail(ptr,ch) и ch:- poptail(ptr). [□ели пользоваться только подпрограм- мами push и poptail, то цепь будет работать как очередь. Элементы вталкиваются с одной стороны, стоят в очереди и выталкиваются для обслужива- ния с другой стороны. Использование только подпрограмм pushtail и pop означает, что такая же очередь выстраивается в обратном направлении, ^тобы достичь низа стека, используется pushtail, текущее звено цепи оказывается в рекурсия. Когда вызывается процедура одном из двух состояний: ptrltlllJ или pt ptrt.next Если pt г- NIL, то мы находимся в конце цепи, и поэтому надо заменить NIL указателем на новую запись. Если ptroNIL, то мы - не в конце цепи, и поэтому вызываем процедуру pushtailfptrt.next,ch) для вталкивания ch. [рекурсия используется также и в функции poptail, но здесь возможны уже три состояния: ptr (NIL I или ptr B*Onil ptrt .next ил и pt г I ..> Если ptr—NIL, то очередь пуста. Если ptrt. next-NIL, то в очереди - единственный элемент, который можно вытолкнуть, как если бы очередь была стеком. Если ptrt.nextoNIL, то мы вызываем функцию poptall(ptrt. next), которая сделает то, что надо. PROGRAM staque(INPUT, OUTPUT); TYPE pointertype - t recordtype; recordtype - RECORD next: pointertype; data: CHAR END, VAR top: pointertype; ch: CHAR; .next .data сперва задается структура данных 150
PROCEDURE push(VAR ptr: pointertype; c: CHAR); VAR p: pointertype; BEGIN NEW(p); pt.data:= c; pt.next:= ptr; ptr:= p END; [если, хотите, то * можете избавиться •от этого Летучего Голландца FUNCTION pop(VAR pt BEGIN pop:= CHAR(O);*^ IF ptroNIL , THEN ' BEGIN pop;= pt rt. data; ptr= ptrt.next END END; PROCEDURE pushtail(VAR ptr: pointertype; c: CHAR); BEGIN IF ptr=NIL THEN BEGIN NEW(ptr); pt.data;= c; pt. next; = NIL; END ELSE pushtgjl(ptrt.nexttc) BEGIN { staque } top:= NIL; REPEAT и READ(ch); IF ch IN [V, THEN CASE ch OF Здесь приведены четыре утилиты; они весьма эффективно используются в прог рамме на с. 154. г: pointertype): CHAR; W если стек пуст?^. \ то pop возвращает (этот символ-не видимы FUNCTION poptail (VAR ptr:pointertype):CHAR; BEGIN <3=-------- poptai h = CHR(O), ... >1 пусто; L. IF ptroNIL \£м^вьиие) THEN IF ptrt.next = NIL THEN poptail:=pop(ptr) E poptaib»poptail(ptrt.next) END; екуоси BEGIN READLN(ch); push(top,ch) END; WRITELN(pop(top)); *>’: BEGIN READLN(ch); pushtail(top.ch) END; используйте эту программу! как программу со с, 149 но попробуйте две новые \ возможности: J +L втолкнуть *L* (или любую букву в стек) т >L втолкнуть букву в стек ] снизу < вытолкнуть из стека < вытолкнуть из стека сниз1 ♦ остановка . ’<’: WRITELN(poptail(top)) END UNTIL ch=’*’ END. 151
93^00 ИЛЛЮСТРАЦИЯ ИСПОЛЬЗОВАНИЯ СТЕКОВ (®/бычные алгебраические выражения можно записывать также в обратной польской нотации - записи без скобок. Нотация «польская», потому что ее предложил польский математик Jan Lukasiewicz; произнести это правильно могут только поляки, по-русски говорят Ян Лукасевич или Лукашевич. Нотация «обратная», потому что в сравнении с исходным вариантом порядок операндов и операций был обращен. Пример обратной польской нотации: А + (В - С) * D - F/(G + Н) преобразуется в ABC-D*+FGH+/- Зычислить обратное польское выражение проще, чем это может показаться. Пусть, например, А=6, В=4, C=l, D=2, F=3, G=7, Н=5. С этими значениями вычисляемое выражение запишется так: 6 4 1 - 2 * + 3 7 5 + / - Просматриваем все элементы слева направо. Как только встречается операция, выполняем ее по отношению к двум предыдущим элементам, заменяя два элемента одним: <^/тот пример призван продемонстрировать, что обратная польская запись может быть весьма полезной при вычислении выражений на компьютере. Итак, с чего начать преобразование выражения вида A+(B-C)*D-F/(G+H)? Здесь используются два стека; последовательность действий поясняется ниже. А + (В - С) * D - F / (G + Н) = /Ч Просматриваем^ыражение слева ►. Операнды складываем в стек X, левые скобки и операции - в стек Y j -j СВстретив правую скобку/отыскиваем\ ;Т стеке соответствующую ей левую. При а\этом все, что сверху - выталкивается г Тмз стека Y и вталкивается в стек X.J ^/продолжаем\^^~^ (заполнение стеков77У\ не помещаем в стек Y —ч. ную операцию, если только {ая ниже операция не имеет более) I приоритет ~ или не оказывается) скобкой. Вместо этого вытал- i элементы из Y и вталкиваем тех пор, пока не встретится .я скобка или дно стека. * их оператор приоритет ♦ 3 (выс- 3 ший) + 2 - 2 ( 1 0 Заметьте, что левая скобка включена в таблицу приоритетов с низким приоритетом. Этот трюк позволяет избежать проверки дополнительного условия «~ или не оказывается левой скобкой». Ловко придумано. 152
WWW выталкиваем, как раньше,) добираемся до дна, потому что знак равенства (разделитель) рассматривается как операция самым низким приоритетом - смотрите таблицу обратная польская} X*J\ ,нотация Наряду с процедурой push(stack,ch) и функциями рор( stack) и poptail(stack) необходима функция, которая бы возвращала приоритет операции. Приведенная ниже функция получает в качестве параметра литеру и возвращает целое в соответствии с таблицей приоритетов: FUNCTION ргес(с: CHAR): INTEGER; BEGIN CASE c OF --- ’/* : prec:= 3; 4 см. маленькую z ’+*, : prec;= 2; M табличку напротив) ’=’ : prec:= 0 END END; IjJa следующей странице приведена программа, которая преобразует выражение в традиционной записи в обратную польскую нотацию. Для использования программы наберите выражение, закончив его знаком равенства: A+(B-C)*D-F/(G+H)= ]<?фвведите^это) ABC-D*+FGH+/- в результату 153
ПРИМЕР ДЛЯ ИЛЛЮСТРАЦИИ ИСПОЛЬЗОВАНИЯ СТЕКОВ (инструкции к использованию - в конце предыдущей страницы) PROGRAM hsilop(INPUT,OUTPUT); { яаксьлоп } TYPE pointertype - t recordtype; recordtype - RECORD next: pointertype; data: CHAR END; VAR ^^^ctSku^u^\ x,y: pointertype; *"" ch: CHAR; i: 0..40; exit: BOOLEAN; ^десьвста^те процедуры ифункщли c^ {предыдущих страниц: push, pop, poptail, precj BEGlN{hsnop} x:- NIL; y:- NIL; ин1ищал1^ци^стеко^ REPEAT ' f READ(ch); IF ch IN [’A’..*Z’] THEN push(x.ch); IF ch - ’(’ THEN push(y,ch); IF ch - У THEN BEGIN WHILE yt.datao’f DO push(x,pop(y)); ch:- pop(y) END; IF ch IN THEN BEGIN REPEAT i exit:- TRUE; IF yoNIL J THEN t IF prec(ch)< prec(yt.dat THEN BEGIN push(x,pop(y)); exit:- FALSE \ END; ______________ вытолкнуть до соответствующей левой скобки затем выкинуть e если приоритет операциях что загружается сверху операции или лево скобки снизу UNTIL exit; push(y.ch) < END UNTIL ch - WHILE xoNIL DO WRITE(poptail(x)); WRITELN; END. ^^тольк^теп ерб будете— правильным втолкнуть в стек новую операцию 1—•* 'печать ттека снизу вверх 154
й’ЕПй МОДЕЛИ ДЛЯ 'ОБХОДА* И 'ВСТАВКИ ПОСЛЕ* (Отличительной чертой стека или очереди является то, что вызов записи означает ее удаление, (В предыдущем примере программа жульничает, глядя иа запись перед тем, как вытолкнуть ее из верхушки стека.) Однако, существует много приложений, в которых последовательные записи извлекаются но не исключаются из цепи. Такая последовательная обработка называется обходом. ЗДиже показана обычная цепь и фрагмент программы для ее обхода, примере «обработка записи» есть не более чем печать одной из ее однако в общем случае это может быть более сложная процедура. В этом компонент, head emp r (tempt, next ^tempt.data ЪГ “СГ { traversal } temp:- head; WHILE tempoNIL DO „BEGIN WRITE(tempt.data); temp:- tempt.next END обработ к ер МОДЕЛЬ ДЛЯ (НЕ РЕКУРСИВНАЯ ) ОБХОДА ^?]тобы вставить элемент после некоторого: { вставка ’В’ после ’N’ temp:- head; WHILE tempt.datao’N нахождения N О МОДЕЛЬ ДЛЯ «ВСТАВКИ ПОСЛЕ* NEW(p); pt.data:- ’В’; pt.next:- tempt.next; tempt. next:-p »я — ue/B ерация 1 затем операция 2 ^Дтобы удалить элемент: 0°^ temp ___как при выталки- вании из стека head eadt { удаление ’N’ } IF headt.data - ’N’ THEN { случай 1 } head:- headt.data ELSE { случай 2 } BEGIN temp:- head WHILE tempt.nextt.datao’N’ DO <_temp:- tempt, next; tempt.next:- tempt.nextt.next END temp tempt tempt.next! [Деуклюже! Если необходимы выборочное уничтожение или «вставка перед», лучше использовать дважды связанные кольца (см. позже), нежели простые цепочки. 155
ПРИМЕР ДЛЯ ИЛЛЮСТРАЦИИ ИСПОЛЬЗОВАНИЯ ЦЕПЕЙ I3CW ЯМТЙШЙИ Задача поиска кратчайшего (или самого длинного) пути через сеть возникает во многих приложениях ~ примером может служить определение критического пути графика работ в техническом планировании. Пусть имеется сеть наподобие изображенной ниже. Задача заключается в поиске кратчайшего пути из узла с пометкой НАЧАЛО к узлу с пометкой КОНЕЦ. Двигаться можно только в направлении стрелок. Число возле каждой стрелки указывает время в пути. Структура данных, необходимая для программы поиска кратчайшего пути, нарисована ниже. Каждому узлу отвечает своя запись и цепь, начинающаяся от этой записи. Каждая такая цепь включает записи с информацией обо всех ребрах, выходящих из этого узла. .head best i me .switch .rout запись для узла(2) .link — .tip 5 .time 8.0 запись для — .link NIL .tip 4 .time 6.0 запись для pe6paQ) —>0) Сдаписи для всех узлов объединены рассматривается запись для узла 2. nodefacts[2].head nodef acts[2]. best i me nodefacts[2]. switch nodefacts[2]. route в массив nodefacts. Ниже более подробно В компоненте bestime находится значение huge (константа, равная 1О20). В компоненте switch (переключатель) - логическое значение; первоначально это - on (вкл). Использование этих элементов поясняется ниже. записи для ребер, выходящих из узла, создаются динамически. В каждой записи есть компонента для хранения связи, компонента для хранения номера узла в nodefacts[2].headt.link nodef acts[2]. head!, t i p nodef acts[2]. headt. t i me кратчайший путь ищется в ходе итеративного процесса. Перед началом процесса 8.0 конце ребра и компонента для хранения времени путешествия по самому ребру. Этот пример - для ребра0 — должны быть сформированы все цепи и во все компоненты должны быть помещены начальные значения; в дальнейшем' они, возможно изменятся. Компонента bestime будет содержать лучшее время достижения данного узла по испробованным к текущему моменту путям. Изначально это время устанавливается столь большим, что первый же возможный путь, каким бы он ни был медленным, это время улучшит. Исключением является начальный узел: в начальном узле, по определению, лучшее время - нулевое. [Доначалу все переключатели устанавливаются в положение on. Установленный в положение on переключатель означает, что ребра, выходящие из этого узла, должны быть еще исследованы (в первый раз или повторно). 156
процесс стартует в начальном узле и вплоть до завершения циклически обрабатывает массив узловых записей. Процесс завершается, когда все переключатели оказываются в положении off. каждом узле производится обход цепи записей ребер. Для каждого ребра в цепи вычисляется время, необходимое для достижения узла на его конце. Для этого к лучшему на данный момент времени достижения исходного узла прибавляется время путешествия по ребру. Результат сравнивается с лучшим временем, которое хранится в узловой записи этого конечного узла. Если новое время лучше, то следует сделать несколько действий, которые нарисованы ниже: .head . best i me .switch • .route 14.0 on I 3 : link ♦" . tip[j5 .time 8.0 лучшее данный момент} время узла 2J [рибавьте „время г путешествия} к^узлу “ узе.л(~5) • head . best i me .switch off? \ .route 27 1 a о чГ80"= :який раз, когда обнаруживается лучший путь к узлу, вместо старого записывается лучшее время и переключатель переводится в положение on, как нарисовано выше для узла 5. Для того чтобы впоследствии можно было проследить найденный путь, используется компонента route, содержащая номер узла, через который проходит найденный путь. Таким образом, в результате обработки ребра, ведущего из узла (2) в узел (б) получается: . head узел(£) . best i me .switch .rout □□осле обхода цепочки ребер, начинающихся во втором узле, переключатель switch узла 2 устанавливается в положение off. Тем не менее в результате обработки узла 2 переключатель в узле 5 был установлен в положение on, поэтому итерации еще не закончены. Процесс продолжается до тех пор, пока все переключатели не окажутся в положении off ~ другими словами, пока в результате цикла по всем узлам не выяснится, что сделать какое-либо улучшение пути уже невозможно. ^узловые записи скомпонованы в массив, а не формируются динамически со связыванием в цепь. Структура массива была выбрана ввиду того, что узловые записи обрабатываются в «случайном» порядке (например, при работе с узлом 2, вам понадобятся также узлы 5 и 4). При использовании массива такие ссылки делаются быстро и просто при помощи варьирования индекса. [^опробуйте выполнить программу для сети напротив. Данные и результаты (предполагается интерактивная работа) должны быть такими, как показано здесь . Число Число Начальный Конечный узлов ребер узел узел j 6 9 3 6 / 3 1 10.0 / 3 2 16.0 / 1 2 5.0 / 1 5 12.0 / 2 4 6.0 [ 2 5 8.0 / 5 6 9.0 / 4 6 10.0 / 4 5 1.0 / Путь из 6 в 3 / 6...4...2...1...3 / Требуемое время 31.0,^х 157
( ПОЛНАЯ ПРОГРАММА ) PROGRAM network(INPUT, OUTPUT); CONST on - TRUE; < huge - 1E20; maxnodes - 30; off - FALSE; nothing -0.0; ; maxedges - 50; TYPE nodetype - 0..maxnodes; edgetype - 0..maxedges; pointertype - tchaintype; chaintype - RECORD link: pointertype; tip: nodetype; time: REAL END; rectype - RECORD head: pointertype; best i me: REAL; switch: BOOLEAN; route: nodetype END; arraytype - ARRAY[nodetype] OF rectype; VAR nodes, startnode, endnode, i, n, tail: nodetype; edges, j: edgetype; edge, p: pointertype; nodefacts: arraytype; cycles: 0..2; try: REAL; массив узловых записей BEGIN WRITELNf Число Число WRlTELNf узлов ребер Начальный Конечный’); узел узел’); READLN(nodes, edges,"start node,endnode); FOR is-1 TO nodes DO WITH nodefacts[i] DO BEGIN f head:- NIL; I bestime:- huge; I switch:- on; \ route:- 0 'END; { WITH } nodef ac ts[st art node], best i me:- nothing; FOR is-l TO edges DO BEGIN (NEW(p); . READLN(tail,pt.tip,pt.time); pt.link;- nodefacts[tail].head; nodefactsftail].heads- p END; инициализация замена времени в начальном узле чтение данных новой записи формирование всех 1епей подключение^, цеп 158
cycles:- 0; ^^^^fneifeducno^ ивеличивается^наК n:- startnode-1; Y поэтому -/ заранее WHILE cycles < 2 DO A BEGIN / cycles:- SUCC(cycles); —^^^опустите c-oni^V^ / n:- n MOD nodes + 1; если так будет яснее» I IF nodef actsfn]. switch - on f THEN BEGIN { IF switch } cycles:- 0; edge:- nodefactsfn].head; WHILE edgeoNIL DO 1 BEGIN { WHILE edge } try:- nodefacts[n].bestime + edget.time; IF try < nodefactsfedget. tip], bestime WITH nodefactsfedget.tip] DO bestime:- try; route:- n; switch:- on END; \ edge:- edge!.link END; { WHILE edge } 1 nodefacts[n].switch:- off \ END { IF switch } END; { WHILE cycles } WITH nodefactsfendnode] DO IF (bestime <> huge) AND (bestime <> THEN BEGIN WRITELNf Путь из’,endnode:3,’ n:- endnode; nothing) B’,startnode:3); [смотрим {J назад на ” предыдущий* узел WHILE п о 0 DO A BEGIN к Г WRITE(n:1^4f n:-nodef ас ts[n]. route; \\ IF n<>0 THEN WRITEf...*) /'END; WRITELN; WRITELN(*Tpe6yeMoe время bestime:6:2) END поля автоматически увеличивается < ширина поля автоматически увеличивается, до 2, если^номер узла состоит из двух цифр апример, 6...4...2...1...3 ELSE WRITELNf Пути нет - или идти некуда’) END. 159
ИЗЯЩНАЯ СТРУКТУРА ДАННЫХ каждая запись дважды связанного кольца содержит указатели вперед .fore и назад: .aft оанн\ 1^*\оступ к записям в кольце упрощается, если добавить к ним еще одну фиктив- ную головную запись, с которой начинается кольцо, как это показано ниже. Такой подход избавляет от необходимости специально проверять, находится ли уничтожаемая запись в начале или попадет ли туда добавляемая запись. ыше изображено кольцо с четырьмя записями и пустое кольцо. 'Оправа - определение записи, подходящей для конструирования кольца. Для простоты в этой записи хранится всего одна литера. 3 главной программе пустое кольцо можно создать, например, следующим образом. NEW(head); head!, fore: = head; head t. aft: = head; TYPE pointertype - t recordtype; recordtype - RECORD fore,aft: pointertype; data: CHAR END; VAR head,temp: pointertype; овую запись можно вставить до или после указанной записи. представлены процедуры, реализующие эти две операции: Ниже 160
PROCEDURE inbefore(VAR old,young: pointertype); BEGIN youngt.fore:» old; youngt.aft:» oldt.aft; oldt.aftt.fore:» young; oldt.aft:» young END^^-v-— ^youngM oldM У*азатели: Г Iе начале young в конце old далекие записи делается просто и красиво: ВСТАВКА young ПЕРЕД операции копирования ГоТ> Idt.aftt youngt oldt PROCEDURE delete(VAR old: pointertype); BEGIN oldt.foret.aft:» oldt.aft; oldt.aftt.fore:» oldt.fore END; oldT.aftT oldt. fore о dt операции old И копирования (®)бход в любом н правлении прост. Единственная трудность - вовремя остановиться. Если цель - обойти кольцо в точности один раз, то начинайте с указателя на первую запись и укажет на фиктивную головную предусмотрите остановку, как только указатель запись (перед выборкой данных из нее). temp:» headt.fore; WHILE tempohead DO f BEGIN I WRITE(tempt.data); \ temp:» tempt.fore 'END; WRITELN ABLE ели заменить программы было «fore» на «aft» в обоих местах, то результатом этого кусочка бы не ABLE, a ELBA. IjQa следующей странице - демонстрационная программа, разработанная с целью изучения принципов и процедур, приведенных на этом развороте. 161 6-458
ПРОГРАМММА-ПРИМЕР ДЛЯ ДЕМОНСТРАЦИИ РАБОТЫ ДВАЖДЫ СВЯЗАННОГО СПИСКА (Следующая программа реализует дважды связанное кольцо, организованное в алфавитном порядке. Чтобы ввести букву, наберите в начале строки +Б ( или + любую букву). Чтобы вычеркнуть букву, введите -Б (или - какую-то букву). Для вывода хранящихся букв в алфавитном порядке введите в начале строки литеру >. Для вывода в обратном порядке - введите <. Для остановки введите в начале строки литеру *. PROGRAM aster(lNPUT, OUTPUT); TYPE pointertype • record type - - t recordtype; • RECORD fore,aft: pointertype; data: CHAR END; VAR ch: CHAR; head,p,temp: pointertype; caps, operators: SET OF CHAR; AC +A TCPA AAPCT -A +A +C PROCEDURE inbefore(VAR old,young: pointertype); BEGIN youngt.fore;- old; youngt.afb- oldt.aft; oldt.aftt.fore:- young; oldt.aft:- young END; PROCEDURE delete(VAR old: pointertype); BEGIN oldt.foret.aft- oldt.aft; oldt.aftt.fore:- oldt.fore END; процедуры inbefore ) u delete - как на предыдущей странице BEGIN caps.- [’А’./Я’]; operators:- NEW(head); headt.fore:- head; headt.aft:- head; headt.data:- CHR(O); REPEAT READ(ch); IF ch IN operators THEN Создание пустого кольца. Чтобы предотвратить аварийную] ситуацию, о которой говорится на следующей странице, поместим фиктивную головную запись/ пустую литеру CHR(O) 162
END. CASE ch OF ' Остерегайтесь возможной > аварии. Условие tempt. data<ch будет проверяться, даже если условие tempohead ложно. Поэтому tempt.data ' не должно оставаться неопределенные в фиктивной записи. Поэтоми^Х - CHR(O) BEGIN READ(ch); IF ch IN caps THEN BEGIN NEW(p); pt.data;- temp:- headt.fore; ***** XWHILE (tempohead) AND (tempt.data<ch) DO 4—temp:- tempt.fore; ------ inbefore(temp,p) END END; ch; вставка BEGIN READ(ch); IF ch IN caps THEN BEGIN temp:- headt.fore; XWHILE (tempohead) —temp:- tempt.fore; IF tempohead THEN delete(temp) END END; AND (tempt, dataoch) DO удаление^ BEGIN temp:- headt.fore; WHILE tempohead DO /BEGIN ( WRlTE(tempt.data); \ temp:- tempt.fore; END; WRITELN END; BEGIN temp:- headt.aft; WHILE tempohead DO / BEGIN ( WRITE(tempt.data); \ temp:- tempt.aft; END; WRITELN END END { CASE } UNTIL ch - ’** { aster } ( вывод*^ гв порядке возрастание вывод । порядке* убывания. работа заканчивается* 163 6*
ЕЩЕ ОДНА КРАСИВАЯ СТРУКТУРА |усть требуется отсортировать несколько букв: D, Z, В, Е, A, F, С [jtJepByio букву (D) запишем в узел, который поместим в корень дерева. (В программировании деревья «узел» добавляем D «корень» растут сверху вниз.) Теперь возьмем следующую букву - Z, и подведем ее к корневому узлу, чем D, поэтому здесь показано, г. Она «больше», идем вправо и создаем, как новый узел для хранения Z. влево Теперь третья буква, В. Она меньше, чем D, поэтому идем и создаем новый узел. добавляем В (Следующая буква - Е. Она больше, чем D, поэтому идем вправо. Она меньше, чем Z, поэтому идем влево. Затем создаем, как здесь показано, узел для хранения Е. общем случае: сравниваем очередную букву с буквой в корневом узле. Если новая буква меньше, идем влево; если больше, идем вправо. Ф*Ы Делаем то же в каждом из достигнутых узлов, пока узлы с буквами для срав- нения не иссякнут. Затем создаем новый узел, в который помещаем новую букву. добавляем добавляв Порядок может быть и обратным, если обходить иначе. добавляем F) 3 любой момент можно обойти (или выпрямить) дерево, как показано ниже. Заметьте, что стрелка проходит через буквы в алфавитном порядке 164
^ип нарисованной напротив узловой записи определяется несложно: TYPE pointertype = t nodetype; nodetype = RECORD left,right: pointertype; data: CHAR END; left .data .right □Подвешивать к дереву буквы ~ напротив этот процесс показан по стадиям ~ лучше всего рекурсивно. Если текущий узел - пустой (NIL), то для хранения новой буквы создается новый узел; в противном случае вызываем процедуру добавления hang с параметром, специфицирующим правый или левый указатель в зависимости от того, как новая буква соотносится с текущей: PROCEDURE hang(VAR nptr: pointertype; ch: CHAR); BEGIN IF nptr = NIL THEN { Случай BEGIN NEW(nptr); nptrt.left nptrt.right: = nptrt.data ;= END ELSE { IF ch < THEN ELSE nptr INIL nptrt.left nptrt.right nptrt.data NIL; NIL; ch VAR существенно: nptr изменяется процедурой NEW(nptr) END; Случай ‘2 } nptrt.data hang(nptrt.left,ch) IF ch > nptrt.data THEN hang(nptrt.right,ch) ELSE WRITELNfCoBnaflaioume буквы’) (§)бход дерева можно выполнить рекурсивно: PROCEDURE strip(VAR nptr: pointertype); BEGIN IF nptr <> NIL THEN BEGIN растягиваем левое подд st ri p( npt rt. lef t); WRITE(nptrt. 6а(а)\^з^ем^имеем^дело^с^ yzjioM, strip(nptrt. right) END END; затем растягиваем правое поддереву разве Л уэто не прекрасно?) 3 обеих приведенных выше процедурах можно использовать «WITH nptrt DO>, сократив число появлений «nptrt> ценой лишних строк и меньшей ясности. Слово VAR в процедуре обхода не обязательно с точки зрения логики работы, однако оно предотвращает копирование структуры данных при каждом обращении копии структуры данных. Охг □Перевернув страницу, вы найдете программу, использующую двоичное дерево. Она считывает набранные в любом порядке буквы и выводит их на экран в алфавитном порядке. Добавление возможности вывода букв в обратном порядке оставлено в качестве упражнения. {^^воичные деревья полезны в работе любого сорта, не только в сортировке. 165
ИЛИ СОРТИРОВКА с помощью ДВОИЧНОГО ДЕРЕВА £/та программа использует двоичное дерево во многом аналогично тому, как программа АСТРА использует дважды связанное кольцо. Чтобы подвесить к дереву новую букву, введите +Б (или + любую букву). Для удаления буквы введите -Б (или минус любую букву). Для вывода букв в алфавитном порядке введите в начале строки символ >. Для остановки введите в начале строки символ ♦. ^\обавление к дереву делается просто и изящно, однако удаление узла, который не является «листом» ~ особенно если на дереве возможны одинаковые элементы ~ отнюдь не просто. В этой программе используются счетчики одинаковых элементов. Если элемент удаляется, значение счетчика уменьшается. PROGRAM monkey(INPUT,OUTPUT); TYPE pointertype - tnodetype; nodetype - RECORD left, right: pointertype; data: CHAR; count: INTEGER END; VAR root,p: pointertype; ch: CHAR; PROCEDURE hang(VAR nptr: pointertype; ch: CHAR); { добавить букву } BEGIN IF nptr - NIL THEN BEGIN NEW(nptr); nptrt.left NIL; nptrt.right:- NIL; nptrt.data ch; nptrt.count;- 1 END ELSE IF ch < nptrt.data THEN hang(nptrt.left,ch) ELSE IF ch > nptrt.data THEN • /J при повторе ни hang(nptrt.right,ch) {^увеличиваем счетчик) ELSE nptrt. COUnt:— nptrt.count + 1 END; _______________ ......................................... _ _ (Следующая функция служит для нахождения подлежащей удалению буквы. Написаная рекурсивно, эта функция основывается на тех же принципах, что и процедура hang. 166
FUNCTION find(VAR nptr: pointertype; ch: CHAR): pointertype; BEGIN _ ____ ____{ найти узел } IF nptr - NIL THEN find;- NIL возвращаем NIL) ELSE IF ch < nptrt.data THEN find:- find(nptrt.left,ch) ELSE IF ch > nptrt.data THEN find:- find(nptrt. right,ch) ELSE find:- nPtr END; vs^tauZ/ш) PROCEDURE strip(VAR nptr: pointertype); { VAR i: 0..MAXINT; BEGIN IF nptr <> NIL THEN BEGIN strip(nptrt. left); ^FOR i;-l TO nptrt.count DO OwRITE(nptrt.data); strip(nptrt. right) END обход дерева } например, если значение X счетчика = 2, печатаем букву дважды; если в счетчике 0, f то ничего не печатаем END; BEGIN { root: — monkey } NIL; REPEAT READ(ch); IF ch IN [’+’/-’/>*] THEN CASE ch OF ’+’: BEGIN READ(ch); IF ch IN [’А’..’Я’] THEN hang(root,ch) END; BEGIN READ(ch); p:-find(root,ch); IF p <> NIL r THEN IF pt. count > 0 then pt.count:— pt.count - 1 END; Vk ’>’: BEGIN /по существу, - strip(root); ( удаление одной буквы WRITELN ' END END { CASE } UNTIL ch - END. 167
жртлвкия По GfQariHiiiKTe программу, которая будет считывать арифметические выражения, наподобие этого: 3.5 * (7 + (4 - 6.2) / 32) и печатать результат. Для считывания чисел и операций, входящих в выражение, воспользуйтесь процедурой типа процедуры grab (с. 128-133). Используйте логику программы построения обратной польской записи (с. 152-154), внеся в нее важное изменение: непосредственно перед переносом операции из стека Y в стек X, сделайте по-другому: • вытолкните из стека X два числа • примените к ним данную операцию • втолкните результат в стек X Если действовать по этой схеме, то в конце концов в стеке X останется единственное число - это и будет значение выражения. So [Запишите приключенческую игру, где игрок исследует волшебный дворец или зловонное подземелье, переходя из одного помещения в другое, подни- мая и ставя вещи, одновременно воюя с монстрами. Для написания такой программы потребуются процедуры обработки строк: ведь игроку необходимо будет получать от компьютера вразумительные ответы на свои запросы типа: ВЗЯТЬ ЯД или ИДТИ НА ЗАПАД Нужные процедуры разработаны в следующей главе. Описание простой и вместе с тем законченной приключенческой игры есть в моей книге: Illustrating Super-BASIC С. U.P. 1985 Там используются кольцевые структуры, чтобы можно было брать предметы в одной комнате и класть их в другой; матрицы состояний - для описания топологии комнат и дверей; таблицы состояний - для кодирования правил игры. Изложенных нами приемов уже вполне достаточно для создания законченной и интересной приключенческой игры. (с. 152) Условие выталкивания текста из стека У в последнем комментарии следует изменить на «пока не встретится левая скобка или дно стека или операция с более низким приоритетом». Программа на с. 154 использует именно это, исправленное условие. 2) (с. 157) Программа, приведенная на с. 158-159, неправильная. Например, на следующий набор исходных данных (приведены только вводимые строки, разделенные точкой с запятой): 5 7 14; 1 2 5.0; 13 3.0; 1 5 1.0; 3 2 1.0; 5 3 1.0; 2 4 1.0; 3 4 5.0 программа печатает ответ: Путь из 4 в 1 4...2...3...5...1; Требуемое время 5.0. Здесь путь найден правильно, а время - нет (правильный ответ - 4.0). Для исправления программы, возможно, достаточно изменить условие WHILE cycles < 2 (третья строка на с. 159) на WHILE cycles < nodes с соответствующим изменением типа переменной cycles. 3) (с. 165) Слово VAR целесообразно убрать из описания параметров процедур обхода; это не приведет к копированию больших структур данных, копиро- ваться будет только один элемент типа pointertype, т.е. указатель. 168
ПРОГРАММЫ ОБРАБОТКИ СТРОК • READSTRING • WRITESTRING • MIDDLE • CONCAT • COMPARE • INSTR • PEEK • POKE ОБРАТНЫЙ СЛЕНГ ( ПРИМЕР ) ТЕХНИКА ХЕШИРОВАНИЯ ( ПРИМЕР ) HASHER ( ПРИМЕР )
МОГУТ БЫТЬ ПОЛЕЗНЫ, даже если в вашем ПАСКАЛЕ ЕСТЬ TYPE STRING 3 стандартном Паскале предопределено не слишком много процедур по обработке строк, и, как следствие, современные компиляторы предлагают еще и ряд нестандартных процедур. Недостатком использования нестандартных процедур является потеря переносимости: программа, написанная для одного компилятора, не будет работать с другим. Эту проблему можно обойти, определив свой собственный набор программ (утилит), которые строятся только из стандартных частей. Именно такое направление и развивается ниже. Конечной целью является скорее иллюстрация предлагаемой методологии, нежели попытка стандартизации строковых утилит. Читателю наверняка понадобится более мощный и лучшего качества набор процедур, чем предложенный здесь. Зсе утилиты используют запись, которая изображена ниже: [^использование динамической памяти снимает ограничения на длину обрабаты- ваемых строк и позволяет строкам иметь различную длину. Ниже представлено определение типов. (Приводится также определение перечисляемого типа, который будет использоваться позже при сравнении строк.) PROGRAM strings(INPUT, OUTPUT); TYPE st ringrange - 0.. MAXI NT; pointertype - t lettertype; lettertype - RECORD next: pointertype; letter: CHAR < END; stringtype - RECORD length: stringrange; head: pointertype END; relation - (eq, ne, gt, ge, It, le); должно бытг\ неотрицательным .letter может содержат^ любую литеру ~ не обязательно буквуJ заменяет отношения <>, > IjJepBbie две процедуры - рекурсивные. Процедура append служит для добавления новой литеры в конец строки; процедура reclaim предназначена для освобождения подзаписей в том случае, когда в запись помещается новая строка. Эти процедуры снижнего уровня» используются основными строковыми утилитами. Программисту, который пользуется последними, нет нужды знать о процедурах нижнего уровня. 1уо всех процедурах параметры, обозначающие строковые записи, сделаны параметрами-переменными. Смысл этого в том, чтобы процессор не создавал копий строк ~ которые могут быть очень длинными. 170
PROCEDURE append(VAR р: pointertype; с: CHAR); BEGIN IF р - NIL THEN BEGIN NEW(p); pt. letter;- c; pt.next:- NIL END ELSE append(pt.next, c) END; рекурсия PROCEDURE reclaim(VAR p: pointertype); BEGIN IF p <> NIL THEN BEGIN IF pt.nextoNIL THEN reclaim(pt.next); DISPOSE(p); p:— NIL END END; рекурсия |редставим себе строку с именем st: <^VARst?stringty^e?V К st. length 2 st. head I» ____ 3 результате работы процедуры appendfst.head,’!’) будем иметь: st. length 2 st. head » h (процедурой append г не изменяется А 3 результате работы процедуры reclalm(st.head) будем st. length 2 st. head NIL не установлено в нуль NIL ! иметь: куча Щ}иже нарисована пустая строка. Любую строку надо обязательно инициализи- ровать, прежде чем ее можно будет использовать в последующих процедурах. Для этого можно написать формальную процедуру, но не стоит тратить усилия - это лишь усложнит дело. st. length st. head /st. head ;- NIL;) hst.length ;- 0;j ТАК НАДО ИНИЦИАЛИЗИРОВАТЬ . ЭТО - ПУСТАЯ СТРОКА СТРОКУ 171
(имястроки) ПЕРЕД ВЫЗОВОМ ПРОВЕРЯЙТЕ УСЛОВИЕ EOLN <3/та процедура считывает строку и помещает ее в память под указанным именем. Параметром может быть как пустая, так и непустая строка: предыдущее содержимое строки теряется. Обращение к процедуре с неинициализированной строкой является ошибкой. Предполагается, что строка завершается пробелом или символом EOLN (т.е. нажатием клавиши RETURN). Пробелы перед строкой этой процедурой игнорируются. PROCEDURE readstring( VAR newstring: stringtype); CONST space - 1 VAR ch: CHAR; BEGIN reclaim( newstring. head); newstring. length:» 0; REPEAT f READ(ch); ' UNTIL (chospace) OR EOLN; IF chospace THEN REPEAT (append( newstring. head, ch); newstring.length:» newstring. length ch:- space; IF NOT EOLN THEN READ(ch) UNTIL ch=space END; -v^e^trcrpoKfrnewstring пуста, то процедура reclaim ничего не ' делает начальные пробелы пропускаются счетчик*^ числа букв + 1; НИЧЕГО HE ДЕЛАЕТ С ПУСТОЙ СТРОКОЙ □Приведенная ниже процедура печатает копию указаной строки, не содержащей пробелов в начале и конце строки, а также литер конца строки. Если заданная строка - пустая, то процедура ничего не делает. PROCEDURE writestring(VAR oldstring: stringtype); VAR p: pointertype; BEGIN p:-. oldstring.head; WHILE poNIL DO BEGIN WRITE(pt. letter); p.’» pt. next END END; 172
^имяновойстроки* имястаройстроки’ начало> числолитера ^)та процедура создает новую строку, копируя некоторую часть из середины исходной строки. Новая строка делается из «середины» старой строки. Новая строка содержит заданное число литер и начинается с символа, который находится в указаной позиции. Смысл параметров лучше всего объяснить на рисунке: middle( new, old, 4, 7 ) число литер = DEFGHIJ 1234 ABCDEFGHIJKL строка с именем «old» результат: новая строка с именем «new» [Процедура middle моделирует популярную в языке Бейсик команду MID$(, , ,). * Четвертый параметр может оказаться слишком большим. В этом случае новая строка обрывается на том месте, где кончается старая строка. Процедуру можно использовать для копирования строк целиком. Новая строка может быть записана на место старой. PROCEDURE middle(VAR newstring,oldstring: stringtype; start,span: stringrange); VAR i: stringrange; p,temp: pointertype; BEGIN IF (start>0) AND (start<»oldstring. length) THEN BEGIN temp:= NIL; p:= oldst ring, head; i:= 1; WHILE i<start DO zBEGIN Л p;= pt. next; 1 i:»SUCC(i) \END; i:= 1; WHILE (poNIL) AND (i<=span) DO z BEGIN f append(temp, pt. letter); I p:» pt. next; пробегаем док начала - «start» отбрасываем^хвост, ?сличислд\ литер, «span», слишком велико) 'END; newstring.length:» i-1; reclaim(newstring. head); newstring. head:» temp; END END; формируем результат во временной у \^строке, temp /очищаем пространство^^ затем подставляем указателе на временную строку 173
^имяновойстроки* имялевойстроки’ имяправойстроки) ^)та процедура создает новую строку, получаемую в результате копирования двух указанных строк одна за другой ~ другими словами, в результате их конкатенации. Указание для конкатенации левая и правая строки остаются нетронутыми, если только новая строка не будет записана на место одной из них. PROCEDURE concat( VAR newstring, left, right: stringtype); VAR p,temp: pointertype; BEGIN temp;- NIL; p:- left.head; WHILE poNIL DO z BEGIN Г append(temp, pt. letter); \ p:— pt. next \ END; p;— right, head; WHILE poNIL DO z BEGIN f append(temp, pt. letter); \ p.- pt. next ' END; newstring.length:- left.length + right.length; reclaim(newstring. head); newstring.head:- temp; END; Следующая функция предназначена для сравнения строк. Критерий сравнения тот же, что используется при составлении алфавитных каталогов. Прописные буквы полагаются «равными» соответствующим строчным буквам. Строки считаются равными, если они одинаковой длины и все литеры в них слева направо попарно равны: AbCd считается равной строке aBCd Зсли строки не равны, то порядок их следования в каталоге определяется первой с левого конца несовпадающей литерой. Большей считается строка, в которой эта литера имеет большее порядковое значение. AbCdg считается больше, чем строка aBCdefg Зел и одна строка короче другой, то к более короткой припишите «пустые» (nul) литеры с нулевым порядковым снова действует правило, приведенное выше: строке мысленно значением. Теперь AbCde считается больше, чем строка аВСи-^-х—^воображаемая) 174
'строки' кРит- имя строки le IF 7ompare(res^onc?*^eq^ [F compare(left, ge, right) THEN ... критерий сравнения ФУНКЦИЯ ВОЗВРАЩАЕТ ЛОГИЧЕСКОЕ ЗНАЧЕНИЕ'. TRUE ИЛИ FALSE Перечислены на с. 170 eq, пе, gt, ge, It, firm FUNCTION,cdmpare(VAR left: stTirigtype^r:~relatiortj VAR right: stringtype): BOOLEAN; VAR cp,cq: CHAR; same,pmore,qmore: BOOLEAN; p,q: pointertype; FUNCTION upper( c: BEGIN IF c IN [’a’..’z’] THEN CHAR): CHAR; при сравнении строку любая строчная буква обрабатывается как^ прописная ь-S • upper:- CHR(ORD(c)-ORD(’a’)+ORD(’A’)) ELSE upper- с END; BEGIN { compare } p :- left.head; pmore :- poNIL; same :- TRUE; WHILE (pmore AND qmore) AND same DO BEGIN 4 cp :- upper(pt. letter); ( cq :- upper(qt. letter); same :- cp-cq; i p :- pt.next; pmore poNIL; \ q :- qt.next; qmore :- qoNIL ^END; IF (same AND qmore) AND (NOT pmore) THEN cp.- CHR(O); IF (same AND pmore) AND (NOT qmore) THEN cq:- CHR(0); Литера CHR(0) обязана" быть меньше, чем любая другая, с которой она сравнивается CASE r OF eq: compare :- cp - cq; ne: compare :- ср <> cq; gt: compare :- cp > cq; ge: compare cp >» cq; It: compare ••- cp < cq; le: compare :- cp <- cq END { CASE } END; { compare } 175
юта (имястроки* имяподстроки^ ФУНКЦИЯ ВОЗВРАЩАЕТ ПОЗИЦИЮ СОВПАДЕНИЯ ~ ИЛИ НУЛЬ, ЕСЛИ СОВПАДЕНИЯ НЕТ ^)та функция моделирует популярную функцию языка Бейсик. Она ищет первое вхождение подстроки в строку и возвращает номер позиции, в которой начина- ется подстока. Номера изменяются с 1. Если вхождений нет, то возвращается нуль. подстрока cD У^^пдЁлед^ FUNCTION instr(VAR super,sub: stringtype): stringrange; VAR tempstring: stringtype; i,j: stringrange; match: BOOLEAN; BEGIN instr := 0; tempstring, head — NIL; i 0; j super.length - sub.length + 1; IF j >= 1 THEN BEGIN z REPEAT 7 i := SUCC(i); I middle(tempstring,super, i,sub. length); \ match: = compare(tempstring,eq,sub) x UNTIL match OR (i=j); IF match THEN instr= i; \^s'6pcu& reclaim(tempstring.head); co c END END; из строки super, начиная су текущей позиции, извлекаем короткую временную строку i , п строки' позиция* ФУНКЦИЯ ВОЗВРАЩАЕТ п-Ю ЛИТЕРУ <^)та функция возвращает литеру из n-й позиции указаной строки, или CHR(O), если п больше длины строки. FUNCTION peek(VAR old: stringtype; n: stringrange): CHAR; VAR i: stringrange; p: pointertype; BEGIN p old.head; i 1; WHILE (i<n) AND (poNIL) DO 4 BEGIN ( i := SUCC(i); \ P := pt. next END; IF poNIL THEN peek := pt.letter ELSE peek := CHR(O)' END; ABCD’ ПРИМЕР peek(str,2) возвращает ’В’ данная позиция больше длины - строки 176
(имя строки’ ^позиция* слитера) ЗАМЕНЯЕТ п-Ю ЛИТЕРУ НА с |роцедура может делать различные действия: > если 1 п длины строки, то процедура заменяет данной литерой п-ю литеру указаной строки: POKE(str,3,c) asca если п - 0, то данная литера вставляется в начало: строка POKE(str,0,’P’) 'строкаЪ^. Pasca • если п > длины строки, то данная литера добавляется в конец: строка Pasca POKE(str,6,T) Pascal Цаким способом из пустых строк можно строить строковые «константы». Для длинных строковых констант лучше написать процедуру построения строк из строковых констант Паскаля, присваиваемых упакованным массивам литер. PROCEDURE poke( VAR old: stringtype; n: stringrange; c: CHAR); VAR p: pointertype; i: stringrange; BEGIN IF n > old. length THEN BEGIN append(old.head,c); old. length: = old. length + 1; END ELSE IF n = 0 THEN BEGIN NEW(p); pt.next:= old. head; pt. letter:- c; n > длина; v-y . добавляем n - 0; вставляем в начцль END; old. head: = p; old.length:= old. length + 1 END ELSE BEGIN p:= old. head; i:- 1; WHILE (i < n) AND (p <> NIL) DO BEGIN T i:- SUCC(i); \ p:= pt. next Vend IF p <> NIL THEN pt.letter- c END 177
Isthay isay Ackslangbay! Ancay ouyay eadray itay? Erhapspay otnay atay irstfay. б ратный сленг - это секретный язык, на котором разговаривают в школах. Он совершенно непонятен, когда слышишь его впервые, однако, им легко овладеть, если знаешь грамматические правила. Возможно, существует много диалектов обратного сленга (его еще называют pig Latin); этот запомнился еще со школьной скамьи. В каждом английском слове делается циклическая перестановка относительно первой гласной буквы и в конце добавляется ay (tea —» eatay, tomato —> omatotay). Если слово начинается с гласной, то осью вращения становится вторая гласная (item —> emitay). Если второй гласной нет, то ничего не переставляется ( itch —> itchay). Две гласные в начале слова воспринимаются как одна гласная (oil —> о Нау, а не iloay; earwig —» igearway, а не arwigeay). Прописную букву в начале слова следует преобразовать (Godfather —> Odfathergay, а не odfatherGay). Буква и, следующая за буквой q требует специальной обработки (Queen —> Eenquay, а не ueenQay). Знак пунктуации, завершающий слово, так и остается в конце (Crumbs! —> Umbscray! ,а не Umbslcray). ^Огобы все это нормально работало, входной файл нижеследующей программы должен до самого конца вводиться без нажатия клавиши RETURN. Вводите текст строчными буквами, используя, где это нужно, прописные. После каждого знака пунктуации, но не перед ним следует ставить пробел. Кавычки, как двойные так и одинарные, не обрабатываются, и поэтому их следует опустить. Знаки пунктуации внутри слов, такие, как апострофы, трактуются как согласные. [^опробуйте выполнить программу со следующим входным файлом. Программа должна зашифровать текст и получить результат, представленный в самом верху этой страницы: This is Backslang! Can you read it? Perhaps not at first. PROCEDURE colossus; VAR puncmark: CHAR; recap: BOOLEAN; btm: 2..3; fold,k,quin: stringrange; offset: INTEGER; word,fore,aft,qu,ay: stringtype; Ze действительности, гцель этого примера показать использование ^средств обработки строк, разработанных на предыдущих страницах BEGIN word, head— NIL; word, length— 0; fore.head:= NIL; fore.length— 0; aft. head- NIL; .......... ‘ ay. head: = NIL; qu.head— NIL; aft. length— 0; ay. length— 0; qu. length— 0; 1 инициализация всех строковых \пе ременных!^ offsets ORD(’a’) - ORD(’A’); «строковые констант#» poke(ay,l,’a’); poke(ay,2,’y’); poke(ay,3,’ poke(qu,0,’u’); poke(qu,0,’q’); ’ay* и ’qu’ 178
WHILE NOT EOLN DO BEGIN a readstring(word); Г recap:-peek(word, 1) IN [’A’..’Z’]; ___________ I ^THEN начальна^ква "#пРоп™шя^^лаем ee строчной | poke(wo^,irCHR(ORb(peek(word,l)j + offset)); ---- I IF NOT (peek(word,word.length) IN [’A’..’Z’,’a’..’z’J) I THEN BEGIN puncmark:- (peek(word,word.length); IF word, length - 1 THEN poke(word,0,’ ’); middle( word, word,!, word, length-!) END ELSE /если последняя литера не является puncmark:- CHR(O); ( буквой, то запоминаем ее как in!, inctr/wnrd niiV Ч ^ё^л1/'^бвд/с^Э^жи:^ ’qu^ то<^ ^заменяем это сочетание наJq*j quin:- instr(word.qu); IF quin > 0 THEN poke(word,quin+l,’*’); IF peek(word.l) IN [’A’,’a’,’E’,’e’,’I’,’i’,’O’,’o’,’U’,’u’] THEN btm:- 3; ELSE btm:- 2; A. t , или fold:- 1; FOR k:- word, length DOWNTO btm DO IF peek(word.k) IN [’A**/a’,’E’,*e\’r,’i’/O’/o’,’U’,’u’] THEN fold:- k; IF quin > 0 THEN poke(word, quin+1, ’ u’); middle(f ore, word, fold, word, lengt h-f old+1); middle(aft,word,!,fold-1); concat(word, fore, aft); concat( word, word, ay); IF puncmark <> CHR(O) THEN BEGIN poke(word, word, length, puncmark); poke(word,l+word.length,’ ’) END; <- IF recap AND (peek(word,l) IN [’a’..’z’]) THEN poke( word, 1, CHR(ORD(peek( word, 1)) - offset)); writestring(word) END; { WHILE } WRITELN END; { colossus } earwig^ btik-3 r восстанавливаем и после q переставляем слово; добавляем ’ау’/' если был Унак, пунктуации, та\ добавляем его^ восстанавливаем, если\ необходимо, прописную ^^букву^^^^ BEGIN { strings } colossus END. { strings } главная программа 179
МЕТОД БЫСТРОГО ПОИСКА □С>ак вы ищете слово в списке? FOR i =l ТО 9 DO list [1] Tr- Простейшее решение - просмотреть list 2‘ ТГ список сверху вниз и, найдя /BEGIN list :з: TT совпадение, что-либо предпринять. IF list[i]-’C’ list s4: “T“ Здесь приведена несложная программа THEN list :5: ТГ для поиска буквы ’С’ в списке букв. \ WRITELNfC B’,i); list :6: ПЕГ В таком подходе нет ничего плохого, \ lastposition;= i; ’ list T ТГ если только список достаточно 'END list :8: короткий. list 9: лг ^рюк в работе с длинными списками состоит в том, чтобы ухитриться сразу попасть в нужное место. В списке латинских букв, имеющем длину 26, техника поиска очевидна. Такой список надо располагать в алфавитном порядке так, чтобы при поиске, например, буквы ’С* надо было бы смотреть элемент list[3]. Для поиска произвольной буквы х надо будет проанализировать элемент list[ORD(x) - ORD(’A’) + 1]. Выражение ORD(x) - ORD(’A’) + 1 в математи- ческой терминологии называется функцией от х. Эта функция возвращает правильный адрес для произвольной буквы х. (®/днако в случае списка слов обеспечить уникальный адрес для каждого мыслимого слова невозможно. На практике используется следующее решение: устанавливается предельная длина списка и выбирается функция (похожая на ту, что рассматривалась выше), которая дает вероятный адрес искомого слова. Такая функция называется хеш-функцией. 45еш-функция напоминает по внешнему виду и поведению функцию, генерирующую случайные числа. Генераторы случайных чисел используют операцию MOD для того, чтобы привести случайное число в определенный диапазон. Подобно этому, в хеш-функции операция MOD используется для того, чтобы адрес находился в пределах длины списка. Приведенная ниже хеш-функция получена из функции, предложенной Керниганом и Плоджером. Зозьмем слово ANT, которому надо найти место в списке из 17 компонент - с О по 16. Хеш-функция использует порядковые значения букв. В примере использован код ASCII, хотя метод работает и с другими кодами. порядковое значение • 3 х перенос складываем сумма MOD 17 О «А» «N» «Т» 65 78 84 &* ° А^42 ><65 ^><^120 14^ 2- таким образдМх ANT относится? к элементу list[2] Zy *см. литературу этим алгоритмом, слово AARDVARK породит код 7 и будет, ’начальное значений 19 соответствии с следовательно, отнесено к элементу list[7]. Из сравнения этих двух адресов ясно, что хеш-коды не располагают слова в алфавитном порядке. Хеширование слова STOAT дает код 2, разрешаются при помощи пересекающийся с кодом слова ANT. Подобные коллизии метода, рассматриваемого на противоположной странице. д]исло 3 - «магическое число»; можно также попробовать 5, 7 или другое небольшое простое число. Длина списка (17 в этом примере) для большей эффективности также должна быть простым числом. Под «большей эффективностью» понимается более равномерное распределение хеш-кодов по списку, с тем чтобы не было скученности. На с. 182 приведена программа, демонстрирующая рассмот- ренную хеш-функцию. Испытайте ее и посмотрите, не скучиваются ли коды (у меня - не скучивались). 180
\£>писок должен быть круговым, с тем чтобы при достижении переменной hash последнего значения увеличение ее на 1 возвращало бы hash в нуль. Вместе с этим должен быть предусмотрен механизм, предотвращающий зацикливание при поиске в случае заполненного списка. [JlJporpaMMa на следующей странице разработана для демонстрации метода хеширования. При работе с ней просто вводите слова. Каждое новое слово помещается в список, после чего список полностью выводится; при этом видно, куда помещено слово. Если слово оказывается «старым», то сообщается его местонахождение. Изначально программа предполагает, что данное слово - «старое» и начинает его поиск. Если поиск оказывается безуспешным, то программа запоминает данное слово как «новое». IJlJporpaMMa использует строковые утилиты, которые рассмотрены ранее ~ поэтому прописные буквы рассматриваются как равные соответствующим строчным: ANT = Ant. ииспользуется структура данных в виде массива указателей, указывающих на записи типа stringtype. Массив указателей должен быть объявлен и инициали- зирован. Остальные данные формируются динамически. list [0] 1 INILl list [1] 2 list 2] Kim list 3] NIL list [4] NIL list ъ] NIL list [14] NIL list [15] list [16] NIL .head L*J "^.next |*T^rTNil .length 0 .letter Ш LN] Lf .head . length .letter 181
ПРИМЕР ПРОГРАММЫ ДЛЯ ДЕМОНСТРАЦИИ ХЕШИРОВАНИЯ ^Приведенная здесь программа основана на принципах, разобранных на предыдущем развороте. Для использования программы просто вводите слова и наблюдайте за экраном, чтобы увидеть, куда они записываются. Введите какие-нибудь слова, которые уже были введены, и обратите внимание, что дубли не записываются: вместо этого сообщается их местонахождение. PROGRAM hasher(INPUT,OUTPUT); Здесь вставьте . объявления и утилиты обработки строк, которые приведены на с. 170-177 (таким образом, процедура colossus и основная программа со с. 178-179 не нужны). Процедура хеширования не обращается к процедурам middle, concat, instr и poke, которые также могут быть опущены. Procedure hashplay; CONST size - 17; siz - 16; TYPE «а единицу меньше size4 sizerange - O..siz; nametype - t stringtype; arraytype - ARRAY[sizerange] OF nametype; VAR name: stringtype; i, hash,recall: sizerange; full,found,ahole: BOOLEAN; list: arraytyffe; n: INTEGER; на экран PROCEDURE show; VAR i: sizerange; BEGIN FOR b-0 TO siz DO IF Hst[i]oN!L THEN BEGIN WRITE(i,’ wri testring(list[i]f); WRITELN END WRITELN(i,’** END; BEGIN { hashplay } name.head:- NIL; full:- FALSE; FOR b-0 TO siz DO 182
readstring(name); hash;=O; FOR i—1 TO name, length DO /BEGIN T n:= ORD(peek(name,i)); / IF n IN [ORD(’a*)..ORD(’z’)] THEN ! n— n - ORD(’a’) + ORD(’A’); \ hash— (3*hash + n) MOD size; 4END; ahole:= list[hash]=NIL; IF NOT ahole THEN BEGIN recall— hash; REPEAT found— compare(name,eq,list[hash]t); IF found THEN WRlTELNf Найдено B’,hash:4); ELSE „ BEGIN Z3 hash--= (1 + hash) MOD size; ahole:= list[hash]=NIL; full— hash=recall END UNTIL (ahole OR found) OR full _S END; с целью сравнения^— строчные буквы заменяем хеш-функция увеличиваем hash на 1 1 IF ahole THEN BEGIN NEW(list[hash]); list[hash]t:= name; WRITELN(’3anHcaHO B’,TTash:3)7 WRITELN; show; name, head— NIL END ( ELSE IF full > THEN BEGIN WRITELNfList full*); show; END UNTIL full Tist[hash] g name •head Ц Важный момент! Если этого\ не написать, то процедура А readstring сотрет посредством । DISPOSE слово, на .которое указывает nameu —s END; { hashplay } BEGIN { main program } hashplay; END. { main program } 1 Оператор name.head;= NIL по существу стирает • слово, только что записанное в список, и, таким образом, не решает проблему, отмеченную в комментарии к этому оператору. Чтобы обеспечить сохранение слова, следует перед присваива- нием NIL скопировать хотя бы одно первое звено цепи (так, как это показано на предыдущей картинке, но не так, как записано в программе). - Прим. ред. 183
AMiawwA BSI Specification for Computer programming language Pascal BS6192: 1982 Британский стандарт, который определяет тот же диалект Паскаля, что представлен в моей книге. Стандарт BS6192 не предназначен для чтения лежа в постели, однако если вам понадобится выяснить точный синтаксис или поведение Паскаль-процессора в каких-либо редких ситуациях, то стандарт BS6192 - как раз то, что нужно. В его предисловии предпринимаются попытки объяснить сложную взаимосвязь между BS6192 и ISO 7185, но мне пока не удалось расшифровать этот текст. По-видимому, предполагалось, что эти стандарты будут определять Одно и то же, но фактически это не совсем так. Jensen, К. & Wirth, N. (1975). Pascal user manual and report. (Springer- Verlag). (Русский перевод: К- Йенсен, Н. Вирт. Паскаль. Руководство для пользователя и описание языка. - М.: Финансы и статистика, 1982.). Это первая книга по Паскалю; один из ее авторов, Никлаус Вирт, является создателем языка. Руководство для пользователя, написанное Кэтлином Йенсеном, являясь примером простоты и точности, вошло в историю. Grogono, Peter (1980). Programming in PASCAL (Addison-Wesley). (Русский перевод: П. Грогоно. Программирование на языке Паскаль. - М.: Мир, 1982.) Это - классика. Впервые книга была опубликована в 1978 г. путем воспроизве- дения компьютерной распечатки.Теперь же она и отлично отпечатана. Это до сих пор лучшая из всех книг, содержащих полный курс программирования на Паскале, которую мне доводилось видеть. Книгу отличает ясное изложение и образные примеры. Чтобы взять все из этой книги, вам потребуется много работать и смело погружаться в длинные примеры. Для читателей, которые захотят пойти еще дальше, Грогоно приводит обширную и представительную библиографию. Brown, P.J. (1982) Pascal from BASIC (Addison-Wesley) Хороший самоучитель, простой для понимания, в котором, увы, не удалось избежать ряда недоразумений. Книгу населяют странные персонажи - проф. Примпл (архаический академик) и Билл Мадд (неопытный, но полный энтузиазма), призванные продемонстрировать различные точки зрения на программирование. Вместе с тем наг учат простить им их предвзятую позицию. Структуры данных и динамическая память рассматриваются кратко^ Эта книга должна помочь бывшим приверженцам Бейсика, сохранившим старые привычки, перестроиться на Паскаль. Kernighan, B.W. & Plauger, P.J. (1981) Software tools in Pascal (Addison- Wesley) Книга полна практичных и проверенных программ на Паскале. Фразу «Картинка стоит тысячи слов* вы найдете под одной из двух картинок во всей книге; остальное - 95 тысяч слов текста. Проза, которую я читал с чувством скуки, наградит вас за упорство обилием и еще раз обилием информации. 184
(Стандартные процедуры и стандартные функции перечислены в алфавитном порядке. Справа приводятся номера страниц, на которых рассматривается соответствующая процедура или функция. Обзор синтаксиса построен по принципу сверху вниз. ЕСЛИ ИМЯ ФАЙЛА НЕ УКАЗАНО, ТО ПОДРАЗУМЕВАЕТСЯ INPUT ИЛИ OUTPUT В15РО5Е(ил<я^казателя) • возврат ненужной записи в кучу ОЕТ(МЛя^айла) • продвижение окна указанного входного файла 149 135 NEW(«^ указателя^ • создание новой пустой записи 149 РАСК(имямассива1, индекс массива1, имямассива2) • упаковка содержимого одного массива и запись рАОЕ(«ля^айла) в другой • запись в указанный выходной файл литеры конца страницы (если таковая распознается при печати) 98 126 • продвижение окна указанного выходного файла риТ(ИЛя0айла) READLN чтение из указаного файла; элементы файла типа TEXT разделяются пробелами или признаками новой строки • то же, что READ, но только для файлов типа TEXT: когда прочитан последний параметр, переход на следующую строку ввода 135 127 127 RESET(wjh^файла) • подготовка указанного файла для чтения 124 (никогда не делайте reset для INPUT и rewrite для OUTPUT) ^Я\^имяфайла) 9 подготовка указанного файла для записи 124 иМРАСК(«з<яжассива2. имямассида1, индексмассида1) 9 обратная для РАСК 98 126 126 t Параметры ширина и точность - выражения целого типа. Параметр точность используется только для печати выражений типа REAL. 185
В ВАШЕМ ПАСКАЛЕ, ВЕРОЯТНО, БОЛЬШЕ ФУНКЦИЙ, ЧЕМ ЗДЕСЬ ОПИСАНО ©уква / означает выражение, которое приводится к целому значению; буква г - выражение, которое приводится к вещественному значению; буква т означает параметр, имеющий порядковое значение: например, целое, литеру или элемент перечисляемого типа. ABS(Z) • абсолютная величина'. ABS(-6) возвращает 6 (целое) 46 ABS(r) • абсолютная величина'. ABS(-6.5) возвращает 6.5 (веществ.) 46 ARCTAN(r) • арктангенс'. ARCTAN(l.O) возвращает 0.785398 ( тг/4 ) 47 CHR(Z) • литера: в кодировке ASCII CHR(65) возвращает ’А’ 51 COS(r) • косинус: COS(3.141593/3) возвращает 0.5 47 • возвращает TRUE, если окно - в конце файла EOF (имяфайла) (процедура READ не сработает еще раз) 49 EOLN (имя • возвращает TRUE, если последующий вызов процедуры файла) READ прочитает пробел, означающий конец строки 49 ЕХР(г) • экспонента или натуральный антилогарифм: ЕХР(1) возвращает 2.7182818 (т.е. а1) 46 LN(r) • натуральный логарифм: LN(2.7182818) возвращает 1 (т.е. /п(е) ) 46 ODD(Z) • нечетность: ODD(-3) возвращает TRUE, ODD(0) - FALSE 49 ORD(/n) • порядковое значение: в коде ASCII ORD(’A’) возвращает число 65, ORD(TRUE) - 1, ORD(FALSE) - 0 50 PRED(m) • предшественник: PRED(’B*) возвращает ’A’, PRED(6) возвращает 5, PRED(TRUE) возвращает FALSE 51 ROUND(r) • округление до ближайшего целого: ROUND(3.5) возвращает число 4, ROUND(-3.8) - число -4 48 SIN(r) • синус: SIN(3.141593/6) возвращает 0.5 47 SQR(Z) • квадрат: SQR(-3) возвращает 9 (целое) 46 SQR(r) • квадрат: SQR(-3.0) возвращает 9.0 (вещественное) 46 SQRT(r) • квадратный корень: SQRT(81) возвращает 9.0 (вещественное) 46 SUCC(m) • следующий: SUCQ’A’) возвращает ’В’, SUCC(5) возвращает число. 6, SUCC(FALSE) возвращает TRUE 51 TRUNC(r) • отбрасывает дробную часть: TRUNC(-3.8) возвращает число -3 (целое) 48 186
ОБЗОР СВЕРХУ ВНИЗ. СИСТЕМА ОБОЗНАЧЕНИЙ ПРИВЕДЕНА В ГЛАВЕ 3 блок программа VAR 4 имя имятипа параметры тип FUNCTION имя параметры^: имя„п к к типа PROCEDURE имя параметры имя^,^ типа дискретный типа PACKED SET OF перечисление ARRAY [ ^перечисление, ] OF тип RECORD поля вариант END FILE OF тип дискретный имя ( .имя .) константа.. константа 187
( ПРОДОЛЖЕНИЕ ОБЗОРА ) оператор •- переменная := выражение имяфун= вЬ('Ражение имя^' проц выражение имя . Фу* BEGIN ^оператор . END IF условие THEN оператор ELSE оператор REPEAT итераторUNTIL условие WHILE условие DO оператор FOR имя п t;= выражение 15оу7йто выражение Пврвм DO оператор CASE выражение 0FK ^константа^ : оператор END WITH .переменная DO оператор GOTO цифры компаратор выражение :: которое приводится к значению типа BOOLEAN условие :;= выражение £ исключение > в процедурах WRITE и WRITELN выражения могут записйваться как ^выражение. ' 188
Член имя КОНСТ число NIL строка переменная имя компаратор список v------ ЗАРЕЗЕРВИРОВАННЫХ СЛОВ AND FORWARD PROCEDUR1 ARRAY FUNCTION PROGRAM 5 BEGIN GOTO RECORD J CASE IF REPEAT < CONST IN SET J DIV LABEL THEN / DO MOD TO \ DOWNTO NIL TYPE J ELSE NOT UNTIL / END OF VAR \ FILE OR WHILE J FQR A PACKED . WITH/ строка ::= * имя буква MOD AND OR буква цифра литера пробел { } переменная константа буква цифра цифры, число : ^Цифр$,' Цифры . Цифры Е .имя t число имя конст строка 189
КРАТКУЮ СПРАВКУ О СИНТАКСИСЕ, СТАНДАРТНЫХ ПРОЦЕДУРАХ И ФУНКЦИЯХ СМ. НА С. 185-189 А аргументы 46 астра (пример) 162, 163 в базовый тип 84, 90 безопасное чтение (пример) 128-133 блок-схемы 27, 54, 55 буквы 34 буферизация 142 былая слава (пример) 29 быстрая сортировка (пример) 96, 97 3 возврат 148, 171, 183 выражения 24 - логические 43 f геометрические фигуры (пример) 27 двоичные деревья 164-167 динамическая память 146, 147 3 заголовок программы 20, 21, 38 заем (пример) 25 записи 110-119 зарезервированные слова 21, 32 — , список 189 а имена - полей 110, 111 - , синтаксис 35 - , эквивалентность 91, 98 индексы 90 интерактивный режим 140-143 интервальный тип 83 и клавиатура 15 код ASCII 50, 99, 125 кольца связанные 160-163 команды 16, 17 компаратор 34, 45 - IN 45 компараторы для множеств 85 компиляция 14-16 компоненты записей 110 - массивов 90 конец строки 123, 140, 141 - файла 143 конкатенация 174 константа NIL 147 константы 13, 22, 23 - строковые 99 константы-указатели 147 кратчайший путь (пример) 156-159 Л литерный тип. (CHAR) 23 литеры 23 логические - выражения 24, 26, 43, 45 - значения 26 логический тип (BOOLEAN) 23 маляр (пример) 12-14 массив - литер 99 - упакованный 98 массивы-параметры - настраиваемые 106, 107 многоугольник (пример) 92 множества 84, 85 м-у-у-у (пример) 87 © обозначения 33 обратная польская нотация 152-154 обратный сленг (пример) 178-179 объявление - констант 20-23, 38, 80 - меток 37, 55 окно файла 123, 134, 141 оператор - AND 42 - DIV 42 - MOD 42 - NOT 36 - OR 42 операторы 20, 37 операции 24, 34, 42, 43 - отношения 34, 45 определение процедуры (PROCEDURE) 69 - функции (FUNCTION) 64, 65 отложенный ввод 140, 143 отступы 21 очереди 150, 151 190
и пакетный режим 16, 140 параметры - , синтаксис 38 тип 80 - фактические 64, 65 - формальные 64, 65 параметры-переменные 68, 69 параметры-значения 68, 69 параметры-функции 73 пересечение множеств 85 персональные записи (пример) 112—115 площадь бака (пример) 12—14 площадь многоугольника 92 побочные эффекты 76 поля вывода 26, 29, 426 порядковые значения 23, 39, 40, 80, 92, 99, 126 правила видимости 77 приоритет 24, 34 присваивание 24 - целиком 91, 111 проблема буферизации 142 - конца файла 143 провода (пример) 93 программа - исходная 16 - объектная 16 - , расположение 21, 74 синтаксис 38 процедура - DISPOSE 149 - GET 135 - GRAB (пример) 130-133 - NEW 149 - PACK 98 - PAGE 126 - PUT 135 - READ 127 - READLN 127 - RESET 124 - REWRITE 124 процедуры, сводка 185 пунктуация 14, 20, 21 p размер чисел 44 редактор текстовый 14, 15, 125 рекурсия 67, 75, 94-97, 104, 150, 165, 170, 171 (5 связанные структуры 148, 160, 161 сжатие (пример) 136 символ 34 синтаксис - выражений 36 - , обозначения 33 - оператора 37 - , определение 34 - программы 38 - , сводка 187-189 - составных операторов 35 - типа 39 - , элементы 34 синус (пример) 29 системы счисления(пример) 102-104 случайные числа (пример) 70—71 снова заем (пример) 72 сортировка - быстрая 96-97 - двоичным деревом 164-167 - методом пузырька (пример) 94, 95 - обезьянья (пример) 166, 167 - связанного кольца 162, 163 ссылки вперед 74, 147 стеки 150, 151 строки 99 - , сравнение 99, 174 - , утилиты 170-177 - , хеширование 180-183 структура - BEGIN..END 20 - CASE..OF с вариантами 119 - CASE..OF управляющая 55 - FOR..TO..DO 57 - GOTO 37, 55 - IF-THEN-ELSE 56 - REPEAT..UNTIL 58 - WHILE..DO 58 таблица состояний 61, 129 текст программы 16 тип - записей 111 - констант 80 - INTEGER 12, 23 - REAL 12, 23 точка с запятой 20 точность 44 указатели 146, 147 умножение матриц (пример) 105 упакованный тип 91, 111, 135 упаковки процедуры 98 условия 24, 26, 36, 56 файл - стандартный INPUT 122-125 - - OUTPUT 122-125 файлы 122-137 - временные 131 - двоичные 134, 135 - , открытие 124 191
- , свойства 137 - стандартные 122-127 - текстовые 123, 125-127 фильтр (пример) 59 фильтр2 (пример) 86 фокус (пример) 100-101 функции - арифметические 46 - логические 49 - над дискретными типами 50, 51 - , определение 64, 65 - преобразования 48 - , примеры 66 - , сводка 186 - тригонометрические 47 функция - ABS 46 - ARCTAN 47 - CHR 51 - COS 47 - EOF 49 - EOLN 49 - EXP 46 - LN 46 - ODD 49 - ORD 50 - PRED 51 - ROUND 48 - SIN 47 - SQR 46 - SQRT 46 - SUCC 51 s хеширование 180 хеширование (пример) 182 ш цепи 146, 148, 155-157 циклы 28, 54, 57, 58 цифры 34 RA>DdROn (пример) 154 192
4 р. 60 к. ISBN 5-03-001292-3 (русск.) ISBN 0-521-33695-3 (англ.)