/
Author: Алкон Д.
Tags: компьютерные технологии программирование языки программирования язык программирования паскаль
ISBN: 5-03-001292-3
Year: 1991
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 (англ.)