Текст
                    С. Немнюгин, Л. Перколаб
ИЗУЧАЕМ TURBO
PASCAL

С Немнюгин, Л. Перколаб ИЗУЧАЕМ TURBO PASCAL Е^ППТЕР Москва Санкт-Петербург • Нижний Новгород Воронеж Новосибирск Ростов-на-Дону Екатеринбург Самара Киев Харьков Минск 2006
Содержание От издательства...........................6 Давайте познакомимся!.....................8 О чем эта книга?......................9 Об авторах этой книги................11 Стоит ли читать эту книгу?...........11 Как читать эту книгу?................12 Глава 1. Собираемся в путешествие........13 Наставления..........................15 Что мы возьмем с собой...............23 Учим арифметику Паскаля..............36 Переменные...........................39 Оператор присваивания................44 Преобразование типов.................49 Итог.................................53 Глава 2. В поисках истины и... лжи. Операторы....................56 Условный оператор....................58 Логические операции..................66 Логический тип переменных............74 Составной оператор...................75 Оператор выбора......................77 Итог.................................78
4 Содержание Глава 3. Помогаем древнегреческому герою. Циклы ..............................81 Цикл с предусловием..................85 Цикл с постусловием..................88 Цикл со счетчиком....................89 Программы с циклами..................92 Итог................................101 Глава 4. В поисках сокровищ. Массивы.....106 Массивы.............................1°8 Сортировка — от хаоса к порядку.....118 Поиск заданного элемента в массиве. ... 122 Двумерные массивы...................125 Итог................................128 Глава 5. Привал, играем в «Puzzle». Подпрограммы и модули.....................134 Процедуры ......................... 136 Подпрограммы-функции................151 Рекурсивные процедуры...............157 Модули..............................163 Итог................................167 Глава 6. В диковинном саду. Типы переменных и множества....................170 Предопределенные типы переменных ... 1 72 Строковый тип........................181 Перечисляемый тип данных.............192 Ограниченный тип данных..............195 Запись...............................203 Множества............................209 Итог.................................215 Глава 7. Что посеешь, то и пожнешь, если... научишься работать с файлами.............218 Файлы.................................219
Содержание 5 Текстовые файлы......................221 Типизированные файлы.................230 Нетипизированные файлы...............234 Итог.................................234 Глава 8. Восточный мотив, или Рисуем Королеву Красоты. Работа с графикой .... 237 Текстовый и графический режимы.......238 Инициализация графического режима . . . 240 Команды графического режима..........245 Вывод текста.........................263 Наш собственный графический модуль . . 266 Построение графиков функций в декартовых координатах.............269 Физико-математические узоры..........271 Рекурсивные методы в построении графических изображений..............273 Что такое анимация?..................278 Итог.................................291 Глава 9. ООП! Или лебединая песня клавиатуры...............................293 ООП..................................294 Лебединая песня клавиатуры...........301 Итог.................................309 Прощание..................................310 Что еще почитать?....................312
От издательства Не секрет, что многие школьники разбираются в ком- пьютерах гораздо лучше своих родителей и своих учителей... Их не нужно учить нажимать на клавиши и «двигать окна» по экрану. Они уже не «чайники» — они хотят стать «крутыми юзерами» или «программе- рами»... Вот для таких ребят и предназначены книжки на- шей новой серии — «КомпАс» (компьютерный ас!)- Они для тех, кто хочет быть на «ты» с компьютером, кому интересно попробовать свои силы в создании собственных программ, собственных Web-страничек, короче — использовать «комп» как инструмент для творчества! У этой серии есть подзаголовок — «школьный клуб». Что значит «клуб»? Это неформальное обще- ние, это общие интересы, это увлеченность совмест- ным делом... Авторы книг нашей серии «КомпАс» занимаются со школьниками в компьютерных клубах или круж- ках, а некоторые главы или примеры из книг даже придуманы самими ребятами. Итак, это не скучные учебники по программирова- нию — мы постарались сделать книги интересными и увлекательными, оформить их забавными рисунками, привести нестандартные примеры.... Хотя это никак не означает, что книги получились несерьезными — они учат программированию и ком- пьютерным технологиям, их можно использовать и на внешкольных занятиях, и на уроках информатики, и для самостоятельного чтения. Они пригодятся не только школьникам, но и учителям. Сейчас в серии «КомпАс» задуманы или уже опуб- ликованы такие книги:
От издательства 7 «Изучаем Интернет, создаем Web-страничку»; «Изучаем Turbo Pascal»; «Изучаем Delphi»; «Изучаем Basic»; «Изучаем C++»; «Изучаем устройство компьютера». Одну из них вы держите сейчас в руках... Успехов вам в создании собственных программ, в превращении из «просто пользователя» в «компью- терного аса»! А если у вас есть идеи насчет новых книжек, если вы хотите задать вопросы авторам или издательству, если вы найдете в книгах ошибки (бывает и такое!..) или просто захотите поделиться своими впечатления- ми от книг — пишите нам по адресу электронной поч- ты comp@piter.com (издательство «Питер», компью- терная редакция). Информацию о других книгах, выпускаемых изда- тельством, можно найти на нашем Web-сайте http:// www. р i t е г . с от.
Давайте познакомимся!
О чем эта книга? В 1970 г. появился новый язык программирования. Автор языка, швейцарский ученый Никлаус Вирт назвал его Паскалем в честь великого французского математика, философа и изобретателя XVII века Блеза Паскаля. Паскаль изобрел вычислительное устройство, именно поэтому новому языку было дано его имя. Благодаря своей четкости и логичности Паскаль надолго занял свое место среди других языков про- граммирования не только как средство разработки серьезных программ-приложений. Он также очень хорошо подходит для обучения программированию. Впоследствии появились различные версии языка и его расширения, дополненные новыми возможностя- ми. Наиболее известным расширением стал пакет Турбо Паскаль фирмы Borland, появившийся в 1983 г. и сразу ставший событием в мире компьютерных тех- нологий. Первое упоминание о нем содержалось в рекламе, опубликованной в журнале BYTE, а сам па- кет предназначался для операционной системы СР/М. В начале 1984 г. он был перенесен в среду операционной системы для персональных компьютеров MS-DOS и приобрел огромную популярность. С тех пор появи- лось несколько версий Турбо Паскаля, последняя — седьмая. Фирма Borland/Inprise завершила линию продук- тов Турбо Паскаль и перешла к выпуску системы ви- зуальной разработки для Windows — Delphi. Несмот- ря на это, Турбо Паскаль продолжает играть роль отличного языка для первого знакомства с миром «серьезного» программирования. Это связано как с
10 Давайте познакомимся! его четкой логической структурой, так и с теми воз- можностями, которые позволяют использовать Турбо Паскаль для решения разнообразных задач. Среди них вычисления и обработка данных, компьютерная графика, работа со звуком, системное программиро- вание. Турбо Паскаль позволяет применять приемы объектно-ориентированного программирования — од- ной из ведущих современных компьютерных техно- логий. Вместе с тем Турбо Паскаль прекрасно работает на сравнительно маломощных компьютерах, и поэтому именно этот пакет часто устанавливается в компью- терных классах школ и кружков информатики. Как научиться программировать на Паскале? Нет недостатка в учебниках, содержащих описание грам- матики и синтаксиса языка Паскаль, и они действи- тельно необходимы для усвоения основных правил программирования. Однако лучший способ научить- ся программировать — посмотреть, как это делают другие. В нашей книге рассматриваются решения различных задач, от простых до достаточно сложных, соответствующих уровню обучения информатике и программированию как в школах, так и, частично, в высших учебных заведениях. Параллельно с решени- ем задач мы изложили курс программирования на языке Паскаль так, чтобы его мог освоить каждый и без посторонней помощи. Последовательность изло- жения материала такова, что читатель может сразу приступить к самостоятельной работе на компьютере, чтобы научиться программированию, решая конкрет- ные задачи. Все без исключения примеры и упраж- нения, вошедшие в это пособие, были проверены на вычислительной машине. В конце каждой главы име- ются задачи для самостоятельного решения.
Стоит ли читать эту книгу? 1 1 Об авторах этой книги Авторы этой книги уже давно работают со школьни- ками и студентами. С. А. Немнюгин преподает про- граммирование на физическом факультете Санкт-Пе- тербургского государственного университета и ведет занятия курса «Физическая информатика» в лицее № 395 Санкт-Петербурга, а Л. В. Перколаб работает в физико-математическом лицее № 27 г. Харькова. Опыт проведения этих занятий и лег в основу книги. Мы считаем наших учеников, бывших и нынешних, свои- ми соавторами. Стоит ли читать эту книгу? Эту книгу стоит читать, если вы, уважаемый чита- тель, еще не знакомы с программированием в среде Турбо Паскаля, хотите научиться программировать, но при этом не любите читать слишком серьезные книги-учебники. Чтобы не было скучно от излишне схоластических упражнений, мы разнообразили темы, привлекая там, где было возможно, элементы фантазии, опираясь на авторитет различных литера- турных героев — от Ала ад-Дина до Шерлока Холмса, но не жертвуя при этом строгостью рассуждений. В части основ алгоритмизации и программирова- ния книга соответствует школьной программе по ин- форматике для 8—11-х классов и рассчитана в первую очередь на учащихся школ, лицеев, колледжей. Мы надеемся, что книга будет полезной также и для аби- туриентов, студентов младших курсов высших учеб- ных заведений и всех тех, кто хотел бы самостоятель- но научиться писать программы на Паскале.
1 2 Давайте познакомимся! Авторы надеются, что наша книга поможет читате- лю открыть увлекательный мир программирования, и с благодарностью примут замечания и пожелания по адресу электронной почты: nemnugin@mph.phys.spbu.ru Как читать эту книгу? Нельзя научиться бегло говорить на языке, познако- мившись с ним только по учебнику, нельзя стать про- граммистом, не поработав на вычислительной маши- не. Так и эту книгу лучше читать, расположившись у компьютера, на котором установлен Турбо Паскаль версии 7.0. Дело в том, что в каждой главе имеются примеры готовых программ, демонстрирующих раз- личные возможности Турбо Паскаля, и чтение книги следует сопровождать выполнением этих примеров.
ГЛАВА 1 Собираемся в путешествие
Программирование можно сравнить с огромной стра- ной, полной чудес, сюрпризов, замечательных нахо- док и даже... опасностей! Начинающий пользователь или опытный «геймер» — победитель виртуальных чудовищ не всегда знает, что, овладев даже основами программирования, он может заставить компьютер рисовать картинки, немногим уступающие по красоте шедеврам игровой компьютерной графики, или ис- полнять мелодию, подмигивая при этом световыми индикаторами своей клавиатуры. Ну а чем не замеча- тельная перспектива — решение сложной программи- стской задачи, решение, которое смог найти именно ты и никто другой, и вот уже твою программу «скачи- вают» через Интернет сотни и тысячи пользователей, передавая друг другу имя ее создателя! Ну а опасно- сти? Есть и опасности. В историю программирования вошла ошибка программиста, создавшего управляю- щую программу для американского межпланетного зонда и «набившего» вместо запятой точку. Запятая потеряла хвостик, а межпланетный зонд «промахнул- ся» мимо Венеры на несколько миллионов километ- ров. А если нечто подобное произойдет в программе, управляющей, например, полетом боевой ракеты? Ну что же, если ты готов в поисках интересного и неожиданного преодолевать трудности, а порой и са- мого себя, мы приглашаем тебя, дорогой читатель, в путешествие по стране программирования! Любое серьезное путешествие начинается с подго- товки. На этом этапе путешественник достает карты местности, изучает их, разрабатывает маршрут. Затем он собирает рюкзак, укладывая в него все необходи- мое, но не перегружая себя на первых порах излиш-
Наставления 15 ним имуществом вроде телевизора или микроволно- вой печи, которые вряд ли понадобятся в походе, хотя в домашнем обиходе это вещи очень полезные. Карту местности ты, читатель, уже достал — это наша книга. Ее название говорит о том, что нам предстоит путешествие в один из достаточно хорошо изученных районов страны программирования — район програм- мирования на Турбо Паскале. Это, конечно, уже не перёдний край информационных технологий, но раз- ве пустит кто-нибудь начинающего скалолаза на Эверест? Вот и мы начнем с того, что проще, хотя Паскаль нельзя назвать совсем уж простым языком программирования. Это серьезный язык, который хо- рош и для первоначального знакомства с миром серь- езного программирования, и для разработки приклад- ных программ. Проводниками в первом походе будем мы — авторы этой книги, которые уже не один год знакомят школьников с программированием на Тур- бо Паскале. Разработан и маршрут путешествия, по- знакомиться с ним можно по оглавлению книги. Мы постараемся показать все основные достопримеча- тельности и дать вам возможность «потрогать их ру- ками». Наставления Программирование. Итак, приступаем к сборам. Преж- де всего, попробуем разобраться, куда мы отправля- емся: что такое программирование? Вы знаете, что программы пишут для компьютера. Слово «компью- тер» в переводе на русский язык означает «вычисли- тель». Вычислительных устройств много, среди них различные калькуляторы, а также более старые, меха- нические вычислители — арифмометры, логарифми-
16 Глава 1 Собираемся в путешествие ческие линейки, счеты. Главное отличие компьютера от всех этих устройств заключается в том, что он яв- ляется многоцелевым вычислительным устройством, то есть устройством, которое может решать самые разные задачи. Количество этих задач очень велико, нельзя заранее предусмотреть все возможные ситуа- ции, поэтому надо придумать способ «объяснить» компьютеру, какую задачу и каким образом следует решить в данном случае. Составление таких инструк- ций-программ и называют программированием. Первые компьютеры. Первые настоящие компью- теры появились в 40-е годы XX века. Самым первым компьютером считают британский сверхсекретный «Колосс», который использовался во время второй мировой войны для дешифровки немецких сообще- ний. Примерно в то же время появились компьютеры (или электронно-вычислительные машины — ЭВМ) и в США. Эти компьютеры — ENIAC, EDVAC (и даже MANIAC!) применялись для разработки атом- ной бомбы. Программирование для первых компьютеров было непростым делом. Заметим, что даже такая, казалось бы, умная машина, как компьютер, понимает лишь самые простейшие команды (машинные коды). Лю- бое сложное действие приходится разбивать на мно- жество элементарных, понятных компьютеру и, пре- жде всего, его центральному процессору, устройству, которое является мозгом и сердцем компьютера. Со- ставить программу в машинных кодах может лишь человек, который очень хорошо разбирается в устрой- стве компьютера. На заре компьютерной эры про- граммированием занимались в основном специалис- ты по электронике, которые создавали компьютеры и совершенствовали их. Написать программу в машин- ных кодах для них не составляло большого труда,
Наставления 17 а саму программу набирали на специальных панелях с помощью перемычек. Человеку, не посвященному в тайны устройства электронно-вычислительной ма- шины, такой труд был не по силам. Вот пример не- большого фрагмента программы, написанной в ма- шинных кодах: ...457f464с01010О0100О0000000О000000002000 30001000003d0080000340000069800000000000 00034002 0000500280016001300060000003400 00003408000000000000а00О0000а0000000О500 00000400000003000000d4000000d40800000000 0000130000001300000004000000010000000100 0000000000000008000000000004f5000004f500 0000050000100000000001000004f8000014f808 000000000000C4000000C8000000060000100000 0000020000052C0000152C080000000000009000 000090O000000600000O0400006c2f62696c2f2d 64696c756e2e786f73312e000000110000001100 0000000000000eO000000a000000000000... Инженеры-электронщики совершенствовали ком- пьютеры, переходя с электронных ламп на транзи- сторы, а с транзисторов — на интегральные схемы. Компьютеры становились все более мощными, на- дежными и дешевыми. В результате этого новыми машинами заинтересовались ученые и инженеры, которые хотели использовать их для решения своих задач, таких, например, как расчет траектории меж- планетного полета или моделирование структуры эле- ментарной частицы. Но ученые и инженеры были специалистами в своих областях и не могли тратить время на детальное изучение устройства компьютера и его машинных кодов. Они хотели общаться с ком- пьютером на языке, который был бы понятен им — ученым и инженерам. Так появились первые языки
1 8 Глава 1 Собираемся в путешествие программирования высокого уровня, в которых ис- пользовались понятные человеку слова английского языка и привычные математические символы. Такой язык был уже слишком сложным для компьютера, поэтому пришлось создать и специальные програм- мы-трансляторы, то есть программы-переводчики, пе- реводившие программы с языка высокого уровня на язык машинных команд. Языки программирования. Паскаль. Первыми язы- ками программирования были FORTRAN, COBOL, ALGOL и некоторые другие. У каждого из них были свои достоинства и свои недостатки. Одним из наи- более удачных долгое время считался ALGOL, на- столько удачным, что этот язык стали использовать в специальной литературе для записи алгоритмов. Но и он не был лишен недостатков, в частности, последняя версия ALGOL’a была излишне громоздкой, поэтому швейцарский профессор Никлаус Вирт занялся раз- работкой своего собственного языка, который унасле- довал бы от ALGOL’a лучшее, но был бы более лако- ничным и имел бы более четкую логическую структу- ру. Предназначался новый язык для обучения сту- дентов, и сам Вирт поначалу относился к нему как к игрушке. Язык был назван в честь французского фи- лософа и изобретателя механического калькулятора Блеза Паскаля — Паскалем. Новый язык оказался на- столько удачным, что быстро привлек к себе внима- ние и завоевал популярность. Появление нового языка совпало с началом эры персональных компьютеров, которые стали доступ- ными почти каждому обычному человеку (это про- изошло не сразу, и были времена, когда стоимость «персоналки» была сравнима со стоимостью легко- вого автомобиля!). Масла в огонь подлила фирма Borland, которая в первой половине 80-х выпустила
Наставления 19 пакет Турбо Паскаль, содержавший не только транс- лятор, но и редактор, а также другие программы, ко- торые значительно облегчали процесс программиро- вания. Это сделало Турбо Паскаль популярнейшей системой программирования. Турбо Паскаль. Почему не просто Паскаль, а Тур- бо Паскаль? Слово «Турбо» в английском лексиконе обозначает ускорение. Транслятор, входящий в со- став Турбо Паскаля, очень быстро переводит про- грамму с языка программирования в машинные коды, заметно быстрее, чем трансляторы в других системах программирования. Вот поэтому — Турбо. Скажем еще, что Турбо Паскаль — это не отдельный язык программирования, а «расширение» обычного, стандартного Паскаля, включающее интегрирован- ную среду программирования. Слова «интегрирован- ная среда» означают, что из одной программы имеется доступ к редактору текстов, транслятору, справочной системе, отладчику и т. д. В состав Турбо Паскаля входят дополнительные наборы процедур, которые позволяют не заниматься каждый раз программиро- ванием некоторых сложных действий, таких, напри- мер, как вывод графики. Кроме Турбо Паскаля есть и другие системы программирования на Паскале, но мы будем использовать именно Турбо Паскаль. Как пишут программы. Как пишут программы? Новичок может подумать, что делается это просто — выучил язык программирования, сел за клавиатуру, и пожалуйста! Это не совсем так. Настоящее, профес- сиональное программирование — это сложный и час- то трудоемкий процесс (так, исходный текст попу- лярной операционной системы Microsoft Windows 98 содержит несколько десятков миллионов операто- ров!). Начинается разработка программы с постанов- ки задачи, когда становится ясно, что ее имеет смысл
20 Глава 1 Собираемся в путешествие решать с помощью компьютера. Задачу может поста- вить, например, учитель — громким и сердитым голо- сом. А может и программист — сам себе. После того как поставлена задача, возможно, придется хоро- шенько над ней подумать. Если программированием занимается физик или химик, он запишет задачу на языке математики, то есть составит математическую модель явления. Затем наступает очень важный и от- ветственный момент — выбор уже известного или разработка нового алгоритма решения задачи. Алгоритм. Алгоритм — это набор правил, располо- женных в определенном логическом порядке, кото- рый позволяет решать однотипные задачи. Правила эти предназначены для исполнителя, которым может быть и человек, и автоматическое устройство. Значи- тельная часть учебы школьника связана с изучени- ем разнообразных алгоритмов. Исполнителем может быть центральный процессор компьютера, именно этот, последний случай нас и интересует. Действи- тельно, центральный процессор представляет собой такое устройство, которое, как мы уже выяснили, мо- жет действовать только на основе четких, однознач- ных, не допускающих двойного толкования инструк- ций. Эти инструкции, записанные на обычном языке человеческого общения, специальном языке-псевдо- коде или изображенные в виде специальной диаграм- мы (блок-схемы), и являются алгоритмом. Хорошо продуманный алгоритм — половина дела.
Наставления 21 Кодирование и отладка. Алгоритм записывается на языке программирования. Эта операция называет- ся кодированием. На этапе кодирования главное — внимательность, поскольку каждая опечатка являет- ся ошибкой в тексте программы. Программа — это пе- ревод алгоритма на язык программирования. Готовая программа должна быть оттранслирована и проверена. Во время трансляции программа-транс- лятор сообщает о наиболее грубых ошибках, допу- щенных при наборе программы. Это ошибки несоот- ветствия правилам, принятым в данном языке. Если такие ошибки исправлены, создается исполняемый файл, который и заставляет компьютер выполнять предписанную последовательность действий. Такой файл называется исполняемым файлом. Часто оказывается, что даже если «грамматиче- ских» ошибок при наборе программы допущено не было, она работает не так, как хотелось бы програм- мисту, — вместо прямоугольника рисует треугольник или, складывая двойку с тройкой, получает единицу. В этом случае приходится возвращаться к исходному тексту программы и разбираться, в чем ошибка. Та- кой процесс называется отладкой программы, и завер- шается он, если, конечно, программисту хватит терпе- ния, созданием окончательного варианта программы. Алфавит и зарезервированные слова языка Пас- каль. Обычный язык человеческого общения — рус- ский, английский, строится из элементарных состав- ляющих — букв, образующих алфавит языка. Буквы используются для построения слов, слова складыва- ются в предложения. Из предложений состоит любой текст — записка подружке, толстый роман или сек- ретное донесение. Всякий язык программирования организован примерно так же. Имеется алфавит язы- ка, то есть набор символов, которые можно использо-
22 Глава 1 Собираемся в путешествие вать в программе. В Паскале алфавит содержит буквы латинского алфавита, цифры, некоторые специаль- ные символы (такие как знак подчеркивания, двоето- чие и т. д.) и двойные символы (примером такого двойного символа является знак «больше или равно» >=)• В «нормальном» языке содержится огромное ко- личество слов (сотни тысяч), да еще каждое слово может иметь варианты. Язык программирования про- ще и одновременно точнее. Прежде всего, в нем суще- ствуют зарезервированные слова, имеющие вполне определенный смысл и определенное назначение. Их нельзя изменять, любая неточность в написании та- ких слов является серьезной ошибкой. Стоит в про- грамме написать не то слово или чуть-чуть изменить зарезервированное слово, транслятор сразу же отве- тит сообщением об ошибке и, «обидевшись», прекра- тит работу. С другой стороны, в отличие от естественных язы- ков человеческого общения в языках программиро- вания можно вводить свои собственные слова и при- давать этим словам свой собственный смысл. Неболь- шую программу можно уподобить письму или ма- ленькому рассказу. Большой программный проект — это роман. Как и обычное письмо, программа может быть написана хорошим или плохим «слогом» (сти- лем), ,и чем лучше стиль, тем понятнее программа, тем меньше вероятность появления в ней ошибок. Язык Турбо Паскаль состоит приблизительно из 80 зарезервированных слов и специальных символов. В большинстве случаев овладение даже небольшой частью этого «словаря» достаточно для начала ус- пешной работы по программированию на Паскале. Далее в этой книге мы будем постепенно знакомиться
Что мы возьмем с собой 23 с наиболее важными зарезервированными словами языка Паскаль. Основные понятия программирования. Опытный программист знает, а новичку полезно узнать, что главными элементами любой программы являются переменные, константы и операторы. Переменная — это ячейка (или несколько ячеек) оперативной памя- ти компьютера. Такой ячейке присваивается опреде- ленное имя, ее содержимое может изменяться в ходе выполнения программы. Вид информации, содержа- щейся в ячейке, набор преобразований, которые мож- но выполнять над этой информацией, и множество допустимых значений определяются типом перемен- ной. Константа отличается от переменной тем, что ее значение не может быть изменено в ходе выполнения программы. Операторы описывают те действия, кото- рые должна выполнять программа. И, наконец, в про- граммах используются предложения описания, кото- рые позволяют перечислить и описать свойства пере- менных, констант и других объектов, которые встре- чаются в данной программе. Что мы возьмем с собой Ну вот, получены краткие наставления и инструкции, и теперь нам остается только собрать все самое необ- ходимое для похода. Транслятор. Мы выяснили, что человек и компью- тер сущности принципиально различные и наладить между ними взаимопонимание очень непросто. Ма- шина понимает только программы, написанные в ма- шинных кодах, но человеку научиться даже просто понимать машинные коды, не говоря уже о том, что- бы составлять на них программы, крайне сложно.
24 Глава 1 Собираемся в путешествие Для того чтобы установить контакт между столь по-разному мыслящими созданиями, как человек и машина, придуманы языки программирования. Чело- век может научиться одному из этих языков, машину тоже можно «научить» понимать его. Нам с вами не- обходимо для этого несколько лет упорных занятий, а компьютеру — всего лишь специальная программа, именуемая транслятором, которая служит для пере- вода программ с языка программирования в машин- ные коды. Трансляторов с языка программирования Паскаль существует множество, одними из наиболее распространенных в нашей стране являются трансля- торы Турбо Паскаль фирмы Borland различных вер- сий. Данная книга рассчитана в основном на трансля- тор версии 7.0, однако необходимо учитывать, что не все написанное для одного транслятора подойдет и для остальных, хотя вроде бы язык программирова- ния остается одним и тем же. Все дело в том, что при написании трансляторов разные фирмы добавляют некоторые дополнительные возможности, которые, однако, трансляторы других фирм не понимают. Тем не менее существует некий стандарт языка Паскаль, который должен оставаться общим для всех фирм, и любой транслятор должен понимать текст, написан- ный на нем. Наша книга посвящена программирова- нию на Турбо Паскале, хотя многие из приведенных в ней программ написаны в соответствии со стандар- тами языка Паскаль, без использования особенно- стей, присущих транслятору Турбо Паскаля. Программа. Транслятор способен понять текст написанный и оформленный надлежащим образом'в виде программы, находящейся в отдельном файле (мы можем считать, что файл — это область на диске, которой присвоено определенное имя). Файл может содержать и более одной программы, однако рассмат-
Что мы возьмем с собой 25 риваться в таком случае будет только первая из них по порядку. При написании программ следует быть внимательным и соблюдать все необходимые пра- вила, ведь транслятор понимает только программы, написанные абсолютно правильно, в отличие от чело- века, который может простить вам некоторые орфо- графические ошибки (кроме разве что учителя рус- ского языка). Интегрированная среда. Для набора текста про- граммы на языке Паскаль (такой текст часто назы- вают исходным текстом программы) используется встроенный редактор текстов интегрированной среды Турбо Паскаля. Интегрированная среда — это тот «ящик с инструментами», без которого не обойтись в нашем путешествии, поэтому нам придется познако- миться с некоторыми ее возможностями и, прежде всего, с теми, которые понадобятся для того, чтобы собрать в путь нашу первую программу. Интегрированная среда запускается командой turbo. При успешном выполнении этой команды на экране появится изображение (рис. 1.1), состоящее из строки меню (верхняя строка), рабочей области ре- дактора, занимающей большую часть поверхности экрана, и строки с перечислением основных функ- циональных клавиш (это клавиши Fl, F2, ...), доступ- ных в каждый текущий момент и связанных с выпол- нением различных действий, таких, например, как запись в файл или трансляция. Ее называют еще строкой статуса. С помощью клавиш этой строки многие действия можно производить «в обход» сис- темы меню. В окне редактора вы и будете набирать и исправлять свои программы. Для входа в главное меню нажмите функциональ- ную клавишу F10 или используйте левую кнопку мышки. Это меню содержит следующие пункты: File,
26 Глава 1 Собираемся в путешествие Edit, Search, Compile, Run, Debug, Tools, Options, Window, Help. За каждым из перечисленных пунктов скрыва- ется вертикальное подменю, которое можно рас- крыть, щелкнув на нужном пункте меню кнопкой мыши или нажав клавишу Enter, когда пункт меню подсвечен. Переход от пункта к пункту осуществля- ется клавишами управления курсором —> или с помощью мыши. Для выхода из главного меню или любого подменю нажмите клавишу Esc. па Search Fun Debuo T Ontions Window Hein (tn-l program diophantine_equation_2: var и. у. z. к, n: Longlnt; begin n := NaxLongint: n :« Trunc(Sqrt(n)); n := n - 7; x := 0; HriteLn( Реяенмя уравнения x*3 » y*2 ♦ 63,’); NriteLnCnpw 1 <= j- <- - for у := 1 to n do begin у <= '. n z :» у » у ♦ 63: repeat 1пс(и): • := и • x » x: until w >= z; if « = z then WriteLn(~(x. y) = (’ 1,1 lJ. Help 1 / Save f-d Open Coxpile F9 Make Rlt»F10 Local xenu Рис.1.1. Рабочий экран интегрированной среды Турбо Паскаля Рассмотрим назначение некоторых пунктов глав- ного меню. Пункт File. Меню File содержит команды, управ- ляющие работой с файлами, а именно: + New — создает в редакторе новое окно, в которое можно загрузить существующий файл или создать в этом окне новый файл; + Open — открывает диалоговое окно, позволяющее загрузить файл с диска и перейти в режим экран-
Что мы возьмем с собой 27 ного редактирования. То же самое действие можно выполнить нажатием клавиши F3; 4 Save — сохраняет на диске текущий редактируе- мый файл и продолжает редактирование. То же са- мое действие можно выполнить нажатием клави- ши F2; + Save as — открывает диалоговое окно, позволяющее сохранить текущий файл под новым именем и, возможно, на новом месте жесткого диска компью- тера. После этой команды текущим становится файл с новым именем; 4- Print — открывает диалоговое окно, позволяющее задать характеристики печати и отправить файл на принтер; 4- Exit — выход из Турбо-среды. Выйти из среды можно, нажав клавиши Alt+x (знак + в такой записи обозначает одновременное нажатие обеих клавиш). Другие, не столь часто используемые команды ме- ню File (Save all. Change dir..., Printer Setup..., DOS shell), мы опускаем. Самая первая программа. Авторы этой книги по своему опыту знают, как хочется новичку побыстрее написать какую-нибудь программу и почувствовать себя настоящим программистом. Ну что же, не будем испытывать ваше терпение, читатель, и напишем са- мую первую программу на Паскале. Программа на Паскале оформляется всегда стандартным образом и имеет следующий вид: program <имя программы>; <раздел описаний> begi п <раздел операторов> end.
28 Глава 1 Собираемся в путешествие Первой строкой программы является ее заголовок. Он, подобно заголовку книги, должен сообщить чита- телю, что это за программа, для решения какой зада- чи она предназначена. Для этого после зарезервиро- ванного слова program пишется название программы. Его можно давать по своему усмотрению, например, mama, Vasja, подчиняясь, однако, следующим прави- лам — имя программы может содержать практически произвольное количество букв (как говорилось выше, только английского алфавита), цифр 0, 1, ...9, знаков подчеркивания «_» и начинаться должно с бу- квы, причем «_» считается буквой. Нельзя давать программам имена, уже имеющие для Паскаля ка- кое-либо значение, как, например, begin, end, program. Это зарезервированные слова. Вот примеры правильных имен программ: ml, _al, ___m__, alal, endd, а вот неверных: l_m, a+b, laaaa, end В программировании принято давать программам имена, которые отражают их назначение, например sortingarray («сортировка массива») или draw_ nicepicture («рисуем замечательную картинку»). Заканчивается заголовок программы символом «точ- ка с запятой». В Турбо Паскале строку заголовка можно не писать. О разделе описаний будет рассказано дальше, в са- мых простых программах его может и не быть. При написании текста программы следует соблю- дать несколько простых правил: > разрешается использовать только символы алфа- вита языка программирования. Большие и малень- кие буквы Паскаль не различает, то есть слово
Что глы возьмем с собой 29 begin может быть написано и так: begin, BEGIN, BeGiN; + вся программа может быть написана и в одну стро- ку — компьютеру все равно, однако из соображе- ний удобства чтения лучше располагать каждое повое высказывание на новой строке. Между begin и end (после завершающего про- грамму end записывается точка) располагаются опе- раторы, разделяющиеся символом «;». Эта часть про- граммы называется разделом операторов. Их, кстати, может и не быть, в этом случае программа не будет ничего делать. Совсем простая программа, которая ничего не делает, имеет вид: program nothingtodo; begin end. или даже: begi n end. Толка от такой программы нет, поэтому сразу же усовершенствуем ее, добавив какой-нибудь оператор. Самый простой и одновременно один из самых важ- ных операторов — оператор вывода, ведь программа должна сообщить пользователю о результатах своей работы, то есть вывести результат на экран дисплея. Пусть наша программа при выполнении просто сооб- щит о себе. Выглядеть она будет следующим образом: program s_privetom_l; begi n Wr ite(’Привет, это я!’); end. Здесь Write — оператор, выводящий на экран все то, что заключено в круглых скобках между апостро-
30 Глава 1 Собираемся в путешествие фами. На самом деле это опера- тор вызова процедуры Write подробнее о процедурах будет рассказано в главе 5. Существу- ет другая разновидность этогс оператора — WriteLn, он дейст- вует точно так же, но после вы- вода информации курсор пере- ходит на новую строку. Про- грамма program s_privetom_2; begi n Wr 1te('Привет,’); Wr 1te(’это я!’) ; end . выведет текст в одну строку, а program s_pr1vetom_3; begin Мг1Ье1_п('Привет, ’) ; Wr i teLn(’это я!’); end. в две: Здравствуй, это я ! В программе может быть практически сколько угодно операторов, надо только не забывать в конце каждого ставить символ « ;» (существует одно исклю- чение — перед словом end «;» можно не ставить) Символ «;» в программах на Паскале играет пример- но ту же роль, что точка в письме. Наберите любой вариант программы (первый, вто- рой или третий) и сохраните его на диске в файл с лю- бым именем по вашему выбору, например my_progl
Что мы возьмем с собой 31 Для этого достаточно нажать клавишу F2, при этом на экране появится диалоговое окно (см. рис. 1.2), в верхней части которого следует набрать имя файла, нажав затем клавишу Enter. В результате на диске появится файл с указанным именем и расширени- ем .pas, которое добавляется к имени файла автома- тически. Рис. 1.2. Диалоговое окно записи файла на диск Пункт Compile. После того как вы набрали про- грамму, ее надо отправить на компиляцию. Это мож- но сделать двумя способами: + выбрать пункт Compile из меню Compile; + нажать комбинацию клавиш Alt+F9. Сначала компилятор проверяет, не содержит ли программа синтаксических ошибок, то есть ошибок в записи операторов и предложений программы, имен переменных и т. д. Обнаружив ошибку, компилятор
32 Глава 1 Собираемся в путешествие останавливает свою работу, устанавливает курсор в то место программы, где найдена ошибка, и выводит соответствующее сообщение (желтым цветом на красном фоне в верхней или нижней части экрана). Меню Compile содержит несколько других пунктов, которые используются редко и потому здесь не рас- сматриваются. Выполните компиляцию набранной вами програм- мы. Если при этом будут обнаружены ошибки, кото- рые, скорее всего, будут связаны с опечатками при наборе текста, исправьте их и еще раз откомпилируй- те программу. Успешная компиляция приведет к со- зданию исполняемого файла, который находится либо в оперативной памяти компьютера и при выходе из интегрированной среды будет утерян, либо на же- стком диске, что предпочтительнее. Для того чтобы сохранить результат компиляции на диске, необходи- мо раскрыть меню Compile, выделить в нем строку Destination и нажать Enter. Слово Memory справа изме- нится на Disk, после чего результат успешной компи- ляции будет записываться на диск — в файл с тем же именем, чго и исходный текст программы, но с рас- ширением .ехе. После этого наша первая программа готова сделать первые шаги. Первые шаги любой про- граммы — это ее выполнение. Запустить программу можно с помощью меню Run. Пункт Run. Меню Run содержит команду Run, ко- торая выполняет два действия. Она компилирует программу, находящуюся в редакторе, и, если в про- грамме не найдено синтаксических ошибок, посылает ее на выполнение. То же самое действие выполняется при нажатии комбинации клавиш Ctrl+F9. Следующие две команды позволяют выполнять программу по шагам (по строкам) и используются в основном при отладке:
Что мы возьмем с собой 33 + Step over (или нажатие клавиши F8) — осуществля- ет построчное выполнение программы без захода в процедуры и функции (последние выполняются как одна строка программы); + Trace into (или нажатие клавиши F7) — команда де- тальной трассировки, которая выполняет построч- ное выполнение программы с заходом в процеду- ры и функции. Режим трассировки заканчивается автоматически, если достигнут конец программы или произошла ошибка выполнения; + Go to cursor (или нажатие клавиши F4) — запускает программу на выполнение до того места, на кото- ром находится курсор (при этом сама отмеченная строка выполняться не будет). Эту команду также полезно использовать при отладке, например, что- бы просмотреть по шагам часть программы с того оператора, в котором предполагается ошибка; + Program reset (или нажатие комбинации клавиш Ctrl+F2) — отменяет установленные ранее режимы Step over, Trace into или Go to cursor. Результатом выполнения нашей первой програм- мы будут слова приветствия. Программа встала на ноги! Ну а вы — вы можете считать себя настоящим программистом! Даже опытному программисту иногда требуется помощь. Сложно бывает, например, удержать в голо- ве правила использования различных процедур и функций Турбо Паскаля. В этом случае можно по- просить помощь. Для этого достаточно просто ска- зать «Help!». Пункт Help. Меню Help предоставляет возмож- ность доступа к информации о языке Паскаль, ин- тегрированной интерактивной среде, библиотечных процедурах, функциях и т. д. Доступ к справочной 2 Зак. №933
34 Глава 1 Собираемся в путешествие информации можно получить несколькими способа- ми: 1. Выбрать пункт Help в главном меню или нажать комбинацию клавиш Alt+H. При этом появляется подменю, содержащее следующие команды: Ф- Index — выводит тематический указатель спра- вочной системы; -Ф- Contents — выводит оглавление справочной сис- темы. Назначение остальных команд меню Help (а их там более десяти) мы рассматривать не будем. Посмотрим, как работать с тематическим указа- телем Index. Пусть мы хотим познакомиться с ра- ботой процедуры Write. Экран тематического ука- зателя Index аналогичен тематическому указателю книги. С помощью клавиш управления курсором можно перемещаться по строкам на странице, а листать странички можно с помощью клавиш Раде Down (Page Up). Найдем строку Write. Клавишей —> выберем ее и откроем справочное окно, нажав на клавишу Enter. В нем будет описана процедура Write (procedure). Можно видеть, что это не просто команда вывода сообщений на экран, а процедура, возможности которой шире. В справке по той или иной процедуре или функции Турбо Паскаля обычно приводится и пример ее использования. По справочному окну тоже можно перемещаться клавишами -it, скажем, до выбора примера: Sample Code Eof.pas Выделим пример Eof.pas клавишей -> и откроем его (клавишей Enter). Текст программы выделяет- ся клавишами Shift+-> или Shift+ 1, после чего его
Что мы возьмем с собой 35 можно скопировать в окно редактора (команда Paste меню Edit или клавиши Shift+Ins). Клавиши Esc или ALt+F3 закрывают справочное окно. Получить ту же информацию можно и открыв окно Contents (содержание). Найдем интересую- щую нас тему. Write — это процедура, поэтому вы- берем тему Function and Procedures. Выделим ее кла- вишей —> и откроем. В списке начальных букв процедур и функций найдем и выделим (клавиша- ми 4- —>) строку: Function and Procedures U - Z Откроем окно, найдем и выделим имя Write, от- кроем и это окно и попадем в описание процедуры Write (procedure). Следует иметь в виду, что это описание дается на английском языке, и для того, чтобы воспользоваться им, придется иметь под ру- кой англо-русский словарь и справочник по грам- матике английского языка. 2. Нажать клавишу F1. При этом вы получите инфор- мацию, зависящую от того, что вы делаете в дан- ный момент — редактируете, отлаживаете про- грамму, выбираете параметры меню и т. д. Такую подсказку называют контекстной. 3. Поместить курсор на интересующий вас термин в окне редактирования и нажать комбинацию кла- виш Ctrl+Fl. В этом случае вы сразу получите справку по тому элементу, который вам нужен. Это самый короткий способ получения необходи- мой информации. Справочная система Турбо Паскаля содержит при- меры программ для каждой библиотечной процедуры и функции. Вы можете скопировать эти примеры из справочной системы в окно редактирования и пораз-
36 Глава 1 Собираемся в путешествие бираться с ними или добавить в качестве составных частей в свою программу. Для этого сделайте следую- щее: -4- Выведите справочный экран по интересующей вас процедуре или функции. Для этого наберите имя нужной вам процедуры или функции и, подве- дя курсор к любой его букве, нажмите клавиши CtrL+Fl. + Найдите и выделите клавишами Shift-ь—>1 весь пример или интересующую вас часть. + Скопируйте пример в буфер обмена, выбрав из ло- кального меню команду Сору. > Вернитесь в окно редактирования и вставьте при- мер в нужное вам место, нажав клавишу F10 и вы- полнив последовательно команды Edit и Paste. Учим арифметику Паскаля Вспомни, читатель, свои молодые годы. Сначала ты научился ходить. Так и мы научили нашу первую программу выполнять несложные, но очень важные действия. Затем ты научился говорить. И наша про- грамма произнесла свои первые слова, точнее, напе- чатала их на экране дисплея. Ну а потом? Потом ты приступил к изучению одной из важнейших наук — арифметики. Давайте научим нашу программу вы- полнять арифметические действия. Не зная матема- тику, нельзя стать хорошим программистом, ведь программирование это не только искусство, это одно- временно еще и точная наука. Первоначально одной из основных функций ком- пьютера была унаследованная им от арифмометра способность производить элементарные арифметиче-
Учим арифметику Паскаля 37 ские вычисления. Эти действия можно, разумеется, запрограммировать на языке Паскаль. Список ариф- метических операций приведен в табл. 1.1. Таблица 1.1. Арифметические операции языка Паскаль Операция Название + Сложение - Вычитание * Умножение / Деление div Деление нацело (отбрасывается дробная часть) mod Остаток от деления нацело (деление по модулю) Даже для вычисления самого простого выраже- ния вам придется написать программу, оформленную надлежащим образом: program two_by_two; begi n Wri te(2 * 2) ; end. На примере этой программы мы познакомились с новыми возможностями оператора Write — как из- вестно, он выводит на экран то, что содержится в круглых скобках и далее в апострофах. Ну а то, что за- писано без апострофов, сначала вычисляется, а затем выводится результат. Так, оператор Write(’2*2’) выведет на экран 2*2, aWrite(2 * 2) — число 4. Без апострофов в операторе Write записано арифметиче- ское выражение.
38 Глава 1 Собираемся в путешествие Существуют у оператора Write и другие удобны^ возможности. Так, один оператор может выводить сразу несколько чисел, предложений и т. д., перечис- ленных через запятые. Например, в результате вы- полнения строчки программы Write(’Результат равен ’ , 5 * 13, ’ см’); на экране появится фраза Результат равен 65 см. Вычисление числовых выражений на Паскал! производится по правилам математики с учетом рас! ставленных скобок и старшинства операций. Стар- шинство операций называется приоритетом. Выс- ший приоритет имеют выражения в круглых скобка^ они вычисляются первыми. Следующие в порядке старшинства операции *, /, div, mod имеют одинаков вый приоритет. Низший приоритет у операций + и -1 Операции одинакового старшинства выполняются слева направо в порядке их появления в выражении. Выражение 2+15/5 вычисляется в таком порядке сначала 15 / 5 = 3. затем 2 + 3 = 5. При записи десятичных дробей (они называются вещественными числами) используется знак «.», на! пример, 5.37 или 3.1415926. Единственная сложности возникает при записи обыкновенных дробей, ведь программы на Паскале записываются в одну строку, вертикальный знак дроби он не понимает. Выраже! ние 1 3-4 можно записать двумя одинаково правильными спо- собами так: 1 / (3 * 4)
Переменные 39 или так: 1/3/4. Итак, наша программа выглядит уже следующим образом: program two_by_two; begi n Wr1teLn(’Здравствуй,’) ; Wri teLn(’это я!’); WriteLn(‘H знаю, что 2*2=’, 2 * 2); end. Переменные В предыдущем разделе разбирались программы, вы- полняющие арифметические вычисления с заранее известными числами (константами). Такие програм- мы при многократном их выполнении всегда выдают один и тот же результат. Теперь мы должны научить нашу программу выполнять действия с величинами, заранее неизвестными, ведь никому не нужен кальку- лятор, умеющий складывать только числа 2 и 3. Для того чтобы сложить два неизвестных до начала вы- полнения программы числа, надо сначала как-то со- общить их компьютеру, он должен их запомнить, сло- жить, а затем уже результат вычислений вывести на экран. Запоминание данных в программе происходит пу- тем присваивания значений переменным. Переменных в программе может быть достаточно много, их коли- чество определяется только объемом доступной па- мяти, а этот объем у современного компьютера очень большой. Каждая переменная, кроме имени, имеет тип и значение.
40 Глава 1 Собираемся в путешествие Имена переменных подчиняются тем же правилам что и имена программ. Имена должны быть уникаль- ными, то есть не может быть двух переменных с од- ним и тем же именем. Имя переменной не может сов падать с именем программы. Напомним, что в Паска ле маленькие и большие буквы не различаются, то есть а и А — одно и то же имя. Желательно (но не обязательно), чтобы имя переменной было логически связано с назначением переменной, например sum или salary_of_my_father. Тип переменной является важнейшей характери- стикой переменной. В Паскале существует много ти- пов переменных, в этой главе мы познакомимся толь- ко с двумя: + Integer — целый; + Real — вещественный. Существуют не только числовые типы — в памяти можно хранить символы, предложения и другую ин- формацию. Значение переменной — это то, что в данный мо- мент хранится в отведенной для переменной области памяти. В процессе выполнения программы значение переменной может изменяться — на то она и пере- менная. Все используемые в программе переменные долж- ны быть перечислены в разделе описаний, который находится между словами program и begin. Там же указывается тип переменных. Это очень важно, по- скольку предохраняет программиста от ошибок, свя- занных, например, с опечатками в процессе набора текста программы. Описать переменную можно следующим образом. Сначала пишется слово var (это зарезервированное слово!). Затем идет пробел и через запятую перечне-
Переменные 41 ляются имена однотипных переменных. В конце спис- ка ставится двоеточие и пишется общий тип перемен- ны^. Завершается описание группы однотипных пе- ременных знаком «;». Таких объявлений после слова var может быть несколько (но слово var пишется только один раз). Вот пример описания переменных целого и вещественного типа: program description; var a, b, al : Integer; c: Real; d: Integer; begin end. После объявления переменных их значения еще не определены, в отведенных для них ячейках памяти находятся нулевые значения. Однако советуем в бу- дущем не полагаться на то, что неопределенным пере- менным присвоены нулевые значения (это не всегда так), и исходить из предположения о том, что в ячей- ках памяти, отведенных для переменных, сразу же может находиться «мусор» — совершенно случайные числа. С переменными можно осуществлять все те же операции, что и с числами (константами), например: а * b + с div 10 - 3.27 Оператор ввода. Теперь нам остается только на- учиться сообщать компьютеру необходимые вам для вычислений числа. Осуществляется это с помощью оператора (процедуры) ввода Read (или Read Ln), ко- торый записывается аналогично оператору вывода, за исключением того, что в круглых скобках через запя-
42 Глава 1 Собираемся в путешествие тую могут перечисляться только имена переменных, например: Read(a, b) — верно; Read(3, с) — неверно. Переменным, перечисленным в скобках, присваи- ваются введенные с клавиатуры значения. Только по- сле выполнения оператора ввода эти значения можно использовать. Учим таблицу умножения. Теперь, наконец, мы можем научить нашу программу таблице умножения. Сомножителями будут целые числа, вводимые с кла- виатуры: program multiplication; var a, b : Integer; {Объявление переменных целого типа а и Ь} begi п Readl_n(a, b) ; {Ввод с клавиатуры чисел а и Ь} Write(a * b); {Вывод результата} end. Комментарии. В фигурных скобках в программе записываются комментарии. Транслятор на них не обращает никакого внимания, а вам они могут по- мочь понять, что же делает программа. В программе multiplication комментарии, может быть, и не - нужны, но представьте себе программу в 1000 строк текста на Паскале — через 2 недели после написания в ней уже сложно будет разобраться и автору про- граммы! А что говорить о других программистах, не- даром считается, что иногда легче написать новую программу, чем разобраться в старой, плохо доку- ментированной (то есть не имеющей подробных ком- ментариев) программе. Использование комментариев
Переменные 43 считается хорошим стилем, хотя на это не всегда хва- тает времени. Текст комментария отображается в окне редактора интегрированной среды более темным цветом. Отступы. При просмотре текста программы может возникнуть вопрос — почему некоторые строки начи- наются с отступом в несколько пробелов? Это делает- ся для улучшения понимания программы. Вот при- мер. В третьей главе мы познакомимся с циклами и узнаем, что циклы можно вкладывать друг в друга. Если писать программу, выстроенную «под линейку», много времени может уйти на то, чтобы разобраться, к какому циклу относится тот или иной оператор — к внутреннему или к внешнему. Использование отсту- пов значительно упрощает эту задачу. Каждый опыт- ный программист имеет свой стиль написания про- грамм — кто-то отступает 2 пробела, кто-то 3, есть и другие мелкие детали. «Дружественный» интерфейс. Программа, напи- санная в предыдущем примере, работает не очень «вежливо» — после ее запуска на выполнение неожи- данно гаснет экран, и непосвященному человеку оста- ется только догадываться, что же делать дальше? Та- кую программу можно уподобить грубому продавцу, который в ответ на вопрос покупателя загадочно мол- чит, перекладывая товар с полки на полку. Неприят- но? Научим нашу программу «хорошим манерам». Хорошие манеры в программировании называются «дружественным интерфейсом». Дружественный ин- терфейс подразумевает постоянное (но не назойли- вое!) общение программы с пользователем, вывод Подсказок о том, какие данные следует ввести, какую клавишу нажать для выполнения того или иного дей- ствия. В этом случае с программой сможет работать
44 Глава 1 Собираемся в путешествие не только автор (как в примере с грубым продав цом — не только директор магазина): program multi plication _2; var a, b : Integer; begin Wr ite(’Введите а:’); {Перед каждым опе- ратором Read или ReadLn} ReadLn(a); {Следует поместить оператор Write, чтобы «подсказать» пользователю, какие данные ожидает от него программа} WrIte(’Введите b: ') ; ReadLn(Ь) ; WrIte(’Произведение : ’.а * Ь) ; end. Оператор присваивания При программировании более сложных действий воз никает задача вычисления чего-либо без вывода hi экран промежуточных результатов. Например, в вы ражении (а-26)2 +7 а — 2Ь сначала удобно вычислить значение а — 2 Ь, сохра- нить его, а затем, используя полученное число, найти конечный результат. В этом случае не обойтись без специального оператора присваивания, записываемо- го с помощью двух символов: переменная выражение; Работает оператор присваивания так — сначала вычисляется значение арифметического выражения путем подстановки всех входящих в него перемен-
Оператор присваивания 45 ных; результат записывается в переменную. Слева мо- жет находиться только имя переменной, но ни в коем случае не выражение. Например: а := 2 + 7; в результате получим значение а = 9 с := a - 4; с становится равным 5 с .= с + 3: значение с увеличивается на 3 и становится равным 8 с + 1 : = 2 - а; неверно, так как слева от знака присваивания стоит не переменная, а выражение! Начинающие программисты иногда путают опера- тор присваивания и математический символ равенст- ва, поскольку их обозначения похожи друг на друга. Это разные вещи! Математик нас не поймет, если мы напишем с = с + 3, поскольку эта запись равносильна неправильному тождеству 0 = 3. Двойка в дневнике за такое выражение гарантирована! Однако програм- мист сочтет строку вида с : = с + 3 нормальной, так как, с его точки зрения, это не отношение равенства, а последовательность действий, состоящая из вычис- ления выражения в правой части оператора присваи- вания и записи полученного результата в соответст- вующую ячейку памяти вместо старого значения переменной с. В данном примере если до выполне- ния оператора с : - с + 3 переменная с имела значе- ние 5, то после его выполнения она будет иметь зна- чение 8. В следующей программе оператор присваивания используется для вычисления выражения (а-2Л)2 +7 а - 2Ь
46 Глава 1 Собираемся в путешествие где а и b — такие вещественные числа, что а — 2 b О (иначе возникнет неприятная ситуация с делением на ноль). При решении этой задачи мы отдельно посчи- таем значение а — 2 b и запишем результат в дополни- тельную переменную с: program expression; var — a, b. с : Real; begin Write(’Введите а и b’); ReadLn(a, b); c := a - 2 * b; Write((c * c + 7) / c) ; end . Форматированный вывод. Если с выводом целых значений особых проблем не возникает, то вывод ве- щественных чисел — несколько более сложная зада- ча. Вывод вещественного числа в Паскале осуществ- ляется по умолчанию в показательной форме ±аЕп, где 1 < а < 10 называется мантиссой, п — целое число со знаком, которое называется порядком. Само значе- ние десятичной дроби при этом следует вычислять по формуле й-10”. Для вывода вещественного числа в привычной форме можно воспользоваться так назы- ваемым форматированным выводом. При использова- нии форматированного вывода в операторе вывода после имени переменной, арифметического выраже- ния или константы через двоеточие указывается ко- личество позиций, отводимых для вывода данного значения (что, кстати, можно сделать и для целых значений). Затем еще через одно двоеточие указыва- ется, сколько десятичных цифр следует сохранить справа от десятичной точки. Если дробная часть не помещается в отведенные для нее разряды, она округ-
Оператор присваивания 47 дяется, целая часть в любом случае выводится полно- стью. Далее приведены примеры работы процедуры Vjrite с использованием форматированного вывода (знаком □ обозначается символ пробела). "им te (2.01: 8:3) □□□2.010 Write(56:4) □□56 Write(3.1415926:4:2) 3.14 Wri te(3.14:6:6) 3.14CDDC Перестановка переменных. Хорошо иллюстриру- ет работу с переменными следующая задача — поме- нять местами значения двух переменных а и b (то есть чтобы в а оказалось то, что было раньше в Ь, и наоборот). Казалось бы, можно записать так: а : = Ь; b : = а ; однако здесь кроется ошибка. Давайте проверим это на конкретном примере. Пусть а было равно 10, а зна- чение b равно 7. После выполнения оператора а : = b в b останется 7; а станет равным 7, и теперь, если вы- полнить оператор присваивания b := а, там также окажется 7. Мы потеряли одно из значений! Как же быть? Выход заключается в использовании дополни- тельной переменной t, в которой мы сохраним значе- ние переменной а до того, как его придется заменить новым: t : = а ; а := Ь; b : = f, Полезно проверить выполнение данного фрагмен- та программы на конкретном примере:
48 Глава 1 Собираемся в путешествие а b t значение до 10 7 - после t := а 10 7 10 после а := b 7 7 10 после b := t 7 10 10 Составление таких таблиц часто помогает прове- рить правильность составления какой-нибудь части программы. Как видим, в исправленном варианте а и b действительно поменялись значениями. Обратная запись числа. Попробуем решить слож- ную задачу. Дано трехзначное число х = abc (а, Ь, с — его цифры). Требуется получить число, записанное теми же цифрами, но в обратном порядке. То есть, если дано число 128, то получить надо 821. Перед нами встает задача нахождения цифр числа — а, b и с. Выстроить эти цифры в обратном порядке легко — результат будет равен х = 100 с + 10 & + «. Проще все- го найти цифру единиц с — она будет равна остатку от деления числа х на 10 (например, если х = 128, то х mod 10 будет 8, что нам и нужно). Итак, с := х mod 10; Чтобы найти Ь, поступим следующим образом: сначала найдем ab, равное х, деленному нацело на 10 (для 128 это будет 12), а затем уже определим у — цифру единиц получившегося числа: Ь := х div 10 mod 10; Найти а просто — это результат деления х нацело на 100: а := х div 100; Теперь можно написать всю программу:
Преобразование типов 49 program naoborot; var x, a, b, с : Integer: begi n Wri te(’x-=>’); ReadLn (x) ; c := x mod 10; b := x div 10 mod 10; a := x div 100; Write(100 * c + 10 * b + a); end. Преобразование типов В Паскале оператор присваивания не всегда работает, даже если он написан синтаксически верно. Когда мы производим вычисления с известными числами, ре- зультат известен и проблем не возникает. С перемен- ными же все проходит не всегда так гладко, поскольку переменные типов Integer и Real хранятся по-раз- ному (занимают разный объем памяти). Компьютер устроен так, что выполнять действия он может толь- ко с одинаковыми объектами. Возьмем следующую программу: program wrong; var а : Integer; b : Real; begi n b := 2; a := 2.9; {Здесь содержится ошибка!} end. Если набрать эту программу и попробовать ее от- транслировать, получим сообщение об ошибке Туре
50 Глава 1 Собираемся в путешествие mismatch. Курсор при этом укажет на ту строку, у ко- торой в комментарии сказано, что здесь содержится ошибка. В переводе на русский язык сообщение озна- чает Нарушение соответствия типов. Давайте разбе- ремся в причинах появления данного сообщения. В пер- вом операторе переменной вещественного типа при- сваивается целое число 2. Ошибки здесь нет, по- скольку произойдет так называемое преобразование типа, при котором число 2 будет представлено в виде десятичной дроби 2.0 и записано в Ь. А наоборот нельзя — в целую переменную а дробное число 2.9 не запишется, и транслятор выдаст ошибку. Такие си- туации возникают очень часто, и сложнее всего быва- ет разобраться со слу чаем, когда используется опера- ция деления «/» (например, что будет с результатом f / 2, где f — целое число?). Существует несколько правил, два из которых уже были описаны ранее (в переменную вещественного типа можно записать целое число, а наоборот — нельзя). Остальные прави- ла, касающиеся выполнения арифметических опера- ций, предстгзлены в табл. 1.2. Таблица 1.2. Правила выполнения арифметических операций языка Паскаль Операция Тип операнда первый второй Тип ре- зультата Пример Резуль- тат +, *, - Integer Integer Integer 2 + 27 29 +, *,,г Integer Real Real 2 - 3.5 -1.5 +, *, - Real Integer Real 3.0 - 27 -24.0 +, *, - Real Real Real 2.5 * 2.5 6.25 / любой любой Real 8/2 4.0
Преобразование типов 51 Что делать, если надо получить в результате целое число, пусть даже округленное? В этом случае можно воспользоваться одной из двух встроенных функций Паскаля: Тгипс(а) или Round (а). Встроенные функ- ции пока можно считать обычными операторами язы- ка Паскаль. Здесь Trunc отбрасывает дробную часть аргумента, a Round округляет его по правилам мате- матики до ближайшего целого. Примеры: Тгипс(2.73) = 2; Trunc(-3.4) = -3; Trunc(1.9) = 1; Round(2.5) = 3; Round(-3.7) =• -4.0. Оптимизация программы. И еще одно замечание. Основной результат работы программиста — пра- вильно работающая программа. Это самое главное. Однако иногда имеет значение и ско рость выполне- ния программы, в том числе скорость проведения ма- тематических вычислений. В этом случае надо писать программы так, чтобы желаемый результат достигал- ся за возможно меньшее число шагов (такая програм- ма называется оптимизированной). Рассмотрим еще один пример. Пусть требуется вычислить значение выражения ? + 7х4 - 18p + 9^-x + 8. Программа для вычисления этого выражения пи- шется очень просто — в две строчки. В первой вводим значение х, а во второй выводим результат: program polynomial: var х : Real; begi n Write(’Введите x:’);
52 Глава 1 Собираемся в путешествие ReadLn (х) ; Write(x *х*х*х*х + 7*х*х*х* х-18*х*х*х+9*х*х-х+8); end. В этой программе для вычисления значения мно- гочлена используется 16 арифметических операций, причем большую часть из них составляют медленные операции умножения. Запишем выражение по-другому: ? + 7? - 18 л3 + 9? - х + 8 = х {х (х (х (х + 7) - 18) + 9) - 1) + 8. После преобразования количество операций умень- шилось до 9, то есть тот же результат мы получим бы- стрее: program polynomial_fast; var x : Real; begi n Wr1te(’Введите x:’); ReadLn(x); Write(x * (x * (x * (x * (x + 7) - 18) + 9) - 1) + 8) ; end. Этот метод вычисления значения многочлена на- зывается схемой Горнера. Встроенные функции. Кроме упомянутых функ- ций Round и Trunc в Паскале существует целый ряд других встроенных функций. Далее приводятся наи- более распространенные из них. Таблица 1.3. Встроенные функции Функция Тип аргумента х Результат Sqr(x) Real или Integer квадрат х
Итог 53 функция Тип аргумента x Результат Sqrt(x) Real или Integer корень квадратный из х Abs(x) Real или Integer модуль X Sin(x) Real или Integer синус X Cos(x) Real или Integer косинус X Pred(x) Integer следующее целое после х Succ(x) Integer предыдущее целое до х Следует особо отметить, что каждая функция воз- вращает значение определенного типа: 4 Sin(x), Cos(x), Sqrt(x) — вещественного; 4 Pred(x), Succ(x) —целого; 4 Sqr(x), Abs(x) — того же типа, что и аргумент. Тип значения, вычисленного в результате обраще- ния к функции, или, как говорят, тип функции дол- жен совпадать с типом переменной, которой присваи- вается значение функции. Итог Ну вот, читатель, мы подготовились к путешествию. Наши программы научились не только выполнять простые действия, они «заговорили», научились нас внимательно слушать, выучили математику. Мы по- лучили первый урок языка, которым владеем «со сло- варем», но и это уже неплохо. В нашем распоряжении такой мощный инструмент, как интегрированная сре- да Турбо Паскаля. Прежде чем двигаться в путь, оста- новись и подумай над приведенными здесь задачами (решение некоторых из них подразумевает написание соответствующей программы).
54 Глава 1 Собираемся в путешествие Задача 1. Вывести на экран слово «мир». Буквы это- го слова должны состоять из символов «*», высота каждой буквы — 10 символов. Задача 2. Вывести в центре экрана квадрат со сторо- ной 5 символов, используя символ «#». Задача 3. Записать на Паскале выражения: 2 3-4-5’ 2 - 37 • 5,34 2 7-2 3 — 5 Задача 4. Вывести на экран кубы целых чисел от 2 до 10 в виде куб 2 равен 8 куб 3 равен 27 Задача 5. Вычислить произведение трех веществен- ных чисел. Задача 6. Вычислить значение выражения а2 — 2аЬ — 3 2 ’ где а и b — вещественные числа. Задача 7. Поменять местами значения переменных а и Ь, не используя дополнительную переменную. Задача 8. Дано четырехзначное число х = abed. Полу- чить число, записанное теми же цифрами в обратном порядке (г/ = deba). Задача 9. Дано трехзначное число х = abc. Найти сумму квадратов его цифр.
Итог 55 Задача 10. Определить тип результата в выражениях: 2 + 7 * 3 / 1; 2 - 7 * 2.1; (3 - 3) / 1. Задача 11. Вычислить значение выражений: Trunc(-2 / 7); 2 - Round(3 + 0.48); 3 * Trunc(l - 2.8). Задача 12. Вычислить значение выражения: х4-2л5 + 4х4~2л3 + хг. Задача 13. По номеру квартиры определить номер подъезда и этаж. Квартира находится в пятиэтажном доме, на каждом этаже по четыре квартиры.
ГЛАВА 2 В поисках истины и... лжи. Операторы
Первая остановка в нашем путешествии — Англия, Лондон, Бейкер-стрит, дом знаменитого сыщика Шер- лока Холмса. Войдем с разрешения хозяина, прися- дем у камина и послушаем его диалог с доктором Ватсоном в рассказе Артура Конан-Дойля «Пестрая лента»: «...Так что же вы обо всем этом думаете, Ватсон? — спросил Шерлок Холмс, откидываясь на спинку кресла. — По-моему, это в высшей степени темное и гряз- ное дело. — Достаточно грязное и достаточно темное. — Но если наша гостья права, утверждая, что пол и стены в комнате крепки, так что через двери, окна и каминную трубу невозможно туда проникнуть, зна- чит, ее сестра в минуту своей таинственной смерти была совершенно одна... — В таком случае, что означают эти ночные свисты и странные слова умирающей? — Представить себе не могу. — Если сопоставить факты: ночные свисты, цыгане, с которыми у этого старого доктора такие близкие от- ношения, намеки умирающей на какую-то ленту и, наконец, тот факт, что мисс Элен Стоунер слышала металлический лязг, который мог издавать железный засов от ставни... Если вспомнить к тому же, что док- тор заинтересован в предотвращении замужества сво- ей падчерицы, — я полагаю, что мы напали на верные следы, которые помогут нам разгадать это таинствен- ное происшествие. — Но тогда при чем здесь цыгане? — Понятия не имею...»
58 Глава 2 В поисках истины и... лжи. Операторы Обратите внимание на выделенное курсивом сло- во если, которое несколько раз использует Шерлок Холмс в своих рассуждениях о совершённом преступ- лении. Употребление этого слова имеет глубокий смысл — в ситуации неопределенности оно позволяет направить ход рассуждений по одному из нескольких возможных путей. Слово если или пара если... то яв- ляются неизменными спутниками логических рассу- ждений. Быть может, искусство употребления этих слов и составляет секрет гениальности Шерлока Хол- мса? В программировании при создании «умных» и умеющих рассуждать программ тоже невозможно обойтись без если... то. Эта конструкция в Паскале (да и во многих других языках программирования) записывается по-английски if... then... и называется условным оператором. Почему условным? Просто по- тому, что после i f следует условие, которое может выполняться, а может и не выполняться — заранее это неизвестно. Условный оператор Процедуры ввода и вывода, а также оператор при- сваивания позволяют писать только линейные про- граммы, в которых все команды выполняются после- довательно, одна за другой. Но очень часто возникает необходимость выполнять различные команды в за- висимости от выполнения какого-то условия (напри- мер, надо найти наибольшее из двух чисел и вывести это значение на экран). Для таких целей в Паскале введен условный оператор: if условие then оператор_1 else оператор_2;
Условный оператор 5 9 Работает этот оператор так — сначала проверяет- ся условие, и, если оно верно, выполняется опе- ратор_1, в противном случае выполняется опе- ратор_2. В простейших случаях условие — это математическое сравнение двух выра- жений по величине {отношение двух величин). Отно- шение можно записать при помощи следующих зна- ков: Знак Отношение = равно < меньше <= меньше или равно > больше >= больше или равно <> не равно Примеры: 2 >= 5 а + 2 * b < 0 а <> с В качестве оператора_1 и оператора_2 может вы- ступать любой из известных вам операторов, в том числе и еще один условный оператор (таких вложен- ных условных операторов может быть сколько угод- но). Больше, меньше... В качестве примера использо- вания условного оператора рассмотрим задачу о на-
60 Глава 2 В поисках истины и... лжи. Операторы хождении наибольшего из двух чисел. Программа должна выполнить следующие действия: + ввести с клавиатуры два числа (пусть это будут целые числа); + сравнить два значения; 4- вывести на экран наибольшее из них. Текст программы: program greatest_num; var firstjiumber, second_number : Integer; begi n Write(’ Введите два целых числа а и Ь:’); ReadLn(а, Ь); if firstnumber >= secondnumber then Write(’большее из а, ’и b. a) else Write(’большее из а, ’и b, , b); end. Эта программа умеет «рассуждать», сравнивая два числа и выбирая наибольшее из них. Это оказывается возможным благодаря использованию условного опе- ратора. У нашей программы есть небольшой недостаток — в ней не предусмотрен случай, когда оба значения равны. В данной ситуации она сообщит, что наиболь- шим значением будет первое, что с точки зрения ма- тематики не совсем точно. Следующая программа умеет дать правильный ответ и в случае равных зна- чений. Пусть для разнообразия она будет определять наименьшее значение. Результат ее выполнения — текстовое сообщение о том, какое значение является наименьшим (или о том, что оба значения равны):
Условный оператор 61 program two_numbers; var first_number, secondjiumber : Real; begi n WriteLn(’Введите первое число'); ReadLn(fi rstnumber); Wr iteLn('Введите второе число'); ReadLn(secondjiumber); if firstjiumber < secondnumber then WriteLn('Наименьшим является первое число') else if first_number = secondnumber then WriteLn('Введенные значения равны') else WriteLn('Наименьшим является второе число'); end. В программе two numbers используются вложен- ные условные операторы (оператор_2 — это услов- ный оператор). Наиболее часто возникающие ошибки при выпол- нении условного оператора таковы: 4- иногда после оператора 1 ставится знак «;», чего делать не следует. Данный символ завершает опе- ратор, и все, что следует после него до очередного символа «;», считается следующим оператором, а это неверно! 4- иногда предпринимаются попытки сделать сразу два сравнения, например 0 < а < 1, что также оши- бочно (о том, что делать, если действительно нуж- ны два сравнения, будет рассказано чуть позже). Рассмотренная нами форма условного оператора называется полной, она позволяет направить выпол-
62 Глава 2 В поисках истины и... лжи. Операторы нение программы (алгоритма) по одному из двух пу- тей. Такую конструкцию называют ветвлением (по аналогии с развилкой дерева). В Паскале есть и краткая форма условного оператора, которая просто позволяет выполнять заданный оператор, если вы- полнено условие: if условие then оператор; Если условие верно, то выполняется оператор, иначе выполняться будет следующий за условным оператор. Вот еще несколько примеров условных операторов: if а >= 5 then Wr ite(* больше или равно 5’) else а := 5; {Полная форма} if a<2*b+3 then Read(a, b, с, d); {Краткая форма} if а < 5 then if а > 0 then {Вложенные условные операторы} if b = 7 then Wri te(’Да’) else Write(’Нет’); В последнем примере ветвь else относится только к последнему i f, то есть она выполнится в случае, ко- гда а<5, а > О и b <> 7. Почему не к первому или ко второму i f, спросите вы? Таково правило — каждое else относится к ближайшему слева if, не имеюще- му el.se. Абсолютная величина. Разберем еще один при- мер. Пусть требуется найти абсолютную величину (модуль) числа [rj. Напомним определение — модуль числа х равен самому этому числу х, если оно поло- жительно, и —х, если число отрицательно. Здесь, оче- видно, имеется условие, в зависимости от выполне-
Условный оператор 63 ния или невыполнения которого следует выполнять различные действия. Без условного оператора в пол- ной форме не обойтись: program absolute; var х : Integer; begi n Write(’Введите целое число:’); ReadLn (x); if x >= 0 then Wri te(’|. x. ’ | = ’ , x) else Write(’|’, x, ’| = ’, -x) ; end. Знакомьтесь — палиндром. Вот еще одна, более сложная задача. Требуется определить, является ли заданное трехзначное число палиндромом (палиндром читается одинаково слева направо и справа налево, например, палиндромами являются числа 121, 282, слова «шалаш», «наган»). Для решения этой задачи можно использовать простое условие — первая цифра числа должна равняться последней: program palindrom; var х : Integer; begi n Write(’Введите целое число:’); ReadLn(x); {Ввод числа х} if х mod 10 = х div 100 then Write(’Введенное число является палиндромом’) else Write(’Введенное число не является палиндромом’); end.
64 Глава 2 В поисках истины и... лжи. Операторы Делим поровну. Следующая наша программа долж- на проверять, делится ли введенное с клавиатуры це- лое число нацело на 4. Числа а и b делятся нацело, если остаток от деления а на Ь равен 0. Вспомним, что в Паскале есть специальная операция вычисле- ния остатка от деления одного числа на другое. Это операция mod: program divide; var m : Integer; begi n Write(’Введите целое число’); ReadLn(m); if m mod 4=0 then Write('Введенное число делится на 4’) else Write(’Введенное число не делится на 4’); end. Линейное уравнение. Решение линейного уравне- ния а х + b = 0 зависит от значений а и Ь. Если а не равно 0, то х = -Ь / а, если же а = 0, то необходимо проверить значение Ъ. При b = 0 решением является любое число, а при b ф 0 уравнение не имеет решения. Здесь имеется несколько условий, и в программе ре- шения линейного уравнения используется условный оператор: program linearequation; var а, b : Real; begin Write(’Введите коэффициенты а и b: ); RreadLn(a, b); if a <> 0.0 then Write(*x=’, -b / a)
Условный оператор 65 else if b = 0.0 then Write(’Корень - любое число’) else Write(’Корней нет’); end. Високосный год. А вот задача, достойная самого Шерлока Холмса! Как определить, является ли ука- занный год високосным? Напомним, что год считает- ся високосным, если его номер делится нацело на 4, кроме тех случаев, когда он делится на 100. Если но- мер года делится на 400, то он все равно високосный. Так, например, високосные годы — 24, 1952, 1600, 2000, а не високосными являются 153, 1800 и 1900 годы. Для решения задачи выберем следующий алго- ритм. Сначала проверим, кратен ли номер года 400, если да, то год високосный, если нет, проверим номер года на кратность 100. Если окажется, что он кратен 100, то год не високосный (суеверный человек может вздохнуть с облегчением!), если нет, проверяем на кратность 4. Если номер года делится на четыре без остатка, то он високосный, иначе — нет. А вот про- грамма, умеющая выполнять необходимые проверки: program February_29; var а : Integer; begi n Write(’Введите год:’); ReadLn(a); if a mod 400 = 0 then Write(a, ’- високосный год’) else if a mod 100 = 0 then Write(a, не високосный год’) else 3 Зак. № 933
66 Глава 2 В поисках истины и... лжи. Операторы if a mod 4=0 then Write(a, високосный год’) el se Write(а, не високосный год’); end. Итак, мы убедились, что важнейшей частью услов ного оператора является условие. Математик сказал бы, что условие — это некое утверждение, имеющее вид отношения между двумя арифметическими выра- жениями, которое может быть либо истинно, либо ложно. Программистам часто приходится иметь дело с величинами, принимающими одно из двух возмож ных значений — «истина» или «ложь». Такие величи- ны (переменные, константы и т. д.) называются логи- ческими. Для них существуют специальные операции, с помощью которых можно записывать логические выражения, подобные арифметическим выражени- ям. В Паскале для логических величин есть особый тип — Boolean («булев», в честь английского матема- тика XIX века Джорджа Буля, который внес большой вклад в математическую логику). Есть всего две логи- ческие константы: True («истина») и False («ложь»). Логические операции Для чего нужны логические операции? При решении различных задач иногда возникает необходимость проверять выполнение нескольких условий сразу. Например, в задаче, определяющей, является ли чис- ло п двузначным, нужно проверить сразу два усло- вия — п должно быть больше 9 и меньше 100. Сделать это можно, записав два условных оператора: if п > 9 then if n < 100 then Write(’двузначное’)
Логические операции 67 else Write(’He двузначное’) else Write(’не двузначное’); Но в этом случае приходится два раза записывать строку Wri te (’ не двузначное ’), и, кроме того, такое решение выглядит немного запутанным. Для удобст- ва записи таких и подобных им условий следует ис- пользовать логические операции: + and — логическое «и» («логическое умножение»); + or — логическое «или» («логическое сложение»); > хог — логическое «исключающее или»; 4- not — логическое отрицание. С помощью логических операций простые условия можно объединять в составные. Простые условия при этом обязательно заключаются в скобки, так как ло- гические операции имеют более высокий приоритет, чем операции сравнения: (а < 3) and (b + а >= 7) (а = 7) or (2 < b) not(a = 7) or (2 < b) and (a = b) Правила выполнения логических операций. Слож- ное условие, составленное из двух простых, соеди- ненных операцией and, верно только тогда, когда вер- ны оба простых условия. Значению «истина» можно условно сопоставить числовое значение 1, а значению «ложь» — нулевое (ведь только истина что-то значит в нашей жизни!). В этом случае результат примене- ния операции and можно получить, если перемно- жить оба логических значения в их числовом выра- жении. Только если оба они истинны (равны единице), -в результате получим истину (единицу). Поэтому операцию and и называют еще логическим ум- ножением.
68 Глава 2 В поисках истины и... лжи. Операторы Для операции or сложное условие истинно, ес верно хотя бы одно из простых условий. Здесь так^ можно свести выполнение логической операции сложению числовых эквивалентов логических значс ний, только при этом результатом будет остаток с? деления полученной суммы на два. Операцию о г п< этому называют логическим сложением. Результат операции логическое исключающее или-. условие! хог условие_2 «истина» тогда, когда верно только одно из уело вий — условие_1 или условие_2. Операция not — это операция отрицания. Отрица- ние истины ложно, а отрицание лжи — истина, поэто му not (условие) истинно тогда, когда условие ложно. Приоритет (последовательность) выполнения ло- гических операций таков — сначала выполняется от- рицание (not), затем and, а потом наравне or и хог. Если сложное условие содержит несколько одина- ковых логических операций, то выполняются они по порядку слева направо, как и арифметические опера- ции. Для изменения порядка вычисления условий мож- но использовать дополнительные скобки. Вот приме- ры выполнения логических операций: (2 >= 3) and (3 = 3) — ложно; (2 < 7) and (3 = 3) and (2 < 0) — ложно; (3 > 4) and not(2 < 7) or (0 = 0) — истинно; । (2 = 0) хог (2 <> 2) — ложно. Сколько цифр в числе? Посмотрите теперь, как выглядит программа, определяющая, является ли вве- денное число двузначным, если в ней используются логические операции: -program twodigits;
Логические операции 69 var П : Integer; begin Write(’Введите целое число:’); ReadLn(n) ; if (n > 9) and (n < 1©0) then Write(’Введенное число двузначное’) else Write(’Введенное число не двузначное’); end. Снова високосный год. Программа, которая опре- деляет, является ли указанный год високосным, тоже может быть переписана с использованием логических операций. В первом варианте этой программы мы два раза записывали операторы Write(’- високосный год ’) и Wr 1 te ( ’ - не високосный г од’). Избавиться от лишнего оператора вывода можно, используя услов- ный оператор со сложным условием; program February_29_2; var а : Integer; begi n Write(’Введите год;’); ReadLn(a) ; if (a mod 400 = 0) or ((a mod 100 <> 0) and (a mod 4 = 0)) then Write(a, ’- високосный год’) else Write(a, ’- не високосный год’); end. Времена года. Следующая программа, в которой также используются сложные условия и логические операции, позволяет по номеру месяца определить время года;
70 Глава 2 В поисках истины и... лжи. Операторы program seasons; var а : Integer; begi n Write(’Введите номер ReadLn(a); месяца ’); if (a > 2) and Wri te(’весна’) (a < 6) then if (a > 5) and Wri te(’лето’); (a < 9) then if (a > 8) and Wri te(’осень’) (a < 12) then if (a < 3) or Wri te(’зима’); end. (a = 12) then Вариации на шахматную тему. На очереди — игр в шахматы. Шахматы — это одна из сложнейших л< гических игр, и составить программу, которая мог; бы играть даже на уровне начинающего шахмат! ста, нам пока не под силу, поэтому мы сейчас решиг задачу попроще. Как определить цвет указанно клетки шахматной доски? Клетка задается своим координатами х (номер вертикали) и у (номер гори зонтали). Будем считать, что клетка (1, 1) — черна (см. рис. 2.1). Эта задача решается просто, если заметить, чт сумма номеров строки и столбца четна для черны' клеток и нечетна для белых: program black_or_white; var x. у : Integer; begi n Write(’Введите номер вертикали и номер горизонтали:’);
Логические операции 71 ReadLn (х , у); if (х + у) mod 2=0 then Write(’Клетка черная’) else Write(’ Клетка белая’); end. Рис. 2.1. Шахматная доска Вот еще одна простая шахматная задача. Требует- ся определить, может ли шахматная ладья за один ход попасть из клетки с координатами (xj, г/i) в клет- ку с координатами (хг, г/г)- Напомним, что ладья хо- дит только в горизонтальном или вертикальном на- правлениях. Для того чтобы выбрать правильное направление хода, заметим, что у всех клеток гори- зонтали совпадают номера строк, а у клеток вертика- ли — столбцов. program chess; var
72 Глава 2 В поисках истины и... лжи. Операторы xl, х2, yl, у2 : Integer; begi n Write(’Введите координаты первой клетки х 1 и у 1: ’) ; ReadLn(xl, у1); Write(’Введите координаты второй клетки х2 и у2 : ’ ) ; ReadLn(х2, у2); if (xl = х 2 ) or (yl = у2) then Write(’Ладья может за один ход попасть в указанную клетку’) else Write(’Ладья не может за один ход попасть в указанную клетку’); end. Добро пожаловать в тир! Что еще требуется хоро- шему сыщику, кроме умения рассуждать и играть в шахматы (последнее, впрочем, не так важно)? Судя по детективам, хороший сыщик должен метко стре- лять. Итак, выбираем мишень, делаем выстрел и... Надо посмотреть, попали ли мы в «яблочко». А для этого придется решить еще одну математическую за- дачу. Она состоит в том, чтобы определить, принадле- жит ли точка с координатами (х, у) заданному мно- жеству точек на координатной плоскости, включая его границу (см. рис. 2.2 — заштрихованная область). Координаты левой верхней и правой нижней вер- шин внешнего прямоугольника равны соответствен- но (xl,yl) и (х2,у2), а внутреннего — (хЗ,уЗ) и (х4,у4). Принадлежность точки внешнему прямоугольни- ку проверяется следующими условиями: xl < х < х2 yl < у < у2
Логические операции 73 (х2. у2) (xLyl) Рис. 2.2. Множество точек на плоскости Точка не должна принадлежать внутреннему пря- моугольнику, поэтому вторая пара условий должна быть отрицанием условий: хЗ < х < х4 уЗ < у < у4 Итак, текст программы: program v_moloko; var х. у, xl, х2, хЗ, х4, yl, у2, уЗ, у4 : Real; begi п xl : = 1.1; yl : = 1-2; х2 : = 3.9; у2 : = 5.2; хЗ : = 2.2; уЗ : = 1.3; х4 : = 3.5; у 4 : = 4.9; Write(’Введите х, у:’); ReadLn(х, у);
74 Глава 2 В поисках истины и... лжи. Операторы if (х >= xl) and (х <= х2) and (у >= yl) and (у <= у2) and not ((х >= хЗ) and (х <= х4) and (у >= уЗ) and (у <= у4)) then Write(’Попадание в мишень!!!’) else Wri te ( ’ Мимо...’) ; end. Логический тип переменных Мы уже знаем, что в Паскале существует логически! тип переменных Boolean. Переменные логического типа могут принимать только два значения: true («истина») и false («ложь»). Таким переменные можно присваивать значения, выводить их на экран применять к ним логические операции, нельзя только вводить их значения с клавиатуры. Например: program boolboolean; var a, b : Boolean: begi n a := true; b := false; a := a and b or not true; Wri te(a , b); end . В результате работы программы будут выведены на экран два слова false и false. Почему? Разбери- тесь самостоятельно. Логический тип возник не случайно. Дело в том, что логические операции в качестве результата выда- ют значение true либо false. Результат логической
Составной оператор 7 5 операции можно присваивать переменным логиче- ского типа, например: а := true and (2 <= 3) or (0.5 <> 7); Выполнив такое присваивание, в условном опера- торе вместо условия можно использовать перемен- ную логического типа. Например: if a then b := b + 1; Тип Boolean обычно используется для удобства записи условий, чтобы, например, избежать много- кратного написания одного и того же условия или со- кратить запись сложного условия. Программируем формулу. В следующей програм- ме используется переменная логического типа. Зада- ча состоит в том, чтобы ввести с клавиатуры целое число а и, если оно находится между 10 и 100, вычис- лить значение выражения а2 — 2 а + 7. program expression; var а : Integer; b : Boolean; begi n Write(’Введите целое число:’); ReadLn (a) ; b := (a >= 10) and (a <= 100); if b then Write(a * a - 2 * a + 7); end. Составной оператор Часто бывает так, что в случае выполнения некоторо- го условия надо осуществить несколько действий, а не одно, как это предусмотрено в условном операто- ре. Из этого положения можно выйти, написав не-
76 Глава 2 В поисках истины и... лжи. Операторы сколько условных операторов, проверяющих одно и то же условие. Но если действий много, программа получится громоздкой и сложной, а в мире програм- мирования, как и в мире литературы, ценится способ- ность кратко и точно выражать свои мысли. Есть, к счастью, другой, более простой выход, и заключается он в использовании составного оператора. Выглядит составной оператор следующим образом: begi п <операторы> end; Зарезервированные слова begin и end называются операторными скобками. Между ними может быть сколько угодно других операторов, но считается все это одним составным оператором. В условном опера- торе да и в других ситуациях, с которыми мы встре- тимся позже, там, где предусмотрено использование одного-единственного оператора, можно использо- вать составной оператор. Найдем цифры. Приведем пример. Требуется вве- сти целое число п и, если оно трехзначное, найти его цифры, в противном случае напечатать сообщение «Не могу найти цифры». Попробуйте самостоятельно разобраться в работе программы f i nd di gi ts: program find_digits; var n, a, b, c : Integer; begin Write(’Введите целое число:’); ReadLn(n); if (n > 99) and (n < 1000) then begin a := n div 100; (, b := n div 10 mod 10;
Оператор выбора 77 с : = n mod 10; Write(a, b, с) end else Write (’He могу найти цифры!’); end. Сумма или квадрат. Другой пример. Программа должна вычислить сумму двух вещественных чисел х и у, если х — положительное, иначе вычислить х2. В этой задаче, если х неположительно, у уже вводить не надо: program sum_or_square; var х, у : Real; begin Write(’Введите вещественное число x:’); ReadLn(x); if x > 0 then begin Write(’Введите вещественное число у:’); ReadLn(у); Write(x + у) end else Wri te(x * x) ; end. Оператор выбора В Паскале есть еще один оператор, который позво- ляет программировать сложные ветвления, когда имеется несколько (больше двух) вариантов дейст- вия. Этот оператор называется оператором выбора (case) и имеет следующий вид:
78 Глава 2 В поисках истины и... лжи. Операторы case выражение of список_значений_1 : оператор_1; список—значений_2 : оператор_2; список—значений—п : оператор_п; else оператор; end; Здесь между зарезервированными словами case и of находится выражение, принимающее значение, ко- торое может присутствовать в одном из списков значений, находящихся слева от двоеточий. Каждый оператор, идущий за двоеточием, отделяется от сле- дующего списка значений точкой с запятой. Ветвь else, отвечающая всем неперечисленным значениям выражения, необязательна. При выполнении данного оператора вначале вычисляется значение выражения. Затем выбирается тот список значений, в котором на- ходится полученное значение, и выполняется соот- ветствующий ему оператор. В списках значений оператора case допустимыми являются целые и некоторые другие (но не вещест- венные) типы. Любое заданное значение выражения может входить в список значений неоднократно, но выполняться будет лишь первая подходящая ветвь. Если значение выражения отсутствует в списках зна- чений, ни один из вариантов выполняться не будет. В этом случае выполняется ветвь else оператора case или, если эта ветвь отсутствует, следующий за case оператор. Итог Итак, мы познакомились с использованием в про граммах на Паскале условного оператора и оператора
Итог 79 выбора, а также с логическими переменными и логи- ческими выражениями. Все это часто используется в программах, которые могут выполняться по-разному, в зависимости от введенных данных. Ну а мы... Мы не будем злоупотреблять терпением великого Холмса, тем более что к нему стучится уже некий полный огненно-рыжий господин из рассказа «Союз рыжих». А вам, уважаемый читатель, полезно будет подумать над решением следующих задач (ре- шение здесь подразумевает написание и выполнение соответствующей программы). Задача 1. Проверить, является ли число а четным. Задача 2. Найти наибольшее из трех чисел. Задача 3. Для целого числа п найти сумму его цифр. Найти сумму квадратов его цифр в случае, если п че- тырехзначное. Задача 4. Определить, является ли число п одновре- менно положительным и кратным 3. Задача 5. Определить, может ли шахматный конь за один ход попасть из клетки с координатами (xj, z/i) в клетку с координатами (хг, г/г)- Задача 6. Определить, принадлежит ли точка с коор- динатами (х, у) заданному множеству точек на коорди- натной плоскости (множества изображены на рис. 2.3, множества включают границу). OD0 1 2 3 Рис. 2.3. Множества точек к задаче 6
80 Глава 2 В поисках истины и... лжи. Операторы Задача 7. Пусть а и b — переменные типа Boolean Найти значения а и b в результате выполнения еле дующего фрагмента программы: а := ( 2<> 3) or true; b := a and а хог (true and (2 >= 7)); а := b or false and (2 < 7); Задача 8. У наибольшего из чисел т и п найти цифру младшего разряда (единиц) и остаток от его деления на 3. Задача 9. Проверить, равно ли целое число а сумме кубов своих цифр (<з < 100 000).
ГЛАВА 3 Помогаем древнегреческому герою. Циклы
Перенесемся из Англии конца XIX века на две тыся- чи лет назад — в Древнюю Грецию. Древняя Гре- ция — страна великих ученых, поэтов и легендарных героев. Познакомимся с историей одного из них. «...Сизиф, сын бога повелителя всех ветров Эола, был основателем города Коринфа, который в древ- нейшие времена назывался Эфирой. Никто во всей Греции не мог равняться по ковар- ству, хитрости и изворотливости ума с Сизифом. Си- зиф благодаря своей хитрости собрал неисчислимые богатства у себя в Коринфе; далеко распространилась слава о его сокровищах. Когда пришел к нему бог смерти мрачный Танат, чтобы низвести его в печальное царство Аида, то Си- зиф, еще раньше почувствовав приближение бога смерти, коварно обманул бога Таната и заковал его в оковы. Перестали тогда на земле умирать люди. Ни- где не совершались большие пышные похороны; пе- рестали приносить и жертвы богам подземного царст- ва. Нарушился на земле порядок, заведенный Зевсом. Тогда громовержец Зевс послал к Сизифу могучего бога войны Ареса. Он освободил Таната из оков, а Та- нат исторг душу Сизифа и отвел ее в царство теней умерших. Но и тут сумел помочь себе хитрый Сизиф. Он сказал жене своей, чтобы она не погребала его тела и не приносила жертвы подземным богам. Послуша лась мужа жена Сизифа. Аид и Персефона долго жда- ли похоронных жертв. Всё нет их! Наконец прибли зился к трону Аида Сизиф и сказал владыке царства умерших, Аиду:
83 — О, властитель душ умерших, великий Аид, рав- ный могуществом Зевсу, отпусти меня на светлую землю. Я велю жене моей принести тебе богатые жертвы и вернусь обратно в царство теней. Так обманул Сизиф владыку Аида, и тот отпустил его на землю. Сизиф не вернулся, конечно, в царство Аида. Он остался в пышном дворце своем и весело пировал, радуясь, что один из всех смертных сумел вернуться из мрачного царства теней. Разгневался Аид, снова послал он Таната за душой Сизифа. Явился Танат во дворец хитрейшего из смертных и застал его за роскошным пиром. Исторг душу Сизифа ненавистный богам и людям бог смер- ти; навсегда отлетела теперь душа Сизифа в царство теней. Тяжкое наказание несет Сизиф в загробной жизни за все коварства, за все обманы, которые совершил он на земле. Он осужден вкатывать на высокую, крутую гору громадный камень. Напрягая все силы, трудится Сизиф. Пот градом струится с него от тяжкой рабо- ты. Все ближе вершина; еще усилие, и окончен будет труд Сизифа; но вырывается из рук его камень и с шумом катится вниз, поднимая облака пыли. Снова принимается Сизиф за работу. Так вечно катит камень Сизиф и никогда не может Достигнуть цели — вершины горы...» Ничего не скажешь — грустная история! Ведь Си- зиф — в общем-то неплохой парень, во всяком случае в уме ему не откажешь! Нельзя ли помочь бедняге? Вот что интересно — дайте прочитать древний Миф программисту, и он скажет: «Ничего страшного. Речь идет о выполнении бесконечного цикла. Я и сам иногда попадаю в такое же положение и нахожу из Него выход!»
84 Глава 3 Помогаем древнегреческому герою. Циклы Цикл является одной из важнейших алгоритмиче- ских структур и представляет собой последователь ность операторов, которая выполняется неоднократ- но. В программах, связанных с обработкой данные или вычислениями, часто приходится выполнять циклически повторяющиеся действия. Циклы позво- ляют записать такие действия в компактной форме. Циклы принадлежат к числу управляющих опера- торов. Внимательный читатель мог заметить, что до сих пор мы использовали два вида операторов. Одни из них (Read, Write, оператор присваивания) только выполняли какие-либо действия, другие же управля- ли ходом выполнения программы (например, услов- ный оператор). Последние и называются управляю- щими операторами. Давайте познакомимся с примерами использова- ния циклов в программах на Паскале. Рассмотрим за- дачу на вычисление суммы большого числа слагае- мых: 1 1 1 + —+—+...+ — + 4 1 100 Можно было бы выбрать простое решение и запи- сать вычисление данной суммы в строчку, употребив 99 операций деления и 99 операций сложения. Ну а если число элементов суммы равно 1000 или просто любому целому числу? Представьте себе программу с оператором, который занимает несколько страниц и содержит 999 сложений! Очевидно, простое решение здесь уже не подходит. Можно заметить, что при вы- числении суммы повторяются всего три операции, причем в определенном порядке: 1. Разделить единицу на знаменатель. 2. Прибавить частное к ранее полученной сумме.
Цикл с предусловием 85 3. Увеличить на 1 значение знаменателя. Следовательно, задачу можно решить, например, так: 1. Присвоить переменной Sum значение, равное О (Sum : = 0). 2. Присвоить переменной 1 значение, равное 1 (1 : = 1). 3. Добавить к сумме значение 1/1 (Sum := Sum +1/1). 4. Увеличить 1 на 1 (1 := 1 + 1). 5. Повторить шаги 3 и 4. Повторив операции 3 и 4 99 раз, мы получим тре- буемую сумму. Это пример алгоритмической конст- рукции «цикл». В языке программирования Паскаль имеется три разновидности цикла: + цикл со счетчиком (цикл «для» — for ... to / downto); 4 цикл с предусловием (цикл «пока» — while); 4 цикл с постусловием (цикл «до тех пор, пока» — repeat...unti I). Каждая из трех разновидностей цикла имеет свои особенности, для каждой из них есть свой круг задач, наиболее естественно решаемых именно с ее помо- щью. Цикл с предусловием Цикл с предусловием имеет вид: while условие do {Эта часть называется заголовком цикла}
86 Глава 3 Помогаем древнегреческому герою. Циклы оператор; {Эта часть называется телом цикла} Телом цикла может быть и группа операторов, за- ключенная в операторные скобки begin... end (то есть составной оператор). Цикл с предусловием выполняется до тех пор, пока истинно условие в заголовке цикла, причем оно проверяется вначале, потом исполняется оператор. Переменным, входящим в условие, должны быть присвоены определенные значения до входа в цикл. В теле цикла должны быть операторы, которые в какой-то момент изменят значение условия, сделав его ложным. Если этого не случится, цикл будет бес- конечным. При возникновении в программе беско- нечного цикла говорят, что программа «зациклилась». Зациклившуюся программу приходится останавли- вать одновременным нажатием клавиш Ctrl+Break, ина- че она будет выполняться вечно (точнее, до первого отключения компьютера). Задача о вычислении суммы может быть решена с использованием цикла while... do следующим обра- зом: program summal; const n = 100; {Так объявляется именованная константа программы} var i : Integer; sum : Real; begi n sum := 0; i := 1; {Присвоим переменной sum начальное значение 0, a i - начальное значение 1}
Цикл с предусловием 87 while i <= n do begi n sum := sum + 1 / i; i := i + 1 {Переменная i меняется внутри цикла, и ее величина определяет очередное повторение тела цикла} end; WriteLn(’сумма n, ’ элементов= sum:10:5) ; end. Цикл в этой программе работает следующим обра- зом: 4 вначале i = 1, sum = 0; 4 условие i <= 100 в заголовке оператора while ис- тинно, поэтому начинается выполнение цикла; 4- значение суммы увеличивается на единицу: sum = 0+1; 4 i увеличивается на 1 i =2; 4 условие i <= 100 вновь истинно, поэтому тело цикла повторяется очередной раз; 4 значение суммы sum = 0 + 1 + 1/ 2, а перемен- ной i = 3; 4 после выполнения данной последовательности дейст- вий необходимое число раз получаем sum = ...+ 1 / 100 , i = 101; 4 условие i <= 100 ложно, поэтому цикл завершает- ся. Следующим действием будет вывод результата. Отметим, что в данном решении использовалась константа. Константа может иметь имя, тогда она называется именованной константой. Объявляется именованная константа в предложении описания
88 Глава 3 Помогаем древнегреческому герою. Циклы констант, которое размещается в разделе описаний программы и имеет вид: const имя = значение; Использование именованных констант преследует две цели: + сделать программу более удобной для понимания. Если, допустим, в программе часто используется число 12, то иногда удобнее один раз дать ему имя, например dozen, а затем использовать это имя; + облегчить изменение программы. Если, скажем, нужно изменить количество элементов суммы, то лучше изменить одну строку в предложении опи- сания констант, чем вносить исправления по всей программе. Цикл с постусловием Следующая разновидность цикла — цикл с постусло- вием. Рассмотрим эту разновидность: repeat группа операторов until условие; Здесь вначале выполняется группа операторов, а потом производится проверка, следует ли вновь по- вторить эту группу. Если условие ложно, выполне- ние цикла повторяется, иначе — заканчивается. Решение предыдущей задачи о суммировании с использованием цикла repeat... until выглядит таи: program summa2; const n = 100; var i : Integer;
Цикл со счетчиком 89 sum : Real; begin sum := 0; i := 1; repeat sum := sum + 1 / i; i := i + 1; {i управляет повторением цикла} until i > n; WriteLn(’сумма n, ’ элементов = sum:10: 5) ; end. Здесь выполнение цикла происходит следующим образом: 4- вначале i = 1 и sum = 0 + 1; 4- i увеличивается на 1: i = 2; 4- условие i > 100 ложно, поэтому выполнение цик- ла повторяется; + значение суммы изменяется: sum =1 + 1/ 2; + i увеличивается на 1: i = 2 + 1; + условие i >100 ложно, цикл повторяется; 4- цикл повторяется, пока не окажется i = 100 + 1, а значение суммы sum = 1+1/2 +...+ 1 / 100; 4 условие i > 100 истинно, цикл завершен. Слова repeat и until являются зарезервирован- ными, как, впрочем, и слово while. В отличие от цик- ла while операторы внутри цикла repeat выполня- ются хотя бы один раз, в то время как в цикле while они могут не выполниться ни разу. Цикл со счетчиком Цикл со счетчиком имеет следующий вид:
90 Глава 3 Помогаем древнегреческому герою. Циклы for 1 : = начальное_значение to конечное_значение do ... оператор; Здесь переменная 1, называемая управляющей пе- ременной цикла for (или его счетчиком), является произвольным идентификатором, который объявля- ется как переменная целого (чаще всего) типа. Она может быть также логической или символьной (о символьном типе речь пойдет дальше). Допускаются и некоторые другие типы, но этот случай мы рассмат- ривать не будем. При выполнении оператора for сначала вычисля- ется значение выражения начальное_значение, за- тем вычисляется значение выражения конечнос_ значение, далее управляющая переменная цикла по- следовательно пробегает все значения от начального до конечного. В том случае, когда начальное значение оказывается больше конечного значения, тело цикла не будет выполняться вовсе. Начальное и конечное значения остаются неизменными в ходе выполнения всего цикла for. Параметр цикла 1, если он целого типа, пробегает все значения с приращением 1, и его текущее значе- ние не должно изменяться операторами внутри цикла. Такое изменение не запрещено правилами языка, но его последствия будут непредсказуемы. После заве >- шения цикла параметр 1 считается неопределенным. В цикле: for 1 := начальное_значение downto конечноезначение do ... оператор; параметр цикла меняется от начального значения до конечного с шагом —1.
Цикл со счетчиком 91 Программа вычисления суммы с использованием цикла со счетчиком дана далее в двух вариантах (ва- риант с" to и вариант с downto). program summa3; const n = 100; var 1 : Integer; sum : Real; begi n sum := 0; for i := 1 to n do {При первом выполнении цикла i равняется 1, к sum добавляется 1. затем i = 2, к sum добавляется 1/2} sum := sum + 1 / i ; {Затем i = 3, к sum добавляется 1/3, и так продолжается до i = п} WriteLn(’сумма п,’ элементов = sum:10:5); end. Второй вариант: program summa4; const п = 100; var i : Integer; sum : Real; begi n sum := 0; for i := n downto 1 do sum sum + 1 / i;
92 Глава 3 Помогаем древнегреческому герою. Циклы Wr1teLn(’сумма п, ’ элементов = sum:10:5); end. Самостоятельно разберите работу циклов for в обоих вариантах программы. Программы с циклами Какую разновидность цикла лучше выбрать в каждом конкретном случае? Вот наши советы. + Используйте цикл for в том случае, когда точно знаете, сколько раз должно быть выполнено тело цикла. В противном случае обратитесь к циклам repeat или while. + Используйте цикл repeat, если необходимо, что- бы тело цикла выполнялось по крайней мере один раз. + Используйте цикл while, если хотите, чтобы про- верка была произведена прежде, чем будет выпол- няться тело цикла. 212° по Фаренгейту. У писателя-фантаста Рэя Бред- бери есть роман «451° по Фаренгейту». В подзаголов- ке к роману сказано, что «451° по Фаренгейту — тем- пература, при которой горит бумага». А что значит «по Фаренгейту»? Температурная шкала Фаренгейта была предложе- на немецким физиком Габриэлем Фаренгейтом и ис- пользуется в настоящее время в ряде англоязычных стран. В этой шкале температура замерзания воды при стандартном атмосферном давлении равна 32 °F, а температура кипения составляет 212 °F. В более привычной для нас шкале Цельсия аналогичными «опорными» точками являются соответственно О °C и
Программы с циклами 93 100 °C. Эти значения и используются для пересчета одних температур в другие. Формула для пересчета имеет вид: tf = 9 / 5 * tc + 32, где tf — температура по Фаренгейту, a tc — температура по Цельсию. Следую- щая программа предназначена для вывода таблицы соответствия между температурными шкалами Цель- сия и Фаренгейта в интервале температур от точки замерзания воды до точки ее кипения: program CelsiustoFahrenheit; var i, Celsius, Fahrenheit : Integer; begi n Writeln(1 Таблица соответствия между температурными шкалами'); Writeln('Цельсия и Фаренгейта'); Writein; for i := 0 to 20 do begi n Celsi us := 5 * i; Fahrenheit := 32 + Celsius * 9 div 5; Write(' C =', Celsius); Write(’ F =', Fahrenheit); W r i t e I n ; end; WriteLn('Нажмите <Enter>'); ReadLn; end. В данной программе еще до ее выполнения точно известно число повторений цикла, поэтому ясно, что лучше всего здесь использовать цикл со счетчиком. Так чему же равна температура горения бумаги по Шкале Цельсия? Измените программу так, чтобы най- ти ответ на этот вопрос.
94 Глава 3 Помогаем древнегреческому герою. Циклы Паскаль-рулетка. В следующем примере число повторений цикла заранее неизвестно, поэтому вме- сто цикла со счетчиком лучше использовать одну из разновидностей цикла с проверкой условия. Предла- гаем поиграть в простую, но азартную игру (крепко закройте дверь в своей комнате — родители могут увидеть!) на угадывание целого числа от 1 до 10. Пусть программа «загадает» такое число, а пользова- тель введет предполагаемое значение. Если число угадано, программа поздравит победителя, а если нет — попросит его повторить попытку еще раз. Каж- дая безуспешная попытка снижает призовые баллы. В самом начале игроку назначается 10 призовых бал- лов. Описание алгоритма: + выбрать случайное целое число от 1 до 10; 4- вывести приглашение на ввод целого значения; + если введенное число меньше задуманного, сооб- щить об этом игроку, иначе сообщить ему о том, что введенное число больше задуманного; 4- повторять ввод целого значения до тех пор, пока число не будет угадано; 4- вывести поздравление победителю и сообщить ему о набранном числе баллов; + завершить работу. Не исключена возможность того, что число будет угадано сразу. В этом случае уже не надо выводить подсказку игроку, поэтому следует использовать цикл с предусловием while. . .do: program roulette; var number, guess, bonus : Byte; begin bonus := 10;
Программы с циклами 95 Randomi ze; number := Random(ll); WriteLn('Задумано целое число от 0 до 10. Угадайте! ') ; Wri teLn; WriteLn('Введите целое число от 0 до 10'); ReadLn(guess) ; while guess <> number do begin Dec(bonus) ; WriteLn('Вы не угадали.'); Wri teLn; if guess < number then WriteLn('Ваше число меньше задуманного') else WriteLn('Ваше число больше задуманного'); WriteLn('Попытайтесь еще раз!'); ReadLn(guess) ; end; WriteLn(’Поздравляю! Вы угадали и набрали ', bonus, ' очков'); WriteLn('Нажмите <Enter>'); ReadLn; end. В этой программе используются новые операторы. Это Randomize — начальная установка специальной процедуры — «генератора» случайных чисел Random (п), выдающей случайные целые числа от 0 до п — 1, а также Dec (bonus) — вызов процедуры, уменьшаю- щей на единицу значение переменной bonus. Пробуем разбогатеть. Рассмотрим пример исполь- зования цикла с постусловием. Пусть некто (ну, на-
96 Глава 3 Помогаем древнегреческому герою. Циклы пример, вы, уважаемый читатель), обладая опреде- ленной денежной суммой, открыл счет в банке. Банк ежегодно начисляет определенный процент от вклада (это называется «учетной ставкой процента»), соот- ветственно увеличивается и сумма вклада. Считается, что этот процент не зависит от времени и от величи- ны вклада. Такая схема называется «правилом слож- ных процентов». Необходимо написать программу, которая рассчитывает величину вклада и выводит эту величину для каждого года до тех пор, пока величина вклада не удвоится. Вот алгоритм решения данной задачи: 1. Ввести первоначальную величину вклада, учетную ставку процента и год помещения денег в банк. 2. Рассчитать новую величину вклада. 3. Вывести год и величину вклада в этом году. 4. Повторять шаги 2 и 3 до тех пор, пока величина вклада не удвоится. Текст программы: program rockafeller; var balance, balance_ini11 a I, rate, interest : Real; year- : Word; begi n WriteLn('Введите год помещения денег в банк'); ReadLn(year); WriteLn('Введите величину вклада'); ReadLn(balance); WriteLn('Введите ставку процента (0.0-1.0)') ; ReadLn(rate);
Программы с циклами 97 balance!nitial := balance; Иг11е1_п('Год Вклад'); Wri teLn('============ 1); repeat interest rate * balance; balance := balance + interest; Inc(year); WriteLn(year:6, ' balance) until balance > 2 * balance_initial; WriteLn('Нажмите <Enter>'); ReadLn; end. В данном случае тело цикла выполняется по край- ней мере один раз, поэтому используется цикл с по- стусловием. Процедура Inc(year) увеличивает на единицу значение переменной year. Игра Баше на 15 предметах. Игра Баше известна во Франции. Интересно поиграть в нее, например, на уроке химии (однако авторы не отвечают за послед- ствия такой игры!). Правила игры Баше таковы. Име- ется 15 одинаковых предметов (обычно это деревян- ные палочки). В игре участвуют двое. Соперники ходят по очереди, за каждый ход играющий может взять 1, 2 или 3 предмета. Проигрывает тот, кто выну- жден взять последний предмет. Пропускать ход нель- зя. Предполагая, что нашим соперником по игре Баше будет компьютер, напишем для него программу. Прежде всего придумаем алгоритм выигрышной стратегии, при которой первый соперник начинает и выигрывает. Легко видеть, что исходные 15 предме- тов можно разбить на 5 групп, содержащих не более Чем по 4 предмета так, как указано на рис. 3.1. При таком распределении начинающий игрок бе- Рет первые 2 предмета, и далее, сколько бы ни взял 4 Зак. №933
98 Глава 3 Помогаем древнегреческому герою. Циклы второй игрок (1, 2 или 3 предмета), первый будет до- бирать до 4 предметов так, чтобы вместе они за дв? полухода выбрали одну группу. После четырех ходог первый игрок оставляет сопернику 1 предмет и выиг рывает. Рис. 3.1. Распределение предметов в игре Баше program bashe; var a, b, m : Integer; begi n m := 15 ; a := 2; m : = m - a; while m > 1 do begin ,4 Wr i te ( ’ я взял ’ , a) ; if a = 1 then Write(’ предмет’) else Write(’ предмета’); WriteLn(’ осталось m);
Программы с цмклами 99 Wr iteLn(’Соперник , ваш ход:’); ReadLn(Ь); а : = 4 - b; m := m - (а + b); end ; WriteLn(’Остался m, ’ предмет, Вы проиграли’); end . Измените эту программу так, чтобы можно было играть вдвоем с человеком. Ищем квадратный корень. Квадратный корень из числа а, у[а можно приближенно вычислить как пре- дел последовательности чисел: Xq, •••> -^к> Х^+{, ••• > где х0 — произвольное число, а каждое следующее приближение Xk+i получается из предыдущего по формуле Xk+i = 1 / 2 * (хк + а / Хк). Процесс заканчи- вается тогда, когда очередное Xk+i отличается от пре- дыдущего Хк на величину, меньшую нек^тоРого за- данного и достаточно маленького числа. Это число называют точностью вычисления квадрат кого корня, и чем оно меньше, тем точнее будет опред£лено иско- мое значение. Данный алгоритм используй ТСЯ в про- грамме squareroot: program squareroot; const eps = le-15 ; {Показательная форма записи вещественного числа, эквивалентна 10 15} var а, х0, xl : Real; begi п WriteLn(’Введите число а ’); ReadLn(а) ;
100 Глава 3 Помогаем древнегреческому герою. Циклы х0 := 0; х1 := а: {задаем предыдущее и последующее приближения к корню} repeat х0 := xl; х1 := (х0 + а / х0) / 2; {получаем из предыдущего последующее приближение} until Abs(xl - х0) < eps; WriteLn(’Sqrt(’, a, xl:10:5); end. Определить заранее, сколько потребуется повторе ний цикла для достижения заданной точности расче- та квадратного корня из определенного значения, практически невозможно, поэтому в данном примере следует выбрать какой-нибудь цикл с условием. Цикл repeat в данном случае нагляднее, так как выход из цикла определен условием достижения заданной точ- ности расчета, а для того, чтобы проверить это усло- вие, необходимо вычислить по крайней мере два эле- мента числовой последовательности, иначе нечего будет сравнивать. В поисках совершенства. Число называется со- вершенным, если оно равно сумме всех своих делите- лей, включая 1, например 6 = 1+2 + 3, 28 =1+2 + + 4 + 7+14. Поставим задачу определить, является ли заданное число совершенным. Алгоритм решения этой задачи довольно простой. Необходимо пере- брать все натуральные числа от 1 до половинки (или ее целой части) от проверяемого числа и, если оста- ток от деления рассматриваемого числа на данное ра- вен нулю, добавить очередное значение к сумме. Зна- чения, большие половинки проверяемого числа, не
Итог 101 имеет смысла рассматривать, так как они не будут его делителями. program sover; var а, i , s : Integer; begi n Write(’Введите целое число a:’); ReadLn(a); s := 0; for i := 1 to a div 2 do {Подсчет суммы делителей} if a mod i = 0 then begi n s : = s + i ; Wri te(’ + ’, i) end; if s = a then Writeln(’Число a, ’ совершенное’) else WriteLn(’Число a, ’ не совершенное’); end. Итог Итак, мы познакомились с циклами, важнейшей со- ставной частью большинства программ. Но как нам быть с героем древнегреческого мифа? Мы выяснили, что циклическое действие, которое он выполняет, бу- дет конечным, если существует условие, при выпол- нении (или невыполнении!) которого цикл должен завершаться. Ограничение может быть и по числу по- нторений цикла. От цикла, который выполняет Си- зиф, немного пользы, ведь каждый раз он повторяет °дно и то же действие. Цикл в программе при каждом
1 02 Глава 3 Помогаем древнегреческому герою. Циклы новом повторении выполняет действие, которое хот^ бы немного отличается от предыдущего. Так, Сизиф мог бы, скажем, каждый раз вкатывать на гору камень меньшего размера, чем предыдущий. Условием окон- чания нелегкого сизифова труда было бы в этом слу- чае вкатывание на гору камня, например, весом в 1 грамм. Следовательно, помочь Сизифу можно, изме- нив алгоритм его работы, введя в него «правильный,, цикл. Напоследок, как обычно, предлагаем подумать нал задачами. В каждой из задач необходимо вначале придумать алгоритм, а затем написать программу. Задача 1. Проверить тождества: 1 + 2 + 3 +...+ п = п * (п + 1) / 2, 1+3 + 5 +...+ (2 * п - 1) = п2, I2 + 22 + З2 +...+ п2 = п * (n + 1) * (2 * п + 1) / 6 I3 + 23 + З3 +...+ и3 = п2 * (п + I)2 / 4, I2 + З2 + 52 +...+ (2 * п - I)2 = п * (4 * п2 - 1) / 3, I3 + З3 + 53 +...+ (2 * п - I)3 = п2 (2 * и2 - 1). Задача 2. Пусть даны числа a, b (а > 1). Получить вс члены последовательности а, а2, а3, ... меньшие Ь. Задача 3. Пусть даны числа a, b (а > 1). Получите первый элемент последовательности а, а2, (?, ... боль ший числа Ь. Задача 4. Найти Va как предел последовательност! +o, Xi, Х2, —, где Xq = а / 3, а каждое следующее xn+i пс лучается из предыдущего по формуле: Задача 5. Числа Фибоначчи — это члены числовш последовательности которые вычисляютс по следующему правилу:
Итог 103 U\ = 1, «2 = 1, W3 = U\ + «2, —, Wk = Wk-1 + Wk-2, —, Написать программу вычисления и-го числа Фи- боначчи. Задача 6. Вводится произвольная последовательность целых чисел, заканчивающаяся нулем. 1. Найти наибольшее из всех чисел, кратных трем. 2. Найти два наименьших числа. 3. Определить, сколько раз последовательность ме- няет знак. Задача 7. Подсчитать значение многочлена n-й сте- пени: Рп(х) = а0 + ai х"'1 + а2 х”'2 +...+ апА х + а0 для заданного х, используя схему Горнера (о схеме Горнера было сказано в первой главе). Коэффициен- ты а0, определить оператором ввода. Задача 8. Вычислите приближенное значение ука- занной бесконечной суммы, задав точность ее вычис- ления: 1 + 1 / 22 + 1 /32+1/42 +... (стремится к л2 / 6) 1 / (1 * 2) + 1 / (2 * 3) + 1 / (3 * 4) +... (стремится к 1) 1 / (1 * 2 * 3) + 1 / (2 * 3 * 4) + 1 / (3 * 4 * 5) 4-... (стремится к ?) Задача 9. Дано действительное число х. Вычислить бесконечную сумму: Задача 10. Определить, являются ли два заданных числа взаимно простыми (взаимно простые числа не имеют общих делителей, кроме единицы).
I Глава 3 Помогаем древнегреческому герою. Циклы исключая само число) 11 + 20 + 22 + 44 + = 220.- Задача 11. Найти все дружественные числа в интер вале от 1 до 300. Два числа называются дружествен ными, если каждое из них равно сумме делителе! другого числа (включая 1 и Например: 220 и 284: 220: 1 + 2 + 4 + 5+10 + + 55 + 110 = 284, 284: 1 + 2 + 4 + 71 + 142 Задача 12. Дано натуральное число п. Разложить ег< на простые множители. Задача 13. Напишите программу для приближенное вычисления числа п, используя следующее представ- ление: л . 1 1 1 1 — = 1---+ — -—+ — -... 4 3 5 7 9 Задача 14. Напишите программу для приближенной вычисления числа л, используя следующее представ ление: л _ 1 + 1 + 1 + 8 1x3 5x7 9 х 11 Задача 15. Напишите программу для приближенного вычисления числа л, используя алгоритм Архимеда 4 Положить а = 1 и п = 6. 4 Повторить шагов: 4 Присвоить 4 Присвоить а = у2 — лМ — т. L па 4 Положить b = . 2 Ь + Положить с — . === т раз следующую последовательность п = 2 п. а2
Итог 105 -f- Положить р = . Это — приближенное значе- ние ТС. Ь — с -4- Положить е = —-—. Это — оценка погрешности вычисленного значения числа тс. + Вывести значение р и е.
ГЛАВА 4 В поисках сокровищ. Массивы
Маршрут нашего путешествия проходит мимо усадь- бы мистера Трелони, сквайра из приключенческой повести Роберта Луиса Стивенсона «Остров сокро- вищ». Давайте подойдем к окну и послушаем, о чем говорят сквайр и доктор Ливси: «— Деньги! — вскричал сквайр. — Разве вы не слы- хали, что рассказывал Дане? Чего могли искать эти злодеи, если не денег? Что им нужно, кроме денег? Ради чего, кроме денег, они стали бы рисковать своей шкурой? — Мы скоро узнаем, ради чего они рисковали шку- рой, — ответил доктор. — Вы так горячитесь, что не даете мне слова сказать. Вот что я хотел бы выяснить: предположим, здесь, у меня в кармане, находится ключ, с помощью которого можно узнать, где Флинт спрятал свои сокровища. Велики ли эти сокровища? — Велики ли, сэр! — закричал сквайр. — Так слу- шайте! Если только действительно в наших руках на- ходится ключ, о котором вы говорите, я немедленно в бристольских доках снаряжаю подходящее судно, беру с собой вас и Хокинса и еду добывать это сокро- вище, хотя бы нам пришлось искать его целый год!..» Герои повести Стивенсона ищут сокровища капи- тана Флинта. А где прячут свои сокровища програм- мисты? Речь, конечно, идет не о золотых монетах и не о бриллиантах. Самым ценным сокровищем для программиста является информация, с которой рабо- тает его программа. Информация может храниться в виде чисел или текста. Но где? Ответ прост — в ячей- ках массива.
1 08 Глава 4 В поисках сокровищ. Массивы Массивы Разберемся сначала, что такое массив. Пусть рассмат ривается переменная величина, которая может при нимать различные значения, и мы можем определить эти значения в какие-то моменты времени. Получен ные значения можно собрать в таблицу. Измеряя, на- пример, ежедневно температуру в январе, можно со- ставить таблицу: Число 1 января 2 января 31 января t -5.2 -2.5 -12.5 Первая строка этой таблицы фактически нумерует столбцы и ее можно опустить, тогда таблица будет иметь вид: -5.2 -2.5 ... -12.5 Такая таблица-строка содержит 31 значение. Пер- вая ячейка в ней содержит значение температуры 1 января, что можно обозначить Т[1] = —5.2, вторая температуру второго января Т[2] = —2.5 и т. д. Заме- тим, что все ячейки содержат однотипные (похожие) данные — значения температуры. Другой пример таб- лицы, содержащей однотипные данные, — классный журнал со списком учащихся, где каждая ячейка (клетка журнала) определяется двумя значениями — фамилией ученика и датой, а в ячейку заносятся от- метки — целые числа от 1 до 5. Массив в любом язы- ке программирования похож на таблицу. Массив — это совокупность конечного числа эле- ментов одного и того же типа, в которой каждый элемент имеет номер, а все элементы — общее имя.
Массивы 109 Номер элемента массива называется его индексом, причем индексов может быть несколько (как в при- мере с классным журналом — два). Массив с одним индексом называют одномерным массивом, с дву- мя — двумерным и т. д. Если в программе используется массив, его следу- ет описать в разделе описаний программы. Предло- жение описания одномерного массива имеет вид: var имя_массива : array [начальный_индекс..конечный_индекс] of тип_данных; Здесь имя массива выбирается в соответствии с обычными правилами Паскаля. Array — зарезервиро- ванное слово, указывающее, что имя относится к мас- сиву. Начальный и конечный индексы — это обяза- тельно целые числа, определяющие диапазон изменения индексов (номеров) элементов массива. Так, для хранения таблицы температур в январе следует описать массив: var January_temperatures : array[1..31] of Real; Нумерация элементов массива не обязательно на- чинается с 1, но нижняя граница должна быть мень- ше верхней. Количество элементов в массиве можно легко получить по формуле: конечныйиндекс - начальныйиндекс + 1 Так, например, массив var summer : array[6..8] of Integer; состоит всего из трех элементов. Обратиться к элементу массива как к обычной переменной можно, указав имя массива и номер эле- мента (индекс) в квадратных скобках [ ]. С элемен- тами массива можно выполнять все операции, ко-
110 Глава 4 В поисках сокровищ. Массивы торые допускаются его типом. Так, для числовых массивов (целый или вещественный типы) допусти- мыми являются арифметические операции. Средняя температура месяца. Для того чтобы найти среднюю дневную температуру месяца, необхо- димо сложить значения измеренных температур и разделить полученную сумму на количество дней: program average_temperature; var temperature : array[1..31] of Real; {Массив для хранения таблицы температур} 1 : Integer; {i - номер дня месяца} s г, su : Real; {su - сумма температур за месяц, sr - средняя температура} begi п WriteLn(’Введите температуру: ’); for i := 1 to 31 do begi n WriteLn(i:2, ’-го числа ReadLn(temperature[i]); {Ввод значений температур} end ; su : = 0; for i := 1 to 31 do su := su + temperature[i]; {Вычисление суммы} st* := su / 31; {Вычисление среднего} WriteLn(’Средняя температура месяца = sr:6:2); end . Посмотрим, как выполняется эта программа. Сна- чала она попросит ввести значения температур для каждого дня:
Массивы 111 Введите температуру: 1-го числа -5.2 2-го числа -2.5 31-го числа -12.5 Введенные значения будут присвоены соответст- вующим элементам массива temperature. Можно считать, что такой массив представляет собой храня- щуюся в оперативной памяти компьютера таблицу: -5.5 -2.5 ... -12.5 Для того чтобы найти среднемесячную температу- ру, нужно сложить temperature[1] + temperature[2] +... + temperature[31] и полученную сумму разделить на 31. Подведем итоги контрольной работы. В некото- рых случаях удобно присваивать начальные значения элементам массива, поместив их в раздел описаний констант. Пусть требуется подсчитать количество пя- терок, четверок и троек, полученных за контрольную работу по алгебре, если число учеников равно 15. Та- кой подсчет можно поручить программе: program algebra_test; const algebra : array[1..15] of Integer = (5, 4, 5, 4, 3, 4, 3, 5, 3, 4, 5, 5, 4, 4, 4) ; var k3, k4, k5, i : Integer; begi n k3 := 0;
112 Глава 4 В поисках сокровищ Массивы к4 := 0; к5 := 0; for i := 1 to 15 do begi n i f algebra[i] = 5 then k5 := k5 + 1; if algebra[i] = 4 then k4 := k4 + 4; i f algebra[i] = 3 then k3 := k3 + 3 ; end ; WriteLn(’Пятерок k5, ’четверок k4, ’троек ’, k3) end . Заметим, что в массиве, описанном в разделе кон- стант, можно изменять первоначальные значения (в от- личие от констант простых типов). Пятый элемент. Задача заключается в том, чтобы в массив, состоящий из 10 элементов, добавить на 5-е место новый элемент, сохранив последующие элемен- ты. Бывший пятый элемент после вставки смещается на шестое место, шестой — на седьмое и т. д. Послед- ний элемент при этом будет потерян: program fifth_element; const N = 10; Ar : array[l..N] of Integer=(ll, 12, 13, 14, 16, 17, 18, 19, 20, 21); var i Integer; begin for i := 10 downto 6 do Ar[1] : = Ar [ i - 1] ; Ar[5] := 15; for i := 1 to 10 do WriteLn(Ar[1] , ’|’); {Вывод массива} end.
Массивы 113 Поясним, как в программе f 1 f thelement созда- ется новый массив. В массиве Аг: Т1 12 13 14 16 17 18 19 20 21 пропущен элемент 15, который должен стоять на пя- том месте. Если мы хотим добавить этот элемент, мы должны раздвинуть таблицу, освободив пятое место для элемента 15, и потеряв при этом последний эле- мент 21. В массивах количество элементов строго определено их описанием и не может быть увеличено в ходе выполнения программы. Мы достигли желае- мого, поместив: 4- на 10-е место 9-й элемент; 4 на 9-е место 8-й элемент; 4 на 6-е место 5-й элемент; 4 после этого вместо числа 16 на 5-е место можно поместить число 15. Изгнание из массива. Эта задача противоположна предыдущей. Пусть требуется из таблицы Аг: И 12 13 14 15 15 16 17 18 19 исключить 6-й элемент и добавить недостающий 10-й элемент, равный 20. Вот решение этой задачи: program new_element; const Ar : array[1..10] of Integer = (11, 12, 13, 14, 15, 15, 16, 17, 18, 19); var i : Integer; begi n
114 Глава 4 В поисках сокровищ. Массивы for 1 := 6 to 9 do Ar [1 ] := Ar[i + 1]; Ar[10] := 20; for 1 := 1 to 10 do Wri teLn(Ar[i ] , ’ | ’ ) end. Для того чтобы удалить элемент из таблицы, мож- но просто его заменить следующим элементом, то есть: + на 6-е место поставить 7-й элемент; + на 7-е место поставить 8-й элемент; + на 8-е место поставить 9-й элемент; + на 9-е место поставить 10-й элемент; + на 10-е место ввести новый элемент, равный 20. Можно было бы, конечно, переписать массив зано- во, занеся в него подряд идущие элементы от 11 дс 20, но как быть, если мы не знаем заранее содержимое массива? Все наоборот. Теперь переставим элементы мас- сива в обратном порядке. Пусть массив Аг содержит следующие значения: И 12 13 14 15 16 17 18 19 20 Чтобы получить на этом же месте таблицу: 20 19 18 17 16 15 14 13 12 11 достаточно поменять местами элементы: + первый и десятый; + второй и девятый; + третий и восьмой;
Массивы 115 4 четвертый и седьмой; 4 пятый и шестой. Можно, конечно, просто создать таблицу заново, но мы не сможем этого сделать, если ее элементы за- ранее неизвестны (скажем, получены случайным об- разом). Программа reverse решает поставленную за- « дачу: program reverse; const N = 10; var 1, ra : Integer; Ar : array[l..N] of Integer; begi n for i := 1 to N do Ar[i] := 10 + i; {Присвоение первоначальных значений} for 1 := 1 to N do Wr iteLn(Ar[i] , ’|’); {Печать первоначальной таблицы} for i := 1 to N div 2 do begin ra := Ar[i]; Ar[i] := Ar[N - i + 1] ; Ar[N - I + 1] := ra; {Обмен значениями} end; for i ;= 1 to N do Wri tei_n(Ar [i ] , ’ | ’ ) ; {Вывод новой таблицы} end. Заметим, что при каждом i, которое меняется от 1 До 5, N - i + 1 меняется от 10 до 6.
116 Глава 4 В поисках сокровищ. Массивы Поиск минимального элемента. В следующем при- мере в массиве из N случайных целых чисел (каждое из диапазона от —20 до 20) найдем наименьшее значе- ние и его номер: program min_element; const N = 10; {Здесь может быть любое число} var Ar : array[l..N] of Integer; i, min, nomer : Integer; begi n Randomi ze; for i := 1 to N do begi n Ar[i] := random(41) - 20; {Заполнение массива случайными числами вывод таблицы для контроля} Write(Ar[i], ’|’) end ; Wri teLn; {Печать результата с новой строки} min := Аг[1]; nomer := 1; for i := 2 to N do if Ar[i] < min then begi n min := Ar[i]; nomer := i; WriteLn(’Меньший из i, ’ = min, ’его номер =’, nom); end ; end. В этой программе используются процедуры полу- чения случайных чисел Random и Randomize, знако- мые нам по предыдущей главе.
Массивы 117 Для нахождения минимального элемента приме- ним следующий алгоритм. Положим min = Аг[1] и поте г = 1. Сравним min с Аг[2] и присвоим мень- шее из двух значений Ar [1] и Аг [2] переменной min, а его номер — переменной поте г. После этого значе- ние min сравним с Аг[3] и так далее. Перебрав последовательно все элементы и сравни- вая значение очередного элемента с наименьшим из предыдущих, получим искомый минимальный эле- мент, в переменной поте г при этом будет содержать- ся его номер. Кружатся стрелки. Следующая программа — twirl — выводит на экран маленькую вращающуюся стрелку. Имитацию вращения стрелки можно запро- граммировать, используя массив, состоящий из четы- рех символьных элементов: | , \ , — и / (символьный тип данных будет обсуждаться позже). Эти символы последовательно, один за другим и с некоторой за- держкой по времени выводятся в заданном месте эк- рана. Эффект «смены кадров» и создает иллюзию вращения стрелки. Алгоритм реализован в процедуре rot. Первый параметр этой процедуры (j) задает но- мер позиции в строке, второй (к) — номер строки вы- вода, третий (color) — это цвет стрелки, и четвертый (speed) — временная задержка (скорость вращения). Курсор устанавливается в заданное положение в ре- зультате вызова процедуры GotoXY. Цвет стрелки за- дается с помощью процедуры TextColor. Индексы элементов массива line перебираются циклически до тех пор, пока не будет нажата любая клавиша. Вре- менная задержка между выводом последовательных элементов массива обеспечивается процедурой Delay. program twi rl ; uses dos, crt;
118 Глава 4 В поисках сокровищ. Массивы const line : array[0..3] of char = Cl’. ’V , •-* . */') ; var m : integer; begin TextColor(Yellow); m : = 0; repeat GotoXY(40, 13); Wr ite(line[m]); GotoXY(l. 1); Delay(100); m := (m + 1) mod 4; until KeyPressed; TextColor(LightGray); end. В операторах присваивания можно использовать не только элементы массивов, но и массивы в целом. Оператор вида А := В короче цикла for i :=1 to n do A[i] := B[i]. С другой стороны, необходимо помнить, что оператор вида А : = В приводит к копи- рованию всего массива, и поэтому использовать его следует очень аккуратно. Массивы в целом можно ис- пользовать и в логических отношениях равенства (=) и неравенства (<>). Другие операции отношения при- меняются только к элементам массива. Сортировка — от хаоса к порядку Сортировка массива — один из наиболее распростра- ненных процессов обработки данных. Так, например, список учеников класса, фамилии в телефонном справочнике, банковские картотеки
Сортировка — от хаоса к порядку 119 клиентов всегда отсортирова- ны для облегчения доступа к нужной информации. Сорти- ровка — это размещение объ- ектов в определенном поряд- ке. Числа могут размещаться по убыванию или по возрас- танию, фамилии — в алфа- витном порядке. Известно несколько алго- /1 / | | \ ритмов сортировки. Мы рас- (I I I I \ смотрим только один из них — 44 I I метод сортировки обменом. Л Он не очень эффективен и на л практике используется ред- * ко, однако для простых случаев, когда нужно отсор- тировать до сотни элементов, он вполне удовлетво- рителен. Кроме того, этот метод интересен тем, что моделирует естественное поведение человека, осуще- ствляющего сортировку вручную. Он легко описыва- ется в форме четких алгоритмов и приводит к про- стой программной реализации. Итак, рассмотрим задачу о размещении целых чи- сел из таблицы: 50 40 10 20 30 ... 7 в порядке неубывания. Пускаем пузырьки. Сортировку обменом называ- ют еще методом пузырька. Суть метода состоит в том, что последовательно сравниваются пары соседних элементов массива. Если первый элемент пары ока- зался больше второго, то они меняются местами и на второе место (как пузырек) «всплывает» больший из Двух элементов:
120 Глава 4 Б поисках сокровищ. Массивы 40 | 50 | 10 | 20 | 30 |... | 7 | Затем выбирается пара, состоящая из 2-го и 3-гс элементов массива, сравнение и перестановка повто ряются, и на 3-е место всплывает больший элемент ис трех: 40 | 10 | 50 | 20 | 30 |...| 7 | 40 | 10 | 20 | 50 | 30 |...| 7 | Сравнение с перестановкой повторяются, пока нс будет достигнут конец массива, в результате чего са мый большой элемент массива «всплывает» и зани мает крайнее правое место: 40 | 10 | 20 | 30 |... | 7 | 50 | Такой проход от начала к концу массива составля ет один шаг процесса сортировки. В результате вы полнения прохода самый большой элемент оказало самым правым элементом массива. Следующим шагом алгоритма является проход о первого до (72-7)-го элемента. Его результатом буде' размещение наибольшего из оставшихся элементов на (72-7)-м, предпоследнем месте: 10 | 20 | 30 |...| 7 | 40 | 50 | Третий по величине элемент окажется третьи? справа: 10 | 20 | ... | 7 | 30 | 40 | 50 | ' и т. д. А вот программа, которая выполняет сортиров ку массива методом пузырька: program hubblebubble; const n = 7; a : array[l..n] of Integer = (50, 40, 10, 20, 30, 5. 7);
Сортировка — от хаоса к порядку 121 {Размер п и первоначальные значения можно задать любыми другими} var i. j. k, rab : Integer; begin for i := 1 to n - 1 do {i- номер “заплыва” , на каждом шаге пузырек “плывет” от первой позиции в (n-i+D-ю позицию} for j := 1 to п - i do {Сравниваются пары элементов} if а [ j]>а[j+1] then {обмен} begin rab := a[j + 1] ; a[j + 1] : = a [ j ] ; a[j] := rab; for k := 1 to n do {По k выводим таблицу на экран после каждого ее изменения} Wri te(а[к]:4, ’ | ’); Writel_n(’-----------------------------’ ) ; end ; WriteLn(’Массив отсортирован’); {Вывод результата сортировки} for к := 1 to n do Wri te(а[к] :4, ’ | ’) ; WriteLn(’------------------------------’)'• end . В приведенной программе вывод массива на экран встречается в тексте дважды. Это сделано для того, чтобы проследить сортировку по шагам. До сих пор речь шла о расположении чисел в по- рядке их неубывания. А как сделать, чтобы сортиров- ка обеспечивала убывание или невозрастание? Это совсем несложно. Нужно лишь вместо условия
122 Глава 4 В поисках сокровищ. Массивы A[j] > А[j + 1] записать прямо противоположное ему условие А [ j ] < A[j + 1 ]. Попробуйте самостоя- тельно изменить программу hubble_bubble так, что- бы она выполняла сортировку по убыванию. Заметим, что возможность появления двух одина- ковых чисел не создает каких-либо проблем. В мо- мент сравнения двух одинаковых элементов оба оста- ются на прежних местах, но затем, постепенно перемещаясь по ряду, они займут свои окончатель- ные положения, оставаясь в соседних позициях. Наша программа сортировки написана для эле- ментов, принимающих целые значения. Если мы из- меним в объявлении массивов тип Integer на Real и сформируем массив, содержащий вещественные зна- чения, то он будет отсортирован точно так же (при этом, конечно, нужно привести в соответствие типы промежуточных переменных, таких, например, как г ab). Так же дело обстоит с символами и строками. Если в элементах массива хранятся имена или симво- лы, этот массив будет отсортирован в соответствии с кодами символов. Поиск заданного элемента в массиве Последовательный поиск. Пусть массив из п элемен- тов содержит список имен: 1 Олег 2 Иван 3 Максим ... 8 Степан
Поиск заданного элемента в массиве 123 Как определить, на каком месте находится иско- мое имя и есть ли оно там вообще? Решить такую задачу можно, применив алгоритм поиска. Самый простой алгоритм поиска — последовательный. Он предполагает, что мы перебираем элементы массива подряд, начиная с первой позиции. При этом либо бу- дет найдено указанное имя (если оно там есть), либо мы просто исчерпаем массив: program seq_search; const n = 8; a : array[l..n] of String = (’Олег’, ’Иван’, ’Максим’, ’Ольга’, ’Галина’, ’Рита’, ’Алла’, ’Степан’); {Так описывается массив строк} var i : Integer; im : String; begi n Write(’Введите искомое имя ’); ReadLn(i m); i := 0; repeat i : = i + 1; until (im = a[i]) or (i > n); if i <= n then WriteLn(’HMfl ’, a[i] , ’ на ’ , i, ’-m месте’); else WriteLn(’Такого имени в списке нет’); end . Общим свойством алгоритмов последовательного поиска является то, что время поиска пропорцио- нально количеству элементов списка. А можно ли ис-
124 Глава 4 В поисках сокровищ. Массивы кать быстрее, ведь количество элементов массива мо- жет быть очень велико? Оказывается, можно искать существенно быстрее, если список упорядочить по алфавиту. Списки учеников в классных журналах, картотеки в библиотеках, поликлиниках, банках, те- лефонные справочники и другие списки упорядоче- ны по алфавиту. Двоичный (логарифмический) поиск. Суть дво- ичного поиска заключается в следующем. Пусть в предварительно упорядоченном массиве нужно най- ти имя Петр: а = (’Алла*. ’Анна’, ’Дмитрий’, ’Ирина’, ’Мария’, ’Олег’, ’Петр’, ’Софья’, ’Степан’, ’Федор’); Выполним следующую последовательность дейст- вий: + Делим список пополам (n div 2 = 5), а [5] = ’Мария’. + Сравним искомое имя с тем, что находится в сере- дине списка. Если имена совпадают, поиск завер- шен. + Если имя в середине списка меньше искомого (по коду первой буквы), продолжаем поиск в правой половине списка, если больше — в левой. + Так продолжаем до тех пор,'пока или найдем имя. или делить будет нечего. program bin_search; const n = 10; a : array[l..n] of String = (’Алла’, ’Анна’, ’Дмитрий’, ’Ирина’, ’Мария’, ’Олег’, ’Петр’, ’Софья’, ’Степан’. ’Федор’);
Двумерные массивы 125 var 1, г, m : Integer; im : Stri ng; fl : Boolean; begi n Write(’Введите имя ’); ReadLn(i m); I := 1; r : = n ; fl := false; repeat m := (I + r) div 2; if im = a[m] then fl := true; if im < a[m] then r : = m - 1 else I := m + 1; until (I > r) or fl; if fl then WriteLn(’a[’, m, a[m]) else WriteLn(’1акого имени нет ’) end. Двумерные массивы Итак, мы разобрались с тем, как используются одно- мерные массивы. С двумерными таблицами (мас- сивами) любой из нас впервые встретился еще на Уроках арифметики. Таблица сложения натуральных чисел от 1 до 9 — пример двумерной таблицы, масси- ва с двумя измерениями:
126 Глава 4 В поисках сокровищ Массивы 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 10 2 3 4 5 6 7 8 9 10 И 3 4 5 6 7 8 9 10 11 12 4 5 6 7 8 9 10 И 12 13 5 6 7 8 9 10 И 12 13 14 6 7 8 9 10 И 12 13 14 15 7 8 9 10 И 12 13 14 15 16 8 9 10 11 12 13 14 15 16 17 9 10 11 12 13 14 15 16 17 18 Чтобы указать элемент такой таблицы, нужно за- дать два индекса — номер строки (индекс г) и номер столбца (индексу). Так, например, на пересечении пя- той строки и шестого столбца (г = 5, j = 6) стоит эле- мент а[5, 6], он равен 11. В программах на Паскале двумерные массивы описываются следующим образом: var имя_массива : array [номерпервойстроки.. номер_лоследней_строки, номерпервогостолбца.. номер_последне го_столбца] of тип_элементов_ массива; Место для массива в памяти компьютера отводит- ся согласно этому описанию. Таблица сложения натуральных чисел может быть описана так: var Add : array [1..9, 1..9] of Integer; Для двумерного массива Add в памяти ЭВМ отво- дится сплошной участок, в котором элементы табли-
Двумерные массивы 127 цы располагаются по строкам (то есть сначала идут элементы первой строки, затем элементы второй строки и т. д.): Add [1,1] Add [1,2] ... Add [1.9] Add [2,1] ... Add [9,9] Обратиться к четвертому элементу третьей строки таблицы можно, указав номера столбца и строки в квадратных скобках после имени массива — Add [3, 4]. Эта же таблица может быть описана и как массив массивов с помощью задания типа: type Ar = array[1..9] of Integer: var Add : array [1..9] of Ar: В Паскале есть возможность ввести свой собствен- ный тип переменных. Делается это с помощью пред- ложения описания типов type и тех типов данных, которые уже есть в Паскале: type имя новоготипа = описание_типа; Имя нового типа может быть любым, а его описа- ние строится с помощью встроенных типов данных. В нашем примере имя нового типа — Аг, описан он как целочисленный массив из девяти элементов. Да- лее имя нового типа можно обычным образом ис- пользовать в предложениях описания переменных. Второй вариант описания массива Add можно по- нимать следующим образом. Add — это массив, со- стоящий из девяти элементов, каждый из которых, в свою очередь, является массивом из девяти целочис- ленных элементов. Расположение в памяти такого массива такое же, как и в предыдущем случае, но об- ращение, скажем, к четвертому элементу третьего массива иное: Add[3][4]
128 Глава 4 В поисках сокровищ. Массивы где 3 — номер массива (то есть строки), а 4 — номер элемента в этом массиве. Заполнение двумерных таб- лиц и вывод их на печать происходит, как правило, построчно. Таблица сложения. Следующая программа выво- дит на экран таблицу сложения натуральных чисел от 1 до 9: program addition_table; const n = 9; var a : array [1..9, 1..9] of Integer; i , j : Integer; begi n for i := 1 to n do {Заполнение таблицы производится построчно} for j:= 1 to n do a [ 1 , j ] : = I + j ; for i := 1 to n do {Вывод элементов таблицы построчно} begin for j := 1 to n do Wri te(a[i, j], ’ | ; {Вывод i-й строки} WriteLn; {Переход на новую строку} end; end. Итог Итак, мы узнали... ну если и не где хранятся сокрови- ща капитана Флинта, то, во всяком случае, где про- граммист может разместить драгоценные жемчужины информации — в ячейках массивов. Что делает жад- ный обладатель сокровищ? Он любуется ими, пере-
Итог 129 бирает их, а иногда пытается найти в своих многочис- ленных сундуках драгоценную диадему. Так и мы — научились перебирать свои сокровища, сортируя мас- сивы, а также научились искать нужную нам драго- ценность, применяя алгоритмы поиска. В заключение, как обычно, предлагаем самостоятельно подумать над задачами. Задача 1. Алгоритм «сжатие». Целочисленную таб- лицу из А элементов переписать так, чтобы вместо одинаковых идущих подряд элементов оставался толь- ко один. Остаток таблицы заполняется нулями. Задача 2. Алгоритм «уплотнение». Целочисленную таблицу из N элементов уплотнить так, чтобы сохра- нить порядок следования ненулевых элементов (то есть убрать из таблицы нулевые элементы, поместив их в конец). Задача 3. Алгоритм «вставка». Дана таблица из А элементов вещественного типа, расположенных по неубыванию. Вставьте в таблицу любое заданное ве- щественное число М так, чтобы упорядоченность таб- лицы сохранилась (последний элемент при этом по- теряется, если только М его не превышает). Задача 4. Из двух одномерных массивов A[1..N] и В[1..М], отсортированных в порядке неубывания, сфор- мировать новый одномерный массив C[1..N + М], со- стоящий из элементов обеих таблиц, который сохра- няет упорядоченность. Задача 5. Подсчитать количество различных чисел, встречающихся в одномерном массиве из N элементов. Задача 6. Определить, является ли заданный массив упорядоченным. Задача 7. Заданы массив и некоторое число. Найти, на каком месте находится это число в массиве. 5 Зак. №933
130 Глава 4 В поисках сокровищ. Массивы Задача 8. Известно, что ферзь может перемещаться по вертикали, горизонтали и диагонали на любое же- лаемое число полей. Составить программу, входными данными которой являются номер горизонтали М и вертикали N, определяющую местоположение фер- зя на шахматной доске и отмечающую поля под боем — единицами, а остальные — нулями. Пример для М = 4, N = 3. 1 2 3 4 5 6 7 8 1 0 0 1 0 0 1 0 0 2 1 0 1 0 1 0 0 0 3 0 1 1 1 0 0 0 0 4 1 1 0 1 1 1 1 1 5 0 1 1 1 0 0 0 0 6 1 0 1 0 1 0 0 0 7 0 0 1 0 0 1 0 0 8 0 0 1 0 0 0 1 0 Задача 9. Составить программу, отмечающую, в зави- симости от местоположения ладьи, слона, короля, коня на шахматной доске, поля под боем каждой из этих фигур. Задача 10. Заполнить массив А размера 10x10 сле- дующим образом: а) 0 0 0 ... 0 0 1 0 ... 0 0 0 2 ... 0 ... ... ... ... ... 0 0 0 ... 9
Итог 131 б) 1 2 3 ... 10 0 1 2 ... 9 0 0 1 ... 8 ... ... ... ... 0 0 0 ... 1 в) 0 0 0 ... 0 1 0 0 ... 0 1 2 0 ... 0 ... ... ... ... ... 1 2 3 ... 0 Задача 11. Написать программу, располагающую чис- ла по спирали в таблице из N строк и N столбцов. 1 2 3 4 5 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9 Задача 12. Задать квадратную таблицу из N элемен- тов случайным образом. аи ai2 ап ai4 ai5 a2i а22 агз агд агз аз1 азг азз аз4 азз ад1 адг адз а44 а45 asi азг а5з а54 а55
1 32 Глава 4 В поисках сокровищ. Массивы Найти суммы элементов из области, помеченной звездочками. а) * * * * * * * * * б) * * * * * * * * * в) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Итог 133 Задача 13. Найти минимальный элемент двумерной таблицы и указать его номер. Задача 14. Дана таблица AfxTV, содержащая буквы ла- тинского алфавита. Отсортировать строки матрицы в алфавитном порядке.
ГЛАВА 5 Привал, играем в «Puzzle». Подпрограммы и модули
Мы прошли половину нашего пути, и настало время сделать привал. А на привале можно и отдохнуть. Достанем из рюкзака головоломку, которую принято называть «паззлом» (от английского слова puzzle). Эта игрушка представляет собой набор маленьких фрагментов причудливой формы, несущих на себе частичку какой-то большой картины. Задача заклю- чается в том, чтобы сложить эти фрагменты и полу- чить картину. Оказывается, что маленькие пластинки не только являются частями картины, но и соеди- няться со своими соседями могут только вполне опре- деленным образом. Вот так и многие большие про- граммы, как из кусочков, собираются из отдельных частей, которые называются подпрограммами и моду- лями. Эти кусочки, как мы увидим, могут взаимодей- ствовать между собой только вполне определенным образом. Следовательно, забавная головоломка «puzzle» может научить нас важнейшему принципу программи- рования — принципу модульности программ. В модульной программе отдельные ее части, пред- назначенные для решения каких-то частных задач, организованы в подпрограммы. В такой организации есть два больших преимущества. Во-первых, один и тот же фрагмент можно использовать многократ- но, как в одной, так и в разных программах, не наби- рая его текст заново. Во-вторых, программы лучше писать небольшими частями. Такие программы лег- че читать, тестировать и отлаживать. У них, как пра- вило, более четкая логическая структура. В языке Паскаль модульность обеспечивается использовани- ем подпрограмм — 'подпрограмм-функций, подпро- грамм-процедур, а также модулей.
136 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули Процедуры Необходимость в подпрограммах возникает по мере нарастания сложности решаемой задачи. Процеду- ра — это относительно самостоятельная программа решения некоторой частной задачи, написанная и оформленная так, что ее можно многократно выпол- нять с различными входными данными в различных местах общей программы. Процедура дает возмож- ность заменить группу команд одной командой. Выполняется процедура только в момент обраще- ния к ней. Обращение к процедуре называют вызовом процедуры. Вызвать процедуру можно из раздела опе- раторов программы, из другой подпрограммы-проце- дуры или подпрограммы-функции. Для того чтобы использовать процедуру, ее надо описать. Описывается процедура в тексте программы после раздела описания переменных. Следует при- держиваться правила, согласно которому любая под- программа должна быть описана до того, как она бу- дет вызвана в программе или в другой подпрограмме. «Устройство» процедуры напоминает устройство программы. Она состоит из заголовка, раздела описа- ний и набора операторов, заключенного в оператор- ные скобки begin... end (аналогично разделу операто- ров программы). В общем виде описание процедуры выглядит так: procedure имя_процедуры(var параметр_1 : тип_1; var параметр 2 : тип_2; . . . var параметр_И : тип N) ; раздел описаний процедуры . * begi п раздел операторов процедуры end;
Процедуры 137 Слово procedure является зарезервированным сло- вом языка Паскаль. Имя процедуры подчиняется обычным правилам для имен Паскаля и используется для вызова процедуры. Параметры — это список пере- менных с указанием их типа, var — необязательное в данном случае зарезервированное слово языка Пас- каль, означающее, что при вызове процедуры пара- метром должна быть переменная основной програм- мы. С помощью параметров в процедуру при вызове передаются данные, необходимые для ее работы, а об- ратно, в вызывающую программу, передаются значе- ния, полученные в результате выполнения подпро- граммы. Список параметров может отсутствовать. Завершается подпрограмма-процедура зарезерви- рованным словом end, но за ним следует не точка, а точка с запятой. В разделе описаний основной про- граммы или подпрограммы-процедуры могут содер- жаться описания других подпрограмм. Таких описа- ний может быть несколько. Кто больше? Познакомимся с примером исполь- зования процедуры. Посмотрим, как можно найти большее из четырех чисел а, Ь, с и d, используя алго- ритм нахождения большего из двух в качестве вспо- могательного: 4 найдем большее из а и Ь, пусть это будет mab', + найдем большее из с и d, пусть это будет mcd‘, 4 найдем большее из двух больших, пусть это будет max-, 4 найденное значение (max') и будет решением задачи. Текст программы: program largest; var а , b , с , d ,
138 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули mab, med, max : Real; procedure max2(x. у Real; var z : Real); begi n if x >= у then z := x else z := y; {z = max(x ,y)} end; {max2} begin {Основная программа} Write(’Введите четыре числа ’); ReadLn(a, b, c, d); max2(a, b, mab); {Вызов процедуры} max2(c, d, med); {Процедура работает именно в момент вызова} max2(mab, med, max); WriteLn(’Боиьшее из a:10:5, b:10:5, c:10:5, d:10:5, ’ = max:10:5); end. Формальные и фактические параметры. Пара- метры, которые используются в описании процедуры (в нашем примере это х , у , z), называются формам - ними параметрами, вместо них при обращении к процедуре подставляются фактические параметры: первый раз это второй раз это третий раз это х а с mab У b d med Первый фактический параметр связывается с первым формальным параметром, второй со вторым и т. д. Если параметр описан в заголовке процедуры как переменная (var z), этой переменной при обращении к процедуре передается адрес в памяти компьютера, по которому располагается аргумент (первый раз — адрес переменной mab, второй раз — адрес med, тре
Процедуры 139 тий адрес переменной пах). В результате процедура имеет доступ к этим параметрам и может их менять во время своей работы. Такие параметры называются параметрами-переменными. Параметры без описателя var (в нашем случае х, у : Real) называются параметрами-значениями. Этим параметрам отводится место в памяти компью- тера, и при вызове процедуры max2(a, b, mab) зна- чение аргумента а пересылается в х, значение аргу- мента b пересылается в у. Можно в качестве аргумента использовать константу или выражение того же типа, что и формальный параметр. Например, при вызове процедуры max2 (2-3 , 7, mab) аргумент mab получит значение 7. При использовании процедур следует соблюдать следующие правила: + параметры, через которые в процедуру передаются исходные данные, должны быть описаны как пара- метры-значения; + параметры, в которые записываются результаты работы процедуры, описываются как параметры- переменные; + если формальный параметр — это параметр-пере- менная, соответствующий фактический параметр должен быть переменной. Для параметра-значения фактическим параметром может быть выражение, в том числе и константа; + количество фактических и формальных парамет- ров должно совпадать. Соответствие между ними устанавливается просто — первый связывается с первым, второй со вторым и т. д.; + соответствующие формальные и фактические па- раметры должны иметь один тип;
140 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули + в заголовке процедуры типы формальных пара- метров задаются только именем типа. Так, если процедура выводит массив типа аг (type аг = array[l..N] of Integer), то ее заголовок должен быть следующим: procedure print(var а : аг); Функции и процедуры имеют собственные разде- лы описаний. Все переменные (и прочие объекты, ис- пользуемые в программе), описанные в подпрограм- ме, являются локальными и действуют только внутри этой подпрограммы. Никакой связи между ними и объектами (например, переменными) вызывающей программы, имеющими (возможно, случайно) такие же имена (идентификаторы), нет. Они полностью не- зависимы. Пусть, например, в процедуре say_bye_bye описана переменная t и такая же переменная описана в разделе описаний основной программы. В этом слу- чае присваивание в процедуре say bye bye перемен- ной t нового значения не изменит значение перемен- ной t в основной программе. Если переменная описана в основной программе, но в подпрограмме нет описания переменной с таким же именем, ее тоже можно использовать. Она называ- ется глобальной потому, что имеет одно и то же зна- чение и в основной программе, и в подпрограмме. Глобальная переменная может определяться и в под- программе, содержащей описание других подпрограмм. В этом случае она будет глобальной по отношению к той подпрограмме, где она описана и подпрограммам, которые описаны здесь же. Область действия описа- ния конкретной переменной называется еще ее обла- стью видимости. Если в подпрограмме описаны другие процедуры или функции, область видимости описанных в ней
Процедуры 141 переменных распространяется на вложенные подпро- граммы, если в них не описаны переменные с такими же именами. Биквадратное уравнение. Напишем программу решения биквадратного уравнения: ox4 + ftx2 + c = 0. Известно, что для решения биквадратного уравне- ния достаточно заменить х2 на у и решить квадратное уравнение относительно у. ay2 + b у + с = О, d = b2 — 4 а с, если d >= 0, то Затем в зависимости от того, вещественны ли кор- ни у\ и г/2 и каковы их знаки, необходимо решить уравнение: л2 = г/i, получив Xi 2 — \ если г/i >= 0 и уравнение: л2 = г/2, получив х3 4 = ±у/у2, если у2 >= 0. В программе решения биквадратного уравнения используется процедура решения квадратного урав- нения: procedure kvur(var yl. у2 : Real; var flag : Boolean); в ней yl. у 2 и flag являются результатом работы процедуры, поэтому они описаны как параметры-пе- ременные. Переменные а. b. с будут глобальными переменными для процедуры kvur (так как мы реша- ем квадратное уравнение один раз).
142 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули program bikvur; var a, b, с : Real; {Глобальные переменные} yl, у 2 : Real; flag : Boolean; procedure kvur(var yl, y2 : Real; var flag : Boolean); var d : Real; {Дискриминант - локальная переменная} begi n d := sqr(b) - 4 * a * с; {Глобальные переменные a, b и с известны процедуре} if d >= 0 then begi n flag := true; yl := (-b + sqrt(d)) / 2 / a; y2 := (-b - sqrt(d)) / 2 / a; end else flag := false; end; {kvur} begi n Write(’Введите значения коэффициентов a, b, c : ’) ; Readl_n(a, b, c) ; kvur(yl, y2, flag); if flag then begin if yl >= 0 then WriteLn(’ xl= sqrt(yl):10:5. ’ x2= -sqrt(y1):10:5) else WriteLn(’Вещественных корней xl и x2 нет’) ; if y2 >= 0 then WriteLn(’ x3= sqrt(y2):10:5, ’ x4= ’, -sqrt(y2):10:5)
Процедуры 143 else WriteLn(’Вещественных корней хЗ и х4 нет ’ ) end else WriteLn(’Вещественных корней нет’); end. Формальные и фактические параметры имеют раз- ные области действия, при этом они могут иметь оди- наковые имена: формальные — yl, у2, flag — и фактические -»yl, у2, flag. Обратите особое вни- мание на случай d = 0. Здесь может быть d = —0.0, и тогда с извлечением квадратного корня в программе будут проблемы. «Не думай о секундах свысока...» В следующей программе используется несколько процедур. Задача состоит в том, чтобы вычислить суммарное количест- во секунд, соответствующее заданному числу часов, минут и секунд, и, наоборот, определить, сколько ча- сов, минут и секунд, содержится в заданном числе се- кунд. Алгоритм читатель без труда составит сам, а вот программа: program timeconversion; var choice: Integer; procedure Menu; begin WriteLn('l. Преобразовать часы, минуты и секунды в секунды'); WriteLn('2. Преобразовать секунды в часы, минуты и секунды'); WriteLn('3. Завершить работу'); Wri teLn; WriteLn('Введите номер (1-3)'); end; procedure seconds_to_time;
144 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули var total_seconds: Longlnt; hours, minutes, seconds: Longlnt; temp: Longlnt; begi n WriteLn('Введите суммарное количество секунд:’); ReadLn(total_seconds); WriteLn; temp := totalseconds div 66; seconds := total_seconds mod 60; hours := temp div 60; minutes := temp mod 60; Wr i teLn; WriteLn(totalseconds, ' секунд - это'); Wri teLn; WriteLn(hours, ' часов, ', minutes, ' минут, ', seconds, ' секунд'); Wri teLn; WriteLn('Для продолжения работы нажмите <Enter>'); ReadLn; end; procedure time_to_seconds; var totalseconds: Longlnt; hours, minutes, seconds: Longlnt; begin WriteLn('Введите часы: '); ReadLn(hours); Wri teLn; WriteLn('Введите минуты: '); ReadLn(minutes) ; Wri teLn;
Процедуры 145 WriteLn('[ зедите секунды: '); ReadLn(seconds); Wri teLn; totalseconds := hours * 3600 + minutes * 60 + seconds; Wri teLn; WriteLn(hours, ' часов, ' , minutes, минут, 1 , seconds, ' секунд это ', totalseconds, 1 секунд'); Wri teLn; WriteLn('4nfl продолжения работы нажмите <Enter>'); ReadLn; end; begi n choice := 0; while choice <> 3 do begi n Menu; ReadLn(choice); case choice of 1 : time_to_seconds ; 2 : secondstotime; end; end; end. В программе используется текстовое меню с воз- можностью выбора трех вариантов работы. Можно считать это элементом «дружественного интерфей- са». Нужный вариант задается пользователем путем ввода с клавиатуры соответствующего целого значе- ния. Поскольку число вариантов — три, условный оператор if... then... else... здесь не подходит, поэто- му в программе используется оператор выбора case.
146 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули В данной программе используются две процеду ры — 1ime_to_seconds и seconds_to_time, выпол няющие преобразования заданного времени в секун- ды и наоборот. «Представьте себе...». В программе Strings_to_ Chars_Demo используются процедуры, а также появ- ляется новый тип данных — строковый. Значением переменной строкового типа (String) является стро- ка текста. Строка должна располагаться между апост- рофами. В программе имеется массив строковых значений, который содержит первые два куплета зна- менитой песни Джона Леннона «Imagine». Обратите внимание на то, что в строковых значениях апостроф обязательно дублируется, иначе он будет восприни- маться как ограничитель строки. Процедура Strings_ to Chars имеет два параметра. Первый — массив строк, а второй — массив символов, содержащихся в строках. Заполнение символьного массива Chars происходит в двойном цикле со счетчиком. Элемент строкового массива Strings в правой части операто- ра присваивания во внутреннем цикле имеет два ин- декса. Первый из них является индексом массива, а второй обеспечивает доступ к элементу строки с нуж- ным номером. Элементы символьного массива выводятся на эк- ран, а цвет каждого символа выбирается случайным образом (процедура TextColor). После нажатия на клавишу Enter вывод повторяется, причем символы выводятся в случайные позиции экрана (процедуры GotoXY и Random). Вывод повторяется циклически, с задержкой (встроенная процедура Delay), до тех пор, пока не будет нажата произвольная клавиша (функ- ция Keypressed модуля Crt — о модулях мы погово- рим в заключительной части этой главы). После на- жатия произвольной клавиши вновь выводится нор-
Процедуры 147 иальный, упорядоченный текст, и работа программы заканчивается: program St гings_to_Chars_Demo; uses Crt; const nstrings = 100; nchars = 80; type Array_of_Str1ngs = Array[0..nstrings] of String; Array_of_Chars = Ar ray[0..nchars * nstrings] of Char; var ArSt : Array_of_Strings; ArCh : Array_of_Chars; i : Wo r d; procedure Strings_to_Chars (var Strings : ArrayofStrings; var Chars : Array_of_Chars); var i, j : Byte; begi n for i :=1 to nstrings do begin for j := 1 to nchars do begin Chars[nchars * (i - 1) + j] := Stringsfi] [j] ; end; end; end; begi n ClrScr;
148 Глава 5 Привет, играем в «Puzzle». Подпрограммы и модули Randomi ze; ArSt[l] := 'Imagine there' 's no heaven'; ArSt[2] := 'It''s easy if you try'; ArSt[3] := 'No hell below us'; ArSt[4] := 'Above us only sky'; ArSt[5] := 'Imagine all the people'; ArSt[6] := 'Living for today'; ArSt[7] := 'Imagine there''s no countries'; ArSt[8] := 'It isn''t hard to do'; ArSt[9] := 'Nothing to kill or die for'; ArSt[10] := 'And no religion too'; ArSt[ll] := 'Imagine all the people'; ArSt[12] := 'Living life in peace.'; StringstoChars(ArSt, ArCh); i : = 1; repeat TextColor(Random(15) + 1); Write(ArCh[i]); Inc (i); until ArCh[i]='.'; ReadLn; while not Keypressed do begi n Delay(300); ClrScr; i := 1; repeat TextColor(Random(15) + 1); GotoXY(Random(40), Random(20)); Wri te(ArCh[i]); Inc(i) ; unt i1 ArCh[ i ] = ' . ' ; end ; ReadLn; ClrScr;
Процедуры 149 i := 1; repeat TextColor(Random(15) + 1); Wr ite(ArCh[i]); Inc(i); unti1 ArCh[i]='.'; Wr iteLn; WriteLn('Ana завершения нажмите <Enter>:'); ReadLn; end. Здравствуй, Цезарь. В 45 году до нашей эры дек- ретом римского императора Гая Юлия Цезаря в Риме был введен календарь, получивший название юлиан- ского. В этом календаре три года подряд содержали по 365 дней, а четвертый (високосный) содержал на один день больше. Дополнительный день включался в год, который делился на четыре. Но этот календарь не давал полного соответствия с движением Солнца и в 1582 году календарная дата отставала о г истинной на 10 дней. И тогда Папа Григорий XIII решил, чтобы ликвидировать накопившееся отставание, произвести реформу календаря, согласно которой: + за 4 октября последует 15 октября; + введение високосных годов позволит избежать от- ставания в будущем. В дальнейшем юлианским календарем продолжала пользоваться и Россия, где он получил название «ста- рого стиля», а григорианский календарь использовал- ся во многих европейских странах. В настоящее вре- мя юлианской системой летоисчисления пользуются астрономы, которые решили вести отсчет времени от полудня (на Гринвичском меридиане) 1 января 4713 года до нашей эры. Каждому дню в этой системе при-
150 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули своей свой порядковый номер, который можно счи- тать «юлианской датой». Процедура DATA определяет юлианскую дату, если задана дата по григорианскому календарю, или сколь- ко дней прошло с начала новой эры до названной даты: day (дни), month (месяцы), year (годы): procedure DATA(day, month, year : Longlnt; var date : Longlnt); var MOIS : array[1..12] of Longlnt; begi n MOIS[1] := 0; M0IS[2] := 31; M0IS[3] := 59; M0IS[4] := 90; M0IS[5] : = 120; M0IS[6] := 151; M0IS[7] : = 181; M0IS[8] := 212; M0IS[9] := 243; MOIS[10] := 273; MOIS[11] := 304; M0IS[12] := 334; {Массив MOIS определяет количество дней с начала года по 1-е число каждого месяца, не включая это число} date := day + MOIS[month] + 365 * year + 1721060 + year div 4 - year div 100 + year div 400; if ((year mod 4=0) and (year mod 100 <> 0) or (year mod 400 =0)) and (month <= 3) then date := date - 1 end; {DATA} Вычисляется юлианская дата так. День начала на- шей эры определяется как 1 721 060-й день юлиан- ского календаря. Добавим к этому числу количество прошедших лет, полагая, что в каждом году по 365 дней, затем приплюсуем количество дней с начала на- званного года до указанного дня, да еще плюс один
Подпрограммы-функции 151 день в каждом четвертом году, < минус один день в каждом сотом, | Ш и плюс один день в каждом четы- / и рехсотом. Учтем также, что висо- Д. В косный год добавляет 1 день толь- ко после февраля. Фууу... А какая польза от таких слож- ных расчетов, спросите вы? С по- .—-—-тц-----ь мощью юлианской системы удоб- I5T6I но определять, например, сколько я шн '*'* ш времени прошло от одного собы- тия до другого. Так, на вопрос, сколько дней прошло со дня убийства Генриха IV 14 мая 1610 г.) до дня вступления Наполеона в Иену 3 октября 1806 г.), отвечает фрагмент программы: DATAU3, 10, 1806, dl); DATA(14, 5, 1610, d2); Wri teLn(dl - d2) ; Напишите программу, которая использует проце- дуру DATA, и найдите ответ на этот вопрос. Подпрограммы-функции Функции уже нам немного знакомы — ранее мы поль- зовались встроенными функциями Паскаля Sin(x), Sqr(x), Sqrt(x), Random и некоторыми другими. Кроме них, программист может пользоваться свои- ми функциями, то есть подпрограммами, оформлен- ными как функции. Описание этих функций должно быть помещено в тексте программы после раздела описания переменных. Подпрограммы-функции не- много похожи на подпрограммы-процедуры, но отли- чаются от них тем, что:
152 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули + функция возвращает при обращении к ней од- но-единственное значение, и это значение — про- стого типа (массив или множество не могут быть значениями функции); + вызов подпрограммы-функции производится про- сто путем указания ее имени в составе какого-либо выражения или в списке аргументов подпрограм- мы. Это может быть арифметическое выражение, если функция арифметическая. Имя функции в вызывающей программе может появиться только в правой части оператора присваивания. Описание функции, создаваемой программистом, выглядит так: function имя_функции(параметр_1 : тип_1; параметр_2 : тип_2; . . . параметр_И : тип_М) : тип_функции; раздел описаний функции begi п раздел операторов функции имя_функции := выражение; end; Слово function является зарезервированным сло- вом языка Паскаль. Имя_функции выбирается про- граммистом в соответствии с обычными правилами и используется для вызова функции в тексте програм- мы. Параметры — это список переменных с указанием их типов. В отличие от стандартных функций пара- метров может быть сколько угодно. Тип_функции — это тип значения, которое вычисляет функция. Он обязан быть простым. Вслед за заголовком располагается тело функции, которое устроено аналогично программе или проце- дуре. Есть важное требование — в теле функции дол- жен присутствовать оператор присваивания:
Подпрограммы-функции 153 имя_функции : = выражение; Понятно, что возможности функций ограниченны. Ведь иногда результат работы подпрограммы — это целый набор значений, а иногда результат ее выпол- нения не сводится к вычислениям. Кто больше (вариант с использованием функ- ции)? Задача поиска большего из четырех чисел мо- жет быть решена следующим образом: program largest?; var a, b, с, d : Real; function max2(x, у : Real) : Real; begin if x > у then max2 := x else max? : = y; end; begin Write(’Введите четыре числа:’); ReadLn(a, b, c, d); WriteLn(’Большее из a:10:5, b:10:5, c:10:5, d:10:5. ’ = max2(max2(a, b), max2(c, d)):10:5) end. Площадь четырехугольника. Требуется найти пло- щадь выпуклого четырехугольника, заданного коор- динатами своих вершин (см. рис. 5.1). Для того чтобы вычислить площадь четырехуголь- ника, заметим, что она складывается из площадей двух треугольников Si и 3J. Площадь каждого тре- угольника можно найти по формуле Герона, согласно которой: 5 = Jp(p - а)(р ~ ЬХР ~ с), где а, Ь, с — длины сторон, а р = (а + b + с)/2 — полу- периметр треугольника. Длина каждой стороны тре-
154 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули угольника вычисляется по формуле длины отрезка прямой между точками (хь у С) и (х2, у 2}'. 1 = ~х2У +(#! -у2У. Рис. 5.1. Выпуклый четырехугольник Вычисление площади треугольника поручим про- цедуре space, а вычисление длины стороны по коор- динатам ее граничных точек — функции length: program area; const n = 4; {Количество вершин} var xl, yl, x2, y2, x3, y3 : Real; {Координаты вершин} 1 : Integer; su, pl : Real; {su-площадь фигуры, pl-площадь треугольника} function length(xl, yl, x2, y2 : Real) : Real; begi n length := Sqrt(sqr(xl - x2) + Sqr(yl - y2)); end; {length}
Подпрограммы-функции 155 procedure space(xl. yl, x2, y2, x3, y3 : Real; var pl Real); var a, b, c, p : Real; begi n a := Length(xl, yl, x2, y2); b := Length(x2, y2, x3, y3); c := Length(xl, yl, x3, y3); {Процедура вычисления площади вызывает функцию вычисления длины} р := (а+Ь+с) /2; pl := Sqrt(p * (р - а) * (р - b) * (Р - с)); end; {space} begi п Write(*Введите координаты 1-й и 2-й вершин ’); ReadLn(xl, yl, х2, уЗ) ; i := 2 ; {i - вспомогательная переменная, которая считает вершины п-угольника} su := 0; repeat i := i +1; Write(’Введите координаты ’, i, ’-й вершины’); ReadLn(x3, y3); space(xl, yl, x2, y2, x3, y3, pl); su := su + pl; x2 := x3; y2 : = y3; until i = n; WriteLn(’Площадь =’, su:10:5); end .
156 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули В этой программе можно изменить п и получить площадь и-угольника, разумеется, только если он вы- пуклый. Кроме того, используется принцип опере- жающего описания, согласно которому какое-либо имя может быть использовано лишь после того, как оно описано. Процедура space, вычисляющая пло- щадь, описана после функции length, вычисляющей длину, которую вызывает процедура space. Сначала описывается вызываемая подпрограмма, потом вызы- вающая. Программу вычисления площади //-угольника мож- но сделать более короткой, поместив координаты вер- шин в массивы: program агеа2; const п = 4; var х, у : array[l..n] of Real; 1 : Word; su, pl : Real; function length(nl, n2 : Word) : Real; {Word-целый тип, диапазон значений от 0 до 65585} begi n length := Sqrt(sqr(x[nl] - x[n2]) + sqr(y[nl] - y[n2])) end; {length} procedure space(nl, n2, n3 : Word; var pl : Real); var a, b, c, p : Real; begin a := length(nl, n2); b := length(n2, n3);
Рекурсивные процедуры 157 с : = length(nl, пЗ); р :р (а+Ь+с) / 2 ; pl := Sqrt(p ♦ (р - а) * (р - b) * (р - с)) ; end;{space} begi n Wr ite(’Введите координаты 1-й и 2-й вершин ’); ReadLn(x[l] , у[1] , х[2] , у [2]) ; i := 2 , su := 0; repeat i := 1 + 1; {Подсчет вершин} Write(’Введите координаты i, ’-й вершины’); ReadLn(х[1] , у [ 1]); space(1, i - 1, i, pl); su : = su + pl until i = n; WriteLn(’Площадь - su:lG:5); end. Рекурсивные процедуры Рекурсивным называется объект, который определя- ется с помощью самого себя. Рекурсивные определе- ния наиболее широко используются в математике. В качестве примера можно привести определение степени с целочисленным показателем: а ak 1 а k 1, 1 А+1 — • а а если k > О, если k = О, если k < 0.
158 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули Достоинством рекурсивных определений является то, что они позволяют с помощью ко- нечных формул определять бес- конечное множество объектов. Рекурсия широко применяется в программировании, однако пре- жде, чем начать работать с ре- курсивными процедурами, вве- дем несколько определений. Процедуру или функцию, которая вызвана или выполняется, будем называть активной. До сих пор мы считали, что, если процедура или функция вызва- на, она выполняется один раз от начала и до конца, а затем управление передается в точку вызова. Однако процедура или функция могут вызываться из самой себя, при этом возможны такие ситуации: Т некоторая процедура или функция вызвана (акти- визирована), ее выполнение еще не завершилось, а в ней встречается обращение к этой же процедуре (или функции); + процедура или функция активизируется вторично, в процессе второго выполнения (первое еще не окончено!) снова встречается вызов этой процеду- ры (функции) и т. д. Такой порядок выполнения процедуры называется рекурсией, а сама процедура — рекурсивной. Если не принять специальных мер, рекурсия становится бес- конечной. Чтобы процесс рекурсии когда-нибудь за- вершился, необходимо рекурсивный вызов поместить внутри условного оператора, когда одна ветвь этого оператора содержит рекурсивный вызов, а другая — нет. Переход на ветвь условного оператора, не содер- жащую рекурсивный вызов, но обеспечивающую за-
Рекурсивные процедуры 159 вершение работы рекурсивной подпрограммы, дол- жен произойти через конечный промежуток времени. Громкое N. Восклицательный знак в конце пред- ложения указывает на то, что текст должен произно- ситься громко. Человек, не знакомый с математикой, удивится, зачем в некоторых математических форму- лах используется тот же самый восклицательный знак — неужели для того, чтобы формула произноси- лась громко? Ну а человек, осиливший премудрости школьной математики, знает, что в этом случае воскли- цательный знак обозначает математическую функцию — факториал целого положительного числа. Определение этой функции может быть записано рекурсивно: п - (н — 1), 1, п > 1, п — 0,1. Редкая книга по программированию обходится без программы вычисления факториала. Мы тоже отда- дим должное традиции. При определении п\ через (и — 1)! идет последова- тельное уточнение алгоритма сверху вниз: 5! = 4! * 5 4! = 3! * 4 3! = 2! * 3 2! = 1! * 2 1! = 1, а потом алгоритм «собирается» снизу вверх. Вот про- грамма вычисления факториала: program factorial: var n : Integer: function fact(n : Integer) : Word; begi n if n = 0 then fact := 1
160 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули else fact := n * fact(n - 1); end; {fact} begi n WriteLn(’Введите число, факториал которого вы хотите получить ’); ReadLn (л); if л < 0 then WriteLn(’Jlnfl отрицательного числа факториал не определен’) else Wri te'_n (’ Факториал п, ’равен: ’, fact(п) ) ; end . Отметим, что такую задачу эффективнее решить с помощью цикла: х = 1; for i := 1 to n do x := x * i; fact := x ; В результате использования в рекурсивной под программе нескольких копий переменных для их хра нения требуется больше оперативной памяти компь ютера. Целая степень числа. А вот рекурсивная функцш вычисления целой степени вещественного числа с. (У = а*). function power(a : Real; х : Integer) : Real; begi n if x = 0 then step := 1 { конечный уровень рекурсии } else if x > 0 then step := step(a, x - 1) * a else
Рекурсивные процедуры 161 step : = step(a, х + 1) /а end; Как сложить квадрат. Решим задачу о вычисле- нии квадрата натурального числа т, используя толь- ко операции сложения и вычитания. Для этого вспом- ним формулу квадрата суммы двух чисел и запишем ее для т + 1: (т/г + I)2 = тл2 + 2 * тп + 1 = т2 + т + т + 1. Перепишем эту формулу, взяв вместо т выраже- ние т — 1: 2 f(m — I)2 + т + т — 1, т > 1 т = \ ' 1, т = 1 X. Видим, что в нашей формуле есть рекурсия, есть операции сложения и вычитания, но нет операций умножения и деления. Функция вычисления квадра- та натурального числа может выглядеть следующим образом: function square(m : Word) : Word; begi n if m = 1 then square := 1 else square := square(m -l)+m+m-l end; В принципе, любую задачу, которая поддается ре- курсивному решению, можно решить с использова- нием циклов, но в некоторых случаях использование циклов усложняет понимание программы. Задача о «Ханойских башнях». Из маленькой итальянской деревушки перенесемся в экзотическую страну. В большом храме Бенареса бронзовая плита поддерживает 3 алмазных стержня, на один из кото- рых Бог нанизал во время сотворения мира 64 золо- тых диска, образующих пирамиду. С тех пор монахи 6 Зак. №933
162 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули каждую секунду перекладывают по одному диску со- гласно правилам, описанным далее: + за один раз мсжно перекладывать только один диск; + нельзя класть диск на диск, меньший по размерам; + можно пользоваться только одним резервным стержнем. - Монахи считают, что конец мира наступит, когда все 64 диска будут перемещены, на что потребуется чуть больше 58 миллиардов лет Мы с вами, уважаемый читатель, как люди ученые, прекрасно понимаем, что никакой связи между кон- цом мира и золотыми дисками нет. Давайте считать, что если мы сможем переместить все диски с одного стержня на другой, это будет означать, что мы по- стигли тайну рекурсии. Будем рассуждать следующим образом. Мы ре- шим поставленную задачу для п дисков, если сумеем дважды решить ее для (п — 1)-го диска. Вначале пере- несем верхушку пирамиды, состоящую из (и — 1)-го диска, с первого стержня на второй, затем перенесем один диск с первого стержня на третий, а потом пере- несем верхушку пирамиды, состоящую из (п — 1)-го диска, со второго стержня на третий (сумели перене- сти один раз, сумеем и второй!). Далее повторим ал- горитм переноса, но уже для (лг — 1)-го диска, затем для (лг — 2)-го диска и так далее, пока не опустимся до одно] о диска. Уж его-то мы перенести сумеем! Алгоритм решения задачи о ханойских башнях представлен на рис. 5.2. На этом рисунке: + X — исходное состояние; + Y — промежуточное состояние; + Z — конечное состояние;
Модули 163 4- 1-й шаг — перенести (и — 1) диск со стержня X на стержень Y, используя стержень Z как вспомога- тельный; 4- 2-й шаг — перенести один нижний диск со стерж- ня X на стержень Z; 4- 3-й шаг — перенести (л — 1) диск со стержня Y на стержень Z, используя свободный стержень X. 2 шаг Рис.5.2. Алгоритм решения задачи о ханойских башнях procedure Hanoi(n : Word; x, у, z : Char); begi n if n = 1 then WriteLn(’Переложить x, ’ на ’, z) else begi n Hanoi(n - 1, x, z, у); {1 шаг} Wri tel_n( ’ Переложить x, ’ на z) ; {2 шаг} Hanoi(n - 1, y, x, z) {3 шаг} end; end; {Hanoi} Модули Кроме процедур и функций в Паскале используются модули. Библиотечный модуль находится в отдель-
1 64 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули ном файле и содержит описания, процедуры и функ- ции, которые могут применяться в различных про- граммах. Подпрограмму включают в состав модуля в том случае, когда она реализует действие, которое приходится выполнять достаточно часто. Такую под- программу можно написать и отладить один раз, а ис- пользовать многократно. Файл, содержащий модуль, обязан иметь имя, совпадающее с именем модуля. Разберем в качестве примера модуль с описаниями гиперболических функций. Их определение: 4- гиперболический синус: рх — р х Sinh(x) =-------; + гиперболический косинус: Cosh(x) = е ; + гиперболический тангенс Tanh(r) = —--. ех + е х Гиперболических функций нет в числе встроен- ных функций языка Паскаль, но эти функции могут встретиться в различных «серьезных» вычислитель- ных задачах, и поэтому имеет смысл включить их в состав библиотечного модуля. Доступ из программы к функциям этого модуля обеспечивает оператор ис- пользования uses, который размещается сразу же по- сле заголовка программы и в котором указывается имя модуля (или нескольких модулей). Сам модуль выглядит следующим образом: {$N+} unit hyp_fun; interface
Модули 165 function sinh(x: Extended) : Extended; function cosh(x: Extended) : Extended; function tanh(x; Extended) : Extended; implementation var t: Extended; function sinh(x; Extended); Extended; begi n t ; = Exp(x); sinh := 0.5*(t - 1.0/t) ; end; function cosh(x: Extended); Extended; begi n t := Exp(x); cosh ;= 0.5*(t + 1.0/t); end; function tanh(x; Extended); Extended; begi n t ;= Exp(2. 0*x) ; tanh := (t - 1.0) / (t + 1.0); end; end. В самой первой строке находится директива ком- пилятору. Директива начинается со знака доллара и заключается в фигурные скобки, причем между от- крывающей фигурной скобкой и знаком доллара не должно быть пробела. Директива позволяет изменить работу транслятора (компилятора). В данном случае директива указывает на необходимость использова- ния программой математического сопроцессора — специального устройства, которое встраивается в про- цессор компьютера и выполняет арифметические опе- рации. Когда-то математические сопроцессоры были
166 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули не на всех компьютерах, и приходилось специально указывать транслятору на использование сопроцессо- ра. Каждый модуль начинается с зарезервированного слова unit и заканчивается словом end, за которым следует точка. Для этого end не требуется соответст- вующего слова begi п, хотя можно и поставить его не- посредственно перед end. Каждый модуль имеет части {секции), озаглавлен- ные специальными зарезервированными словами. Сек- ция interface (она называется интерфейсной секци- ей) содержит описания констант, типов, переменных и процедур, доступных из вызывающей программы или модуля. Секция implementation {секция реали- зации} содержит исходный код подпрограмм. Она мо- жет также содержать локальные по отношению к мо- дулю описания, такие как: var t: Real; из нашего примера. В модуле hyp_fun мы встречаем новый тип пере- менных Extended. Это вещественный тип, диапазон допустимых значений которого гораздо больше, чем у типа Real. Модуль обязательно надо оттранслировать, при этом появляется файл, имя которо) о совпадает с име- нем исходного файла, а расширение имеет вид .TPU. Выполнить оттранслированный модуль нельзя. В сдедующей программе используется только что рассмотренный модуль hypfun. Проверьте резуль- тат ее работы с помощью калькулятора, имеющего ги- перболические функции: {$N+} program testhyperbolicfuns; uses hyp_fun;
Итог 167 begi n WriteLn('sinh( 0.5) = ', sinh(0.5)); Wri tel_n( 1 cosh(-0.5) =', cosh(-0.5)); Wri tel_n(' tanh( 1.5) =', tanh(1.5)); Write('Нажмите <Enter>: ’); ReadLn; end. Итог Итак, мы познакомились с тем, как из сравнительно небольших фрагментов-подпрограмм складывается це- лая картина, то есть программа. Наши первые про- граммы содержали всего одну или две подпрограммы, и в этом смысле они были простыми «головоломка- ми». Большие программы могут содержать сотни процедур и функций, без которых разработка таких программ была бы просто невозможной. В заключе- ние предлагаем решить задачи. Задача 1. Определите результат вычисления следую- щей функции: function unknownl : Real; const eps = 1.0e-16; var x, у : Real; begi n x := 1.0; repeat У : = x; x := 1.0/(1.0 + y); until Abs(x - y) < eps; unknownl := x; end;
168 Глава 5 Привал, играем в «Puzzle». Подпрограммы и модули Задача 2. Определите результат работы следующей процедуры: procedure unknown3(var х, у : Real); const eps = 1.0е-16; var z. w : real; begi n x := 0.0; у := 0.0; repeat z : = x; w : = у ; x : = Sqrt(7.0 - y); у :- Sqrt(7.0 + x); until (Abs(x - z) < eps) and (Abs(y - w) < eps); Задача 3. Напишите функцию, которая для любого целого аргумента возвращает количество цифр в его записи. Задача 4. Напишите процедуру, которая для любого целого аргумента возвращает массив, содержащий цифры в записи этого аргумента. Задача 5. Проверьте гипотезу Гольдбаха, согласно которой каждое четное число, большее 2, представля- ется в виде суммы двух простых чисел. Используйте процедуру prost: procedure prost(a : Integer; var p : Byte), {Byte - целое значение от 0 до 255} var 1, i ; Integer; begi n 1 := Round(Sqrt(a)) ; p := 1; i : = 2; while (i <= 1) and (p = 1) do
Итог 169 if a mod i = 0 then p := 0; end; {prost} Задача 6. Задан массив из и-символов (type ar = array[l..n] of Char;). Написать процедуру, уда- ляющую из массива ^-элементов, начиная с р-го но- мера. Оставшееся после удаления место заполнить символами «*». Задача 7. Опишите массив размером 25x80, в кото- ром хранится образ экрана, и определите процедуры построения: + горизонтальной линии; + прямой линии; + окружности; 4- прямоугольника. Задача 8. Напишите функцию, вычисляющую целую степень числа а\ п > 0, п - 0, п < 0. Пользуясь этой функцией, посчитайте------. (а + Ь)п Задача 9. Запрограммируйте рекурсивный поиск наи- меньшего элемента массива. Задача 10. Напишите программу двоичного поиска в рекурсивной форме. Задача 11. Напишите программу, определяющую день недели по заданной дате.
ГЛАВА б В диковинном саду. Типы переменных и множества
В сказке мадам д'Олнуа «Королевский баран» млад- шая дочь короля, Ванда, попавшая в немилость к сво- ему отцу, оказывается в царстве бараньего короля. «... Оставайся с нами, — сочувственно произнес ба- ран. Слуги-бараны внесли громадную тыкву. Она была выдолблена изнутри и отделана белоснежным барха- том. Бараний король взял Ванду за руку и помог вой- ти ей внутрь. Затем слуги-бараны подняли тыкву и понесли ее к высокому холму, где находилась пещера. Бараний король открыл дверь ключом. — Не пугайся, принцесса, и следуй за мной. Ступенька за ступенькой спускались они по вин- товой лестнице в глубь пещеры, и вдруг перед ними открылся волшебный сад, полный цветов и сверкаю- щих фонтанов. Вместо воды в них были заморские напитки. На деревьях висели диковинные фрукты, и кроме того, на ветках раскачивалась аппетитная вет- чина, свежезажаренные цыплята, ноздреватый сыр и благоухающие раки. Лепестки цветов были сделаны из шоколада и карамели. Вся еда была как будто спе- циально приготовлена к их приезду. Вдоль аллеи стояли золотые, усыпанные драгоцен- ными камнями домики. Выбрав самый красивый, ко- роль-баран сказал: — Здесь ты можешь жить спокойно. Любое твое желание будет тотчас же выполняться...» Приготовься, читатель, сейчас мы тоже попадем в диковинный сад, но растут в нем не ветчина и не уди- вительные фрукты, а... различные типы данных, кото- рые используются в Паскале.
172 Глава 6 В диковинном саду. Типы переменных и множества Предопределенные типы переменных Кроме уже известных нам типов в Паскале есть много других. Этот диковинный сад — довольно большой! Все типы принято делить на группы. Типы, принадлежащие одной группе, имеют определен- ное сходство. Прежде всего выделяют простые и структурные типы. Простые типы в свою очередь подразделяют на порядковые и вещественные типы В табл. 6.1 приведен список предопределенных типов Паскаля. Предопределенные типы «встроены» в Паскаль в отличие от типов, задаваемых программи- стом. Таблица 6.1. Предопределенные типы языка Паскаль Группа Подгруппа Название Идентификатор Простой Поряд- ковый Короткий Shortlnt целый Байтовый Byte Слово Word Целый Integer Длинный Longlnt целый Символьный Char Булев Boolean Вещест- венный Вещественный Real С одинарной Single точностью С двойной Double точностью
Предопределенные типы переменных 173 Группа Подгруппа Название Идентификатор С повышенной точностью Extended Сложный Comp Строковый String Структурный Массив Array Множество Set Файл File Запись Record Ссылочный Pointer Процедурный Процедура Procedure Функция Functi on Объектный Obj ect Порядковые типы называются так потому, что их допустимые значения представляют собой последо- вательность, состоящую из конечного числа элемен- тов, расположенных в определенном порядке. В этой последовательности есть первый и последний эле- менты. Каждый элемент порядкового типа имеет предшествующий ему и следующий за ним элемен- ты. Так, например, у целого значения 2000 есть пред- шественник (значение 1999) и преемник (значение 2001). Исключением являются первый (у него нет предшественника) и последний (нет преемника) элементы. Элементы порядкового типа можно про- нумеровать, расположив их в определенном поряд- ке, например по возрастанию. В табл. 6.2 приводятся диапазоны допустимых значений порядковых типов Паскаля.
174 Глава 6 В диковинном саду. Типы переменных и множества Таблица 6.2. Порядковые типы языка Паскаль Идентификатор Описание типа Множество допусти- мых значений Short!nt 8-битный целый со знаком -128..127 Integer 16-битный целый со знаком -32768..32767 Longi nt 32-битный целый со знаком -2147483648.. 2147483647 Byte 8-битный целый без знака 0..255 Word 16-битный целый без знака 0..65535 Boolean Логический False, True Char Символьный Символы из расширенного набора символов кода ASCII Вещественные типы представляют вещественные числа (числа, имеющие как целую, так и дробную части). В Паскале имеется пять видов вещественных типов. Вещественные типы различаются диапазоном и точностью связанных с ними значений. Эти типы перечислены в табл. 6.3. Таблица 6.3. Вещественные типы языка Паскаль Идентификатор Описание типа Диапазон Real 6-байтовый вещественный тип, 11 — 12 значащих цифр -1.7x1038 .. —2.9х10-39, 2.9x10 39 .. 1.7х1038
Предопределенные типы переменных 175 Идентификатор Описание типа Диапазон Single 4-байтовый вещественный тип с одинарной точностью, 7—8 значащих цифр —3.4х1038 .. — 1.5x10-45, 1.5x10-45 .. 3.4x1038 Double 8-байтовый вещественный тип с двойной точностью, 15—16 значащих цифр -1.7x10308 .. —5.0х10~324, 5.0x10 324 .. 1.7x10308 Extended 10-байтовый вещественный тип с повышенной точностью, 19—20 значащих цифр -1.1Х104932 .. -1.9х10-4951, 1.9х10“4951 .. 1.1хЮ4932 Comp 8-байтовый сложный тип । j. 263 1 Сложный тип Comp может представлять только целочисленные значения в диапазоне от —263 + 1 до 263 — 1, что приблизительно составляет от —9.2x1018 до 9.2х1018. Символьный тип До сих пор мы имели дело преимущественно с числа- ми. И это понятно, ведь главным назначением компь- ютера является, прежде всего, умение считать (само слово computer переводится с английского языка на русский как «вычислитель»). Однако второй, пожа- луй, по трудоемкости операцией, которую доверили вычислительной машине, является обработка тек- стов. И современные машины едва ли не большую часть времени расходуют на обработку текстов. Этим объясняется введение в язык программирования Пас-
176 Глава 6 В диковинном саду. Типы переменных и множества каль специальных типов для работы с символами и фрагментами текста. Мы уже встречали эти типы — символьный и строковый, здесь же речь о них пойдет более подробно. Наряду с цифрами на клавиатуре компьютера име- ются буквы, знаки операций, знаки препинания и другие значки. Каждый из них является символом. В программах на Паскале можно использовать символьные константы и символьные переменные Символьные константы заключаются в простые ка- вычки: ’а’ , ’с’, , ’!’, ’1’ Напомним, что для символьных переменных име- ется тип Char (от английского слова character — «сим- вол»). Пример описания символьной переменной: var х : Char; Каждому символу отвечает специальное число код символа. Символы можно сравнивать с помощью операций сравнения = (равно, то есть совпадают ли символы) или <> (не равно, символы не совпадают). Их можно также сравнивать и по величине кодов с помощью операций: + < = — меньше или равно; 4- >= — больше или равно; + < — меньше; 4- & — больше. Из двух символов большим считается тот, код ко торого больше: ’А’ < ’В’ ’X’ < ’Y’ ’1’ < ’2’
Предопределенные типы переменных 1 77 Стандартная функция Chr(n) возвращает в про- грамму символ с кодом п, а функция Ord(s) возвра- щает код символа s. Отдельные символы в программе можно задавать их порядковыми номерами с предше- ствующим знаком #, например^ ’ а ’ равносильно #65; ’ 1 ’ равносильно #48. Существует функция UpCase, которая преобразует строчные буквы латинского алфавита в прописные, но не изменяет другие символы, например: UpCase('р') = 'Р' UpCase('B') = 'В' UpCase('+') = '+' Говорят, что допустимые значения символьного типа принадлежат расширенному набору символов кода ASCII. ASCII — это сокращение от American Standard Code for Information Interchange (Американ- ский стандартный код для обмена информацией). Со- гласно стандарту ASCII каждому символу и некото- рым операциям соответствует свой числовой код, принимающий значения от О до 127. Коды этих сим- волов приведены в табл. 6.4. Расширенная таблица ASCII состоит из двух частей. Первая, в которую вхо- дят символы с кодами 0—127, является универсаль- ной, а вторая (коды 128—255) предназначена для спе- циальных символов и букв национальных алфавитов (в том числе и русского). Таблица 6.4. Символы ASCII с кодами 0—127 Код Символ Код Символ Код Символ Код Символ 0 NUL 32 SP 64 @ 96 X 1 SOH 33 1 65 А 97 а 2 STX 34 »• 66 В 98 b
178 Глава 6 В диковинном саду. Типы переменных и множества Код Симьол Код Символ Код Символ Код Символ 3 ЕТХ 35 # 67 С 99 с 4 EOT 36 $ 68 D 100 d 3 enq 37 о/ /о 69 Е 101 е 6 АСК 38 & 70 F 102 f 7 BEL 39 • 71 G 103 g 8 BS 40 ( 72 Н 104 h 9 НТ 41 ) 73 I 105 I 10 LF 42 * 74 J 106 j И VT 43 + 75 К 107 k 12 FF 44 76 L 108 1 13 CR 45 - 77 М 109 m 14 SO 46 78 N 110 n 15 SI 47 / 79 О 111 о 16 DLE 48 0 80 Р 112 P 17 DC1 49 1 81 Q ИЗ q 18 DC2 50 2 82 R 114 г 19 DC3 51 3 83 S 115 s 20 DC4 52 4 84 Т 116 t 21 NAK 53 5 85 и 117 u 22 SYN 54 6 86 V 118 V 23 ETB 55 7 87 W 119 w 24 CAN 56 8 88 X 120 X 25 EM 57 9 89 Y 121 У 26 SUB 58 - 90 Z 122 z 27 ESC 59 91 [ 123 { 28 FS 60 < 92 \ 124 29 GS 61 = 93 ] 125 } 30 RS 23 > 94 126 31 us 63 ? 95 127 DEL
Предопределенные типы переменных 179 Первые позиции этой таблицы занимают управ- ляющие символы. Управляющие символы представля- ют собой команды, вывод которых на стандартное выходное устройство приводит к выполнению опре- деленных действий. Примером служит символ BEL, подающий звуковой сигнал. Таблицу кодов ASCII можно получить, используя такую программу: program ascii_table; var i , j, dec : Integer: begin for i := 0 to 15 do {Вывод в 16 строк} begi n dec := i; {Для первой половины кодовой таблицы или dec := 1 + 128 для следующих кодов} for j := 1 to 8 do {Вывод в 8 колонок} begi n if (dec <> 7) and (dec <> 8) and (dec <> 10) and (dec <> 13) then Write(dec:4, Chr(dec):l, Chr(179)) else Write(dec:4, Chr(179):2); dec := dec + 16 end; Wri teLn; {Переход к новой строке экрана} end end. В этой программе исключены коды, которые соот- ветствуют управляющим символам.
180 Глава 6 В диковинном саду. Типы переменных и множества Ввод символов с клавиатуры. Программа ascii_ table выводила символы на экран. Обратимся теперь к вводу символьных значений с клавиатуры. При на- жатии на символьную клавишу естественно ожидать увидеть на экране в месте расположения курсора тот символ, который был введен. Ниже приводится текст программы testread. Эта программа использует опе- ратор ReadLn для того, чтобы считать с клавиатуры один символ и вывести его в следующей строке В данном случае при вводе символа вы увидите его на экране еще до того, как будет нажата клавиша Enter. Нажмем клавишу а, а затем Enter, и программа сообщит, что она считала символ «а». program testread; var h : Char; begi n WriteLn('Введите символ: '); ReadLn(ch); WriteLn('Введен символ: ch); Write('Нажмите <Enter>: '): ReadLn; end. Можно ли прочитать значение нажатой клавиши так, чтобы она не отображалась при этом на экране? Да, можно — модуль Crt содержит функцию ReadKey, которая именно это и делает. При вводе символа эта функция не сдвигает курсор и поэтому дает возмож- ность вместо введенного символа вывести любой дру- гой. Программа test readkey использует эту функ цию, замеряя каждую строчную букву заглавной. program test_readkey; uses CRT; var ch : Char; begin
Строковый тип 181 WriteLn('Вводите строчные латинские буквы или z для того, чтобы выйти'); repeat ch := ReadKey; Wri te(UpCase(ch)); until ch = 'z'; end Нажатию каждой символьной клавиши в данной рограмме можно сопоставить вывод другого симво- ла, код которого определяется по какому-то «секрет- ному» закону. Получим, таким образом простейшую программу шифрования: program encrypt; uses CRT; var ch: Char; begi n WriteLn(’Вводите строчные латинские буквы или z для того, чтобы выйти'); repeat ch := ReadKey; Wri te(Chaг(Ord(ch)+1))); until ch = 'z'; end. Строковый тип Из символов строятся слова и предложения, которые можно считать массивами символов. Но работать со строками как с массивами было бы неудобно, поэто- му в Турбо Паскале имеется специальный тип дан- ных — строковый (String). Значением строкового типа является набор символов, заключенный в ка- вычки:
1 82 Глава 6 В диковинном саду. Типы переменных и множества ’Pascal’ ’Волохатий Д1дько варить брудну каву’ ’Информатика’. Строкам можно присваивать значения, сравнивать их (используя операции отношения), вводить и выво- дить целиком (а не посимвольно), объединять вместе. Операция сравнения выполняется поэлементно - слева направо, при этом сравниваются коды симво- лов, из которых состоят строки. Справедливы еле дующие отношения: ’string’ < ’strong’ ’micro’ < ’microprocessor’ ’100’ < ’110’ ’Boy’ > ’girl’. Все приведенные отношения истинны. Строки мо- гут иметь разные длины, в этом случае, если первая строка короче второй и все ее символы совпадают с символами второй строки, большей считается более длинная. Пустая строка изображается двумя кавыч- ками ”, между которыми ничего нет. Строки-переменные описываются так: var имя_переменной String; или так: var имя_переменной : String[M]; где М — количество байт, которые отводятся под стро- ковую переменную. Это фактически длина строки так как в памяти компьютера код одного символа за- нимает один байт. Если длина строки не указана, он., может содержать до 255 символов. При попытке при- своить строковой переменной значение, превосходя- щее по числу символов заданную в описании длину строки, транслятор не выведет сообщение об ошибке но само это значение будет «обрезано».
Строковый тип 1 83 Со строковой переменной можно обращаться, как с массивом. Доступ к i-му слева символу строки дает обращение с [ i ], где с — строковая переменная. В нулевом элементе строки указывается ее текущая длина. Если строковой переменной присваивается значение, большее, чем указанная длина, то лишние правые символы теряются. В примере: program s_privetom; var si , s2, s3 : String; begi n si := ’Вам ’; s2 := ’Привет’; s3 := si + s2 + ’!’ end. в строке s3 будет содержаться текст ’Вам Привет!’, а в ее элементе s3 [0] будет отмечена текущая длина строки, которую можно получить с помощью вызова процедуры Ord(s3[0]). Организация строки подобно массиву дает воз- можность выделить любой ее элемент непосредствен- но: program pascal_peskar; var а : String[10]; с : Char; begin a := ’Паскаль’; с := a[4]; {Символьная переменная с получает значение ’к’ } WriteLn(а, ' ', с); end. Можно изменить значение любого символа в стро- ке:
184 Глава 6 В диковинном саду. Типы переменных и множества а[2]:=’е’; а[6]: = ’р’; После этого в строковой переменной а будет нахо- диться не ’Паскаль’, а ’Пескарь’. Можно обращаться и к нулевому байту строки, на- пример: write(а[0]); а[0] := chr(5) ; Последний оператор принудительно изменит зна- чение текущей длины строки на 5, поэтому если сразу после него поместить оператор вывода Write (а), то будет выведено слово: ’Песка’ Для удобства обработки текстовой информации в Паскале введен целый ряд процедур и функций рабо- ты со строками. Сору. Функция Copy(s : String; start, ten : Integer) : String; выделяет из строки s подстроку длиной len симво- лов, начиная с позиции start. Пример использова- ния этой функции: program algorhythm; var s, si : String; begin s := ’алгоритм’; si ;4= Copy(s, 5, 4); {si = ’ритм’} WriteLn(sl) ; end. Если значение параметра start превышает размер строки s, то результатом выполнения функции Сору будет пустая (то есть не содержащая ни одного сим-
Строковый тип 1 85 вола) строка. Если неправильно задано значение len, то возвращается остаток строки, начиная с позиции start. Delete. Процедура Delete(var s : String; start, len : Integer); удаляет из строки s подстроку длиной len символов, начиная с позиции start. В результате выполнения операторов: s := ’алгоритм’; delete(s, 1, 4) ; строка s будет содержать значение ’ритм’, а резуль- татом вызова: delete(s,3,1); является значение ’ рим ’. Если значение параметра start больше длины строки, то ее содержимое не меняется; а если значе- ние len превышает длину остатка строки, то удаляет- ся подстрока, начиная с позиции start и до конца строки. Insert. Процедура Insert(subs : String; var s : String; start : Integer); позволяет вставить в строку s другую строку (ее в этом случае называю/ подстрокой) subs, начиная с позиции start. Вот пример: s := ’алм’; subs := ’горит’; Insert(sues, s, 3); В результате выполнения этой последователь- ности операторов значением строки s будет слово ’ алгоритм’.
Глава 6 В диковинном саду. Типы переменных и множества Если размер результирующей строки больше объ- явленной длины s, то лишние правые символы теря- ются. Pos. Функция Pos(subs, s : String) : Byte; позволяет определить, входит ли подстрока subs в строку s. Эта функция возвращает число, соответст- вующее позиции, начиная с которой subs входит в s. Если subs не входит в s, то возвращается ноль. При- мер: s : = ’алгоритм’; subs := ’ритм’; х := Pos(subs, s); if x <> 0 then WriteLn(’строка subs. ’ входит в строку s. начиная с х.’-й позиции’); Здесь х = 5. Если subs входит в s несколько раз, то функция Pos вернет число, соответствующее первому слевг вхождению. Length. Функция Length(s : String) : Byte; возвращает число, соответствующее текущей длине строки s. Тот же самый результат, как мы уже знаем можно получить из нулевого байта строки: Ord(s[0]) Concat. Функция Concat(sl. s2. s3... sn : String); объединяет строки si,..., sn в одну, например: si := ’кон’; s2 := ’корд’; s := Concat(sl, s2);
Строковый тип 1 87 Результат выполнения этих операторов — строка 'конкорд*. Если результирующая строка длиннее s, то вся лишняя правая часть теряется. Вместо функ- ции Cone at для слияния строк можно использовать операцию конкатенации (слияния) +: s := si + s2; Ищем палиндромы. Палиндром — это слово (или фрагмент текста), которое читается одинаково слева направо и справа налево. Примеры палиндромов: 4- потоп; + казак; + шалаш; + «А роза упала на лапу Азора». В программе, проверяющей, не является ли вве- денный текст палиндромом, не обойтись без строко- вых переменных: program palindroml; var а, b, с : Stri ng; i : Integer; begi n Wr iteLn(1 Введите текст длиной не более 255 символов:1)^ ReadLn(а) ; b : = ’’; с : = ’ ’ ; for i := 1 to Length(a) do if a[i] <> ’ ’ then begin {Замена заглавной русской буквы на строчную} if (a[i] >= ’A’) and (a[i] <= ’П’) then а[i] := Chr(Ord(a[i]) + 32)
188 Глава 6 В диковинном саду. Типы переменных и множества else if <a[i] >= ’Р’) and (a[i] <= ’Я’) then a[i] := Chr(Ord(a[i]) + 80); b : = b + a [ i ] ; c := a[i] + c ; end ; if b = c then WriteLn(b, ’ - ’, ’ палиндром’) else WriteLn(b, ’ - ’, ’ не палиндром’) WriteLn ('Для завершения работы нажмите <Enter>'); ReadLn; end . Обратите внимание на то, что коды русских букв в таблице ASCII следующие — Ord (А) = 128,.... Ord(Я) = 159, а прописные русские буквы располо жены в таблице кодов не подряд — Ord (а) = 160. Ord(n) = 175, Ord(p) = 224..... Ord(a) = 239. Замену заглавных букв на маленькие можно офор мить функцией, и тогда эта же программа может вы глядеть так: program pal i ndгогт12 ; var a, b : String; r, i : Integer ; flag : Boolean; function Modif(x : Char) : Char; begi n Mod if := x; if (x >= ’A’) and (x <= ’П’) then Modif := Chr(Ord(x) + 32); if (x >= ’П’) and (x <= ’Я’) then Modif := Chr(Ord(x) + 80)
Строковый тип 189 end; {Modif} begi n a := ’А роза упала на лапу Азора’; b := ’ ’ ; {Удалим из а пробелы и заменим заглавные буквы прописными} for i :- 1 to Ord(a[0]) do if a[i ] <> ’ ’ then b : = b + Modi f (a [ i] ) ; i := 1; flag := true; r := Ord(b[0]); {Сравним попарно буквы: первую и последнюю, вторую и предпоследнюю и т. д.} while flag and (i <= r div 2) do if b[i] = b[r - i + 1] then i := i + 1 else flag := false; if flag then Writel_n(b, ’ - палиндром’) else Writel_n(b, ’ - не палиндром’); end. Как из кота сделать кита? Это может программа: program catkit; var s : String; i , j : Integer; a. b, c : String;
1 90 Глава 6 В диковинном саду. Типы переменных и множества begi п а := ’Кот плывет по океану, Кит на кухне ест сметану.’; b := 'Поменяйте Кит на Кот , Кот на Кит наоборот.’; с : = а + b; i : = 1; repeat s := Сору(с, i, 3) ; if s = ’Kot’ then begi n Delete(c, i. Length(’Кот’)); Insert(’Кит’, c, i) end else if s = ’Кит’ then begi n Delete(c, i, Length(’Кит’)); Insert(’Kot’, c, i) end; i := i + 1 until i = Length(c) - 2; Wri teLn(c) ; ReadLn ; end. Разберите ее работу самостоятельно. Сила есть? А вот следующая задача. Значением строки служит предложение, содержащее тире. Тре- буется поменять местами части предложения до и по- сле тире. Эту задачу решает следующая программа program sila; var a, b, с : String; р ; Integer;
Строковый тип 191 begi n а := ’Сила есть - ума не надо.’; р := Pos(’- ’ , а) ; b := Сору(а. 1, р - 1); с := Сору(а, р + 1, Length(a) - 1); а : = b + ’ - ’ + с; Wri teLn(а) end. Попробуйте переделать эту программу так, чтобы она меняла местами только слова (последовательно- сти символов от пробела до пробела), разделенные тире. Эффект раздвигания строки. С помощью проце- дуры Сору можно построить интересный пример по- явления строки на экране, когда она выводится с эф- фектом раздвигания и звуковым сигналом. В про- грамме explo используется модуль Crt, который содер- жит процедуры и функции для управления работой дисплея в текстовом режиме: program explo; uses Crt; procedure ExplodeString(x, у : Byte; s : String; c : Word); var i, 12 : Byte; begi n 12 : = Length(s) div 2 + 1; {Середина строки} if x < 12 then x := 12; {Настройка x} for i := 0 to 12-1 do {Цикл вывода} begi n GotoXY(x - i, у); {Начало строки} {Вывод расширяющейся центральной части стооки}
192 Глава 6 В диковинном саду. Типы переменных и множества Write(Copy(s, 12 - i , 2 * i + 1)); Sound(i * 50); {Звуковой сигнал} Delay(с) ; NoSound; {Выключение звукового сигнала} end; {Конец цикла} end; begi n ClrScr; ExplodeString(40, 12, ’12345678900987654321’, 1500); end. Вывод строки производится процедурой Explode- String. Первый параметр этой процедуры задает но- мер позиции в строке (х), начиная с которой выво- дится текст, а второй — номер строки экрана (у) на которой этот текст будет располагаться. Третий параметр — строка текста, а четвертый определяет задержку вывода символов в тысячных долях секун- ды. Звуковой сигнал включается процедурой Sound параметр которой — частота звучания динамика в герцах, а выключается процедурой без параметроь NoSound. Перечисляемый тип данных До сих пор в своих программах мы использовал! стандартные типы данных языка Паскаль. Вместе с тем Паскаль позволяет программисту создавать свои собственные типы данных, как если бы в диковин- ном саду можно было сажать растения, которые при- думали мы сами! Одним из таких типов является пе речисляемый тип. Объявляется перечисляемый тиг простым перечислением допустимых значений в круг
Перечисляемый тип данных 193 лых скобках, через запятую. Вот пример такого опи- сания: type Month = (jan, feb, mar, apr, may, jun, jut, aug, sept, oct, nov, dec); Значения переменных перечисляемого типа не мо- гут быть ничем, кроме имен, приведенных в описании типа. Порядок внутри списка устанавливается сле- дующим образом: первый в списке элемент получает номер О, второй — 1, и т. д. Переменная, описанная как: var tecmes : Month; может принимать значения jan, apr, но никогда не может принять, например, значение ’холодный’. Можно считать, что объявление перечисляемого типа — это сокращенная форма записи объявления именованных констант. Приведенное выше объявле- ние типа Month равносильно следующему описанию: const jan = 0; feb = 1; mar = 2 ; dec = 11; Элемент одного перечисляемого типа не может вхо- дить в список элементов другого перечисляемого типа. К переменным перечисляемого типа можно приме- нять операции присваивания, сравнения, но они не могут находиться в операторах ввода-вывода. Если все-таки необходимо вывести значение переменной перечисляемого типа, обычно приходится использо- вать оператор выбора case. К переменным перечис- 7 Зак. №933
194 Глава 6 В диковинном саду. Типы переменных и множества ляемого типа можно также применять встроенные функции: + Succ(x) — элемент, следующий за указанным; + Pred(x)— элемент, предшествующий указанному; 4- Ord(x) — порядковый номер элемента. В нашем примере результат выполнения этих функций будет таким: Ord(feb) = 1, Pred(feb) = jan, Succ(feb) = mar. Названия месяцев можно напечатать так: if tek_mes = okt then WriteLn(’октябрь’); а номер квартала — так: if tek_mes < apr then WriteLn(’Первый квартал’) или так: case tek_mes of j an , feb, mar : WriteLn(’Первый квартал’); apr, may, jun : WriteLn(’Второй квартал’); j ul, aug, sept : WriteLn(’Третий квартал’); oct, nov, dec WriteLn(’Четвертый квартал’); end; С введением перечисляемого типа программа ста- новится нагляднее — mes := January нагляднее, чем mes := 1, кроме того, возрастает возможность кон- троля за значениями переменных.
Ограниченный тип данных 195 Ограниченный тип данных В программировании часто используются перемен- ные, которые должны принимать значения, лежащие в ограниченном диапазоне. Например, если нужно вести учет, сколько дней в неделю работал человек, то соответствующая переменная может иметь значе- ния только от О (не работал ни одного дня) до 7 (ра- ботал каждый день). Нельзя работать 8 дней в неде- лю! Для решения подобных задач в Паскале можно использовать ограниченный тип данных. Он пред- ставляет собой отрезок (интервал) другого типа, ко- торый является базовым, например: type Rabden = 0..7; {Здесь базовым является целый тип} Zagl = ’A’.-’Z’; {Базовый тип - Char} Leto = jun..aug; {Базовый тип - Month, определенный в предыдущем параграфе} Задается ограниченный тип своими начальным и конечным значениями, а базовым может служить лю- бой простой тип данных — Integer , Boolean , Char, а также перечисляемый тип. Вещественный тип не может быть базовым. К переменным ограниченного типа применимы все операции, которые допускает базовый тип. С ис- пользованием переменных ограниченного типа про- грамма становится нагляднее, надежнее, так как авто- матически контролируется всякое присваивание значений такой переменной. Поэтому ограниченный тип рекомендуется использовать всегда, где это воз- можно. Високосный год. Следующая программа опреде- ляет, является ли заданный год високосным (висо-
196 Глава 6 В диковинном саду. Типы переменных и множества косным является год, кратный четырем или 400, но не кратный 100): program visokosgod; var God : 1582..9999; {Ограниченный тип} Flag : Boolean; begi n WriteLn(’Введите год ’); ReadLn(God); Flag := False; if (God mod 4=0) and (God mod 100 <> 0) then Flag := True; if God mod 400 = 0 then Flag := True; if Flag then WriteLn(God:4. ’ - високосный’) else WriteLn(God:4, ’ - не високосный год’); end . Время года по дате. Эта программа определяет время года по дате: program seasons; type season = (winter, spring, summer, autumn); {зима, весна, лето, осень} Month = (jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec); var sz : Season; d : 1. .31; mn : Month; mon : 1..12 ; у : 1900..9999; begin
Ограниченный тип данных 1 97 WriteLn(’Введите дату /число, месяц, год/ ’); ReadLn(d, mon, у); {Ввод даты - день, месяц, год} case (mon - i; ) of 0 : mn := jan • 1 : mn := feb * 2 : mn := mar > 3 : mn := apr • 4 : mn : = may » 5 : ma := jun 9 6 : mn := jul 9 7 : mn := aug 9 8 : mn : = sep 9 9 : mn oct 9 10 : mn := nov; 11 end; : mn := dec; {case} Write(d:2); {Вывод дня} case mn of j an : Wr i te ( ' января 1); feb : Wri te(' февраля'); mar : Wr1te(‘ марта'); apr : W r 11 e ( ' апреля'); may : Wri te(' мая'); j un : Wri te(' июня'); jul : Wr1te(' июля'); aug : Wri te(' августа'); sep : Wr1te(' сентября'); oct : Write(' октября'); nov : Wr1te(' ноября'); dec : Wr1te(' декабря'); end; Write(y:5, ’ года ’); case mn of
198 Глава 6 В диковинном саду. Типы переменных и множеств: dec. j an , f eb : sz := winter; mar, apr . may : sz := spring; j un, jul . aug : sz := summer; sep, oct , nov : sz := autumn; end; case sz of winter : WriteLn(’зима’); spring : Wr iteLn(’весна’); summer : Wr iteLn(’лето’); autumn : Wr iteLn(’осень’); end; case mn of dec, j an, f eb, nov : Wr iteLn(’холодно’) mar, apr, oct : WriteLn(’прохладно; may, aug, sep : WriteLn(’тепло’); j un, jul : Wri teLn(’жарко’); end; end. Разберите самостоятельно работу этой программы. Компьютер учит склонения. Решим такую задачу Пусть для любого натурального К необходимо вывес- ти фразу «мы нашли К грибов (гриб, гриба)», исполь- зуя правильный падеж существительного «гриб». Ре шение оказывается простым, если заметить, что для К'=0и5<К'<20 следует использовать форму «гри- бов», для К = 1 — форму «гриб» и для 2 < К < 4 форму «гриба». Начиная с К = 21 все повторяется циклически, поэтому достаточно найти остаток от де ленпя К на 20 и применить наш алгоритм: program griby_; type Griby = (grib, griba, gribow); var g : Griby;
Ограниченный тип данных 1 99 р. к, f : Integer; begi n Write(’Сколько грибов вы нашли?*); ReadLn(k); р := 10; f := к; if К > 20 then f := к mod 10; case f of 0 : g := gribow; 1 : g := grib; 2 , 3, 4 : g := griba; 5 ..20 : g := gribow; end; WriteLn(’Mbi нашли ’); case g of grib : WriteLn(k:3, ’ гриб’); griba : WriteLn(k:3, ’ гриба’); gribow : WriteLn(k:3, ’ грибов’); end; end. Самый холодный месяц. Загляните, читатель, в энциклопедию и постарайтесь найти среднемесячные по каждому месяцу значения температуры для не- скольких стран, например Великобритании, Дании, Перу, Кении, Японии и Польши. Можно использо- вать данные и для других стран. Определить, какой месяц и в какой стране самый холодный, поможет та- чая программа: program country_temp; type Country = (GreatBritain, Denmark, Peru, Kenia, Japan, Poland); Month = (jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec);
200 Глава 6 В диковинном саду. Типы переменных и множества var temp : array[Country, Month] of Real; il, i : Country; ml, m : Month; begi n {Ввод значений температур для всех стран из нашего списка} for il := Great_Britain to Poland do begin case il of G r e a t_B r i t a i n : WriteLn(’Великобритания’); Denmark : WriteLn(’Дания’); Peru : WriteLn(’Перу’); Kenia WriteLn(’Кения’); Japan : WriteLn(’Япония’); Poland : WriteLn(’Полыиа’) end; WriteLn(’Среднемесячная температура:’); for ml := jan to dec do Read(temp[i1, ml]); end; {Поиск наименьшей среднемесячной температуры} i := GreatBritain; m := jan; for il := GreatBritain to Poland do for ml ;= jan to dec do if temp[il, ml] < tempfi, m] then begi n i := i1; m := ml; end ; {Вывод результата с помощью оператора case} WriteLn('Самый холодный месяц ');
Ограниченный тип данных 201 case m of j an • Write(’flHBapb-’); feb Write(’февраль-’); mar • Wri te(’март-’); apr Wri te(' апрель-'); may • Wri te(' май-'); j un Wri te(' июнь-*); jul • Wr i te(' июль-'); aug • Wri te(' август-'); sep - Write(' сентябрь-'); oct • Write(' октябрь-'); nov • Write(' ноябрь-'); dec - Write(’декабрь-’); end; case i of GreatBritain; Write(’Великобритания ’) Denmark : Мг1Ге(’Дания ’); Peru : Write(’Перу’); Kenia : Write(’Кения’); Japan : Write(’Япония’); Poland : Write(’Польша ’); end; Wri teLn; WriteLn(temp Ii. m]:7:2); end. В год желтой свиньи. В старом японском календа- ре был принят 60-летний большой цикл, состоящий из пяти 12-летних малых циклов Малые циклы обо- значались названиями цвета — зеленый, красный, желтый, белый, черный. Внутри каждого малого цик- ла годы носили названия животных — крысы, коро- вы, тигра, зайца, дракона, змеи, лошади, овцы, обезья- ны, курицы, собаки и свиньи. Известно, что 1984 год — это год зеленой крысы и начало очередного большого цикла. Вот программа, которая определяет
202 Глава 6 В диковинном саду. Типы переменных и множества название заданного года нашей эры по старому япон скому календарю: program japanese_year ; var year, yearl, i, delta : Integer; begi n WriteLn(’Введите год’); ReadLn(year); yearl := year; delta := yearl mod 60 - 4; if delta >= 12 then begi n i := delta div 12; delta := delta mod 12; end ; case i of 0 : Wr ite(’green’); 1 : Wri te(’red’) ; 2 : Write(’yellow’); 3 : Wri te(’whi te’); 4 : Write(’black’); end ; case delta of - 4, 8 : Wr iteLn(’monkey’);{обезьяна} - 3, 9 : WriteLn(’hen’); {петух} - 2, 10 : WriteLn(’dog’); {собака} - 1, 11 : WriteLn(’pig’); {свинья} 0 : WriteLn(’rat’); {крыса} 1 : WriteLn(’bull’); {бык} 2 : WriteLn(’tiger’); {тигр} 3 : WriteLn(’rabbit’); {кролик} 4 : WriteLn(’dragon’); {дракон} 5 : WriteLn(’snake’); {змея} 6 : WriteLn(’horse’); {лошадь}
Запись 203 7 : WriteLn(’sheep’); {овца} end; end. Для того чтобы избежать проблем со склонения- ми, мы пошли на маленькую хитрость — результат работы программы выводится на английском языке. Подумайте, как можно переделать эту программу, чтобы результат выводился на русском языке — пра- вильно (без сообщений вида «зеленый обезьяна»). Запись Пока записи могли быть нам известны только как за- писи в дневнике. Но тип «запись» имеется и в Паска- ле, играя там довольно заметную роль. Об этом мы сейчас и поговорим. В практике программирования иногда приходится иметь дело с данными, которые представляют собой объединение разнотипных данных. Например, ин- формацию о номере квартиры, дома, названии улицы и города целесообразно сгруппировать и назвать ад- ресом. Такой тип данных, объединяющий разнород- ные объекты, и называется записью. Запись — это совокупность конечного числа разно- родных (разнотипных) элементов, называемых поля- ми. Описание записи выглядит так: type имя_записи = record имя_поля1 : тип_поля1; имя_поля2 : тип_поля2; имя_поляЫ тип_поля1\1; end; Вот пример описания записи, содержащей сведе- ния об адресате:
204 Глава 6 В диковинном саду. Типы переменных и множества type Address = record k : Integer; {Почтовый индекс} city : String; {Название города} street : String; {Название улицы} house : Integer; {Номер дома} flat : Integer; {Номер квартиры} end ; или о книге: type Book = record autor : String; {Автор} title : String; {Название книги} year : Integer; {Год издания} end; Каждое поле в записи можно считать обычной пе ременной, которой можно присвоить имя, ввести или вывести ее значение. Обращаться к полю нужно nt составному имени: имя_записи.имя_поля С другой стороны, запись можно рассматривать в программе и как единое целое. Пусть: var В : Book; тогда присваивание В .author : = ’Вирт.Н.’; определит автора книги В, а ее название даст опера- тор присваивания: В.title := ’Алгоритмы + структуры данных = программы’; а В.year := 1985 даст год издания. Обращение к записи в целом производится по ее имени (например, когда нужно запись поместить во
Запись 205 внешний файл). Если две записи одинаковы (то есть имеют одинаковый тип, например: var А, В : Book;), то к ним можно применить оператор присваивания А : = В. Сравнивать записи можно только по одно- именным полям. Оператор присоединения. Записи могут быть эле- ментами массивов. Отдельные поля записей тоже мо- гут быть записями. За счет этого в Паскале можно создавать сложные структуры данных: const stud = 20; type anketa = record facult : String[15]; group : 1..6; FIO : String; Address : record k : Longlnt; city : String; street : String; house, flat : Integer; end; Born : record date : 1..31; month : 1..12; year : 1970..1980; end; end ; var ved : ar ray[1..stud] of anketa; Здесь переменная ved — это массив записей. Каж- дая запись состоит из пяти полей, причем два послед- них поля сами являются записями. Обращаться к
206 Глава б В диковинном саду. Типы переменных и множества элементам таких сложных структур надо по состав- ным именам, например: Ved[5].Address.city := ’Корытово’; или WriteLn(ved[15].born.year); Эти примеры показывают, что писать составные имена — довольно утомительное занятие, которое к тому же часто сопровождается ошибками. Для сокра- щения текста программы и увеличения ее наглядно- сти в Паскале используется оператор присоединения with. Его формат: with имя_записи do оператор; В пределах оператора при обращении к записи, имя которой указано после зарезервированного слова wi th, можно опускать имя записи, а обращаться толь- ко к собственным именам полей. Имя записи в со- ставном имени поля транслятор добавит сам. Напри- мер: with ved[5].Address do city := ’Корытово’; with ved[5].Born do WriteLn(year); Эти операторы вряд ли проще предыдущих. Опе- ратор with полезно использовать при обращении к нескольким полям записи либо при многократном обращении к какому-то полю: for i := 1 to stud do with ved [i] .Address do begi n Wri teLn(k) ; Wri teLn(ci ty) ; Wri teLn(street) ; WriteLn(house:8, flat:8); Wri teLn; end;
Запись 207 Список учащихся. Составить список учащихся класса с указанием фамилии, даты рождения, пола и отсортировать этот список по алфавиту может сле- дующая программа: program class_list; uses crt; const N = 15; type School = record FIO : String; Born : record date : 1. .31; month : 1. .12; year : 1982..1985; end; sex : (male, female); {Мальчик, девочка } end; {School} var sc : School; class : array[l..N] of School; i , j : Integer; p : Char; Flag : Boolean; begi n {Создание списка учащихся} j := 0; repeat j := j + 1; Write(’ Ф.И.О. j:2, ’ ученика ’); ReadLn(sc.FIO); with sc.Born do begi n Write(’Дата рождения ’); ReadLn(date);
208 Глава 6 В диковинном саду. Типы переменных и множества Wr1te(’Месяц рождения ’) ; ReadLn(month); Write(’Год рождения ’); ReadLn(year) ; end ; Write(’llon /m или f/ ’); ReadLn(p); case p of ’m’ : sc.sex := male; "f* : sc.sex := female; end ; class [ j] := sc; Write(’Повторить ввод? (у или n) ’); ReadLn(р); until (j > N) or (p <> ’y’); {Упорядочение no алфавиту методом пузырька} j := N; repeat Flag := true; for i := 2 to j do if class[i].FIO < class[I - 1].FIO then begi n sc := classfi]; class[i] := class[i - 1]; class[i - 1] := sc; Flag := false; end; j := j - 1; until Flag or (j = 1); for i :=1 to N do with class[i] do WriteLn(FIO, Born.date:3, Born.month:3, Born.year:5); end.
Множества 209 Множества Понятие множества, как и числа, относится к фунда- ментальным понятиям математики. На теории мно- жеств базируется большинство математических моде- лей, и Паскаль является одним из немногих языков программирования, где имеется соответствующий тип данных, хотя математическое понятие множества и его реализация в Паскале — это, строго говоря, раз- ные, хотя и похожие, вещи. В математике множеством называют любую сово- купность объектов, например, множество чисел 1..10 или множество, состоящее из лебедя, рака и щуки. Математическое множество может иметь любое ко- личество элементов. В Паскале множество — это также некоторое «соб- рание» объектов, называемых элементами множества, с той, однако, особенностью, что все они должны быть одного и того же базового типа. В качестве базо- вого типа может выступать любой порядковый тип. Во множествах допускаются только такие элемен- ты, порядковые значения которых не выходят за гра- ницы отрезка 0..255. Для целочисленных множеств это означает, что в них могут присутствовать числа не меньшие 0 и не большие 255. Отсюда правило — от- рицательные числа как элементы множества в Турбо Паскале неприемлемы. Основные правила работы с множествами Правило 1. Постоянные множества в Паскале задают- ся перечислением элементов: [1, 2,3], [ ’ А ’ , ’ К ’ , ’ В ’ , ’ L ’ ], [1.•N], [] — пустое множество.
210 Глава 6 В диковинном саду. Типы переменных и множества Множество может состоять только из разных эле- ментов, порядок элементов не важен. Правило 2. Переменные типа «множество» описы- ваются следующим образом: var имя : set of базовый_тип; например: var Lat : set of ’A’..’Z’; {Множество заглавных английских букв} Nom : set of 1..100; {Множество целых чисел от 1 до 100} Season : set of (winter, spring, summer, outumn) {Множество времен года} ch : set of Char; {Множество символов} Пусть, например, базовым типом являются назва- ния трех городов: type city = (Petersburg, Moscow, Kharkov), var P : set of city; Тогда P может принимать следующие значения: [Petersburg], [Moscow], [Kharkov]; [Petersburg, Moscow], [Petersburg, Kharkov], [Moscow, Kharkov], [], [Petersburg, Moscow, Kharkov] В общем случае, если базовое множество состоит из N-элементов, производный множественный тип определяет 2N подмножеств. Так, переменная Р типа city может принимать 23 = 8 различных значений. Операции над множествами В Паскале определены три операции над множествами. Объединение множеств (+). Результатом этой опе- рации будет множество, которое состоит из элемен-
Множества 211 тов, принадлежащих хотя бы одному из множеств (рис. 6.1). Рис. 6.1. Объединение множеств Пересечение множеств (*). Результирующим бу- дет множество, состоящее из элементов, принадлежа- щих каждому из множеств (рис. 6.2). Рис. 6.2. Пересечение множеств Разность двух множеств (—) — состоит из тех эле- ментов первого множества, которые не принадлежат второму (рис. 6.3).
212 Глава 6 В диковинном саду. Типы переменных и множества Рис. 6.3. Разность множеств Примеры выполнения операций над множествами: + [’В’, ’ F ’ ] + [ ’ В ’ . . ’ D ’ ] — результат: [ ’ В ’ . ’ С ’ , ’ D ’ . ’ F ’ ] ; + [0.-4] * [5, 6] — результат: [] ; 4- [Moscow, Kharkov] * [Kharkov, Kiev] —резуль- тат: [Kharkov] 4- [1, 5, 9] - [2, 4, 8, 9] — результат: [ 1, 5 ]. Для сравнения множеств используются операции отношения: = , о. <, <=, >, > = Выражение А = В истинно тогда, когда сравнивае- мые множества содержат равные элементы. Неравенство А <> В истинно тогда, когда одно из сравниваемых множеств содержит хотя бы один эле- мент, не входящий в другое множество. Отношение А <= В означает нестрогое включение множества А в множество В, а отношение А < В — строгое включение. Эти отношения истинны только тогда, когда все элементы А являются одновременно и элементами В, например: + [ ’ С ’ . . ’ Е ’ ] = [ ’ D ’ , ’ Е ’ , ’С’] — истинно;
Множества 213 4 [ 7, 1, 3] <> [2, 4, 6, 8] — истинно; + [’А’..’Е’] >= [’А’, ’В’] —истинно; + [Kiev] < [Moscow, Kiev] — истинно. Операция i п определяет принадлежность элемен- та некоторому множеству: + 5 in [3 . . 7] — истинно; + К + 2 in [0..7] * [4, 6, 8]— при К = 2 истин- но, при К = 1 ложно. Порядок старшинства операций, предназначенных для работы с множествами, следующий: in, =, <>, <=, <, >=, > Для изменения порядка выполнения операций над множествами, как обычно, используют круглые скоб- ки. Средства работы с множествами позволяют в не- которых случаях сократить программы и сделать их более наглядными и эффективными за счет уменьше- ния числа проверок. Считаем цифры. Программа counter может под- считать количество цифр в символьной строке: program counterl; var с : Char; k : Integer; begi n k := 0; Write(’Введите символьную строку’); repeat Read (с) ; if not (c in [’ ’, ’+’, ’/’]) then
214 Глава 6 В диковинном саду. Типы переменных и множества begi П if с in [’©’..’9’] then k := k + 1; Wri te(с) ; end; until Eoln; Wri teLn; WriteLn(’Число цифр в строке k:2); end. В этой программе используется функция Eoln, ко- торая возвращает значение «истина» при достижении конца строки. Считаем буквы. Программа znaki_prepi naki фор- мирует множество znaki, в которое входят только строчные латинские буквы, встретившиеся во входной строке, и множество знаков препинания prepinaki, содержащихся во входной строке: program znakiprepinaki; var с, i , j : Char; znaki : set of ’a’..’z’; prepinaki : set of begi n WriteLn(’Введите строку’); znaki := []; prepinaki : = []; repeat Read (c) ; „ i f c in [ ’a’..’z’] then znaki := znaki + [c] else if c in [ ’ : ’ , ’ ; ’ , ’,’. ’.’, ’!’, ’?’] then prepinaki := prepinaki + [c] ; until Eoln; Write(’ Латинские буквы : ’); for i := ’a’ to ’z’ do
Итог 215 if i in znaki then Write(i:2); Wri teLn; Write(’ Знаки препинания for j ’ to ’?’ do if j in prepinaki then Write(j:2); end. Итог Ну вот, читатель, мы и побывали в диковинном саду типов Паскаля. Он столь же разнообразен и удивите- лен, как и сад из сказки мадам д’Олнуа, и, научив- шись в нем ориентироваться, вы также сможете ис- полнить любое свое «программистское» желание, как и младшая дочь короля. А напоследок — несколько задач. Задача 1. В модуле System имеется функция llpCase, выполняющая преобразование строчных латинских букв в прописные (небуквенные символы не преобразуют- ся). Запрограммируйте свой вариант этой функции, используя тот факт, что Ord ('А' ) =Ord('a‘) - 32. Задача 2. Запрограммируйте обратную по отноше- нию к LlpCase функцию LowCase преобразования прописных латинских букв в строчные, отсутствую- щую в стандартных библиотеках. Используйте тот факт, что Ord ( ' А ' ) = Ord ('а') + 32. Задача 3. Напишите программу, которая заменяет во введенном слове все буквы «а» на буквы «б». Задача 4. Напишите программу обращения слова (если был «кот», то результатом будет — «ток»). Задача 5. Напишите программу, которая каждую встречающуюся в слове букву «б» заменяла бы соче- танием «ку».
216 Глава 6 В диковинном саду. Типы переменных и множества Задача 6. Задан массив русских слов. Напишите про- грамму, которая указывает все пары слов, образую- щих рифму. Слова рифмованы, если у них совпадают две или три последние буквы. Задача 7. Написать программу, которая для любо- го целого числа К от 1 до 99 выводит фразу «Мне К лет», учитывая согласование между числитель- ным и существительным (то есть «Мне 5 лет», «Мне 1 год»). Задача 8. Написать программу, в которую вводится последовательность слов из строчных русских букв, разделенных запятыми, а за последним словом стро- ки идет точка. Программа должна вывести в алфавит- ном порядке: 4- все гласные буквы, которые входят в каждое слово; + все согласные буквы, которые не входят ни в одно слово; + все звонкие согласные буквы, которые входят хотя бы в одно слово; + все глухие согласные буквы, которые входят хотя бы в одно слово; *4 все согласные буквы, которые входят только в одно слово; "4 все глухие согласные буквы, которые не входят только в одно слово; -4 все звонкие согласные буквы, которые входят бо- лее чем в одно слово. Задача 9. Известен набор продуктов — хлеб, масло, сыр, молоко, имеющийся в ассортименте магазинов. В три магазина доставлены отдельные виды этих про- дуктов. Построить множества А, В и С, которые со- держат соответственно:
Итог 217 А — продукты, имеющиеся одновременно во всех магазинах; В — по крайней мере в одном магазине; С — которых нет ни в одном магазине.
ГЛАВА 7 Что посеешь, то и пожнешь, если... научишься работать с файлами
Данные, которые хранятся в переменных и массивах, пропадают при завершении работы программы. Та- ким образом, рассмотренные ранее типы данных не сохраняют полученные результаты для последующе- го использования их другими программами или этой же программой в другом сеансе работы. Для «спасе- ния» информации ее необходимо записать на гибкие или жесткие магнитные диски, снабдив определен- ным именем, чтобы по этому имени к сохраненным данным можно было обратиться вновь. Такой набор данных называют файлом. В файлах можно хранить списки, документы, изображения и другую информа- цию. Файлы По способу организации в Паскале различают файлы трех типов — текстовые, типизированные и нетипи- зированные. Текстовые файлы. Текстовые файлы состоят из символьных строк. Строки могут иметь разную дли- ну, каждая строка заканчивается специальными символами воз- врата каретки CR (#13) и перево- да строки LF (#10). Эти символы во время просмотра файла обыч- но не выводятся и являются «не- видимыми» символами. Оканчи- вается текстовый файл призна- ком конца файла (EOF — сокра- щение от английского End Of File):
Глава 7 Что посеешь, то и пожнешь, если.. Строка 1 #13 #10 Строка 2 #13 #10 EOF Работу с текстовыми файлами обеспечивает мо- дуль System Турбо Паскаля. Этот модуль содержит процедуры и функции для основных операций ввода й вывода информации, работу со строками и т. д. Мо- дуль System подключается к программе автоматиче- ски, если в этом есть необходимость, и не требует ис- пользования оператора uses. Существуют два способа доступа к файлам — по- следовательный и прямой. Текстовые файлы допуска- ют только последовательный доступ. Это означает, что для нахождения определенного элемента такого файла нужно просмотреть все, что ему предшествова- ло. При последовательном доступе программа не мо- жет в любой момент времени считать из файла про- извольную порцию информации или произвести запись в произвольное место файла. Так, например, чтобы прочитать третий элемент текстового файла, нужно сначала прочитать первый и второй элементы. Это напоминает поездку в школу на трамвае — если сел в трамвай, то прежде, чем доберешься до школы, придется побывать на всех промежуточных останов- ках! Можно считать, что при обращении к файлу ис- пользуется специальный указатель на элемент файла, а файл представляет собой последовательность эле- ментов, каждому из которых присвоен свой номер. Указатель при считывании очередного элемента фай- ла перемещается к следующему элементу. Типизированные файлы. Типизированные файлы подобны массивам. Схожесть с массивами состоит
Текстовые файлы 221 как в том, что все элементы типизированного файла имеют один и тот же тип, так и в том, что каждый элемент такого файла имеет свой номер (считая от пуля). Любой элемент типизированного файла досту- пен для записи или чтения как прямым, так и после- довательным доступом. Прямой доступ производится по порядковому номеру элемента. Типизированные файлы используются для хране- ния однородной по типу информации. Если речь идет о хранении числовых данных, следует использовать типизированные файлы, так как величина типизиро- ванного файла значительно меньше, чем текстового. Программа может читать данные из имеющегося файла, создавать новые файлы, изменять уже создан- ные файлы, но в любом случае последовательность обращения к файлу следующая: 4- установить связь программы с файлом; 4- «открыть» файл для чтения или записи; 4 читать из файла или записать в файл; 4- закрыть файл. Текстовые файлы Как было сказано ранее, текстовые файлы хранят ин- формацию в символьном виде и эта информация раз- бита на строки. В процессе ввода (или вывода) в тек- стовый файл возможно преобразование информации несимвольного типа (например, чисел) в символы. С файлом на магнитном диске связывается файловая переменная, тип которой объявляется как text. У Лукоморья. Программа luk создает текстовый файл, в который может быть записана любая тексто-
222 Глава 7 Что посеешь, то и пожнешь, если... вая информация. Это могут быть, например, Пушкин ские строки: У Лукол юрья дуб зеленый, Златая цепь на дубе том. И днем и ночью кот ученый Все ходит по цепи кругом. program luk; var FIL : text; {Описание файловой переменной} s : String; begi n Assign(FIL, ’ASPUSHKN.TXT’); {Устанавливается связь файловой переменной FIL с файлом ASPUSHKN.TXT. ASPUSHKN.TXT - имя файла на диске, оно больше нигде в программе не появится, его будет замещать имя FIL} ReWrite(FIL) ; {Теперь файл ASPUSHKN.TXT открыт для записи. Если в нем уже было что-то записано, все будет уничтожено} WriteLn(’Вводите стихи построчно’); ReadLn(S) ; while S <> ’’ do begin Wri teLn(FIL, s); ReadLn(S) end; Close(FIL); {Файл закрыт} end. В этой программе сначала вводится переменная файлового типа FIL. Затем с помощью процедуры Assign устанавливается связь между этой перемен-
Текстовые файлы 223 ной и файлом ASPUSHKN.TXT, который создается (или уже создан) на диске компьютера. После установле- ния связи внешнего файла с файловой переменной внешний файл надо открыть для записи или чтения. При открытии файла выполняются необходимые системные операции, подготавливающие файл к за- писи или считыванию информации. Текстовый файл my_file открывается процедурой Reset (my_fi le) — только для чтения и процедурой ReWr 1 te (my_f1le) — только для записи. В нашем примере процедура ReWrite выполняет все подготовительные действия, необходи- мые для записи в файл. Каждая строка, набранная на клавиатуре, считывается в переменную S строкового ина, а затем с помощью файловой переменной FIL пе- редается в файл ASPUSHKN.TXT. При нажатии клавиши Enter вводятся символы #13 и #10, завершающие строку. Запись в файл будет осу- ществляться до тех пор, пока не будет введена пустая строка. Процедура Close «закрывает» файл. После закрытия файла связанный с ним внешний файл об- новляется, затем файловая переменная может быть связана с другим внешним файлом. Работа с фай- лом — запись в файл или чтение из него — обязатель- но должны завершаться закрытием файла с помощью процедуры Close. Запустим программу, наберем знаменитое четве- ростишие, нажимая в конце каждой строки клавишу Enter. Ввод завершим двойным нажатием Enter, при котором в программу будет передана пустая строка. Выполнение программы завершится, и на диске по- явится файл. Посмотрим, что он собой представляет. Для этого выйдем из интегрированной среды Турбо Паскаля в Norton Commander, найдем в каталоге имя ASPUSHKN.TXT и с помощью окна просмотра (клавиша F3) прочитаем его. Мы увидим строки в том виде, в
224 Глава 7 Что посеешь, то и пожнешь, если... каком мы их набирали, то есть информация в тексто вом файле хранится в символьном, удобном для про смотра виде. Чтение из текстового файла. Чтение из текстово го файла осуществляется процедурами ReadLn ил Read. Если перед списком ввода стоит имя файловой переменной, то данные читаются не с клавиатуры, ц из файла, связанного с этой переменной: ReadLn(FIL, списокввода); Очередная строка созданного нами ранее на диске файла ASPUSHKN.TXT считывается во вспомогательную переменную списка ввода через файловую перемен- ную FIL. Вот как это делается в программе readluk считывающей файл ASPUSHKN.TXT и выводящей текст на экран: program readluk; var FIL : text; S : String; {s - имя вспомогательной переменной списка ввода} begi п Assign(FIL, ’ASPUSHKN.TXT’)’. Reset(FIL); {Открыли файл для чтения} while not EOF(FIL) do {Чтение продолжается, пока не будет достигнут конец файла} begi п -ReadLn(FIL, S); {Чтение из файла ASPUSHKN.TXT в S построчно} WriteLn(S); {Вывод прочитанного на экран} end; Close(FIL); {Закрыли файл} end .
Текстовые файлы 225 Текстовый файл ASPUSHKN.TXT должен находиться в том же каталоге, что и программа. Процедура Reset устанавливает указатель теку- щей позиции файла на начало первой строки тексто- вого файла. Процедура ReadLn считывает строку пол- ностью, сохраняет считанное значение в переменной S и передвигает указатель на начало следующей стро- ки. Движение допускается последовательно только в одну сторону — слева направо (ведь текстовый файл является файлом последовательного доступа!). Так продолжается до тех пор, пока указатель не достигнет конца файла. Повторим последовательность действий, которую необходимо выполнить для того, чтобы записать в файл (или прочитать из файла): + поставить в соответствие имени файла на диске имя файловой переменной. Это делает процедура Assign(файловая_переменная, имя_файла). Пе- ред именем файла можно указать путь к нему; 4- открыть текстовый файл, для этого используют одну из трех процедур: •Ф- Reset(файловаяпеременная) — открыть для чтения; Ф“ ReWri te (файловая_переменная) —открыть для записи; Ф- Append(файловая_переменная) — открыть для пополнения. В файл, открытый для записи или пополнения, можно только писать, из файла, открытого для чте- ния — можно только считывать данные. Для дополне- ния информации в существующий текстовый файл его необходимо открыть процедурой Append. При этом файл откроется для записи, а указатель текущей 8 Зак. №933
Глава 7 Что посеешь, то и пожнешь, если позиции установится в конец файла, после чего в файл можно писать — информация будет добавлять- ся в его конец. В завершение следует закрыть файл процедурой Close(файловаяпсременная). Только после закры- тия файл будет сформирован окончательно. Именно Close сообщает операционной системе все сведения о созданном файле (дату его создания, размер и т. д.)_ Как исправить опечатку. Как исправить ошибку, допущенную в текстовом файле? Допустим, мы на- брали в строке слово «Кит» вместо слова «Кот». Про- ще всего, конечно, считать файл в текстовый редак- тор, исправить все ошибки и заменить старый файл новым. Это «ручная» работа. А что, если исправление файла надо автоматизировать? Это может понадо биться, например, при обработке большого числа файлов, а также в некоторых других ситуациях. Исправить файл можно средствами Паскаля. Для этого достаточно скопировать один текстовый файл в другой. В процессе чтения строк текста из первого файла в переменную S можно просматривать содер- жимое каждой строки, редактируя ее в случае необхо- димости. В данном случае заранее известно, какое слово следует заменить заданным фрагментом текста. Исправленная строка записывается в новый файл. Вот пример программы: program kit_kot; var FILI, FIL2 : text; s : String; x : Word; {Вспомогательная переменная} begi n Assign(FILl, ’ASPUSHKN.TXT’); Reset(FILl); {Открыли файл ASPUSHKN.TXT для чтения}
Текстовые файлы 227 Assign(FIL2, ’ASP1.TXT’); ReWrite(FIL2) ; {Файл ASP1.TXT открыт для записи} while not EOF(FILl) do begi n ReadLn(FILl, s) ; {Считываем строки из ASPUSHKN.TXT в s и ищем позицию вхождения слова ’Кит’ в строку s} х := Pos(’кит ’ , s) ; {Исправляем строку - если в ней есть Кит, удаляем «кита» и вставляем «кота»} if х <> 0 then begi п Delete(s, х. Length(’кит’)); Insert(’кот’, s, х) end ; WriteLn(S); {Выводим исправленную строку на экран} WriteLn(FIL2 , S) ; {Записываем исправленную строку в файл ASP1.TXT} end ; Close(FILl) ; Close(FIL2) ; end. Как «склеить» текстовые файлы. Задача заклю- чается в том, чтобы объединить два текстовых файла в один, сохранив порядок расположения строк. Для выполнения этой операции предназначена программа cat. Разберите ее самостоятельно: program cat; var fill, fil2, fil3 : text; si, s2, s3 : String;
228 Глава 7 Что посеешь, то и пожнешь, если.. begi п Assign(fill, ’tl.txt’); Assign(fil2,’t2.txt’); Assign(fil3, ’t3.txt’) ; {Открываем файлы, связанные с переменными fill, fil2 для чтения, a fil3 для записи} Reset(fill); Reset(fi12) ; ReWrite(fil3); ReadLn(fill, si); ReadLn(fil2, s2) ; while not(Eof (fi11)) or not(Eof(fi12)) do if si < s2 then begi n Wr iteLn(fi13, si); ReadLn(fill, si); end else begi n WriteLn(fi13 , s2) ; ReadLn(fil2, s2); end; if Eof(fill) then while not(Eof(fi12)) do begi n ReadLn(fi12 , s2); Write(fil3, s2); ^nd else while not(Eof(fi11)) do begin ReadLn(fill, s2); W.'ite(fil3, s2); end;
Текстовые файлы 229 Close(fi11); Close(fi12); Close(fi13); end. Устройства в Турбо Паскале. Турбо Паскаль по- зволяет работать с такими устройствами, как клавиа- тура, принтеры, дисплей и т. д. С точки зрения про- граммиста, эти устройства можно рассматривать как внешние файлы и обращаться с ними как с внешними файлами. Каждое устройство имеет зарезервированное имя. Вот некоторые из них. Консоль — это клавиатура и дисплей, обеспечивающие ввод и вывод информации. Ввод производится с клавиатуры, а вывод — на дис- плей. Имя файла, связанного с консолью, — CON. В программах оно используется в каждый конкретный момент только для ввода или только для вывода — подобно текстовым файлам, которые также доступны только для ввода или только для вывода. Есть два стандартных файла, которые связаны с консолью по умолчанию (то есть программисту не надо предпринимать никаких действий, чтобы свя- зать консоль и эти файлы). Это — файл ввода Input и файл вывода Output. Процедура ReadLn(S) по умол- чанию работает как ReadLn (Input, S) и считывает в переменную S информацию с клавиатуры. Проце- дура WriteLn(S) действует так же, как процедура WriteLn(Output, S), и выводит информацию из пе- ременной S на экран монитора. Принтеры — с принтерами связаны специальные файлы PRN, LPT1 (первый принтер), LPT2 (второй принтер), LPT3 (третий принтер). Для построчной пе- чати можно использовать любой из трех файлов LPT1, LPT2, LPT3.
230 Глава 7 Что посеешь, то и пожнешь, если... Если требуется вывести информацию не на экран, а на принтер, нужно указать в качестве имени файла специальный файл устройства: Assign(FIL, ’PRN’); ReWri te(FIL); С этого момента вся информация, выводимая про- цедурами Write HWriteLn, будет поступать не на эк- ран, а на принтер. Вернуться к вводу и выводу ин- формации с консоли можно с помощью процедур: Assign(FIL, ’CON’); ReWri te(FIL); Иногда возникает необходимость в одной про- грамме выводить информацию и на экран, и на прин- тер. Вывод на экран выполняется, как обычно, про- цедурой Wr i teLn (списоквывода). Для вывода на принтер используется процедура WriteLn в формате Wri teLn (1st, список_вывода), где 1st — имя файла, связанного с принтером. Это имя объявлено в модуле Printer, который должен быть подключен операто- ром uses Printer. Типизированные файлы Как говорилось ранее, типизированные файлы допус- кают как последовательный, так и прямой доступ. Элементы таких файлов должны быть одного типа, а следовательно, и одного размера. Это могут быть чй’сла, массивы или записи. Нумеруются элементы файла целыми числами, начиная с нуля, и именно благодаря этому возможен произвольный доступ к любому элементу типизированного файла — по номе- ру однозначно определяется местоположение элемен- та файла.
Типизированные файлы 231 Файловая переменная для типизированного файла описывается так: var имя : file of базовый_тип; Пример описания: var next : file of Char; {Компонентами файла являются символы} look : file of String[20]; {Файл состоит из строк до 20 символов каждая} В типизированных файлах информация хранится в машинном представлении. Никаких преобразова- ний при вводе-выводе не происходит, за счет этого возрастает скорость ввода-вывода, но зато такая ин- формация непригодна для просмотра в обычных тек- стовых редакторах. Для того чтобы прочитать файл или, наоборот, произвести в него запись, необходимо, как и при ра- боте с текстовыми файлами, установить связь между файловой переменной и файлом на диске компьюте- ра. Это делается с помощью процедуры: Assi gn(имя_файловой_переменной, ’prim’); Затем файл открывается процедурой: Reset(имя_файловой_переменной); или ReWri te(имя_Файловой_переменной); Процедурой Reset открываются уже существую- щие файлы, ReWri te — новые. Если файл уже был от- крыт, то он сначала закрывается, а затем открывается вновь. В отличие от текстовых типизированные файлы допускают операции как записи, так и чтения незави-
232 Глава 7 Что посеешь, то и пожнешь, если спмо от того, какой процедурой файл открыт. Закры- вается файл процедурой Close. Чтение из типизированного файла производится процедурой: Read(имя_файловой_переменной, vl, v2.... vN) ; , а запись — процедурой: Wri te(имя_файловой_переменной, vl, v2.... vN) ; , где vl, v2 , .... vN — переменные базового типа (того же, что и элементы файла). Произвольный доступ к элементам файла осуществляется процедурой: Seek(имя_файловой_переменной, номерэлемента : Longlnt); Эта процедура устанавливает указатель файла на элемент, номер которого является вторым парамет- ром процедуры Seek. Именно в этот элемент будет записано значение переменной vl: Write(имя_файловой_переменной, vl); или считано значение: Read(имя_файловой_переменной, vl); Текущее положение указателя файла определяет- ся функцией: FilePcs(имя_файловой_переменной) : Longlnt;, а общее количество записей — функцией: FileSize(имя_файловой_переменной) : Longlnt: Поскольку типизированные файлы не разбиты на строки, процедуры ReadLn HWriteLn для них не име- ют смысла. Позвони мне, позвони... Программа phones создает телефонный справочник. Телефоны, вводимые поль-
Типизированные файлы 233 зователем после запуска программы, записываются в типизированный файл nomera: program phones; type Zapis = record {Файл nomera состоит из элементов типа запись} fam : Stri ng[20]; tel : String[6] end; var out : file of Zapis; nam : Zapis; kon : Char; beg -i n Assign(out, ’nomera’); ReWri te(out); repeat Wr te(’Введите фамилию абонента:’); ReadLn(nam.fam); Write(’Введите номер телефона:’); ReadLn(nam.tel); Wri te(out, nam); {Первая запись помещается в нулевой элемент файла nomera} WriteLn(’Будете продолжать? Y/N’); ReadLn(kon); untiI kon <> ’Y’; {Замена первой записи последней} Seek(out, FileSize(out) - 1); {FileSize(out) определяет общее количество записей} Read(out, nam); {Считываем последнюю запись во вспомогательную переменную nam} Seek(out, 0);
234 Глава 7 Что посеешь, то и пожнешь, если... Wri te(out, nam); {Выведем содержимое файла на экран} Reset(out) ; {Без закрытия файла не будут видны изменения, reset закроет файл, а потом откроет его заново} while not Eof(out) do begi n Read(out, nam); WriteLn(nam.fam, nam.tel); end; Close(out); end. Нетипизированные файлы Для быстрого выполнения операций ввода/вывода из внешних файлов в Паскале используют нетипизиро- ванные файлы. Нетипизированные файлы дают воз- можность прямого доступа к данным. Описание нетипизированной файловой перемен- ной имеет вид: var untyped_file : file; Такая файловая переменная связывается с внеш- ним файлом как обычно. Попробуйте самостоятельно написать три вариан- та программы, записывающей в текстовый, типизиро- ванный и нетипизированный файлы одну и ту же по- следовательность целых чисел. Сравните размеры полученных файлов. Итог Мы научились «сеять» — записывать в файл инфор- мацию, которую необходимо сохранить и «жать» , то
Итог 235 есть считывать информацию из файла. С файлами ра- ботает любая программа, поэтому научиться програм- мировать файловые операции — важно. Для того что- бы лучше усвоить материал этой главы, предлагаем самостоятельно решить несколько задач. Задача 1. Написать программу, которая создает файл с информацией об абитуриентах. В этом файле сведе- ния хранятся в виде записи, содержащей следующие поля: 4 фамилия, имя и отчество; 4 год рождения; 4 средний балл в аттестате; 4 оценки вступительных экзаменов. Задача 2. Написать программу, которая из внешнего файла, содержащего данные об абитуриентах, удаля- ет записи, соответствующие: 4 абитуриентам, получившим хотя бы одну двойку; 4 абитуриентам, имеющим средний балл по экзаме- нам меньше 3.5. Задача 3. Набрать текстовый файл «Кит и Кот» Кит и Кот В этой сказке нет порядка: Что ни слово — то загадка. Вот что сказка говорит: Жили-были Кот и Кит. Кот — огромный, просто страшный, Кит был маленький, домашний. Кит мяукал, Кот пыхтел. Кит купаться не хотел, Как огня, воды боялся. Кот всегда над ним смеялся. Время так проводит Кит:
236 Глава 7 Что посеешь, то и пожнешь, если... Ночью бродит, днем храпит. Кот плывет по океану, Кит на блюдце ест сметану. Переправим Кит на Кот, Кот на Кит, наоборот. Написать программу, которая исправит этот файл, заменив «Кот» на «Кит» и наоборот. Задача 4. Пусть имеется типизированный внешний файл, описанный как file of Char. Написать про- грамму, которая считывает значения из файла и нахо- дит, сколько раз каждый алфавитно-цифровой сим- вол встречается в этом файле. Задача 5. Написать программу, которая считывает записи из одного файла и записывает их в обратном порядке во второй файл. Первый элемент первого файла становится последним элементом второго фай- ла, второй элемент — предпоследним и т. д.
ГЛАВА 8 Восточный мотив, или Рисуем Королеву Красоты. Работа с графикой
Маршрут нашего путешествия ведет нас в волшеб- ную восточную страну, где живет сын бедного порт- ного Ала ад-Дин. Случилось так, что увидел он пре- красную дочь султана госпожу Бадр аль-Будур. «...Бы- ла она высока ростом, красива, прелестна и совер- шенна, стройна и соразмерна, с сияющим лбом и ру- мяным лицом, с глазами, напоминающими серн и газелей, и бровями, подобными луку новой луны Ее щеки были как анемоны, и рот как соломонова печать, и алые губки как коралл, и зубки как стройно нанизанный жемчуг или цветы ромашки, а шея как у газели... Ум Ала ад-Дина был пленен этой девушкой, и любовь к ней ошеломила его, страсть к ней захвати- ла все его сердце, и он вернулся домой и вошел к сво- ей матери, ошеломленный, потеряв рассудок...» Ну а что произошло дальше, знает каждый, кто читал ис- торию про Ала ад-Дина и волшебный светильник в сказках «Тысячи и одной ночи»... Часто бывает так, что начинающий программист, познакомившись с «прелестями» компьютерной гра- фики, если и не теряет рассудок, то, во всяком случае, покоряется беспредельным изобразительным возмож- ностям компьютера. Ведь с помощью компьютера можно создавать такие картины, которые просто не- мыслимы на обычном холсте. Можно, конечно, вос- пользовавшись этими возможностями, попробовать нарисовать и Королеву Красоты, пленившую сердце ад-Дина. Текстовый и графический режимы Прежде чем приступать к рисованию на экране дис- плея, давайте разберемся, что же такое «графический
Текстовый и графический режимы 239 режим». Основным устройством компьютера для вы- вода информации является его монитор. Монитор внешне очень похож на телевизор, но у него имеется важная особенность. Эта особенность заключается в том, что у телевизора один-единственный с точки зрения вывода изображения режим работы, а у ком- пьютерного монитора их два. Это текстовый и гра- фический режимы. Различие между текстовым и графическим режи- мами работы монитора заключается в возможностях управления выводом на него информации. В тексто- вом режиме наименьшим объектом, выводимым на экран, является символ. Экран монитора, работающе- го в текстовом режиме, может содержать не более 80 символов по горизонтали и 25 строк по вертикали, то есть всего 2000 символов (или «знакомест»). При этом имеются небольшие возможности по управле- нию цветом символов. Конечно, в таком режиме мож- но выводить на экран не только обычный текст, но и простые графические изображения, качество которых будет вне всякой критики. В 60-е годы этот метод был единственным и поэтому очень популярным спо- собом вывода графиков и целых картин как на экран, так и на принтер. Программистам удавалось созда- вать настоящие шедевры «компьютерной псевдогра- фики», которые украшали стены вычислительных центров. Но для серьезной работы с изображениями текстовый режим дисплея не подходит. В графическом режиме минимальным объектом, который можно вывести на экран, является пиксел (от английского pixel, возникшего в результате объе- динения слов «рисунок» (picture) и «элемент» (ele- ment)). Пиксел — это точка. Размер пиксела меньше размера символа (на один символ в текстовом режи- ме отводится площадка размером в несколько пиксе-
240 Глава 8 Восточный мотив, или Рисуем Королеву Красоты лов). Его геометрические размеры определяются раз- решением монитора. Разрешение монитора задается в виде гх х ту , где гх — количество пикселов, умещаю- щихся на экране по горизонтали, а ту — количество пикселов по вертикали. На практике используются не произвольные, а некоторые определенные значе- ния разрешения. Такими разрешениями являются, например: + 320x200; + 640x480; + 800x600; + 1024x768; + 1280x1024 — и т. д. При самом грубом разрешении изображение в гра- фическом режиме формируется с помощью 64 000 графических элементов, что намного превышает воз- можности текстового режима. Из такого количества «строительных кирпичиков» можно построить не- плохую картинку! О том, как это делается в Турбо Паскале, мы сейчас и поговорим. Инициализация графического режима После запуска интегрированной среды Турбо Паска ля включается текстовый режим и для использова- ния графики необходимо предпринять ряд действий по переходу в графический режим. Прежде всего необходимо подключить модуль Graph Турбо Паскаля. В этом модуле описаны проце- дуры и функции, предназначенные для работы с гра- фическим экраном, а также некоторые встроенные константы и переменные, которые могут быть ис- пользованы в графических программах. Для того
Инициализация графического режима 241 чтобы воспользоваться всеми возможностями модуля Graph, в начале программы (после ее заголовка) не- обходимо разместить оператор использования: uses Graph; Основную часть модуля составляют процедуры вывода основных графических элементов, таких как точки, отрезки прямых линий, дуги и целые окруж- ности и т. д. Такие элементы называются графически- ми примитивами. Другая группа процедур предназна- чена для управления графическим режимом. Всего в библиотеке модуля Graph находится более 50 проце- дур и функций для работы с графикой. Далее следует определить тип видеоадаптера, уста- новленного на компьютере. Видеоадаптером называют набор микросхем, управляющих работой конкретно- го дисплея. Можно поручить выяснить тип видео- адаптера программе. Как? Немного терпения, и вы это узнаете. Как почти всякое физическое устройство ком- пьютера, видеоадаптер может работать только в том случае, когда в память загружена программа, управ- ляющая его работой. Такая программа называется драйвером устройства (driver в переводе с англий- ского языка — «шофер», так что драйвер является водителем», управляющим работой устройства). Графические драйверы содержатся в файлах с расши- рением .BGI (например, EGAVGA.BGI или IBM8514.BGI). В Турбо Паскале уже имеется необходимый набор BGI-файлов, поэтому программисту остается лишь казать в соответствующем месте программы распо- гожение каталога, содержащего эти файлы. Параметры графического режима. Графический сжим работы определяется тремя параметрами: . Разрешение экрана.
242 Глава 8 Восточный мотив, или Рисуем Королеву Красоты 2. Палитра, то есть количество возможных цветов (от двух в монохромных дисплеях до 16 и более — в цветных). 3. Число видеостраниц, то есть количество полноэк- ранных изображений, которые могут одновремен- но храниться в видеопамяти компьютера (обычно 4, 2 или 1 страница — чем больше разрешение, тем меньше страниц). Как определить тип видеоадаптера. Итак, програм- ма при переключении в графический режим должна определить тип видеоадаптера. Это можно сделать, указав в программе тип видеоадаптера или «пору- чив» программе самостоятельно определить значение соответствующих параметров. Для этого необходимо ввести переменную целого типа, например gd. При явном определении видеоадаптера в программе дол- жен присутствовать оператор присваивания: gd := value; где value — это либо некоторое число (код видео- адаптера), либо встроенная константа (напомним, что встроенные константы не надо описывать специаль- но, так как их описания содержатся в модулях). Зна- чения кодов можно определить с помощью справоч- ной системы Турбо Паскаля, но лучше использовать автоматическое распознавание видеоадаптера, когда в правой части оператора присваивания используется встроенная функция Detect (или нулевое значение). Установка определенного графического режима Второе, что должна сделать программа, — задать оп- ределенный графический режим. Для этого следует ввести еще одну переменную целого типа, например gm, и присвоить ей соответствующее значение. Неко- торые допустимые значения приведены в табл. 8.1.
Инициализация графического режима 243 Таблица 8.1. Графические режимы Константа Значение Описание графического режима EGALo 0 640x200, 16 цветов, 4 страницы EGAHi 1 640x350, 16 цветов, 2 страницы EGA64Lo 0 640x200, 16 цветов, 1 страница EGA64Hi 1 640x350, 4 цвета, 1 страница HercMonoHi 0 720x348, 2 страницы VGALo 0 640x200, 16 цветов, 4 страницы VGAMed 1 640x350, 16 цветов, 2 страницы VGAHi 2 640x480, 16 цветов, 1 страница IBM8514Lo 0 640x480 точек, 256 цветов IBM8514Hi 1 1024x768 точек, 256 цветов Каждый из перечисленных в таблице режимов может использоваться только с определенным видео- адаптером. В столбце «описание графического режи- ма» приведены разрешение, количество цветов и ко- личество страниц. Процедура InitGraph. Переход в графический режим (инициализацию графического режима) вы- полняет процедура (из модуля Graph) InitGraph. Только после ее вызова становится возможным ис- пользование подпрограмм графики: InitGraph(gd, gm, 'c:\tp\bgi'); Первый и второй параметры этой процедуры нам уже знакомы, а третий представляет собой строку с
244 Глава 8 Восточный мотив, или Рисуем Королеву Красоты указанием расположения драйвера на диске. Пустая строка означает, что графический драйвер находится в том же каталоге, что и программа. Для большинст- ва современных видеоадаптеров можно использовать драйвер EGAVGA.BGI. Процедура InitGraph переводит систему в графический режим, а затем возвращает управление вызывающей программе. Вызвать ее мож- но в любом месте программы. Завершение работы в графическом режиме произ- водится с помощью процедуры CloseGraph, которая выгружает драйвер из памяти и восстанавливает пре- дыдущий видеорежим. Стандартный вид программы, использующей гра- фическую библиотеку, имеет следующий вид: program risunok; uses Graph; {Подключение стандартной графической библиотеки} var gd, gm : Integer; begi n gd := Detect; gm := 1; InitGraph(gd, gm, ’c:\bp7\bgi’), {Инициализация графического режима, “c:\bp7\bgi” - путь, указывающий местоположение .BGI-файлов на вашей машине; если путь задан неверно, то графика не инициализируется, поэтому будьте внимательны!} Операторы, использующие графическую библиотеку
Команды графического режима 245 CloseGraph; {Завершение графического режима} end . Команды графического режима Графические координаты. Любое изображение фор- мируется из простых геометрических фигур. Это точ- ки, отрезки прямых, окружности и т. д. Из геометрии известно, что положение геометрического объекта и его форма задаются координатами его точек. Для того чтобы запрограммировать вывод изображения, надо научиться задавать координаты графических объек- тов. Графические координаты задают положение точки на экране дисплея. Поскольку минимальным элемен- том, к которому имеет доступ программист, являет- ся пиксел, в качестве графических координат исполь- зуют порядковые номера пикселов. Допустимый диапазон изменения графических координат состав- ляет [0, гх — 1] для х-координаты и [О, ту — 1J для у-координаты. Началом отсчета является верхний ле- вый угол экрана. Значения х-координаты отсчитыва- ются слева направо, а «/-координаты — сверху вниз (рис. 8.1). Последнее отличает графические коорди- наты от обычных декартовых координат, принятых в математике, и служит неиссякающим источником ошибок для начинающего программиста. Проблема заключается в том, что при разработке программы график или другое изображение обычно разрабатывается в привычной для нас декартовой системе координат. Для правильного отображения такого графика на экране необходимо учесть три раз- личия между декартовой и графической системами координат.
246 Глава 8 Восточный мотив, или Рисуем Королеву Красоты Рис. 8.1. Графические координаты Проблема заключается в том, что при разработке программы график или другое изображение обычно разрабатывается в привычной для нас декартовой системе координат. Для правильного отображения такого графика на экране необходимо учесть три раз- личия между декартовой и графической системами координат. 1. Графические координаты принимают только цело- численные значения. 2. Графические координаты принимают значения, ограниченные как снизу (нулевым значением), так и сверху (значением разрешения). 3. Графическая координата у отсчитывается сверху вниз. Геометрические декартовы координаты точки (х, у) для отображения ее на экране следует пересчитать в графические (xg, yg) по формулам: xg = Lxx х xj + dx, yg = ry - \_sy * y\ ~ dy,
Команды графического режима 247 где LxJ — целая часть х, sx и sy — масштабные множи- тели, выбираемые из условия: тх = L-sx х xinaxJ + 1, ry = {sy К 2/maxJ + 1. Здесь Хщах и т/max — максимальные значения геомет- рических координат. Пересчет координаты у по такой же формуле, что и для х, привел бы к зеркальному от- ражению изображения относительно горизонтальной оси. Слагаемые dx и dy обеспечивают смещение изо- бражения относительно левого верхнего угла экрана. Изображение будет смещено в центр экрана при: dx = Lrx / 2 J, dy = L?i/ / 2j. Все процедуры и функции модуля Graph можно разбить на следующие группы: + управление графическими режимами и их анализ (InitGraph, CloseGraph, DetectGraph и другие); + рисование графических примитивов и фигур: < управление текущим указателем (MoveTo, Move- Rel, GetMaxX, GetMaxY, GetX, GetY); < собственно рисование (Line, Ci rcle, Ellipse и другие, всего 10); стиль линий и коэффициент сжатия изобра- жения (SetLineStyle, GetLineSettings, Set- AspectRatio, GetAspectRatio); + управление цветами и шаблонами заполнения (SetColor, GetColor, GetPalette и другие); + битовые операции (PutPixel, GetPixel, Image- Size, Getlmage, Putlmage); + управление видеостраницами (SetActivePage, SetVi sualPage);
248 Глава 8 Восточный мотив, или Рисуем Королеву Красоты + операции по управлению графическими окнами (SetViewPort, GetViewSett1 ng, ClearVi ewPort); + управление выводом текста (OutText, OutTextXY, SetTextStyle и другие). Рассмотрим основные графические процедуры. Управление графическим режимом. PutPixel(x, у, color) — закрашивает точку с графическими ко- ординатами (х, у) цветом color. Цвет можно указы- вать как числом, так и предопределенной константой. Список этих констант и кодов для 16-цветного режи- ма приведен в табл. 8.2. Таблица 8.2. Встроенные константы Турбо Паскаля, обозначающие цвета и соответствующие им цифровые коды Цвет Канстанта Код Черный Black 0 Синий Blue 1 Зеленый Green 2 Бирюзовый Cyan 3 Красный Red 4 Розовый Magenta 5 Коричневый Brown 6 Светло-серый Ugh t Gray 7 Темно-серый DarkGray 8 Светло-синий LightBlue 9 Светло-зеленый LightGreen 10 Светло-бирюзовый LightCyan 11 Светло-красный LightRed 12 Светло-розовый LightMagenta 13
Команды графического режима 249 Цвет Константа Код Желтый Yellow 14 Белый White 15 GetMaxX, GetMaxY — возвращают наибольшие зна- чения графических координат по осям абсцисс и ор- динат соответственно для выбранного графического режима. Например, для разрешения 800x600 эти зна- чения будут равны 799 и 599. SetColor(color) — устанавливает текущий цвет. Все нарисованное вызванными после обращения к SetColor подпрограммами будет иметь цвет color (до нового вызова процедуры SetColor). Исключе- ние составляет процедура PutPixel, которая выпол- няет закрашивание точки в соответствии со своим собственным параметром color. SetBkColor (color) — устанавливает текущий цвет фона экрана. Clear Dev ice — очищает весь экран, закрашивая его текущим цветом фона. Рисование графических примитивов и фигур. Про- цедура SetLineStyle(style , def, thickness) — устанавливает стиль рисуемых линий. Стилем ли- нии называют ее внешний вид. Линия может быть сплошной, тогда аргумент style принимает значе- ние SolidLn, пунктирной (DottedLn), штрихпунк- тирной (CenterLn), штриховой (DashedLn). Стиль может быть задан и программистом (UserBi tLn). Ар- гумент def используется при задании своего стиля линии, a thickness задает толщину линии (1 — нор- мальная толщина, 3 — толстая линия). Процедура Line(xl, yl, х2 , у2) рисует отрезок прямой линии, проведенный между точками с графи- ческими координатами (xl, yl) и (х2, у2).
250 Глава 8 Восточный мотив, или Рисуем Королеву Красоты Circle(x, у. R) выводит окружность с центром в точке (х, у) радиусом R. Агс(х, у, beg, end, R) рисует дугу с центром окружности дуги в точке (х, у). Параметры beg и end — это начальный и конечный углы, отсчитывае- мые от горизонтальной оси против часовой стрелки в градусах, R — радиус окружности. Rectangle (xl, yl, х2 , у 2 )—вывод прямоуголь- ника с координатами левого верхнего угла (xl, yl) и правого нижнего (х2, у2). Использование графического указателя. Некото- рые подпрограммы используют при своей работе в качестве начальной позиции для вывода графический указатель. Этот указатель на экране не отображается. Устанавливает указатель в точку с координатами (х, у) процедура MoveTo(x , у). Используется курсор в основном следующими подпрограммами: + Li пеТо(х , у) — рисует отрезок с началом в точке текущего положения графического курсора и кон- цом в точке (х, у). При этом курсор перемещается в точку (х, у). Эта процедура очень удобна для по- строения сложных ломаных линий. + OutText(’текст’) — выводит указанный текст, начиная с текущего положения графического ука- зателя так, что положение указателя совпадает с ле- вым верхним углом первого выводимого символа. 4- OutTextXY(x, у, ’текст’) — выводит текст, на- чиная с точки (х, у). Способ вывода текста задает- ся процедурой SetTextStyle. Закрашивание областей экрана. Закрашивание от- дельных областей экрана представляет собой более сложную задачу. Способ закрашивания задается про- цедурой SetFi UStyle (style , color). Здесь пер- вый параметр style задает способ заполнения (воз-
Команды графического режима 251 можные значения приведены в табл. 8.3). Это могут быть именованные константы или соответствующие им численные значения, color — цвет заполнения. Таблица 8.3. Стили заполнения геометрических фигур Константа Код Описание EmptyFill 0 Сплошное заполнение цветом фона SolidFill 1 Сплошное заполнение заданным цветом LineFill 2 Заполнение горизонтальными линиями LtSlashFill 3 Диагональное заполнение (///) SlashFill 4 Диагональное заполнение толстыми линиями BkSlashFill 5 Обратное диагональное заполнение толстыми линиями LtBkSlashFill 6 Обратное диагональное заполнение (\\\) HatchFill 7 Клетчатое заполнение XhatchFill 8 Косое клетчатое заполнение InterleaveFill 9 Чередующееся линейное заполнение WideDotFill 10 Редко расположенные точки Close Dot Fill 11 Часто расположенные точки UserFill 12 Стиль определен пользователем Закрашивание прямоугольных областей лучше производить процедурой Bar(xl, yl, х2, у2), кото- рая рисует прямоугольник текущим цветом и закра- шивает его в соответствии с установленным при об- ращении к SetFillStyle стилем. Она отличается от
252 Глава 8 Восточный мотив, или Рисуем Королеву Красоты процедуры Rectangle тем, что в этом случае внут ренняя часть прямоугольника закрашивается заранее определенным образом. В случае процедуры Rec- tangle для заполнения внутренней области фигуры приходится использовать дополнительный вызов про- цедуры модуля Graph. FloodFill(x, у, color) — закрашивает область внутри которой находится точка (х, у). Область долж на быть ограничена замкнутой линией цвета color Этой процедурой следует пользоваться очень осто- рожно — если граница окажется где-либо нарушен ной, то закрасится весь экран! Процедура fillbar демонстрирует вывод пря моугольника и заполнение его внутренней области определенным шаблоном. В ней используются стили заполнения, заданные пользователем: procedure fill_bar(x, у, хх, уу : integer; color, raster : byte); const filpat :array[0..9] of fillpatterntype =( ($ff,$ff ,$ff»$ff,$ff,$ff,$ff,$ff). (0»$fb,$fb,$fb,0,$df,$df,$df), (0,$10,$28,$44.$28,$10,0,0), ($22,0,$88,0,$22,0»$88,0), ($cc,$3 3 , $cc , $3 3 , $cc,$33,$cc,$33), ($aa,$55,$aa,$55,$aa,$55,$aa,$55), ($94,$84,$48,$30,0.$cl,$22,$14), ,A ($aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa) , ($ff,0,$ff,0,$ff,0,$ff,0)); begi n if raster < 10 then begi n SetFiIIPattern(fil_pat[raster], color); SetFi UStyle (12 , color) ; Bar(x, у , хх , yy);
Команды графического режима 253 end; end ; Первые четыре параметра этой процедуры зада- ют левый верхний и правый нижний углы прямо- угольной области. Пятый параметр определяет цвет заполнения, а шестой является индексом, позволяю- щим выбрать из массива fil_pat элемент, содержа- щий пользовательский стиль заполнения. Каждый элемент этого массива имеет тип fillpatterntype, (встроенный тип модуля Graph). Переменная типа fillpatterntype, в свою очередь, является масси- вом, состоящим из восьми элементов типа Byte. Каж- дая такая переменная описывает шаблон (маленькую картинку, которой заполняется область) размером 8x8 пикселов. Шаблон можно рассматривать как таб- лицу соответствующего размера, элементы которой принимают единичное или нулевое значение. Еди- ничное значение соответствует «подсвеченному» пик- селу, а нулевое — «погашенному». Так, например, элементу массива шаблонов f i l_pat [0] соответству- ет сплошная закраска, а второму — картинка, пред- ставленная в табл. 8.4: Таблица 8.4. Шаблон заполнения, соответствующий элементу fil pat[l] 0 0 0 0 0 0 0 0 $00 1 1 1 1 1 0 1 1 $fb 1 1 1 1 1 0 1 1 $fb 1 1 1 1 1 0 1 1 $fb 0 0 0 0 0 0 0 0 $00 1 1 0 1 1 1 1 1 $df 1 1 0 1 1 1 1 1 $df 1 1 0 1 1 1 1 1 $df
254 Глава 8 Восточный мотив, или Рисуем Королеву Красоты Рисуем Королеву Красоты. Ну вот, теперь можн< попробовать нарисовать царевну Будур или хотя бь изображение похожего на нее человечка. program man; uses Graph; var gd, gm : Integer; begi n gd := Detect; InitGraph(gd, gm, ’’); SetFillStyle(l, Green); {Трава} Bar(0, 350, 639, 479); SetFillStyle(l, LightBlue) ; {Небо} FloodFi1I(0, 0, Green); SetColor(Red); Circle(320, 200, 19); {Г олова} SetLineStyle(0, 0, 3); Rectangle(300, 220, 340, 300); Line(320, 300, 300, 350); {Туловище} {Ноги} Line(320, 300, 340, 350); Line(300, 240, 250, 250); {Руки} Line(340, 240, 390, 250); SetFi UStyle (1, Red); {Закрашивание FloodFi11(320, 200, Red); FloodFi11(320, 230, Red); SetColor(Yellow); Circle(315, 190, 2); человечка} {Левый глаз} Circle(325, 190, 2); {Правый глаз} Line(315, 210, 325, 210); {Рот} Readln; CloseGraph; end.
Команды графического режима 255 Ну как? Нарисованный программой человечек... конечно, далеко не прекрасная дочь султана! Но в Турбо Паскале есть достаточно средств и возможно- стей для того, чтобы создать по-настоящему краси- вую картинку. В дальнейшем мы познакомимся с не- которыми из них. Рассмотрим несколько примеров, иллюстрирую- щих работу команд графического режима. Разноцветные полосы. Нарисовать 14 разноцвет- ных вертикальных полос, окрашенных 14 цветами (кроме белого и черного), можно с помощью проце- дур рисования линий и установки цвета: program stripes; uses Graph; var gd, gm, c, x, y. i : Integer; begi n gd : = Detect; InitGraph(gd, gm, ’’); SetBkColor(white); ClearDevi ce; x := O; for c := 1 to 14 do begi n x := x + 35; SetColor(c); Li ne(x, 0, x, 400); for i :=1 to 5 do Line(x + i, 0, x + i, 400); {Рисуем 6 полос} end; Readln; CloseGraph; end.
256 Глава 8 Восточный мотив, или Рисуем Королеву Красоты Паутина. Программа web («паутина») выводит на экран изображение, составленное из отрезков пря- мых и концентрических окружностей. Общий центр точки пересечения отрезков и окружностей находит- ся в центре экрана независимо от установленного разрешения. Чтобы изображение не зависело от раз- решения, здесь используются функции GetMaxX и GetMaxY. Графические координаты правого нижнего угла экрана равны (GetMaxX, GetMaxY). В программе используется процедура Delay моду- ля Crt. Эта процедура приостанавливает выполнение программы на указанное количество миллисекунд. Программа прекращает свою работу при нажатии клавиши Enter. program web; uses CRT, Graph; var i : Wo r d; gd, gm : Integer; begi n gd := Detect; gm : = 0 ; InitGraph(gd, gm, ’’); SetBkColor(Blue); SetColor(LightCyan); Line(0, 0, GetMaxX, GetMaxY); Delay(1000); SetColor(Yellow); Line(0, GetMaxY, GetMaxX, 0); Delay(1000); SetColor(LightGreen); Line(0, GetMaxY div 2, GetMaxX, GetMaxY div 2); Delay(1000);
Команды графического режима 257 SetColor(LightGray) ; Line(GetMaxX div 2, 0, GetMaxX div 2, GetMaxY); Delay(1000); SetColor(LightRed); for i:=2 to 20 do begi n SetColor(16 - i div 2); Circle(GetMaxX div 2, GetMaxY div 2, GetMaxY div i); Delay(500 - 15 * i); end; ReadLn; CloseGraph; end. Диагональные линии. Попробуем в графическом режиме заштриховать экран линиями, параллель- ными одной из его диагоналей. Расстояние меж- ду линиями выберем 20 пикселов по вертикали (см. рис. 8.2). Возьмем диагональ, соединяющую левый верхний и правый нижний углы экрана. Эта задача на первый взгляд кажется простой, однако при ее решении воз- никают трудности, связанные с тем, что диагональ- ные линии не параллельны биссектрисе угла, а на- клонены под некоторым углом (экран дисплея — не правильный квадрат, а прямоугольник). В этом слу- чае довольно сложно подсчитать координаты концов отрезков. Тем не менее эту задачу можно очень про- сто решить, если воспользоваться одним свойством графического режима — если рисуемая фигура не по- мещается в поле экрана, то отображается только ее видимая часть, остальное же просто не принимается во внимание, ошибки при этом не возникает. Линии, 9 Зак. № 9зз
258 Глава 8 Восточный мотив, или Рисуем Королеву Красоты начинающиеся на верхней границе экрана, можно ри- совать начиная с продолжения левого края. Правда, у части этих линий ^-координата начальной точки бу- дет отрицательна, но, как уже говорилось, ошибки при этом не возникнет. Рис. 8.2. Заполнение графического экрана диагональными линиями program diagonals; uses Graph; var gd, gm, i : Integer; begi n gd := Detect; InitGraph(gd, gm, ’’); i := -GetMaxY; •’’while i <= GetMaxY do begi n Line(0, i, GetMaxX, i + GetMaxY); i := i + 20; end; Readln;
Команды графического режима 259 CloseGraph; end. Пускаем пузыри. Следующая программа рису- ет картинку, состоящую из разноцветных кружков (рис. 8.3). Попробуйте разобрать ее самостоятельно. Рис. 8.3. Вывод программы «пузыри» program bubbles; uses Graph, Crt; var gd, gm, ec, uf : Integer; begi n gd := Detect; gm := 0; InitGraph(gd, gm, ’’); Randomize; {Инициализация датчика случайных чисел} for gm := 1 to 500 do begin ec := ec + 1; if ec = 16 then ec := 1;
260 Глава 8 Восточный мотив, или Рисуем Королеву Красоты SetColor(ес); Ci rcle(Random(GetMaxX), Random(GetMaxY), Random(gd + 10)); end; SetColor(Random(White)); SetTextStyle(0, HorizDir, 5); OutTextXY(50, 20, ’Bubbles’); ReadKey; CloseGraph; end. Привет от Винни-Пуха. Программа vinnie рисует 16 разноцветных кружков, равномерно расположен ных на отрезке прямой, соединяющем точки (xl, yl) и (х2, у 2), в каждом из которых помещен один сим вол текстовой строки: program vinnie; uses Graph, Crt; var xl, yl, x2, y2, c, r : Integer; x, y, dx, dy : Real; gm, gd : Integer; a : String; begi n a := ' Vinnie The Pooh '; gd ;= Detect; InitGraph(gd, gm, ''); WriteLn('Введите xl, yl, x2, y2:'); Readln(xl, yl, x2, y2); ClearDevi ce; dx := (x2 - xl) / 19; dy : = (y2 - yl) / 19; SetColor(Green); r := Round(Sqrt(dx * dx + dy * dy) / 2); x : = xl;
Команды графического режима 261 у := yl; for с := 0 to 18 do {с-цвет} begin SetColor(c mod 13 + 1); SetTextStyle(l,0,2); Circle(round(x), round(y), r) ; OutTextXY(Round(x), Round(y), a[c + 1] ) ; x := x + dx; у := у + dy; end; Readln; CloseGraph; end. Для использования процедур Write и WriteLn в графическом режиме в этой программе используется операция присваивания: DirectVideo := false; Di rectVideo — это переменная, предопределенная в Турбо Паскале (модуль Crt), с помощью которой происходит управление процессом вывода информа- ции на графический экран посредством процедур Write и WriteLn (в этом случае курсор на экран не выводится). При обращении к процедуре Clear- Device графический экран очищается. Павлин. Большое число разнообразных картинок может быть построено на основе математических за- висимостей. Созданные таким образом фигуры часто удивляют своей красотой и необычностью. Програм- ма «Павлин» фиксирует на экране положение отрез- ка, концы которого перемещаются следующим обра- зом: один из концов движется по горизонтальной прямой, проходящей через центр экрана, а для вычис- ления координат другого конца используются триго- нометрические функции Sin(x) и Cos(x). Результи-
262 Глава 8 Восточный мотив, или Рисуем Королеву Красоты рующее изображение напоминает сказочную птицу (рис. 8.4). Рис. 8.4. Павлин — результат выполнения программы pavlin program pavlin; uses Graph, Crt; var gd, gm, xl, x2, a, y2 : Integer; begi n gd Detect; InitGraph(gd, gm, ’’); Randomi ze; xl := 0; a := 1; While xl <= 600 do begi n x2 := Round(320 + 140 * Sin(xl / 30)); y2 := Round(240 + 140 * Cos(xl / 30)); Delay(100); Li ne(xl, 240, x2, y2) ; xl := xl + 2; a := a + 1;
Вывод текста 263 if а > 14 then а := 1; end; Readln; CloseGraph; end. Вывод текста Обратимся теперь к проблеме вывода текста в графи- ческом режиме. Для вывода текстовой информации используются шрифты. Шрифты различаются начер- танием символов. При работе с графикой Турбо Пас- каля используются два вида шрифтов, различающих- ся своим внутренним форматом, — растровый (он один) и векторные (их несколько). Растровый символ задается с помощью таблицы (матрицы) элементов изображения этого символа. Матрица имеет размер 8x8 пикселов. Векторный шрифт задается набором векторов, которые указывают гра- фической системе, как рисовать символ. Разница между растровым и векторными шрифта- ми становится очевидной при отображении символов увеличенного размера (см. рис. 8.5). Поскольку векторный шрифт задается векторами, при увеличении такого шрифта качество и разреше- ние остаются хорошими. При использовании растро- вого шрифта для отображения увеличенных симво- лов битовая матрица умножается на масштабный коэффициент, а когда этот масштабный коэффици- ент большой, разрешение становится низким. Для вывода мелких надписей можно использовать растро- вый шрифт, но для больших символов рекомендуется использовать векторные шрифты.
264 Глава 8 Восточный мотив, или Рисуем Королеву Красоты Raster Font Vector Font Рис. 8.5. Растровый (верхняя надпись) и векторный (нижняя надпись) шрифты Использование различных шрифтов демонстриру ет программа fonts. Она содержит процедуру, позво- ляющую управлять размещением текста на экране, — SetTextStyle. Векторные шрифты существуют в виде отдельных файлов, имеющих расширение .CHR и расположенных в том же каталоге Турбо Паскаля что и графические драйверы (то есть BGI). Чтобы программа выполнялась правильно, ей должны быть доступны файлы со всеми необходимыми шрифтами. Если графическая программа не сможет найти необ- ходимый шрифт, будет использоваться шрифт, задан- ный по умолчанию. В нашем случае для нормальной работы программы в рабочем каталоге программы должны находиться файлы, перечисленные в табл. 8.5. Таблица 8.5. Векторные шрифты Турбо Паскаля Константа Код Файл TriplexFont 1 trip.chr SmallFont 2 litt.chr SansSerifFont 3 sans.chr GothicFont 4 goth.chr Стандартные векторные шрифты не содержат рус- ских букв. Для вывода текста используется проце- дура OutTextXY. Способ вывода текста можно задать,
Вывод текста 265 вызвав предварительно процедуру SetTextStyle. Пер- вый параметр этой процедуры задает шрифт, вто- рой — направление текста, горизонтальное (встроен- ная константа Hcri zDi г) или вертикальное (VertDi г), и, наконец, третий — размер шрифта (масштабный множитель). Для растрового шрифта значение 1 мас- штабного множителя соответствует битовой матрице 8x8, а 2 — битовой матрице 16x16. program fonts; uses Graph, Crt; procedure show_font(font : Word; ss ; String) ; var j : Word; begi n for j 1 to 7 do begi n SetTextStyle(font, horizdir, j); OutTextXY(0, 50 * j, ss); end; SetTextStyle(font, vertdir, 3); OutTextXY(600, 0, 'Press <Enter>:'); ReadLn; ClearDevi ce; end ; begi n gd := Detect; InitGraph(gd, gm, ’’); showfont(defaultfont, 'Default Font'); show_font(triplexfont, 'TriplexFont'); showfont(smallfont, 'SmallFont'); showfont(sansseriffont, 'SansSerifFont'); showfont(gothicfont, 'GothicFont');
266 Глава 8 Восточный мотив, или Рисуем Королеву Красоты ReadLn; CloseGraph; end. Процедура Clear De vice устанавливает текущий указатель в исходное положение (точка с координата- ми (0, 0)) и очищает экран, заполняя его цветол фона. Наш собственный графический модуль Мы узнали, что переключение в графический режим требует записи в программе нескольких операторов Не очень удобно каждый раз записывать в программе целый набор операторов инициализации. Хорошо бы еще сразу запрограммировать и перевод геометриче- ских координат в графические по формулам, приве- денным в начале этой главы. Все эти пожелания учте- ны в графическом модуле graphs, использующем возможности стандартного модуля Graph и «скры вающем» от нас детали переключения в графически1 режим и обратно. unit graphs; i nterfасе procedure opengraph; procedure closegraph; function gx(x : Extended; sx : Integer) Integer; function gy(y : Extended; sy : Integer): Integer; implementation uses Graph; var x, у : Extended; sx, sy : Integer;
Наш собственный графический модуль 267 procedure opengraph; var graph_deviсе, graph_mode : Integer; begin graph_device := Detect; InitGraph(graphdevice, graph_mode, ''); if GraphResult <> 0 then begi n WriteLn(‘Ошибка инициализации графического режима '); ReadLn; Halt; end; end; procedure closegraph; begin CloseGraph; if GraphResult <> 0 then begi n WriteLn(' Ошибка завершения графического режима '); ReadLn; Halt; end; end; function gx(x : Extended; sx ; Integer) ; Integer; begi n gx := trunc(sx * x) + GetMaxX div 2; end; function gy(y : Extended; sy : Integer) : Integer; begi n gy := GetMaxY div 2 - trunc(sy * y); end; end.
268 Глава 8 Восточный мотив, или Рисуем Королеву Красоты Этот модуль содержит описания процедур ореп_ graph и с I os e g г a ph. Первая производит переклю- чение в х’рафический режим, а вторая — завершение графического режима.. Они используются без пара- метров. Сюда же включены и функции gx и gy для преобразования геометрических координат в графи- ческие. У каждой из этих функций два параметра, первый — математическая координата, а второй — масштаб. В дальнейшем для инициализации или за- вершения графического режима достаточно скопи- ровать драйвер видеоадаптера в рабочий каталог про- граммы и разместить в программе обращение к соот- ветствующим процедурам. Включение графического режима сопровождает- ся обработкой возможных ошибок инициализации, которые могут быть связаны с отсутствием графи- ческого драйвера или неправильными значениями параметров. Процедура Graph Re suit при наличии ошибки вырабатывает отличное от нуля значение — код ошибки. Если в процессе работы процедуры воз- никла ошибка, на экран будет выведено соответст- вующее сообщение и программа в целом завершится. Вывод «трехмерной» надписи. Процедура shadow_ text выводит заданный текст. Для вывода надписи используется процедура OutTextXY. Исполь- зуя эту процедуру, мы идем на маленькую хитрость — выводим на экран один и тот же текст два раза — с небольшим смещением и разными цветами. Такой незамысловатый трюк создает иллюзию выпуклости надписи. Просто, но красиво! Параметры процедуры shadow_text позволяют задать не только расположе- ние текста (х , у), но и его цвет (bg), цвет «тени» (vg), а также шрифт надписи (здесь используется шрифт DefaultFont) и его размер (font, size).
Построение графиков функций в декартовых координатах 269 program text3d; uses Graph, graphs; procedure shadowtext(x, у : Integer; text : String; bg, vg, font, size : Byte); begi n SetTextStyle(font, 0, size); SetColor(bg); OutTextXY(x, у, text); SetColor(vg); OutTextXY(x + 1, у + 1, text); SetTextStyle(0, 0, 0); end; begi n opengraph; shadow_text(50, 50, 'My Best Friends', blue, red, DefaultFont, 2); ReadLn; closegraph; end. Построение графиков функций в декартовых координатах При построении графиков функций следует учиты- вать различия между графическими и математиче- скими координатами. Эти различия учтены в функ- циях gx и gy модуля graphs. Далее приведен пример построения графика функции у = [х] (целая часть от х): program dekart; uses Graph, graphs, Crt; var x, у, i, j : Integer; h, xtek, ytek : Real;
270 Глава 8 Восточный мотив, или Рисуем Королеву Красоты function f(x : Real) : Real; {Здесь можно задать любую функцию} begin if х>0 then f := Trunc(x) else f : = Trunc(x) - 1; end; begi n opengraph; SetBkColor(Bl ack); {Установка черного цвета фона} SetColor(Green); {Координатные оси - зеленые} Line(GetMaxX div 2, 0, GetMaxX div 2, GetMaxY); {Ось Y проходит через центр экрана} Line(0, GetMaxY div 2, GetMaxX, GetMaxY div 2) ; {Ось X проходит через центр экрана} for i := 1 to 64 do Line(i * 10, GetMaxY div 2 - 2, i * 10, GetMaxY div 2 + 2); {Размечаем ось X делениями по 10 пикселов} for j := 1 to 48 do Line(GetMaxX div 2 - 2, j * 10, GetMaxX div 2 + 2, j * 10); {Размечаем ось Y делениями no 10 пикселов} xtek := -32; h := 0.1; repeat уtek := f(xtek); PutPixel(gx(xtek, 10), gy(ytek, 10), Yellow);
Физико-математические узоры 271 xtek := xtek + h; until (xtek > 32) or (KeyPressed); Readin; closegraph; end. He следует бояться, что значение функции будет слишком велико, точка просто окажется за границей экрана и не будет нарисована (так произойдет, если мы захотим построить график функции у = tg(x)). Главное, чтобы при вычислении функции в точке х значение х находилось в области определения функ- ции. Физико-математические узоры Программа oscillato-s позволяет строить доволь- но интересные кривые. Эти кривые задаются уравне- ниями: х = sin(cox t), у = COS((Oy t), где t — вещественный параметр, изменяющийся в опре- деленном диапазоне значений. Такие уравнения опи- сывают траекторию движения точки, которая совер- шает гармонические колебания с некоторой частотой (ох в горизонтальном направлении и гармонические же колебания с частотой а>у в вертикальном направ- лении. Напомним, что гармоническими называются такие колебания, при которых отклонение х тела от положения равновесия изменяется по закону х = a Sin((o t + ср). Гармонические колебания совер- шают, например, математический маятник или на- пряжение в контуре электрической цепи. Траектории
272 Глава 8 Восточный мотив, или Рисуем Королеву Красоты такого движения физики называют фигурами Лисса жу. Фигуры Лиссажу могут быть довольно сложными, особенно при близких частотах вертикальных и гори- зонтальных колебаний. Задавая различные частоты, можно получить самые разнообразные кривые. Обя- зательно поэкспериментируйте с этой программой (в фигурах Лиссажу можно, по крайней мере, усмотреть неплохие узоры для вышивания)! program oscillators; uses Graph, graphs, Crt; var x, y, t, h, OmegaX, OmegaY : Extended; sx, sy : Integer; begin WriteLn('Введите значение частоты колебаний по х: '); ReadLn(OmegaX); WriteLn('Введите значение частоты колебаний по у:'); ReadLn(OmegaY); opengraph; SetBkColor(Blue); sx := GetMaxX div 3 - 10; sy := GetMaxY div 3 - 10; t := 0; h := 0.001; repeat x : = Sin(OmegaX * t); у := Cos(OmegaY * t); t := t + h; Delay (5); PutPixel(gx (x , sx), gy(y. sy), yellow); until KeyPressed;
Рекурсивные методы в построении графических изображений 273 ReadLn; closegraph; end. В этой программе для преобразования математи- ческих координат в графические используются функ- ции gx и gy из нашего модуля graphs. Траектория строится точками. Разумеется, поточечное построе- ние траектории — не самое лучшее решение, ведь тра- ектория в действительности является плавной кри- вой. Но о построении плавных кривых мы говорить не будем. Рекурсивные методы в построении графических изображений Рисуем «дырявый» треугольник. Построить фигуру, изображенную на рис. 8.6, сложно. Заданный тре- угольник делится на четыре части своими средними линиями; центральная часть фигуры остается пустой, а к трем крайним треугольникам применяется та же процедура. Построение повторяется п раз, а п называ- ют порядком фигуры. Эту задачу очень сложно решить нерекурсивным методом, тогда как рекурсия здесь практически оче- видна — каждый из маленьких треугольников повто- ряет большой с точностью до размера. В программе triangle используется рекурсивная процедура tr: program triangle; uses Graph, graphs; var xl, yl, x2, y2, хЗ, y3 : Real; n : Integer; {Процедура построения треугольника}
274 Глава 8 Восточный мотив, или Рисуем Королеву Красоты procedure triang(xl, yl, х2 , у2 , хЗ, уЗ : Real); begi n Linc(Round(xl), Round(yl), Round(x2), Round(y2)) ; Line(Round (x2) , Round(y2), Round(x3), Round(y3)) ; Line(Round(x3), Round(y3), Round(xl), Round(y1)); end ; procedure tr(xl, yl, x2, y2, x3, y3 : Real; n : Integer); var xln, yin, x2n, y2n, x3n, y3n ; Real; begin if n > 0 then begi n xln : = (xl + x2) / 2;{Найдем середин координаты сторон} yin := (yl + У2) / 2 ; x2n := (x2 + хЗ) / 2 ; y2n : = (y2 + УЗ) / 2 ; хЗп := (x3 + xl) / 2 ; у 3n := (УЗ + yi) / 2 ; triang(xln, yin, x2n, y2n, x3n , y3n) ; {Рисуем треугольник в центре} tr(xl, yl, xln, yin, хЗп, y3n, n - 1); {Обработка крайних треугольников} tr(x2, y2, xln, yin, x2n, y2n, n - 1); *tr(x3, y3, x2n, y2n, x3n, y3n, n - 1) ; end ; end; begi n Write(’Введите координаты вершин треугольника:’);
Рекурсивные методы в построении графических изображений 275 Readl_n(xl, yl, х2, у2, хЗ, уЗ) ; Write(’Введите порядок фигуры:’); ReadLn(п); open_graph; triang(xl, yl, х2, у2, хЗ, уЗ) ; {Исходный треугольник} tr(xl, yl, х2, у2, хЗ, уЗ, п); {Обработка исходного треугольника} ReadLn; CloseGraph; end. Рис. 8.6. Треугольник Серпинского Ковер Серпинского. Еще один интересный узор называется «ковром Серпинского» порядка п (см. рис. 8.7). Данная фигура представляет собой за- крашенный прямоугольник, который делится двумя горизонтальными и двумя вертикальными линиями на девять равных частей-прямоугольников, подобных исходному. Затем центральная часть выбрасывается, а к остальным восьми применяется та же процедура. Решение этой задачи отличается от предыдущей раз-
276 Глава 8 Восточный мотив, или Рисуем Королеву Красоты ве что тем, что мы имеем дело с закрашенными фигу- рами. Рис. 8.7. Ковер Серпинского program serpinski; uses Graph, graphs; var xl, yl, x2, y2 : Real; n Integer; procedure serp(xl, yl, x2, y2 : Real; n : Integer); var xln, yin, x2n, y2n : Real; begi n if n > 0 then begi n xln ;= 2 * xl / 3 + x2 / 3; {Найдем координаты разделительных прямых} x2n := xl / 3 + 2 * х2 / 3; yin := 2 * yl / 3 + y2 / 3;
Рекурсивные методы в построении графических изображений 277 у2п : = у 1 / 3 + 2 * у 2 / 3; {Выбросим середину} Ваг(Round(xln), Round(yln), Round(x2n), Round(y2n)); serp(xl, yl, xln, yin, n - 1); {Обработка крайних прямоугольников) serp(xln, yl, x2n, yin, n - 1): serp(x2n, yl, x2, yin, n - 1): serp(xl, yin, xln, y2n, n - 1), serp(x2n, yin, x2, y2n, n - 1); serp(xl, y2n, xln, y2, n - 1); serp(xln, y2n, x2n, y2, n - 1)• serp(x2n, y2n, x2, y2, n - 1); end; end; begin Write(’Введите координаты вершин прямоугольника (левая верхняя+правая нижняя): ’) ; ReadLn(xl, yl, х2, у2); Write(’Введите порядок:’); ReadLn(n); opengraph; SetFi UStyle (1, White); {Рисование начального прямоугольника} Bar(Round(xl), Round(yl), Round(x2), Round(y2)) ; SetFi UStyle (1, Black); serp(xl, yl, x2, y2, n); ReadLn; closegraph; end.
278 Глава 8 Восточный мотив, или Рисуем Королеву Красоты Что такое анимация? Существует много различных методов организации движения изображений по экрану компьютера (иначе это называется компьютер- ной анимацией). Идея всех методов заключается в быст- рой смене кадров, представ- ляющих различные фазы движения. Выбор метода за- висит от конкретной ситуа- ции. Главная проблема, с ко- торой сталкиваются создате- ли программ, состоит в том, чтобы сделать механику дви- жения «невидимой» для гла- за, то есть заставить фигуры на экране передвигаться как можно более плавно. Ниже описываются два наиболее простых метода анимации. Метод перерисовки Этот метод заключается в том, что нарисованная фи- гура в цикле после некоторой задержки во времени стирается (закрашивается цветом фона), а затем пол- ностью рисуется заново с небольшим сдвигом в на- правлении движения. Этот способ хорош для относи- тельно простых фигур малого размера, рисование которых не занимает много времени (например, точка или окружность). При попытках передвигать фигуры, закрашенные при помощи процедур FloodFill или Ваг, результаты оставляют желать лучшего, так как «картинка» двигается, но заметно мерцает. Вот пример программы, которая выводит окруж- ность, двигающуюся слева направо в горизонтальном
Что такое анимация? 279 направлении. В процесс вывода окружности вносится задержка с помощью процедуры Delay, которая обес- печивает определенный темп движения — быстрый или медленный. Величина задержки подбирается «на глаз» из соображений плавцости движения. program moving_circle; uses Graph, graphs, Crt; var x : Integer; begin opengraph; for x := 20 to GetMaxx - 20 do begin SetColor(White); {Рисование окружности на новом месте} Ci rcle(х,200,20); Delay(200); {Задержка} SetColor(Black); {Стирание окружности на старом месте} Circle(x, 200, 20); end ; close_graph; end. Часы. А сейчас попробуем нарисовать часы с дви- жущимися минутной и часовой стрелками. Для реше- ния этой задачи необходимо, во-первых, понимать, что обе стрелки движутся почти одинаково, только часовая в 12 раз медленнее (пока она проходит 5 ми- нут, минутная делает полный оборот), и, во-вторых, надо научиться вращать отрезок относительно одной из своих вершин. Для вычисления координат точки (конечная точка стрелки) при движении по окружно- сти следует знать угол <р между стрелкой и горизон- тальным направлением. Расстояние от точки до оси
280 Глава 8 Восточный мотив, или Рисуем Королеву Красоты вращения р будет постоянным. Второй конец отрезка вообще не двигается (рис. 8.8). Рис. 8.8. Координаты конечных точек стрелок Из соображений экономии места мы приводим пример программы (clock) без рисования цифербла- та и без привязки к реальному времени — желающие смогут это сделать самостоятельно. В программе используются процедуры включения динамика ком- пьютера на определенной частоте f — Sound(f) и выключения динамика No Sou nd. Обе находятся в мо- дуле Crt. Таким образом мы заставляем наши вирту- альные часы «тикать». Величину задержки придется подобрать опытным путем, в зависимости от быстро- действия компьютера. program clock; uses Graph, graphs, Crt; var fi : Real; begi n
Что такое анимация? 281 opengraph; Circle(GetMaxX div 2, GetMaxY div 2, 105); fi := 0.0; while fi < 6 * Pi do begi n SetColor(White); {Часовая стрелка} Line(GetMaxX div 2, GetMaxY div 2, Round(GetMaxX/2 + 70 * Cos(fi/12)), Round(GetMaxY/2 + 70 * Sin(fi/12))); {Минутная стрелка} Line(GetMaxX div 2, GetMaxY div 2, Round(GetMaxX / 2 + 100 * Cos(fi)), Round(GetMaxY / 2 + 100 * Sin(fi))); Delay(6000); Sound(300); Delay(5); Nosound; fi := fi + 0.1; SetFi UStyle (1, Black) ; SetColor(Bl ack); FillEllipse(GetMaxX div 2, GetMaxY div 2, 104, 104); end; ReadLn; closeg-aph; end. Обновление изображения в программе clock про- изводится стиранием сразу всех часов при помощи процедуры Fi 11Е11 i pse (х , у, гх , гу), которая ри- сует закрашенный черным цветом эллипс с центром в точке (х, у) и радиусами гх и г у соответственно по осям абсцисс и ординат. У окружности оба радиуса
282 Глава 8 Восточный мотив, или Рисуем Королеву Красоты совпадают. Каждый цикл соответствует новому зна- чению угла (переменная fi), который изменяется с шагом 0,1. Метод сохранения изображения в памяти Этот метод заключается в том, что фигура рисуется всего один раз. Затем с помощью процедуры Get- Image(xl, yl, х2 , у2, ppp) она сохраняется в па- мяти. Аргументами процедуры являются: (xl, yl) и (х2, у2) — координаты левого верхнего и нижнего правого углов «запоминаемой» прямоугольной об- ласти экрана, ррр — область в памяти размера size в байтах (ррр является переменной типа Pointer — указателем на определенное место в оперативной па- мяти компьютера). Определить этот размер можно с помощью функции Images i ze (xl, yl, x2, у2),где xl, yl, x2, у2 задают прямоугольную область. Далее сохраненная область выводится в новом месте графического экрана, при этом старое изобра- жение должно стираться (например, процедурой Ваг). Вывод осуществляется процедурой Putlmage(x, у, ррр, L), в которой (х , у) — координаты левого верх- него угла выводимой области, ррр — указатель на об- ласть памяти, L — способ вывода (в обычном режиме L должно быть равно 1). Этот метод дает хорошие ре- зультаты при передвижении по экрану фигур любой сложности. Однако при его использовании могут воз- никнуть проблемы: например, при смене цвета фона сохраненная фигура будет выводиться со старым цве- том. Вот как программируется движение окружности методом сохранения изображения в памяти: program moving_circle2; uses Graph, graphs, Crt; var
Что такое анимация? 283 х : Integer; size : Word; ppp : Pointer; begin opengraph; size := ImageSize(9, 180, 50, 220); {Определение размера памяти} GetMem(ppp,size); {Выделение памяти} SetColor(White); {Рисование окружности} Circle(30, 200, 20); Getlmage(9, 180, 50, 220, ppp74); {Сохранение рисунка в памяти} SetFi UStyle (1, Black); for x := 10 to GetMaxX - 20 do begin Putlmage(x, 180, ppp74, 1); {Отображение на новом месте} Delay(200); Bar(x, 180, x + 40, 220); {Стирание изображения на старом месте} end; close graph; end. Что наша «Жизнь»? Игра! Завершим главу о программировании графики в Тур- бо Паскале известной игрой Дж. Конвея «Жизнь». Это в действительности не игра, а простая модель эволюции сообщества виртуальных организмов. Пас- сивное наблюдение над совокупностью большого чис- ла поколений может увлечь, поэтому и назвали эту модель игрой.
284 Глава 8 Восточный мотив, или Рисуем Королеву Красоты Правила игры таковы. Жизненным пространством в ней является плоскость, разделенная на квадратные ячейки. У каждой ячейки имеется 8 соседей. Ячейка может быть заселена одним организмом, а может быть пустой. Популяция в первом поколении задает ся случайным образом. Это значит, что решение о том, будет ли каждая конкретная ячейка заселена, принимается с некоторой вероятностью. Затем строится новое поколение, в котором рас- пределение населенных ячеек определяется прави- лами, которые применяются одновременно ко всем ячейкам: 4- живая ячейка, имеющая не более одной живой со седки, погибает от одиночества; -4- живая ячейка, имеющая 4 или больше живых сосе- дей, погибает от перенаселения; 4- мертвая ячейка с тремя живыми соседями возрож- дается; 4- во всех прочих случаях состояние ячейки не изме- няется. В программе life при отображении эволюции со- общества виртуальных организмов применен прием анимации. Для быстрого перехода от одного «кадра» с изображением популяции используются две графи- ческие страницы. Напомним, что графическая стра- ница представляет собой область видеопамяти, ко- торая хранит изображение. При этом, если режим ра- боты видеоадаптера поддерживает работу только с одной страницей, ее содержимое отображается на эк- ране дисплея. В некоторых режимах работы может поддерживаться несколько графических страниц. Со- держимое одной из них («визуальной» страницы) отображается на экране, а на второй («активной»),
Что такое анимация? 285 невидимой глазу пользователя, в это время может строиться новое изображение. Подготовленное на ак- тивной странице изображение можно вывести на экран. В разделе описаний констант заданы следующие параметры программы: + ho г — количество ячеек по горизонтали; + ver — количество ячеек по вертикали; + cellwidth, cellheight — ширина и ячейки; высота + prob factor — параметр, определяющий вероят- ность заселения ячеек при формировании началь- ной популяции. Размеры приведены в пикселах. program life; uses Graph, Crt, Dos; const hor = 100; ver - 70; cellwidth = 8; cell_height - 6; probfactor = 0.5; var oldgen, newgen : array[0..ver, 0..hor] of 0..1; prob : Real; ch : Char; xcenter : array[0..hor] of Word; y_center : array[0..ver] of Word; gencount, radius, page : Word; ss : String! 10] ; procedure init_cells; var
286 Глава 8 Восточный мотив, или Рисуем Королеву Красоты j, к : Word; begin gencount := 0; for j := 0 to ver do for к := 0 to hor do begin old_gen[j, k] : = 0; if Random <= prob then new_gen[j, k] : = 1 else new_gen[j, k] := 0; end ; end ; procedure nextgeneration; var j, к. m. pre v_j , n e x t_j. prevk, nextk : Word; begi n old_gen := new_gen; for j : = 0 to ver do begi n if j =0 then prevj := ver else prev_j := j - 1; if j = ver then next_j := 0 ’* else next_j := j + 1; for к := 0 to hor do begin if к = 0 then prev_k := ver else
Что такое анимация? 287 prev_k := к - 1; if к = hor then next_k := 0 else next_k := к + 1; m : = old_gen[prev_j, prevk] + oldgen[prev_j, к ] + oldgen[prev_j, next_k] + old_gen[j, prevk] + old_gen[j, next_k] + old_gen[next_j, prev_k] + oldgen[next_j, к ] + old gen[next ] , next k] ; if (old_gen[j, k] = 1) and ((m <= 1) or (m >= 4)) then new_gen[j, k] : = 0 else if (old_gen[j, k] = 0) and (m = 3) then new_gen[j . k] := 1 else new_gen[j, k] := old_gen[j, k] ; end; end; end: procedure init_screen; var GraphDriver, GraphMode : Integer; j, к : Word; begi n GraphDriver := VGA; GraphMode : = VGAMed; page : = 0; InitGraph(GraphDriver, GraphMode, ''); if GraphResult <> grOK then halt;
288 Глава 8 Восточный мотив, или Рисуем Королеву Красоты for к := 0 to hor do x_center[k] := к * cell_width + cell_width div 2; for j := 0 to ver do y_center[j] := j * cellheight + cell_height div 2; radius := 4; end; procedure display; var j. к : Word; procedure ruleplane; var j, к : Word; begin SetViewPort(0, 0, GetMaxX, GetMaxY, cli pon); SetFiIIStyle(SolidFiII, Blue); Bar(0, 0, GetMaxX, 10); SetColor(white); OutText('Generation: '); OutTextXY(250, 0 'Q: Quit'); OutTextXY(450, 0, 'Any other key: renew'); Str(gen_count, ss); outtext(ss); SetBkColor(DarkGray); end;{rule_plane} begi n if gencount <> 0 then nextgeneration; i nc(gen_count); page := 1 - page;
Что такое анимация? 289 SetActivePage(page); ClearDevi се; SetColor(yellow); for j := 0 to ver do for k := 0 to hor do if new_gen[j, k] = 1 then circle(xcenter[k], y_center[j], radius); ruleplane; SetVisualPage(page); end; {di splay} begin i ni t_screen; repeat Randomi ze; prob := 0.1 + probfactor * random; OutTextXY(0, 0, 'Conway''s Game of Life'); Wri teLn; OutTextXY(0, 15, 'Live cells inserted at random,'); Str(prob:3:3, ss); OutTextXY(0, 30, 'with probability ' + ss) ; OutTextXY(0, 60, 'Press any key to start: '); ch := ReadKey; ClearDevi ce; ini t cells; repeat di splay; if KeyPressed then begi n 10 Зак. №933
290 Глава 8 Восточный мотив, или Рисуем Королеву Красоты ch := ReadKey; Break; end; until false; SetViewPort(0, 0, GetMaxX, GetMaxY, clipon); ClearDevi ce; SetColor(whi te); if UpCase(ch) = 'Q' then Break: until false; CloseGraph; end. Процедура injit cells формирует исходную по- пуляцию, присваивая элементам массива new_gen значения 0 или 1 с учетом выбранного значения веро- ятности заселения. Процедура next_generation строит в массиве new_ gen следующую популяцию в соответствии с прави- лами игры. В процедуре init_screen инициализируется гра- фический режим и заполняются массивы графиче- ских координат центров ячеек х_с enter и ус enter. Процедура display выполняет основную работу по построению изображения рабочего поля игры на скрытой активной странице и выводу ее на экран. Это обеспечивают две процедуры: SetActi vePage и SetVi sual Page, вызываемые с параметром 0 или 1 определяющим номер графической страницы. Первая процедура при вызове устанавливает активную стра- ницу, а вторая — визуальную. Пронаблюдав достаточное число поколений, мож- но видеть, что любая начальная популяция превраща- ется в набор застывших или изменяющихся цикличе- ски «колоний».
Итог 291 Итог Заканчивается наше знакомство с элементами компь- ютерной графики и программированием графических изображений в Турбо Паскале. Ну как, уважаемый читатель, сможешь ли ты теперь достойно изобразить портрет возлюбленной Ала ад-Дина? Если эта задача покажется тебе все еще слишком сложной, попробуй решить несколько задач попроще. Они приведены в конце главы. Задача 1. Построить окружность некоторого радиуса, разбитую на четыре равных сектора, в каждый из ко- торых вписана окружность. С полученными окруж- ностями произвести ту же операцию, и так п раз. Задача 2. Построить графики функций: У = [х]; у = | х - [х] - 0.5|; У = (х - [х])2; У = [х]2; у = 1 / х; у = х / (х2 + 1); у = (х2 + 1) / X. Задача 3. Нарисовать и-угольную звезду, в которой каждая вершина соединяется с отстоящей от нее че- рез одну вершиной. Задача 4. Нарисовать шахматную доску. Задача 5. Разделить круг на 4, 8, п секторов и закра- сить их разными цветами. Задача 6. Построить движение окружности по задан- ным кривым.
292 Глава 8 Восточный мотив, или Рисуем Королеву Красоты Задача 7. Нарисовать часы с циферблатом и движу- щимися секундной, минутной и часовой стрелками. Задача 8. (Любителям игры в бильярд.) Написать программу для вывода в графическом режиме траек- тории движения шарика в прямоугольном бильярде. Шарик можно считать точечным объектом, трением можно пренебречь. Столкновение шарика со стенка- ми бил ьярда происходит по закону зеркального отра- жения. Входные параметры программы — скорость и направление начального удара по шарику. Задача 9. Определите и нарисуйте стили заполнения в процедуре fil_bar для всех элементов массива f 1 l_pat.
ГЛАВА 9 ООП! Или лебединая песня клавиатуры
Наше путешествие близится к концу. Осталось сде- лать последние шаги. Мы успели заглянуть в разные уголки страны Программирование, эта страна огром- на, и во время такого короткого путешествия, как наше, можно лишь бегло познакомиться с ее «досто- примечательностями». Но постойте! А что за стран- ное «ООП!» в заголовке этой главы? Странным оно кажется лишь нам — новичкам в программировании. А «серьезные» программисты с большим уважением относятся к ООП. ООП ООП — это «объектно-ориентированное программи- рование». Метод объектно-ориентированного про- граммирования стал одним из основных методов, применяемых при разработке современных программ. Основная цель ООП, как и большинства других под- ходов к программированию, — повышение скорости разработки программ. Идеи ООП оказались настоль- ко удачными, что нашли применение не только в язы- ках программирования, но и в других областях, на- пример в области разработки операционных систем. Объектно-ориентированные программы обычно пи- шут на языках, которые поддерживают эту возмож- ность. В Турбо Паскале поддержка ООП появилась начиная с версии 5.5. Идея ООП состоит в следующем. Компьютерные программы представляют собой описание действий, выполняемых над различными объектами. Это могут быть, например, графические объекты (точки, окруж- ности) или совокупности числовых значений (пере-
ООП 295 менные, записи, массивы). Изменение данных или правил их обработки обычно приводит к необходимо- сти изменения программы, а всякое изменение про- граммы является неприятностью для программиста, так как при этом в программу могут быть внесены но- вые ошибки. Использование ООП позволяет выйти из такой ситуации с наименьшими потерями, сводя необходимое изменение программы к ее расширению и дополнению. Изучение идей и методов объект- но-ориентированного программирования — не очень простая задача, однако освоение ООП может упро- стить разработку и отладку сложных программ. Есть и другое соображение. Мы уже привыкли ис- пользовать в своих программах процедуры и функ- ции для программирования тех сложных действий по обработке данных, которые приходится выпол- нять многократно. Использование подпрограмм в свое время было важным шагом на пути к увеличению эф- фективности программирования. Так появился прин- цип модульности. Подпрограмма может иметь фор- мальные параметры, которые при обращении к ней заменяются фактическими параметрами. В этом слу- чае есть опасность вызова подпрограммы с непра- вильными данными, что может привести к сбою про- граммы и ее аварийному завершению. Подстраховать от ошибок такого рода может объединение данных и подпрограмм (процедур и функций), предназначен- ных для их обработки. Объекты. Важнейшим понятием объектно-ори- ентированного программирования является понятие объекта. Программная реализация объекта представ- ляет собой объединение данных и процедур их обра- ботки. В Турбо Паскале имеется тип Obj ect, который можно считать обобщением структурного типа Record. Переменные объектного типа называются экземпля-
296 Глава 9 ООП! Или лебединая песня клавиатуры рами объекта, хотя, строго говоря, экземпляр лишь формально можно назвать переменной. Его описа- ние дается в предложении описания переменных, но в действительности экземпляр — это больше, чем обычная переменная (это переменная + подпрограм- мы). В отличие от типа «запись» объектный тип содер- жит не только поля, описывающие данные, но также процедуры и функции, описания которых содержатся в описании объекта. Эти процедуры и функции назы- ваются методами. Методам объекта доступны его поля. Методы и их аргументы определяются в описа- нии объекта, а их реализация (то есть подробное опи- сание соответствующих процедур и функций) дается вне этого описания, в том месте программы, которое предшествует вызову данного метода. В описании объекта содержатся лишь шаблоны (образцы) обра- щений к методам, которые необходимы компилятору для проверки соответствия количества аргументов и их типов при обращении к методам. Вот пример опи- сания объекта: type Location = Object X, У : Integer; procedure Init(InitX, InitY : Integer); function GetX : Integer; function GetY : Integer; end; Здесь описывается объект, который может исполь- зоваться в дальнейшем, скажем, в графическом режи- ме для определения положения на экране произволь- ного графического элемента. Объект описывается с помощью зарезервированных слов Ob j ect... end, меж- ду которыми находятся описания полей и методов.
ооп 297 В нашем примере объект содержит два поля для хранения значений графических координат, а также описания процедуры и двух функций — это и есть методы данного объекта. Процедура предназначена для задания первоначального положения объекта, а функции позволяют считывать его координаты. В отличие от других описаний, описание объект- ного типа может находиться только на самом верхнем уровне программной единицы (программы, модуля), в которой используется этот тип. В разделе описаний процедур, функций, методов такое описание содер- жаться не может. Инкапсуляция. Инкапсуляция является важней- шим свойством объектов, на котором строится объ- ектно-ориентированное программирование. Инкапсу- ляция заключается в том, что объект «скрывает» в себе детали, которые несущественны для использова- ния объекта. В обычном подходе к программирова- нию программист не был застрахован от ошибок, связанных с использованием процедур, не предназна- ченных для обработки определенных данных. Пред- положим, например, что имеется обычная программа, предназначенная для начисления заработной платы сотрудникам организации, а в программе имеются два массива. Один массив хранит величину заработ- ной платы, а другой — телефонные номера сотрудни- ков. Что произойдет, если программист случайно пе- репутает эти массивы? Сотрудники получат круглую сумму, а для бухгалтерии начнутся тяжелые времена. «Жесткое» связывание данных и процедур их обра- ботки в одном объекте позволяет избежать неприят- ностей такого рода. Инкапсуляция и является средст- вом организации доступа к данным только через соответствующие методы.
298 Глава 9 ООП! Или лебединая песня клавиатуры В нашем примере описания объекта процедура инициализации Init и функции GetX и GetY уже не существуют как отдельные, самостоятельные объ- екты. Это неотъемлемые части объектного типа Lo- cation. Вызов каждого метода возможен только с по- мощью составного имени, явно указывающего, для обработки каких данных предназначен данный метод. Наследование. Наследование позволяет определять новые объекты, используя свойства прежних, допол- няя или изменяя их. Объект-наследник получает все поля и методы «родителя», к которым он может доба- вить свои собственные поля и методы или заменить («перекрыть») их своими методами. Пример описа- ния объекта-наследника дается ниже: type Point = Object(Location) Visible : Boolean; procedure Init(InitX, InitY : Integer); procedure Show; procedure Hide; function IsVisible : Boolean; procedure MoveTo(NewX, NewY : Integer); end ; Наследником здесь является объект Point, описы- вающий графическую точку, а родителем — объект Location. Наследник не содержит описания полей и методов родителя. Имя последнего указывается в круглых скобках после слова Object. Из методов на- следника можно вызывать методы родителя. Для со- здания наследника не требуется иметь исходный текст объекта-родителя, это удобно. Объект-родитель может быть в составе уже оттранслированного моду- ля.
ООП 299 В чем привлекательность наследования? Если ка- кой-то объект уже был определен и отлажен, он мо- жет быть использован и в других программах. При этом может оказаться, что новая задача отличается от предыдущей и возникает необходимость некоторого изменения данных и методов их обработки. Програм- мист может сэкономить время, используя результаты предыдущей работы и применяя механизм наследова- ния. Он при этом может и не знать деталей реализа- ции объекта-родителя. В нашем примере к объекту, связанному с опреде- лением положения графического элемента, при на- следовании просто добавилось новое поле, описы- вающее признак видимости графической точки, и несколько новых методов, связанных с режимами отображения точки и ее преобразованиями. Полное описание методов должно находиться по- сле описания объекта. Имена методов — составные и складываются из имени объекта и имени метода, раз- деленных точкой: procedure Location.Init(InitX, Ini tY : Integer); begi n X := InitX; Y := InitY; end; function Location.GetX : Integer; begi n GetX := X; end; function Location.GetY : Integer; begin GetY := Y; end;
300 Глава 9 ООП! Или лебединая песня клавиатуры После того как объект описан, в программе можно использовать переменные указанного объектного типа: var GrMarker : Location; Разговор отца с сыном. Программа father2son демонстрирует использование объектов. program father2son; uses crt; type father = Object age : Integer; procedure init; procedure talk; end; son = Object(father) girlfriends : Integer; procedure init; procedure talk; end; var sergei : father; anton : son; procedure father.init; begi n age 41; end; procedure father.talk; begi n WriteLn(’Tu сделал уроки!!?'); end ; procedure son.init; begin age := 14;
Лебединая песня клавиатуры 301 girlfriends := 2; end; procedure son.talk; begin WriteLn('HaM ничего не задано - учительница заболела!'); end; begin ClrScr; sergei.i ni t; anton.i ni t; sergei.talk; ReadLn; anton.talk; ReadLn; end. Здесь используются объекты father и son. Как и положено, объект son наследует свойства объекта father. Методами обоих объектов являются проце- дуры in it и talk. Лебединая песня клавиатуры Турбо Паскаль позволяет писать программы, кото- рые не только занимаются вычислениями или выво- дят на экран красивые картинки, но и управляют работой различных частей компьютера. Разработка таких программ — предмет системного программиро- вания. Системные программисты занимаются состав- лением программ, которые работают в составе опера- ционной системы и решают свойственные ей задачи. Примером операционной системы является MS-DOS, в которой и запускается интегрированная среда Тур-
302 Глава 9 ООП! Или лебединая песня клавиатуры бо Паскаля, а также все программы, написанные на Турбо Паскале. Назначение операционной системы заключается в обеспечении удобства управления компьютером. Зна- чительная часть ее заключается в том, чтобы освобо- дить пользователя от выполнения некоторых опера- ций. При обычном копировании файла, например, системе приходится выполнять до двух десятков дей- ствий «низкого уровня», в числе которых проверка наличия копируемого файла, а также файла с таким же именем на целевом устройстве, проверка нали- чия свободного места на диске и т. д. Если бы пользо- вателю приходилось каждый раз самостоятельно вы- полнять все необходимые действия низкого уровня, жизнь пользователя превратилась бы в сущий ад. Прерывания. Операционная система управляет про- граммами, периферийными устройствами и всем тем, чем она должна управлять, с помощью прерываний. Прерывание представляет собой сигнал процессору, вырабатываемый программой, каким-либо устройст- вом или самим процессором. Этот сигнал приоста- навливает выполнение программы и запускает соот- ветствующую функцию операционной системы для выполнения необходимых системных действий. Пре- рываниям присваиваются номера в шестнадцатерич- ной системе счисления (о чем говорит знак доллара перед числом), например $10. Модуль Dos. Модуль Dos содержит процедуры, обеспечивающие возможность выполнения некото- рых системных операций. Среди них, например, пря- мой доступ к различным устройствам. Лебединая песня клавиатуры. Рассмотрим при- мер программирования на Турбо Паскале одного из важнейших устройств компьютера — клавиатуры. В этом примере используется прерывание $16. При
Лебединая песня клавиатуры 303 запуске программы keyborad last song динамик компьютера играет мелодию известной песни «Happy birthday», а на клавиатуре вспыхивают и гаснут инди- каторы нажатия клавиш NumLock, CapsLock и ScrollLock. Познакомимся подробнее с тем, как работает кла- виатура персонального компьютера. Стандартная кла- виатура содержит свой собственный микропроцессор. Нажатие или отпускание клавиши посылает сигнал специальной микросхеме — контроллеру прерыва- ний, который вызывает подпрограмму обслуживания прерывания. Эта подпрограмма читает и декодирует считываемый код (он называется кодом сканирова- ния), определяет нажатие специальных клавиш (Control, Shift, Alt и другие) и преобразует коды сканирования во внутренние коды. Каждое нажатие клавиши выра- батывает два кода сканирования — для нажатия и для отпускания клавиши. Выбор кода сканирования за- висит от состояния клавиатуры. Например, нажатие клавиши А генерирует код сканирования $61 (код ASCII строчной латинской буквы а). Если при нажа- тии клавиши А нажата еще и управляющая клавиша Ctrl, код сканирования трансформируется в $01. Если при нажатии клавиши А нажата клавиша Shift, код сканирования будет $41 (код ASCII для заглавной латинской буквы А). Состояние клавиатуры хранится в памяти компь- ютера в специальном байте состояния клавиатуры. В результате нажатия таких клавиш, как Shift и Alt, программа обработки прерывания обновляет байт со- стояния клавиатуры. Определенные комбинации кла- виш имеют специальное назначение. Такой специаль- ной комбинацией является, например, Ctrl+ALt+Del, которая генерирует прерывание $19. При вводе сим- волов они заносятся в буфер клавиатуры. Если буфер полон, программа обработки выдает звуковой сигнал
304 Глава 9 ООП! Или лебединая песня клавиатуры и отвергает символ, в противном случае символ до- бавляется в конец буфера. Прерывание $16 позволяет осуществить доступ к клавиатуре. program keyborad_last_song; uses Dos, Crt: var keybstatusbyte : Byte absolute $40:$17; num, caps, scroll, old_num, old_caps, old_scroll : boolean; loop : Byte; k : Integer; const max = 51; procedure get_leds_status(var n, c, s : Boolean); begi n s := true; if (keyb_status_byte and $10) = 0 then s := false; n := true; if (keyb_status_byte and $20) = 0 then n := false; c := true; if (keyb_status_byte and $40) = 0 then c := false; end; procedure set_leds_status(n, c, s : Boolean); var reg : registers;
Лебединая песня клавиатуры 305 begi п if s then keyb_status_byte := keyb_status_byte or $10 else keyb_statusbyte := keybstatusbyte and not $10; if n then keyb_status_byte := keyb_status_byte or $20 else keybstatusbyte := keybstatusbyte and not $20; if c then keybstatus_byte keyb_status_byte or $40 else keyb_status_byte := keybstatusbyte and not $40; reg.ax := $0200; Intr($16, reg); end ; procedure keyb_song(tone : Integer); const melody : array[0..max, 1..2] of Integer = ((0, 0), (1046, 200), (1046, 200), (1174, 400) , (1046, 400) , (1398, 400) , (1318, 800) , (1046, 200) , (1046, 200) , (1174, 400) , (1046, 400) , (1568, 400) , (1398, 800) , (1046, 200) , (1046, 200) , (2092 , 400) , (1760, 400) , (1398, 400) , (1318, 400) , (1174, 800) , (1864, 200) , (1864, 200) , (1760, 400) , (1398, 400) , (1568 , 400) , (1398, 800) , (1046, 200) ,
306 Глава 9 ООП! Или лебединая песня клавиатуры (1046. 200) . (1174. 400) , (1046, 400) , (1398. 400) . (1318. 800) , (1046. 200) . (1046. 200) . (1174, 400) , (1046, 400) , (1568. 400) . (1398, 800) , (1046, 200) , (1046. 200) . (2092, 400) , (1760, 400) . (1398. 400) . (1318, 400) , (1174, 800) , (1864. 200) . (1864. 200) , (1760, 400) , (1398, 400) . (1568. 400) , (1398. 800) , (0. ©)); begin Sound(melody[tone][1]); Delay(melody[tone][2]*10); {Множитель подбирается для воспроизведения мелодии в подходящем темпе} NoSound; end ; begi n get_leds_status(oldnum, oldcaps, oldscroll); num:= false; caps := false; scroll := false; loop := 0; k : = 0; WriteLn('Нажмите <enter> для остановки программы'); repeat keybsong(k); k := (k + 1) mod max; loop := Random(4); case loop of 0 : scroll := false;
Лебединая песня клавиатуры 307 1 : num := true; 2 : begin num := false; caps := true; end; 3 : begin caps := false; scroll := true; end; end; set_leds_status(num, caps, scroll); until KeyPressed; setledsstatus(old_num, oldcaps, oldscroll); end. Процедура get led status определяет состояние индикаторов в момент запуска программы на выпол- нение и сохраняет это состояние в переменных old_num, oldcaps и оId_sс roll. Чтобы выяснить состояние индикаторов, используется один из специальных ад- ресов MS-DOS, содержащий данные о состоянии кла- виатуры. Переменная keyb_status_byte указывает на байт состояния клавиатуры (слово absolute в описании этой переменной указывает на адрес в памяти, где должна храниться эта переменная). Изменение зна- чения определенных битов этой переменной в проце- дуре set led status позволяет включать или вы- ключать индикаторы клавиатуры. Доступ к битам обеспечивается масками $10, $20 и $40, имеющи- ми двоичное представление 00010000, 00100000 и 01000000 соответственно. Маска здесь — это специ- альное число, которое позволяет изменить опреде- ленные биты в двоичном представлении другого чис- ла. Установка единичного значения для 4-го бита
308 Глава 9 ООП! Или лебединая песня клавиатуры (отсчет идет справа налево, начиная с нуля) означает включение режима ScroLLLock, 5-го — включение режи- ма NumLock и 6-го — включение режима CapsLock. Установить в необходимый бит единичное значение можно с помощью побитной логической операции о г, операндами которой являются байт состояния кла- виатуры и соответствующая маска. Для установки нулевого значения бита необходимо проинверти- ровать маску с помощью логической операции not, а вместо операции о г следует использовать and. В процедуре setledstatus используется спе- циальный тип registers (это запись), который опи- сан в модуле System. Регистр — это внутреннее запо- минающее устройство процессора для временного хранения обрабатываемой или управляющей инфор- мации. В нашем примере reg. ах является указани- ем на определенный регистр процессора. Процедура Intr модуля Dos предназначена для обращения к прерыванию с указанным номером (первый пара- метр). Перед выполнением прерывания процедура за- гружает регистры процессора значениями соответст- вующих полей записи reg. В нашем примере это номер функции обработки прерывания. После вы- полнения прерывания содержимое регистра вновь за- писывается в соответствующую переменную. В начале программы после определения состояния клавиатуры переменным num, caps и scroll присваи- ваются значения false, соответствующие выключен- ным индикаторам. Затем в игру вступает цикл repeat...until, в котором имеется параметр — целая переменная к, значение которой изменяется цикличе- ски от О до 51. Именно 52 ноты в мелодии, которую проигрывает динамик компьютера при вызове проце- дуры keyb_song. Мелодия хранится в виде двумер- ного массива, каждая нота описывается высотой и
Итог 309 продолжительностью звучания. Выбор комбинации включенных индикаторов производится случайным образом, а их переключение происходит в процедуре set_leds_status. Выполнение цикла прекращается при нажатии любой клавиши, после чего восстанав- ливается исходное состояние индикаторов клавиату- ры и программа завершает свою работу. Итог Мы узнали о том, что объектно-ориентированное программирование является важнейшим методом раз- работки сложных программ. Узнали и о том, что на Турбо Паскале можно писать программы, управляю- щие различными устройствами персонального ком- пьютера. Но это было лишь беглое знакомство. Обе темы требуют отдельного изучения, и в разделе «Что еще почитать?» вы найдете литературу, в которой эти вопросы рассматриваются более подробно.
Прощание
Наступило время прощаться. Мы надеемся, что нам удалось познакомить вас, уважаемый читатель, с про- граммированием на Турбо Паскале, не утомив при этом слишком серьезными рассуждениями или из- лишне сложными примерами. Мы надеемся также, что наша книга послужит началом более серьезного знакомства с увлекательным миром программирова- ния. Миром разнообразным, населенным не только числами, но и замечательными ландшафтами, вол- шебными звуками и прочими диковинками. Замеча- тельными ландшафтами занимается компьютерная графика, волшебными звуками — компьютерная об- работка звука. А ведь кроме этого есть и базы данных, и компьютерные сети, и «силы специального назначе- ния» современных компьютерных технологий — су- перкомпьютеры. Остается выбрать в этом сложном и интересном мире свою дорогу. А для этого придется еще многому научиться.
Что еще почитать? Для серьезного изучения программирования на Тур- бо Паскале можем посоветовать книги одного из ав- торов — С. А. Немнюгина «Turbo Pascal: Учебник» (СПб.: Питер, 2000) и «Turbo Pascal: Практикум» (СПб.: Питер, 2000). В этих книгах не только содер- жится подробное и систематическое изложение основ языка, но и рассматриваются методы решения тех за- дач, с которыми часто приходится сталкиваться про- фессиональному программисту. «Наследницей» Турбо Паскаля можно считать Delphi — систему визуальной разработки программ для Microsoft Windows. Увлекательным введением в программирование на Delphi является книга А. В. Жу- кова «Изучаем Delphi» (СПб.: Питер, 2000). Совету- ем ее почитать! Любители путешествовать в Интернете могут най- ти в Сети сайты, посвященные программированию на Паскале. Ссылки на некоторые из этих сайтов разме- щены на Web-страничке одного из авторов: http://mph.phys.spbu.ru/~nemnugin
Немнюгин Сергей Андреевич Перколаб Людмила Викторовна Изучаем Turbo Pascal Главный редактор Заведующий редакцией Литературный редактор Художники Корректор Верстка Е. Строганова А. Кривцов Н. Дубнова Н. Биржаков, Л. Милько С. Шевякова Ю. Сергиенко Лицензия ИД Ns 05784 от 07.09.2001. Налоговая льгота — общероссийский классификатор продукции ОК 005-93, том 2; 95 3005 - литература учебная. Подписано к печати 09.09.2005. Формат 84xl08'/32. Усл. п. л. 16,8. Доп. тираж 3000 экз. Заказ Ns 933. ООО «Питер Принт», 194044, Санкт-Петербург, пр. Б. Сампсониевский. д. 29а. Отпечатано с готовых фотоформ в ФГУП ИПК «Лениздат» Федерального агентства по печати и массовым коммуникациям Министерства культуры и массовых коммуникаций РФ. 191023, Санкт-Петербург, наб. р. Фонтанки, 59.
В 1997 году по инициативе генерального директора Издательского дома «Питер» Валерия Степанова и при поддержке деловых кругов города в Санкт-Петербурге был основан «Книжный клуб Профессионал». Он со- брал под флагом клуба профессионалов своего дела, которых объединяет постоянная тяга к знаниям и любовь к книгам. Членами клуба являются луч- шие студенты и известные практики из разных сфер деятельности, которые хотят стать или уже стали профессионалами в той или иной области. Как и все развивающиеся проекты, с течением времени книжный клуб вырос з «Клуб Профессионал». Идею клуба сегодня формируют три основные «клубные» функции: • неформальное общение и совместный досуг интересных людей; • участие в подготовке специалистов высокого класса (семинары, пакеты книг по специальной литературе); • формирование и высказывание мнений современного профессионала (при встречах и на страницах журнала). КАК ВСТУПИТЬ В КЛУБ? Для вступления в «Клуб Профессионал» вам необходимо: • ознакомиться с правилами вступления в «Клуб Профессионал» на страницах журнала или на сайте www.piter.com; • выразить свое желание вступить в «Клуб Профессионал» по электронной почте postbook@piter.com или по тел. (812) 703-73-74; • заказать книги на сумму не менее 500 рублей в течение любого времени или приобрести комплект «Библиотека профессионала». «БИБЛИОТЕКА ПРОФЕССИОНАЛА» Мы предлагаем вам получить все необходимые знания, подписавшись на «Библиотеку профессионала». Она для тех, кто экономит не только время, но и деньги. Покупая комплект — книжную полку «Библиотека про- фессионала»,* вы получаете: • скидку 15% от розничной цены издания, без учета почтовых расходов; • при покупке двух или более комплектов — дополнительную скидку 3%'; • членство в «Клубе Профессионал»; • подарок — журнал «Клуб Профессионал». Закажите бесплатный журнал «Клуб Профессионал». ИЗДАТЕЛЬСКИЙ ПОМ Гл ' питкп ® WWW. PITER. С ОМ
[^ППТЕР' Нет времени ходить по магазинам? www.piter.com Здесь вы найдете: Все книги издательства сразу Новые книги — в момент выхода из типографии 1 Информацию о книге — отзывы, рецензии, отрывки Старые книги — в библиотеке и на CD И наконец, вы нигде не купите наши книги дешевле!
С. Немнюгин, Л. Перколаб ИЗУЧАЕМ TURBO PASCAL Как научиться программировать на Паскале? Нет.недостатка в учебниках, содержащих описан, е грамматики и синтаксиса языка. Однако лучший способ Научиться программировать — посмотреть, как это делают другие. В книге рассматриваются решения разных интересных задач, от простых до достаточно сложных. Курс программирования на языке Паскаль изложен так, чтобы его мог освоить каждый и без посторонней помощи. Многим профессиональным программистам и лучение Турбо Паскаля помогло стать настоящими «компьютерными асами* Авторы надеются, что и эта книга послужит для читателей началом серьезного знакомства с увлекательным миром программирования. Если хотите стать компьютерным асОлц - ищите другие кнйжки серии: Жумо* Лжомв ИЗУЧАЕМ DELPHI ИЗУЧАЕМ ИНТЕРНЕТ СОЗДАЕМ WEB-СТРАНИЧКУ ИЗУЧАЕМ BASIC ИЗУЧАЕМ УСТРОЙСТВО КОМПЬЮТЕРА ' ЯМ Посетите наш Web-магазин: httpiZ'www.piter.com I^HMTEP