КулЛиб - Классная библиотека! Скачать книги бесплатно
Всего книг - 710630 томов
Объем библиотеки - 1389 Гб.
Всего авторов - 273941
Пользователей - 124931

Новое на форуме

Новое в блогах

Впечатления

Stix_razrushitel про Дебров: Звездный странник-2. Тропы миров (Альтернативная история)

выложено не до конца книги

Рейтинг: 0 ( 0 за, 0 против).
Михаил Самороков про Мусаниф: Физрук (Боевая фантастика)

Начал читать. Очень хорошо. Слог, юмор, сюжет вменяемый.
Четыре с плюсом

Рейтинг: +2 ( 2 за, 0 против).
Влад и мир про Д'Камертон: Странник (Приключения)

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

  подробнее ...

Рейтинг: +1 ( 2 за, 1 против).
Влад и мир про Коновалов: Маг имперской экспедиции (Попаданцы)

Книга из серии тупой и ещё тупей. Автор гениален в своей тупости. ГГ у него вместо узнавания прошлого тела, хотя бы что он делает на корабле и его задачи, интересуется биологией места экспедиции. Магию он изучает самым глупым образом. Методам втыка, причем резко прогрессирует без обучения от колебаний воздуха до левитации шлюпки с пассажирами. Выпавшую из рук японца катану он подхватил телекинезом, не снимая с трупа ножен, но они

  подробнее ...

Рейтинг: 0 ( 1 за, 1 против).
desertrat про Атыгаев: Юниты (Киберпанк)

Как концепция - отлично. Но с технической точки зрения использования мощностей - не продумано. Примитивная реклама не самое эфективное использование таких мощностей.

Рейтинг: +1 ( 1 за, 0 против).

Простой учебник программирования [Рик Гаско] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

Серия

«Программирование»

Рик Гаско

Простой учебник
программирования

Москва
СОЛОН­Пресс
2018

УДК 681.3
ББК 32.973­18
К 63
Под редакцией Н. Комлева
Рик Гаско
Простой учебник программирования. — M.: СОЛОН­Пресс,
2018. — 320 с : ил. (Серия «Программирование»)
ISBN 978­5­91359­281­1
Книга написана необычным для многих — живым, простым и емким
языком. Автор не любит длинных описаний программ, поэтому
прерывается на пояснения, что делает книгу удобной для понимания и
легкой в усвоении материала.
Чтение учебника не утомляет, а наоборот, — захватывает. Это
лучший учебник программирования, по крайней мере, из всех доступных
на русском языке. Проработав книгу от начала и до конца, читатель
получит ясное понимание — что это такое, программирование.
Выбранный в дальнейшем конкретный язык программирования неважен,
важны принципы.
В первую очередь это книга для тех, кто, являясь профессионалом в
своей области, хочет овладеть программированием или, по крайней мере,
научиться разговаривать с программистами на равных.
От самых начал до понятий достаточно глубоких. Прочтите и
узнайте!
Rick Gassko. Straight Programming. — Moscow: SOLON­Press, 2018. —
320 p. (The "Programming" series)
По вопросам приобретения обращаться:
ООО «СОЛОН­Пресс»
Тел: (495) 617­39­64, (495) 617­39­65
E­mail: kniga@solon­press.ru, www.solon­press.ru
ISBN 978­5­91359­281­1

© «СОЛОН­Пресс», 2018
© Рик Гаско, 2018
© Rick Gassko, 2018

Посвящается
тем
кто прочитал
мою предыдущую книгу
и кто теперь на самом деле хочет
научиться писать настоящие программы

Содержание
Всяческие вступления и предисловия
Вступление №1
Вступление №2 ­ от старой книги с моей давно изменившейся
точки зрения
Для кого эта книга
Почему я решил эту книгу написать. И почему именно я
Почему буковки разные
Что я ожидаю, что читатель уже знает
Ещё раз ­ почему Паскаль?
А почему, собственно, именно Turbo Pascal
Что бы ещё почитать
Disclaimer
Том первый, Война и немцы
Глава 1 Просто программа
Самая простая программа, которая ничего не делает
Очень простая программа, которая делает хоть что­то
Улучшаем программу. Много новых слов
Весело, в цветочек
И кое­что ещё
Глава 2 Переменные
Что такое и зачем
Ввод и вывод
Дроби
Глава 3 Условные операторы
Что такое и зачем
Усложняем
Окончательно усложняем
Небольшая программка и кое­что ещё
Глава 4, очень простая Немного графики
Начальные заклинания
Точки, линии и окружности
Прямоугольнички и кружочки
Красивые буковки
Что там ещё осталось?
Полезная вещь ­ метод опорной точки
Глава 5, сложная Циклы и массивы
Просто массив
4

8
8
11
11
11
12
12
14
16
17
19
20
20
20
24
26
31
32
33
33
37
38
43
43
44
46
47
50
50
51
53
55
56
57
59
59

Просто цикл
Просто циклы и графика
Ещё одна несложная программа
А теперь всё вместе
Опыты
Ещё опыты
Самый главный опыт
Как не делать ничего
Что­нибудь полезное
Глава 6 Строки
Просто строка
Просто строка и её процедуры
Строка и цикл
Ой, кто пришёл!
Считаем, наконец, слова
Глава 7, продолжение пятой Ещё циклы и массивы
Массивы двумерные и далее
Вложенные циклы
Пример посложнее
О самом важном. Всё сразу и побольше
Другие циклы
Глава 8 Процедуры и функции
Процедура без параметров
То же и с параметрами
А какие бывают параметры?
О грустном
Скучная, но необходимая теория
А теперь функция
А теперь тараканчик
А этот раздел просто больше некуда было вставить
Всем стоять и не разбегаться!
Применим к тараканчику
Глава 9 Совсем настоящая программа
Про что программа?
Отладка. Давно пора
Ещё одна очень важная вещь. Модули
С чего начать?
Поле
Крестик и нолик
5

61
67
70
72
75
77
78
81
84
91
91
93
95
98
100
104
104
105
108
111
113
120
120
121
124
127
129
132
135
137
145
146
148
148
149
153
157
161
162

Курсор и чтобы бегал
Делаем ход
А не выиграл ли кто?
Вражеский интеллект
Имеем в результате
Глава 10 Файлы
Коротенько. Почему это очень важно
Найти и снова найти
Файлы текстовые и никому не нужные
Бинарные
Бинарные файлы. Задачка
К чему­нибудь прикрутим
Глава 11 Всякие глупости, она же Глава очень длинная
Записи. И как мы только без них обходились!
Указатели
Round, Ord, Chr и другие пустячки
Есть такая штука ­ множество
Совсем глупость ­ про музыку
Оно надо? Рекурсия
Меряем время
Страшная сила
Никаких новых слов
Том второй, пять старушек ­ рупь
Глава 2­1 Ещё раз: простая программа и переменные
Повторение пройденного
Разбор полётов
Глава 2­2 Вспомнить всё или Не очень сложная программа ­
Ханойские Башни
О чём речь?
Всем всё понятно, программируем
Решительно кончаем программу
Глава 2­3 Всё таки кое что новое
Как устроена большая программа
Очень простая и маленькая большая программа
Глава 2­4 По ту сторону ­ опустимся чуть ниже
Вступление
Начало. Просмотр картинок
Вариант 1
Вариант 2
6

163
166
167
169
176
177
177
177
179
183
186
187
190
190
192
196
200
201
209
213
219
222
225
225
225
230
232
232
233
242
247
247
247
261
261
261
263
264

Вариант 3
Глава 2­5 Указатели. Зачем они действительно нужны
А чем списки лучше массивов? А чем хуже?
Всё то же самое, но медленно и по шагам. Шаг первый
Всё то же самое, но медленно и по шагам. Шаг второй
Усложняем. Шаг третий
Дополнение Всякие важные вещи
Как установить Турбо Паскаль
Как настроить Турбо Паскаль, чтобы было приятно и удобно
И ещё кое­что
Имейте свой стиль
Все полезные клавиши на одной странице
Все типы данных на одной странице (ну, на двух...)
Даже те, которые от вас скрывали
Чем заняться на досуге
Модуль для работы с клавиатурой
Модуль для работы с нотами
Полный и аккуратный текст программы про Ханойские Башни

7

267
276
276
277
282
289
296
296
299
301
301
307
308
310
311
313
316

Всяческие вступления и предисловия
Вступление № 1
Почему это не просто Вступление, а Вступление под номером один?
Сейчас поймёте. Это потому, что я очень честный человек. И я очень
честно признаюсь, что 50% этой книги ­ совершенно свежий и новый
текст. А другие 50%, как легко догадаться, не совсем новый и не совсем
свежий.
Что я могу сказать в своё оправдание и убедить вас купить и прочитать
эту книгу ­ главное, конечно, купить? После покупки читать совершенно
не обязательно.
Первое ­ это хорошая книга. Я плохих не пишу. Эта книга
действительно
простой
учебник
программирования,
и
даже
альтернативно одарённый может по ней научиться программировать ­
если захочет.
Второе ­ откуда в этой книге взялся не очень новый текст? И откуда,
кстати, взялся новый? Несколько лет назад я написал книгу по
программированию на языке Паскаль в среде программирования Turbo
Pascal. Книга была благосклонно принята Главным Издателем™, который
предварительно отправил её ­ книгу ­ на экспертизу к, извините за
тавтологию, к экспертам. Через два месяца я объяснил Главному
Издателю™, что главный эксперт здесь ­ это я. Главный Издатель™
согласился, но за две недели до выпуска объявил, что Turbo Pascal нынче
не в тренде, а в тренде нынче, наоборот, Pascal ABC, потому что его
заставляют учить в школах. Действительно заставляют, я проверял.
Я стремительно переписал книгу под Pascal ABC, который, напоминаю,
преподают в школах. Книга даже в таком покоцанном виде имела три
переиздания, что как бы намекает на её определённый, не низкий,
уровень.
Третье ­ одна моя давняя знакомая­подруга имеет то ли дочку, то ли
внучку, которая, чуть что сделали не по ней объявляет злобным голосом ­
Сделайте, как было! Так вот, мне написал письмо Главный Издатель, в
котором попросил вежливыми русскими буквами ­ Сделайте, как было!
8

Как несколько раньше говорил товарищ Сталин ­ Гитлеры приходят и
уходят, а немецкий народ остаётся. Pascal ABC приходит и уходит, а
простое программирование остаётся. И это правильно, товарищи.
Четвёртое ­ я достал исходный текст книги, сдул с него пыль, но
посчитал совершенно бесчестным издавать её в таком виде. Ведь кто­то
уже заплатил свои деньги за этот текст!
Пятое ­ я прошёлся по книге, по каждому абзацу и, уверяю вас, в книге не
сталось ни одного абзаца мною неизменённого. Что важнее, сразу после
первой книги, которая, если вы помните, называлась Самоучитель игры
на Паскале, я начал писать новую, под названием Школа игры на
Паскале. Книга, по объективным обстоятельствам, так и осталась
наполовину недописанной ­ или наполовину написанной ­ как кому
менталитет подсказывает. Не пропадать же добру.
А теперь ­ о главном! Я, во­первых, восстановил всё о Турбо Паскале, во­
вторых, переписал весь текст, в­третьих, добавил сотню страниц из
Школы.
И кое что ещё,
И кое­что другое,
О чём не говорят,
О чём не учат в школе © Старая смешная песня
Далее, я учёл все замечания благодарных и восторженных читателей, а
замечания завистливых и злобных, наоборот, проигнорировал. Ещё я
стандартизировал текст под мои последующие, теперь уже вышедшие,
книги ­ моноширинный шрифт для текстов программ в интегрированных
средах программирования используется не случайно. Ещё я везде заменил
Вы с большой буквы на вы с маленькой. Мне кажется это более
правильным, орфографически допустимым и не обидным.
И вы даже не представляете, сколько в книге обязательно остаётся
опечаток ­ сколько бы раз и сколько бы людей её не вычитывали.

9

Решено было не допустить ни одной ошибки. Держали двадцать
корректур. И все равно на титульном листе было напечатано:
"Британская энциклопудия " © Ильф и Петров
А теперь ­ о самом главном! Эта книга ­ не учебник какого­то
конкретного, пусть и самого лучшего, языка программирования! Это и
есть тот самый что ни на есть самый простой учебник программирования.
Когда Великий Дейкстра написал свою гениальную книгу Дисциплина
программирования, он не использовал ни один существующий язык, он
тут же, на месте, изобрёл свой собственный!
И все были счастливы. Так считайте, что и Турбо Паскаль, мною
использованный, так же изобретён мною специально для этой книги, хотя
это, увы, не совсем так. Даже, точнее, совсем не так. Язык ­ это
условность, фикция, для обучения концепциям программирования он
абсолютно не важен, кроме, конечно, случаев использования в
программировании совершенно ортогональных, маргинальных и
перпендикулярных языков. Впрочем, не пугайся, мой маленький дружок.
И на эту тему, об этих языках я собираюсь написать книгу. А Добрый
Издатель™ даже пообещал её опубликовать. Потом.

10

Вступление № 2 ­ от старой книги с моей д а в н о и з м е н и в ш е й с я
точки зрения
Для кого эта книга
Для тех, кто хочет научиться программировать. И кто при этом не умеет
программировать вообще. Возможно, другая книга научила бы вас
программировать на Delphi/Pascal/Python быстрее. Но если вы хотите
научиться программировать, неважно на каком языке, то эта книга ­ то,
что вам нужно. Принципы программирования остаются неизменными, а
им я и стараюсь научить. Ну а уж если вы хотите научиться
программировать с нуля, и именно на Паскале, то мы с вами встретили
друг друга... Это именно учебник программирования, неважно на чём,
хоть на кухонном комбайне, в конце концов.
Почему я решил эту книгу написать. И почему именно я
Так получилось, что шесть лет я учил программировать. Сначала решил
для интереса попробовать один год, но получилось шесть.
Есть очень много преподавателей программирования со значительно
большим педагогическим стажем.
Но до того как начать учить, я много лет сам программировал. И пока
учил, тоже программировал.
Сейчас учить закончил, но всё равно
программирую.
// Занудства ради
Преподавание ­ один сплошной восторг, если это развлечение после
работы. Первые два­три года. Потом надоедает и утомляет. Потом или
бросают, или занимаются этим профессионально. Я бросил.
// конец Занудства ради
А в придачу руководил, и руковожу группой коллег­программистов, а это
занятие ещё более увлекательное. Недаром переводная с американского
языка книга о ремесле руководства программистами называется Как
пасти котов.
Так получается, что опытные педагоги, обучающие программированию,
обычно
программисты­теоретики
и
ничего
существенного
запрограммировать им в жизни не довелось. Опять­таки, занудства ради,
среди моих компаньонов по программированию был целый доктор
11

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

не хочется

Почему буковки разные
Названия клавиш будут выделены вот так ­ F2,Enter. Если нажаты сразу
две клавиши, это будет выглядеть вот так ­ Ctrl/F9. Пункты меню Турбо
Паскаля заключаются в угловые скобки ­ .
А тексты программ будут выглядеть вот так:
program kuku;
begin
[Вот тут
end.

программа}

Полужирным
шрифтом
будут
выделены
так
называемые
зарезервированные слова, редактор Турбо Паскаля их тоже выделит.
Курсивом выделены комментарии ­ в редакторе Паскаля они будут
выглядеть бледненько.
Зарезервированные слова в программах я пишу маленькими буквами, в
тексте могу и большими ­ для наглядности. То же самое с
однобуквенными именами ­ I , K, X и тому подобное. В программах они
будут маленькими, а в тексте ­ большими ­ чтобы не потерялись.
Что я ожидаю, что читатель уже знает
Можно конечно начинать программировать с полного нуля, не зная
абсолютно ничего ни о чём. Такие подопытные попадались и, в общем,
ничего. В смысле научить всё равно можно.
Если зайца долго бить,
Можно выучить курить! © Народный стих
12

Но некий минимум знаний всё же очень ускоряет процесс.
Естественно, я понимаю, что общее развитие у будущих программистов
отсутствует начисто. Книжек они не читают, Бабеля от Бебеля не
отличают, пишут по­русски фантастически безграмотно. Это нормально.
Татьяна Ларина, как известно изъяснялася с трудом на языке своём
родном, Бабеля с Бебелем я и сам не читал, а орфографию спеллчекер
проверит.
За время с написания этой книги, один из работавших у меня молодых
программистов стал чемпионом России по программированию. Это я к
тому, что писать по­русски без ошибок он вообще не мог, и вам не надо.
На заборе в честь Гоголя написано слово Вий, но с двумя ошибками © А
как бы вы, как программист, обработали в своей программе эту
ситуацию?
Однако от программиста всё же хотелось бы:
Знание арифметики и умение считать в уме, хотя бы на уровне целых
чисел ­ как это ни покажется странным, несмотря на то, что компьютер
считает быстрее человека, от программиста арифметика очень даже
требуется. Дробные числа можно считать на бумажке. Для
профессионального программиста знание высшей математики крайне
желательно, и чем с большим количеством разделов этой самой
математики он хотя бы поверхностно знаком, тем лучше. Знающий
математику программист крайне полезен в хозяйстве, его все любят и
повышают ему зарплату ­ шутка ­ пояснение для людей без чувства
юмора. Утешение ­ программист, не знающий математики, совершенно
не понимает, чего он лишён и с довольным лицом сидит в своём
загончике, блаженно похрюкивая и пуская пузыри.
Из геометрии ­ ну хотя бы понимание, что такое система координат.
Сокровенное знание, чем круг отличается от окружности, также весьма
пользительно. Самое важное ­ несложная формула расчета расстояния
между двумя точками на плоскости. Она обязательно пригодится, рано
или поздно, скорее даже рано.

13

Вот она, формула: d

(O ­ b ) + (a ­ b ) .
2

2

ab

1

2

1

2

Для гениев обязательно ­ умение расширить эту формулу на n­мерное
пространство. Утешение для незнающих ­ если не будете заниматься
графикой ­ ну её нафиг, эту геометрию.
Ещё из математики ­ системы счисления и, как ни страшно это звучит,
немного математической логики. Хотя, куда­то меня не туда несёт ­
многие не знают ­ и ничего. Забудьте.
Английский язык на уровне знания сотни­другой слов ­ begin, end, repeat,
until, red, green, blue. Очень способствует. Для профессионального
программиста знание технического английского абсолютно обязательно.
Профессиональная
пригодность
незнающего
английский
язык
программиста автоматически делится на два. Зарплата тоже. Утешение ­
умение требуется только одно ­ перевести текст; говорить и понимать на
слух ни к чему. Сам­то я говорить могу, а на слух не понимаю. Ну так мне
в школе на уроках пения и петь запрещали.
Чукча не читатель, чукча писатель © Чукча
Из чисто компьютерных материй ­ что есть такая сущность ­ каталог
(директория (папка)), что есть такая единица измерения ­ байт, что есть
такая штука ­ файл, что бывают они текстовые и бинарные и что лежат
они в каталогах. Суперзнание ­ что была такая штука DOS, и что были
такие программы, которые под ним работали. Если про байты и файлы не
понимаете ­ ну не знаю, чем и утешить. Если вы думаете, что в наше
продвинутое время это совсем ни к чему, то очень даже ошибаетесь.
Ещё раз ­ почему Паскаль?
Давным­давно, когда я был маленьким программистом, а языков
программирования
уже было много, в каждой книжке по
программированию повторялось заклинание, что нет хороших и плохих
языков программирования, нет, и не может быть самого лучшего языка,
нет, и не может быть языка на все случаи жизни. Прошло много времени,
с предрассудками давно покончено, век наивности завершился, зелёная
трава кончилось. Над миром распростёр сумрачные крылья лучший язык
в мире на все случаи ­ C++. Точнее, мне так показалось, потому что так
14

оно и было в моей окрестности. Если взглянуть чуть шире, то не всё так
однообразно. Есть и малосимпатичная мне Java, есть и более
симпатичный мне C# ­ напоминаю, произносится Си Шарп ­ вот эта вот
решёточка ­ # ­ является музыкальным знаком диез.
Upd. Уже устарело. C++ заворачивается в простыню и ползёт в
направлении кладбища.
Так зачем же я предлагаю не совсем модный Паскаль (мне больше
нравится писать название по­русски)? Для начала, рекомендация
меркантильная ­ на нашей части суши Турбо Паскаль (в реинкарнации
Object Pascal/Borland Delphi) всё ещё очень популярен, наверное,
популярнее, чем во всех других частях суши, вместе взятых. В последние
годы идёт процесс вытеснения Паскаля и Delphi в пользу сами знаете
чего. Если ваша тётя живёт на Брайтон­Бич, то C++ конечно полезнее. А
если ваш дядя проживает в деревне Красный Слон, учите Паскаль. И C#,
само собой.
Не выпендривайся, Петрик. Слушай свою любимую песенку про комбайн
©Анекдот.
Так что если вы, дорогой читатель, не собираетесь отправляться
программировать в более другие места прямо завтра, Паскаль вам очень
даже пригодится в плане зарабатывания денег. Далее, бесконечное число
раз повторено, что Паскаль ­ идеальный язык для обучения
программированию. Как ни странно, так оно и есть ­ в конце концов, он
для того и создан. Ещё дальше, с желанием оттоптаться по телу
безобидных сиплюсплюссников ­ C++ язык очень сильный, позволяющий
программисту буквально всё. Паскаль не позволяет. Шаг влево, шаг
вправо ­ ошибка компиляции. Си программиста не ограничивает ни в чём,
Паскаль берёт за хобот и тащит унылой, но безопасной дорогой.
Сильного программиста Паскаль местами сковывает, хотя для сильного
программиста как всегда ­ если нельзя, но очень хочется, то есть в запасе
хитрый фокус. Но в реальной жизни сильных программистов мало.
Больше средних. Ещё больше слабых.
И вот для них и существует Паскаль. Он, худо­бедно, ГАРАНТИРУЕТ
получение приемлемого результата. А когда программирование
15

используется не как средство самоудовлетворения, а как возможность для
получения разноцветных бумажек, именуемых также деньгами, наличие
результата становится главным. Пробегающим мимо ситникам просьба
не мнить себя могучими кодерами и не плевать в колодец.
Ну и главное ­ Паскаль лично мне очень нравится.
А почему, собственно, именно Turbo Pascal
Потому что, как минимум, учиться программировать надо именно на
Паскале, в этом я вас, кажется, убедил. А теперь вполне резонный вопрос
­ зачем, собственно, начинать с Турбо Паскаля (я опять по­русски) если
есть Delphi. Надо признать, что программировать на Турбо Паскале,
кроме как в процессе обучения, почти наверняка не придётся ­ если
только не достанется сопровождать какую­нибудь древнейшую
программу. Турбо Паскаля в природе уже давно нет ­ есть только Delphi.
Печально, но такова суровая реальность.
Так почему Паскаль? А вот почему. Логика Delphi не совсем проста и
привычна, даже для опытного программиста, переходящего на него с
Паскаля или любого традиционного языка. Традиционный язык ­ тот,
который, что вижу ­ то пою. Программа медленно и торжественно
разворачивается сверху вниз. Как сильно в Delphi будет вам не хватать
маленькой радости ­ той, что у программы есть начало и есть конец. Это
как воздух ­ пока есть, его не замечаешь. Вот так и с Турбо Паскалем,
извините за пафос.
У дельфовской программы начала нет, и конца нет. Вся она состоит из
обрывков­методов, реагирующих на различные события в жизни
исполняемой программы. Потом, немного поработав, программист
поймёт, что это логика правильная и естественная, но сначала выглядит
это как­то странно и ужасно. Кроме того, Delphi оказывает полумедвежью
услугу разработчику ­ отчасти само генерирует текст. Это плюс ­ при том
маленьком условии, что вы этот сгенерированный текст полностью
понимаете и можете в любой момент переписать его сами.
Ну и конечно, любимый вопрос дельфиста, за который над ним глумятся
иноязычные коллеги программисты ­ а где взять вот такой компонент?
Синдром Гарри Поттера ­ махнуть волшебной палочкой, компонентом в
16

смысле ­ и счастье. Быстро и дешево. Поневоле оценишь справедливость
армейского юмора ­ мне не надо, чтоб быстро, мне надо, чтоб ты
задолбался! В нашем случае ­ научился!
Так что начинать программировать сразу на Delphi (или аналогичном
средстве разработки) ­ это беспощадная травма для профессионального
будущего начинающего программиста.
­ Всё, всё... Всё. Теперь так и останется...
­ Что останется?
­ Что, что? Косоглазие!!! © к/ф Любовь и голуби
Величайший программист всех времён и народов Дейкстра вообще писал
программы ­ в своих книгах, по крайней мере ­ на лично им выдуманном
исключительно для этих целей языке программирования. Чем Паскаль
хуже? Паскаль лучше! Он, по крайней мере, существует, как объективная
реальность.
Что бы ещё почитать
Само собой, лучше всего читать и перечитывать только мои книги ­
изданные, переизданные, неизданные и недоизданные. Список изданных
и переизданных будет, надеюсь, на задней обложке. Впрочем, в
позапрошлый раз там напечатали портрет какого­то совершенно левого
мужика. Объясняю для девушек ­ я какой угодно, но не лысый!
Учебник обычно сопровождается задачником, или идёт с ним вместе под
одним переплетом. На задачник меня не хватило, так что могу только
порекомендовать какой­либо из уже существующих. Лучшее, что мне
встречалось ­ Н.Кулътин "Turbo Pascal в задачах и
примерах".
Несколько задач из него мы разберем. У этого автора есть ещё пачка
задачников для почти всех языков на свете ­ а кстати, здесь могла быть и
ваша реклама!
Ещё пара книжек, которые читать или уже поздно или ещё рано. Уже
поздно ­ Б.Керниган, Ф.Плоджер "Элементы стиля программирования",
"Радио и связь", 1984. Поздно потому, что языки программирования, в
этой книге используемые, сильно повымерли. С фортраном, впрочем,
встреча очень даже возможна, а вот PL/I однозначно того... Тем не менее,
17

это лучшая книга об этом. О чём? ­ о том, что в заголовке ­ о стиле
программирования. Читать от начала до конца, потом снова ­ от начала
до конца и снова...
Ещё рано ­ JTy Гринзоу "Философия программирования Windows 95/NT",
Санкт­Петербург, "Символ", 1997 Lou Grinzo Zen of Windows 95
Programming. Рано только в том смысле, что мы тут программируем
исключительно под DOS, а там, как из заглавия понятно, несколько о
другом.
И ещё книги:
Э.Дейкстра "Дисциплина программирования "
У.Дал, Э.Дейкстра, KXoop "Структурное программирование ".
Попробуйте почитать, даже если в них будет ничего не понятно. Как­то
где­то прочитал, что какой­то профессор говорил своим студентам ­
Читайте, понимание придёт потом. Где­то так оно и есть.
И ещё старая книжка попопсовей:
Дж. Хъюз, Дж. Мичтом "Структурный подход к программированию ".
Половина из того, что там написано, уже неправда, но всё равно
прочитайте. Вторая­то половина остается в силе, а из той половины, что
скончалась, узнаете, какими представлениями жил программистский мир
тридцать лет назад. Способствует развитию здорового скептицизма, ­
какой же ерундой вам пудрят мозги сейчас.
Как вы, конечно, заметили, большинство рекомендуемых книг изданы
очень­очень давно, при коммунистах. Дело в том, что Советская Власть
была заинтересована
в умных людях и, применительно
к
программированию, выпускала литературу, ориентированную на
думающих разработчиков. Сейчас нужны тупые кодеры, для их массового
изготовления книжки и печатают. Попадаются, конечно, полужемчужные
зерна, но всё же редко. Когда подрастёте и станете маленьким
начальником, прочитайте Брукс "Мифический человеко­месяц". Это наше
всё.

18

Похоже, что книги эти в последние годы не переиздавались, кроме разве
что Дисциплины программирования. Впрочем, в Интернете все найдутся.
Disclaimer
Это модное американское слово, на русский его можно перевести только
целым предложением, примерно так ­ Фирма веников не вяжет, или, по
другому ­ Никто ни за что не отвечает.
Электрон, как и атом, неисчерпаем © В.И.Ленин
Опечатки в этой книге тоже неисчерпаемы. Не обижайтесь и не
огорчайтесь.

19

Том п е р в ы й , В о й н а и н е м ц ы
Глава 1
Просто п р о г р а м м а
Самая простая программа, которая ничего не делает
Пишем в редакторе, или по­другому в IDE ­ интегрированной среде
программирования:
program
begin
end.

kuku;

Что интересно, некоторые слова после написания сами окрашиваются в
другой цвет, в зависимости от настроек редактора ­ скорее всего белый. В
связи с трудностью отображения на белой бумаге шрифта белого цвета, у
нас эти слова будут полужирными.
Это так называемые зарезервированные слова. Зарезервированность их в
том, что эти слова нельзя использовать в качестве идентификаторов.
Идентификатор ­ то же самое, что имя, но звучит гораздо лучше. Имена
бывают у переменных, процедур, функций, констант, у самой программы,
в конце концов.
Вместо kuku можно написать любое слово, главное
буквами. Kuku ­ это имя нашей программы.

английскими

Действительность немного сложнее ­ это слово может состоять из
английских букв, цифр и знака подчёркивания, но начинаться должно с
буквы или подчеркивания ­ с цифры начинаться оно не имеет права.
Большие буквы, или маленькие ­ это никакой роли не играет ­ а в C++
играет! Точно так же, все остальные маленькие буквы по желанию могут
быть заменены на большие, от этого ничего для компилятора не
изменится.
Всё сказанное имеет очень большое значение ­ эти правила
распространяются на все идентификаторы языка Pascal. С этого и
начинается программирование. Идентификаторами являются все имена в
программе. В данном случае имя только одно: kuku ­ имя программы.
20

Остальное должно быть в точности как написано. Ну, почти ­ там где
стоит один пробел, можно поставить сколько угодно, это общее правило
для всех языков. Но лучше не увлекаться. Но там, где стоит хотя бы один
пробел ­ хотя бы один пробел и должен быть.
Сокровенный смысл написанной выше программы.
То, что находится между строчками program ; и begin является
частью не выполняющейся, а декларативной. Говоря по другому, эта
часть ничего не делает, а объясняет, как понимать написанное далее
(понимать не программисту ­ компьютеру). А вот то, что написано между
begin и end как раз что­то делает. Даже если какая­то строка не делает
как будто ничего, на самом деле это не так ­ она указывает как, в каком
порядке, при каких условиях должны выполняться остальные строки.
Да, впервые упомянуто новое слово ­ оператор. Что это такое?
Обычно это одно слово в языке Паскаль, которое что­то делает. Но есть,
например, оператор цикла, который состоит из многих слов, и оператор
присваивания, который вообще не слово. Как ни печально, про оператор
все всё понимают, но объяснить не могут.
Экран сейчас должен выглядеть вот так:
I l c:\bpascal\bin\r.bat ­ Far
File
Edit
Search
H=E Q 3
program kuku;
begin
end.

P l Help

F2 Saue

Run

F 3 Open

­ | п| X
Conpile

Alt+F9

Debug Too I s
NONfiMEOtD PfiS

Conpile

F9

Options

Hake

Uindow

Help

=2=m=n

flit+FlO

L o c a l menu

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

Зачем F2? Сохранить текст нашей программы
Появится вот такое окошко:

Остаётся указать имя файла, в котором будет сохранена наша программа,
и нажать клавишу Enter.
В каталоге, откуда мы запускали Турбо Паскаль, появился файл kuku.pas.
Для упрощения жизни настоятельно рекомендую давать файлу то же имя,
что и программе, то есть тот идентификатор, что стоит после слова
program. И не забывать, что по техническим причинам имя должно быть
не длиннее восьми символов, не содержать пробелов и так далее.
Теперь нажать клавишу F9. Это трансляция нашей программы ­ перевод
её из исходного, написанного нами, текста в исполняемый машинный
код. И, что на данном этапе даже важнее, проверка исходного текста на
правильность и отсутствие в нём ошибок. Экран должен теперь выглядеть
вот так:

22

Нам сообщают, что пока всё идёт хорошо. Теперь нажимаем ­ как нас
собственно и просят ­ любую клавишу. Итак, в программе ошибок нет ­
да и откуда им взяться ­ программа­то маленькая. А теперь программу
запускаем!!!
Жмём Ctrl/F9. Происходит быстрое мелькание экрана ­ и всё. Что
случилось? Да ничего, собственно. Программа наша маленькая, ничего не
делает. Соответственно при запуске она ничего и не сделала.
Сейчас в каталоге, где находится исходный текст нашей программы,
появился файл kuku.exe. Точнее, появился чуть раньше, после нажатия
F9. Его ­ kuku.exe ­ можно запустить на исполнение обычным способом,
через проводник или FAR или Total Commander. Результат его
исполнения, будет в точности тот же, что и после запуска через нажатие
Ctrl/F9.
А теперь мы завершили работу, и вышли из Турбо Паскаля. Выйти,
кстати, можно или через меню, или нажав Alt/X. При выходе нас,
естественно, спросят, не забыли ли мы сохранить наш текст ­ если мы
забыли. Обратите внимание на звёздочку слева внизу над F l . Если она
есть, значит, сохраниться мы забыли.
А теперь мы запустили Турбо Паскаль снова. Если всё настроено
правильно, у нас автоматически загрузится та самая программа, с которой
23

мы работали, и на том самом месте, где мы с ней работу прекратили. А
если нет? Или если нами написано программ уже много и нам надо
загрузить совсем другую? Очень просто. Нажимаем F3 и выбираем то, что
нам надо.
Каждая новая открытая программа занимает в окне редактирования чуть
меньшее пространство ­ чтобы были видны заголовки всех открытых
программ. Если окошко стало уж очень маленьким, нажмите клавишу F5
­ текущая программа займёт всё доступное место. Ранее открытые
программы никуда не делись ­ они где­то там, только сзади. Вывести их
на передний план можно клавишей F6 ­ по мере нажатия она переберёт
поочерёдно все загруженные программы.
Очень простая программа, которая делает хоть что­то
Если мы хотим, чтобы программа что­то делала, это что­то должно быть
выражено в тексте, находящемся в исполняемой части программы ­
между begin и end. Например:
program kuku;
begin
write('Aul');
end.

Вместо Au!, естественно, можно написать всё, что угодно, но пока по­
английски. И ещё одно ограничение ­ текст не должен содержать
кавычек.
Если хочется писать по­русски? Придётся постараться. Сначала надо
раздобыть русификатор для DOS. Рекомендую keyrus Дмитрия Гуртяка.
Как найти? Наберите в Яндексе keyrus и выбирайте подходящий
источник. Возможно, в скачанном архиве будет несколько файлов, нам
понадобится только один ­ keyrus.com. Скопируем его в c:\bpascal\bin\.
Можно в другое место, только не забудьте куда. Как написано в
дополнении, хотя Вы, возможно, обошлись без него, мы предполагаем,
что Турбо Паскаль установлен в каталог c:\bpascal.
Теперь нам надо, чтобы перед запуском Турбо Паскаля (то есть turbo.exe)
запускался keyrus.com. Будем писать командный файл, известный также

24

как пакетный. Заходим в notepad.exe,
текстовое и набираем:

или что­нибудь

аналогично

c:\bpascal\bin\keyrus.com
c:\bpascal\bin\turbo.exe
Сохраняем то, что набрали под именем r.bat. Можно не r, но расширение
.bat обязательно. Теперь там, где у нас вызывался turbo.exe, меняем его
вызов на вызов r.bat. Пробуем. Всё должно заработать.
Внешне всё выглядит так же. Нажимаем правый Shift и пишем ­ теперь
пишется по­русски. Снова правый Shift ­ пишется по­английски. При
нажатом Shift е в полноэкранном режиме вокруг экрана появляется тонкая
синяя рамочка. Обратите внимание ­ переключение на русский язык с
помощью Shift'a необходимо только для набора текста по­русски,
отображаться по­русски текст будет и без этого, достаточно загруженного
русификатора.
Кстати, вы освоили простейший скриптовый
командных файлов DOS именно им и является.

язык. Потому что язык

Если очень хочется кавычек, то всё, как в анекдоте, очень просто ­ дайте
две! Вместо одной кавычки поставьте две. Не двойную кавычку вместо
одиночной, а именно две. Видно в тексте программы будет две, но
восприняты компилятором они будут не как две, а как одна. При подсчете
символов в строке ­ об этом дальше ­ обе они будут восприняты как один
символ. При выводе такой строки на экран видно будет тоже только одну
кавычку.
Далее снова то же самое:
F2 ­ сохранить.
F9 ­ скомпилировать.
Ctrl/F9 ­ выполнить.
Опять на экране что­то промелькнуло и исчезло. Обидно, да?
Учим новое слово, в смысле сочетание клавиш ­ Alt/F5. Нажимаем и
видим приблизительно вот такую картинку:

25

D : \ К о п 1 е и \ У ч е б н и к > с : \ b p a s c a l \ b i n \ r . bat
D : \ К о п 1 е и \ У ч е б н и к > с : \ b p a s c a l \ b i n \ k e y r u s . con
Драйвер

к л а в и а т уры уже з а г р ужен ­

В:\Коп1еи\Учебник>с:\bpascal\bin\turbo.exe
Turbo P a s c a l
Uersion 7.0
C o p y r i g h t 1 9 8 3 , 9 2 B o r l a n d
flut

Internat ional

Много всего непонятного, и среди этого непонятного наше Au!
Нажимаем любую клавишу и возвращаемся в привычный (уже) редактор
Турбо Паскаля. Вывод из произошедшего ­ оператор write выводит текст
на экран. Текст в кавычках, кавычки в скобках. Кавычки и скобки всегда
ходят парами ­ если есть открывающая кавычка или скобка, где­то
неподалёку должна быть и закрывающая.
Улучшаем программу. Много новых слов
Лично мне нажимать Alt/F5 не нравится. Хотелось бы автоматизировать
процесс. Добавим в программу ещё одну строчку:
program kuku;
begin
write('Aul');
readln;
end.

Опять те же движения­ F2, F9, Ctrl/F9. И сразу, безо всяких Alt/F5 видим
знакомую уже картинку. Или вот такую:

26

D : \ К о т 1 е и \ У ч е б н и к > с : \ b p a s c a l \ b i n \ r . bat
D:\Кот1еи\Учебник > с : \ b p a s c a l \ b i n \ k e y r u s . c o n
Драйвер

клавиатуры

уже

загружен.

D:\Кот1еи\Учебник > с : \ b p a s c a l \ b i n \ t u r b o . е х е
Turbo P a s c a l
Uersion 7.0
C o p y r i g h t 1 9 8 3 , 9 2 B o r l a n d
flu?
Au?

International

Теперь чтобы вернуться в редактор Турбо Паскаля надо нажать клавишу
Enter. Вывод ­ оператор readln занимается тем, что ничего не делает,
ожидая нажатия клавиши Enter (внимание ­ не любой клавиши, а именно
клавиши Enter). И ещё вывод ­ каждый оператор заканчивается точкой с
запятой.
Ведутся глубокие философские дискуссии, ставится ли точка с запятой
после оператора, или она является его неотъемлемой частью. Вообще­то.
что совой об пень, что пнём об сову.
Однако что­то ещё не так. На экране кроме нашего Au! Куча левого
мусора. Мало того, Au! от предыдущего запуска тоже висит на экране. А
если запустим программу на выполнение ещё раз­другой, Au!
размножатся в соответствующем количестве. Так что предлагаю при
запуске программы очищать экран от всего ранее на него выведенного.
Правда здесь одним словом­оператором не обойтись, новых слов
придётся добавить побольше.
Программа приобретает такой вид:
program kuku;
uses
Crt;
begin
ClrScr;
write('Aul');

27

readln;
end.

Запускаем программу на исполнение ­ и имеем чистенькую картинку
одной только нашей надписью, как и хотелось.

Волшебное слово ClrScr очищает экран, а чтобы оно заработало, надо
написать ещё два волшебных слова ­ uses Crt. Если написать ClrScr, но
забыть про uses Crt, то получим вот такое несимпатичное сообщение об
ошибке:
Error 3: Unknown identifier
Означает оно, что Паскаль не знает и даже не догадывается, кто такой
ClrScr и где его искать ­ а искать надо в модуле Crt. ClrScr очень похож
на оператор, но это не оператор ­ это вызов внешней подпрограммы или
процедуры ­ об этом позже ­ кому как больше нравится. Впрочем, для
всех практических целей ClrScr можно считать оператором
Для расширения кругозора. Невразумительное слово ClrScr ­ сокращение
от Clear Screen ­ очистить экран. Внешние процедуры содержатся в
модулях, об этом позже. Имя модуля ­ Crt. Переводится ЭЛТ ­
электронно­лучевая трубка. В этом модуле содержатся подпрограммы для
работы с экраном в текстовом режиме. Используемые модули
28

перечисляются через запятую после слова uses. Любознательные это
слово переведут сами.
А теперь очень важное! Если кому­то это покажется очевидным, заранее
извиняюсь ­ как показывает опыт, очень даже многим это далеко не
очевидно.
Если пишем:
ClrScr;
write('Aul');

То имеем чистый экран и надпись на нём. Что и требовалось.
А если пишем вот так:
write('Aul');
ClrScr;

Чистый экран имеем, но, собственно, и всё.
Операторы выполняются по порядку. Сверху вниз. В порядке написания.
Сначала первый, потом второй. Как ещё объяснить?
Ещё раз приношу извинения, если кому­то кажется это тривиальным. До
многих почему­то не доходит, или доходит, но очень медленно. А теперь
вместо
write('Aul');

напишем
write('Aul');
write('Aul');

После запуска программы получим на экране:
AulAul

А теперь слегка изменим программу
29

writeln('Aul');
write('Aul');

Картинка после запуска изменится на более приятную ­
Aul
Aul

После недолгого размышления приходим к выводу ­ отличие оператора
writeln от оператора write заключается в том, что writeln переходит на
новую строку после вывода текста. А если написать вот так:
writeln('Aul');
writeln;
write('Aul');

то между нашими Au! появится пустая строка. Запомним на всякий
случай ­ оператор writeln без параметров (параметрами здесь называется
то, что в скобках) обеспечивает переход на новую строку.
А теперь ещё и ещё раз ­ каждый оператор заканчивается точкой с
запятой. Или ­ операторы между собой разделяются точкой с запятой.
Это уж как вам больше нравится. Естественно, начинающие
программисты регулярно эти точки с запятой пропускают. Программисты
продвинутые тоже, хотя и не так регулярно. Компилятор это замечает и
выдает тревожное сообщение "Error 85: ";"expected". Сообщение в целом
понятное, но вот курсор при этом указывает не на то место, где
пропущена точка с запятой, а на начало следующего оператора. То есть,
если у нас вот такой текст
writeln('a');
writeln('b')
writeln('c');

то курсор будет мигать в начале третьей строки:
writeln('a');
writeln('b')
writeln('c');

Мигающий курсор на бумаге не отображается. Будьте внимательны.

30

Весело, в цветочек
А теперь несколько дополнительных возможностей и несколько новых
слов. Такая программа:
TextColor(Green);
Writeln('Aul');

Получили текст зелёного цвета. Пустяк, а приятно. Обратите внимание,
что Green пишется без кавычек. Можно задать и другие цвета. Вот
полный список, или почти полный
Black
Blue
Green
Cyan
Red
Magenta
Brown
LightGray
DarkGray
LightBlue
LightGreen
LightCyan
LightRed
LightMagenta
Yellow
White

чёрный
синий
зелёный
фигня какая­то синеватая
красный
опять фигня, но красноватая
коричневый
светло­серый
темно­серый
светло­синий
светло­зелёный
нет слов
розовый
смотри выше
жёлтый
белый

Запомните на всякий случай. Позже, в процессе освоения графики,
научимся красить и в более экзотические цвета.
А если сделать вот так:
TextColor(Green+Blink);
Writeln('Aul');

то оно будет ещё и моргать. Вот так тоже неплохо:
GoToXY(10,10);
Writeln('Aul');

31

Надпись переехала в другое место экрана. Первая десятка ­ номер строки,
на которую мы отправились. Всего строк 24. Вторая десятка ­ номер
столбца, или, говоря по­другому, номер символа в строке. Всего их 80.
Поэкспериментируйте. Строки нумеруются сверху вниз. Это, конечно,
естественно ­ пока мы не доберемся до графики. Там нумерующиеся
сверху вниз строки пикселов вызывают некоторое неудобство у
обладающих начальными математическими познаниями и отличающих
ось абсцисс от оси координат ­ извините за умные слова.
И кое­что ещё
Текст, заключённый в фигурные скобки, ­ это комментарии. Компилятор
этот текст игнорирует, как будто его нет совсем. То есть пишется он не
для компьютера, а для человека ­ в первую очередь для вас, когда завтра
вы забудете, что насочиняли вчера.
{ Переходим
в центр
GoToXY(40,12);
Writeln('Aul');

экрана}

32

Глава 2
Переменные
Что такое и зачем
А теперь о главном. Три вещи, по нарастающей сложности, которые надо
понять зарождающемуся программисту ­ переменные, массивы и
указатели. Всё остальное ­ сущая ерунда. Сейчас будут переменные.
Пишем вот такую программу:
program kuku;
uses
Crt;
var
x
begin
ClrScr;
end.

:

integer;

Что мы сделали? Объявили переменную. Имя у неё X. Тип у неё целый,
то есть переменная эта может содержать только целые значения.
Непонятно? Мы ещё на этом задержимся и задумаемся.
А теперь делаем вот так:
x:=5;
writeln(x);

Транслируем, запускаем, смотрим. На экране получилось вот что ­
5
А теперь медленно повторяю : мы
­ объявили переменную целого типа
­ присвоили ей значение
­ вывели значение переменной.
А что такое переменная,собственно? Вопрос из категории ­ а что такое
электричество?
Переменная ­ ключевое понятие программирования. Без неё не обходится
ни один язык. Ну, почти ни один. (Здесь, и далее, а также и прежде под
языками я понимаю только и исключительно языки программирования).
Переменная имеет две составляющих ­ имя и значение. Как известно, на
33

заборе что­то там написано, а лежат там дрова. Так вот, то, что написано
­ это имя переменной, а дрова ­ это значение.
В большинстве языков переменная имеет ещё и третью составляющую ­
тип. В нашем случае тип переменной ­ целое число. То есть поместить в
неё можно только целое число, дробное ­ никак, не говоря уже о всякой
экзотике. Но к экзотике ещё вернёмся, попозже.
Для любознательных ­ тип переменной integer позволяет хранить только
значения от +32768 до ­32767. Причина этого ­ размер два байта. Если
хочется простора, используйте вместо integer тип longint. Он занимает
четыре байта. Посчитайте на досуге, какое максимальное значение в него
поместится. Это сложно, разумеется.
x
: integer; ­ это объявление переменной. Слева от двоеточия имя
переменной. Часто говорят об идентификаторе переменной. Это то же
самое, что имя. Имя может быть написано большими буквами или
маленькими ­ Паскалю всё равно. Два подряд идущих неразъёмных
символа ":=" называются оператором присваивания. Слева от него имя
переменной, справа ­ значение, которой ей присваивается.
А теперь сделаем с переменными что­нибудь полезное. Для начала
объявим три переменные:
var
x,y,z

: integer;

Двум из них присвоим значения:
x:=5;
y:=2;

А почему только двум? Потому что значение третьей переменной мы
сосчитаем. На самом деле не мы, конечно, а компьютер.
x:=5;
y:=2;
z:=x+y;
writeln(z);

Получили ожидаемый результат ­ на экране красуется семёрка. Теперь
быстро осваиваем вычитание и умножение.
34

z:=x­y;
writeln(z);
z:=x*y;
writeln(z);

Соответственно, в результате имеем два и десять.
Осталось четвертое действие ­ деление. С ним сложнее и намного.
Почему? Потому что числа у нас целые, а результат деления целым быть
не обязан ­ от деления пятерки на двойку получим два с половиной. Что
делать? Вспомнить чудное детство в первом классе ­ деление нацело с
остатком. Делим пять на два ­ получаем два и один в остатке.
Попрактикуйтесь в этом деле ­ как ни странно, некоторые напрочь это
забыли. Так что вместо одной операции получаем две ­ деление нацело
и получение остатка.
Сначала деление нацело.
x:=5;
y:=2;
z:=x d i v y;
writeln(z);

В результате имеем два. А теперь получение остатка
x:=5;
y:=2;
z:=x mod y;
writeln(z);

В ответе получаем единицу. Задача сложнее ­ разобрать двузначное
число на составляющие его цифры.
var
x,c1,c2

: integer;

begin
x:=85;
c 1 : = x d i v 10;
c 2 : = x mod 10;
writeln(cl);
writeln(c2);
end.

35

Маленькое новшество ­ имена переменных у нас уже не из одного
символа, а из двух, причём второй символ ­ цифра. Главное, чтобы имя
начиналось с буквы. И само собой, чтобы имена не повторялись.
Обратите внимание на особый смысл выражения x mod 2. Если оно равно
нулю, то число x чётное, если единице ­ то нечётное. Это пригодится
далее.
Переменные можно было бы объявить и каждую в отдельной строке:
var
x
c1
x2

: integer;
: integer;
: integer;

Но мы написали так, как короче. Аналогично можно сэкономить и на
операторах writeln, объединив два в один. Пишем :
writeln(c1,c2);

Запускаем программу на выполнение и получаем не совсем то, что бы
хотелось.
85
Циферки слиплись. С одной стороны, всё правильно ­ вот восьмерка, вот
пятерка ­ но хотелось бы как­то видеть, где кончается одно число, и где
начинается другое. Напишем вот так:
writeln(c1:3,c2:3);

Гораздо лучше. Тройка означает, что мы требуем выделения для вывода
числа трех символов. Число у нас всего из одной цифры, так что впереди
перед ней появились два пробела.
А почему мы должны, глядя на результат работы программы,
догадываться, где у какой переменной значение? Делаем так:
writeln('c1=',

c 1 : 3 , 'c2=',

c2:3);

To, что в кавычках, будет выведено как есть, это мы уже проходили.
Долго думаем о тонкой разнице между c l в кавычках и c l без кавычек. В
кавычках это просто бессмысленный набор символов. Компьютер
36

выведет его так, как он написан. А без кавычек это ­ имя переменной, и
выведено будет её значение. Если это сразу кажется понятным и
очевидным ­ поздравляю...
Вы, конечно, уже заметили, что вывод всё равно не совсем такой, как
хотелось бы ­ опять чего­то слиплось. Но теперь уж постарайтесь
справиться сами.
Ввод и вывод
Напишем программу возведения числа в квадрат.
program Square;
uses
Crt;
var
x, x 2
begin
x:=5;
x2:=x*x;
writeln('x2=',x2);
readln;
end.

: integer;

Всё хорошо, всё работает ­ пятью пять двадцать пять. А если надо
шестью шесть тридцать шесть? Менять текст программы, снова
компилировать ­ кажется как­то не очень правильным. Хотелось бы,
чтобы программа при запуске спросила, а какое, собственно, число нам
надо возвести в квадрат.
Легко. Вместо
x:=5;

пишем
readln(x).

Теперь при запуске программы видим чёрный экран. Программа упорно
ждёт, когда мы наберем на клавиатуре число, хотя бы ту же пятерку, и
нажмём Enter. Тоже как­то не очень удобно. Мы­то, конечно, знаем, что
надо делать, а если вдруг кто­то непонятливый к компьютеру подойдёт?
Улучшаем программу. Пишем:
37

write('x =
readln(x).

Обратите внимание, что написано не writeln, а именно write.
Исключительно из эстетических соображений. Иначе мы бы перешли на
новую строку и уже там набирали вводимое число. Число ввелось бы,
конечно, но выглядит это как­то не очень аккуратно. Ещё обратите
внимание на пробел после равенства. Исключительно из тех же
эстетических соображений. В результате наконец­то получили первую
хоть сколько­то полезную программу.
Делаем вывод ­ порядочная программа должна:
­ ввести данные ­ предварительно предложив их, данные, ввести;
­ что­то с ними сделать;
­ вывести результат.
А теперь немного о грустном. Мы пока что работаем только с целыми
числами. Если попытаться ввести вместо пяти пять с половиной,
результат будет катастрофическим ­ попробуйте. Если в процессе ввода
промахнуться мимо нужной клавиши и набрать вместо цифры букву ­ та
же самая катастрофа. Что делать? С непопаданием по клавишам ­ пока
ничего. Внимательнее смотрите на клавиатуру. Разберёмся с этой
проблемой попозже.
Дроби
Так что насчёт пяти с половиной? Это не просто, а очень просто. Меняем
всего одну строку. Вместо
var
x

: integer;

пишем
var
x

: single;

и наступает полное дробное счастье ­ ну почти. Почему счастье не совсем
полное?
38

Single ­ дробное число, занимающее четыре байта. Дробные числа обычно
называют плавающими или числами с плавающей точкой. Когда­то,
давным­давно, были ещё и числа с фиксированной точкой, например, три
знака до точки, два после и никак иначе. Числа с плавающей точкой
отличались от чисел с фиксированной точкой тем, что помнили
определенное количество знаков (single ­ шесть, семь, как повезёт) ­ а
точка могла стоять где угодно. То есть, плавающее число помнит шесть
знаков ­ 123456, но это может быть 123.456 или 0.0123456 или 123456000.
А седьмой знак тут уже никак поместится не может.
Если хочется большей точности, single можно заменить на double (восемь
байтов). Как очевидно из английского языка, число значащих цифр
удвоится. В неновых/старых учебниках часто встречается также single ­
шестибайтовое плавающее. Это атавизм ­ забудьте. Обратите внимание,
что у целых переменных при изменении количества байт на число
меняется максимальная величина, которое это число может содержать, а у
дробных ­ ещё и точность представления числа.
Во­первых, дробные числа вводятся (и выводятся) с точкой в качестве
разделителя. То есть, пресловутые пять с половиной будут выглядеть так:
5.5, а не 5,5.Потом, в реальной жизни реального программиста, всё будет
гораздо сложнее, но пока что именно так.
В качестве разделителя целой и дробной части в Турбо Паскале
используется всегда точка, независимо от того, какой разделитель
установлен в Windows.
Во­вторых, оператор writeln(x), оставленный без присмотра. выведет на
экран что­то страшное и ужасное. На самом деле, никакое оно не
страшное и, даже, где­то полезное, но разбираться с ним (пока, по
крайней мере) не будем. Будем улучшать. Напишем
writeln(x:8:2);

Очень напоминает вывод целых чисел ­ там тоже первая и единственная
цифра задавала общее количество выводимых цифр. Если реально цифр
было меньше, спереди выводимое число дополнялось пробелами. Но
сейчас для нас интереснее вторая цифра. Двойка указывает, сколько
знаков после десятичной точки должно быть выведено. И опять очень
39

сложный момент. Восьмерка здесь ­ сколько знаков всего выводится ­ до
точки плюс после точки плюс один знак на саму точку.
А теперь у нас появилась ещё одна арифметическая операция ­ обычное
деление, не нацело. Обозначается оно так:
x:=y/z;

сейчас будет ещё одна программа, с небольшим отличием. В ней
используется константа, то есть число, значение которого мы заранее
знаем и, в процессе выполнения программы, измениться это значение не
может. Чем она ­ константа ­ собственно, и отличается от переменной. А
чем она отличается от просто числа? ­ тем, что имеет имя.
Если мне не изменяет память, константы делятся на константы
именованные и неименованные. То есть, термины могут быть другими, но
смысл остаётся. Сейчас мы встретимся с неименованной константой, а до
именованных доберёмся в процессе освоения массивов.
program C i r c l e ;
uses
Crt;
var
R,S
: single;
begin
w r i t e l n ( 'Radius =
readln(R);
S:=3.14*R*R;
writeln('S=',S:8:2);
readln;
end.

Здесь 3.14 ­ число % ­ константа. На всякий случай, если кто­то не понял,
мы только что вычислили площадь круга.
Идею поняли. А теперь ­ быстро пишем программу, которая считает, что
мы заработаем, если положить в банк X рублей под Y процентов на один
год. Для тех, кто совсем никак, дальше искомая программа. Но всё­таки,
постарайтесь сначала написать сами.
p r o g r a m Money;
uses
Crt;

40

var
Rub, r a t e ,

HowMany : s i n g l e ;

begin
w r i t e l n ( 'Rubles = ' ) ;
readln(Rub);
w r i t e l n ( 'Rate = ' ) ;
readln(rate);
HowMany:=Rub + ( R u b * r a t e ) / 1 0 0 ;
writeln('Result=',HowMany:8:2);
readln;
end.

Ещё немного математики. В школе кроме четырех действий что­то
говорилось и про пятое ­ логарифмирование. Вспоминается также и
тригонометрия с синусами, косинусами и прочими тригонометрическими
функциями. В Паскале это всё есть и выглядит очень похоже на обычные
математические формулы:
y:=Sin(x);
y:=Cos(x);
y:=Ln(x);

И называется это так же, как и в математике ­ функции. В Паскале есть
много уже готовых функций, а попозже будем осваивать и
самостоятельное их изготовление.
И ещё о важном. Например, у нас есть объявление переменных
var
x
n

: single;
: integer;

В начале нашей программы следуют вот такие операторы присваивания:
x:=3.5;
n:=4;

До сих пор всё хорошо и понятно.
Теперь, если мы напишем
x:=n;

то по­прежнему всё будет хорошо. Но если мы напишем
41

n:=x;

то мы даже не дойдем до этапа выполнения программы. В процессе
трансляции нам сообщат, что мы глубоко не правы:
Error 26: Type mismatch
Это надо постараться или запомнить, или как­то понять. Чтобы
запомнить ­ учим мантру: дробному целое присвоить можно, целому
дробное присвоить нельзя. Постараться понять ­ на уровне для
блондинок: у дробного есть ящички под целые и дробные части, а у
целого ­ только один ящичек ­ для части целой. Поэтому, если мы
пихаем целое в дробное, то оно из одного ящичка в два влезет, а если,
наоборот, из двух ящичков в один ­ ну никак.
А вообще всё даже проще ­ в Паскале в принципе нельзя присваивать
друг другу переменные разных типов. Таков Великий Замысел Отцов­
Основателей. Единственная данная ими поблажка ­ дробному числу
можно присвоить целое. Вот и всё.

42

Глава 3
У с л о в н ы е операторы
Что такое и зачем
Программа выполняется сверху вниз, я уже говорил. А если надо не
совсем сверху вниз, а зигзагом? Для этого у нас есть условные операторы.
Условный оператор ­ это очень просто. Покажем на примере. Программа
определяет, положительное ли у нас число, или отрицательное, и выдаёт
соответствующее сообщение.
program P l u s ;
uses
Crt;
var
num
: integer;
begin
w r i t e l n ( 'num = ' ) ;
readln(num);
i f num > 0 t h e n w r i t e l n ( ' p o s i t i v e ' ) ;
i f num < 0 t h e n w r i t e l n ( ' n e g a t i v e ' ) ;
readln;
end.

На интуитивном уровне вроде понятно. Теперь подробнее ­ из чего
состоит условный оператор.
Слово I F ­ в начале. Дальше условие. Условие у нас пока что самое
простое ­ два числовых выражения, разделенные знаком сравнения.
Знаки бывают вот такие:
> < ­ это понятно, больше и меньше
>= 0 t h e n

writeln('positive');

пишем
num>0 t h e n b e g i n
TextColor(Red);
writeln('positive');
end;
if

Появилась новая конструкция языка, которая в дальнейшем будет
употребляться очень­очень часто. Там где можно употребить один
оператор, всегда ­ почти ­ можно употребить несколько, поставив перед
первым begin, а после последнего оператора end;. Перечитал и испугался
­ точка здесь исключительно в предложении, а не в синтаксисе Паскаля!
По научному это называется операторные скобки.
Операторы,
заключенные внутри пары begin­end могут быть любыми, в том числе
условными операторами, содержащими begin­end, внутри которых...
И ещё. До сих пор у нас был только один end в конце программы, после
которого стояла точка. После нашего нового end'a ставится точка с
запятой. Это как раз типично, a end с точкой ­ аномалия. Разумеется, наш
исходный условный оператор
if

num>0 t h e n

writeln('positive');

можно записать и так
44

num>0 t h e n b e g i n
writeln('positive');
end;
if

Кому что больше нравится.
А теперь что делать, если нам недостаточно простой проверки на
положительность, а хочется чего­нибудь эдакого ­
например
положительное, но не больше ста.
Важно! ­ если у нас условий несколько ­ два, например ­ каждое из них
должно быть взято в скобки! В особенности это важно потому, что при
отсутствии скобок сообщение компилятора об ошибке является
неадекватным и вводящим в заблуждение, понять по нему, в чём дело
трудно. Разумеется, у компилятора на это есть весьма уважительные
причины, но я их не знаю.
Итак, имеем два условия:
(num>0)

(num0) a n d

(num0) a n d (num100) то же самое, что (x=0) and (okCount=6)
and
(okCount=9) and (okCount a [ i + 1 ] then begin

80

ok:=0;
{поменять
местами}
wrk:=a[i];
a[i]:=a[i+1];
a[i+1]:=a[i];
end;
end;
u n t i l ok=1;

Прикрутите в начало заполнение массива случайными числами, вывод до
сортировки и вывод после неё и любуйтесь результатом.
Как не делать ничего
У нас уже была программа, которая не делает ничего и строка, которая не
содержит ни одного символа. Двинемся дальше по этому заманчивому
пути ­ теперь у нас оператор, который не делает ничего. Как он
называется? Пустой оператор. Как он выглядит? Да никак. Точнее есть
две школы. Согласно первой, пустой оператор состоит из точки с запятой.
Согласно второй школе, пустой оператор фигуры не имеет и места не
занимает.
Иллюстрирую на примере. Это без пустого оператора:
writeln('qwerty');
А это с пустым оператором:
writeln('qwerty');;
Вот то, что находится между двумя точками с запятой и есть пустой
оператор. Что значит, там ничего нет? Ну да, ничего нет, так он ничего и
не делает. Вообще­то, Паскаль несколько чувствительно относится к
лишним точкам с запятой. Точнее, не то, чтобы чувствительно, а в
строгом соответствии с синтаксисом языка. Точка с запятой между begin
и end. Это, как и обещано, пустой оператор. А вот лишняя точка с запятой
в декларативной части программы, между program и begin, будет жестока
караться и преследоваться.
Для чего пустой оператор нужен? Для чего хотите. Я сам не хочу,
совершенно. А где его можно использовать? Пустой оператор может
использоваться во всех местах программы, где может использоваться
любой другой оператор. Сначала, плохие примеры. Некоторые пишут вот
так:
81

if

x>0 then y:=100

else;

Это глупость, но безвредная. Убираем лишнее, остаётся
if

x>0 then y:=100;

А некоторые пишут вот так:
if

x>0 then e l s e

y:=200;

Поскольку перед else точка с запятой не ставится, пустой оператор
уменьшился до своего естественного вида, то есть до никакого. Так вот,
это тоже глупость, но очень вредная. Программиста надо долго бить по
рукам, затем повернуть ему мозги набок и написать так:
if

x ' ') t h e n o n l y S p a c e s : = 0 ;
end;
i f onlySpaces = 1
then w r i t e l n ( ' Y e s l ' )
else writeln('Nol);

А теперь доведём наш шедевр до полного блеска
переменными. Сначала теория, разумеется. Объявляем:

булевскими

var
onlySpaces: boolean;

Булевская переменная может получать только два значения:
onlySpaces:=true;
onlySpaces:=false;

ИЛИ

{ да]
{нет]

Использовать переменную в условном операторе можно аналогично
целой:
if

onlySpaces = t r u e

{ да]

ИЛИ

99

if

onlySpaces = f a l s e

{нет].

Ho так никогда не пишут, поскольку ради булевских переменных
разрешена, для наглядности, сокращённая форма записи:
if
if

onlySpaces
not onlySpaces

{ да]
{нет].

ИЛИ

Окончательный вариант ­ onlySpaces объявлено как boolean:
onlySpaces:=true;
f o r i : = 1 t o L e n g t h ( s ) do b e g i n
i f ( s [ i ] < > ' ') t h e n o n l y S p a c e s : = f a l s e ;
end;
i f onlySpaces
then w r i t e l n ( ' Y e s l ' )
else writeln('Nol);

Обратите внимание ­ это важно! Если нам надо определить, есть ли в
строке хоть один пробел (для умных ­ квантор существования), то
признак перед началом цикла устанавливается в ложь (false), а при
встрече первого пробела устанавливается в истину (true). Напротив, если
надо определить, все ли символы в строке пробелы (для умных ­ квантор
общности), то символ устанавливается перед циклом в истину, а при
встрече первого не пробела устанавливается в ложь.
Считаем, наконец, слова
Строка состоит из слов, разделённых пробелами. Символы, из которых
состоят слова ­ не пробелы. То есть, запятая у нас не разделитель, а часть
слова. Впрочем, решение (когда мы его получим) тривиальным образом
обобщается на совершенно произвольный набор разделителей.
Сначала решение на пальцах. Обнуляем счетчик слов. Движемся по
строке от начала до конца. Как только начинается новое слово,
увеличиваем счетчик на единицу.
Очень хорошее решение, главное ­ абсолютно правильное. Или выглядит
правильным. Осталось разъяснить не вполне ясные моменты, до полного
превращения их, неясных моментов, в моменты абсолютно ясные. Если
вдруг окажется, что какой­то неясный момент не хочет разъясняться,
значит, мы были неправы, и надо начинать сначала. Это ещё называется
метод пошаговой детализации. Он же разработка сверху вниз.
100

Неясный момент у нас ровно один ­ как понять, что началось новое
слово? Как вариант ­ встретился символ, в смысле не пробел, а до того
был пробел ­ значит новое слово. Логично, но если перед первым словом
нет ни одного пробела, получается особый случай. А особый случай на то
и особый, что обрабатывать его приходится особо. Это плохо, не надо
нам особых случаев.
Введём логическую переменную ­ есть слово или нет. Что значит есть?
Есть ­ значит, слово встретили и движемся по нему. То есть, текущий
символ, тот на который указывает переменная цикла ­ не пробел. Тогда
наша логическая переменная имеет значение истина. А если движемся по
пробелам, переменная имеет значение ложь. Таким образом, если
переменная ложна и встретился не пробел, то мы нашли новое слово.
Думаем дальше. Наша логическая переменная принимает два значения,
как ей и положено. Наш текущий символ, по сути, тоже принимает только
два ­ или пробел, или не пробел. Подробности, какой именно не пробел,
то есть, какой именно символ, нам совершенно не интересны. Итого
имеем ­ два умножить на два ­ четыре сочетания.
Объявим переменные, напишем цикл по строке и по условному оператору
на каждый вариант. А там поглядим.
s
inWord
count
i

:
:
:
:

s t r i n g ; {строка для разбора]
boolean;{признак ­ в слове или нет]
integer;{peзyльтaт ­ количество слов]
integer;{это, понятно, переменная цикла]

count:=0;
inWord:=false;
f o r i : = 1 t o L e n g t h ( s ) do b e g i n
i f i n W o r d a n d ( s [ i ] = ' ') t h e n b e g i n
{в слове и пробел]
end;
i f i n W o r d a n d ( s [ i ] < > ' ') t h e n b e g i n
{в слове и не пробел]
end;
i f n o t i n W o r d a n d ( s [ i ] = ' ') t h e n b e g i n {не в слове и пробел]
end;
i f n o t i n W o r d a n d ( s [ i ] < > ' ') t h e n b e g i n { H e в слове и не пробел]
end;
end;

101

Теперь опять думаем. Как будет выполняться наша программа? Рано или
поздно встретится первый не пробел. Это ситуация номер четыре. Наша
реакция? Значит, началось новое слово ­ следовательно, увеличиваем
счётчик на единицу. Слово началось, слово когда­то и кончится. То есть,
мы находимся внутри слова и внезапно встретили пробел. Это ситуация
номер один. Наша реакция ­ установить логическую переменную в ноль.
Смотрим, что получилось.
count:=0;
inWord:=false;
f o r i : = 1 t o L e n g t h ( s ) do b e g i n
i f i n W o r d a n d ( s [ i ] = ' ') t h e n b e g i n
{в слове и пробел]
inWord:=false;
end;
i f i n W o r d a n d ( s [ i ] < > ' ') t h e n b e g i n
{в слове и не пробел]
end;
i f n o t i n W o r d a n d ( s [ i ] = ' ') t h e n b e g i n { H e в слове и пробел]
end;
i f n o t i n W o r d a n d ( s [ i ] < > ' ') t h e n b e g i n { H e в слове и не пробел]
inWord:=true;
count:=count + 1;
end;
end;

А дальше? А всё. Оставшиеся две ситуации нас не интересуют. А зачем
мы их вообще выделили? Во­первых, для наглядности. Во­вторых, сейчас
мы исходную задачу усложним.
Кстати, вспомогательная информация ­ время от времени встречаются
операции типа увеличить счетчик на единицу. На этот случай
предусмотрен особый оператор. Вместо count:=count + 1; можно написать
Inc(count). Разумеется, предусмотрен и обратный вариант. Dec(count)
уменьшит нашу переменную на единицу. И ещё, секций VAR может
быть несколько. Только зачем?
А пока, оформите наш программный код в виде законченной программы
и протестируйте её.
А теперь усложняем. Вернее, доводим до практического применения.
Теперь наша задача ­ извлечь из строки слово с заданным номером. Если
нет слова с заданным номером ­ вернуть пустую строку. Несколько позже
мы (вы) оформим (оформите) это в виде процедуры (функции).
Почти все переменные у нас уже есть, объявляем недостающее:
102

var
needCount
theWord

: i n t e g e r ; { H O M e p нужного
слова]
: s t r i n g ; {результат
­ то самое

слово]

Теперь надо как­то определиться с новыми переменными. theWord перед
циклом должна быть проинициализирована пустой строкой, а значение
needCount где­то перед этим уже введено пользователем программы.
Снова глядим на уже обработанные ситуации.
Номер четыре ­ началось новое слово. В дополнение к тому, что уже там
есть ­ проверить, а вдруг номер начавшегося слова совпадает с номером
слова заказанного? Тогда текущий символ отправляем в результат. Номер
один ­ ничего не изменилось. Кончилось слово, и ладно.
Первый символ слова мы уже пристроили. А остальные? Ясно, что номер
третий для наших новых целей совершенно ни к месту. А вот номер два
подойдёт ­ если мы по­прежнему в слове и номер слова тот, что надо,
приклеиваем к результату текущий символ.
count:=0;
inWord:=false;
theWord:='';
f o r i : = 1 t o L e n g t h ( s ) do b e g i n
i f i n W o r d a n d ( s [ i ] = ' ') t h e n b e g i n
{в слове
inWord:=false;
end;
i f i n W o r d a n d ( s [ i ] < > ' ') t h e n b e g i n {в слове
i f count = needCount
then theWord:=theWord + s [ i ] ;
end;
i f n o t i n W o r d a n d ( s [ i ] = ' ') t h e n b e g i n
{не
end;
i f n o t i n W o r d a n d ( s [ i ] < > ' ') t h e n b e g i n
{не
inWord:=true;
c o u n t : = c o u n t + 1;
i f count = needCount
then t h e W o r d : = s [ i ] ;
end;
end;

и

пробел]

и не

пробел]

в слове

и

пробел]

в слове

и не

пробел]

Доделайте. Проверьте. Подумайте, как быть, если разделитель может
быть не один? Например, пробел и запятая? Никаких проблем? А если
разделители ­ пробел, запятая, точка, двоеточие, тире, точка с запятой,
восклицательный знак, вопросительный знак?
103

Глава 7, п р о д о л ж е н и е пятой
Е щ ё ц и к л ы и массивы
Массивы двумерные и далее
Массивы без циклов ­ вещь бесполезная, это я уже говорил, а двумерные
массивы без вложенных циклов ­ ну даже и не знаю, нечто бесполезное в
квадрате, и в высших степенях, в зависимости от размерности массива.
Но раз я решил излагать в таком порядке ­ циклы сначала, массивы потом
­ значит так тому и быть.
Три составляющие применения любых данных ­ как объявить, как
присвоить и как вывести. Ещё неплохо понять, что это вообще такое и
зачем надо. Что такое и зачем целые и дробные переменные ­ понятно.
Строки ­ в целом тоже. А массивы, тем более двумерные?
Массивы, как было упомянуто ранее, используются для хранения и
обработки большого количества однотипных данных. Пример из любого
учебника ­ если наша программа помнит и что­то делает с температурой
воздуха сегодня, вчера и позавчера, достаточно объявить три переменные
типа single. Если надо обрабатывать температуру за месяц, то объявлять
тридцать одну переменную ­ ну неправильно это как­то.... Для этого
массивы есть ­ array[1..31] of single;.
А если всё ещё хуже и требуется хранить температуру не просто по
каждому дню, но ещё и по каждому часу дня? ­ оцените, как плавно и
ненавязчиво я подвёл разговор к теме двумерных массивов. Объявляем
двумерный массив ­ почти как одномерный
var
deg

: array[1..31,1..24] of single;

of single ­ это понятно, массив состоит из дробных чисел. А вот в
квадратных скобках содержимое удвоилось. Индексов стало два. Первый
может меняться от 1 до 31 ­ это, как можно догадаться, дни. Второй, от 1
до 24, очевидно часы. А сколько всего элементов массива? Правильно, 31
умножить на 24 равняется 744.
Теперь присваиваем
deg[1,1]:=10.5;

104

deg[31,24]:=12.5;

Всё точно так же, как и с одномерным массивом, только работать
вручную стало ещё неудобнее ­ элементов в массиве намного больше. С
выводом разберитесь сами. Переходим к циклам.
Вложенные циклы
Напоминаем. Вот цикл:
i : = 1 t o 3 do b e g i n
writeln('Au');
end;
for

Внутри цикла может находиться любой оператор, или несколько
операторов. А раз любой ­ значит, этим оператором может быть и
оператор цикла, внутри которого может быть... и так далее. Называется
это ­ вложенный цикл. Тот цикл, что снаружи ­ внешний цикл, тот цикл,
что внутри ­ внутренний цикл. На самом деле, не внутри оператора
цикла, а внутри операторных скобок, ­ но какая разница...
Например, можно вот так:
i : = 1 t o 3 do b e g i n
f o r j : = 1 t o 3 do b e g i n
writeln('Aul');
end;
end;
for

После запуска получим:
Au!
Au!
Au!
Au!
Au!
Au!
Au!
Au!
Au!
Очень важно! Во внешнем и внутреннем циклах переменные цикла
должны быть обязательно разные! По традиции, это I и J.
105

Как нетрудно убедиться, наша строка выведена ровно девять раз.
Внешний цикл выполняется три раза, при каждом его выполнении три
раза выполняется внутренний цикл ­ три раза по три будет девять. А если
написать вот так:
i : = 1 t o 9 do b e g i n
writeln('Aul');
end;
for

получим абсолютно тот же результат. Так зачем два цикла, если можно
один? Совершенно верно, в данном конкретном случае абсолютно
незачем. Слегка подправим программу:
i : = 1 t o 3 do b e g i n
f o r j : = 1 t o 3 do b e g i n
writeln('Aul');
end;
writeln;
end;
for

Получим вот что:
Au!
Au!
Au!
Au!
Au!
Au!
Au!
Au!
Au!
Три группы по три строки, разделённые пустой строкой. Конечно, можно
было бы слегка извратиться и обойтись один циклом ­ где­то вот так:
i : = 1 t o 9 do b e g i n
writeln('Aul');
i f ( i mod 3) = 0 t h e n
end;
for

writeln;

106

Но лично мне кажется, что вариант с двумя циклами естественнее ­ но
допускаю, что я неправ. Тогда ещё чуть­чуть усложним ­ пусть в начале
каждой непустой строки стоит номер группы, к которой она относится.
i : = 1 t o 3 do b e g i n
f o r j : = 1 t o 3 do b e g i n
writeln(i,'.Au!');
end;
writeln;
end;
for

На выходе:
1.Au!
1.Au!
1. Au!
2. Au!
2.Au!
2. Au!
3. Au!
3.Au!
3.Au!
Можно, конечно, ещё слегка извратиться и опять обойтись одним циклом,
но, по­моему, не стоит. Теперь простенькая программка. Смысл очевиден
­ выводим значения переменных цикла, смотрим, как они меняются и
думаем.
i : = 1 t o 3 do b e g i n
f o r j : = 1 t o 3 do b e g i n
writeln('i=',i, '
end;
writeln;
end;
for

i=1
i=1

j=1
j=2

i=1

j=3

i=2

j=1

j= ' , j ) ;

107

i=2
i=2

j=2
j=3

i=3
i=3
i=3

j=1
j=2
j=3

Если Вам это совершенно очевидно, то очень хорошо, иначе ­ думаем,
думаем... А теперь пишем три вложенных цикла, выводим значения
управляющих ими переменных и снова думаем, думаем... По традиции,
третью переменную цикла зовут K.
Пример посложнее
Номер 121 из задачника Культина. Вывести таблицу умножения девять на
девять в виде квадрата, она же почему­то называется таблицей Пифагора.
Берём тетрадку в клеточку, переворачиваем обратной стороной и вот она,
таблица:

1
2
3
4
5
6
7
8
9

1

2

3

4

5

6

7

8

9

1
2
3
4
5
6
7
8
9

2
4
6
8
10
12
14
16
18

3
6
9
12
15
18
21
24
27

4
8
12
16
20
24
28
32
36

5
10
15
20
25
30
35
40
45

6
12
18
24
30
36
42
48
54

7
14
21
28
35
42
49
56
63

8
16
24
32
40
48
54
64
72

9
18
27
36
45
54
63
72
81

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

108

Начнем с изготовления того, что внутри у таблицы, а строчку сверху и
столбец слева, обозначающие, что на что умножать, пририсуем после.
Первым делом, надо решить вопрос, как будем рисовать ­ по строкам или
по столбцам? Мне кажется, что лучше построчно, аргументировать не
буду. Внешний цикл выполняется девять раз, и каждый раз при этом
выводится по одной строке, внутренний цикл выполняется девять раз и
при этом каждый раз выводится по одному числу. Проверка: девять на
девять равняется восемьдесят один, а в таблице чисел как раз столько. Я
проверял. Всё сходится. Так что имеем:
i : = 1 t o 9 do b e g i n
[цикл по строкам}
f o r j : = 1 t o 9 do b e g i n
[цикл по
столбцам}
[здесь что­то
делаем}
end;
end;
for

Теперь не хватает только того, что должно быть внутри цикла ­ это и есть
самое главное, правда. Начнем с малого и простого ­ выведем самую
первую строку, ту в которой числа 1,2,3...9. Тут всё хорошо. Цикл от
одного до девяти у нас уже есть, значение того, что надо вывести,
удивительным образом совпадает со значением переменной цикла.
Поскольку все числа выводятся в одной строке, подойдёт оператор
write(i); не переходящий после вывода на новую строку. Чтобы все числа
не склеились в одно длинное слово, укажем количество выводимых
знаков ­ у нас это будет четыре ­ одна/две цифры и три/два пробела
спереди. А поскольку на следующую строку перейти всё­таки когда­то
надо, добавим после завершения внутреннего цикла оператор writeln; без
параметров.
i : = 1 t o 9 do b e g i n
[цикл по строкам}
f o r j : = 1 t o 9 do b e g i n
[цикл по
столбцам}
write(j:4);
end;
writeln;
end;
for

В результате получили повторенную девять раз одну и ту же строку ­
1 2 3
1 2 3
1 2 3
и т.д.

4
4
4

5
5
5

6
6
6

7
7
7

8
8
8

9
9
9
109

Логично, ведь от переменной I , управляющей выводом по строкам, наш
оператор write(i:4); никак не зависит. А как он должен зависеть?
Элементарно ­ число на пересечении I­й строки и J­ro столбца
представляет собой их произведение. Делаем вот так ­ write(i*j:4);
Гораздо лучше ­
1 2
2 4
3 6

3 4 5 6 7 8 9
6 8 10 12 14 16 18
9 12 15 18 21 24 27

Собственно, то, что и хотели получить.
Добавить строчку сверху труда не составляет, надо только не забыть
добавить соответствующее число пробелов перед ней и пустую строку
после неё.
write('
' ) ;
f o r j : = 1 t o 9 do
write(j:4);
writeln;
writeln;

Переменная цикла J использована из принципиальных соображений ­ раз
она у нас в основном цикле отвечает за перемещение по горизонтали ­
так путь отвечает за это везде. begin и end отсутствуют, напоминая, что
можно и так. Почему writeln в двух экземплярах догадайтесь сами.
Теперь добавим вертикальную строчку. Вывести её в отдельном цикле ­
мысль не очень хорошая ­ а почему, в чём проблема?. Можно конечно
теоретически использовать GoToXY, но зачем нам лишние приключения?
Рассуждаем так. Где должна стоять цифра? Перед строкой с
результатами. Одна строка ­ одна цифра. Перед строкой ­ значит перед
циклом, выводящим строку. Цифра соответствует номеру строки,
который соответствует значению переменной цикла I . И вот, наконец,
программа:
write('
' ) ;
f o r j : = 1 t o 9 do
write(j:4);
writeln;
writeln;
for

i:=1

t o 9 do b e g i n

[цикл no

строкам}

110

write(i:4);
f o r j : = 1 t o 9 do b e g i n
write((i*j):4);
end;
writeln;
end;

[цикл по

столбцам}

А теперь, самостоятельно, бодро­весело рисуем шахматную доску. И
подумайте, как посимпатичнее выбирать, в какой цвет какой квадрат
покрасить. Посимпатичнее, не в смысле, чтобы красный и зеленый, а
чтобы соответствующий условный оператор был бы
не слишком
страшен. Если сделаете легко и быстро, то напишите на каждой клетке её
обозначение ­ E2, E4 и тому подобное.
О самом важном. Всё сразу и побольше
Циклы и массивы созданы друг для друга. Двумерные массивы и
вложенные циклы ­ вообще, наверное, символ мировой гармонии. Друг
без друга им просто не жить.
Сначала несколько простых примеров, из серии это должен
каждый. Массив предполагается объявленным как
var
a

: array[1..N,1..M]

of integer;

To есть, массив вовсе даже не квадратный ­ обратите внимание!
Задача номер раз. Заполнить массив нулями:
for

i : = 1 t o N do
f o r j : = 1 t o M do
a[i,j]:=0;

Номер два. Посчитать сумму элементов массива:
sum:=0;
f o r i : = 1 t o N do
f o r j : = 1 t o M do
sum:=sum + a [ i , j ] ;

Три. Найти минимальный элемент массива:
min:=a[1,1];

111

знать

for

i : = 1 t o N do
f o r j : = 1 t o M do
i f a [ i , j ] < min
then m i n : = a [ i , j ] ;

Наверное, вам уже всё понятно. Вообще, двумерные массивы очень
хорошо проецируются на математические реалии. Двумерный массив ­
это матрица, а количество математических манипуляций с матрицами
воистину безгранично и даёт очень важные и полезные результаты. А ещё
это напоминает мне о молодости и курсе высшей алгебры и, вообще, небо
тогда было голубее, трава зеленее, но девки сейчас лучше! Или, говоря по
другому, лучше сейчас!
А Вам две задачки для самостоятельной работы.
Первая. Повернуть квадратный массив набок. То есть, было:
I 2 3
4 5 6
9 8 9
Должно получиться:
9 4 1
8 5 2
9 6 3
Вторая задача сложнее. Заполнить квадратный массив змейкой, начиная с
левого нижнего угла. Для массива размерностью 5x5 на выходе будет:
II
10
4
3
1

19 20 24 25
12 18 21 23
9 13 17 22
5 8 14 16
2 6 7 15

Разумеется, ваша программа должна работать с квадратным ­ только
квадратным ­ массивом любой размерности. Как это технически будет
реализовано? Как уже сказано, в Паскале размерность массива ­
константа. В некоторых других языках в этом месте может быть и
переменная, но у нас допустима только константа. Что делать? Например,
112

объявить большой­большой массив, сто на сто, например, и передать в
процедуру в качестве параметра его фактическую размерность, то есть
какую часть из него заполнять. Некрасиво? Учите Фортран, там и
переменные в этом качестве разрешены.
Другие циклы
Циклы, с которыми мы встречались до сих пор, в одном отношении были
совершенно одинаковы. Они могли выполняться пять раз, десять раз, сто,
тысячу или сто тысяч миллионов раз, выполняться от единицы до
миллиона или от миллиона до единицы, но число выполнений цикла нам
было известно заранее, ещё при написании программы.
Упрощаю, конечно. Число выполнений цикла могло и не быть
константой. Могло быть и переменной. Но с математической точки
зрения ­ какая разница? Всё равно значение переменной этой известно до
начала выполнения цикла. Это важно ­ количество выполнений цикла
нельзя изменить внутри цикла. Прощу прощения у чистых математиков за
вольную трактовку понятий переменной и константы. Конечно, если они,
чистые математики, их вообще понимают... Чистые это не в смысле не
грязные, это в смысле не прикладные.
А если число выполнений заранее неизвестно? А почему? Самый частый
случай ­ когда есть живой пользователь. Например, запрограммировали
мы игрушку. И пользователь наш радостно долбит по клавишам,
устанавливая справедливость в одном отдельно взятом подвале. И когда
он, зараза, утомится долбить по клавишам и захочет выйти из цикла и из
игры, предугадать нам не дано. Вот тут и нужен цикл, который
выполняется столько раз, сколько не знаем заранее.
К монстрам, подвалам и справедливости вернемся позже, а пока
попытаемся придумать более удобное для программиста, то есть без
живого пользователя, применение для такого рода циклов. К сожалению,
на ум идёт исключительно математика, причем не простая, а всё как­то
высшая. Постараемся, однако, обойтись без жертв. Напишем цикл, самый
обычный:
f o r i : = 1 t o 5 do
writeln((1/i):5:3);

113

На выходе получим
1.000
0.500
0.333
0.250
0.200
Если бы мы вывели эти значения в виде не десятичных дробей, а
натуральных, то получили бы следующее:
1, /

1

/3,

1

/5

Эта последовательность называется гармоническим рядом. Как её
продолжить дальше, вопросов не вызывает ­ V , V , V ...У неё есть одна
особенность ­ если сложить достаточное количество его членов, то в
сумме можно получить любое заранее заказанное число.
6

7

8

Чуть математики. Это значит, что ряд расходится. Полюбуйтесь на ряд 1,
1/4, 1/8, 1/16... Он сходится. Это не просто значит, что он не
расходится. Это значит, что его сумма бесконечно и сколь угодно близко
приближается к некоему числу. Осознание и даже доказательство этого
факта лежит в пределах понимания любого не совсем деградировавшей
особи. Слово особь не ругательное, а использовано исключительно для
исключения возможности какой­либо тендерной сегрегации. Слово
гендерный тоже не ругательное. Кстати, ряды бывают ещё не
расходящимися и не сходящимися одновременно.
Поставим себе такую задачу, вовсе не искусственную, а вполне себе
встречающуюся в реальной жизни. Ввести какую­то значение (сумму), а
затем определить, сколько элементов ряда нам надо просуммировать,
чтобы эту сумму достичь. Начинаем с простого ­ объявляем необходимые
переменные, вводим ввод и выводим вывод:
program
uses

series;

Crt;
var
sum
factSum
x
howMany

s i n g l e ; [сколько
s i n g l e ; [сколько
s i n g l e ; [текущий
integer;[ответ ­

114

надо получить
в сумме}
фактически
имеем в сумме}
член ряда}
сколько членов
просуммировали}

i

: i n t e g e r ; [ a это переменная
цикла,
[вообще не
понадобится}

которая

нам }

begin
ClrScr;
write('Sum =
readln(sum);
[а вот

тут получаем

writeln('how

ответ}

many = ' howMany);

end.

Если выразить человеческим языком то, что от нас требуется, получим
примерно следующее: вначале ответ равен нулю. Затем, пока сумма
меньше требуемой, вычисляем очередной член ряда, прибавляем его к
сумме, увеличиваем ответ. Так до тех пор, пока фактическая сумма не
превысит сумму требуемую. Обратите внимание на выделенные слова.
Теперь запишем это на Паскале, и будем разбираться.
howMany:=0;
factSum:=0;
w h i l e ( f a c t S u m < sum) do b e g i n
howMany:=howMany + 1 ;
x:=1/howMany;
f a c t S u m : = f a c t S u m + x;
end;

Извлекаем самое важное:
w h i l e (условие) do b e g i n
[чего­то делаем}
end;

Условие здесь ­ самое обычное условие, такое же, как и в условном
операторе. То, что внутри цикла (там, где сейчас комментарий), будет
выполняться до тех пор, пока условие истинно. В нашем случае ­ пока
фактическая сумма не достигла требуемой величины. То есть ­ проверили
условие, выполнили цикл, проверили условие, выполнили цикл, и так
пока условие вдруг не окажется ложным. Обратите внимание ­ сначала
проверили условие, затем выполнили цикл. Утром деньги, вечером
стулья. А если нет денег, в смысле условие не выполняется? Не будет и
выполнения цикла. Может быть и такая ситуация, когда цикл не
выполнится ни разу. В нашем примере ­ если захотеть набрать нулевую
или, того хуже, отрицательную сумму. Зачем что­то суммировать ­ ноль у
нас и так уже с самого начала есть.
115

А можно утром стулья? Можно. Разработчики Паскаля, исходя из высших
педагогических соображений, предусмотрели и этот вариант. На мой
взгляд, можно было бы и обойтись. Лучше бы они предусмотрели
оператор возведения в степень. Но это я так, к слову.
Цикл, который сначала выполняется, а потом думает ­ а стоило ли оно
того? Выглядит он сложнее и загадочнее. Перепишем нашу программу с
его применением.
howMany:=0;
factSum:=0;
repeat
howMany:=howMany + 1 ;
x:=1/howMany;
f a c t S u m : = f a c t S u m + x;
until
( f a c t S u m >= s u m ) ;

Исполняемая часть цикла теперь заключена между repeat и until.
Условие поменялось на противоположное ­ это принципиально! Цикл
теперь выполняется не до тех пор, пока условие истинно, а до тех, пока
оно ложно. Как только условие станет истинным ­ исполнение цикла
прекратится. Зато цикл выполнится как минимум один раз. Обратите
внимание, возможно окажется полезным. Вот такой цикл while будет
выполняться вечно:
while true
[ЧТО­ТО

do b e g i n
вредное}

end;

А вот такой цикл вообще ни разу:
while

f a l s e do b e g i n
полезное}

[ЧТО­ТО

end;

Вариант repeat­until и его вечно выполняющийся цикл
repeat
until false;

Для цикла repeat­until вариант, не выполняющийся ни разу, по понятным
причинам невозможен.
116

А теперь скрестим... Кого мы ещё не скрещивали? Ну, например
двумерные массивы и только что освоенные другие циклы.
Есть такая игра ­ пятнашки. Напоминаю, кто не знал, да ещё и забыл ­
коробочка, рассчитанная на шестнадцать фишек, четыре на четыре. Но
фишек не шестнадцать, а пятнадцать, одно место в коробочке остаётся
пустым. На фишках числа ­ от единицы до пятнадцати. Фишки,
тщательно перетасовав, помещают в коробочку. Задача ­ не вынимая
фишек из коробки, только передвигая, пользуясь при этом для маневра
или манёвра свободной клеткой, восстановить нормальный порядок
вещей ­ расставить фишки по рядам от первой до пятнадцатой.
Показываю на примере:
До того:
12
3
14
7

13 1 4
15 6 9
2 5 6
8 10

После того:
1
5
9
13

2
6
10
14

3 4
7 8
11 12
15

Вот картинка, картинку я взял из книги Перельмана, того, не этого,
Живая математика. Издание ещё 30­х годов, так сто никаких копирайтов
быть не должно.

117

Это головоломка в финале, в собранном и упорядоченном состоянии. На
данный момент, на начало игры, наша задача ­ перетасовать. То есть,
случайнымобразом расставить в коробочке пятнадцать фишек. Какие
варианты? Вариантов, как часто случается, ровно два.
Или перебираем все первые пятнадцать свободных посадочных мест в
коробке. Случайным образом определяем на первое свободное место
одну из фишек, вначале ­ одну из пятнадцати. Определив фишку на
место, вычёркиваем её из числа доступных к расстановке, и, в следующий
раз, уже выбираем одну из четырнадцати оставшихся. И далее пока все не
кончатся. Вариант номер два. Берём фишку, от первой до пятнадцатой,
пытаемся её поставить в коробочку, совершенно куда попало. Если место
свободно, то уже хорошо. Если занято, пытаемся снова. Когда­нибудь,
повезёт.
Мне больше нравится вариант номер два. В первом случае надо помнить,
какая фишка уже поставлена, а какая ещё нет. Во втором случае у нас
цикл по фишкам от первой до пятнадцатой ­ и помнить ничего не надо,
потому что нечего помнить. Нет, конечно, надо помнить, где какая фишка
уже стоит, но это ведь надо помнить в любом случае? Ведь это наша цель.
Излагаем словесно:
Цикл по количеству фишек
Цикл пока не найдём свободного места для фишки
Попытаться фишку пристроить
118

Просто и ясно. Первый цикл, как уже много раз было сказано, от единицы
до пятнадцати, значит ­ for. Для второго цикла очень даже подойдёт
repeat ­ until. Ведь как минимум одну попытку расстановки предпринять
мы должны.
Не совсем хорошая особенность нашего алгоритма ­ мы не можем
гарантировать, что он завершится. Ну бывает же так, что каждый раз ­ и
мимо! Конечно, вероятность незавершения нашего алгоритма за сколько­
то заметное время сопоставима с вероятностью одномоментного выхода
всех молекул кислорода из комнаты. С одной стороны, это вряд ли. С
другой стороны, если бы мы программировали ракету на Марс, за такое
программирование я лично поотрывал бы все программистские ручонки.
Но у нас не ракета, сойдёт. В результате:
for

i : = 1 t o 4 do
f o r j : = 1 t o 4 do
a[i,j]:=0;

i : = 1 t o 15 do b e g i n
repeat
col:=Random(4) + 1;
row:=Random(4) + 1 ;
i f a [ r o w , c o l ] = 0 then begin
a[row,col]:=i;
nasli:=true;
end
else nasli:=false;
until nasli;
end;
for

Обсуждение. Во­первых, ждите мою книгу, где я объясню теорию
вероятностей весело, в подробностях и понятно для кого угодно. Далее.
Числа 4 и 15 употреблены для большей наглядности нашей небольшой
программы. В реальной жизни вместо них, конечно, должны быть
константы. Массив A вначале заполнен нулями. Ноль означает, что в
соответствующей ячейке фишки нет. Если фишка там присутствует, то
элемент массива содержит её номер, то есть то, что на фишке написано.
Обратите внимание на использование функции Random.
Продолжение последует после освоения работы с клавиатурой.

119

Глава 8
Процедуры и функции
Процедура без параметров
Помните мою печаль по поводу циклов без массивов? Их
бессмысленности и бесполезности? В чём­то похожая ситуация с
процедурами без параметров. Ситуация похожая, но не до конца ­
процедуры без параметров действительно жизненно необходимы. Но не
тем, что они, подобно правильно применённым циклам, сокращают текст
программы и, тем самым, труд программиста. Процедуры без параметров,
в большинстве случаев, текст не сокращают нисколько, а вот труд
программиста сокращают очень даже ­ но не на этапе кодирования, а на
этапе отладки и даже позже. Поймёте тоже позже.
Процедуры структурируют программу. А по простому? А по­простому ­
делают её понятнее. А это важнее всякой краткости.
Теперь пример. Пример абсолютно бесполезный, бессмысленный,
абстрактный, и, поэтому, на примере текстового вывода. Привожу всю
программу целиком, поскольку здесь особо важна структура программы,
в смысле ­ что за чем идёт.
program p r o c ;
uses
Crt;
{
j
procedure WriteSomething;
begin
w r i t e l n ( ' W e d o n ' ' t n e e d no e d u c a t i o n ' ) ;
w r i t e l n ( ' W e d o n ' ' t n e e d no t h o u g h t c o n t r o l ' ) ;
writeln;
{ (C) Pink Floyd
'The Wall'
1979, так, на
end;
{
j
begin
WriteSomething;
WriteSomething;
WriteSomething;
end.

На выходе имеем:
We don't need no education
120

ВСЯКИЙ

случай

j

We don't need no thought control
We don't need no education
We don't need no thought control
We don't need no education
We don't need no thought control
Пластинку заело, короче. Долго думаем. В целом понятно. Даже не знаю,
что объяснять­то... Попробую всё же. Закомментируйте вот эти строки:
{
WriteSomething;
WriteSomething;
WriteSomething;}

Запустите программу снова. Что случилось? Абсолютно ничего. Не в
хорошем смысле, что ничего не изменилось, а в плохом ­ что ничего не
вывелось.
То
есть,
то,
что
находится
между
словами
procedure...begin...end; само по себе не делает ничего. А называется это
описанием процедуры, оно же тело процедуры. А вот то, что мы
закомментировали, называется вызовом процедуры. Вызов процедуры без
параметров эквивалентен тому, как если бы вместо него просто
подставили тело процедуры ­ то, что между begin и end. Три вызова, как
у нас ­ значит, тело процедуры подставили три раза.
Обратите внимание на поразительное сходство структуры процедуры и
программы. Вся разница только в точке в конце процедуры. Или почти
вся разница.... Как увидим чуть позже, в процедуре точно так же можно
объявлять переменные. Это важно. Имя процедуре даётся по тем же
правилам, что и программе и вообще переменным. На самом деле, текст
процедуры не подставляется вместо её вызова. Но может и подставляться,
в некотором смысле. Посмотрите на слово inline ­ возможно, когда­
нибудь и пригодится.
Ну, вот, пожалуй, и всё о процедурах без параметров.
То же и с параметрами
Сначала простенькая бесполезненькая программка для демонстрации
идейки. Здесь и далее я буду писать только саму процедуру и её вызов, а
121

программу вокруг всего этого дорисуйте сами, до той степени, чтобы её
можно было запустить.
procedure Out(
begin
writeln(x);
end;

x : integer);

Процедуру можно вызвать так:
Out(5);

На экране появится пятёрка.
А можно так:
y:=5;
Out(y);

На экране опять появится пятёрка.
Теперь важные термины. X : integer; ­ это описание формального
параметра. Сам X ­ это формальный параметр. То, что стоит в скобках
при вызове процедуры ­ 5 или Y ­ это фактический параметр. Видим, что
фактический параметр может быть константой или переменной. Имя этой
переменной не обязано совпадать с именем фактического параметра, но, в
общем случае, может. Об этом мы подробно поговорим позднее.
Фактический параметр может быть и выражением:
y:=5;
Out(y+1);

Выведется, понятное дело, шестёрка. Условие одно ­ фактический
параметр, будь то константа, переменная или выражение, должен
совпадать по типу с формальным параметром. В нашу процедуру нельзя
передать дробное число или строку. Нельзя передать и выражение,
принимающее дробное значение, например 5/2. Соответственно, если
формальный параметр строка, то фактический параметр обязан быть
объявлен как string или быть строковой константой, но не как integer или
single. Но если формальный параметр объявлен как single, на вход
процедуре можно передать и целое число и целую переменную ­ целое в

122

данном случае рассматривается как частный случай дробного. Вспомните
о правилах присваивания дробных и целых чисел. И здесь то же самое.
Теперь о сложном. Напишем вот так:
procedure Out(
x : integer);
begin
x:=x*2;
writeln('inside x ='x);
end;
x:=5;
Out(x);
writeln('outside

x

=',x);

Что видим на экране?
i n s i d e x =1C
o u t s i d e x =5;

Во­первых, мы видим, что формальный параметр внутри процедуры
можно использовать как самую обычную переменную, в том числе можно
изменить его значение. Но изменение действует только в пределах самой
процедуры. Вышли ­ забыли об изменениях.
Подробнее позже, очень скоро ­ в следующем разделе. А пока процедура
посложнее ­ с несколькими параметрами.
Вспоминаем рисование квадрата методом опорной точки.
xC:=1CC;
yC:=1CC;
L i n e ( x C , y C , xC+1CC,yC);
L i n e ( x C + 1 C C , y C , xC+1CC,yC+1CC);
L i n e ( x C + 1 C C , y C + 1 C C , xC,yC+1CC);
Line(xC,yC+1CC, xC,yC);

Переоформляем это в виде процедуры.
procedure Square(
xC,yC : i n t e g e r ) ;
begin
L i n e ( x C , y C , xC+1CC,yC);
L i n e ( x C + 1 C C , y C , xC+1CC,yC+1CC);
L i n e ( x C + 1 C C , y C + 1 C C , xC,yC+1CC);
Line(xC,yC+1CC, xC,yC);

123

end;
Square(1CC,1CC);

Как легко догадаться, вам надлежит оформить это в виде законченной
программы. Добавим третий параметр ­ цвет, которым надо рисовать
квадрат. А какого цвет типа? Как несложно предположить из сказанного о
цвете раньше ­ цвет целого типа, то есть integer;
Замечание в скобках. Догадка на самом деле неверная. То есть, цвет
действительно целого типа, но не integer, a word. Разница в том, что word
может принимать только положительные значения, зато в диапазоне от 0
до 65535. Тип word вам никогда не понадобится. Забудьте.
Добавляем цвет.
procedure Square(

xC,yC
color

: integer;
: integer);

begin
SetColor(color);
L i n e ( x C , y C , xC+1CC,yC);
L i n e ( x C + 1 C C , y C , xC+1CC,yC+1CC);
L i n e ( x C + 1 C C , y C + 1 C C , xC,yC+1CC);
Line(xC,yC+1CC, xC,yC);
end;
Square(1CC,1CC,

Green);

Тип параметров указывается аналогично типу переменных. Однотипные
параметры можно сгруппировать в одной строке, как вы уже заметили.
А какие бывают параметры?
Теперь о сложных и серьезных материях. Кроме шуток. Какие бывают
параметры у процедур? Двух видов.
С чем мы только что познакомились? В качестве фактического параметра
можно передать хоть переменную, хоть константу или выражение, лишь
бы оно совпадало по типу с формальным параметром. Или почти
совпадало, как в случае целых и дробных. Если подать на вход
переменную и изменить её внутри, по выходе из процедуры всё
забудется. Это называется передача параметров по значению. Другой,
незнакомый нам пока вариант ­ передача параметров по ссылке. Вообще
124

говоря, в других учебниках они, виды параметров, могут называться как
угодно по­другому. Так что, забываем про глупости и встречаем по
одёжке. Как оно выглядит? Если так:
p r o c e d u r e Make(

x : integer);

то это первый вариант, нам уже знакомый, передача по значению. На
вход что угодно ­ переменная, константа, выражение, но изменения после
выхода не сохраняются.
Если так:
p r o c e d u r e Make( v a r x : i n t e g e r ) ;

то это вариант номер два, нам пока незнакомый. Будем знакомиться. В
качестве фактического параметра можно передать только и только
переменную типа integer. Никаких констант, никаких выражений. Даже
если бы формальным параметром был single, передать на вход целое всё
равно нельзя. Зато если изменить значение переменной внутри
процедуры, изменения сохранятся и после выхода наружу. Вернёмся к
примеру из предыдущего раздела, только добавим var.
procedure Out( v a r x : i n t e g e r ) ;
begin
x:=x*2;
writeln('inside x ='x);
end;
x:=5;
Out(x);
writeln('outside

x

=',x);

Что теперь мы получим на экране?
i n s i d e x =1C
o u t s i d e x =1C;

Как этим счастьем пользоваться? Никогда нельзя допускать, чтобы в
процедуру в качестве параметра с var входило одно значение, а
возвращалось, в этом же параметре, другое значение. Это, разумеется,
транслятором будет пропущено, но так делать низзя! Вход ­ параметр без
var, выход ­ параметр с var. Получается, что параметры с var при входе в
125

процедуру вообще, по сути, не должны иметь никакого значения. Так оно
и есть. Значение, всё равно присутствует, хотя бы и какое­нибудь
мусорное, от этого никуда не деться. Но мы должны вести себя так, как
будто его нет. Непонятно? Забудьте! Поймёте потом. Если станете
программистом.
В качестве примера вспомним про когда­то подсчитанный нами
факториал и изготовим из него процедуру. К факториалу нам придётся
вернуться ещё минимум дважды ­ скоро, добравшись до функций, и
нескоро, когда доползём до рекурсии. Напоминаю ­ факториал N ,
обозначается N!, представляет собой произведение всех целых чисел от 1
до N. И подсчитали мы его так:
N:=5;
fact:=1;
f o r i : = 1 t o N do
fact:=fact *i ;

Теперь изготовляем из этого процедуру с двумя параметрами.
procedure Fact(
var

N
result

: integer;
: integer);

var
i
: integer;
begin
result:=1;
f o r i : = 1 t o N do
result:=result *i ;
end;

Обратите внимание. Мы впервые встретились с одновременным
использованием параметров разных типов. И впервые ­ с объявлением
переменных внутри процедуры.
Кстати, факториал какого (максимально) числа, мы можем подсчитать,
если в качестве результата у нас используется переменная типа integer?
Отмотайте назад, найдите какое максимальное значение может содержать
integer и оцените. А потом оцените, целесообразна ли замена integer на
word. По тому же алгоритму ­ отмотайте назад, найдите, сосчитайте... Не
хочется? Скучно? Такова программистская жизнь...

126

О грустном
Почему о грустном? Когда­то, когда я изучал Паскаль, совсем даже ещё
не Турбо, всё было хорошо, просто и понятно. Всё, кроме передачи
массивов в качестве параметров. Я как­то даже поверить не мог, что это
настолько плохо и ожидал, что вот­вот и прочитаю в какой­нибудь
книжке про человеческий способ сделать ЭТО. Не прочитал.
Что у нас есть в программе между program и первым begin'oM?
program
uses
const
var
begin

Нам пока ни разу не понадобилась ещё одна секция. Начинается она
словом type. Размещается обычно между объявлениями констант и
объявлениями переменных. Это не строго обязательно, но обычно по
другому никак. Предназначена секция для объявления пользовательских
типов. Что это такое и зачем, подробно и осмысленно рассказывать здесь
не буду, эта тема для совсем другой книги.
Сначала очень простой пример.
type
TColor =

integer;

Что это значит? Мы объявили новый тип по имени TColor. По существу
типа integer. Поняли? Нет? В
новый тип является псевдонимом
сущности, пользовательские типы,
то есть, типы, определённые
пользователем, вещь абсолютно бесполезная, введённая исключительно
из образовательно­дисциплинарных целей. Как ни старался, я не смог
найти им ни малейшего применения. Преувеличиваю, конечно. Запись
(record) вполне целесообразно объявить типом, и в дальнейшем ссылаться
на него. Возможно, если постараться, можно вспомнить ещё пару­тройку
случаев, когда пользовательские типы не совсем бесполезны. Но, в общем
и целом, они таковой бессмыслицей оставались, до появления в Turbo
Pascal 5.5 концепции объектно­ориентированного
программирования.
Тут, конечно, всё пошло совсем по­другому. Но об этом в другой книге. А
пока, всё что мы получили от введения нового типа, это возможность
вместо
127

procedure

Square)

xO,yO
color

: integer;
: integer);

написать
procedure

Square!

xO,yO
color

: integer;
: TColor);

Лично моё эстетическое чувство как­то травмировала необходимость
передавать романтический цвет через какой­то математический integer.
Теперь у нас всё красиво ­ цвет передаётся как цвет. К сожалению, если
попытаться передать вместо TColor обычное целое, возражений не
последует, как­то это неправильно. Сказано TColor, значит надо
объявлять как TColor, и чтобы никаких целых. И обратите внимание на
букву T в начале имени типа TColor. Есть хорошая традиция ­ все имена
типов должны начинаться с буквы T. Так будет лучше, и вам, и
окружающим.
А теперь объясню, к чему была вся эта теория. Захотели мы написать
процедуру для подсчёта суммы элементов массива. Интуиция
подсказывает, что сама процедура и её применение будут выглядеть
примерно вот так:
const
N = 5;
var
a
result
{
procedure

: array[1..N]
: integer;

of integer;
}

Sum)

a
result

: array[1..N]
: integer);

var
i
: integer;
begin
result:=O;
f o r i : = 1 t o N do
result:=result + a [ i ] ;
end;
{
{заполнили чем­то
Sumf a, r e s u l t ) ;
{вывели
ответ}

массив}

128

of integer;

}

Что произойдёт при попытке транслировать этот, с виду вполне
разумный, текст? Как говорится в народе, пролетела птица обломинго и
приехал литовский политический деятель Обломайтис. Ничего хорошего
не произойдёт. А как надо? А надо вот так:
const
N = 5;
type
TArray = array[1..N] of integer;
var
a
: TArray;
result
: integer;
{

}

p r o c e d u r e Sum)

a
result

: TArray;
: integer);

var
i
: integer;
begin
result:=O;
f o r i : = 1 t o N do
result:=result + a [ i ] ;
end;
{
{заполнили чем­то
Sum) a, r e s u l t ) ;
{вывели
ответ}

}

массив}

Это надо просто запомнить. С многомерными массивами надлежит
поступать точно так же. В смысле, сначала объявить их типами и только
потом передавать как параметры.
Скучная, но необходимая теория
Какие бывают параметры и как их правильно готовить, мы в целом
разобрались. Теперь унылая тема под названием Глобальные и локальные
переменные, тесно примыкающая к процедурам и параметрам. Когда­то
тема эта была одной из важнейших при изучении, пожалуй что, любого
языка программирования. По моему сегодняшнему мнению, тему надо
открыть и тут же закрыть. В приличной программе глобальных
переменных быть не должно. В приличной программе вообще много чего
быть не должно. Подробнее на вопросе, чего не должен знать хороший
программист, мы остановимся как­нибудь позже. Но чтобы глобальных
переменных не допустить в программу, надо знать, что это такое. Врага
надо знать в лицо!
129

Напишем такую совершенно абстрактную программу.
program g l o b a l ;
var
x,y
integer;
{
procedure MakeNothing;
var
x,z
: integer;
begin
x:=10;
y:=20;
z:=30;
w r i t e l n ( ' x = ',x:2,
У = \y:2,
end;
{

z =

',z:2);

begin
x:=1;
y:=2;
writeln('x =
MakeNothing;
writeln('x =

',x:2,

У =

\y:2);

',x:2,

У =

',y:2);

end.

Запустите программу на выполнение. Посмотрите на результат.
Подумайте. Сделайте выводы. Для читающих эту книгу не подходя к
компьютеру и не вставая с дивана, привожу всё­таки результат её,
программы, выыполнения:
x= 1 y= 2
x = 10 y = 20 z = 30
x = 1 y = 20
Подумали? Сделали выводы? Первая строка вывода никаких вопросов не
вызывает. Что присвоили, то и получили. И думать здесь не о чем. Вторая
строка интереснее. С одной стороны, опять­таки, что присвоили, то и
вывели. Но переменные наши здесь не совсем равноправны. Y объявлена
в самой программе, сверху, так сказать. В процедуре она не объявлена. X
объявлена и там и там. Z объявлена только в процедуре. А вот третья
строка заставляет задуматься и, возможно, надолго.
Значение X не изменилось, хотя в процедуре оно было другим. Значение
Y сохранилось. Z мы выводить и не пытались. Это и понятно, она
объявлена только в процедуре.
130

Теперь объясняем, сначала практически, потом теоретически. Если
переменная объявлена в программе, а в процедуре не объявлена, то
любые её изменения ­ в программе ли, в процедуре ли ­ действуют, и
навсегда. Если переменная объявлена в программе, и в процедуре
объявлена тоже, то изменения сделанные в процедуре, в ней и останутся.
По выходе из процедуры всё будет прощено и забыто. Если переменная
не объявлена в программе, а объявлена только в процедуре ­ то всё
становится гораздо проще ­ проще, в основном, для компилятора.
Обратиться к этой переменной мы можем только в процедуре. Снаружи
её не видно.
Теперь обещанная теория. Заранее предупреждаю, что моё толкование
терминов может отличаться от приведённых в учебниках. В конце
концов, я столько программирую, что имею право на личное мнение в
отношении терминов. В моём коллективе я решаю, кто у меня локальная
переменная!
Немного выше я сказал о переменной, что её не видно. Это не случайно.
Наиважнейший термин ­ область видимости. Область видимости ­
откуда видно переменную. Если переменная объявлена в процедуре, то её
область видимости ограничивается этой процедурой. Снаружи её не
видно. Если переменная объявлена в программе, её область видимости
поистине безгранична ­ переменную видно и в программе и во всех
процедурах. Если переменная объявлена и в программе, и в процедуре,
то, на самом деле, это совсем разные переменные. Ту переменную, что в
процедуре, видно только из процедуры, ту переменную, что объявлена в
программе, видно только в программе.
Переменные, объявленные в процедуре, называются локальными. Если
переменная объявлена в программе, а мы сейчас в процедуре, то эта
переменная для нас глобальная. Глобальные переменные ­ зло. То есть,
они не запрещены, вполне могут существовать и это может даже от нас не
зависеть ­ если программу и процедуру пишут разные люди. Это зло
неизбежное. А вот зло, которого можно избежать ­ обращаться из
процедуры к глобальной переменной. Не надо так делать.
А если процедур не одна, а две, и в каждой объявлены свои переменные?
Всё так же, но область видимости переменной, объявленной в одной
процедуре, этой процедурой и ограничивается, в другой процедуре эту
переменную видно не будет.
131

Ещё одно порождение больной фантазии ­ процедура, вложенная в
другую процедуру. То есть, имеем программу, в программе процедура, в
процедуре другая процедура. Всё так же, как и было сказано выше, но
роль программы выполняет первая процедура ­ а так всё сказанное о
глобальных и локальных переменных и об области видимости остаётся в
силе.
Вот так всё запутано.
А теперь функция
Вернёмся к нашему факториалу. Помните?
procedure Fact(
var
var
i
begin
result:=1;
f o r i : = 1 t o N do
result:=result
end;

N
result

: integer;
: integer);

: integer;

*i ;

Соответственно, вызов этой процедуры и использование полученного
результата будет выглядеть так:
F a c t ( N,
writeln(

result);
result);

А если пользоваться, к примеру, синусом, могли бы так:
x:=Sin(y);
w r i t e l n ( x:8:2);

Или даже так:
writeln(

Sin(y):8:2);

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

function Fact(
N
: integer)
var
result
: integer;
i
: integer;
begin
result:=1;
f o r i : = 1 t o N do
result:=result * i ;
Fact:=result;
end;

: integer;

Вот так это можно использовать:
k:=Fact(N);
writeln( k ) ;
writeln(

Fact(N));

Это называется функция. А теперь разглядываем нашу функцию
внимательно. Принципиально важны две строчки. Первая ­ заголовок
функции. Вместо
procedure Fact(
var

N
result

: integer;
: integer);

пишем
function

Fact(

N : integer)

: integer;

Выходной параметр result исчез из заголовка. От него остался только тип
integer, который переместился в конец заголовка функции. Зато result
появился в списке объявленных внутри функции переменных. Это не
обязательно, но удобно в случае, когда, как у нас, ранее написанная
процедура преобразуется в функцию. И очень важная строчка
Fact:=result;

Слева ­ имя нашей функции. Справа, вообще говоря, может быть что
угодно. Если мы напишем Fact:=5, то значением нашей функции при всех
значениях аргумента будет неизменная пятёрка. Этот оператор
присваивания обеспечивает возврат значения нашей функцией. Короче,
без него ничего не получится. Только не промахнитесь и не напишите
наоборот, result:=Fact. Транслятор проглотит, но результат выполнения
будет нехороший. Это называется рекурсия. Так тоже можно, но только
133

если вы очень точно знаете, чего вы хотите. Впрочем, к этому мы ещё
вернёмся.
Сочиним ещё какую­нибудь функцию. Для разнообразия, с результатом
логического
типа.
Функция
должна
проверять,
является ли
положительное число точным квадратом. На вход ­ целое число, на
выходе ­ логическая переменная. Проверять будем в цикле, тупым
перебором всех целых чисел, меньших числа, заданного на вход. Для
экономии будем прекращать проверку, как только квадрат проверяемого
числа превышает заданное число. Как я замечал ранее и ещё замечу
позже, оптимизировать программу по скорости ­ последнее дело.
Впрочем, об это я ещё напишу. Оптимизировать программу стоит, только
если экономия обещает быть очень значительной, у нас именно этот
случай. Цикл наш должен будет завершиться в неизвестный заранее
момент времени. Вообще­то, для таких случаев ­ приращение по одному,
начиная с единицы, максимальное значение известно заранее, но цикл
может прекратиться раньше ­ очень хорошо подходит обычный цикл for
с использованием оператора Break. Но из педагогических соображений
мы используем цикл repeat­until. И обратите внимание ­ нас совершенно
не интересует, квадратом какого именно числа является число,
поступившее на вход функции. Нас только интересует ­ да или нет? В
результате вырисовывается такая вот функция:
function IsSquare(
N : integer)
var
x
: integer;
begin
x:=0;
repeat
x:=x+1;
u n t i l ( x * x = N) o r ( x * x > N ) ;
if

: boolean;

x*x = N
then I s S q u a r e : = t r u e
else IsSquare:=false;

end;

А почему я не написал x*x>=N? Для наглядности. Чтобы чётко были
видны две причины нашего возможного выхода из цикла. Или нашли
точный квадрат, или перебрали все числа, которые есть смысл
перебирать. В качестве упражнения, оформите в виде функции
написанный ранее программный код, который считал количество слов в
134

строке и выделял слово с заданным номером из строки. Заголовки
функций должны быть примерно такими:
f u n c t i o n WordCount(
f u n c t i o n GetWord(

s
num

s : string)
: string;
: integer)

: integer;
: string;

И ещё, по какому­то странному недосмотру, в Турбо Паскале
отсутствуют функции для выбора минимального и максимального из двух
чисел. Исправьте это.
А теперь тараканчик
Вот здесь я изобразил таракана. Как мог.
procedure

Tarakan(

x,y
color

: integer;
: integer);

begin
SetColor(color);
SetFillStyle( SolidFill, color);
{тельце)
F i l l E l l i p s e ( x,y, 60,30);
{головёнка)
C i r c l e ( x ­ 6 0 ­ 2 0 , y, 2 0 ) ;
{лапки)
Line( x,y­20, x , y ­ 6 0 ) ;
L i n e ( x­20,y­20, x­50,y­60);
L i n e ( x+20,y­20, x+50,y­60);
L i n e ( x,y+20, x , y + 6 0 ) ;
L i n e ( x­20,y+20, x­50,y+60);
L i n e ( x+20,y+20, x+50,y+60);
{глазки)
C i r c l e ( x­60­25, y­10,3);
C i r c l e ( x­60­25, y+10,3);
end;

А вот здесь я оформил в виде процедуры задержку:
procedure Delay;
var
i,j
: integer;
begin
f o r i : = 1 t o 1000 do
f o r j : = 1 t o 30000 do;
end;

А теперь, чего я собственно хочу. А хочу я, чтобы таракан бегал. Ну, или,
для начала, перемещался по экрану. А как он может перемещаться?
135

Естественно, в цикле. Нарисовать, подождать, стереть, передвинуть и всё
снова. Настолько просто, что не надо даже сначала записывать в
комментариях (или, говоря правильно, в псевдокоде). Пишем сразу:
x:=500;
y:=200;
f o r i : = 1 t o 300 do b e g i n
Tarakan( x,y, Green);
Delay;
Tarakan( x,y, B l a c k ) ;
x:=x­11
end;

А теперь соберите из этого законченную программу и запустите. Должен
забегать, хотя и помаргивая. На что обратить внимание? Переменная
цикла I в процедуре и переменная I в программе ­ совсем разные
переменные.
А как вы можете внести свой посильный вклад и поучаствовать в этом
веселье? Хочу, чтобы таракан перебирал лапками. Ну, или как минимум,
что бы каждая лапка могла быть в одной из трёх позиций. Напрашивается
добавление параметра в процедуру рисования таракана ­ номер
положения конечности. Это может быть даже переменная цикла ­ только
к ней надо применить общеполезный оператор mod. И ещё я хочу, чтобы,
добежав до одной стенки (границы экрана), таракан разворачивался и
бежал в обратную сторону. И, в конце концов, запустите тройку
тараканов со случайными тараканьими скоростями, принимайте ставки и
организуйте маленький ипподром. Надеюсь, эти просьбы не слишком
сложны для вас.
В процессе перемещения таракан безобразно мерцает. Можно это
победить? Можно. Надо только поступиться принципами, то есть
разрешением экрана. Во времена DOS'a тоже была видеопамять.
Размещалась она, правда, не на отдельной плате, а в основной памяти. И
назначение её было несколько другим ­ она хранила образ изображения
на мониторе. Тем не менее, размер её был ограничен и за её пределы ­
никак. Так вот, в режиме VGA (640x480), которым мы здесь везде
пользуемся, в видеопамяти помещается только одна страница с
изображением. А если быть чуток поскромнее и спуститься до EGA
(640x350), то этих страниц поместится уже две. А что это значит? Значит,
первую страницу показываем зрителю, на второй тайком рисуем, затем
136

переключаемся на отображение второй, на первой рисуем и так далее.
Практически, что для этого надо? Вместо VGA написать EGA. И
прочитать про SetActivePage (выбирает, на каком экране рисовать) и про
SetVisualPage (какой экран показывать). Это просто. Но, в сущности, не
нужно.
А этот раздел просто больше некуда было вставить
Честно говоря, единственная причина, по которой этот раздел оказался
именно в этой главе ­ то, что результат предпочтительнее оформить в
виде процедур, точнее функций. Причина весьма формальная, но пусть
будет лучше здесь. Надо же где­то ему быть. Заодно в первом
приближении познакомимся с понятием модуля, хотя бы на чисто
эмпирическом уровне. Про эмпирический уровень ­ это когда что­то
делаешь, оно работает, а почему работает ­ непонятно. О чём,
собственно, речь? Или, говоря по­другому, в чём наша проблема? У вас
нет проблемы? Сейчас убедим, что она у вас есть.
В игрушки играли? В некоторых старых игрушках управление мымриком
было организовано даже не стрелками, а буквенной клавиатурой.
Насколько помню, использовались клавиши Q,W,E,S. Кажется, так.
Нажали E ­ мымрик побежал направо. Нажали S ­ вниз, или что он там
делал в этом случае.
// Лирика
Кстати, как программист, преклоняюсь перед разработчиками игрушки,
не знаю официального названия, исполняемый файл назывался
goody.com. Запихать в шестьдесят четыре килобайта десятки экранов
действия, а ведь там ещё и какой­то сюжет был, судя по­всему. Мы всем
отделом за год так до конца и не добрались... Ещё раз ­ преклоняюсь.
Upd. Игрушка есть в американской Википедии Так и называется Goody,
платформер, разработчики испанцы, 1987. Через двадцать лет сделали
римейк.
И, разумеется, о программировании игр я напишу в отдельной книге.
// конец Лирики
Однако, возвращаемся к нашей клавиатуре. Мы, конечно, можем понять,
что нажата клавиша W ­ если после неё нажмут Enter. Но это совсем не
то, что надо для игрушки. Enter нам не нужен. А без него мы пока не
137

умеем. Хуже того, мы даже не можем определить, что на клавиатуре что­
то нажали ­ безотносительно того, что именно нажали. Будем учиться.
Тут я нахожусь в затруднительном положении ­ гусары, молчать! С
одной
стороны,
Турбо
Паскаль
предлагает
соответствующие
возможности, но как­то кривовато. С другой стороны, наша версия
гораздо симпатичнее, но требует веры на слово. Поясню, о чём речь.
В Турбо Паскале есть две функции:
function KeyPressed : boolean;
f u n c t i o n ReadKey : c h a r ;

Попутно познакомимся с новым типом данных. Даже с двумя, но второй
новый тип явится попозже. А пока первый новый тип ­ Char. Тип новый,
хотя и очень простой, и даже, в каком­то смысле, уже знакомый. Char ­
это один символ. Строковый тип представляет собой, в некотором
упрощении, массив элементов типа Char. Будем называть его (Char)
символьным типом, или просто символом. То есть, если есть S, которое
строка (string), то какое­нибудь отдельно взятое s[3] как раз и есть
символ. Если объявлено ch : char;, то можно написать ch:=s[3]; или
s[3]:=ch;, но, разумеется, нельзя написать ch:=s; или s:=ch;. Договоримся,
что если в дальнейшем попадётся переменная по имени ch, то объявлена
она как ch : char;.
Что всё это значит применительно к нашей задаче чтения с клавиатуры?
Мы можем написать такой текст:
repeat
i f KeyPressed
t h e n ch:=ReadKey;
i f c h = 'w'
then w r i t e l n ( ' w k e y p r e s s e d ' ) ;
u n t i l ch = 'q';

Код вполне рабочий и даже почти полезный Если нажата клавиша W мы,
всего­навсего, выводим сообщение об этом факте. А могли бы
отреагировать по­другому, повелев нашему игровому мымрику
попрыгать.
Для любознательно­дотошных есть ещё возможность объявить что­то
вроде
as : array[1..N] of char; Потом долго думать (и проводить
эксперименты) на тему совместимости этого типа с типом string.
138

Функции эти, вроде бы, делают именно то, что нам и надо. Первая
проверяет, нажато ли что­то на клавиатуре. Если не нажали, ответ, как и
ожидалось, отрицательный. Если нажали, то поведение функции чуть
сложнее ­ она будет отвечать "Да" то тех пор, пока мы не прочитаем
нажатую клавишу второй функцией. Тогда признак нажатия клавиши
сбросится, и первая функция будет говорить "Нет". Теперь программа
чуть сложнее, но и универсальнее:
p r o g r a m Key;
uses
Crt;
var
ch
begin

: char;

ClrScr;
repeat
KeyPressed then begin
ch:=ReadKey;
writeln( Ord(ch));
end;
u n t i l ch = 'q';
if

end.

Программа проверяет, не нажали ли что. Если нажали, программа читает
нажатый символ, а затем выводит символ на экран. Точнее, не символ, а
его код. О функции Ord, как уже обещано, поговорим отдельно. Каждому
символу однозначно соответствует код ­ число в диапазоне от нуля до
255. Выполнение программы прекращается при нажатии символа 'q'. Это
от слова Quit ­ в старых программах часто использовалось такое
сокращение. Понаблюдайте за поведением программы при нажатии
клавиш нестандартной ориентации ­ всяких стрелочек, функциональных
клавиш и тому подобного.
Подводим итоги. В целом всё хорошо, но, как обычно, присутствуют
нюансы. Нюанс мелкий ­ в текстах программ не случайно написано w и q,
а не W и Q. Наша программа будет реагировать только на маленькие
буквы. Сочетание клавиш w/Shift не будет распознано как нажатие
клавиши w. Это даже нюансом назвать трудно ­ функция ведёт себя в
точном соответствии со своим названием. Сказано прочитать символ ­
вот она и читает. Буква большая и та же буква, но маленькая ­ это ведь

139

разные символы. Возможно, это именно то поведение функции, которое
нас лучше всего устраивает, но всё же ­ помнить об этом надо.
Теперь нюанс несравнимо серьёзнее. Есть клавиши хорошие ­ те,
которые с буковками и циферками. Нажал на буковку ­ и функция
ReadKey вернула нам нажатый символ, готовый для дальнейшего
использования. А есть клавиши нехорошие, но, по странному
совпадению, как раз самые нужные и интересные. Те же стрелочки в
первую очередь. При нажатии на такую клавишу ReadKey сообщит, что
прочитана какая­то странная клавиша, вернувшая символ, кодируемый
нулём. И если мы прочитали такой нулевой символ,
то должны
повторить чтение с помощью ReadKey ещё раз, и вот только тогда мы
узнаем, что же такое было нажато. Вот это двукратное считывание
кажется мне как­то не очень правильным или, по крайней мере,
неудобным. Сразу возникает желание написать свою, улучшенную
функцию ReadKey, или как мы её назовём.
DOS позволяет узнать нам, какой символ был нажат на клавиатуре. Но
ещё DOS позволяет узнать, какая клавиша была нажата. А это, как
говорят в определённых кругах, две большие разницы. Одна и та же
клавиша при разных условиях может одарить нас разными одиночными
символами, и наоборот ­ одна клавиша может сгенерировать два символа
за одно нажатие ­ как мы видели чуть раньше. А вот с собственно
клавишами всё железобетонно просто. Каждая клавиша генерирует свой
скан­код. Только она и только его. Скан­код ­ звучит загадочно, но, на
самом деле, очень просто. Когда­то, в совершенно неправдоподобно
далёкие времена, кто­то незатейливо пронумеровал всю клавиатуру. Как
положено ­ сверху вниз и слева направо. В левом верхнем углу оказался
Esc, он и стал номером первым. Следующей, при тогдашней конструкции
клавиатуры, оказалась клавиша с единицей. Она стала номером вторым.
Ну и так далее. Вот эти номера клавиш и называются скан­кодами.
Соответственно, я предлагаю читать именно эти скан­коды, по­простому
­ номера. К сожалению, Турбо Паскаль не предоставляет простого
способа прочитать скан­коды. Но Турбо Паскаль даёт возможность
сделать почти всё, что мы хотим. Хотим читать скан­коды, значит, будем
читать скан­коды. Только придётся немного потрудится. Как всегда есть
два варианта ­ для ленивых и для трудолюбивых. Трудолюбивые ­ это
такие же ленивые, только они чуть­чуть поумнее ленивых и согласны
140

сегодня поработать немного больше, чтобы всю следующую неделю
работать намного меньше.
В целом варианты весьма похожи. Первый. Добавляем в секцию uses имя
Dos. Затем включаем в программу описание двух функций:
}

{

function OurKeyPressed : boolean;
var
R
: Registers;
begin
R.ah:=1;
I n t r ( $16, R);
i f ( R . f l a g s a n d f Z e r o ) C
then OurKeyPressed:=false
e l s e OurKeyPressed:=true;
end;
{
f u n c t i o n OurReadKey : b y t e ;
var
R
: Registers;
ch
: char;
sc
: byte;
begin
R.ah:=C;
I n t r ( $16, R);
ch:=Chr(R.al);
sc:=R.ah;
OurReadKey:=sc;
end;
{

}

}

Что мы получили и как этим пользоваться? Первая функция заменяет
стандартную турбопаскалевскую фикцию KeyPressed. Вторая функция
заменяет, соответственно, стандартный ReadKey, с поправкой на то, что
наш OurReadKey читает не символы, а скан­коды.
И попутно ­ второй новый тип ­ byte. Он ещё проще, чем char. Это такая
половинка от integer, точнее от word. Занимает он не два байта, а один, и
вмещается в него диапазон целых чисел от 0 до 255. В большинстве
случаев ­ и в нашем ­ вместо byte можно использовать обычный integer.
То есть, можно писать k:=OurReadKey, где K объявлено как integer. Так
зачем мы используем byte? Зачем, зачем, ну, получается что
исключительно из принципа. На самом деле скан­код ­ это всегда один
байт, вот мы и используем переменную, которая ровно один байт
занимает. Если Вы нежадный, можете в предыдущем коде заменить sc :
141

byte; на sc : integer;. Главное, запомните общий принцип ­ переменной
типа integer можно сколько угодно присваивать переменные типа byte,
обратное весьма нежелательно. Как обычно, думать над этим три минуты.
Как этим пользоваться? Следующий код проверяет, нажата ли клавиша.
Если какая­то клавиша нажата, выводим её скан­код. Если нажали Esc,
прекращаем работу. Sc объявлено как byte или как integer, что вам больше
нравится.
repeat
i f OurKeyPressed
t h e n sc:=OurReadKey;
writeln('scan =
sc);
u n t i l sc = 1;

Теперь немного о смысле того, а что мы написали. Всё это можно было
написать на ассемблере ­ Турбо Паскаль разрешает ассемблерные
вставки. Такой псевдопаскалевский/псевдоассемблерный
стиль
­
следствие моих личных предпочтений. В смысле, мне так больше
нравится.
Тип Registers ­ это запись (смотри далее) обеспечивающая доступ к
регистрам процессора. Поля записи ­ отдельные регистры. Разумеется,
обращение к записи не означает непосредственного обращения к
регистрам процессора. Обращение происходит при вызове процедуры
Intr. Она вызывает так называемое 21­е прерывание DOS, которое на
самом деле никакое не прерывание. Первый параметр ­ номер функции
прерывания, второй параметр ­ состояние регистров. Если регистры
меняются во время вызова прерывания, изменённые состояния мы
получим в этом же параметре. Подробнее об этом можно прочитать в
П.Нортон,
Р.Уилтон
"IBM PC
и
PS/2
Руководство
по
программированию". Питера Нортона программисты старой школы по
традиции уважают, но ещё лучше прочитать об этом в... В чём? Как
обычно, книжку из шкафа только что украли, а Интернет расходится во
мнениях. И как его только не называли... Жорден, Жордэн, Джурден,
Жордейн, Джордейн А потом оказалось, что это вовсе и не он, а такая
специальная очень умная тётенька, и зовут его вовсе и не Роберт, а
Роберта. Название приблизительно ­ Справочник программиста PC XT и
AT. Когда я был молодым, а всё вокруг зелёное и цвело и кустилось...

142

Короче, в те времена ни один серьёзный программист не мог обойтись без
этой книги.
Теперь вариант для трудолюбивых. Для тех, кому не хочется вставлять
этот код в каждую свеженаписанную программу. Попутно знакомимся с
новым понятием ­ модуль. Пока чисто прикладным образом, теория ­ в
следующей главе.
Итак ­ , . Появляется новое, абсолютно пустое окно
редактирования без заголовка. Точнее, с заголовком nonameOO.pas, что, в
целом, ничем не лучше, чем без заголовка вообще. Набираем в этом
пустом окне вот такой текст:
unit

Scan;

{
interface
{
Scan Codes
const
ArrowLeft
= 75;
A r r o w R i g h t = 77;
ArrowDown
= 80;
ArrowUp
= 72;
{
f u n c t i o n OurKeyPressed
: boolean;
f u n c t i o n OurReadKey : b y t e ;

}
}

Esc

=

1;
}

{
implementation
uses
Dos;

}

{
f u n c t i o n OurKeyPressed
: boolean;
var
R
: Registers;
begin
R.ah:=1;
I n t r ( $16, R);
i f ( R . f l a g s a n d f Z e r o ) 0
then OurKeyPressed:=false
e l s e OurKeyPressed:=true;
end;

}

{
f u n c t i o n OurReadKey : b y t e ;
var
R
: Registers;
ch
: char;
sc
: byte;
begin
R.ah:=0;
I n t r ( $16, R);
ch:=Chr(R.al);
sc:=R.ah;

}

143

OurReadKey:=sc;
end;
{
end.

}

Всё почти так же, как и в ранее набранных функциях, включенных в тест
нашей программы. Только добавились волшебные слова unit, interface,
implementation. С волшебными словами подробно разберёмся в
следующей главе. Ещё у нас появилась секция const, а в ней объявления
нескольких констант. Всё это в целом называется модуль. Теперь
нажимаем F2 и сохраняем наш модуль под именем Scan.pas. Обратите
внимание, тот же подход, что и с программой. Имя модуля (то, что после
unit), совпадает с именем файла, в котором этот модуль сохранён.
И как всем этим богатством воспользоваться? В разделе uses основной
программы кроме Graph и Crt (если они нам нужны) дописываем наш
Scan. Теперь, как всегда ­ нюанс. А могли бы мы назвать функцию не
OurReadKey, а просто ReadKey, как и стандартную? Могли бы. Но в этом
случае мы были бы должны аккуратно вписать его в uses перед Crt.
Причина? А подумать? Понимаю, что сложно, но гипотезу высказать
можно? В Crt уже есть и KeyPressed и ReadKey, и, вписав наш модуль
раньше, мы занимаем для себя эти имена. Кто первый встал, того и тапки.
Я понимаю, что это кажется очень простым. А в жизни пишут после Crt, и
долго чешут репу. И зачем нам лишние хлопоты? Проще переименовать.
Описания функций из основной программы выкидываем. Получается так:
uses
Scan, G r a p h , C r t ;
repeat
i f OurKeyPressed
t h e n sc:=OurReadKey;
writeln('scan =
u n t i l sc = Esc;

sc)l

И что мы от этого имеем? Польза основная ­ вместо добавления текстов
процедур каждый раз в текст основной программы, достаточно добавить
одно имя в секцию uses. И не забыть при этом, что файл с текстом модуля
Scan должен лежать в том же каталоге, что и сама программа.
На самом деле, это не совсем так строго. Модуль может лежать и в
другом каталоге, но имя этого каталога должно быть указано в
. И, обратите внимание, Dos в секции
144

uses основной программы уже не обязателен. На него ссылается
непосредственно наш модуль Scan.
Польза вспомогательная ­ нам не надо помнить, что скан­код Esc равен
единице. Оно у нас теперь константа. Там же, в разделе констант,
определены и скан­коды стрелок на все четыре стороны. В Дополнении
имеется полный текст модуля Scan. Можете вытаскивать оттуда скан­
коды нужных клавиш по мере надобности, или набрать весь модуль
сразу.
Кстати, что характерно, для применения в основной программе доступны
только имена, описанные в модуле в секции interface. Это неспроста.
Всем стоять и не разбегаться!
Написав предыдущий раздел, меня замучила совесть © Антон Палыч
Чехов, почти.
А может, зря это я? Может, не надо скан­кодов? Может, надо быть проще
и ближе к народу? Изложим предыдущее применительно к стандартным
средствам Турбо Паскаля. То есть, как можно работать с клавиатурой,
ничего не зная о скан­кодах и прочих ужасах, только с помощью
KeyPressed и ReadKey. Напоминаю, там всё было бы хорошо, если бы не
тот прискорбный факт, что в случае нажатия какой­нибудь непростой
клавиши мы получаем два как бы нажатых символа ­ первый ноль, а
второй означающий, что, собственно, мынажали. Попробуем с помощью
такой технологии различить нажатия на клавиши со стрелочками и
пробел. Первые четыре ­ клавиши, порождающие два символа, пробел ­
клавиша честная, простая и незатейливая. В смысле, символ только один.
В программе нашей мы просто будем в цикле выводить название нажатой
клавиши. Завершится это незатейливое развлечением нажатием "q".
repeat
i f keyPressed then begin
ch:=ReadKey;
i f Ord(ch) = 0 then begin
ch:=ReadKey;
i f O r d ( c h ) = 75 t h e n w r i t e l n ( ' A r r o w l e f t ' ) ;
i f O r d ( c h ) = 77 t h e n w r i t e l n ( ' A r r o w r i g h t ' ) ;
i f O r d ( c h ) = 80 t h e n w r i t e l n ( ' A r r o w d o w n ' ) ;
i f O r d ( c h ) = 72 t h e n w r i t e l n ( ' A r r o w u p ' ) ;
end
e l s e begin
i f O r d ( c h ) = 32 t h e n w r i t e l n ( ' S p a c e B a r ' ) ;

145

end;
end;
u n t i l ch='q';

Обратите внимание, без функции Ord обойтись всё­таки никак не
удалось. И ещё, если нажали что­нибудь отличное от заранее
предусмотренных пяти клавиш, программа наша будет молчать, как рыба
об лёд. Добавьте, для этого случая, вывод кода нажатой клавиши. И, что
важнее, подумайте об оформлении всего этого в виде отдельного модуля,
пусть он будет в минимальном варианте содержать одни константы кодов
клавиш. Лично я этих кодов не помню и, не то, что не хочу, но даже и
помнить не могу. И вас от этого категорически отговариваю.
Применим к тараканчику
Вспомним тараканчика.
заголовком:
procedure

Tarakan(

Рисовался

x,y
color

он

процедурой

с

вот

таким

: integer;
: integer);

А бегал посредством вот такой программы:
x:=500;
y:=200;
f o r i : = 1 t o 300 do b e g i n
Tarakan( x,y, Green);
Delay;
Tarakan( x,y, B l a c k ) ;
x:=x­1;
end;

Хотелось бы, чтобы зверушка наша не скакала тупо и упрямо всегда
влево, как здесь, а управлялась с клавиатуры. Стрелка влево ­ бежит
налево, стрелка вправо ­ направо... Не будем двигаться по шагам, а
приведём результат сразу. Он нам ещё много раз пригодится.
x:=500;
y:=200;
repeat
i f OurKeyPressed then begin
sc:=OurReadKey;
oldX:=x;
oldY:=y;
i f sc = A r r o w L e f t then b e g i n

146

x:=x­1;
end;
i f sc = A r r o w R i g h t then begin
x:=x+1;
end;
i f sc = ArrowUp then begin
y:=y­1;
end;
i f sc = ArrowDown then b e g i n
y:=y+1;
end;
Tarakan( oldX, oldY, B l a c k ) ;
T a r a k a n ( x , y, G r e e n ) ;
end;
u n t i l s c = Esc;

Обратите внимание, Delay здесь нам не нужен. Таракан теперь не в
режиме автопилота, а на ручном управлении. Поразмышляйте о чувствах
таракана, двигающегося кормой вперёд. Ну и, конечно, я хочу чтобы
таракан головёнкой о края экрана не стучался. Я в курсе, что он не
стучится, а молча уползает дальше, в голубую даль. А вы в курсе, что я в
курсе. Сделайте же что­нибудь, жалко ведь зверушку...

147

Глава 9
Совсем настоящая п р о г р а м м а
Про что программа?
Конечно, игрушка. Остальное просто недостойно нашего внимания. Не
электронную же таблицу нам выстругивать.
Тем не менее, оценим наши возможности. Morrowind, Civilization и
HOMM пока программировать не будем. Morrowind ­ наполовину
математически тосклив, наполовину просто тосклив. Тосклив для
программиста, который пишет движок ­ но мы же тут программисты, и
движок придётся писать именно нам. И тоскливо будет нам ­ а весело
совсем другим категориям наёмных работников. И для 3D нужно очень­
очень много математики.
Civilization (первый) и HOMM вполне реальны для начинающего
программиста. Реальны при условии, что он усвоит ещё кое­что из
технических подробностей, что мы ещё не усвоили, и, главное, будет
знать, как правильно подойти к делу. Последнее, повторюсь, главное.
Технические детали позволительно не знать ­ я не всё знаю, функции
Windows API не то, что можно, нужно не знать ­ я, понятное дело, и не
знаю и не хочу. Точнее, знаю, но со справочником. Это то же самое как
знать английский язык со словарём.
Главное, освоить правильный подход. Как сказал, не знаю кто, главное в
работе руководителя ­ подбор исполнителей и проверка исполнения.
Детали и подробности оставьте подчиненным ­ не царское это дело. И всё
стало хорошо, только исполнители почему­то злятся. Для вас разница в
том, что пока исполнителями являются написанные Вами процедуры и
функции, а проверкой исполнения ­ ваша же уверенность в том, что они
выдают правильный результат. И будет Вам ЩЩастие. Почти.
Однако, к делу. Раз ничего из вышеперечисленного
(пока)
программировать мы не будем, что, собственно, нам остаётся? Пятнашки,
Ханойские башни, Ним, Морской бой, Поле чудес, всяческие игры со
словами. Теперь отстреливаем лишнее.
Поле чудес ­ без файлов никак.
Пятнашки, Ханойские башни ­ слишком просто, нет противника.
148

Ним ­ противник есть, но игра за него элементарна.
Морской бой ­ оставим для Delphi, если получится.
Игры со словами ­ пока просто не хочу, как­то уныло.
Предлагаю крестики­нолики. Присутствуют, какая­никакая графика,
какое­никакое управление со стороны игрока и какой­никакой
искусственный интеллект противника. А графическое решение открывает
простор для необузданной фантазии.
Отладка. Давно пора
Программы, которые мы до сих пор писали, были простыми. Или,
выражаясь мягче, не очень сложными. Если что­то сочинялось не так, по
нашему ли неразумению, или по простой описке­опечатке, выловить
ошибку удавалось упорным (тупым) изучением (разглядыванием) текста
программы. И ошибка быстро обнаруживалась ­ я в этом уверен, честное
слово!
Эта программа будет посложнее. И ошибки будут посложнее. И
справиться с этими ошибками будет куда сложнее. Некоторые гиблые
места мне известны заранее ­ соответствие строк и столбцов поля,
координат расположения курсора и элементов двумерного массива,
описывающего текущее состояние игры. Ошибаются все, проверено.
Поэтому для отлова и отстрела ошибок придётся задействовать тяжёлую
артиллерию ­ отладочный режим. Тренироваться будем, как всегда, на
кошках. В роли кошки ­ вот такая, совершенно бессмысленная
программка. Циферки слева, ясное дело, к тексту программы не
относятся, это номера строк. Они здесь, в книге, не в Паскале, для
удобства ссылки на них. Набирайте текст:
01
02
03
04
05
06
07
08
09
10
11
12

program debug;
var
x,y
i
{
procedure I n c r (
begin
x:=x + 1;
end;
{
begin
x:=1;

: integer;
: integer;
var x : integer)

149

13
write('y = ' ) ;
readln(y);
14
Incr(x);
15
f o r i : = 1 t o 1000 do b e g i n
16
17
i f y > 0 then begin
x:=x + 1;
18
19
end
e l s e begin
20
21
x:= x ­ 1 ;
22
end;
23
end;
2 4 end.

Как всегда нажимаем F9. А дальше не как всегда. Вместо Ctrl/F9
нажимаем F7. Образовался зелёный курсорище во всю ширину экрана и
установился на строке 11, то есть на первом begin'e. Снова нажимаем F7.
Курсор переместился на следующую строку. Мы находимся в режиме
пошагового выполнения программы, он же отладочный режим. Зелёный
курсор показывает строку, которая сейчас будет выполняться. Внимание:
эта строка ещё не выполнена, она будет выполнена только при
следующем нажатии F7. Одно нажатие F7 ­ одна выполненная строка. И
снова внимание ­ выполняется одна строка, а не один оператор. То есть,
если в строке несколько операторов, выполнены будут все сразу, что не
есть хорошо. Так что не жалейте заварку, в смысле пишите по одному
оператору на строке.
Добрались до строки 14. Нажали F7. Оказались на чёрном экране с
приглашением ввести Y. В принципе, это естественно, никто за нас Y
вводить не будет. А теперь, остановившись на строке 15, опять жмём F7.
И попадаем в процедуру Incr. Затем тем же способом, через F7, из неё
выбираемся. Вроде бы всё понятно и разъяснений не требует. Нажимаем
Ctrl/F2. Зелёный курсор пропал ­ мы вышли из режима отладки.
Начинаем всё сначала, только вместо F7 будем нажимать F8. Всё
происходит абсолютно так же, пока мы не доберёмся до строки 15.
Теперь по нажатию F8 мы не входим в процедуру Incr, а перемещаемся на
следующую строку 16. Смысл клавиши F8 должен быть вам понятен ­
такое же пошаговое выполнение, но без захода в процедуры и функции.
Теперь главный вопрос ­ а зачем, собственно? Зачем это надо, и какие
блага мы, собственно, от этого получаем? Некоторая польза
обнаруживается на строке 17. В зависимости от введённого значения Y,
вы видим, по какой из веток условного оператора движется наша
150

программа. Это уже очень полезно. В нашем случае всё и так очевидно,
но ведь условие может быть гораздо сложнее и, что важнее, Y мог быть
не введён нами только что, а рассчитан где­то давным­давно. А теперь
желание посложнее, но более пользу приносящее. Хотелось бы узнать,
чему равно значение переменной, и как оно меняется в процессе
выполнения программы. Для этого придётся произвести ряд
утомительных телодвижений. В связи с неочевидностью процесса, будем
его иллюстрировать.
Начинаем всё сначала, отслеживать выполнение программы в пошаговом
режиме. То есть, нажимаем F7, потом ещё и ещё, пока наш большой
зелёный курсор не окажется на строке 14. Теперь приступаем к
действиям. Пусть, к примеру, нас интересует судьба переменной X.
Подкрадываемся курсором и устанавливаем его точно на переменной. Мы
можем выбрать любое вхождение переменной X в программе, это
неважно. Нажимаем Ctrl/F7.
Появляется вот такое окно:

Вместо X мы можем ввести имя другой переменной, но, скорее всего, X
это именно то, что нам и надо. Нажимаем Enter и получаем вот такое:

151

Мы видим значение переменной. Это хорошо. Но активным является окно
со значением переменной. Это, всё­таки, плохо. Нам бы хотелось
продолжать движение по программе, а для этого надо вернуться в окно с
текстом программы. Нажимаем F6. Эта клавиша, если помните, поочерёдно
перебирает все открытые окна. Даже картинку приводить не буду. Осталось
только окно редактирования. Окно с переменными тоже осталось, конечно,
но где­то сзади и нам недоступное и невидимое. Опять плохо.
Теперь выберите в меню , . Или Alt/W, A. Все
окошки выстроились и мы получили вот такой образцовый порядок:


­ |п| х|

c:\bp7win\bin\tp.bat

152

А что ещё есть в запасе полезного? Нажав Ctrl/F4 можно не только
посмотреть значение переменной, но и его, значение, изменить.
Мучительно пытаюсь припомнить, когда мне это понадобилось, и не
могу.
А теперь, предмет гораздо более существенный, чем изменение значений
переменных. Встали на строку 21 и нажали Ctrl/F8. Строка выделилась
жирным красным цветом. Что это значит? Как это называется?
Называется это ­ точка останова. Не остановки, а останова! А кто говорил
остановки, тех мои первые учителя программирования хватали за волосы
и возили мордой по столу, приговаривая А знаешь, как Ваньку Жукова
учили?!. Если попадался гуманный учитель, то просто гуманно бил
линейкой по рукам. Извините, расчувствовался. Точка останова нужна,
когда мы точно знаем, где хотим оказаться ­ в данном случае на строке
21, но при этом не хотим шлёпать туда пошагово, долбя по клавише F7.
Запускаем программу на выполнение как обычно ­ через Ctrl/F9. Вводим
отрицательное значение Y. Попадаем на точку останова ­ зелёный курсор
на строке 21. Дальше можно двигаться в пошаговом режиме. Отменить
точку останова можно, остановившись курсором на ней и снова нажав
Ctrl/F8.

И помните ­ когда вам всё это надоест ­ просто нажмите Ctrl/F2. И ещё.
Если программа работает в графическом режиме, то при переходе из окна
редактирования в экран с графикой и обратно, графический экран будет
выглядеть как­то так... Нехорошо... Грязновато... Это нормально и
совершенно не страшно.
Ещё одна очень важная вещь. Модули
Модуль ­ по­английски unit. Осваивать будем по той же схеме ­ сначала
модуль, ничего не содержащий. Затем модуль хоть что­то делающий и
как его применить. А затем я постараюсь объяснить, а Вы постараетесь
понять, зачем они, модули, нужны. Модуль, в отличие, от, например,
процедуры, вещь реальная. Модуль можно потрогать руками. Один
модуль ­ один файл.

153

А теперь ­ абсолютно пустой модуль. Что, разумеется, не означает
абсолютно пустой файл. , ­ получаем новое окно
редактирования. Набираем:
u n i t SomeUnit;
interface
implementation
end.

Нажимаем F2 и сохраняем модуль под именем SomeUnit.pas. Имя модуля,
разумеется, должно быть уникальным, как и все имена в программе.
Модуль можно оттранслировать, как обычно нажав F9. Ошибок быть не
должно. Запустить на выполнение, правда, не удастся. Модуль, подобно
процедуре, сам по себе ничего не делает. Процедуру надо вызвать.
Модуль надо подключить. То есть, главная ничего не делающая
программа должна выглядеть вот так:
program N o t h i n g ;
uses
SomeUnit;
begin
end.

Обращаем внимание на определённое сходство структуры модуля и
структуры программы. Сходство, как увидим в дальнейшем, ещё
значительнее ­ модуль, как и программа, может иметь свои собственные
секции uses, const, type, var. Теперь добавляем нашему модулю хотя бы
минимальную функциональность. На самом деле даже не совсем
минимальную ­ постараемся продемонстрировать весь, что называется,
спектр возможностей.
u n i t SomeUnit;
interface
uses
Dos;
const
Esc = 1 ;
type
TColor = i n t e g e r ;
var
x,y
: integer;
procedure DoSomething;
{
implementation
uses

154

j

Crt;
const
e = 2.718;
var
z
{
procedure
begin
end;
{

: integer;

DoNothing;

procedure DoSomething;
begin
z:=0;
DoNothing;
end;
{
end.

Программа, использующая наш модуль ­ всё та же ­ Nothing. На наш
модуль мы сослались в секции uses. В программе можно использовать
константу Esc. Это хорошо. В программе можно объявить переменную
типа TColor. Это тоже хорошо. В программе можно вызвать процедуру
DoSomething. Это даже не то, что хорошо ­ это желательно и
обязательно. Как правило, модули как раз и содержат различные
процедуры и функции. По крайней мере, все модули Турбо Паскаля
именно таковы.
В программе мы можем обратиться к переменным X и Y. Это плохо. Это
те же глобальные переменные, только хуже. Когда­то это было
распространённой практикой ­ создавался специальный модуль, где
объявлялись все глобальные переменные и только они. На этот модуль
ссылались все остальные модули и головная программа. Это я вам
подробно объяснил, как не надо себя вести. Раньше это было неизбежным
злом. С появлением объектов (Турбо Паскаль версии 5.5) такое поведение
стало аморальным и местами даже преступным. Об объектах мы здесь
говорить не будем, но, тем не менее, к вам это тоже относится. Впрочем,
об объектах, если повезёт, ещё напишем.
Это всё было о том, что можно. Теперь о том, что нельзя. Нельзя из
программы обратиться к переменной Z и константе E. Нельзя из
программы обратиться к процедуре DoNothing. К кому обращаться
можно, а к кому нельзя, определяют волшебные слова interface и
implementation. Технически это секции. Секция интерфейса ­ кто бы
подумал ­ и секция реализации.
155

Всё, что от слова interface до слова implementation видно снаружи
модуля. Любая программа или модуль, указавшая SomeUnit в секции
uses, может получить доступ ко всему нашему барахлу, упомянутому
здесь, в секции интерфейса. Всё, что между implementation и концом
модуля, видно только из секции implementation этого модуля.
И ещё. Если в программе есть ссылка на SomeUnit, это вовсе не значит,
что в программе есть ссылка на модуль Dos. Её, ссылки, там нет. И это
несмотря на то, что ссылка есть в секции интерфейса нашего модуля. О
ссылке на Crt в секции реализации даже не говорю. Как там нас на
истории средних веков учили ­ вассал моего вассала не мой вассал. Так и
здесь. Хотите Dos ­ ссылайтесь явно.
А теперь о главном ­ зачем это надо?
Первый ответ банальный ­ чтобы программа не была слишком большой.
Чтобы можно было попилить программу на части. А когда модуль станет
слишком большим ­ его тоже попилить. Это не пустяк. Это важно.
Поверьте. А каков максимальный размер программы или модуля? Лично
мне кажется, что тысяча строк. В крайнем случае, две тысячи, если никак
не пилится на части. Две тысячи ­ это для моего старого, измученного
жизнью программистского интеллекта. Для вас и пятисот хватит.
Попутно. А каков максимально рекомендуемый размер процедуры? Есть
такое мнение, что процедура должна умещаться на одной странице.
Страницы, конечно, разные бывают, но в целом я согласен. подход
правильный.
А ещё зачем? В смысле, зачем надо? Говоря по­умному, для сокрытия
реализации. Ещё раз ­ модуль состоит из двух секций ­ секция
интерфейса (interface) и секция реализации (implementation). В секции
интерфейса ­ только объявления и заголовки. В секции реализации ­ как
оно на самом деле внутри устроено. Из секции реализации наружу дороги
нет, как из чёрной дыры. Всё что там объявлено ­ константы,
переменные, типы ­ всё там и останется. И это правильно. Программист
слаб. Не может он думать обо всём сразу. А здесь мы ему жизнь
облегчаем. Когда он пишет модуль ­ думать надо о секции реализации.
Когда он модуль использует ­ думать только о секции интерфейса.
156

До совершенства эта концепция дошла
ориентированного программирования.

с появлением

объектно­

Некоторые потаённые технические детали. Если в uses перечислено
несколько модулей, а в их секциях интерфейса присутствуют одинаковые
имена, проблема решаема. Перед именем надо через точку указать имя
модуля ­ Graph.Green. Если модуль A ссылается на модуль B, а модуль B
ссылается на модуль A, то так нельзя и вообще, это страшное
преступление, именуемое circular reference. Однако, модуль A может
ссылаться на секцию интерфейса модуля B, а модуль B может ссылаться
на секцию реализации модуля A. Ничего не поняли? Учиться, учиться и
учиться.
Практический совет. Есть программа, есть модуль. Редактируете
программу, хотите запустить ­ нет проблем. Редактируете модуль, хотите
запустить, или даже оттранслировать всю программу сразу ­ никак.
Неудобно. Лечится просто ­ , . Выбираем
имя нашей программы. А чтобы не на один раз ­ идём .
Только убедитесь, что сохранено будет в текущем каталоге, в том,
который содержит наш проект.
С чего начать?
С чего начать нашу программу? Про искусственный интеллект пока
задумываться не будем ­ на этом этапе и никакого своего ещё не хватит.
Ещё раз ­ начинать всегда надо с того, что очевидно. Читал я в детстве в
какой­то книжке по физике, в целом замечательной, притчу о том, как
папа организовал детишек на перетаскивание кучи камней. Сначала он
заставил их перетаскать самые тяжёлые, а уже потом мелочёвку. И
возблагодарили они папу и жили долго и счастливо. Такие программисты
счастливо не живут. И даже долго не живут. Начинать надо с мелочёвки.
С неё и начнём.
С чего начинается игра? С того, что кто­то чертит игровое поле ­ три на
три. Значит, и мы с этого начнем.
Пошли дальше. Имеем цикл. До тех пор, пока игра не закончится. Не
забыть, что игра может закончиться не только победой игрока или
компьютера, но и ничьей. После цикла поздравления и фейерверки или
наоборот. Внутри цикла: ходит игрок крестиком. Куда ходить, выбирает
157

курсором, которым управляет стрелками на клавиатуре. Если этим ходом
игрок не выиграл, в ответ компьютер ходит ноликом.
Это наша программа в динамике. Теперь посмотрим в статике, какие
составляющие­процедуры нам будут нужны. Нарисовать поле ­
процедура без параметров. Нарисовать крестик, нарисовать нолик ­
процедуры с параметрами, параметры ­ где рисовать. Нарисовать курсор
­ параметры, в данном случае ­ где рисовать. А поскольку курсор должен
уметь бегать по экрану, ещё надо уметь его стирать, то есть рисовать
цветом фона. Если без лишних усилий ­ чёрным. Для этого добавить в
качестве параметра цвет. Или написать две процедуры ­ для рисования и
для стирания.
А о каких параметрах мы сейчас говорили? Что значит ­ где рисовать?
Понятно, что параметров будет два ­ нам надо задать расположение по
горизонтали и по вертикали. Но в каких единицах? Говорю сразу ­ на
экранные точки (пиксели) я категорически не согласен. Даже
аргументировать не буду. Ну не согласен я, и всё тут. Координаты ­ это
строка и столбец нашего расчерченного поля. Три строки на три столбца.
Процедура для проверки завершения игры. На вход ­ игровое поле, на
выходе результат ­ победа, поражение, ничья. Ну и что­то там для
торжественного финала. Также и пресловутый искусственный интеллект
требует своего места под солнцем. Процедуру, в смысле, искусственный
интеллект требует для проживания. Кстати, в процессе финала, неплохо
бы нарисовать линию, перечёркивающую три крестика (или три нолика),
которой обычно и завершается игра. Это не совсем тривиально.
Теперь подумаем о наших данных. Чтобы помнить, где что ­ массив три
на три. Если крестик ­ в соответствующий элемент пишем единицу.
Мысль кодировать нолик ноликом, отвергаем как идиотскую. Ноликом
может кодироваться только отсутствие чего­либо, то есть, в нашем случае
пустое поле. Нолик будет кодироваться двойкой. Ещё две целых
переменных ­ запомнить, где у нас курсор. Ну и всякая вспомогательная
мелочь.
Где­то позади переменных уныло тащатся константы. На этапе
проектирования о константах думают не всегда, но мы хорошие, мы
подумаем. Вспоминаем про метод опорной точки ­ значит нам нужны X0
158

и YO ­ для левого верхнего угла. Думаем ещё. У клетки игрового поля
есть размер. Размер, он только один ­ клетки­то квадратные. Крестики и
нолики должны помещаться в клетках. Курсор по размерам тоже должен
соответствовать. Операция проведения победной линии также не сможет
обойтись без знания размера квадрата. Уговорили! Вводим константу S ­
размер стороны квадрата. Вроде бы, наша геометрия полностью
определена.
Начинаем. Пишем пустые процедуры и их вызовы, там, где это очевидно.
Содержимое добавим потом.
Теперь глубокая мысль, причём, что нечасто, полностью моя мысль. При
прочих равных, следует стремиться к тому, чтобы программа была
безошибочно транслируемой в любой момент. То есть, написав вызов
процедуры, тут же бежим наверх и пишем её тельце, хотя бы и пустое. О
том, что, написав begin, тут же обязательно надо написать end, и
говорить нечего. На практике это значит, что в любой момент можно
нажать F9 и получить сообщение об отсутствии ошибок. Понятно, что не
в совершенно любой, это практически невозможно. Но пока ваша
программа неспособна безошибочно оттранслироваться, идти пить чай вы
не имеете права. И даже откинуться на спинку кресла права не имеете. И
важное отсюда следствие ­ транслируйте чаще!
И ещё ­ стремитесь к тому, чтобы программа в каждый момент её
написания была правильной. Даже изготовляя первый набросок
программы, старайтесь, чтобы всё, вами написанное так и осталось
неизменным. Заранее рассчитывайте, что добавлять вам очень много чего
придётся, а вот убирать что­то ­ в идеале нет. Это, конечно, очень­очень в
идеале. В реальности придётся и удалять и исправлять уже написанное и
много­много раз. Но стремиться к идеалу надо. Теперь вперёд!
program T i c ;
{я в курсе, что по­английски
это будет
tic­tac­toe,}
{но настаиваю
на том, что имя программы
и имя файла}
(должны совпадать.
Значит в имени не более восьми
символов}
uses
G r a p h , Scan;
const
{ мне кажется неправильным
объявлять константу N = 3}
{если что­то надо сделать три раза,}
{то можно обойтись без
циклов.}
{а с какого момента константы
необходимы?}

159

{вопрос философский...наверное,
X0 = 2 0 0 ;
Y0 = 1 0 0 ;
S
= 100;
{циферки,
что

с четырёх.

Кроме

называется,

от

жуток}

балды.

Уточним

потом}
type
{этот
integer;
array[1..3,1..3]
of
V Hll
TArray3x3
=
неспроста}
var
TArray3x3;
{самый главный
массив}
a
integer;
{координаты
курсора}
x,y
byte;
{это для
клавиатуры}
sc
integer;
{не закончилась
ли игра и чем}
result
integer;
{понятно, для чего}
d r i v e r , mode
boolean;
{gamover}
gamover
integer;
{а это куда выберет ходить A I }
xkuda, ykuda
integer;
{запас для циклов}
i,j,k
{
{эта процедура рисует
крестик}
procedure Cr(
x,y : i n t e g e r ) ;
{параметры ­ номера столбца и строки!}
begin
end;
{
{эта процедура рисует
нолик}
procedure Zero(
x,y : integer)
{параметры
номера
столбца
и строки}
begin
end;
{
{эта процедура рисует
курсор}
procedure DrawCursor(
x,y : integer)
{параметры ­ номера столбца и строки}
begin
end;
{
{эта процедура
стирает
курсор}
procedure HideCursor(
x,y : integer)
{параметры ­ номера
столбца и строки}
begin
end;
{
{эта процедура рисует
procedure DrawField;
begin
end;
{

поле}

{эта процедура
проверяет
завершение
игры}
{результат: крестики выиграли
­ 1}
{нолики выиграли
­ 2}
{ничья ­ 3}
{игра не окончена ­ 0}
p r o c e d u r e Check(
a
: TArray3x3;

160

var

result

: integer);

begin
end;
{
{чего­то там делает}
procedure F i n a l e (
begin
end;
{

result

: integer);

{а это великий и ужасный Искусственный
Интеллект}
{на выходе ­ столбец и строка, куда ставить
нолик}
procedure A I (
a
: TArray3x3;
var xkuda, ykuda : i n t e g e r ) ;
begin
end;
{
begin
driver:=VGA;
mode:=VGAHi;
InitGraph( driver,
{тут всё

самое

mode, ' ' ) ;

главное}

CloseGraph;
end.

Вот такой симпатичный скелетик нашей программы.
Поле
Рисуем поле. Три на три. Не просто, а очень просто. Это у нас процедура
без параметров.
procedure DrawField;
begin
SetColor(Green);
S e t L i n e S t y l e ( S o l i d L n , 0, T h i c k W i d t h ) ;
{горизонтали}
L i n e ( x0, y0+s,
x0+3*s, y0+s);
L i n e ( x0, y0+2*s, x0+3*s, y0+2*s);
{вертикали}
Line( x0+s, y0,
x0+s, y0+3*s);
Line( x0+2*s, y0, x0+2*s,y0+3*s);
end;

Немного минималистки. Дорисуйте, чего не хватает.

161

Крестик и нолик
Здесь чуть сложнее ­ процедуры с параметром. Начнём с нолика. Что
использовать? Без вариантов ­ Circle. А где будет его центр? В центре
клетки, понятно.
procedure Zero(
x,y : integer);
{параметры ­ столбец,
строка}
begin
SetColor(Red);
S e t L i n e S t y l e ( S o l i d L n , 0, T h i c k W i d t h ) ;
Circle( x0+(x­1)*s+(s d i v 2 ) ,
y0+(y­1)*s+(s d i v 2 ) ,
(s d i v 2) ­ 1 0 ) ;
end;

Пояснения нужны? Тогда поясняем. Пояснения для координаты X. Для Y
всё то же самое, что естественно.
x0+(x­1)*s+(s div 2). XO ­ это самое начало. Его прибавлять надо всегда,
это не обсуждается. Если мы в третьей, например, клетке, нам надо
прибавить размеры двух предыдущих и половину размера от текущей
клетки, чтобы попасть ровно в центр третьей.
(s div 2) ­10). Зачем нужна десятка? Чтобы было красиво.
Крестик будет чуть посложнее. Сначала пишем безо всякой красоты.
procedure Cr(
x,y : integer);
{параметры ­ столбец,
строка}
begin
SetColor(Red);
S e t L i n e S t y l e ( S o l i d L n , 0, T h i c k W i d t h ) ;
Line( x0+(x­1)*s,
y0+(y­1)*s,
x0+(x­1)*s+s, y0+(y­1)*s+s);
Line( x0+(x­1)*s+s, y0+(y­1)*s,
x0+(x­1)*s,
y0+(y­1)*s+s);
end;

Теперь неплохо бы всё это проверить. Надо сразу отбросить наивную
веру в осмысленность и правильность написанного нами текста. Пишем
мы ерунду с кучей ошибок. Соответственно, проверять написанное надо
как можно раньше и чаще. Написали процедуру ­ тут же проверили.
Написать всё сразу и потом всё сразу проверять ­ глупость несусветная и
необсуждаемая. Так что проверяем. Между InitGraph и CloseGraph, там,
где у нас будет потом самая главная программа, пишем:
162

DrawField;
Zero( 1, 3 ) ;
Cr( 3 , 1 ) ;

Вспоминаем, что первый параметр у нас номер столбца, второй параметр
­ номер строки. А почему мы вызвали с параметрами (1,3), а не (1,1)? Для
того, чтобы отловить случай перепутанных местами координат. Ошибка
совершенно обычная и несимметричные параметры очень хорошо её
ловят. Всё работает? А я пугал, что работать без проверки не будет? Ну
так я ведь уже всё сам проверил...
Работать­то оно работает, но как­то неаккуратно. Крестик наезжает на
разметку поля. Надо бы крестик слегка пообкусывать по краям, по всем
четырём. Ход мысли должен быть примерно таким ­ левый верхний край
должен быть короче, то есть находиться правее и ниже. Значит, к обоим
координатам надо что­то прибавить. Прибавить, конечно, что­то
одинаковое. А если одинаковое ­ значит нужна константа. А поскольку
константа эта применяться будет только внутри процедуры ­ то внутри
процедуры её и объявим. Получаем:
procedure Cr(
x,y : i n t e g e r ) ;
{параметры ­ столбец,
строка}
const
d = 5;
begin
SetColor(Red);
S e t L i n e S t y l e ( S o l i d L n , 0, T h i c k W i d t h ) ;
Line( x0+(x­1)*s+d,
y0+(y­1)*s+d,
y 0 + ( y ­ 1 ) * s + s­d) ,
x 0 + ( x ­ 1 ) * s + s­d,
Line( x0+(x­1)*s+s­d,
y0+(y­1)*s+d,
x0+(x­1)*s+d,
y 0 + ( y ­ 1 ) * s + s­d) ,
end;

Проверить и подобрать D по вкусу!
Курсор и чтобы бегал
Сначала курсор, потом чтобы бегал. Курсор будет внизу клетки и, уже
ясно, чуть короче. Вот так:
procedure DrawCursor(
x,y : i n t e g e r ) ;
{параметры ­ столбец, строка}
const

163

d = 5;
begin
SetColor(LightBlue);
S e t L i n e S t y l e ( S o l i d L n , 0, T h i c k W i d t h ) ;
Line( x0+(x­1)*s+d,
y0+(y­1)*s+s­d,
x0+(x­1)*s+s­d,
y0+(y­1)*s+s­d);
end;
{
}
procedure HideCursor(
x,y : i n t e g e r ) ;
{параметры ­ столбец, строка}
const
d = 5;
begin
SetColor(Black);
S e t L i n e S t y l e ( S o l i d L n , 0, T h i c k W i d t h ) ;
Line( x0+(x­1)*s+d,
y0+(y­1)*s+s­d,
x0+(x­1)*s+s­d,
y0+(y­1)*s+s­d);
end;

А как проверить? А вот так (дописываем в хвост).
DrawField;
Zero( 1, 3 ) ;
Cr( 3 , 1 ) ;
DrawCursor(3,2);
readln;
HideCursor(3,2);

Деликатный вопрос ­ а зачем отдельные процедуры для рисования и
стирания? А вот захотелось мне так. Точнее ­ я решил, что это будет
правильно. Размер кода небольшой, можно и продублировать (почти),
зато наглядно. Не нравится ­ напишите одну универсальную процедуру с
указанием цвета рисования.
В статике работает. А теперь в динамике, чтобы бегало. Этот цикл у нас
будет в основной программе. Выглядеть сначала он будет так же, как и
цикл раньше для бегающего тараканчика. Прочитали клавишу ­ стёрли
тараканчика, где стоит, поменяли позицию, нарисовали тараканчика. Не
забыть только инициализировать позицию тараканчика/курсора перед
циклом и нарисовать курсор. А когда цикл закончится? Когда закончится
игра ­ для этого у нас припасена переменная gamover. Или когда игроку
надоест и он нажмёт Esc.
Ещё раз ­ ЭТО у нас будет в теле основной программы.
164

DrawField;
x:=1;
y:=1;
DrawCursor(x,y);
gamover:=false;
repeat
i f OurKeyPressed then begin
sc:=OurReadKey;
i f sc = A r r o w L e f t then b e g i n
HideCursor(x,y);
x:=x ­ 1;
DrawCursor(x,y);
end;
i f sc = A r r o w R i g h t t h e n b e g i n
HideCursor(x,y);
x:=x + 1 ;
DrawCursor(x,y);
end;
i f sc = ArrowUp then b e g i n
HideCursor(x,y);
y:=y ­ 1;
DrawCursor(x,y);
end;
i f sc = A r r o w D o w n t h e n b e g i n
HideCursor(x,y);
y:=y + 1;
DrawCursor(x,y);
end;
end;
until

( s c = Esc) o r gamover;

Finale(result);

Замечание в сторону. В финальную процедуру мы передаём результат как
параметр.
Хотя
могли
бы
сэкономить
и
воспользоваться
соответствующей переменной, ведь её из процедуры видно. Самой
процедуре всё равно, текст её от этого не изменился бы ни на копейку. Но
­ глобальные переменные есть зло! И мы будем их уничтожать, там, где
встретим. Теперь по существу. Курсор резво бегает. Это хорошо. Но
бегает где попало. Это плохо. Никаких ограничителей на выход за
пределы поля у нас нет. Надо добавить. Ещё такой подход называется
защита от дураков. Главное, чтобы дураки это не прочитали.
Для стрелки влево будет так:
if

( s c = A r r o w L e f t ) and (x>=2) then b e g i n
HideCursor(x,y);
x:=x ­ 1;

165

DrawCursor(x,y);
end;

Для трёх оставшихся случаев первая строка условного
модифицируется соответственно:
if
if
if

оператора

( s c = A r r o w L e f t ) and (x=2) then b e g i n
( s c = A r r o w L e f t ) and (y>

');

d i s : = b * b ­ 4*a*c;
x1:=(­b+Sqrt(dis))/2*a;
x2:=(­b­Sqrt(dis))/2*a;
writeln('x1
readln;

=

',x1:8:2,'

x2 =

',x2:8:2);

end.

У этой программы есть масса недостатков. А что будет если:
Дискриминант меньше нуля? Корней нет, но мы об этом не узнаем.
Уравнение линейное (a=0)? В теории этого не может быть, потому что
тогда это будет уже совсем не квадратное уравнение. Но на практике это
будет! Именно так, с восклицательным знаком!
Тождество (0=0 3=3)
Корней не, но по­другому. Что­то вроде (7=3)

249

То, что вводимые данные не проверяются на корректность, то есть что
они действительно числа, а не бессмысленный набор символов, это
мелочь и мы её игнорируем. И, чисто эстетическое ­ если дискриминант
равен нулю, то эта программа выдаст два одинаковых корня. Это,
безусловно, правильно, но некоторые предпочитают думать что их,
корней, в таком случае только один. Можно было бы пойти этим
некоторым навстречу, нам пустяк, а им приятно.
Но вы, конечно, поняли, что это лишь шутка. Ни один пионер такой
программы никогда не напишет.
Он напишет примерно такое:
program KwaReal;
uses
OpCrt;
var a,b,i,e,
koren,a21
:
single;
var j , k ,
c9 9: i n t e g e r ;
begin C l r S c r ;
writeln('vv');
readln(a,b,i);e:=Sqr(b
) ­ 4*
a
+
koren:=((­1)*b+
Sqrt(e))/(a+a);
a21:=
(­b ­ S q r t ( b * b
­1*(a*i+a*i+a*i+a*i)))/2*a;
w r i t e l n ( ' x 1 = ',
koren,
'x2 = ' , a 2 1 + k ) ;
f o r j : = k t o R o u n d ( e ) do b e g i n
end;
readln;

:

end.

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

И ещё. Программа, безусловно, работает, и работает правильно. Если
задать на вход хорошие данные.
В реальной жизни, если нам понадобится программно решить квадратное
уравнение, то это означает, что нам понадобится решить по меньшей мере
тысячу квадратных уравнений. Разъясняю. Если мне надо решить одно
квадратное уравнение в год, я решу его на бумажке с помощью
калькулятора. Если мне надо решить одно квадратное уравнение в месяц,
я всё равно решу его на бумажке. Писать для этих жалких целей
программу ­ себе дороже. А вот если решать квадратные уравнения
придётся каждый день, то уже стоит подумать. Немедленно и сразу
следует обдумать возможность применения написанного нами текста в
безинтерфейсном режиме ­ то есть для вызова из других программ.
И программа наша будет представлять из себя процедуру, на вход
которой поступают коэффициенты уравнения, а на выходе корни или
некий признак, указывающий на тождественность или несовместимость
уравнения. В виде отдельной программы эта штуковина не будет жить
никогда. То есть будет жить, конечно ­ как же иначе мы будем её
тестировать? Но недолго.
Но мы пока учимся. И это будет отдельная, независимо выполняемая
программа. Мало того, сама по себе эта основная программа ничего
решать не будет. Ее задачей станет ввести входные данные, проверить
коэффициенты, вызвать на выполнение один из трёх внешних модулей ­
квадратное уравнение, линейное уравнение, всякая фигня, получить ответ
от них и вывести результаты. Я сам человек не очень занудный, потому
не стал выделять в отдельный случай приведённое квадратное уравнение,
что делают в каждой порядочной школе. Кстати, вспомните, что это такое
­ приведённое квадратное уравнение. Со стороны подсказывают, что есть
ещё вариант с чётным коэффициентом A.
Так что проектируем программу. Попутно даём имена модулям. Это
очень важно и очень сложно ­ Как вы лодку назовёте, так она и
поплывёт.
Program Kwa. Основная программа. Ее задача ввести входные данные,
определить, что именно перед нами ­ квадратное уравнение, линейное
251

уравнение, вообще не уравнение, вызвать соответствующий модуль,
получить от него ответ, вывести ответ.
Вызываемые модули и их обязанности.
Unit KwaUr
корни.

Возвращает количество корней (один, два, ноль) и сами

Unit LineUr

Возвращает один корень линейного уравнения

Unit ChtoTo
Возвращает одно из двух ­ или у нас тождество (0=0),
или корней нет вообще (3=0).
А что, собственно, означает возвращает количество корней? Скорее
всего, это целое число, принимающее значения 0,1,2.
Что должен возвращать третий модуль? Вариантов завершения его
работы два: либо тождество, либо корней нет. Корней нет ­ это уже было,
означает, что количество корней равно нулю. А если тождество? Вернуть
бесконечно большое число в качестве количества корней мы не можем.
Программист халтурщик вернёт что­то вроде 99 ­ практически
бесконечное число. Мы пойдём другим путём и определим свой тип,
причем определим его в отдельном модуле, на который будут ссылаться
остальные модули и главная программа
u n i t KwaType;

{
{

01.06.2017
01.06.2017

}
}

interface
type
TRes = ( r 0 , r 1 , r 2 , r a l l ) ;
implementation
end.

Мелкое замечание ­ даты сверху ­ первая ­ когда модуль создан, вторая ­
когда последний раз вносились изменения. И вам того же желаю.
Хорошо бы в этот же модуль впихнуть и объявления констант. А какие у
нас, собственно, могут быть константы? Как сказал кто­то умный ­ не я ­
с точки зрения программирования ноль и единица константами не
являются и могут быть свободно употребляемыми в исходном тексте
программы в первозданном виде. Тем не менее, константы здесь нам
252

абсолютно необходимы, но только другие, не численные, а строковые
константы.
Наша программа будет выдавать сообщения ­ в ответ на некоторые
ситуации. Тексты сообщений время от времени придётся менять ­ по той
банальной причине, что они не понравились заказчику или пользователю
­ это, вообще говоря, разные люди с разным характером.
Крупное замечание ­ нас не должно волновать, каким образом в памяти
представляется наш новый тип TRes. Даже и не думайте об этом. Ну или
по крайне мере не думайте до тех пор, пока не захотите записать
переменную этого типа в файл.
Обратите внимание, что после того как мы разделили нашу
первоначально одну программу на четыре части, работы у нас явно
прибавилось ­ ведь теперь нам надо организовать взаимодействие между
модулями. И так всегда и везде.
Как будут связаны между собой наши модули? Первый модуль будет
вызывать три остальных. Все четыре будут ссылаться на модуль с
объявлениями типов и констант. И только этот модуль ни на кого
ссылаться не будет. И только хомячки никого не любят © Шутка.
Первый модуль
u n i t KwaA;

{
{

01.06.2017
09.06.2017

}
}

interface
uses
KwaType;
p r o c e d u r e MakeA(

a,b,c
var x1,x2
var res
var textRes

single;
single;
TRes;
string);

a,b,c
var x1,x2
var res
var textRes

single;
single;
TRes;
string);

implementation
p r o c e d u r e MakeA(

var
dis

:

single;

253

begin
d i s : = b * b ­ 4*a*c;
d i s > 0 then begin
x1:=(­b+Sqrt(dis))/(2*a);
x1:=(­b­Sqrt(dis))/(2*a);
res:=r2;
textRes:=r2Text;
end e l s e
i f d i s = 0 then begin
x1:=(­b)/(2*a);
res:=r1;
textRes:=r1Text;
end
e l s e begin
res:=r0;
textRes:=r0Text;
end;
if

end;
end.

Комментарии.
Хотя мы и выделили в отдельный модуль случай полного квадратного
уравнения, всё же и внутри него (модуля) мы имеем три ситуации ­
дискриминант больше нуля ­ два корня, дискриминант равен нулю ­ один
корень, и дискриминант меньше нуля. В этом случае корней как бы и нет
­ они вроде бы и есть, только они комплексные, но для нас это всё равно,
что корней вообще нет.
Проверки на деление на ноль здесь нет ­ догадайтесь сами, почему.
Обратите внимание на даты, я писал этот шедевр программисткой мысли
ровно восемь дней. Это потому, что я очень аккуратный и внимательный
к мелочам.
Второй модуль, как легко предсказать, будет ровно в полтора раза проще:
u n i t KwaB;

{
{

01.06.2017
09.06.2017

}
}

interface
uses
KwaType;
procedure

MakeB(
var

b,c
x1

single;
single;

254

var
var
{
implementation
{
p r o c e d u r e MakeB(

res
textRes

b,c
var x1
var res
var textRes

: TRes;
: string)

single;
single;
TRes;
string)

begin
x1:=(­c)/b;
res:=r1;
textRes:=r1Text;
end;
end.

Комментарии:
Комментариев нет
Третий модуль, для вырожденного случая, что интересно, ничуть не
короче второго.
unit

KwaC;

{
{

01.06.2017
09.06.2017

interface
uses
KwaType;
{
p r o c e d u r e MakeC(

}
}

}
var
var

c
res
textRes

: single;
: TRes;
: string);

{

j

implementation
{
p r o c e d u r e MakeC(

j
var
var

c
res
textRes

: single;
: TRes;
: string);

begin
if

c = 0 then begin
res:=rAll;
textRes:=rAllText;

end
e l s e begin
res:=r0;
textRes:=r0Text;
end;
end;
{
end.

j

255

А вот наконец самый главный модуль, он же головная программа.
p r o g r a m KwaKwa;
uses
OpCrt,
kwaType, kwaA, kwaB, kwaC;
var
a,b,c
numOfX
x1,x2
res
textRes
{
procedure InPut;
begin
W r i t e ( ' a >> ' ) ;
ReadLn(a);
W r i t e ( ' b >> ' ) ;
ReadLn(b);
W r i t e ( ' c >> ' ) ;
ReadLn(c);
end;
{
procedure OutPut;
begin
WriteLn(textRes);
Writeln('x1
Writeln('x2

=
=

single;
integer;
single;
TRes;
string;

', x 1 : 8 : 2 ) ;
', x 2 : 8 : 2 ) ;

ReadLn;
end;
{
begin
ClrScr;
InPut;
a 0 t h e n b e g i n
MakeA( a , b , c , x 1 , x 2 , r e s , t e x t R e s ) ;
end e l s e
i f b 0 t h e n b e g i n
MakeB( b , c , x 1 , r e s , t e x t R e s ) ;
end
e l s e begin
MakeC( c, r e s , t e x t R e s ) ;
end;
if

OutPut;
end.

256

Поглядим на результат и поразмышляем. Хорошо ли это? Довольны мы
ли результатом? Что делать?
Во­первых, программу надо протестировать. Протестировать ­ не значит
прогнать один единственный пример и убедиться, что программа на нём,
этом единственном примере работает. Протестировать ­ значит прогнать
очень много­много примеров. И, очень желательно, прогнать не самому.
Любой программист очень любит свою программу, особенно ту, которую
только что написал. И ему, программисту, очень не хочется этой
программе сделать больно, а тем более ­ убить программу насмерть.
Программист запускает тесты бережно и осторожно, имея в виду те
ситуации, которые он предусмотрел, и в которых его программа точно
будет работать.
Поэтому тестированием должен заниматься отдельный, специально
обученный человек. Желательно такой человек, у которого есть личные
причины вас ненавидеть. Но этого мало. Главное, внушить специальному
человеку, что успешное тестирование не то, при котором все тесты
прошли успешно. Успешное тестирование то, при котором программа
рухнула, упала и разбилась вдребезги. Если программа с первого раза
прошла все тесты ­ тестировщик должен быть наказан.
Теперь вручную (что значит вручную ­ станет ясно дальше) напишем
комплект тестов для прогона по всем закоулкам нашей программы. До
того я запустил программу на одном единственном примере. Пример был
1 2 3, само собой. Программа честно ответила, что корней нет ­ и это
правильно. А теперь подойдем к вопросу скрупулёзно и методически.
Возможные варианты:
1.

a 0
1.1 дискриминант положителен
1.2
1.3

3,10,3
два корня ­3,­1/3
дискриминант отрицателен
2,4,3
корней нет
дискриминант равен нулю

5, 10, 5
один корень ­ 1 /
a = 0, b 0
2

2.

0, 2, ­10

3

один корень 5
257

3.

a = 0, b = 0, c 0

4.

0, 0, 10
корней нет
a = 0, b = 0, c = 0
0, 0, 0

тождество

Пропускаем все тесты через нашу программу, или, наоборот, нашу
программу пропускаем через все тесты. Что мы видим? А почему?
А видим мы два отклонения результатов фактических от результатов
ожидаемых.
В тесте 1.1 получаем фактический результат ­3; 0. Приглядевшись к
исходному тексту, видим чудесное:
d i s > 0 then begin
x1:=(­b+Sqrt(dis))/(2*a);
x1:=(­b­Sqrt(dis))/(2*a);
res:=r2;
textRes:=r2Text;
end e l s e
if

Второе x1 получено путём размножения первой строчки с неаккуратным
её размножением. Комментариев два:
Первый. Только что мы наблюдали основной и самый простой источник
ошибок. Ошибки размножения или, по­умному, Copy­Paste.
Второй. Хотя Delphi не является темой книги, там эта ошибка была бы
отловлена на этапе трансляции ­ мы получили бы совет (Hint), что
значение переменной x1 после первого присваивания больше никогда не
используется. Но вся печаль в том, что Hint и Warning большинством
программистов тупо игнорируются, или, при возможности, вообще
отключаются, так что, в конечном итоге результат был бы тот же.
Но это ещё не всё. В тесте 1.3 ожидаемый ответ ­ 1 / ,реально
полученный ­ 1 , без дробей. Метнувшись в исходный текст, и судорожно
изучив его под микроскопом, никаких видимых ошибок я не обнаружил.
Тогда меня осенило и я пересчитал на бумажке корни уравнения.
Оказалось, программа права, а я ошибся в расчётах. Бывает и так.
2

3

258

А что теперь? Успокоиться на достигнутом? Можно посадить
тестировщика, привязать его за ногу к компьютеру и пусть себе тестирует
до бесконечности. Хорошая мысль. Более прогрессивной считается,
однако, технология автоматической генерации тестов. Идея красива и
благородна ­ компьютер сам генерирует тесты, сам запускает нашу
программу (модуль) и сам проверяет ответы на правильность и
выставляет нам оценку. И никаких злых тестеров.
Вся процедура очевидным образом распадается на три этапа:
1.

оформить нашу программу в виде одного вызываемого модуля

2.

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

3.

Написать модуль проверки
правильный результат.

­

возвращающий

заведомо

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

Если бы я знал третий, самый правильный вариант ответа, я бы сразу его
рассказал. Но я его не знаю, по крайней мере, для нашего
специфического, не самого сложного случая. Что странно, бывают
ситуации, когда выбор решения усложняет простота решаемой задачи.
Зачем напрягаться, если можно посчитать на пальцах?
И ещё, я уже говорил о необходимости объявления констант с текстами
потенциальных сообщений. Посмотрите на программу с точки зрения
пользователя, запускающего её, программу, в ручном режиме. Какие
сообщения от неё, программы, вы бы ожидали увидеть? Теперь
представьте этот же модуль, но вызываемый автоматически, в контексте
более крупной программы. Что должен наш модуль писать в протокол ­
потому что неизбежно настанет торжественный момент поиска крайнего
и виноватого? Объявите приличные случаю константы.

260

Глава 2­4
По ту сторону ­ опустимся чуть ниже
Вступление
После изучения этой главы вы станете маленьким хакером. Или по
крайней мере вы станете маленьким крутым хакером образца
приблизительно двадцатилетней давности. Двадцатилетней ­ просто
потому, что сейчас всё это уже никому не нужно. Но я охотно раскрою
вам все свои маленькие тайны тех лет, раскрою легко, быстро и без
колебаний ­ в основном потому, что ничего сейчас уже и не помню.
Но, в любом случае, даже если я не смогу в этой главе ничему вас
научить, или если вы ничему из этой главы не научитесь, или всё это
окажется для вас совершенно бесполезным ­ всё равно вы можете после
этого считать себя самым крутым на свете хакером. Просто потому ­ а кто
вам запретит?
Сначала я даже не хотел упоминать здесь встроенный ассемблер ­ потому
что это очень просто. А с другой стороны ­ почему бы и нет? Если это
очень просто, а людям приятно ­ то вперёд!
С сего мы начнём ­ начнём мы с совершенно безобидной программы.
Ничего такого она не делает и никакими такими способами для этого она
не пользуется. Но мы из этой программы извлечём две пользы ­ первое,
увидим, насколько всё уныло и печально. Второе ­ большинство
выполняемых нами операций можно выполнить и другими способами,
вот мы и потренируемся на кошках.
Начало. Просмотр картинок
Пусть на вход нашей программы без конца поступают, к примеру,
картинки. Для чего ­ ну не знаю, честное слово. И наша задача эти
картинки хранить в памяти и при первом требовании отображать на
экране.
Если вы думаете, что эта задача искусственная и вообще высосана из
пальца ­ вы ошибаетесь. Вам непременно придётся этим заниматься. А
если вы думаете, что есть какие­то уже готовые средства для управления
коллекциями картинок, то вы опять конечно правы. Пусть это будут не
261

картинки, а песенки. И для песенок есть программа? А если у нас на вход
поступают не картинки и не песенки, а фиговинки? А для фиговинок есть
программа? Эта книжка пишется для будущих профессионалов ­ и
запомните, лично у вас всё всегда будет плохо. Или очень плохо. На вход
вашей программы будут поступать именно такие данные, для которых
никаких средств никто ещё не придумал.
Суммируем требования ­ на входе есть несколько файлов заранее
неизвестного, но, скорее всего, немаленького размера, наша задача
сохранить их в памяти ­ причем для заранее не установленных целей ­ а
что­то потом по требованию с ними сделать ­ например, сохранить под
другим именем и в другом формате, послать на экран, написать поверх
хорошее доброе слово...
Повторяю ­ задача эта абсолютно реальна, и, став ­ если ­
программистом, ты, мой маленький дружок, будешь заниматься этим
бесконечно и безначально.
А теперь постановка задачи:
Написать набор процедур. Набор процедур будет оформлен в виде
модуля. Собственно процедуры:
1. На вход поступает имя файла, который нужно загрузить.
2. Мы загружаем сам файл ­ в предыдущей процедуре.
3. По имени файла выдаем его на экран. Тут у нас будут серьёзные
проблемы. Зато узнаем, как устроен файл с картинками изнутри.
4. Для удобства процедура выдаёт количество файлов и их имена. Куда ­
неважно, она может выводить их на экран или возвращать через
программный интерфейс.
Итого четыре процедуры, которые нам надо написать.
Как всегда поступают настоящие программисты, пишем интерфейсы
программ, при этом всячески стараясь забыть, а что у них будет внутри.
Здесь должна была быть цитата из Дейкстры, но я её не нашёл.
Может, это не Дейкстра был? Дейкстра, если что, ­ это такой
величайший программист всех времён и народов. Смысл мысли был в
том, что когда программист программирует какую­то конкретную
фигню, то он должен быть на ней, на фигне, и сосредоточен, но не
262

полностью, а только на 99%. А в уголке мозга у него, программиста,
должна быть красная лампочка в погашенном состоянии. И когда
программист тем текстом, который он пишет сейчас, портит что­то
из написанного ранее, а, возможно, также их того, что будет написано
только позже ­ лампочка должна ярко вспыхнуть. Ну, вы поняли. © Всё­
таки Дейкстра, я же сам по себе не мог этого придумать
0.

Модуль оформим в первом приближении так:

Вариант 1
unit XPicture;
{
interface
procedure AddPic(
fName : s t r i n g ) ;
procedure ShowPic(
fname : s t r i n g ) ;
f u n c t i o n NumOfPic : i n t e g e r ;
f u n c t i o n NameOfPic(
num
: integer;
v a r fName : s t r i n g ) ;
{
implementation
{
end;

j

j
j

Для чего эти нелепые длинные комментария из одних тире ­ а это мне так
нравится, и я всех так заставляю писать. Они, то есть сотрудники,
сопротивляются, а я их ловлю и заставляю. Для чего буква «X» в начале
имени модуля? Это уже серьёзнее. Если ваша программа будет работать с
картинками, то, почти наверняка, вы будете использовать чужие модули.
Чужие ­ не в смысле, что они будут вылезать из монитора и смачно
откусывать вашу голову как в популярном кино, а в исконном смысле ­
модули, написанные не вами.
И, опять­таки почти наверняка, вы встретите модуль который кто­то
непредусмотрительно называл именно Picture ­ ведь это просто
очевидное
имя. И
программа
наша
немедленно
перестанет
транслироваться ввиду конфликта имён. Предусмотрим хоть какую­то
защиту от дурака ­ пусть имя нашего модуля будет XPicture. А почему не
XPictures, что было бы несколько естественнее? А потому что мы в ДОСе.
Имя файла здесь ограничено восемью символами, так что лучше не
выпендриваться.

263

А теперь по существу. А по существу ­ всё, что мы организовали в нашем
модуле чрезвычайно похоже на объект, или если кому­то нравится
называть его по­другому ­ класс. Об объектах и классах мы поговорим
позже.
Второй, немного занудный момент ­ а почему, собственно, процедура, а
не функция. Ведь можно было написать, и очень многие пишут, а
некоторые даже советуют писать другим, вот так:
function AddPic(

fName

: string)

: integer;

И процедура возвращала бы нам результат нашей операции. Например 0 ­
всё хорошо, ­1 ­ файл не найден, ­2 ­ файл найден, но формат его не
соответствует нашим ожиданиям, и так далее. Простой ответ ­ мне это не
нравится. Ответ чуть посложнее ­ есть такая формула­заклинание ­
Процедура не должна иметь побочных эффектов. Это сказано не мной и
давным­давно. Смысл этой мантры в том, что ум человеческий ограничен
и причём крайне ограничен. Программисту неимоверно сложно уследить
даже за тем, что происходит в программируемой им сейчас процедуре,
даже принимая во внимание то что передаётся и получается
программистом через формальные параметры. А уж понять при этом, что
такое происходит через результат процедуры, таких гениев просто нет.
На самом дел автор заклинания имел в виду, что из процедуры нельзя
менять глобальные переменные объявленные вне процедуры. Но я
искренне надеюсь, что вы и близко даже не понимаете о чём я говорю. И
даже и не пытаетесь понять.
Прошу обратить внимание, что написанное нами транслироваться (пока
что) ни под каким видом не будет. Причина понятна ­ в секции
интерфейса (interface) прописаны заголовки процедур, а в секции
реализации (implementation)
процедуры эти начисто отсутствуют.
Следующим шагом мы это исправим.
Вариант 2
unit XPicture;
{
interface
procedure AddPic(
fname : s t r i n g ) ;
procedure ShowPic(
fname : s t r i n g ) ;
f u n c t i o n NumOfPic : i n t e g e r ;

264

f u n c t i o n NameOfPic(
var
{
inmplemantation
{
procedure AddPic(
begin
end;
{

: integer;
: string);
j
j

fname

: string);

j

procedure ShowPic(
begin
end;
{
f u n c t i o n NumOfPic
begin
end;
{

num
fname

fname

: string);

j
: integer;

f u n c t i o n NameOfPic(

j
num
v a r fName

: integer;
: string);

begin
end;
{

j

end;

А вот этот вариант программы безусловно можно транслировать. Это
значит, что если ваш коллега сейчас пишет Большую Программу, которая
будет использовать этот модуль, то он вполне может его подключить и
работать дальше. Модуль, конечно, неработоспособен, но интерфейсы
процедур и функций уже определены.
Кстати, о функциях, которых здесь ровно две. При трансляции на неё
будет выдано предупреждение (warning) на предмет того, что
возвращаемое значение функции не определено, и при обращения к ней
результаты в лучшем случае будут непредсказуемы. А худшем случае ­
плачевны. Прошу обдумать на досуге, почему это не относится к
процедурам.
Чтобы не изменять текст заново из­за такой мелочи, подумаем о вещах
более серьёзных ­ а где собственно будут храниться наши загадочные
картинки? В целом понятно ­ они будут храниться в динамически
выделенной памяти, на которую будут указывать наши указатели
(извините за каламбур). Но вот где будут храниться сами указатели?
Если бы это была книга о программировании на и под Delphi, то ответ
был бы очевиден ­ указатели хранились бы, само собой, в списке. Это
265

потому, что стандартная библиотека Delphi предоставляет программисту
готовый к использованию класс TList. В Turbo Pascal этого нет. Это есть
в сторонних библиотеках для Паскаля, но с ними я связываться
категорически не рекомендую. Почему не рекомендую, объясню,
красочно и с примерами, в книге по Delphi, если я её допишу и в ней
будет потребность у потребителя.
Хотя, с другой стороны, поскольку Паскаль сейчас язык в основном,
процентов на девяносто учебный, можно применить и такую стороннюю
библиотеку вроде Turbo Professional / Object Professional. Вреда никакого
не будет. Ведь вы же пишете программы просто для развлечения, и
продавать, а тем более ­ поддерживать их ­ не собираетесь, да?
Поэтому моё предложение такое ­ ссылки на картинки мы будем хранить
в самом обычном массиве. Поскольку в Паскале при объявлении массива
обязательно надо указать тип его элемента, этим типом элемента будет
указатель. А поскольку это не список, который сам в себе хранит
количество своих элементов, то нам его, количество элементов, придётся
хранить непосредственно, в виде отдельной переменной.
Сразу надо решить, сколько максимально картинок мы будем хранить.
Вопрос серьёзный, потому что в Turbo Pascal статически можно выделять
не больше 64K памяти. Но там будут храниться не картинки! Ещё раз ­ не
картинки там будут храниться! Там будут храниться имена картинок ­ 12
байтов (имя 8 + расширение 3 + точка). Если кто­то скажет, что точку
хранить не надо, я его заочно стану презирать, и карма его безнадёжно
понизится. Ещё там будет храниться самое главное ­ указатели на
картинки ­ 4 байта и размер картинок ­ ещё 4 байта. Итого 20 байтов на
всё. Для начала я предлагаю ограничиться скромным количеством в 256
хранящихся изображений. Не забывайте, что основная память, в которой
будут храниться сами картинки, тоже не резиновая.
И чтобы два раз не вставать. Заранее предусмотрим случай, когда нам
захочется не только добавить в список картинку, но ещё и удалить её ­ а
нам ведь захочется, правда? Нет, конкретно нам, никогда этого не
захочется ­ ведь мы аккуратные и дисциплинированные люди. А
захочется пользователям, которые вечно мельтешат и путаются под
ногами. Поэтому теперь очередной вариант нашего модуля, со всеми уже
накопившимися добавлениями:
266

Вариант 3
unit XPicture;
{
interface
const
maxPic = 256;
var
numOf
: integer;
Ps
: array[1..maxPic] of pointer;
Names
: array[1..maxPic] of s t r i n g [ 1 2 ] ;
procedure AddPic(
fName
: string);
procedure ShowPic(
fName : s t r i n g ) ;
f u n c t i o n NumOfPic : i n t e g e r ;
f u n c t i o n NameOfPic(
num
: integer;
v a r fName : s t r i n g ) ;
{
implementation
{
procedure AddPic(
begin
end;
{

j
]

fName

: string);

j

procedure DeletePic(
begin
end;
{

nomer : i n t e g e r ) ;

j

procedure ShowPic(
begin
end;
{
f u n c t i o n NumOfPic
begin
end;
{

j

fName

: string);

j
: integer;

j

f u n c t i o n NameOfPic(
var

num
fName

: integer;
: string);

begin
end;
{

j

initialization
numOf:=0;
end;

Теперь неплохо обдумать, почему добавляем мы картинки по имени, а
удаляем уже по номеру, и сравнить преимущества и недостатки того и
другого способа. А также появилось несколько глобальных (для нашего
модуля) переменных. Если по ним есть какие­то вопросы, то они
разъяснятся на следующем варианте нашей программы.
267

А теперь возникает мелкая техническая проблема ­ а в каком виде
хранить картинки в памяти ­ как они есть, то есть в хитром формате
BMP Или преобразовать их в какой­то внутренний универсальный
формат? Опять­таки предлагаю об этом поразмыслить на досуге, в
свободное время, которого у вас конечно предостаточно. А поскольку у
меня свободного времен экстремально мало, предлагаю не думать и
применить вариант номер один. Картинка грузится в память как файл, не
анализируя структуру файла. И как файл в памяти и хранится. Как файл
означает ­ если, безо всяких выкрутасов и преобразований, картинку
скопировать из памяти на диск ­ получим в точности то, что мы и
загружали.
Итак, пошли по списку процедур. Первым номером ­ загрузить картинку,
сохранить в памяти и добавить в наши списки. Или подробнее:
procedure AddPic(
begin
end;

fName

: string);

Очень скучное но очень маленькое техническое отступление. С
переходом от ДОС к Windows многие вещи значительно упростились.
Например, намного проще стала работа с указателями. А некоторые, как
легко догадаться, совсем наоборот. Как оно было в ДОСе ­ запускаем
программу и открываем ­ точнее пытаемся открыть файл. В каком
каталоге программа ищет файл? Странный вопрос в том ­ из которого
она, программа, запущена. Или нет? Вопрос, конечно, интересный и, на
самом деле, непростой. Вы позже много раз столкнётесь с этой
проблемой. В любом случае, общий алгоритм:
Перейти в текущий каталог
Проверить, существует ли файл
Узнать его размер
Выделить память в соответствующем количестве
Загрузить файл в эту память
И всё ­ освобождать память не надо!!! Мы ведь хотим, чтобы картинка
наша оставалась в доступности на всё время выполнения программы.
Получается примерно так:
268

procedure AddPic(
fname : s t r i n g ) ;
var
F
TFile;
SR
SearchRec;
integer;
rez
begin
F i n d F i r s t ( f n a m e , A r c h i v e , SR);
i f rez
0 then begin
numOf:=numOf + 1 ;
Names[numOf]:=SR.Name);
Sizes[numOf]:=SR.Size;
3etMem( P s [ n u m O f ] ,
SR.Size);
A s s i g n ( F, SR.Name);
R e S e t ( F, 1 ) ;
B l o c k R e a d ( F, P s [ n u m O f ] ^ , S R . S i z e ) ;
Close(F);
end;
end;

А теперь заглянем чуть дальше, за границы Турбо Паскаля. Из какого
каталога/директории/папки будет загружен наш файл? Очевидно ­ из
того, из которого запущена наша программа. Если файла там внезапно не
окажется, мы будем разочарованы. Забегая вперёд, в Windows всё не так.
Файл система будет искать в текущем каталоге, который совсем не
обязательно является тем самым из которого наша программа запущена.
Поэтому в Delphi настоятельно рекомендуется первой строкой любой
программы, работающей с файлами, сделать следующую:
ChDir(ExtractFilePath(ParamStr(0)));
Мы видим вызов процедуры, которая вызывает процедуру, которая
вызывает процедуру. У последней процедуры, кстати, тоже есть
параметр.
Поглядим, что они собственно делают, но не слева направо, а справа
налево. Это очень скучно, но это совершенно необходимо, поэтому оно
будет.
ParamStr ­ функция возвращающая один из параметров, с которым
вызвана наша программа. Имеется в виду, что программа вызвана из
командной стоки что­то вроде pkzip ­rP mumu. В этом случае ParamStr(I)
вернет строку "­rP', a ParamStr(2) вернет строку. "mumu". Всё это для нас
имеет сугубо исторический интерес, поскольку вряд ли придётся писать
нам программу, параметры которой передаются через командную строку.
Но вот параметр номер ноль имеет совершенно иной смысл ­ почему­то.
269

Последний параметр возвращает полный путь к запускаемой программе ­
в данном случае что­то вроде "c:\pkzip.exe". Для чего нам это нужно? В
нашем случае чтобы выковырять из этого полного имени нашей
программы путь, где она, наша программа, лежит. А затем, путем
применения процедуры ChDir туда в этот каталог и перейти ­ то есть
сделать его рабочим. А что такое рабочий каталог было объяснено
несколько раньше.
Поскольку писать этот ужас каждый раз заново совершенно невозможно,
напишем это один раз и положим в отдельный модуль, который и будем
прицеплять потом ко всем нашим программам, даже не раздумывая,
нужен он там и ли нет. Примерно так:
unit OurLib;
{
interface
{
p r o c e d u r e GoHome;
{
implementation
uses
Dos;
{
p r o c e d u r e GoHome;
var
path
dir
name
ext

}
}
j

j

:
:
:
:

PathStr;
DirStr;
NameStr;
ExtStr;

begin
path:=ParamStr(0);
F S p l i t ( p a t h , d i r , name, e x t ) ;
ChDir( path + d i r ) ;
end;
{

j

end.

А надо ли это вообще? Скорее всего, не надо. В предыдущих главах вам
встречалась милая пионерская программка, в которой половина
переменных была не инициализирована ­ а программа несмотря на это
работала, и хорошо работала. При запуске Паскаля память зачищается
нулями. Тем не менее, если вам за программирование платят деньги,
рассчитывать на такое везение я бы не стал. Так и здесь. Я бы не стал
рассчитывать что каталог, из которого программа запущена, является
рабочим. Одна манипуляция со свойствами ярлыка...
270

Один меткий выстрел... © Женитьба Фигаро.
Теперь пишем процедуру, удаляющую из нашей коллекции картинку.
Она будет заметно проще.
procedure D e l e t e P i c (
ind : integer);
var
i
: integer;
begin
i f ( i n d > = 1 ) a n d ( i n d < = n u m O f ) do b e g i n
FreeMem( P s [ i n d ] , S i z e s [ i n d ] ;
f o r i : = n u m O f downto i n d + 1 do b e g i n
Ps[i­1]:=Ps[i];
Names[i­1]:=Names[i];
Sizes[i­1]:=Sizes[i]
end;
numOf:=numOf­1;
end;
end;

Проверку на корректность номера удаляемого элемента добавлять надо,
не раздумывая, и не надеясь на порядочность пользователя. Со мной
работала чудесная девушка, в смысле ­ красавица просто неописуемая, а
ко всему ещё исключительно умная. Единственный её недостаток
заключался в том, что, встретив в программе диаметр чего­то, она
никогда не проверяла его на равенство нулю. «Но это же диаметр!» ­
объясняла она окружающим идиотам ­ «Диаметр всегда больше нуля!».
Жизнь над ней периодически грязно надругивалась.
Обратите внимание на заполнение дыры в массивах, образовавшейся
после удаления элемента. Делается это от конца массива к началу, в
обратном порядке. Почему и зачем, рассказано в первой книге, а сейчас
просто напоминаю. Почему­то все забывают.
Далее пишем элементарнейшие функции, возвращающие количество
картинок и их имена. Даже объяснять нечего.
{
f u n c t i o n NumOfPic : i n t e g e r ;
begin
NumOfPic:=numOf;
end;
{
procedure NameOfPic(
num

j

j
: integer;

271

var

fname

: string);

begin
(num>=1) a n d (num