Text
                    Б.© 5.® 5.0®.© 5.0 S.©
Б.05.©5.©5.©5.©5.©
5.05.© 5.© 5.05.0 5.©
5.® 5.® 5.® 5.® 5.0 5.®
5.05.© 5.0 5.® 5.05.0
5.® 5.05.05.05.05.®
В.В.ФАРОНОВ
П рограмми рова н не
на персональных
ЭВМ в среде
ТУРБО-ПАСКАЛЬ

В.В. ФАРОНОВ Программирование на персональных ЭВМ в среде ТУРБО-ПАСКАЛЬ 1990
УДК 681.3.06 Ф 24 ББК 32.973 Фаронов В.В. Ф24 Программирование на персональных ЭВМ в среде Тур- бо-Паскаль. - М.: Изд-во МГТУ, 1990.-580 с., ил ISBN 5-7038-0188-5 Описывается версия 5.0 системы программирования Турбо-Паскаль, рассматриваются практические приемы использования этой системы для гибкого управления техническими средствами ПЭВМ, приводятся тексты законченных Турбо-Паскалевых программ широкого назначения. В прилож. дается краткая характеристика версии 5.5. Для инженеров, научных сотрудников, студентов и аспирантов. Табл. 15. Ил.29. Библиогр. 10 назв. л 2404010000-240 л ... . . 4-4U 095<02)-90 ББК 32.973 ISBN 5-7038-0188-5 © Издательство МПУ , 1990
ОГЛАВЛЕНИЕ Предисловие....................................................7 ЧАСТЬ I. ВВЕДЕНИЕ В ТУРБО-ПАСКАЛЬ.............................11 Глава 1. Знакомство со средой Турбо-Паскаля...................12 1.1. Как начать работу с Турбо-Паскалем.....................12 1.2. Функциональные клавиши.................................14 1.3. Текстовый редактор................;....................15 Глава 2. Знакомство с языком Турбо-Паскаля....................18 2.1. Ваша первая программа..................................18 2.2. Типы данных................................./..........21 2.3. Преобразование типов и действия над ними.........;.....25 2.4. Операторы языка.........................................29 2.4.1. Составной оператор и пустой оператор...............29 2.4.2. Условный оператор..................................30/ 2.4.3. Операторы, повторений..............................32 2.4.4. Оператор выбора.....................:.............35 2.4.5. Метки и операторы перехода.........................37 ЧАСТЬ II. ОСНОВЫ ТУРБО-ПАСКАЛЯ.................................38 Глава 3. Среда Турбо-Паскаля...................................39 3.1. Система меню............................................39 3.1.1. Меню опции FILE...................................44 3.1.2. Меню опции RUN....................................47 3.1.3. Меню опции COMPILE................................49 3.1.4. Меню опции OPTIONS................................52 3.1.5. Меню опции DEBUG..................................60 3.1.6. Меню опции BREAK/WATCH............................63 3.2. Директивы компилятора..................................65 3.3. Редактор.....................................:.........67 3.3.1. Служебная строка..................................67 3.3.2. Команды редактора............................... 68 Глава 4. Элементы языка..................................... 72 4.1. Алфавит................................................72 4.2. Идентификаторы.........................................73 4.3. Константы................................м.............73 4.4. Выражения.........................j....................75 4.5. Операции........................................ .....76 Глава 5. Типы данных...........................................80 . 5.1. Простые типы..С........................................80 5.1.1. Порядковые типы...................................80 5.1.2. Вещественные типы.................................88 5.2. Структурированные типы.................................92 5.2.1. Массивы...........................................92
5.2.2. Записи..............................................94 5.2.3. Множества...........................................99 5.3. Строки..................................................102 5.4. Совместимость и преобразование типов....................104 Глава 6. Файлы..................................................108 6.1. Доступ к файлам.........................................109 6.1.1. Имена файлов.......................................109 6.1.2. Логические устройства...............................НО 6.1.3. Инициация файла.................................5... 112 6.2. Процедуры и функции для работы с файлами...............113 6.3. Текстовые файлы.........................................120 6.4. Типизированные файлы....................................127 6.5. Нетипизированные файлы..................................128 Глава 7. Указатели и динамическая память........................132 7.1. Динамическая память.....................................132 7.2. Объявление указателей...................................133 7.3. Выделение и освобождение динамической памяти............135 7.4. Использование указателей.............................. 139 7.5. Процедуры и функции для работы с динамической памятью...142 Глава 8. Типизированные константы...............................145 8.1. Типизированные константы простых типов и типа STRING....145 8.2. Типизированные константы-массивы........................146 8.3. Типизированные константы-записи.........................147 8.4. Типизированные константы-множества......................148 8.5. Типизированные константы-указатели......................148 Глава 9. Процедуры и функции....................................149 9.1. Локализация имен........................................150 9.2. Параметры...............................................153 9.3. Параметры-массивы и параметры-строки....................157 9.4. Процедурные типы, параметры-функции и параметры-процедуры..........................................161 9.5. Нетипизированные параметры-переменные...................163 9.6. Рекурсия и опережающее описание.........................166 Глава 10. Модули.............................................. 169 10.1. Структура модулей......................................170 10.2. Заголовок модуля и связь модулей друг с другом.................................................170 10.3. Интерфейсная часть.....................................171 10.4. Исполняемая часть......................................172 10.5. Инициирующая часть................................... 172 10.6. Компиляция модулей.....................................173 10.7. Доступ к объявленным в модуле объектам.................174 10.8. Стандартные модули.....................................176 Глава И. Другие возможности Турбо-Паскаля.......................178 11.1. Внешние процедуры (функции)............................178 11.2. Использование встроенных машинных кодов...............180 11.3. Обращение к функциям операционной системы..............181 11.4. Поддержка процедур обработки прерываний................183 11.5. Запуск внешних программ................................186 11.6. Оверлей.............................................. 188 4
11.7. Прямое обращение к памяти иш портам ввода-вывода......192 ЧАСТЬ III. УПРАВЛЕНИЕ ТЕХНИЧЕСКИМИ СРЕДСТВАМИ П Э В М......................1'.....................194 Глава 12. Управление экраном в текстовом режиме................195 12.1. Технические особенности видеотерминалов...............195 12.2. Стандартные процедуры и функции управления текстовым экраном........................................202 Глава 13. Управление экраном в графйческом рижиме..............209 13.1. Технические особенности воспроизведения графики.......209 13.2. Стандартные процедуры и функции управления графическим экраном..........................................212 13.2.1. Переход в графический режим и возврат в текстовый..............................................214 13.2.2. Координаты, окна, страницы.......................220 13.2.3. Линии и точки.\..................................225 > ' 13.2.4. Многоугольники..................................230 13.2.5. Дуги, окружности, эллипсы........................233 13.2.6. Краски, палитра, заполнения......................237 13.2.7. Сохранение и выдача изображений..................250 13.2.8. Вывод текста.....................................253 13.3. Черепаховая графика...................................258 13.3.1. Процедуры и функции общего назначения............258 13.3.2. Процедуры и функции черепаховой графики..........261 13.4. Другие возможности графических средств................267 13.4.1. Использование процедур WRITE и WRITELN...........267 13.4.2. Включение драйвера и шрифтов в тело программы............................................ 270 Глава 14. Управление клавиатурой, принтером и звуковым устройством...........................................273 14.1. Технические особенности клавиатуры....................273 14.2. Управление клавиатурой................................276 14.3. Технические особенности принтеров.....................282 14.4. Управление принтером................................. 283 14.4.1. Выбор шрифтов....................................284 14.4.2. Перемещение бумаги...............................285 14.4.3. Печать в графическом режиме....................287 14.5. Управление звуковым устройством.......................289 ЧАСТЬ IV. ПРОГРАММИРОВАНИЕ НА ТУРБО-ПАСКАЛЕ..................................................291 Глава 15. Программы поддержки математических вычислений........293 15.1. Линейные системы уравнений.......................... 293 15.2. Интерполяция..........................................299 15.3. Вычисление определенных интегралов....................304 15.4. Численное интегрирование систем обыкновенных дифференциальных уравнений...................................311 15.5. Решение нелинейных и трансцендентных уравнений........328
15.6. Поиск экстремума функции одной переменной............ 332 Глава 16. Программы разного назначения.........................336 16.1. Дополнительные процедуры и функции управления текстовым экраном.................................336 16.2. Программа установки текущей даты и времени............356 16.3. Дополнительные процедуры и функции управления графическим экраном...............................362 16.4. Программа генерации шрифтов...........................372 Приложения 1. Варианты кодировки знакогенераторов ПЭВМ....................384 2. Стандартные процедуры и функции.............................388 3. Сообщения и коды ошибок.....................................407 3.1. Сообщения об ошибках периода компиляции................407 3.2. Ошибки, возникающие во время выполнения программы...........................................415 4. Основные особенности версии 5.5.;...........................419 Литература.................................................... 433 Предметный указатель...........................................434
ПРЕДИСЛОВИЕ В последние годы в СССР и ряде стран социалистического со- дружества развертывается серийный выпуск разнообразных персо- нальных ЭВМ (ПЭВМ), значительную часть которых составляют так называемые IBM-совместимые ПЭВМ. Эти компьютеры по своей архитектуре (составу аппаратных средств, типу центрального процес- сора и другим особенностям) практически ничем не отличаются от персональных компьютеров фирмы International Business Machines (IBMY таких как IBM PC (Personal Computer - персональный компь- ютер), IBM XT (exclusive Technology - исключительная технология) и IBM AT (Advanced Technology - перспективная технология). В данном случае вряд ли правильным будет упрекать отечественную про- мышленность в слепом копировании западных разработок. Дело в том, что к началу 1989 г. во всем мире было установлено более 20 млн ПЭВМ данного типа и это число продолжает увеличиваться. Таким образом, можно говорить о своеобразном стандарте на персо- нальные ЭВМ 80-х годов, который де-факто определила продукция фирмы IBM. Массовое появление IBM-совместимых ПЭВМ (далее - просто ПЭВМ) в научных лабораториях, конструкторских бюро, вузах стра- ны породило серьезную проблему: богатейшее программное обеспе- чение, созданное многочисленными зарубежными фирмами специ- ально для компьютеров этого типа, по ряду причин остается прак- тически недоступным отечественному пользователю. Пожалуй, наи- большие трудности вызывает нехватка соответствующих методиче- ских материалов: руководств, учебников, пособий, в которых бы рас- сматривались практические вопросы использования разнообразных инструментальных систем, систем программирования, интегрирован- ных сред и других современных программных разработок. Зачастую отечественный программист, воспитанный в аскетических условиях дисплейного класса ОС ЕС и впервые садящийся за ПЭВМ, напо- минает нищего, которому дали в руки ларец со сказочным богатст- вом, но, увы, забыли передать ключи от него. В книге содержится достаточно полное руководство по одной из наиболее популярных у нас в стране и за рубежом систем програм- мирования - Турбо-Паскалю. Эта система относится к семейству Тур- бо-компиляторов, разработанных фирмой Borland International, Inc. (Турбо-Си, Турбо-Бейсик, Турбо-Пролог, Турбо-Ассемблер). Все Тур- бо-компиляторы отличаются весьма высокой скоростью компиляции 7
и, что самое главное, тщательно продуманной и очень удобной сре- дой, создаваемой в них для программиста. При использовании традиционных компиляторов работа про- граммиста строится по схеме: текстовый редактор - компиля- тор - компоновщик - прогон программы. При этом неизбежные мно- гочисленные возвраты к текстовому редактору после компиляции, компоновки или прогона программы для устранения обнаруженных ошибок и необходимой коррекции исходного текста делают проце- дуру создания новой программы весьма утомительной. При работе в среде Турбо-Паскаля пользователю нет необходи- мости покидать эту среду для вызова компилятора, компоновщика или для пробного прогона программы. С точки зрения пользовате- ля, Турбо-Паскаль - это фактически достаточно совершенный тексто- вый редактор, в котором предусмотрены средства компиляции и прогона программ. Переход от режима редактирования к'компиля- ции - компоновке - прогону программы осуществляется нажатием на одну-две клавиши. При обнаружении системой ошибок в программе компиляция или прогон программы прекращается и на экран вы- водится исчерпывающая диагностика с указанием места ошибки. В сложных случаях пользователь может применить мощные средства диалогового (отладчика, входящего в состав Турбо-Паскаля. Все это существенно увеличивает эффективность труда программиста, кото- рая, как показывает практика, может стабильно в течение многих месяцев составлять в среднем 100 и более строк отлаженной про- граммы в день. Разумеется, не только и даже не столько среда, пусть даже весь- ма совершенная, послужила причиной широкой Популярности Тур- бо-Паскаля. Не следует забывать, что в его основе лежит мощный язык программирования, представляющий собой значительно рас- ширенную версию языка Паскаль. Задуманный первоначально авто- ром Н.Виртом как средство для обучения студентов современной методике структурного программирования, этот язык в интерпрета- ции фирмы Borland приобрел множество дополнительных свойств, позволяющих говорить о нем как о вполне современном универ- сальном языке программирования. Пожалуй, именно в Турбо-Паска- ле, как ни в одном другом языке программирования, разумно соче- таются лаконизм и естественность синтаксиса с достаточно гибкими возможностями управления структурами данных и ходом вычисли- тельного процесса. Расширения, сделанные фирмой Borland, произошли в основном в двух направлениях: с одной стороны, включение в язык средств, учитывающих программно-аппаратные особенности ПЭВМ (прямая адресация к памяти, доступ к любым функциям операционной сис- темы, управление звуковыми и графическими средствами и т.д.); с другой, - придание языку некоторых важных свойств, увелйчиваю- 8
щих модульность программ и приближающих Турбо-Паскаль к та- ким более поздним языкам, как Модула-2 и Ада. Все сказанное делает Турбо-Паскаль чрезвычайно привлекатель- ным как для массовых пользователей, так и в глазах профессио- нальных программистов. Турбо-Паскаль создан сравнительно недавно: сама фирма Borland образовалась лишь в 1984 г. В СССР широкое распространение по- лучили три последние версии этой системы - версии 3.0, 4.0 и 5.0. И язык, и среда Турбо-Паскаля существенно изменялись от версии к версии, что, очевидно, отвечает активной рыночной политике фирмы, но в то же время создает дополнительные трудности отече- ственным пользователям, лишенным, как правило, доступа к фир- менной документации. В книге излагаются сведения по новейшей на сегодня (эти строки пишутся в феврале 1989 г.) версии 5.0, дати- рованной августом 1988 г1. Эту книгу ни в коей мере нельзя рас- сматривать как перевод или адаптацию фирменного руководства. Все излагаемые в ней сведения любой читатель сможет подобно ав- тору получить в процессе непосредственной работы с Турбо-Паска- лем, в основном путем внимательного изучения встроенной спра- вочной службы1 2. Несколько слов о структуре книги. В ее основу положен учеб- ный лекционно-практический курс, прочитанный автором весной 1988 г. в МГТУ им. Н.Э.Баумана (в курсе рассматривалась версия 4.0, поэтому при подготовке книги понадобилась существенная кор- рекция его материала). Предполагается, что читателю необязательно знать какой-либо язык программирования, однако самые элементар- ные сведения, хотя бы в пределах школьного курса "Основы ин- форматики и вычислительной техники”, ему должны быть известны (понятие программы, переменной, оператора и т.п.). В ряде случаев мне показалось полезным привести сравнение отдельных конструк- ций языка Турбо-Паскаля с Фортраном ОС ЕС, так как многие сту- денты, аспиранты, инженеры, научные работники - словом, все те, кому в первую очередь адресуется эта книга, в той или иной степе- ни знакомы с Фортраном. Эта книга задумана не как простое пособие по конкретному язы- ку программирования, хотя она и содержит все необходимые сведе- ния по Турбо-Паскалю. В ней сделана попытка рассмотреть с еди- ных позиций современной системы программирования целый ком- плекс вопросов, касающихся проблемы управления техническими средствами ПЭВМ. Вот почему в нее включен обширный материал, не относящийся непосредственно к Турбо-Паскалю, но показываю- 1 См. прилож.4 2 При переводе я отдавал предпочтение смысловым эквивалентам, иногда в ущерб- лингвистической точности. 9
щий, как с его помощью можно обеспечить гибкое управление эк- раном, принтером, звуковым генератором, как получить доступ к средствам операционной системы и как, наконец, разрабатывать прикладные программы, использующие все возможности техниче- ских средств ПЭВМ. Логически книга делится на четыре части. В первой части излагаются основы Турбо-Паскаля в том мини- мальном объеме, который позволит читателю приступить к само- стоятельной работе в его среде. Идеальным можно считать способ изучения материала "за компьютером - с книжкой в руке” (кстати, по принципу "час лекций - час практической работы" был построен и упоминавшийся учебный курс, показавший высокую эффектив- ность такого обучения). К сожалению, далеко не все читатели могут позволить себе сегодня такую роскошь. Вторая часть книги посвящена углубленному изучению среды и языка Турбо-Паскаля. Центральное место в ней занимают принци- пиальные для Паскаля вообще и для Турбо-Паскаля в частности вопросы типов данных и способов работы с ними. После изучения этой части читатель сможет создавать программы любой сложности. В третьей части приводятся более или Менее подробные сведе- ния об особенностях программной и аппаратной реализации ПЭВМ и показано, как можно средствами Турбо-Паскаля решать разнооб- разные задачи управления персональным компьютером. Наконец, в четвертой части приводятся тексты законченных программ, иллюстрирующих практическое использование всего ма- териала. Некоторые из них разработаны специально для этой кни- ги, другие представляют собой перевод на Турбо-Паскаль популяр- ных программ из книги [8] и могут служить основой библиотеки прикладных программ для научных и инженерных расчетов. Излагаемый материал иллюстрируется многочисленными приме- рами. Некоторые из них помечены номерами (пример 1, пример 2 и т.д.), эти примеры рекомендуется повторить при практической работе на ПЭВМ. Для выделения в тексте идентификаторов они на- браны заглавными буквами, хотя во многих фрагментах программ эти идентификаторы могут записываться строчными буквами или комбинацией строчных и заглавных букв. Каждый пример и каждая программа из четвертой части кни- ги - лишь один из многих вариантов реализации лежащих в их ос- нове алгоритмов. Вероятно, некоторые читатели сочтут эти програм- мы недостаточно элегантными и захотят реализовать свой вариант. Ну что ж, в конечном счете именно для того, чтобы побудить чи- тателя к самостоятельной работе с Турбо-Паскалем, и написана эта книга. 10
Часть! ВВЕДЕНИЕ В ТУРБО-ПАСКАЛЬ В этой части излагаются минимальные сведения, необходимые любому читателю для самостоятельного создания несложных Турбо- Паскалевых программ.' Система программирования Турбо-Паскаль представляет собой единство двух в известной степени самостоятельных начал - компи- лятора с языка программирования Паскаль (язык назван так в честь выдающегося французского математика и философа Блеза Па- скаля (1623-1662)) и некоторой инструментальной программной обо- лочки, способствующей повышению эффективности создания про- грамм. Для краткости условимся в дальнейшем называть реализуе- мый компилятором язык программирования языком Турбо-Паскаля, а разнообразные сервисные услуги, предоставляемые программной оболочкой, - средой Турбо-Паскаля.
Глава 1 ЗНАКОМСТВО СО СРЕДОЙ ТУРБО-ПАСКАЛЯ Среда Турбо-Паскаля - это первое, с чем сталкивается любой программист, приступающий к работе с системой. В этой главе приводятся минимальные сведения об основных приемах работы в среде Турбо-Паскаля. Более полные сведения о ней Вы найдете в гл.З. 1.1. КАК НАЧАТЬ РАБОТУ С ТУРБО-ПАСКАЛЕМ Для вызова Турбо-Паскаля необходимо отыскать в древовидной структуре каталогов ПЭВМ файл с именем TURBO.EXE , в котором содержится собственно система, а также файлы TURBO.TPL (библи- отека транслятора) и TURBO.HLP (библиотека справочной службы). При написании программ, использующих графические возможности ПЭВМ, понадобятся библиотечный файл GRAPH.TPU и файл, со- держащий графический драйвер, например CGA.BGI. Тип графиче- ского драйвера и, следовательно, имя соответствующего файла зави- сят от аппаратных средств, реализованных в конкретной ПЭВМ. Драйвер CGA поддерживается на большинстве IBM-совместимых ПЭВМ. Обычно все файлы, относящиеся к Турбо-Паскалю, хранятся в каталоге с именем TP, TURBO, TURBOPAS, PASCAL или подобным. Допустим, что этот каталог называется ТР и располагается на диске С. Тогда для вызова Турбо-Паскаля нужно дать команду C:\TP\TURBO Не рекомендуется работать с системой, назначив в качестве ката- лога по умолчанию (текущего каталога) тот, в котором хранятся пе- речисленные выше файлы: во-первых, в таком случае существует возможность ошибочно стереть какой-либо из этих файлов и тем самым нарушить работоспособность системы, а во-вторых, этот ка- талог очень скоро заполнится другими файлами, прямо не относя- щимися к Турбо-Паскалю. Наконец, Турбо-Паскаль имеет свойство j "запоминать" свою настройку в двух файлах с именами TURBO.TP I и TURBO.PCK. При вызове система начинает поиск этих файлов в текущем каталоге. Если этот каталог - Ваш индивидуальный, систе- 12
ма всякий раз будет настраиваться так, как Вы этого хотите. Если эти файлы не обнаружены в Вашем каталоге (а при первом обра- щении к Турбо-Паскалю так оно и будет), система продолжит поиск в каталогах TURBO и EXE1, а не найдя там, - настроится стандарт- ным образом. Впоследствии Вы сможете сохранить настроечные файлы в своем каталоге и тем самым избавить себя от перенаст- ройки системы всякий раз при обращении к ней. Верхняя строка экрана ПЭВМ после успешного вызова системы содержит ’’меню" возможных режимов работы Турбо-Паскаля, ниж- няя строка - краткую справку о назначении основных функциональ- ных клавишей (рис.1). Вся остальная часть экрана принадлежит так называемому окну редактора. Это окно используется для ввода и коррекции текста Турбо-Паскалевых программ. Верхняя строчка ок- на (вторая сверху на рисунке) содержит служебную информацию текстового редактора: номер строки и позиция в ней, на которой в данный момент располагается курсор, имя редактируемого файла и т.п. Кроме окна редактора в Турбо-Паскале используются также окно отладочного режима и окно вывода результатов прогона программы. File Edit Run Compile Options Debug Break/watch —ш Edit ............I Line 1 Col 1 Insert Indent Unindent A:NONAME.PAS' Turbo Pascal Version 5.0 Copyright (c) 1983> 1988 by Borland International» Inc. Watch Fl-Help F5-Zoom F6~Switch F7-Trace F8-Step F9-Make FlO-Menu Рис.1. Вид экрана после вызова Турбо-Паскаля 1 Это не имена, а функциональное назначение каталогов, см.гл. 3 13
1.2. ФУНКЦИОНАЛЬНЫЕ КЛАВИШИ Функциональные клавиши помечены символами F1 ... F10 и обычно занимают два самых левых ряда на клавиатуре ПЭВМ. С каждой из этих клавишей связывается некоторая команда, управля- ющая средой. Действие почти всех функциональных клавишей можно модифицировать с помощью двух особых клавишей: ALT (от англ. ALTernative - дополнительный) и CTRL (англ. ConTRoL- управление). Клавиши ALT и CTRL используются подобно клавише временной смены регистра: сначала нужно нажать на одну из них и затем, не отпуская ее, нажать функциональ- ную клавишу. В дальнейшем такое нажатие двух клавишей будем обозначать дефисом. Например, ALT-F3 означает, что вместе с кла- вишей ALT необходимо нажать клавишу F3, CTRL-F9 - вместе с CTRL нажимается F9 и т.д. Ниже приводится список тех команд, которые передаются среде некоторыми функциональными клавишами: F1 - обратиться за справкой к встроенной справочной службе; F2 - заЦисать редактируемый текст в дисковый файл; F3 - прочитать текст из дискового файла; F4 - начать или продолжить исполнение программы и остано- виться перед исполнением той ее строки, на которой стоит курсор (используется в отладочном режиме); F5 - совместить на экране изображение двух окон - окна редакто- ра и отладочного окна (работает как двоичный переключатель, т.е. повторное нажатие на F5 вернет экрану первоначальный вид); F6 - сменить окно редактора на отладочное окно; F7- выполнить следующую строку программы; если в строке есть обращение к процедуре (функции), войти в эту процедуру (ис- пользуется в отладочном режиме); F8 - выполнить следующую строку программы, не входить в процедуру; F9 - компилировать программу, но не выполнять ее; F10 - перейти к диалоговому выбору режима работы с помощью главного меню; ALT-F3 - загрузить в редактор одну из программ файла-списка TURBO .РСК; ALT-F5 - сменить окно редактора на окно вывода результатов прогона программы; ALT-F6 - загрузить в редактор последнюю программу из файла- списка TURBO.PCK; ALT-X - кончить работу с Турбо-Паскалем; CTRL-F9 - компилировать программу, находящуюся в редакторе, загрузить и выполнить ее. Полное описание функциональных клавишей Вы найдете в гл.З, а сейчас - самый краткий комментарий. Во-первых, Вам понадобится команда CTRL-F9 для проверки ра- боты Вашей программы и ALT-X (т.е. вместе с ALT нажимается клавиша с латинской буквой X) - для выхода из Турбо-Паскаля. Клавиши F2 и F3 помогут Вам в работе с Вашим каталогом. Ко- 14 .
мандой ALT-F5 Вы в любой момент сможете посмотреть данные, выданные на экран в результате прогона программы. 1.3. ТЕКСТОВЫЙ РЕДАКТОР Текстовый редактор Турбо-Паскаля построен по образцу попу- лярного редактора WORDSTAR и предоставляет пользователю удоб- ные средства создания и редактирования текстов программ. Для пе- рехода от состояния выбора режима из главного меню в состояние редактирования нужно нажать ESC. Признаком того, что среда на- ходится в состоянии редактирования, является наличие в окне ре- дактора мигающего курсора. Если среда находится в состоянии вы- бора из меню, курсор исчезает, а в строке-меню появляется светлый указатель - прямоугольник, выделяющий одно из кодовых слов. Рассмотрим основные приемы работы с текстовым редактором. Для создания текста программы нужно просто ввести этот текст с помощью клавиатуры ПЭВМ подобно тому, как это делается при печатании текста на пишушей машинке. Окно редактора имитирует длинный и достаточно широкий лист бумаги, фрагмент которого виден в окне. Если курсор в своем перемещении дошел до нижне- го края, осуществляется "прокрутка" листа вверх на одну строку. Ес- ли курсор достиг правой границы экрана, окно начинает по мере ввода символов смещаться вправо, показывая правый край "листа". Максимальная длина строки "листа" - 249 символов, однако компи- лятор Турбо-Паскаля воспринимает строки программы длиной не более 126 символов. Вертикальная длина "листа" ограничивается об- щим количеством символов вводимого текста, которых не должно быть больше 64535. Окно можно смещать с помощью следующих клавишей: PGUP - на страницу вверх (от англ. PaGe UP - страницу вверх); PGDN - на страницу вниз (PaGe DowN - страницу вниз); НОМЕ - в начало текущей строки (НОМЕ - домой); END - в конец текущей строки (END - конец); CTRL-PGUP - в начало текста; CTRL-PGDN - в конец текста. Курсор показывает ту позицию экрана, в которую будет поме- щен следующий символ. Клавишами перевода курсора его можно смещать по экрану. При достижении курсором границ окна оно смещается на строку или на символ. Если Вы ошиблись при вводе очередного символа, его можно стереть с помощью продолговатой клавиши, обозначенной стрелкой влево (клавиша "Забой" справа вверху зоны основных клавишей). Клавиша DEL стирает символ, на который в данный момент указывает курсор, команда CTRL-Y - всю строку. Следует помнить, что редактор Турбо-Паскаля вставляет в конце каждой строки невидимый на экране символ-разделитель. Хотя этот разделитель невидим, его можно вставлять и стирать. Разделитель вставляется клавишей "Ввод", а стирается клавишами "Забой” и DEL. С помощью вставки/стирания разделителя можно "разре- зать"/"склеивать" строки: чтобы "разрезать" строку, подведите курсор 15
к нужному месту и нажмите "Ввод" (продолговатая клавиша справа в зоне алфавитно-цифровых клавишей, обозначена изогнутой стрел- кой, иногда словом ENTER); чтобы "склеить" соседние строки, нуж- но установить курсор в конец первой строки (удобно использовать, для этого клавишу END ) и нажать DEL или установить курсор в начало следующей строки (клавишей НОМЕ ) и нажать "Забой". Нормальный режим работы редактора - режим вставки. В этом режиме каждый вновь вводимый символ "раздвигает" текст на экра- не. Учтите, что "разрезание" и вставка пропущенных строк возмож- ны только в этом режиме. Признаком работы редактора в режиме вставки служит кодовое слово INSERT (англ, вставка) в верхней строке окна редактора. Для перехода к режиму наложения новых символов на старый текст нужно нажать клавишу INS, которая ра- ботает в режиме двоичного переключателя. И еще об одной возможности редактора. Обычно в верхней ин- формационной строке окна редактора рядом со словом INSERT ука- зывается слово INDENT (англ, делать абзац). В этом режиме каждая новая строка начинается в «той же позиции на экране, что и преды- дущая. Режим INDENT поддерживает хороший стиль написания программ: отступы от левого края выделяют тело условного или со- ставного оператора и делают программу более наглядной. Отказать- ся от автоотступа можно командой CTRL-O I1, повторное нажатие CTRL-O I восстановит режим INDENT. Ниже перечислены наиболее употребительные команды текстово- го редактора Турбо-Паскаля. Смещение курсора PGUP - на страницу вверх; PGDN - на страницу вниз; НОМЕ - в начало строки; END - в конец строки; CTRL-PGUP - в начало, текста; CTRL-PGDN - в конец текста. Команды редактирования / "Забой" - стереть символ слева от курсора; DEL-стереть символ над курсором; CTRL-Y - стереть строку, на которую указывает курсор; "Ввод" - вставить новую строку, разрезать старую; CTRL-Q L - восстановить текущую строку (действует, если кур- сор не покидал измененную строку). 1 Сочетание CTRL-O I означает: при нажатой клавише CTRL нажима- ется сначала О. затем!. 16
Работа с блоком CTRL-K В - пометить начало блока; CTRL-K К -пометить конец блока; CTRL-K Y -стереть блок; CTRL-K С - копировать блок; CTRL-K V -переместить блок; CTRL-K W -записать блок в дисковый файл; CTRL-K R -прочитать блок из дискового файла; CTRL-K Р - напечатать блок.
Г л а в a 2 ЗНАКОМСТВО С ЯЗЫКОМ ТУРБО-ПАСКАЛЯ 2.1. ВАША ПЕРВАЯ ПРОГРАММА Для знакомства с языком Турбо-Паскаля попробуем составить несложную программу, осуществляющую вывод какой-либо фразы на экран ПЭВМ. Пусть, например, это будет "Турбо-Паскаль". Воз- можный вариант такой программы показан в примере 1. Пример 1 PROGRAM MyFirstProgram; const text='Турбо-Паскаль'; BEGIN writeln(text); END. Прежде всего проанализируем форму представления текста. В программе шесть строк. Строки Турбо-Паскалевой программы обыч- но выделяют некоторые смысловые фрагменты текста и могут не связываться с конкретными действиями в программе: расположение текста программы по строкам - дело вкуса программиста, а не требование синтаксиса языка. Ту же программу можно было бы на- писать, например, так: PROGRAM MyFirstProgram; const text='Турбо-Паскаль’; BEGIN writeln(text); END. В отличие от Фортрана пробел в Турбо-Паскале используется как разделитель отдельных конструкций языка, поэтому программа PROGRAMMyF i rstProgram;consttext=’Турбо-Паскаль’;BEGINwriteln(text); END. будет неверной. В Турбо-Паскале игнорируется различие в высоте букв (заглав- ные или строчные), если только это не связано с текстовыми кон- стантами. Начало программы могло бы, например,выглядеть так: program myfirstprogram; Теперь о смысле отдельных строк. Первая строка начинается за- 18
резервированным словом PROGRAM и содержит объявление имени программы. Поскольку это имя никак в дальнейшем не использует- ся, требование его объявления кажется излишним. В Турбо-Паскале можно опускать объявление имени оператором PROGRAM без ка- ких-либо последствий для программы. Однако во всех примерах этой книги объявление имен программ все-таки сохраняется, что обычно считается признаком хорошего стиля программы. Хороший стиль - это такой способ написания программы, который ‘делает ее наиболее наглядной, понятной любому читателю. В этом отноше- нии использование оператора PROGRAM с именем, в котором, на- пример, сообщается о назначении программы или дается какая-ли- бо дополнительная информация о ней, можно только приветство- вать. В рассматриваемом примере имя MyFirstProgram есть не что иное, как английская фраза "Моя первая программа", но только на- писанная без пробелов - напомню, что пробел в Турбо-Паскале яв- ляется разделителем и не может использоваться произвольно. Как уже говорилось, слово PROGRAM зарезервировано в Турбо- Паскале, т.е. не может использоваться ни в каких иных целях, кро- ме как для объявления имени программы. В Турбо-Паскале имеет- ся множество зарезервированных слов (см. гл. 4). Любое из них нельзя использовать в качестве идентификатора какого-либо объекта программы - переменной, константы и т.д. Первая строка заканчивается особым разделителем - точкой с за- пятой. Этот разделитель в Турбо-Па^калевой программе отмечает конец оператора или описания. Использование особого разделителя позволяет располагать несколько операторов на одной строке. Вторая строка содержит единственное зарезервированное слово CONST, которое означает, что далее будут описаны одна или не- сколько констант. В отличие от Фортрана, константа в Турбо-Паска- ле может иметь собственное имя. Описать константу - значит ука- зать ее имй и значение. Такое указание содержится в третьей стро- ке: константе с именем TEXT присваивается в качестве значения строка символов "Турбо-Паскаль". В Турбо-Паскале могут использо- ваться константы разного типа: целые или вещественные числа, символы, строки символов, массивы и т.д. Признаком того, что TEXT является константой типа строка символов, служат два апост- рофа, обрамляющих строку, причем сами апострофы в строку не включаются. Если понадобится включить апостроф в символьную константу, достаточно его написать дважды подряд. Например, опи- сание , text='Турбо''Паскаль’ создаст константу со значением Турбо’Паскаль Все три первые строки не связаны с какими-либо конкретными действиями при работе программы, но лишь сообщают компиля- тору некоторые сведения о программе и использующихся в ней объектах. Эта часть программы называется разделом описаний. За- резервированное слово BEGIN в четвертой строке сигнализирует компилятору о начале другой части программы - раздела операто- 19
ров. В нашем примере этот раздел содержит единственный испол- няемый оператор WRITELN(TEXT), который, собственно, и осущест- вляет вывод сообщения на экран ПЭВМ. По своей сути оператор WRITELN(TEXT)1 является оператором обращения к встроенной процедуре вывода данных. Понятие проце- дуры - одно из центральных понятий Турбо-Паскаля. Ему в какой- то мере аналогично понятие подпрограммы в Фортране. Процеду- ра - это некоторая последовательность операторов языка Турбо-Пас- каля, к которой (последовательности) можно обратиться по имени. Всякий раз, когда мы называем в операторе имя процедуры, ини- циируется последовательность запрограммированных в ней дейст- вий, поэтому процедура - это прежде всего средство сделать про- грамму более компактной. Компактность достигается как при напи- сании программы, так и при ее реализации: операторы процедуры, образующие ее тело, компилируются лишь один раз и существуют в программе в единственном экземпляре, хотя вызовов этой проце- дуры может быть’ сколько угодно. Однако в Турбо-Паскале роль процедур значительно шире, чем просто средства экономии памяти. Очень часто в Турбо-Паскале в виде процедуры оформляется фрагмент программы, который испол- няется всего лишь один раз. В этом случае процедура используется как средство структурирования программы, расчленения ее на ряд Относительно самостоятельных частей. Процедура WRITELN относится к встроенным процедурам Тур- бо-Паскаля. Встроенная процедура не нуждается в предварительном описании, она доступна любой программе, в которой содержится об- ращение к ней. Процедура WRITELN - одна из немногих процедур, в которой допускается использование произвольного числа парамет- ров. Параметры передаются процедуре в виде списка, располагаю- щегося в круглых скобках сразу за именем процедуры. В нашем примере процедуре передается единственный параметр - константа TEXT. Мы еще много раз будем использовать обращение к встроенной процедуре WRITELN, поэтому имеет смысл разобраться в том, как она работает. Все элементы списка параметров этой процедуры пре- образуются к символьному виду и объединяются в одну строку символов. Строка начинает выводиться с того места экрана, на ко- у торое в данный момент указывает курсор, и продолжается от него вправо. Если символьная строка не умещается целиком на строке экрана (длина строки экрана - 80 позиций), остаток символов пере- носится на следующую строку экрана, причем при необходимости делается "прокрутка” содержимого экрана на строку вверх. После за- вершения вывода символов курсор переходит в начало следующей строки. Именно в этом действии - переходе к началу следующей строки - и состоит единственное отличие между процедурой WRITELN и другой встроенной процедурой WRITE: в процедуре WRITE курсор остается на той же строке Экрана сразу за последним выведенным символом. 1 WRTTELN - от англ. WRITE LiNe - записать строку. 20
Завершает всю программу зарезервированное слово END с точ- кой после него - точка оповещает компилятор о конце текста про- граммы. Анализируя всю программу в целом, мы обнаружим, что четы- ре использовавшихся в ней слова (PROGRAM, CONST, BEGIN и END) являются зарезервированными. Слово WRITELN , строго гово- ря, не относится к зарезервированным, но вряд ли может возник- нуть необходимость переопределять его в программе, так как в этом случае программа лишится мощного и удобного средства вы- вода данных на экран ПЭВМ. Два слова - MYFIRSTPROGRAM и TEXT служат идентификаторами (именами) некоторых объектов программы. Программист может использовать в качестве идентифи- каторов любые последовательности символов, которые удовлетворя- ют следующим ограничениям: - идентификатор может состоять из букв латинского алфавита, цифр, знака подчеркивания; никакие другие символы в идентифика- торе недопустимы; - идентификатор может начинаться только буквой; - идентификатор не может совпадать ни с одним из зарезерви- рованных слов; - длина идентификатора может быть произвольной, но значащи- ми считаются первые 63 символа. Как и всюду в Турбо-Паскалевой программе, в идентификаторах игнорируется разница в высоте букв, поэтому, например, идентифи- каторы text и TEXT с точки зрения компилятора идентичны. Теперь попробуйте исполнить программу. Для этого после ее ввода нажмите CTRL-F9. Если Вы не ошиблись при вводе текста, то спустя несколько секунд заметите быструю смену изображений на экране: сразу после загрузки программы Турбо-Паскаль очищает экран, предоставляя его в распоряжение работающей программы пользователя. Такой экран называется окном программы. После за- вершения программы на экране вновь появится окно редактора с текстом программы. Если Вы не успели разглядеть изображение ок- на программы, нажмите ALT-F5. После нажатия на любую клавишу среда вернет экран в режим воспроизведения окна редактора. 2.2. ТИПЫ ДАННЫХ Структура рассмотренной Турбо-Паскалевой программы имеет следующий вид: PROGRAM MyFirstProgram; { Раздел описаний } BEGIN { Раздел операторов } END. Слова PROGRAM, BEGIN и END выделяют две части програм- мы: раздел описаний и раздел операторов (именно потому эти сло- ва в книге выделены заглавными буквами). Такая структура обяза- тельна для любой Турбо-Паскалевой программы, что является след- ствием жесткого требования языка: любой идентификатор, использу- 21
емый в исполняемых операторах, должен быть предварительно описан в разделе описаний. По сравнению с Фортраном это требо- вание кажется чрезмерно строгим и делающим язык менее свобод- ным. На самом деле в нем проявляется тенденция развития язы- ков программирования в сторону повышения надежности создавае- мых программ. Всякий, кто программировал на Фортране, знает, как порой бывает трудно обнаружить в большой программе оши- бочно введенный или пропущенный символ в идентификаторе. Ес- ли, например, всюду в программе используется переменная с име- нем EPSILON, а в одном месте ошибочно написано EPSLON, то программа может благополучно оттранслироваться и даже давать почти правдоподобный результат для некоторых наборов данных, но в какой-то момент начнет вести себя странно. Обязательное предварительное описание идентификаторов защищает Турбо-Паска- левые программы от такого рода ошибок и повышает их надеж- ность. Описать идентификатор - это значит указать его тип. Понятие типа - одно йз фундаментальных понятий Турбо-Паскаля. В гл. 5 мы подробно рассмотрим различные типы, а сейчас лишь укажем, что тип определяет, во-первых, способ внутреннего представления объекта и, во-вторых, действия, которые разрешается над ним вы- полнять. На первых порах нам понадобятся следующие' простые типы: INTEGtR - целочисленные данные; во внутреннем представле- нии занимают 2 байта; диапазон возможных значений - от -32768 до +33767; данные представляются точно. REAL - вещественные данные; занимают 6 байт; диапазон воз- можных значений модуля от 2.9Е-39 до 1.7Е+38; точность представ- ления данных - 11...12 значащих цифр. CHAR-символ, занимает 1 байт. STRING - строка символов; занимает МАХ+1 байт, где МАХ- максимальное количество символов в строке. BOOLEAN - логический тип; занимает 1 байт и имеет два значе- ния: FALSE (ложь) и TRUE (истина). Тип константы определяется способом записи ее значения, на- пример: const cl=17; с2=3.14; сЗ=’А’; , с4=’3.14’; c5=fa Ise; При анализе этого фрагмента программы компилятор отнесет первую константу к типу INTEGER, вторую - к типу REAL, третью к CHAR, четвертую к STRING и последнюю к BOOLEAN. Призна- ком, позволяющим отнести константу к INTEGER или к REAL, яв- ляется отсутствие или наличие десятичной точки в ее значении. Разумеется, константы С2 и С4 относятся к разным типам: С2 к REAL (в константе есть десятичная точка), а С4 к STRING (кон- станта обрамлена апострофами). В качестве значений объявляемых 22
констант в Турбо-Паскале допускаются выражения. Выражения дол- жны в этом случае содержать в качестве операндов только констан- ты, в том числе и ранее объявленные, а также знаки математиче- ских операций, скобки и стандартные функции,'например: const х = (127 * 3) div 2; у = х + 17; ArraySize = 800 * SizeOf(Real); При описании переменных за идентификатором ставится двоето- чие и кодовое слово-имя типа. Несколько однотипных перемен- ных можно объединять в список, разделяя их запятыми. В начале раздела описания переменных должно стоять кодовое слово VAR (от англ. VARiables - переменные), например: var sigma : real; a,b,c,d : char; textl : string[15]; text2 : string; flag : Boolean; Как уже говорилось, тип данных определяет длину внутреннего представления соответствующих переменных. В частности, длина внутреннего представления переменных типа STRING (строка сим- волов) зависит от максимального количества символов, которые мо- гут составлять строку. В приведенном выше примере переменная ТЕХТ1 описана с указанием ее максимальной длины (15 символов), а в описании переменной ТЕХТ2 максимальная длина не указана и компилятор установит для нее предельно допустимую в Турбо-Пас- кале длину-255 символов. Рассмотрим еще одну несложную программу (пример 2). Ее на- значение - ввести с клавиатуры два целых числа, найти отношение первого ко второму и вывести полученный результат на экран. Пример 2 PROGRAM MySecondProgram; { Программа вводит два целых числа и выводит частное от деления 1-го на 2-е } var nl,n2 : integer; { nl и п2 - вводимые целые } х : real; { х - результат } BEGIN write('nl='); readln(nl); { вводится nl } write('n2='); readln(n2); { вводится n2 } x:=nl/n2; { найти результат } writeln(rnl/n2=',х); { вывести его } END. Прежде всего бросается в глаза появление в программе поясняю- щих комментариев. Комментарий в Турбо-Паскале - это произволь- ная последовательность любых символов, обрамленная фигурными скобками. Комментарий разрешается вставлять в любое место про- граммы, где по смыслу может стоять пробел. В качестве ограничи- телей комментария допускается использовать фигурные скобки, а 23
также пары символов: (* - слева от комментария и *) - справа от него: { Это комментарий } (* Это тоже комментарий *) Комментарии с однотипными ограничителями нельзя вклады- вать друг в друга, т.е. недопустимы последовательности вида { ... { ... } ... } или (* ... (* ... *) ... *) . Однако можно вкладывать комментарии с ограничителями раз- ных типов (не более одной глубины вложения): { ... (* ... *) ... } или (* ... { ... } ... *) . Последнее обстоятельство проясняет кажущуюся странной избы- точность ограничителей: если Вы всюду в программе будете ис- пользовать ограничители одного типа, то для того, чтобы временно исключить из программы какой-либо фрагмент текста, достаточно заключить его в ограничители другого типа. Наличие комментариев в программе избавляет от необходимости пояснять назначение отдельных операторов. Несколько слов о вводе данных. Пары операторов WRITE(.„); READLN(...) работают следую- щим образом. Вначале' оператор WRITE выводит строку на экран и оставляет курсор в конце ее. Затем по оператору READLN вызыва- ется встроенная процедура ввода данных и программа останавлива- ется в ожидании ввода. В этот момент Вы должны набрать на кла- виатуре нужное число и нажать "Ввод". Сразу после этого програм- ма продолжит работу - проанализирует введенное число и перейдет к вводу следующего числа или вычислению результата. Таким обра- зом, сигналом окончания подготовки очередного числа является на- жатие на "Ввод", до этого момента Вы можете стирать любой оши- бочно введенный символ клавишей "Забой”. Для вычисления отношения введенных чисел используется один из основных операторов Турбо-Паскаля - оператор присваивания. В , его левой части всегда указывается имя переменной, правая часть представляет собой выражение того же типа, что и переменная. Па- ра символов ": = " означает "присвоить значение". Практически во всех языках программирования используется оператор присваива- ния. В некоторых, например в Фортране, символом присваивания является знак равенства. Однако новичка, привыкшего к строгости математических формул, может поставить в тупик типичная форма записи фортран-оператора* присваивания, например, такая: X = X + 1 . Турбо-Паскалевый вариант записи этого же оператора X := X + 1 в этом смысле кажется более логичным. Разумеется, вряд ли ко- му-нибудь придет в голову видеть уравнения там, где их нет. Ко- нечно же, и в том и в другом случае реализуется одно и то же ал- горитмическое действие: к содержимому X прибавляется единица и полученный результат вновь присваивается переменной X. Обратите внимание на оператор вывода результатов WRITELN: i нем в. качестве одного из параметров явно указывается констант< типа строка символов ’п!/п2’. Конечно же, константы '(в отличие oi 24
переменных) вовсе не обязательно описывать в разделе описаний, так как их тип легко определяется транслятором по форме записи константы. Учитывая это и опуская предложение PROGRAM, мы могли бы записать программу из примера 1 предельно лаконично: BEGIN writeln('Турбо-Паскаль'); END. 2.3. ПРЕОБРАЗОВАНИЯ ТИПОВ И ДЕЙСТВИЯ НАД НИМИ Как уже говорилось, тип переменной позволяет не только уста- новить длину ее внутреннего представления, но и контролировать те действия, которые осуществляются над ней в программе. Конт- роль за использованием переменных еще на этапе компиляции программы - важное преимущество Турбо-Паскаля перед другими языками программирования, в которых допускается автоматическое преобразование типов. Фактически именно та вольность, с какой, например, ПЛ/1 преобразует по умолчанию различные типы дан- ных, послужила объектов наиболее сильной его критики. В Турбо-Паскале почти невозможны неявные (автоматические) преобразования типов. Исключение сделано только в отношении констант и переменных типа INTEGER, которые разрешается ис- пользовать в выражениях типа REAL. Если, например, переменные XiH Y описаны следующим образом: var х : integer; у : real; то оператор у := х + 2 будет синтаксически правильным, хотя справа от знака присваива- ния стоит целочисленное выражение, а слева - вещественная пере- менная - компилятор сделает необходимые преобразования автома- тически. В то же время оператор х := 2.0 будет неверным, так как автоматическое преобразование типа REAL к типу INTEGER в Турбо-Паскале запрещено. Разумеется, запрет на автоматическое преобразование типов еще не означает, что в Турбо-Паскале нет средств преобразования дан- ных. Эти средства, конечно же, есть, но их нужно использовать яв- но. Для преобразования данных в языке, существуют встроенные Функции, которые получают в качестве параметра значение одного типа, а возвращают результат в виде значения другого типа. В част- ности, для преобразования от REAL к INTEGER имеются даже две встроенные функции такого рода: ROUND округляет REAL до бли- жайшего целого, a TRUNC усекает REAL путем отбрасывания дроб- ной части. 25
Например, ошибочным будет оператор х := у / х, но правильным х := round(y/x) (объявления переменных см. выше). Понятие функции в Турбо-Паскале близко к понятию процеду- ры. Как и процедура, функция вызывается своим именем и может содержать произвольное число операторов Турбо-Паскаля и даже внутренних процедур и функций. Существенным отличием функ- ции от процедуры является то обстоятельство, что функция имеет собственное значение и, следовательно, может использоваться нарав- не с переменными в выражениях соответствующего типа. Для преобразования данных типа CHAR (символ) в Цел°е число используется функция ORD, обратное преобразование от INTEGER к CHAR - функция CHR. С помощью несложной программы (пример 3) Вы сможете уз- нать внутренний код произвольного символа. Пример 3 PROGRAM CodeOfChar; { Программа читает символ с клавиатуры и выводит на экран символ и- соответ- ствующий ему внутренний код } var j ch : char; BEGIN readln(ch); writeln(ch, ’=',ord{ch)); END. Далее в табл.7 приведены основные символы ПЭВМ и их внут- ренняя кодировка. По мере надобности мы будем знакомиться с другими функция- ми преобразования типов данных, а сейчас-о тех операциях, кото- рые разрешены над различными типами. Конечно же, в Турбо-Паскале есть все четыре арифметические операции над переменными REAL и INTEGER: \ + - сложение; - вычитание; * - умножение; / - деление вещественное; div - деление целочисленное. Наличие двух операций деления есть еще одно проявление ос новополагающего принципа Турбо-Паскаля: программист должен яв- но подтверждать компилятору, что он готов к возможным последст- виям преобразования типов. Если, например, в Фортране использу- ется выражение г/2, то результат этого выражения будет зависеть oi того, переменной какого типа он будет присвоен: если X есть пере менная целого типа, a Y - вещественного, то фортрановские присва ивания X = 1/2 Y = 1/2 26
дадут значение 0 для X и 0.5 для Y. В. Турбо-Паскале такой дву- смысленности нет: выражение г/2 всегда имеет значение 0.5 и поэ- тому оператор X := 1/2 для X типа INTEGER просто недопустим. В то же время разрешен- ный в Турбо-Паскале оператор var Y : real; Y := 1 div 2 самим фактом использования. операции целочисленного деления DIV свидетельствует о том, что программист сознательно отбрасыва- ет дробную часть результата. (Надеюсь, что читатель извинит яв- ную искусственность этих примеров, которая вызвана лишь стрем- лением проиллюстрировать обсуждаемые особенности языка.) Для данных типа INTEGER в Турбо-Паскале есть еще одна опе- рация деления - MOD, т.е. получение остатка от целочисленного де- ления, например: 5 mod 2 = 1, 31 mod 16 = 15, 18 mod 3 = 0. В Турбо-Паскале отсутствует операция возведения в степень, что, очевидно, будет вызывать определенные неудобства при реализации вычислительных алгоритмов. Некоторым утешением может служить наличие встроенной функции SQR , возвращающей квадрат от зна- чения параметра, причем тип результата определяется типом пара- метра. (Читателям, имеющим опыт программирования на Бейсике, следует учесть, что функция SQR(X) в Турбо-Паскале имеет прямо противоположный математический смысл по сравнению с одно- именной функцией в Бейсике, которая извлекает корень квадратный из параметра X). И еще об одном существенном недостатке Турбо-Паскаля: в нем отсутствуют комплексный тип и соответствующие операции над ним. Вообще, в отношении реализации разнообразных вычисли- тельных процедур Турбо-Паскаль значительно уступает Фортрану ОС ЕС. В частности, в нем намного беднее набор встроенных мате- матических функций (см. далее табл. 9). Кроме этих функций могут оказаться полезными две процедуры: DEC(X [,N]) - уменьшает значение X на N; если N не задан, то на 1; тип X,N- INTEGER; INC(X [,N]) - увеличивает значение X на N; если N не задан, то на 1; тип X,N - INTEGER1. Над символами и строками символов определена единственная 1 Здесь и далее при описании синтаксиса вызова процедур (функций) в квадратных скобках указываются необязательные параметры. 27
операция - сцепление двух строк. Операция обозначается символом "+". Например, программа var st:string; BEGIN st: = 'Турбо’ + +’Паскаль’; writeln(st); END. напечатает все ту же строку ''Турбо-Паскаль”. Все остальные действия над строками и символами реализуются с помощью встроенных процедур и функций (см. гл. 5). И, наконец, об операциях отношения и логических операциях. Над данными типа REAL, INTEGER, CHAR, STRING, BOOLEAN определены следующие операции отношения (сравнения): ' = - равно; О - не равно; < - меньше; > - больше; < = - меньше или равно; > = - больше или равно. В операциях сравнения должны участвовать однотипные операн- ды. Исключение сделано опять-таки в отношении REAL и INTEGER, которые могут сравниваться друг с другом. Результат применения операции отношения к любым операндам имеет тип BOOLEAN. Сравнение двух строк осуществляется следующим образом. Сим- i волы строк сравниваются попарно друг с другом так, что первый символ первой строки сравнивается с первым символом второй строки, второй символ первой строки - со вторым символом второй и тд. Символы сравниваются путем сравнения их кодов во внут- реннем представлении (см. далее табл. 7). Если одна строка короче; другой, недостающие символы заменяются нулем. Отношение пер-1 вой несовпадающей друг с другом пары символов и принимается! за отношение двух строк. При сравнении данных типа BOOLEAN учитывается внутреннее? соглашение Турбо-Паскаля, в соответствии с которым FALSE есть? нулевой байт, a TRUE-байт с единицей в младшем разряде: ord(false) = 0, | ord(true) = 1. В Турбо-Паскале определены следующие логические операции: not - логическое НЕ; and - логическое И; or - логическое ИЛИ; хог - исключительное ИЛИ. Логические операции применимы к операндам целого и логичен ского типа. 28
Если операнды - целые числа, то результат логической операции есть тоже целое число. Логические операции над логическими дан- ными дают результат логического типа. При вычислении выражений любого типа приоритет вычисле- ний определяется расставленными скобками, а при их отсутствии - по табл.1 (в порядке убывания приоритета). Таблица 1 Приоритет операций Приоритет Операция 1 2 3 4 not, @ *, /, div, mod, and, shl, shr + . -, or, xor =7 < >, <. >, < =, > in Примечание. Операции @ (получение адреса' объекта) и in (принадлежность к множеству) описаны в гл.4. Поскольку логические операции имеют более высокий приори- тет, чем операции отношения, в сложных логических выражениях обычно необходимо расставлять скобки. Если, например, В и С имеют тип INTEGER, то выражение А = В and С < D вызовет сообщение о синтаксической ошибке. Правильным будет выражение: (А = В) and (С < D) . 2.4. ОПЕРАТОРЫ ЯЗЫКА С одним из наиболее часто используемых операторов языка Турбо-Паскаля - оператором присваивания мы уже познакомились. В этом разделе рассматриваются остальные операторы языка. 2.4.1. Составной оператор и пустой оператор Составной оператор - это последовательность произвольных опе- раторов программы, заключенная в операторные скобки BEGIN ... END. Составные операторы - важный инструмент Турбо-Паскаля, да- ющий возможность писать программы по современной технологии структурного программирования (без операторов перехода GOTO). Турбо-Паскаль не накладывает никаких ограничений на характер операторов, входящих в составной оператор. Среди них могут быть и другие составные операторы - Турбо-Паскаль допускает произволь- ную глубину их вложенности: 29
begin begin begin end; end; end; Фактически весь раздел операторов, обрамленный кодовыми сло- вами BEGIN ... END , представляет собой один составной оператор. Поскольку кодовое слово END является закрывающей оператор- ной скобкой, оно одновременно указывает и конец предыдущего оператора, поэтому ставить перед ним признак конца предыдущего оператора - символ не обязательно, и далее во всех примерах мы не будем этого делать. Наличие точки с запятой перед END в предыдущих примерах на самом деле означало, что между послед- ним оператором и операторной скобкой END располагается так на- зываемый пустой оператор. Пустой оператор не содержит никаких действий, просто в программу добавляется лишняя точка с запятой. В основном пустой оператор используется для передачи управления в конец составного оператора. 2.4.2. Условный оператор Условный оператор позволяет проверить некоторое условие и в зависимости от результатов проверки выполнить то или иное дейст- вие. Таким образом, условный оператор - это средство ветвления вычислительного процесса. Структура условного оператора имеет следующий вид: IF < условно THEN < оператор! > ELSE < оператор? >, где IF, THEN, ELSE - зарезервированные слова (англ, если,то,ина- че); ! < условно - произвольное выражение логического типа; 1 <оператор!>,<оператор?>-любые операторы Турбо-Паскаля. Условный оператор работает по следующему алгоритму. Вначале вычисляется условное выражение. Если результат есть TRUE, то выполняется < оператор! >, а < оператор? > пропускается; если ре-’ зультат есть FALSE, то, наоборот, < оператор! > пропускается, а вы- полняется < оператор? >. Часть оператора ELSE < оператор? > может быть опущена. Тог- да при значении условного выражения TRUE выполняется < опера- тор! >, в противном случае этот оператор пропускается. ’ Поскольку каждый из операторов < оператор! > и < оператор? > может быть оператором любого типа, в том числе и условным, а я 30
то же время не каждый из "вложенных" условных операторов мо- жет иметь часть ELSE < оператор! > , то возникает неоднознач- ность трактовки условий. Эта неоднозначность в Турбо-Паскале ре- шается следующим образом: любая встретившаяся часть ELSE соот- ветствует ближайшей к ней "сверху" части THEN условного операто- ра. Например: var a,b,c,d : integer; а:=1; b:=2; с:=3; d:=4; if a>b then if c<d then if c<0 then c:=0 else a:=b; { a = 1 } if a>b then if c<d then if c<0 then c:=0 else else else a:=b; { a = 2 } Рассмотрим программу (пример 4), которая вводит произвольное целое число в диапазоне 0...15, преобразует его к шестнадцатирич- ному основанию и выводит на экран полученное. В шестнадцати- ричной системе счисления используется 16 цифр в каждом разряде: цифры 0...9 обозначают первые 10 возможных значений разряда, буквы A...F - остальные шесть. Пример 4 PROGRAM Hexl; { Программа вводит с клавиатуры целое число 0 • • 15, преобразует его к шестнадцатиричной системе счисле- ния и выводит его на экран } ' var ch : char; n : integer; BEGIN write(’n = ’); readln(n); if (n >= 0) and (n < = 15) then begin if n < 10 then ch := chr(ord(’0 ) + n) else ch := chr(ord(’A') + n - 10); writeln( 'n = ’,ch) end else write 1 n('Ошибка’) END. В программе используются непрерывность и упорядоченность Множества цифр 0...9 и множества букв A...F (см. далее таол./). 31
2.4.3. Операторы повторений В Турбо-Паскале имеются три различных оператора, с помощью которых можно запрограммировать повторяющиеся фрагменты про- грамм. Оператор цикла FOR <пц>:=<нз> ТО <кз> DO <опера- тор >. Здесь FOR, ТО, DO - зарезервированные слова (англ.: для, до, выполнить); < пц> - параметр цикла - переменная типа INTEGER (точнее, любого порядкового типа, см. гл. 5) ; < нз> - начальное значение - выражение того же типа; < кз> - конечное значение - выражение того же типа ; < оператор >-произвольный оператор Турбо-Паскаля. При выполнении этого оператора вначале вычисляется выраже- ние <нз> и осуществляется присваивание <пц>:=<нз> . После этого циклически повторяется: - проверка условия <пц> <= <кз> ; если условие не выпол- нено, оператор FOR завершает свою работу; - выполнение оператора < оператор >; - наращивание <пц> на единицу. В качестве иллюстрации применения оператора FOR ... рассмот-j рим программу, осуществляющую ввод с клавиатуры произвольного целого числа N и вычисление суммы всех целых чисел от 1 до N (пример 5). Пример 5 PROGRAM Summalnteger; { Подсчет суммы целых чисел } var i, n, s : integer; BEGIN write('N= '); readln(n); {ввести N} s:=0; {начальное значение суммы} for i:=1 to n do s:=s+i; writeln('Сумма = ’,s) END. Отметим два обстоятельства. Во-первых, । в отличие от оператор Фортрана DO, условие, управляющее работой оператора FOR, прове ряется ПЕРЕД выполнением оператора < оператор >: если услови< не выполняется в самом начале работы оператора FOR, исполняв мый оператор не будет выполнен ни разу (в Фортране тело цикл выполняется хотя бы раз). Другое обстоятельство - шаг наращивз ния параметра цикла строго постоянен и равен +1. Существуй другая форма оператора: FOR <пц>:=<нз> DOWNTO <кз> DO <оператор>. 32
I Замена кодового слова ТО на DOWNTO означает, что шаг наращи- вания параметра цикла равен -1 , а управляющее условие приобре- тает вид ( <пц> >= <кз>. Вот как можно модифицировать пример 5, чтобы сделать его пригодным для подсчета любых сумм - как положительных, так и отрицательных: s:=0; if n >= 1 then for 1:=1 to n do s:=s+i else for i:=-l downto n do s:=s+i; Два других оператора повторений лишь проверяют условие вы: полнения или повторения цикла, но не связаны с изменением па- раметра цикла. Оператор WHILE < условно DO < оператор >. Здесь WHILE, DO - кодовые. слова (англ, пока [выполняется], де- лать); < условие > - выражение логического типа; < оператор > - произвольный оператор Турбо-Паскаля. Если < выражение > имеет значение TRUE, то выполняется < оператор >, после чего повторяется проверка условия. Если < вы- ражение > имеет значение FALSE, оператор WHILE прекращает свою работу. Рассмотрим полезный пример 6, иллюстрирующий использова- ние оператора WHILE. Найдем так называемое "машинное эпси- лон" - такое минимальное, не равное нулю вещественное число, которое после прибавления его к 1.0 еще дает результат, отличный от единицы. Читателя, привыкшего к непрерывной вещественной арифметике, может повергнуть в недоумение утверждение о том, что в дискретной машинной арифметике всегда существуют такие числа 0 < X < eps, что 1.0 + X = 1.0. Дело в том, что внутрен- нее представление типа REAL может дать "лишь" приблизительно Ю14 возможных комбинаций значащих разрядов в отведенных для него 6 байтах. Конечно же, это очень большое число, но оно несо- поставимо с бесконечным множеством вещественных чисел. Ап- проксимация бесконечного непрерывного множества вещественных чисел конечным (пусть даже и очень большим) множеством их внутреннего машинного представления и приводит к появлению "машинного эпсилон". Обсуждение этой проблемы и много других полезных сведений Вы найдете в прекрасной книге [8]. 2 • Фаронов 33
Пример 6 PROGRAM EpsilonDetect; { Программа вычисляет и выводит на экран значение ’’машинного эпсилон” } var epsilon : real; BEGIN epsilon := 1; while epsilon/2 + 1 > 1 do eps iIon := eps ilon/2 ; writeln( 'Машинное эпсилон = epsilon) END. Оператор REPEAT <тело цикла > UNTIL < условие >. Здесь REPEAT, UNTIL- кодовые слова (англ.: повторять, до тех пор [,пока не будет выполнено]); <тело цикла > - произвольная последовательность операторов Турбо-Паскаля; < условно - выражение логического типа. Операторы <тело цикла > выполняются хотя бы один раз, по- сле чего проверяется <условно: если его значение есть FALSE, операторы <тело цикла > повторяются, в противном случае опера- тор REPEAT ... UNTIL завершает свою работу. Для иллюстрации применения оператора REPEAT ... UNTIL мо- дифицируем программу из примера 3. Модификация (прийер 7) состоит в том, что программа будет все время повторять цикл вво- да символа и печати его кода до тех пор, пока очередным симво- лом не будет CR (вводится клавишей ’’Ввод"). Пример 7 PROGRAM CodeOfChar; { Программа вводит символ и выводит на экран его код. Для завершения работы программы дважды нажать "Ввод” } var ch : char; {вводимый символ} const CR = 13; {код CR} BEGIN repeat readln(ch); writeln(ch,’ = ’,ord(ch)) until ord(ch)=CR END. Обратите внимание: пара REPEAT ... UNTIL подобна оператор- ным скобам BEGIN ... END, поэтому перед UNTIL ставить точку с запятой необязательно. 34
2.4.4. Оператор выбора Оператор выбора позволяет выбрать одно из нескольких возмож- ных продолжений программы. Параметром, по которому осуществ- ляется выбор, служит так называемый ключ выбора - выражение любого порядкового типа (любого из рассмотренных, кроме типов REAL и STRING, см. гл. 5). Структура оператора выбора такова: CASE <ключ выбора > OF < список выбора > ELSE < опера- тор > END. Здесь CASE, OF, ELSE, END - ключевые слова (Ьнгл.: выбор, из, иначе, конец); < ключ выбора > - выражение типа INTEGER (точнее, любого порядкового типа, см. гл. 5); < список выбора>-одна или более конструкций вида: <кон- станта выбора > : < оператор >; < константа выбора > - константа того же типа, что и выражение < ключ выбора >; < оператор > - произвольный оператор Турбо-Паскаля. Оператор работает следующим образом. Вначале вычисляется значение выражения <ключ выбора > , а затем в последовательно- сти операторов <список выбора» отыскивается такой, которому предшествует константа, равная вычисленному значению ключа вы- бора. Найденный оператор выполняется, после чего оператор выбо- ра завершает свою работу. Если в списке выбора не будет найдена константа, соответствующая вычисленному значению ключа выбора, управление передается оператору, стоящему за кодовым словом ELSE. Составим программу (пример 8), имитирующую работу микро- калькулятора. Программа вводит две строки: первая содержит два произвольных числа, разделенных пробелом, вторая - символ ариф- метического действия, например: 2 2 * или 18.35 0.12 / Программа осуществляет над введенными числами соответствую- щее действие и выводит на экран результат. Признаком конца ра- боты программы служит любой символ, отличный от +, -, *, /. 2* 35
Пример 8 PROGRAM Calc; { Программа вводит два числа в первой строке и один из знаков +, ★, / во второй и выводит на экран результат соответствующего арифметического действия } var operation : char; {знак операции} х, у, z : real; {операнды и результат} stop : Boolean; {признак ошибочной опера- ции и останова} BEGIN stop := false; repeat writein; {пустая строка-разделитель} write('x,y= ’); readln(x,y); write('операция: ’); read In(operation); case operation of + / := x + y; z := x - y; ’ * • : z := x * y; : z := x / y; else stop := true; end; if not stop then writeln(’ результат= ’,z) until stop END. Часть ELSE < оператор > можно опускать. Тогда при отсутствии в списке выбора нужной константы оператор выбора просто завер- шит свою работу. Любому из операторов списка выбора может предшествовать не одна, а несколько констант выбора, разделенных запятыми. Напри- мер, следующая программа при вводе одного из символов у или Y выведет на экран слово "Да", а при вводе п или N-слово "Нет": var ch : char; BEGIN readln(ch);, case ch of 'n ’, 'N ' : writein('Нет '); 'y ’, 'Y' : writeln('Да') * end END. ! 36
2.4.5. Метки и операторы перехода Можно теоретически показать, что уже рассмотренных операто- ров вполне достаточно для написания Турбо-Паскалевых программ любой сложности. В этом отношении наличие в языке операторов перехода кажется излишним. Более того, современная технология структурного программирования основана на принципе ’’программи- ровать без GOTO": считается, что злоупотребление операторами пе- рехода затрудняет понимание программы, делает ее запутанной и сложной в отладке (см., например, [4]). Тем не менее, в некоторых случаях использование операторов перехода может упростить программу. Оператор перехода имеет вид GOTO < метка > . Здесь GOTO - кодовое слово (англ, перейти на [метку]). Метка в Турбо-Паскале - это произвольный идентификатор, по- зволяющий именовать некоторый оператор программы и таким об- разом ссылаться на него. В целях совместимости со стандартным Паскалем, как он описан в [3], в Турбо-Паскале допускается в каче- стве меток использовать также целые числа без знака. Метка располагается непосредственно перед помечаемым опера- тором и отделяется от него двоеточием. Оператор можно помечать несколькими метками, которые в этом случае отделяются друг от друга двоеточием. Перед тем как появиться в программе, метка должна быть описана. Описание меток состоит из кодового слова LABEL, за которым следует список меток: ... Л . \ label loop, 1Ы, 1Ь2; begin goto Ibl; loop: ........... 1Ы:1Ь2: ...... goto' 1Ь2; Действие оператора GOTO состоит в передаче управления соот- ветствующему меченому оператору. При использовании меток необходимо руководствоваться следую- щими правилами: - метка, описанная в разделе описаний, обязательно должна встретиться где-нибудь в теле программы; - метки, описанные в процедуре (функции), локализуются в ней, поэтому передача управления извне процедуры (функции) на метку внутри нее невозможна. 37
Ч а с т ь II ОСНОВЫ ТУРБО-ПАСКАЛЯ
Г л а в a 3 СРЕДА ТУРБО-ПАСКАЛЯ Термином “среда Турбо-Паскаля" мы будем обозначать разнооб- разные сервисные средства, которые не относятся непосредственно к языку программирования, но служат для облегчения процесса раз- работки, отладки и испытания программ и в конечном счете обес- печивают повышение производительности труда программиста. На мой взгляд, среда Турбо-Паскаля - это выдающееся достиже- ние в области систем программирования, которое на многие годы вперед будет определять самый высокий уровень подобного рода разработок. Я глубоко убежден, что отныне любая другая фирма- разработчик систем программирования не сможет не учитывать идей и подходов, реализованных в среде Турбо-Паскаля, если только она собирается выпускать конкурентоспособный на мировом рынке программный продукт. Турбо-Паскаль наглядно демонстриру- ет, как разительно увеличивается производительность труда про- граммиста в тщательно спланированной и качественно реализован- ной интерактивной среде. Для примера сошлюсь на собственный опыт: разработка достаточно сложной диалоговой системы автомати- зированного моделирования [7] объемом около 12000 строк исход- ного текста на Турбо-Паскале, включая отладку и тестирование, за- няла 13 недель, т.е. средняя производительность труда составила более 100 строк отлаженной программы в день. К сожалению, диалоговая среда Турбо-Паскаля англоязычна, что может вызывать известные затруднения у многих отечественных программистов. В этой главе содержатся все необходимые сведения о среде Турбо-Паскаля, которые, я надеюсь, будут способствовать быстрой адаптации к ней программистов, не владеющих в достаточ- ной мере английским языком. 3.1. СИСТЕМА МЕНЮ Все управление средой Турбо-Паскаля осуществляется в основ- ном с помощью системы последовательно разворачивающихся ме- ню. Меню фиксирует некоторое текущее состояние диалоговой сре- ды и предлагает несколько альтернативных путей перехода из этого состояния. Каждое конкретное меню реализуется в виде небольшого 39
окна с текстом, которое как бы накладывается на существующее на экране изображение. Содержащиеся в меню альтернативы условим- ся называть в дальнейшем опциями (от англ, option - вариант). На рис. 2 показана структура меню Турбо-Паскаля (на рис. 3 приводится перевод опций). В Турбо-Паскале используются опции трех видов: опции-указате- ли, опции-переключатели и опции-параметры. Опции-указатели фиксируют какие-то новые "состояния или ре- жимы работы среды. При выборе такого рода опции либо, немед- ленно осуществляются предусмотренные ею действия, либо среда переходит в новое состояние и появляется новое меню, фиксирую- щее это состояние. Типичным примером опции-указателя являются все опции главного меню (верхняя строчка на рис. 2). Например, при выборе опции EDIT осуществляется немедленный переход в режим редактирования, а выбор опции COMPILE приводит к разво- рачиванию дополнительного меню. Опции-переключатели позволяют выбрать одно из двух возмож- ных значений некоторого параметра. Справа от опции-переключате- ля в меню обязательно указывается кодовое слово, которое обозна- чает текущее состояние переключаемого параметра. Если выбрать эту опцию и нажать "Ввод", значение параметра изменится на про- тивоположное, соответствующим образом изменится и кодовое слово в меню. Например, в меню, связанном с упоминавшейся опцией COMPILE, есть опция DESTINATION - местоположение вновь созда- ваемой программы. Эта опция задает два возможных значения со- ответствующего параметра компилятора: размещение программы в памяти (MEMORY) или на диске (DISK). Опция-параметр связана с вводом числовых или текстовых пара- метров, Выбор такой опции приводит к появлению на экране не- большого окна, в котором указывается принятое по умолчанию или установленное ранее значение параметра и предлагается ввести его новое значение. Например, меню COMPILE содержит опцию-пара- метр FIND ERROR (поиск ошибки); при ее выборе появляется до- полнительное окно, в котором предлагается ввести адрес ошибки периода исполнения, чтобы среда могла найти и показать соответст- вующий оператор программы. Как следует из рис. 2, меню образуют древовидную структуру. При путешествии по этой структуре необходимо руководствоваться следующими правилами: - Переход в главное меню из режима редактирования и воз- врат обратно осуществляется с помощью клавишей F10 и ESC. - Очередная выбираемая опция в меню выделяется светлым прямоугольником. Переход от одной опции к соседней возможен с помощью клавишей управления курсором (эти клавиши расположе- ны справа на клавиатуре и помечены стрелками вверх, вниз, впра- во и влево). - Подсвеченная опция будет выбрана, если Вы нажМете клави- шу "Ввод". Отказ от выбранного продолжения и возврат в меню верхнего уровня осуществляется с помощью клавиши ESC. 40 '
Рис. 2. Структура меню Турбо-Паскаля - При выборе опции-параметра появляется дополнительное ок- но, в котором указывается принятое по умолчанию или установлен- ное ранее значение соответствующего параметра. Если Вам нужно ввести новый параметр, начните ввод - и старое значение тут же исчезнет, сменившись вводимыми символами. Если же Вы хотите отредактировать параметр или добавить к существующему новый, воспользуйтесь клавишами перевода курсора влево или вправо, что- бы подвести курсор к нужному месту в строке, и введите новые символы в режиме вставки или замены (режимы переключаются клавишей INS). При редактировании Вам также доступны клавиши 41
"Забой” и DEL: первая из них стирает символ слева от курсора, вто- рая - символ, на который указывает курсор. После окончания ввода или редактирования параметра нажмите "Ввод". Если прервать ввод/редактирование параметра клавишей ESC, ранее существовав- шее значение параметра не изменится. - Для перехода к любой опции главного меню из режима ре- дактирования нажмите сочетание клавишей ALT-< буква >, где < буква >- начальная буква названия соответствующей опции. На- пример, ALT-F приведет к немедленному развертыванию дополни- тельного меню, связанного с опцией FILE; ALT-C - дополнительного меню опции COMPILE и т.д. Рис. 3. Структура меню Турбо-Паскаля (перевод) 42
- Перейти из режима редактирования к некоторым наиболее важным опциям дополнительного меню можно непосредственно, минуя главное меню. Для этого используются функциональные кла- виши или их сочетания со сдвиговыми клавишами SHIFT (времен- ная смена регистров), ALT (дополнительный регистр) и CTRL (уп- равляющий регистр). Например, нажатие F2 вызовет переход из ре- жима редактирования в режим записи редактируемого файла на диск, сочетание ALT-F5 заменит экран среды на экран программы и тд. Соответствующие функциональные клавиши и их сочетания со сдвиговыми указаны справа от опции в дополнительных меню. - Для перехода к любой опции дополнительного меню после его развертывания можно, как уже говорилось, использовать клави- ши управления курсором, но можно также просто нажать клавишу с буквой, соответствующей начальной букве нужной опции. Напри- мер, для выбора опции OS SHELL (временный выход в операцион- ную систему) достаточно нажать клавишу "О", для полного выхода из Турбо-Паскаля - клавишу "Q" и тд. Повторяю, такой переход воз- можен только после развертывания на экране дополнительного ме- ню с нужной опцией. - Главное меню всегда содержится на экране в самой верхней его строке. Это единственное меню с горизонтальным расположени- ем опций. Сразу после загрузки Турбо-Паскаля среда переходит к режиму редактирования/создания текста программы, о • чем свиде- тельствует наличие на экране мигающего курсора. Для перехода к режиму выбора опций из главного меню нужно нажать клавишу F10, после чего одна из опций выделяется цветом (оттенком), а курсор исчезает. Нажатие на ESC вернет среду в режим редактиро- вания. - При выборе опции EDIT среда возвращается в режим редак- тирования, при выборе любой из шести других опций на экране развертывается дополнительное меню. Опция FILE. При выборе этого продолжения Вам становятся до- ступны разнообразные операции с файлом текста программы: за- грузка его с диска в память редактора, запись цз редактора на диск, уничтожение редакторского файла, его переименование. Кроме того, здесь имеются опции смены каталога и временного или оконча- тельного выхода из Турбо-Паскаля. Опция EDIT. Возвращает среду в режим создания или редакти- рования текста программы. Опция RUN. Позволяет выполнить созданную Вами программу- осуществить ее прогон или приступить к отладке под управлением встроенного отладчика. Одна из опций возвращает экран в то со- стояние, которое он имел к моменту окончания прогона программы или очередного шага отладку (так называемое окно программы). Опция COMPILE. Позволяет компилировать программу без ее прогона; указать компилятору, куда нужно поместить готовую к ра- боте программу: на диск или в память; отыскать в тексте програм- мы оператор, при выполнении которого возникла ошибка; получить информацию о размере программы и используемых ею объемов стека, сегмента данных и динамической памяти. 43
Опция OPTIONS. При ее выборе Вы сможете управлять режима^ ми компиляции и компоновки программы, а также работой средь^ Турбо-Паскаля. Опция DEBUG. С ее помощью можно проверить и/или изме- нить текущее состояние любых переменных отлаживаемой програм- мы, просмотреть содержимое программного стека, найти нужную процедуру или функцию. Опция BREAK /WATCH. Управляет информацией, которая будет отображаться в отладочном окне. 3.1.1. Меню опции FILE Вид экрана с дополнительным меню, развернутым после вызова опции FILE, показан на рис. 4. Опция IX)AD. Загружает дисковый, файл с текстом программы в память редактора Турбо-Паскаля и таким образом делает его до- ступным для возможных изменений, а также прогона или отладки программы. Это - опция-параметр: при ее вызове на экране появля- ется небольшое окно с надписью LOAD FILE NAME (имя загружае- мого файла) и именем того файла, который был загружен послед- ний раз с помощью этой опции. При первом обращении к опции имя файла в этом окне отсутствует и его заменяет ключ группового выбора файлов *.PAS. Вы можете ввести обычным способом имя вновь загружаемого файла или отредактировать то имя, которое ука- зано в окне. Если текстовый файл с программой имеет стандартное расширение .PAS, это расширение можно не указывать/ File Edit Run Compile Options Debug Break/watch Load F3 Pick Alt-F3 New Save F2 Write to Directory Change dir OS shell Quit Alt-X .... w—wm Лахо ol 1 Insert Indent Unindent A:NONAME.PAS Watch Fl-Help F5-Zoom FS-Switch F7-Trace F8-Step F9-Make FlO-Menu Рис. 4. Меню опции FILE 44
В Турбо-Паскале реализована процедура выбора загружаемого файла из списка файлов. Если, например, Вы указали в качестве параметра "пустое" имя (состоящее из одних пробелов) или вос- пользовались предлагаемым средой ключом *.PAS, на экране по- явится еще одно окно, в котором в алфавитном порядке будут пе- речислены все файлы с расширением .PAS из текущего каталога. После этого Вы можете указать интересующий Вас файл: для этого переведите клавишами управления курсора светлый (цветной) пря- моугольник в этом окне так, чтобы он выделял нужный файл, и нажмите "Ввод" - файл будет загружен в память редактора. Выби- рать файлы из списка можно также по первым буквам их имен. Если, например, Вам нужно выбрать файл MYFILE.PAS, нажмите клавишу М, и прямоугольник выделит первый по алфавиту файл с именем, начинающимся на М, повторное нажатие на М выберет второй файл и тд. В конце списка указываются подкаталоги, заре- гистрированные в текущем каталоге, а также ссылка на каталог вер- хнего уровня, если текущий каталог не корневой. Вы можете подве- сти к ним указатель и, нажав "Ввод", перейти в соответствующий каталог. Отметим, что такой переход не вызывает смену текущего каталога, но позволяет получить доступ к файлам, зарегестрирован- ным в любом другом каталоге на текущем диске. Для формирования списка Вы можете использовать имена с лю- быми ключами группового указания файлов. Если к моменту за- грузки в памяти редактора уже. находился какой-либо текстовый файл и этот файл не был предварительно сохранен на диске, на экране появится окно с запросом ХХХ.ХХХ not saved. Save (Y/N) (Файл XXX.XXX не сохранен. Сохранить (Да/Нет)). Если Вы от- ветите нажатием на клавишу Y, то файл из памяти редактора будет предварительно сохранен на диске; если нажать на клавишу N, файл в памяти будет уничтожен без сохранения и на его место за- грузится дисковый файл. Нажатие на любую другую клавишу, кро- ме ESC, игнорируется, нажатие на ESC вызовет отказ от выбранной опции и возврат в дополнительное меню. Опцию можно вызвать из режима редактирования с помощью ключа F3. Опция-параметр PICK. Позволяет выбрать для загрузки в па- мять редактора один из нескольких файлов, которые уже обрабаты- вались ранее редактором Турбо-Паскаля. При выборе этой опции на экране разворачивается небольшое дополнительное окно. В нем перечисляются файлы, информацию о которых среда сохраняет в специальном файле-списке TURBO.PCK. Список файлов в окне от- крывает текущий файл, загруженный в редактор в настоящий мо- мент, за ним указываются другие файлы в порядке, обратном тому, в котором они загружались ранее в память редактора. Указателем- прямоугольником можно выбрать любой из этих файлов и после нажатия на "Ввод" загрузить его. В файле-списке сохраняется ин- формация о 8 последних файлах, причем среда "помнит" не только имена файлов, но и то состояние редактора, которое было к момен- 45
ту записи файла на диск. После загрузки файла с помощью данной опции это состояние редактора полностью восстанавливается. Файл-список TUPBO.PCK обычно сохраняется в текущем катало, ге, что позволяет по-разному настраивать среду Турбо-Паскаля в за. висимости от того, какой каталог был текущим к моменту обраще. ния к системе Турбо-Паскаль. Опцию FILE/PICK можно вызвать непосредственно из редактора с помощью клавишей ALT-F3. Функция NEW. Очищает память редактора и переводит его в ре- жим создания нового файла. Вновь создаваемому файлу присваива- ется имя NONAME.PAS, которое можно изменить при записи фай- ла на диск. Опция SAVE. Переписывает файл из памяти редактора на диск. Файл записывается под своим именем, однако, если к этому момен- ту имя файла было NONAME.PAS, среда запросит, хотите ли Вы его переименовать. Если на диске уже находится одноименный файл и опция OPTIONS/ENVIRONMENTS/BACKUP FILES установ- лена в состояние ON, дисковый файл будет предварительно пере- именован в файл с тем же именем и расширением .ВАК (страхо- вочная копия); если опция в состоянии OFF, одноименный диско- вый .файл будет уничтожен без создания страховочной копии. Опцию можно вызвать из режима редактирования с помощью ключа F2. Опция WRITE. ТО. Переименовывает редактируемый файл и за- писывает его на диск под новым именем. Опция DIRECTORY. Служит для выбора файла из текущего ка- талога и его загрузки в память редактора. После обращения к этой опции на экране появляется дополнительное окно с приглашением \ Enter File Name (введите имя файла) и предлагаемым по умолчанию ключом груп- пового выбора файлов *.* . Вы можете установить любое значение этого ключа или ввести имя файла, и после нажатия на "Ввод" на экране появится соответствующий список файлов из текущего ката- лога. Дальше Вы можете действовать точно так же, как в случае опции LOAD. Опция-параметр CHANGE DIR. Позволяет изменить установлен- ный по умолчанию диск и/или каталог. Опция-указатель OS SHELL. Позволяет временно выйти из Тур- бо-Паскаля без выгрузки его из памяти. После такого выхода Вы получаете доступ ко всем стандартным средствам дисковой операци- онной системы (ДОС) и можете, например, отформатировать диске- ту или напечатать какой-либо файл на принтере и вообще запу- стить любую другую программу, разумеется, с учетом того, что вре- менно покинутый Вами Турбо-Паскаль остается резидентным в па- мяти и занимает приблизительно 230 Кбайт. Для возврата в Турбо- Паскаль достаточно ввести команду EXIT, и экран тотчас же ока- жется в том состоянии, которое было перед вызовом этой опции. 46
Опция QUIT. Осуществляет выход из Турбо-Паскаля и выгрузку его из памяти. Эту опцию можно вызвать непосредственно из режи- ма редактирования с помощью комбинации ALT-X. 3.1.2. Меню опции RUN Вид экрана после выбора этой опции главного меню показан на рис. 5. File Edit Run Compile Options Debug Break/watch Line 1 C Run Ctrl-F9 Program reset Ctrl-F2 Go to cursor .F4 Trace into F7 Step over F8 User screen Alt-F5 Unindent A:NONAME.PAS Watch Fl-Help F5-Zoom F6-Switch F7-Trace F8-Step F9-Make Fl0-Menu Рис. 5. Меню опции RUN Опция RUN, Осуществляет компиляцию, компоновку и исполне- ние (прогон) Вашей программы из файла редактора. Компиляция проходит в режиме МАКЕ (см. ниже опцию COMPILE/МАКЕ). Ес- ли программа уже откомпилирована к этому моменту, то среда сра- зу запустит программу на счет. При работе программы экран переходит в режим воспроизведе- ния окна программы. После нормального завершения прогона про- граммы окно редактора восстанавливается в том виде, в каком оно было к моменту обращения к этой опции. Если на любом из эта- пов работы обнаружена ошибка, управление вновь передается редак- тору, но курсор позиционируется на ту строку, которая содержит ошибочный оператор, а в служебной строке редактора появляется краткая информация об ошибке. Если Вы захотите просмотреть ок- но программы, нажмите ALT-F5 или вызовете опцию RUN/USER SCREEN (см. ниже). Опцию можно вызвать из режима редактирования с помощью комбинации клавишей CTRL-F9. Опция PROGRAM RESET. Сбрасывает все ранее задействованные отладочные средства и прекращает отладку программы. Удаляет ис- полнявшуюся программу из памяти и закрывает все открытые в ней в этот момент файлы. у 47
Опцию можно вызвать из режима редактирования с помощью комбинации клавишей CTRL-F2. Опция GOTO CURSOR. Начинает или продолжает режим отлад- ки исполняемой программы под управлением встроенного отладчи- ка. Вначале осуществляются все действия по компиляции и компо- новке программы, затем программа начинает работать обычным об- разом (экран переходит в режим воспроизведения окна программы) и останавливается перед выполнением первого оператора из той строки, на которую указывает курсор. В этот момент экран возвра- щается в режим воспроизведения окна редактора, а строка с курсо- ром выделяется светлым прямоугольником. Вы можете перевести курсор к новой строке и вновь выбрать эту опцию - программа ос- тановится перед выполнением нового оператора и тд. В этом режиме Вам доступны все средства встроенного отлад- чика. Для прекращения отладки воспользуйтесь опцией RUN/ PROGRAM RESET или нажмите клавиши CTRL-F2. Опцию GOTO CURSOR можно вызвать из режима редактирова- ния с помощью клавиши F4. Опция TRACE INTO. Выполняет текущий оператор программы и останавливает программу перед выполнением следующего. Если к моменту обращения к этой опции режим отладки не был запущен, он запускается точно так, как если бы была вызвана опция GOTO CURSOR, однако программа останавливается перед первым испол- няемым оператором. Если текущий оператор содержит обращение к процедуре или функции, управление будет передано внутрь проце- дуры (функции) и программа остановится перед исполнением ее первого оператора. Опцию можно вызвать из режима редактирования с помощью клавиши F7. Опция STEP OVER. Работает, как и предыдущая опция, но с одним исключением: если текущий оператор содержит обращение к процедуре (функции), эта процедура (функция) будет исполнена обычным образом и программа остановится перед оператором, сле- дующим за обращением к ней. Иными словами, с помощью этой опции Вы можете "проскочить” вызов процедуры или функции и не прослеживать ее работу. Опцию можно вызвать из режима редактирования с помощью клавиши F8. Опция USER SCREEN. Переводит экран в режим воспроизведе- ния окна программы. После нажатия на любую клавишу экран вновь вернется в режим воспроизведения окна редактора. С по- мощью этой опции Вы всегда можете просмотреть состояние экра- на к моменту завершения прогона программы или очередного шага отладки. 48
Опцию можно вызвать из режима редактирования с помощью комбинации клавишей ALT-F5. 3.1.3. Меню опции COMPILE На рис. 6 показан вид экрана после развертывания дополнитель- ного меню опции COMPILE. File Edit Run Compile Options Debug Break/watch Line 1 Col 1 Compile Alt-F9 Make F9 Build Destination Memory Find error Primary file: Get info :NONAME.PAS Watch Fl-Help F5-Zoom F6-Switch F7-Trace F8-Step F9-Make FlO-Menu Рис. 6. Меню опции COMPILE Опция COMPILE. Компилирует только ту программу (или мо- дуль), которая загружена в данный, момент в память редактора. Ес- ли в этой программе (модуле) содержатся обращения к нестандарт- ным модулям пользователя, последние уже должны быть откомпи- лированы и храниться на диске в виде TPU-файлов. Опцию можно вызвать из режима редактирования с помощью комбинации клавишей ALT-F9. Опция МАКЕ. Создает программу, которая, возможно, содержит включаемые файлы и/или обращения к нестандартным модулям. Прежде всего компилируется начальный файл, если, разумеется, он определен опцией COMPILE/PRIMARY FILE (см. ниже). Если на- чальный файл не задан, осуществляется компиляция файла из па- мяти редактора. Если в процессе компиляции встретилось объявле- ние нестандартного модуля,, среда проверяет, были ли сделаны в соответствующем PAS-файле с текстом программы этого модуля ка- кие-либо изменения с момента последней его компиляции и по- лучения TPU-файла; если изменения были, TPU-файл создается рН°вь. Более того, если изменения коснулись интерфейсной части AS-файла, будут перекомпилированы также все другие объявлен- 49
ные в программе модули, содержащие обращения к измененному модулю. Однако, если PAS-файл с текстом измененного модуля не будет найден, система воспользуется существующим TPU-файлом без контроля его ’’свежести”. Опция существенно упрощает разработку многофайловых про- грамм, так как всегда компилируется тот минимум файлов, кото- рых коснулись сделанные в программе изменения. Опцию можно вызвать из режима редактирования с помощью клавиши F9. Опция BUILD. Подобна опции МАКЕ за одним исключением: для всех TPU-файлов отыскивается соответствующий PAS-файл и осуществляется его перекомпиляция независимо от того, были ли сделаны в нем изменения или нет. После компиляции в этом ре- жиме Вы можете быть уверены в том, что в полученной програм- ме учтены все изменения. Опция-переключатель DESTINATION. Управляет выходом ком- пилятора: если справа от нее стоит кодовое слово MEMORY, выход- ной файл компилятора будет сохранен в памяти и может затем сразу же запускаться из Турбо-Паскаля без его загрузки с диска; ес- ли справа стоит кодовое слово DISK, файл с программой будет со- хранен на диске в виде файла с расширением .EXE. Если объявлен начальной файл, его имя будет присвоено вновь создаваемому ЕХЕ-файлу, в противном случае ЕХЕ-файл получит имя файла из памяти редактора. Отметим, что независимо от значения этой опции TPU-файлы, создаваемые в режимах RUN/RUN, COMPILE/МАКЕ и COMPILE/BUILD, будут помещены на диск. Опция-параметр FIND ERROR. Позволяет локализовать ошибку периода исполнения программы. Если опция OPTIONS/COMPILER/DEBUG INFORMATION нахо- дится в состоянии ON и в таком же состоянии находится опция DEBUG/INTEGRATED DEBUGGING, то при возникновении ошибки в запущенной из Турбо-Паскаля программе ошибка локализуется автоматически: курсор будет помещен на строку, содержащую оши- бочный оператор, а в служебной (второй сверху) строке редактора появится информация о причине ошибки. Однако, если какая-либо из этих опций находится в состоянии OFF или если программа за- пущена не из Турбо-Паскаля, ошибка не будет локализована. В этом случае нужно записать или запомнить два шестнадцатирич- ных числа, которые появляются на экране в сообщении об ошибке и указывают адрес ошибочной ситуации (задаются в формате SSSS:OOOO, где SSSS - сегмент, а ОООО - смещение), затем загру- зить Турбо-Паскаль (если программа исполнялась вне среды) и вы- звать данную опцию. После того как Вы введете адрес ошибки, сре- да начнет поиск ошибочного оператора. Опция-параметр PRIMARY FILE. Указывает имя начального файла. Если это имя задано, то вне зависимости от того, какая часть программы загружена в данный момент в память редактора, ее компиляция в режимах RUN, МАКЕ и BUILD будет начинаться с этого файла. Чаще всего начальный файл содержит текст основ- ной части программы. В этом случае при загрузке в память вклю- 50
чаемого файла или файла-модуля компилятор сумеет правильно достроить программу. Если начальный файл не указан, то компи- ляция в режимах RUN, МАКЕ и BUILD возможна только в том случае, когда в память редактора загружена основная программа. Опция GET INFO. При обращении к ней на экране появляется окно, содержащее подробную информацию о программе (рис. 7). File Edit Run Compile Options Debug Break/watch ...................... Edit — Line 1 Col 1 Insert Indent Unindent C:NONAME.PAS 1 ....—.......... Information ..... ... ........... Current directory : C:\ Current file : C:\NONAME.PAS File size : 0 (Max: 64615) EMS usage ; OK Lines compiled: 0 Code is not available. Program exit code Code size 0 bytes Data size 0 bytes Stack size 0 bytes Minimum heap size 0 bytes Maximum heap size 0 bytes --- Available memory: 281K ----- Press any key Fl-Help F5-Zoom F6-Switch F7-Trace F8-Step F9-Make FlO-Menu Рис. 7. Вид экрана с информацией о программе Вот перевод этой информации о программе: Информация Текущий каталог Текущий файл Размер. файла Дополнительная память : С:\ : C:\NONAME.PAS : 0 (макс. 64615) >: 0 Кбайт Откомпилировано строк : О Код программы недоступен Выходной код программы Размер сегмента программы Размер сегмента данных Размер программного стека Минимальный размер кучи Максимальный размер кучи : 0 байт : 0 байт : 0 байт : 0 байт : 0 байт Доступная память : 281 Кбайт Нажмите любую клавишу 51
3.1.4. Меню опции OPTIONS Вид экрана с развернутым дополнительным меню опции OPTIONS показан на рис. 8. Опция-указатель COMPILER. При ее выборе разворачивается еще одно меню, с помощью которого можно управлять параметра- ми компилятора. Опции этого меню рассматриваются ниже. Опция LINKER. Параметры связанного с ней дополнительного меню управляют работой компоновщика и описаны на с.56. File Edit Run Compile Options Debug Break/watch 1 Line 1 Col 1 Insert Inde Compiler Linker Environment Directories Parameters Save options Retrieve options A:NONAME.PAS • и —. 4. U Fl-Help F5-Zoom F6-Switch F7-Trace F8-Step F9-Make FlO-Menu Рис. 8. Меню опции OPTIONS Опция ENVIRONMENT. С ней связано дополнительное меню, с помощью которого можно управлять средой Турбо-Паскаля (см. с.57). Опция DIRECTORIES. Ее меню задает используемые средой дис- ковые каталоги (см. с.59). Опция PARAMETERS. С ее помощью можно задать строку сим- волов, которая будет передана исполняемой программе в качестве i строки входных параметров. ‘ Опция SAVE OPTIONS. Позволяет сохранить всю настройку сре- ды (параметры компилятора, компоновщика и самой среды)* в спе- циальном файле конфигурации. При обращении к этой опции-пара- метру появляется окно, содержащее принятое по умолчанию имя файла TURBO.TP. Вы можете здесь указать любое другое имя или согласиться с предлагаемым вариантом. После нажатия на "Ввод” параметры настройки будут записаны на диск. Опция-параметр RETRIEVE OPTIONS. Позволяет прочитать из конфигурационного файла параметры настройки среды и установить их в качестве текущих параметров. Меню опции OPTIONS/COMPILER. На рис. 9 показан вид экра- ' на после обращения к этой опции. 52
Опция-переключатель RANGE CHECKING. Определяет режим ге- нерации кодов для проверки возможных выходов значений пере- менных за границы диапазона: если состояние ON, коды генериру- ются, если OFF - нет. Программа, откомпилированная с установлен- ной опцией ON, занимает несколько больший объем памяти и ис- полняется несколько медленнее, зато дает возможность контролиро- вать выход индексов за пределы, определенные в описании масси- вов. File Edit Run Compile Options Debug Break/watch Line 1 Col 1 Insert In Compiler |A:NONAME.PAS Range checking Off Stack checking Off I/O checking Off Force far calls On Overlays allowed Off Align data Word Var-string checking Strict Boolean evaluation Short Circuit Numeric processing 8087/80287 Emulation On Debug information On Local symbols On Conditional defines single Memory sizes Watch Fl-Help F5-Zoom F6-Switch F7-Trace F8-Step F9-Make FlO-Menu Рис. 9. Меню опции OPTIONS/COMPILER Опция STACK CHECKING. Аналогична RANGE CHECKING, но контролирует возможное переполнение программного стека. Опция I/O CHECKING. Используется для включения/отключе- ния генерации программных кодов, контролирующих правильность операций ввода-вывода. Опция FORCE FAR CALLS. В соответствии с архитектурой цент- рального процессора ПЭВМ могут использоваться две так называе- мые модели вызова процедур и функций: ближняя (NEAR) и даль- няя (FAR) (см., например, [2]). Ближняя модель обеспечивает адре- сацию в пределах текущего сегмента, дальняя используется для ор- ганизации межсегментных связей. Если опция установлена в состоя- ние ON, все вызовы процедур и функций будут использовать даль- нюю (межсегментную) модель, если OFF - ближнюю (внутрисегмент- ную) модель. Отметим, что при организации оверлея и при вызове из программы других программ с помощью процедуры ЕХЕС нуж- но использовать дальнюю модель. Опция OVERLAYS ALLOWED. В состоянии ON этой опции ком- пилятор генерирует дополнительный код при компиляции оверлей- ных модулей. Этот код позволяет передавать строки и множества в качестве фактических параметров при обращении из одного овер- лейного модуля в другой. 53
Опция-переключатель ALIGN DATA. Определяет способ выравни- вания переменных и констант в памяти: если в опции указан пара- метр BYTE, переменные и константы располагаются в памяти сплошной цепочкой, если WORD, каждая переменная и константа начинается в байте с четным адресом, т.е. выравнивается на начало слова. Параметр WORD увеличивает скорость выполнения про- грамм ценой несколько неэкономного расходования памяти. Опция-переключатель VAR-STRING CHECKING. Этот параметр позволяет отказаться от проверки на совпадение длины формально- го и фактического параметра-строки при обращении к процедуре или функции. Если установлено состояние переключателя STRICT, компилятор вставляет в программу команды для сравнения длины строк, если RELAXED - не вставляет. Опция-переключатель BOOLEAN EVALUATION. Имеет два возможных значения: SHORT CIRCUIT и COMPLETE. Если установлено значение SHORT CIRCUIT, то компилятор создаст программу, в которой все логические выражения будут вычисляться до тех пор, пока не станет ясен результат; если установлено значение COMPLETE, логические выражения вычисляются полностью, даже когда результат уже определен. Пусть, например, имеется такой фрагмент программы: Function MyFunc(x: integer) -.Boolean; begin x:=x+l; MyFunc:=x>10 end {MyFunc}; x:=0; if False and MyFunc(x) then x:=10; После его компиляции в режиме SHORT CIRCUIT исполнение этого фрагмента даст значение Х=0, так как обращение к функции MYFUNC не произойдет. Однако после компиляции в режиме COMPLETE такое обращение состоится, и X получит значение 1. Разумеется, и в. том и в другом случае не будет выполняться оператор Х: = 10. Опция-переключатель NUMERIC PROCESSING. Ориентирует компилятор на работу с числовым сопроцессором. При значении опции SOFTWARE все операции с вещественными данными реали- зуются программно и в программе допускается использовать только один вещественный тип REAL. Если опция установлена в состоя- ние 8087/80287, компилятор будет создавать код, содержащий обра- щения к числовому сопроцессору, причем программе становятся доступны также типы SINGLE, DOUBLE, EXTENDED и COMP (см. гл.5). Опция-переключатель EMULATION. Указывает компилятору, на- до ли создавать такой код программы, который будет одинаково пригоден при работе на ПЭВМ с сопроцессором или без него. Про- грамма сама определит наличие сопроцессора и, если он имеется, будет использовать все его возможности; если же сопроцессора нет, 54
его работа будет эмулироваться программно. В этом случае про- грамме становятся доступны все вещественные типы, однако испол- нение программ, содержащих типы SINGLE, DOUBLE, EXTENDED и COMP, на ПЭВМ без сопроцессора будет медленнее, чем когда применяется только тип REAL. Отметим, что такая -возможность появляется только в том случае, когда и опция EMULATION и оп- ция NUMERIC PROCESSING установлены в состояния соответствен- но ON и 8087/80287. Если любая из этих опций имеет другое значе- ние, описанная возможность не реализуется. Опция-указатель DEBUG INFORMATION. Устанавливает (значе- ние ON) или отменяет (значение OFF) режим генерации отладоч- ной информации в процессе компиляции программы. Отладочная информация представляет собой специальные таблицы, позволяю- щие установить однозначную связь между операторами исходного текста программы и теми кодами, которые порождает компилятор. Только после компиляции в режиме DEBUG INFORMATION ON становится возможной автоматическая локализация ошибки периода исполнения, а также пошаговая отладка программы. Опция LOCAL SYMBOLS. Аналогична опции DEBUG INFORMATION, но относится к именам локальных и глобальных переменных: если опция установлена в ON, среда получит возмож- ность доступа на этапе отладки к переменным по их именам. От- метим, что эта опция игнорируется, если DEBUG INFORMATION установлена в состояние OFF. Опция-параметр CONDITIONAL DEFINES. Определяет условия, которые можно использовать в операторах условной компиляции. Установить условие - это значит с помощью данной опции ввести некоторое слово, которое затем будет управлять компиляцией како- го-либо фрагмента программы. Если, например, использовать в ка- честве условия слово DEBUG, то можно написать такой фрагмент программы: {$IFDEF Debug} writein ('Отладка: х=', х); {$ENDIF} Теперь, если с помощью описываемой опции ввести параметр DEBUG, в программе будет откомпилирован оператор WRITELN, ес- ли этого параметра нет, компилятор пропустит этот оператор. Опция позволяет ввести несколько условий одновременно, при- чем условия должны отделяться друг от друга точкой с запятой. Допускается использование развернутой формы операторов условной компиляции, например: {$IFDEF Demonstration} code := Protection!; {$ELSE} code := Protections; a := 0; {$ENDIF} Операторы условной компиляции могут быть вложенными, что позволяет контролировать несколько условий одновременно, напри- мер: 55
{$IFDEF Vari} {$IFOEF Var2} a : = n; {$ELSE} a := 0; {$ENDIF} {$£ndif} Опция-параметр MEMORY SIZES. Определяет доступные про- грамме размеры памяти при обращении к стеку и к динамической памяти (куче). С опцией связаны три устанавливаемых параметра: STACK. SIZE - размер программного стека; по умолчанию равен 16384 байта, максимум - 65535 байт; LOW HEAP LIMIT - минимальный размер кучи; по умолчанию 0; HIGH HEAP LIMIT - максимальный размер кучи; по умолчанию 655360 байт; этот параметр не может быть меньше параметра LOW HEAP LIMIT. Для оценки необходимых программе объемов памяти следует учесть, что все локальные переменные при каждом обращении к процедуре (функции) размещаются в стеке, а при выходе из нее стек освобождается. Таким образом, требуемый размер стека опреде- ляется количеством вложенных вызовов процедур (функций) и сум- марным количеством их локальных переменных. Размер кучи оп- ределяется реальными потребностями программы в динамической памяти. Если установлен максимально возможный размер кучи 655360 байт, такая программа после -загрузки займет всю доступную оперативную память, что исключит возможность запуска из нее других программ. Меню опции OPTIONS/LINKER. Опции этого меню управляют работой компоновщика, который обеспечивает формирование связей между отдельными модулями и между программой в целом и биб- лиотекой TURBO.TPL. Опция-переключатель MAP FILE. Задает (состояние ON) или от- меняет (состояние OFF) режим формирования MAP-файла, т.е. тек- стового файла с расширением .МАР, в который компрновщик поме- щает карту распределения памяти компонуемой программы. Эта карта, а следовательно, и MAP-файл нужны в основном только при отладке программы с помощью внешнего отладчика. Опция LINK BUFFER. Указывает компоновщику, должен ли он использовать оперативную память (значение опции MEMORY) или пространство на диске (значение DISK) для размещения своих таб- лиц и временного хранения компонуемой программы. Если уста- новлено значение MEMORY, компоновщик будет работать значи- тельно быстрее, однако при разработке крупных программ ему мо- жет не хватить оперативной памяти и он не соберет программу. Вообще, следует помнить о том, что даже довольно большой объем оперативной памяти (640 Кбайт) может оказаться недостаточ- ным для разработки с помощью среды Турбо-Паскаля крупных программных проектов: ведь сам Турбо-Паскаль занимает в памяти 56
около 230 Кбайт. Если обнаружена нехватка памяти, среда дает со общение > Out of memory (не хватает памяти) и устанавливает курсор в конец программы. В этом случае следует прежде всего попытаться сэкономить память за счет установки описываемой опции в состояние DISK. Кроме того значительную экономию памяти (до 200 Кбайт) может дать отказ от услуг широко распространенной среди отечественных пользователей ПЭВМ диалоговой оболочки Norton Commander [6]. Отметим, спра- ведливости ради, что во 2-й версии этой оболочки появилась весьма ценная утилита NCSMALL.EXE, позволяющая выгружать из опера- тивной памяти громоздкий файл NC.EXE на период работы Турбо- Паскаля, поэтому, если оболочка Norton Commander запущена вызо- вом утилиты NCSMALL.EXE, отказ от оболочки практически не даст никакой экономии. Наконец, может оказаться необходимым от- каз от услуг самой среды Турбо-Паскаля на этапе прогона програм- мы. Для этого нужно установить опцию COMPILE/DESTINATION в состояние DISK, создать программу с помощью опций МАКЕ или BUILD, выйти из среды и запустить программу. Ясно, что в этом случае Вы лишаетесь возможности отлаживать Вашу программу средствами встроенного отладчика. В некоторых случаях за счет оверлейной структуры программы (см. гл.11) ее размеры удается уменьшить настолько, что даже крупная программа помещается в памяти вместе со средой; Если, несмотря на все меры экономии, памяти все-таки не хватает, можно полностью отказаться от услуг среды и использовать автономный компилятор-компоновщик ТРС.ЕХЕ. Меню опции OPTIONS/ENVIRONMENT. Дополнительное меню этой опции показано на рис. 10. Опция CONFIG AUTO SAVE. С ее помощью Вы можете задать режим автоматического сохранения текущей настройки среды Тур- бо-Паскаля в файле конфигурации TURBO.TP. Настройка среды бу- дет сохранена автоматически при временном или окончательном выходе из Турбо-Паскаля (опции FILE/OS SHELL или RUN/QUIT), если эта- опция установлена в ON и если с момента последней за- писи в этот файл настройка среды изменилась. Автоматическое со- хранение настройки несколько затягивает переход к прогону/отладке программы за счет дополнительного обращения к диску, однако даст Вам возможность при очередной загрузке Турбо-Паскаля пол- ностью восстановить состояние среды. Опция EDIT AUTO SAVE. Позволяет автоматически сохранить на диске копию файла из памяти редактора перед выходом из сре- ды или перед передачей управления в программу в режиме прого- на/отладки. Чрезвычайно полезная опция, которую я настоятельно рекомендую всегда устанавливать в состояние ON - автосохранение файла избавит Вас от многих неприятностей при работе с плохо 57
File Edit Run Compile Options Debug Break/watch Line 1 Col 1 Insert Inde Compiler Linker Environment A:NONANE.PAS Config autyO save On Edit auto save On Backup files Off Tab size 8 Zoom windows Off Screen size Watch Fl-Help F5-Zoom F6-Switeh F7-Trace F8-Step F9-Make PIO-Menu Рис. 10. Меню опции OPTIONS/ENVIRONMENT отлаженной программой или на ПЭВМ с ненадежной (сбойной) па- мятью. Состояние ON заставит, среду автоматически сохранять на диске копию редакторского файла, если в нем были сделаны какие- либо изменения с момента последней записи на диск. Разумеется, дополнительное обращение к диску затягивает переход к прого- ну/отладке, однако Вы по достоинству оцените эту услугу среды по- сле первого же "зависания" программы. Опция BACKUP FILES. В состоянии ON этой опции исходный дисковый файл с текстом программы будет автоматически переиме- нован в ВАК-файл перед записью текущего редакторского файла на диск. Таким образом, Вы всегда будете иметь в своем распоряже- нии предыдущую версию отлаживаемой программы в страховочном ВАК-файле. Состояние ON опции затягивает переход к прогону/от- ладке значительно больше, чем просто запись на диск, так как сре- да автоматически уничтожает старую страховочную копию перед со- зданием новой. Кроме того, при разработке многофайловых про- грамм страховочные копии могут использовать значительный объем диска. Если Вы достаточно аккуратно и внимательно работаете со средой, необходимость в активном состоянии этой опции, как пра- вило, не возникает. Опция-параметр TAB SIZE. С ее помощью можно установить длйну шага табуляции для клавиши TAB (обычно эта клавиша рас- полагается слева вверху в зоне алфавитно-цифровых клавишей и помечается двумя стрелками, направленными в противоположные стороны) при работе в режиме редактора. Шаг табуляции может быть в пределах от 2 до 16 знакомест строки, по умолчанию ис- пользуется шаг 8. Если табуляция отключена клавишами CTRL-O Т, то при нажатии на TAB курсор скачком смещается в текущей строке так, чтобы остановиться на начале очередного слова в пре- 58
дыдущей строке. Если при этом включен режим вставки INSERT, часть текущей строки справа от курсора будет соответствующим об- разом сдвинута вправо, в противном случае курсор будет позицио-. нироваться без изменения строки. Если режим табуляции включен (об этом свидетельствует слово TAB в служебной строке редактора, см. с.67), курсор смещается в соответствии с шагом, установленным этой опцией. Пропускаемые при табуляции позиции либо заполня- ются кодом пробела (десятичный код 32), либо в текущую позицию ставится знак табуляции (код 9), после чего остаток строки смеща- ется вправо к ближайшей позиции, кратной шагу табуляции, плюс единица. Заполнение пропускаемых позиций зависит от опции FILL: если в служебной строке присутствует слово FILL, вставляется символ табуляции, иначе - пробелы. Опция FILL переключается клавишами CTRL-O F. Опция-переключатель ZOOM WINDOWS. Указывает среде, надо ли расширять отладочное окно или окно редактора на весь экран. Если опция установлена в состояние ON, на экране может отобра- жаться только одно из окон - редактора или отладочное. Состояние OFF позволяет совмещать окно редактора с отладочным окном. До- ступ к этой опции возможен непосредственно из режима редактиро- вания с помощью клавиши F5. Опция-параметр SCREEN SIZE. С ее помощью можно задавать , количество строк экрана. По умолчанию среда использует стандарт- I ную высоту^ экрана в 25 строк. Если Баша ПЭВМ оснащена монито- ром с адаптерами EGA или VGA (см. гл.12), Вы можете установить высоту соответственно в 43 или 50 строк. Меню опции OPTIONS/DIRECTORIES. Вид экрана с разверну- тым меню этой опции показан на рис. 11. Опция TURBO DIRECTORIES. Задает каталог, в котором Турбо- Паскаль будет искать конфигурационный файл TURBO.TP и файл справочной службы TURBO.HLP в том случае, если при загрузке Турбо-Паскаля они не будут найдены в текущем каталоге. Обычно здесь указывается каталог, содержащий саму систему Турбо-Паскаль. ; Опция EXE & TPU DIRECTORIES. Указывает тот каталог, в ко- торый будут помещаться готовые к работе программы в виде ЕХЕ- файлов и результат компиляции модулей в виде TPU-файлов. Если каталог не указан, эти файлы будут помещаться в текущий ката- лог-именно такое состояние этой опции соответствует стандартной настройке среды. Не рекомендуется устанавливать в этой опции ка- талог, содержащий файлы системы Турбо-Паскаль. Опция INCLUDE DIRECTORIES. Здесь Вы можете перечислить I те каталоги, в которых Турбо-Паскаль будет искать включаемые । файлы, т.е. файлы, задаваемые директивой компилятору {$1 <имя I файла >} (см. с.66). Использование включаемых файлов делает про- ' грамму более компактной. При указании нескольких каталогов они I. перечисляются через точку с запятой. Отметим, что поиск в этих ’ каталогах идет только в том случае, если включаемый файл не | найден в текущем каталоге. 1 Опция UNIT DIRECTORIES. Задает каталоги, в которых среда I ищет TPU-файлы, если они не обнаружены в текущем каталоге. В 59
этой опции обычно указывается каталог, содержащий файл GRAPH.TPU, если в Вашей программе используются графические средства Турбо-Паскаля, а также файл GRAPH3.TPU, если Вы соби- раетесь работать с черепаховой графикой (см. гл. 13). При перечис- лении .нескольких каталогов они разделяются точкой с запятой. Опция OBJECT DIRECTORIES. Если в своей программе Вы ис- пользуете внешние процедуры и функции (см. гл. 11), они должны быть представлены в виде OBJ-файлов. Описываемая опция задает один или несколько каталогов, в которых Турбо-Паскаль будет ис- кать эти файлы, если их нет в текущем каталоге (см. также с.бб). Опция PICK FILE NAME. Позволяет записать в Ваш конфигура- ционный файл TURBO.TP имя файла-списка, если это не стандарт- ное для Турбо-Паскаля имя TURBO.PCK. Файл-список хранит име- на до восьми файлов, которые обрабатывались ранее в редакторе Турбо-Паскаля. Доступ к файлу-списку с помощью опции FILE/PICK или с помощью клавишей ALT-F3 значительно облегча- ет разработку многофайловых npoipaMM. Опция CURRENT PICK FILE. Сохраняет информацию о теку- щем файле в файле-списке с заданным именем. Поскольку в этом случае конфигурационный файл TURBO.TP остается неизменным, при очередной загрузке Турбо-Паскаля в этой опции будет установ- лено имя файла-списка из TURBO.TP. File Edit Run Coapile Options Debug Break/watch Line 1 Col 1 Insert Inde* Coapiler Linker Environment Directories A:NONAME.PAS Turbo directory: EXE & TPU directory: Include directories: Unit directories: Object directories: Pick file naae: TURBO.PCK Current pick file: Watcb Fl-Help F5-Zoo« F6-Switch F7-Trace F8-Step F9-Make FlO-Menu Рис. 11. Меню опции OPTIONS/DIRECTORIES 3.1.5. Меню опции DEBUG На рис. 12 показан вид экрана для опции DEBUG. Опция-параметр EVALUATE. Дает Вам возможность в процессе отладки просмотреть содержимое любой переменной или найти значение любого выражения. При необходимости Вы можете с ее 60
Fl-Help F5-Zoom F6-Switch F7-Trace F8-Step F9-Make Fl0-Menu Рис. 12. Меню опции DEBUG помощью установить новое значение любой переменной. При обра- щении к ней на экране разворачивается дополнительное окно (рис.13), содержащее три поля: EVALUATE (вычисление), RESULT (результат) и NEW VALUE (новое значение). В первом поле Вы можете написать имя любой переменной или некоторое выражение. Сразу после того как Вы нажмете "Ввод", в поле RESULT появится соответствующее значение или сообщение UNKNOW IDENTIFIER .(неопределенный идентификатор). Если Вы запросили значение пе- ременной, Вы можете перевести курсор в нижнее поле и устано- вить новое значение переменной. Для возврата в режим отладки используйте клавишу ESC. File Edit Run Compile Options Debug Break/watch ................ Edit -|----------------------------- Line 1 Col 1 Insert Indent Evaluate Ctrl-F4 Call stack Ctrl-F3 Find procedure Integrated debugging On Standalone debugging Off Display swapping Smart Refresh display Watch Fl-Help F5-Zoom F6-Switoh P7-Tracc F8-8tep F9-Make FlO-Menu Рис. 13. Вид экрана в режиме DEBUG/EVALUATE 61
Отметим, что эта опция может использоваться как несложный калькулятор. Опцию можно вызвать непосредственно из режима ре- дактирования клавишами CTRL-F4. При обращении к опции из режима редактирования (с по- мощью CTRL-F4) среда анализирует ближайшее окружение курсора и, если это возможно, выделяет идентификатор или константу, на которую перед обращением к опции указывал курсор в окне редак- тора. Выделенный идентификатор (константа) автоматически пере- носится в поле EVALUATE и предлагается в виде вычисляемого выражения. Таким образом, если перед' обращением к опции Вы установили курсор на интересующий Вас идентификатор, Вам оста- нется лишь нажать "Ввод", чтобы тут же получить его значение в поле RESULT. Если предлагаемый идентификатор Вас не устраи- вает, Вы можете его отредактировать или ввести новый. Для ввода нового нажмите на любую алфавитно-цифровую клавишу, и предла- гаемый в окне исчезнет, заменившись вновь введенным символом. Для перехода к редактированю сразу после появления окна нажми- те НОМЕ или END, затем переведите курсор к нужному символу идентификатора и отредактируйте его так, как если бы он нахо- дился в окне редактора, - используя клавиши DEL, INS, "Забой". На- конец, если сразу после вызова опции командой CTRL-F4 нажать клавишу перевода курсора вправо, появившийся в окне EVALUATE идентификатор (константа) дополнится символом, распологающимся справа от него в тексте программы. Теперь каждое новое нажатие на клавишу перевода курсора вправо будет приводить к ко- пированию очередного символа из текста программы в поле EVALUATE. Описанная возможность существенно облегчает ввод длинных выражений и составных идентификаторов. Кстати, замечу, что при отладке программ, использующих запи- си (см. 5.2.2.), действие оператора WITH не распространяется на идентификаторы и выражения в окне EVALUATE, поэтому для дос- тупа к полям записи Вам понадобятся составные имена. Опция CALL STACK. При каждом обращении к процедуре или функции все фактические параметры размещаются в программном стеке. С помощью этой опции Вы можете увидеть значения этих параметров после вызова процедуры (функции). Опцию можно вызвать непосредственно из режима редактирова- ния клавишами CTRL-F3. Опция-параметр FIND PROCEDURE. С ее помощью Вы можете отыскать в Вашей программе нужную процедуру или функцию. Оп- ция может оказаться полезной при разработке сложных многофай- ловых программ. Опция INTEGRATED DEBUGGING. Если она установлена в со- стояние ON,-к программе будет добавлена информация, необходи- мая для работы встроенного отладчика. Только в этом состоянии опции Вы можете использовать контрольные точки и пошаговую отладку. Опция STANDALONE DEBUGGING. В значении ON к ЕХЕ-фай- лу программы будут добавлены соответствующие таблицы, которые 62
позволят вести отладку программы вне среды Турбо-Паскаля с по- мощью внешнего отладчика TD.EXE. Отладчик TD.EXE (Turbo Debugger) не входит в комплект поставки Турбо-Паскаля. Опция-указатель DISPLAY SWAPPING. Сообщает среде, в каких случаях следует переключать экран с воспроизведения окна редакто- ра на окно программы. В режиме SMART среда будет переключать экран по мере надобности - только если в очередном операторе программы было обращение к экрану для вывода или к клавиатуре для ввода. Переключение на окно программы будет также и тогда, когда отладчик "перескакивает" через вызов процедуры (функции) по клавише F8, но в этой процедуре (функций) есть обращение к экрану. Если установлен режим ALWAYS, переключение будет про- исходить перед исполнением любого оператора программы. Нако- нец, в режиме NONE среда никогда не переключает экран, даже ес- ли он требуется для вывода данных, т.е. вывод программы может накладываться на текст программы. Опция-указатель REFRESH DISPLAY. Немедленно восстанавли- вает содержимое экрана, если в процессе отладки он "испортился" в результате прогона с опцией DISPLAY/SWAPPING в состоянии NONE (см. выше). 3.1.6. Меню опции BREAK/WATCH Вид экрана с развернутым дополнительным меню опции BREAK/WATCH показан на рис. 14. Опция-параметр ADD WATCH. С бе помощью Вы можете ука- зать отладчику те переменные и/или выражения, за изменением значений которых Вы хотели бы наблюдать при отладке програм- мы. Указанные переменные и выражения вместе с их текущими значениями будут постоянно, содержаться в окне отладчика, доступ к которому возможен с помощью клавишей F5 и F6. По мере до- бавления переменных (выражений) окно отладчика увеличивается до максимальных размеров, установленных с помощью программы настройки среды TINST.EXE. Если Вы перейдете в отладочное окно (клавиша Р6),Вы сможете перемещаться в нем, вызывая при необ- ходимости "прокрутку" содержимого этого окна. Таким образом мож- но наблюдать за произвольным количеством переменных и выраже- ний. Опцию можно вызвать непосредственно из редактора клавишами CTRL-F7. При этом справедливо все сказанное выше относительно вызова CTRL-F4 опции EVALUATE, т.е. выделение идентификатора или константы, его редактирование и дополнение. Опция DELETE WATCH. Удаляет переменную или выражение из окна отладчика. Для использования этой опции необходимо, что- бы среда находилась в режиме воспроизведения окна отладчика и это окно было активным (в нем должен быть виден светлый пря- моугольник-указатель). Опция стирает ту переменную (выражение), на которую показывает в данный момент указатель. Если отладоч- ное окно совмещено с окном редактора, но не является активным, 63
File Edit Run Compile Options Debug Break/watch Fl-Help F5-Zoo* F6-Switch F7-Trace F8-Step F9-Make Fl0-Menu Рис.14. Меню опции BREAK/WATCH из него будет удалена та переменная (выражение), слева от которо- го стоит яркая точка. Если отладочного окна на экране вообще нет, опция игнорируется. Опция EDIT WATCH. Позволяет отредактировать переменную (выражение), на которую в данный момент указывает указатель или слева от которой стоит яркая точка. Опция действует даже в том случае, когда на экране, не воспроизводится окно отладчика. Опция REMOVE ALL WATCHES. Удаляет все переменные и вы- ражения из окна отладчика. Опция TOGGLE BREAKPOINT. Устанавливает в текущей строке j контрольную точку или снимаем ее. Текущая строка - это строка с ] курсором в окне редактора. Если для нее установлена контрольная точка, строка выделяется цветом (яркостью). Если строка уже была выделена, т.е. если в ней уже была установлена контрольная точка, । использование данной опции удалит ее и строка приобретет нор- мальный вид. В программе можно установить произвольное количе- ство контрольных точек. После любого запуска программы с уста- ; новленными контрольными точками (точкой) отладчик прекратит исполнение программы перед выполнением того оператора, кото- | рый содержится в первой (по логике программы) контрольной точ- j ке. Если контрольная точка задана для строки, не содержащей ис- полняемый оператор, программа остановится перед первым после * этой строки исполняемым оператором. 64
С помощью клавишей CTRL-F8 контрольную точку можно уста- новить/снять непосредственно из режима редактирования. Опция CLEAR ALL BREAKPOINTS. Удаляет все ранее установ- ленные контрольные точки. Опция VIEW NEXT BREAKPOINT. Переводит курсор к следую- щей контрольной точке, но программа при этом не исполняется. После такого перехода обычно используется опция RUN/GOTO CURSOR (клавиша F4), чтобы выполнить фрагмент программы до новой контрольной точки. Если ниже курсора по тексту программы нет других контрольных точек, опция вернет курсор к первой свер- ху точке. 3.2. ДИРЕКТИВЫ КОМПИЛЯТОРА В меню OPTIONS/COMPILER включены 14 опций, с помощью которых можно управлять работой компилятора. В ряде случаев бывает необходимо временно отменить действие той или иной оп- ции при трансляции некоторого фрагмента программы. Особенно часто такая необходимость возникает при обращении к диску:, если программа пытается прочитать несуществующий файл или записать данные на защищенный диск, возникнет ошибка периода исполне- ния и программа аварийно закончит свою работу. В то же время, если отключить опцию I/O CHECKING, этого не произойдет, про- грамма сможет проанализировать последствия обращения к диску и предпринять альтернативные действия. В Турбо-Паскале можно использовать так называемые директивы компилятора, которые вставляются непосредственно в текст про- граммы и модифицируют те или иные опции компилятора. Все директивы оформляются в виде особых комментариев: они обрам- ляются фигурными скобками, за бткрывающей скобкой должны без пробелов следовать знак доллара (десятичный код 36), начальная буква опции из меню OPTIONS/COMPILER и знак "+" или Знак ”+” означает установку опции в значение ON, знак - OFF. Напри- мер, директива {$!-} означает временное отключение контроля оши- бок ввода-вывода, директива {$R + } - включение контроля границ диапазона. В одной директиве можно перечислить несколько опций, например: {$A+,B-,D+,E+,F+,1+,L+,N+,0-,R+,S+,V+} Ниже приводится список всех директив компилятора. В скобках дается значение опции для знака {$А+} - выравнивать данные на границу слова (байта); {$В + } - вычислять логические выражения полностью (до получе- ния результата); 3 - Фаронов 65
{$D+} - разрешить (не разрешить) работу со встроенным отлад- чиком; {$Е+} - включить (отключить) режим программной эмуляции сопроцессора; {$F+} - использовать дальнюю (ближнюю) модель вызова; {$! + }- включить (отключить) контроль операций ввода/вывода; {$L+} - включить (не включать) локальные символы в информа- цию для отладчика; {$N+} - использовать числовой сопроцессор (реализовать опера- ции с плавающей точкой программно); {$0 +} - поддерживать (не поддерживать) создание оверлейной программы (см. с.188); {$R + } - включить (отключить) контроль границ диапазона; {$S + } - включить (отключить) контроль возможного переполне- ния стека; {$V+} - включить (отключить) контроль длины строк при обра- щении к процедуре или функции. < Две директивы устанавливаются особым образом. Первая из них {SDEFINE < условие >}-установить условие. Условие должно отделяться от слова DEFINE хотя бы одним пробелом, за условием не должно следовать никаких символов, кро- ме закрывающей фигурной скобки, например: {$DEFINE debug}. Вторая директива {$М <стек>,<н.г.>,<в.г.>}-установить тре- буемые размеры памяти. Здесь <стек> - размер стека; < н.г. > - нижняя граница динамической памяти; <Тз.г.> - верхняя граница динамической памяти^ Все размеры задаются в байтах, располагаются друг за другом в указанной последовательности и отделяются запятыми. Между бук- вой М и первой цифрой размера стека должен быть хотя бы один пробел, между последней цифрой верхней границы динамической памяти и закрывающей фигурной скобкой не должно быть никаких символов, например: {$М 16384,0,655360}. Кроме того, допускается использование приведенных ниже ди- ректив, обеспечивающих подключение к программе других файлов. {$1 <имя файла >}-включение файла. Здесь < имя файла > - имя включаемого файла. Включаемый файл должен содержать исходный текст фрагмента программы, обычно - процедуры или функции. Компилятор исполь- зует эту директиву как указание обратиться к дисковому файлу за очередной строкой программы. После того как весь фрагмент про- граммы будет прочитан, компилятор продолжит чтение строк из файла редактора. Если в имени файла опущено расширение, ис- пользуется стандартное расширение .PAS, диск и каталог, в котором находится файл, . задаются опцией OPTIONS/ENVIRONMENT/ DIRECTORIES. Директива {$L <имя файла >}-включение OBJ-файла Используется для указания компилятору имени файла, в кото- ром содержится результат трансляции ассемблерной процедуры или 66
функции, объявленной в программе внешней (EXTERNAL, см. гл.11). -Если в имени файла опущено расширение, используется стандартное расширение .OBJ; диск и каталог, в котором находится файл, задается опцией OPTIONS/ENVIRONMENT/DIRECTORIES. 3.3. РЕДАКТОР Основные приемы работы с редактором описаны в гл. 1. Ниже приводится полное описание служебной строки и команд редактора. 3.3.1. Служебная строка В верхней части окнА редактора (во второй сверху строке экра- на) содержится служебная строка, в которой отражается текущее со- стояние редактора. Рассмотрим смысл отдельных,сообщений в этой строке. LINE NNN. Здесь NNN - номер строки of начала файла, на кото- рой в данный момент находится курсор. COL NNN. Это номер позиции, которую занимает курсор в стро- ч ке. INSERT. Наличие этого слова свидетельствует о том, что редак- тор работает в режиме вставки символов. Если слово отсутствует, реализуется режим замены символов. Режимы переключаются с по- мощью клавиши INS. INDENT. Определяет режим автоотступа. Если слово отсутствует, то при нажатии на "Ввод” курсор переходит к началу следующей строки; если это слово имеется, то, кроме того, курсор смещается относительно начала строки так, чтобы занять ту же позицию, с ко- торой начиналась предыдущая строка. Переключение режима авто- отступа осуществляется клавишами, CTRL-O I. TAB. Указывает на возможность использования клавиши табуля- ции для позиционирования в строке на позиции, определяемые шагом табуляции (см. опцию OPTIONS/ENVIRONMENT/TAB SIZE). Режим переключается клавишами CTRL-0 Т. FILL. Указывает способ заполнения пустых позиций при табуля- ции: если слово FILL отсутствует, пустые позиции заполняются пробелами, в противном случае используется код горизонтальной табуляции. Опция переключается клавишами CTRL-O F. UNINDENT. Свидетельствует о том, что при нажатии на клави- шу "Забой" осуществляется операция, обратная автоотступу: если слева от курсора нет никаких значащих символов в строке, вся строка сместится влево так, ' чтобы курсор . оказался в позиции предыдущего автоотступа. Этот режим устанавливается/отменяется клавишами CTRL-O U. Символ * (звездочка) появляется перед именем файла в том случае, когда в редактируемый файл внесены изменения после по- следней его записи на диск. з- 67
3.3.2. Команды редактора В распоряжении пользователя имеются следующие группы ко- манд редактора: - перемещения курсора; - вставки/удаления; - работы с блоками; - прочие. При их описании используются такие обозначения клавишей уп- равления курсором: ВЛ - курсор влево; ВП - курсор вправо; ВВ-курсор вверх; ВН - курсор вниз. Команды перемещения курсора CTRL-S4 или ВЛ - на символ влево; CTRL-D или ВП - на символ вправо; CTRL-A или CTRL-ВЛ - на слово влево; CTRL-F или CTRL-ВП - на слово вправо; CTRL-E или ВВ - на строку вверх; CTRL-Х или ВН - на строку вниз; CTRL-W - "прокрутка” вниз на строку вместе с курсором; CTRL-Z - "прокрутка" вверх вместе с курсором; CTRL-R или PGUP - на страницу вверх; CTRL-C или PGDN - на страницу вниз; CTRL-Q S или НОМЕ - в начало строки; CTRL-Q D или END - в конец строки; CTRL-Q Е или CTRL-HOME - в начало экрана; CTRL-Q X или CTRL-END - в конец экрана; CTRL-Q R или CTRL-PGUP - в начало файла; CTRL-Q С или CTRL-PGDN - в конец файла; CTRL-Q В-в начало блока; CTRL-Q К - в конец блока; CTRL-Q Р-на последнюю позицию (используется после поиска или поиска/замены); CTRL-Q W - на последнюю ошибку. Команды удаления/вставки CTRL-V или INS - включить/отключить режим вставки; CTRL-N - вставить строку; CTRL-Y - удалить строку; CTRL-H или "Забой" - стереть символ слева от курсора; CTRL-G или DEL - стереть символ над курсором; CTRL-T - стереть слово справа от курсора; * CTRL-Q Y - стереть часть строки справа от курсора. 68
Команды работы с блоком CTRL-K В - пометить начало блока; CTRL-K К - пометить конец блока; CTRL-K Т-пометить в качестве блока слово слева от курсора; CTRL-K Р - напечатать блок; CTRL-K С - копировать блок начиная с позиции курсора; CTRL-K V - переместить блок; CTRL-K Н - убрать выделение блока цветом; повторное использо- вание CTRL-K Н вновь выделит блок; CTRL-K Y - удалить блок; CTRL-K R-читать блок из дискового файла; CTRL-K W - записать блок на диск; CTRL-K I-сместить блок вправо; CTRL-K U-сместить блок влево. Прочие команды CTRL-Q F - искать по образцу (см. ниже); CTRL-L - продолжить поиск; CTRL-Q А - искать по образцу и заменять (см. ниже); CTRL-U - прекратить дальнейшее выполнение поиска или по- иска и замены; CTRL-K п — установить маркер; п=О...З (см. ниже); CTRL-Q п - искать маркер; CTRL-Q W-искать ошибку; CTRL-Q [ - искать правую парную скобку (см. ниже); CTRL-Q ] - искать левую парную скобку (см. ниже); CTRL-Q L - восстановить испорченную строку (см. ниже); CTRL-Q Т или CTRE-0 Т - включить/отключить табуляцию; CTRL-O F - переключить заполнение табуляции; CTRL-O I или CTRL-Q I - включить/отключить автоотступ; CTRL-O О - вставить настройку компилятора в начало файла (см. ниже); F1 - получить справку (см. ниже); CTRL-F1 - получить контекстную справку (см. ниже); ALT-F1 -получить последнюю справку; F2 или CTRL-K S - записать файл на диск; F3 - читать файл с диска; F4 - выполнить опцию RUN/GOTO CURSOR; F5 - включить/отключить совмещение окон редактора и отлад- чика; F6 - перейти в окно отладчика или выйти из него; F7 - выполнить опцию RUN/TRACE INTO; F8 - выполнить опцию RUN/STEP OVER; F9 - выполнить опцию COMPILE/MAKE; F10 - перейти в главное меню; ALT-Х -выйти из Турбо-Паскаля; ALT-F3 - выбрать для чтения файл из файла-списка; 69
ALT-F5 - показать окно программы; ALT-F9 - выполнить опцию COMPILE/COMPILE; CTRL-F2 - отключить режим отладки (RUN/PROGRAM RESET); , CTRL-F3 - показать стек (BREAK/WATCH/CALL STACK); CTRL-F4-выполнить опцию BREAK/WATCH/EVALUATE; CTRL-F7- выполнить опцию BREAK/WATCH/ADD WATCH; CTRL-F8 - выполнить опцию BREAK/WATCH/TOGGLE BREAK- POINT; CTRL-F9 - выполнить компиляцию и прогон программы. Назначение большинства команд, думаю, понятно без каких-либо комментариев, но некоторые нуждаются в пояснении. CTRL-Q F. Начинает поиск нужной последовательности симво- лов в тексте программы. При обращении к команде в служебной строке появляются запрос FIND и та строка, которая отыскивалась в последний раз. Вы можете ввести новый образец для поиска или согласиться с предложенным. После нажатия на "Ввод" в служебной строке появляется запрос OPTIONS. В этот момент Вы можете вве- сти строку из одного или более параметров, уточняющих процедуру поиска. Допускаются следующие параметры: В - вести поиск от текущего положения курсора к началу файла; если этот параметр не задан, поиск будет вестись к концу файла; G - вести поиск от начала файла; если параметр не задан, поиск начнется от текущего положения курсора; U - игнорировать различий в высоте букв; W - вести поиск целыми словами; п - найти n-е вхождение образца поиска в файл. В строке можно использовать любые комбинации параметров, которые в этом случае перечисляются через запятую. CTRL-Q А. Обеспечивает поиск образца в тексте программы с последующей заменой его на новую строку. При обращении к ко- манде вначале появляется запрос FIND, и в этот момент нужно указать образец для поиска, затем запрос REPLASE WITH-здесь нужно ввести образец для замены. После этого запрашиваются уточняющие параметры. В дополнение к уже перечисленным мож- но использовать параметр N - заменять без запроса подтверждения. Если этот параметр не указан, то после успешного поиска в служеб- ной строке появится запрос на подтверждение замены REPLASE (Y/N) и курсор покажет найденное место. В ответ Вы должны на- жать клавишу Y, если Вы подтверждаете замену, и N - если нет. CTRL-K п. Устанавливает в текущую позицию курсора маркер с номером п=О...З. Маркер на экране невидим и никак не влияет на исполнение программы. Команда используется совместно с коман- дой CTRL-Q п (искать маркер с номером п) для ускорения поиска нужных фрагментов текста при разработке крупных программ. Об- ратите внимание, что комбинации клавишей CTRL-K п набираются следующим образом: вначале нажимается CTRL, затем, не отпуская 70
ее, К; после этого все клавиши отпускаются и нажимается клавиша с цифрой п. CTRL-Q ] и CTRL-Q [. Используются для поиска ближайшей парной скобки. Команды позволяют отыскивать пары скобок ( и ), { и }, [ и ]. Подведите курсор так, чтобы он указывал на одну из скобок, и дайте соответствующую команду - редактор отыщет в тек- сте программы нужную парную скобку. CTRL-Q L. Если Вы случайно испортили строку, но курсор еще не покидал ее, эта команда поможет Вам восстановить испорчен- ный фрагмент. CRTL-O О. Заставит редактор поместить в самое начало файла строки, содержащие текущую настройку среды в виде директив компилятора, например: {$А+,В-,D+,Е+,F+,I+,L+,N+,О-,R+,S+,V+} {$М 16384,0,655360} {$DEFINE single} Fl. Замечательной особенностью Турбо-Паскаля является обшир- ная справочная информация, содержащаяся в файле TURBO.HLP, и возможность доступа к ней в любой момент времени. Если Вы вла- деете английским языком, то у Вас не возникнет никаких проблем при работе в среде - достаточно нажать клавишу F1 и на экране тут же появится окно со справкой. Характер справки зависит от теку- щего состояния среды. CTRL-F1. Напечатайте на экране или подведите курсор к любо- му зарезервированному слову или к имени стандартной процедуры (функции) и дайте эту команду - редактор проанализирует ближай- шее окружение курсора, выделит слово или имя из общего контек- ста и даст соответствующую Справку. ALT-F1. С помощью этой команды Вы можете повторить вывод предыдущей справки.
Г л а в a 4 ЭЛЕМЕНТЫ ЯЗЫКА 4.1. АЛФАВИТ Алфавит Турбо-Паскаля включает в себя буквы, цифры, шест- надцатиричные цифры, специальные символы и зарезервированные слова. Буквы - это буквы латинского алфавита от а до z и от А до Z. В Турбо-Паскале нет различия между большими и малыми буква- ми алфавита, если только они не входят в символьные и строко- вые выражения. Цифры - арабские от 0 до 9. Каждая шестнадцатиричная цифра имеет значение в диапазоне от 0 до 15. Первые 10 значений обозначаются арабскими цифрами 0...9, остальные шесть - латинскими буквами A...F или a...f. Специальные знаки Турбо-Паскаля - это символы: + -*/ = ,.:;< >[](){}"©$# К специальным знакам относятся также следующие пары симво- лов: <><=>=:=(**)(..) В программе эти пары символов нельзя разделять пробелами, если они используются как знаки операций отношения или ограни- чители комментария. Символы (. и .) могут употребляться соответ- ственно вместо символов [ и ]. Особое место в алфавите языка занимают пробелы, к которым относятся любые символы ASCII в диапазоне кодов от 0 до 32. Эти символы рассматриваются как ограничители идентификаторов, кон- стант, чисел, зарезервированных слов. Несколько следующих друг за другом пробелов считаются одним пробелом (последнее не относит- ся к строковым константам). 72
В Турбо-Паскале имеются следующие зарезервированные слова: absolute file mod shr and for nil string array forward not then begin function of to case goto or type const if packed unit div implementation procedure until do in program uses downto inline record var else interface repeat while end interrupt set with external label shl xor Зарезервированные слова не могут использоваться в качестве идентификаторов. 4.2. ИДЕНТИФИКАТОРЫ Идентификаторы в Турбо-Паскале - это имена констант, пере- менных, меток, типов, процедур, функций, модулей, программ и полей в записях. Идентификаторы могут иметь произвольную дли- ну, но значащими (уникальными в блоке) являются только первые 63 символа. Идентификатор всегда начинается буквой, за которой могут сле- довать буквы, цифры и знак подчеркивания (код 95 в ASCII). Про- белы и специальные символы алфавита не могут входить в иденти- фикатор. Примеры правильных идентификаторов: а ALPHA МуProgгamisBestProgram date_27_sep_39 Примеры неправильных идентификаторов: _beta {начинается подчеркиванием} IProgram {начинается цифрой} Ыоск#1 {содержит специальный символ} Му Program {содержит пробел} mod {зарезервированное слово} 4.3. КОНСТАНТЫ В качестве констант в Турбо-Паскале могут использоваться це- лые, вещественные и шестнадцатиричные числа, логические кон- станты, символы, строки символов, конструкторы множеств и при- знак неопределенного указателя NIL. Целые числа записываются со знаком или без него по обычным правилам и могут иметь значение в диапазоне от -2147483648 до + 2147483647. 73
Вещественные числа записываются со знаком или без него с ис- пользованием десятичной точки и/или экспоненциальной части. Экспоненциальная часть начинается латинским символом е или Е , за которым может следовать знак или и десятичный поря- док. Символ е (Е) означает десятичный порядок и имеет смысл "умножить на 10 в степени”, например: 3.14Е5 -3.14 умножить на 10 в степени 5; -17е-2 - минус 17 умножить на 10 в степени -2. Если в записи вещественного числа присутствует десятичная точка, перед ней и за ней должно быть хотя бы по одной цифре. Если используете^ символ экспоненциальной части е (Е), за ним должна следовать хотя бы одна цифра десятичного порядка. Шестнадцатиричное число состоит из шестнадцатиричных цифр, которым предшествует знак доллара (код 36 в ASCII). Диапазон ше- стнадцатиричных чисел - от $00000000 до $FFFFFFFF . Логическая константа - это либо слово FALSE (ложь), либо сло- во TRUE (истина). Символьная константа - это любой символ ПЭВМ, заключенный в апострофы: ’а’ - символ а; ’Z’ -символ Z; Если необходимо записать собственно символ апострофа, он уд- ваивается: ”” - символ ’ (апостроф). Допускается использовать запись символа путем указания его внутреннего кода, которому предшествует символ # (код 35), напри- мер: # 97 - символ а; # 90 - символ Z; # 39 - символ # 13 - символ CR . Строковая константа - это любая последовательность символов (кроме символа CR - возврат каретки), заключенная в апострофы. Если в строке нужно указать сам символ апострофа, он удваивается, например: 'Это - строка символов'; 'That' ’ s string ’ . Строка символов может быть пустой, т.е. не иметь никаких сим- волов в обрамляющих ее апострофах. Строку можно составлять из кодов нужных символов с предшествующими каждому коду симво- лами #, например: #83# 121#109#98#11#108 - эквивалентна строке ’Symbol’. Наконец, в строке можно чередовать части, записанные в обрам- ляющих апострофах, с частями, записанными кодами. Таким спосо- бом можно вставлять в строки любые управляющие символы, в том числе и символ CR, например: #7’Ошибка !’#13,Нажмите любую клавишу ...’#7. Конструктор множества - это список элементов множества, обрам- ленный квадратными скобками, например: 74
[1,2,4,7,12] [blue, red] [] [true] В отличие от стандартного Паскаля, в Турбо-Паскале разрешает- ся в объявлении констант использовать произвольные выражения, операндами которых могут быть ранее объявленные нетипизирован- ные константы, а также следующие функции от них: abs chr , Hi Length Например: Lo round swap ord SizeOf trunc odd str pred succ const MaxReal NumChars LnlO . ' LnlOR = Maxint div SizeOf(real); = ord('Z') - ord('a’) + 1; = 2.302585092994; = 1 / LnlO; 4.4. ВЫРАЖЕНИЯ Основными объектами, из которых конструируется исполняемая часть Турбо-Паскалевой программы, являются константы, перемен- ные и обращения к функциям. Каждый из этих объектов характе- ризуется своим значением и принадлежит к какому-либо типу дан- ных. С помощью знаков операций и скобок из них можно состав- лять выражения, которые фактически представляют собой правила получения новых значений. Частным случаем выражения может быть просто одиночный объект, т.е. константа, переменная или обращение к функции. Зна- чение такого выражения имеет, естественно, тот же тип, что и объект. В более общем случае выражение состоит из нескольких объектов (операндов) и знаков операций, а тип его значения опре- деляется как типом операндов, так и видом примененных к ним операций. Примеры выражений: у 21 (а + Ь) * с sin(t) а > 2 not Flag and (а = b) NIL [1, 3,7] * setl 75
4.5. ОПЕРАЦИИ В Турбо-Паскале определены следующие операции: - унарные not, -мультипликативные *, /, div, mod, and, shl, shr; -аддитивные +, -, or, xor; -отношения =, <>, <, >, <=, >=, in. Приоритет операций убывает в указанном порядке, т.е. наивыс- шим приоритетом обладают унарные операции, низшим - операции отношения. Порядок выполнения нескольких операций равного приоритета устанавливается компилятором из условия оптимизации кода программы и не обязательно слева направо. При исчислении логических выражений будут вычисляться все или только достаточ- ные операции отношения в зависимости от установленной в среде Турбо-Паскаля опции OPTIONS/COMPILER/BOOLEAN EVALUA- TION: при значении опции COMPLETE вычисляются все операции отношения, при значении SHORT CIRCUIT - только те, которые до- статочны для получения результата (см. гл.З). Это обстоятельство необходимо учитывать при использовании операций отношения с функциями, в которых осуществляется изменение глобальных пере- менных или параметров, передаваемых по имени, например: var a,b : integer; FUNCTION Addl(var х : integer) : integer; Begin {AddI} inc(x); AddI := x End {AddI}; BEGIN {main} if (a > b) or (Addl(a) > 100) then b := a; При выполнении этого фрагмента значение переменной А будет зависеть от установленной опции: если установлена COMPLETE, значение А всегда наращивается на 1, если SHORT CIRCUIT - толь- ко в случае А < = В . Правила использования операций с операндами различного типа приводятся в табл.2. При действиях с вещественным типом одним из операндов мо- жет быть значение любого целого типа. Результат операций имеет указанный в таблице тип EXTENDED только для установленного в среде Турбо-Паскаля режима генерации кода, рассчитанного на чис- ловой сопроцессор или на его эмуляцию (см. гл.5). Если этот ре- жим не установлен, результат будет иметь значение типа REAL. Для действий с целыми числами тип результата будет таким наименьшим по мощности целым типом, который еще содержит в себе полученный результат, например: 2+2 {byte} 128 * 2 . {word} -(32000 + 5000) {longint} 76
32000 + 5000 {word} Унарная операция (а применяется к операнду любого типа и возвращает результат типа POINTER (см. гл.7), в котором содержит- Таблица 2 Тип результата для различных операций Операция Действие Тип операндов Тип результата not Отрицание Boolean Boolean not * Любой целый Тип операнда @ Адрес Любой Pointer * Умножение Любой целый Наименьший целый * и Любой вещественный Extended * Пересечение множеств Множественный Множественный . / Деление Любой вещественный Extended div Целочисленное деление Любой целый Наименьший целый mod Остаток и and Логическое И Boolean Boolean and И Любой целый Наименьший целый shl Левый сдвиг То же То же shr Правый сдвиг и II + Сложение Любой целый Наименьший целый + и Любой вещественный Extended + Объединение множеств М ножественный Множественный + Объединение строк Строковый Строковый - Вычитание Любой целый Наименьший целый - W Любой вещественный Extended - Вычитание множеств Множественный х Множественный or Логическое ИЛИ Boolean Boolean or и Любой целый Наименьший целый = Равно Любой простой или Boolean строковый <> Не равно То же < Меньше и < = Меньше.равно W > Больше II > = Больше,равно II in Принадлежность к Любой простой и н множеству множественный ся адрес операнда. Пусть, например, задано описание type TwoChar = array [1..2] of char; var Int : integer; TwoCharPtr : "TwoChar; Тогда оператор TwoCharPtr := ©Int; 77
приведет к ‘тому, что в TWOCHARPTR будет храниться адрес цело- численной переменной INT, которая может теперь интерпретиро- ваться как совокупность двух символов. Поэтому возможен, напри- мер, такой оператор: if TwoCharPt г [1] “ = 'С then ... Если операция (й применяется к процедуре или функции, ее результатом будет адрес точки входа в процедуру (функцию). Этот адрес невозможно использовать в Турбо-Паскалевой программе ка- ким-либо иным способом, кроме как передав его подпрограмме, на- писанной на Ассемблере. В Турбо-Паскале определены следующие логические ’ операции: not - логическое НЕ; and -логическое И; or -логическое ИЛИ; хог - исключительное ИЛИ. Логические операции применимы к операндам целого и логиче- ского типа. Если операнды -• целые числа, то результат логической операции есть тоже целое число, биты которого (двоичные разря- ды) формируются из битов операндов по правилам, указанным в табл. 3. К логическим же в Турбо-Паскале обычно относятся и две сдви- говые операции над целыми числами: 4 i SHL j - сдвиг содержимого i на j разрядов влево; освободившие- ся младшие разряды заполняются нулями; i SHR j - сдвиг содержимого i на j разрядов вправо; освободив- шиеся старшие разряды заполняются нулями. В этих операциях i и j - выражения любого целого типа. С помощью программы примера 9 можно вывести на экран ре- зультат применения логических операций к двум целым числам. Таблица 3 Логические операции над данными типа INTEGER (поразрядно) Операнд 1 Операнд 2 not and or хог 1 - 0 - - - 0 - 1 - - - 0 0 - 0 0 0 0 1 - 0 1 1 1 0 - 0 1 1 1 1 - 1 1 0 78.
Пример 9 PROGRAM IntLogOperations ; ' { Программа вводит два целых числа и печатает результат применения к ним логических операций. Для выхода из программы ввести Ctrl-Z и нажать ’’Ввод" } var n,m:integer; 'BEGIN while not EOF do begin end readln(n,m); writeln(1 ’ not = ’, not n); wr i te In(1 ' and= ’,n and m) writeln(1 ’ or = ’ , n or m); writeln(1 ’ xo-r= ', n xor m) writeln(1 ' shl = ',n shl m) writeln(1 ' shr= ',n shr m) END. Логические операции над логическими данными дают результат логического типа по правилам, указанным в табл. 4. Таблица 4 Логические операции над данными типа BOOLEAN Операнд 1 Операнд 2 not and or xor false - true - - - true - false - - - false false - false false false false true - false true true true false - false true true true true - true true false Операция отношения IN применяется к двум операндам. Пер- вым (левым) операндом должно быть выражение любого порядко- вого типа, вторым - множество, состоящее из элементов того же ти- па, или идентификатор множественного типа. Операция дае,т TRUE, если левый операнд принадлежит множеству, например: var с : char; type • digit = set of 'O'..'9'; if c in digit then ... 79
Г л а в a 5 ТИПЫ ДАННЫХ Любая константа, переменная, значение функции или выраже- ния в Турбо-Паскале характеризуется своим типом. Тип любого из этих объектов определяет множество допустимых значений, которые может иметь объект, а также множество допустимых операций, ко- торые применимы к объекту. Кроме того, тип определяет и формат внутреннего представления значения объекта. Турбо-Паскаль характеризуется разветвленной структурой типов данных (рис. 15). В этой главе подробно описываются все типы, за исключением файлов и указателей, которые рассматриваются в следующих двух главах, а также процедурных типов, которые рассматриваются в гл. 9. Здесь же обсуждаются проблемы совместимости и преобразо- вания типов. 4 5.1. ПРОСТЫЕ ТИПЫ К простым относятся порядковые и вещественные типы. Порядковые типы отличаются тем, что каждый из них имеет конечное число возможных значений. Эти значения можно каким- либо образом упорядочить (отсюда название типов) и, следователь- но, каждому из них в конечном счете можно сопоставить некоторое целое число - порядковый номер значения. Вещественные типы, строго говоря, тоже имеют конечное число значений, которое определяется форматом внутреннего представле- ния вещественного числа. Однако количество возможных значений вещественных типов настолько велико, что сопоставить каждому из них целое, число не представляется возможным. 5.1.1. Порядковые типы К порядковым относятся целые типы, логический тип, символь- ный тип, перечисляемый тип и тип-диапазон. К любому из них применима функция ORD(X), которая возвращает порядковый но- мер значения выражения X. Для целых типов ORD(X) возвращает 80 ,
Рис. IS. Структура типов данных само значение X, т.е. ORD(X)=X для X, принадлежащему любому целому типу. Применение ORD(X) к логическому, символьному и перечисляемому типам дает положительное целое число в диапазо- не от 0 до 255. Тип-диапазон сохраняет все свойства базового поряд- кового типа, поэтому результат применения к нему функции ORD(X) зависит от свойств этого типа. К порядковым типам можно также применять такие функции: PRED(X) возвращает значение, которое соответствует порядково- му номеру ORD(X)-1, т.е. ORD(PRED(X))=ORD(X)-1; SUCC(X) возвращает значение, которое соответствует порядково- му номеру ORD(X) + 1, т.е. ORD(SUCC(X)) = ORD(X) + 1 . Если представить себе любой порядковый тип как упорядочен- ное множество значений, возрастающих слева направо и занимаю- щих на числовой оси некоторый Отрезок, то функция PRED(X) не- определена для левого, a SUCC(X) - для правого конца этого отрез- ка. Целые типы. Диапазон возможных значений целых типов зави- сит от их внутреннего представления, которое может занимать 81
один, два или четыре байта. В табл. 5 приводится название целых типов, длина их внутреннего представления в байтах и диапазон возможных значений. Целые типы Таблица 5 Длина, байт Название типа Диапазон значений 1 byte 0 .. . 255 1 shortint -128 . .. 127 2 word 0 .. . 65535 2 integer -32768 .. .. 32767. 4 longint -2147483648 .. .. 2147483647 Над целыми числами определены операции: "+" -сложение; -умножение; - вычитание; div - целочисленное деление; mod - получение остатка от целочисленного деления. При использовании процедур и функций с целочисленными па- раметрами следует руководствоваться "вложенностью" типов, т.е. вез- де, где может использоваться WORD, допускается использование BYTE (но не наоборот); в LONGINT "входит" INTEGER, который, в свою очередь, включает в себя SHORTINT. В табл. 6 содержится перечень встроенных процедур и функций, применимых к целочисленным типам. Буквами b,s,w,i,l обозначены выражения соотвественно типа BYTE, SHORTINT, WORD, INTEGER и LONGINT; x-выражение любого из этих типов; буквы vb,vs, vw,vi,vl,vx обозначают переменные соответствующих типов. В квад- ратных скобках указывается необязательный параметр. При использовании разных целых типов в одном выражении они приводятся к базовому типу - минимальной мощности целочис- ленному типу, который еще содержит результат. Например, при использовании INTEGER и SHORTINT базовым будет тип INTEGER, при использовании INTEGER и WORD базовым будет LONGINT и тд. Логический тип. Значением логического типа BOOLEAN может быть одна из предварительно объявленных констант FALSE (ложь) или TRUE (истина). Для них справедливы правила: ord(false) = 0; ord(true) = 1; false < true; succ(false)= true; pred(true) = false. Поскольку BOOLEAN относится к порядковым типам, его можно использовать, например, в операторе 82
var • 1 : Boolean; for 1 := false to true do .... Таблица 6 Встроенные процедуры и функции, применимые к целым типам Обращение Тип результата Действие abs(x) chr(b) dec(vx[,i]) x Возвращает модуль х char Возвращает символ Процедура Уменьшает значение vx на i, при отсутствии i - inc(vx[,i]) на 1 Процедура Увеличивает значение vx на i, при отсутствии i - hi(i) hi(w) lo(i) lo(w) odd(l) на 1 byte Возвращает старший байт аргумента " То же * Возвращает младший байт аргумента " То же Boolean Возвращает true, если аргумент - нечетное число: false, если четное random(w) Как у параметра Возвращает псевдослучайное число, равномерно sqr(x) swap(i) swap(w) распределенное на интервале 0<=x<w Как у параметра Возвращает квадрат аргумента integer Меняет местами байты в слове word То же Перечисляемый тип. Задается перечислением тех значений, кото- рые он может получать. Каждое значение именуется некоторым идентификатором, и располагается в списке, обрамленном круглыми скобками, например: type month=(jan,feb,mar, apr,may,jun,jul,aug,sep,oct,nov,dec); Имя, которое программист присваивает перечисляемому ти- пу, - произвольный идентификатор. Ьбъявление типа должно быть сделано в разделе объявлений, и ему должно предшествовать кодо- вое слово TYPE (англ. тип). Между значениями перечисляемого типа и порядковыми номе- рами этих значений устанавливается следующее соответствие: пер- вое значение в списке получает порядковый номер 0, второе - 1 и т.д. Максимальная мощность перечисляемого типа - 256 значений, поэтому фактически перечисляемый тип задает некоторое подмно- жество целого типа BYTE. Пусть, например, заданы перечисляемые типы: 83 ।
type colors = (black, red, white); ordenal= (one, two, three); days = (monday, tuesday, Wednesday); С точки зрения мощности и внутреннего представления все три типа эквивалентны: ord(black)=0, ... , ord(white)=2, ord(one)=0, ... , ord(three)=2, ord(monday)=0, ... , ord(wednesday)=2 . Однако, если определены переменные var col : colors; num : ordenal; day : days; то допустимы операторы col := black; num := succ(two); day pred(tuesday); но недопустимы col := one; day := three; И Т.Д. Применение перечисляемых типов делает программы нагляднее и одновременно повышает надежность программ, так как можно контролировать значения, которые получают переменные перечис- ляемых типов. Как уже говорилось, между значениями перечисляемого типа и множеством целых чисел существует однозначное соответствие, за- даваемое функцией ORD(X). В Турбо-Паскале допускается и обрат- ное преобразование: любое выражение типа BYTE можно преобразо- вать в значение перечисляемого типа, если только значение этого выражения не превышает мощность перечисляемого типа. Такое преобразование достигается применением автоматически объявляе- мой функции с именем перечисляемого типа (см. 5.4). Пример 10 иллюстрирует сказанное. В его программе осуществ- ляется ввод целого числа с клавиатуры, присвоение соответствую- щего значения переменной перечисляемого типа и вывод на экран идентификатора этого значения (стандартная процедура WRITELN не может обрабатывать выражения перечисляемого типа, поэтому для вывода используется оператор выбора CASE...OF). 84
Пример 10 PROGRAM OrdenalType; {$R+} {включение контроля границ изменения индексов и пере- числяемых типов} type colors®(red,black,white, yellow); var b:byte; c:colors; BEGIN readln(b); c:=colors(b); case c of red : writeln('red'); black : writeln('black'); white : writeln('white'); yellow: writeln('yellow’) end END. Обратите внимание: ввод числа, превышающего 3, вызовет ава- рийное завершение программы и выдачу диагностики только в том случае, если в среде Турбо-Паскаля установлена опция контроля границ массива. Для задействования этой опции независимо от на- стройки среды в текст программы включен комментарий специаль- ного вида - директива компилятору (подробнее см. гл. 3). Символьный тип. Значением символьного типа CHAR является множество всех символов ПЭВМ. Каждому символу приписывается целое число в диапазоне 0...255. Это число есть код внутреннего представления символа, его возвращает функция ORD. Для кодировки используется код ASCII (American Standard Code for Information Interchange - американский стандартный код для об- мена информацией). Это 7-битный код, т.е. с его помощью можно закодировать лишь 128 символов в диапазоне 0...127. В то же время в 8-битном байте, отведенном для хранения символа в Турбо-Паска- ле, можно закодировать в два раза больше символов в диапазоне 0...255. Первая половина символов с кодами 0...127 соответствует стандарту ASCII и приводится в табл. 7. Вторая половина символов с кодами 128...255 не ограничена жесткими рамками стандарта и может меняться на ПЭВМ разных типов. В прилож. 1 приведены некоторые распространенные варианты кодировки второй половины символов. Символы с кодами 0...31 относятся к служебным кодам. Если эти коды используются в символьном тексте Турбо-Паскалевой про- граммы, они считаются пробелами (см. гл. 4). В операциях ввода- 85
вывода некоторые из них могут иметь следующее самостоятельное значение: Символ Код Значение BEL 7 Звонок; вывод на экран этого символа со- провождается звуковым сигналом. НТ 9 Горизонтальная табуляция; при выводе на эк- ран смещает курсор в позицию, кратную 8, плюс 1 (9, 17, 25 и т.д.). LF 10 Перевод строки; при выводе все последую- щие символы будут выводиться, начиная с той же позиции, но на следующей строке. VT 11 Вертикальная табуляция; при выводе на экран заменяется специальным знаком. FF 12 Прогон страницы; при выводе на принтер формирует страницу, при выводе на экран за меняется специальным знаком. , CR 13 Возврат каретки; вводится нажатием на кла- вишу "Ввод" (при вводе с помощью READ или READLN означает команду "Ввод" и в буфер ввода не помещается); при выводе означает команду продолжить вывод с начала текущей строки. EOF 26 Конец файла; вводится с клавиатуры нажа- тием Ctrl-Z, при выводе заменяется специаль- ным знаком. ESC 27 Конец работы; вводится с клавиатуры на- жатием на клавишу ESC, при выводе заменя- ется специальным знаком. BL 32 Пробел. К типу CHAR применимы операции отношения, а также встро- енные функции: chr(b) - функция типа CHAR; преобразует выражение типа BYTE в символ и возвращает последний в качестве своего значения; UpCase(ch) - функция типа CHAR. Возвращает символ в верхнем регистре, если он определен для аргумента СН типа CHAR; в про- тивном случае возвращает сам символ СН. Тип-диапазон есть подмножество своего базового типа, в качестве которого может выступать любой порядковый тип, кроме типа-диа- пазона. Тип-диапазон задается границами своих значений внутри базового типа: < мин.знач. >.. < макс.знач. > 86
Здесь <мин.знач.>,<макс.знач.> - минимальное и максимальное значения типа-диапазона. , Таблица 7 Кодировка символов в соответствии с кодом ASCII Код Символ Код Символ Код Символ Код Символ 0 NUL 32 BL 64 @ 96 1 SOH 33 1 65 А 97 а 2. STX 34 И 66 в 98 ь 3 ЕТХ 35 67 с 99 с 4 EOT 36 $ 68 D 100 d 5 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 11 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 0 16 DEL 48 0 80 Р 112 P 17 DC1 49 1 81 Q 113 q 18 DC2 50 2 82 R 114 r 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 EOF 58 90 Z 122 z 27 ESC 59 91 [ 123 { 28 FS 60 < 92 \ 124 1 29 GS 61 93 ] 125 } 30 RS 62 < 94 126 31 US 63 О 95 - 127 Например: type digit = '0'..'9’; dig2 = 48 .. 57; days = (mo,tu,we,th,fr,sa,su); WeekEnd = sa .. su; Как видно из примеров, границы типа-диапазона разделяются специальным символом - двумя последовательными точками (в от- личие от принятого троеточия). При определении типа-диапазона нужно руководствоваться следу- ющими правилами: 87
V - два символа рассматриваются как один символ, поэтому между ними недопустимы пробелы; - необходимо, чтобы левая граница диапазона не превышала его правую границу; - имя типа-диапазона должно быть правильным идентификато- ром. Тип-диапазон наследует все свойства своего базового типа, но с ограничениями, связанными с его меньшей мощностью. В частно- сти, если для предыдущего примера определена переменная var w : WeekEnd; w := sa; то ORD(W) вернет значение 5, в то время как PRED(W) приведет к ошибке. 5.1.2. Вещественные типы В отличие от порядковых типов, значения которых всегда сопо- ставляются с рядом целых чисел и, следовательно, представляются в ПЭВМ абсолютно точно, значение произвольного числа вещест- венного типа представляется в ПЭВМ лишь с некоторой конечной точностью, которая зависит от внутреннего формата вещественного числа. В табл.8 указаны используемые в Турбо-Паскале веществен- ные типы. Вещественное число в Турбо-Паскале занимает от 4 до 10 смежных байт и имеет следующую структуру: s е ш Здесь s - знаковый разряд числа; е - экспоненциальная часть, со- держащая двоичный порядок; m - мантисса числа. Таблица 8 Вещественные типы Длина, байт Название типа Количество значащих Диапазон десятичного порядка цифр 4 single 7...8 -45 . .. + 38 6 real 11...12 -39 . ..+38 8 double 15...16 -324 . .. + 308 10 extended 19...20 й951 * ..+4932 8 comp 19...20 -263 + 1 ...+2 -1 Мантисса m имеет длину • от 23 для SINGLE до 63 для EXTENDED двоичных разрядов, что и обеспечивает точность 7...8 для SINGLE и 19...20 для EXTENDED десятичных цифр. Десяти- чная точка подразумевается перед левым (старшим) разрядом ман- 88 <
тиссы, но при действиях с числом ее положение сдвигается влево или вправо в соответствии с двоичным порядком числа, хранящим- ся в экспоненциальной части, поэтому действия над вещественны- ми числами называют арифметикой с плавающей точкой. Как видим, в Турбо-Паскале имеется богатая гамма веществен- ных типов, однако доступ к типам SINGLE, DOUBLE и EXTENDED возможен только при особых режимах компиляции. Дело в том, что эти типы рассчитаны на аппаратную поддержку арифметики с плавающей точкой и для их эффективного использования в состав ПЭВМ должен входить арифметический сопроцессор 8087, 80287 или их отечественный аналог К1810ВМ87. Компилятор Турбо-Паскаля позволяет создавать программы, работающие на любых ПЭВМ (с сопроцессором или без него) и использующие любые вещественные типы. Необходимая для этого настройка компилятора описана в гл.З. х- Отметим, что арифметический сопроцессор всегда обрабатывает числа в формате EXTENDED, а три других вещественных типа в этом случае получаются простым усечением результатов до нужных размеров и используются в основном для экономии памяти. Для работы с вещественными данными могут' использоваться встроенные математические функции, представленные в табл. 9. В этой таблице REAL означает любой вещественный тип, INTEGER - любой целый тип. В примере 11 приведена тестовая программа, позволяющая оце- нить реальную производительность ПЭВМ, обеспечиваемую Турбо- Паскалем в режиме арифметики с плавающей точкой. z Таблица 9 Встроенные математические функции Турбо-Паскаля Обращение Тип параметра Тип результата Реализуемое действие abs(x) real,integer Тип аргумента Модуль аргумента ArcTan(x) real real Арктангенс (радианы) cos(x) и Косинус, угол в радианах ехр(х) и W Экспонента. frac(x) и It Дробная часть числа int(x) w и Целая часть числа ln(x) и W Логарифм натуральный pi — и пи=3.141592653... Random - II Равномерное псевдослучайное чис ло 0 < = х < 1 Random(x) » integer integer Равномерное псевдослучайное це- лое число 0 < = i < х Randomize - - Инициация датчика псевдослучай- ных чисел sin(x). real real Синус, угол в радианах sqr(x) real,integer Тип аргумента Квадрат аргумента sqrt(x) real real Корень квадратный 89
Программа осуществляет цикл из 10000 вычислений выражения, содержащего все четыре арифметических действия. Перед началом цикла программа обращается к процедуре GETTIME и получает с ее помощью системное время. Сразу после завершения цикла про- грамма вновь получает системное время, что дает ей возможность измерить временной интервал, затраченный на выполнение цикла. Этот интервал используется для подсчета количества прогонов цик- ла в единицу времени (секунду), которое и выводится на экран в качестве меры производительности ПЭВМ. Пример 11 PROGRAM TestOfFloatingPointSpeed; { Программа рассчитывает количество вычислений с плавающей запятой в одну‘секунду } Uses DOS; {доступ к процедуре GetTime} var TimeBeg,TimeEnd : longint; {начальные и конечные моменты времени} Hour,Min,Sec,SeclOO : word; {параметры обращения к GetTime} i : longint; {параметр цикла} a,b,c,d,e : real; {операнды выражения} speed : longint; {результат работы} const N = 10000; {длина цикла} " BEGIN { установить произвольные значения операндов: } b:=pi; с:=0.9; d:=1.17; е:=0.81; { получить время и перевести его в десятки милисекунд:} GetTime(Hour,Min,Sec,Sec100); TimeBeg := ((Hour*60+Min)*60+Sec)*100+Secl00; { основной цикл: } for i:=1 to N do a:=b*c/d+e- 2.09; GetT ime(Hour,Min,Sec,SeclOO); TimeEnd := ((Hour*60+Min)*60+Sec)*100+Secl00; speed := round(N*4*100/(TimeEnd-TimeBeg)); writein(’Относительная скорость = speed, операций с плавающей запятой в секунду') END. Если в состав Вашей ПЭВМ входит числовой сопроцессор, то чрезвычайно полезно повторить ту же самую программу, но предва- рительно произвести переопределение типа. Для этого добавьте фразу type real = single; сразу после Uses DOS;. Такое переопределение заставит программу проводить вычисления с другим типом данных - SINGLE. Сопоста- 90
вив вновь полученный вариант с предыдущим, Вы с удивлением обнаружите, что скорость вычислений возросла более чем в три ра- за! Повторяя вычисления с переопределениями type ( real = double; И type real = extended; Вы заметите, что скорость вычислений с данными этих типов мало отличается от скорости с типом SINGLE, что и следовало ожидать: ведь сопроцессор ВСЕГДА обрабатывает данные одного типа - EXTENDED, и небольшая разница в скоростях определяется только потерями на пересылку большего количества байтов данных типа EXTENDED по сравнению с 1 типом SINGLE. Почему же так рази- тельно отличается скорость при работе с типом REAL? Секрет, как мне кажется, заключается в том, что формат внут- реннего представления типа REAL отличается от остальных вещест- венных типов. Вот он: s m е Изменение местоположения экспоненциальной части по сравне- нию со стандартным для сопроцессора вызывает необходимость до- полнительных преобразований, что и является, судя по всему, при- чиной наблюдаемого эффекта. Разумеется, это только предположе- ние. Остается лишь гадать, какими соображениями руководствова- лись разработчики Турбо-Паскаля, - но факт остается фактом: тип REAL, который используют, не задумываясь, многие программисты при разработке вычислительных программ на Турбо-Паскале, самый медленный из всех вещественных типов. Простым переопределени- ем типа REAL на SINGLE или EXTENDED Вы можете существен- но ус1Фрить выполнение Ваших программ. Повторяю, сказанное справедливо только в том случае, когда Ва- ша ПЭВМ оснащена сопроцессором. Если сопроцессора нет, переоп- ределение типа практически не влияет на производительность. Особое положение в Турбо-Паскале занимает тип СОМР , кото- рый трактуется как вещественное число без экспоненциальной и дробной частей. Фактически СОМР - это "большое” целое число со знаком, сохраняющее 19...20 значащих десятичных цифр (во внут- реннем представлении СОМР занимает 8 смежных байт). В то же время в выражениях СОМР полностью совместим с любыми други- ми вещественными типами: над ним определены все вещественные операции, он может использоваться как аргумент математических функций и тл. Наиболее подходящей областью применения типа СОМР являются бухгалтерские расчеты: денежные суммы выража- ются в копейках и действия над ними сводятся к операциям с до- статочно длинными целыми числами. 91
5.2. СТРУКТУРИРОВАННЫЕ ТИПЫ Любой из структурированных типов, а в Турбо-Паскале их четы- ре: массивы, записи, множества и файлы - характеризуется множест- венностью образующих этот тип элементов, т.е. переменная или константа структурированного типа всегда имеет несколько компо- нентов. Каждый компонент, в свою очередь, может принадлежать какому-либо структурированному типу, что позволяет говорить о возможной вложенности типов. В Турбо-Паскале допускается произ- вольная глубина вложенности типов, однако суммарная длина любо- го из них во внутреннем представлении не должна превышать 65520 байт. Подобно типу-диапазону или перечисляемому типу, структуриро- ванный тип йеобходимо объявлять в разделе описаний после кодо- вого слова TYPE. В целях совместимости ср стандартным Паскалем, в Турбо-Паскале разрешается перед описанием структурированного типа ставить кодовое слово PACKED, предписывающее компилятору по возможности экономить память, отводимую под объекты струк- турированного типа; однако фактически компилятор игнорирует это указание: "упаковка" данных в Турбо-Паскале осуществляется авто- матически везде, где это возможно. Поскольку операции с файлами весьма специфичны и сущест- венно используют аппаратные средства ПЭВМ, описание этого типа данных и действий с ними вынесены в отдельную гл.6. 5.2.1. Массивы Массиву в Турбо-Паскале во многом схожи с аналогичными ти- пами данных в других языках программирования. Отличительной особенностью массивов является то обстоятельство, что все их ком- поненты суть данные одного типа (возможно, структурированного); эти компоненты можно легко упорядочить и обеспечить доступ к любому из них простым указанием его порядкового номера, напри- мер: type digit = array [0..9] of char; matrix = array [byte] of single; var m : matrix; d : digit; i : integer; m[17] := ord(d[i~l])/10; Описание типа массива задается следующим образом: <имя типа> = ARRAY [ <сп.инд.типов > 1 OF <тип> Здесь < имя типа > - правильный идентификатор; 92
ARRAY, OF - кодовые слова (англ, массив, из); < сп.инд.типов >-список из одного или нескольких индексных типов, разделенный запятыми; квадратные скобки, обрамляющие список, - требование синтаксиса; <тип>-любой тип Турбо-Паскаля. В качестве индексных типов в Турбо-Паскале можно использо- вать любые порядковые типы, кроме LONGINT и типов-диапазонов с базовым типом LONGINT. Обычно в качестве индексного типа используется тип-диапазон, в котором задаются границы изменения индексов. Так как <тип>, идущий за кодовым словом OF,-любой тип Турбо-Паскаля, он мо- жет быть, в частности, другим массивом, например: type mat = array [0.. 5] of array [-2.-2] of array [char] of byte; Такую запись можно заменить более компактной: type mat » array [0. .5,-2. .2*,char] of byte; Как уже говорилось, глубина вложенности структурированных ти- пов вообще, а следовательно, и массивов - произвольная, поэтому количество элементов в списке индексных типов (размерность мас- сива) неограничено, однако суммарная длина внутреннего представ- ления любого массива, как уже говорилось, не может быть больше 65520 байт. В памяти ПЭВМ элементы массива следуют друг за другом так, что при переходе от младших адресов к старшим наи- более быстро меняется самый правый индекс массива. Если, напри- мер, var а : array [1..2.1..2] of byte; а [1,1] :=1; а [2,1] ?=2; а[1,2]:=3; а[2,2]:=4; то в памяти последовательно друг за другом будут расположены байты со значениями 1, 3, 2, 4. Это обстоятельство может оказать- ся важным при использовании встроенной процедуры копирования памяти MOVE (см. с.398). Для иллюстрации работы с массивами данных составим программу (пример 12), проверяющую качество работы встроенного генератора псевдослучайных чисел RANDOM (см. табл. 6). Поскольку этот генератор должен давать последова- тельность случайных чисел, равномерно распределенных на интер- вале 0...d , где d > 0 - параметр обращения к RANDOM, то количе- ство чисел, попавших в любой из поддиапазонов 0...1, 1...2, 2...3, ..., (d-l)...d, должно быть приблизительно одинаковым для достаточно большой последовательности из N случайных чисел. Пример 12 PROGRAM TestOfRandomGenerator; {Проверка равномерности распределения 93
случайных чисел, получаемых с помощью встроенной функции RANDOM.} const N = 10000; {количество обращений к RANDOM} d = 10; {диапазон изменения чисел} var measure : array [0..d-l] of word; i ; integer; BEGIN for i ; = ,0 to d-1 do measure[i] := 0; for i := 1 to N do inc(measure[random(d)]); for i := 0 to d-1 do write(measure[i] :5); writein END. , В Турбо-Паскале можно единственным оператором присвавания передать все элементы одного массива другому массиву того же ти- па, например: var a,b : array [1..5] of single; а := b; После этого присваивания все 5 элементов массива А получат те же значения, что и в массиве В. Однако над массивами не опреде- лены операции отношения. Нельзя, например, записать if а = b then .... В Турбо-Паскале есть два предварительно определенных массива с именами МЕМ и PORT. Первый из них позволяет любой Турбо- Паскалевой программе обращаться непосредственно к памяти ПЭВМ по физическим адресам, второй используется для прямого управления устройствами ввода-вывода (см. 11.7). 5.2.2. Записи Запись - это структура данных, состоящая из фиксированного числа компонентов, называемых полями записи. В отличие от мас- сива, компоненты (поля) записи могут быть различного типа. Что- бы можно было ссылаться на тот или иной компонент записи, по- ля именуются. Структура объявления типа записи <имя типа> = RECORD <сп.полей> END Здесь < имя типа >-правильный идентификатор; RECORD, END - ключевые слова (англ, запись, конец); <сп.полей> -список полей; представляет собой последователь- ность разделов записи, между которыми ставит- ся точка с запятой. Каждый раздел записи состоит из одного или нескольких иден- тификаторов, отделяемых друг от друга запятыми. За идентифика- тором (идентификаторами) ставится двоеточие и описание типа. На- пример:
type BirthDay = record day,month : byte; year : word end; var a,b : BirthDay; В этом примере запись BIRTHDAY (англ, день рождения) содер- жит три поля с именами DAY, MONTH и YEAR (англ.: день, ме- сяц и год); переменные А и В - записи типа BIRTHDAY. Как и в массиве, значения переменных и констант типа RECORD можно присваивать другим переменным того же типа, например а := b К каждому из компонентов записи можно получить доступ, если указать имя переменной типа RECORD, затем точку и имя поля: a.day := 27; b.year := 1939; Для вложенных полей приходится продолжать уточнения: var с : record name : string; bd : BirthDay end; if c.bd.year = 1939 then .... Чтобы упростить доступ к полям записи, используется оператор WITH <сп. зап> DO < оператор > . Здесь WITH, DO - зарезервированные слова (англ.: с, делать); <сп.зап.> - список из одной или нескольких переменных типа запись (если в списке несколько переменных, они разделяются запятыми); < оператор > - любой оператор Турбо-Паскаля. Например: with с do Name := ' '; with с, bd do Month := 9; Последний оператор можно записать иначе: with c.bd do Month := 9; ИЛИ with c do with bd do month := 9; или, наконец, c.bd.month := 9; Оператор WITH обычно используется в тех случаях, когда необходимо произвести несколько действий подряд с полями записи. Например, инициацию переменной MYSON описанного выше типа BIRTHDAY можно осуществить следующим образом: with MySon do begin Day := 20; Month := 6; 95
Year := 1989 end; До сих пор мы рассматривали записи с полями, данные в кото- рых строго фиксированы. Турбо-Паскаль разрешает использовать за- писи с так называемыми вариантными полями, например: type forma = record case Boolean of true : (Birthplace : string[40j); false : (country : string[20]; Entryport : string[20]; EntryDate : 1. .31; ExitDate : 1..31) end; Как видно из этого примера, вариантная часть задается предло- жением CASE...OF и состоит из нескольких вариантов. Каждый ва- риант определяется константой выбора, за которой следует двоето- чие и список полей, обрамленный круглыми скобками. В любой записи может быть только одна вариантная часть, и, если она есть, она должна располагаться за всеми фиксированными полями. Замечательной особенностью вариантной части является то об- стоятельство, что все варианты как бы "накладываются" друг на друга, т.е. каждому из них выделяется одна и та же область памя- ти. Это открывает дополнительные возможности преобразования ти- пов, например: var ' m : record case byte of 0 ; (by : array [0..3] of byte); 1 : (wo : array [0..1] of word); 2 : (lo : longint); end; with m do begin lo : = ......; if by[3]=2 then .... Предложение CASE...OF, открывающее вариантную часть, внешне похоже на соответствующий оператор выбора, но на самом деле иг- рает роль своеобразного кодового слова, обозначающего начало вари- антной части. Именно поэтому в конце вариантной части не следу- ет ставить END как пару к CASE...OF. (Поскольку вариантная часть - всегда последняя в записи, END за ней все же стоит, но как пара к RECORD.) Ключ выбора в предложении CASE...OF фактиче- ски игнорируется компилятором: единственное требование, предъяв- ляемое к нему Турбо-Паскалем, состоит в том, чтобы ключ опреде- лял некоторый стандартный или предварительно объявленный по- рядковый тип. Сам этот тип никак не влияет ни на количество 96
следующих ниже вариантных полей, ни даже \на характер констант выбора. В стандартном Паскале в качестве ключа выбора необходи- мо указывать некоторую переменную порядкового типа, причем в исполняемой части программы можно присваивать значение этой переменной и таким образом влиять на выбор полей. В Турбо-Пас- кале также можно в поле ключа выбора описывать переменную по- рядкового типа и даже присваивать ей какое-либо значение, однако это не повлияет на выбор поля: значения констант выбора в Турбо- Паскале могут быть произвольными и даже повторяющимися, на- пример: type reel = record а : byte; b : word end; rec? .= record c : longint; case x : byte of 1 : (d : word); 2 : (e : record case Boolean of 3 : ( f : reel); 3 : ( g : single); '3' : ( c : word) end) end; var r : rec2; r.x := 255; if r.e.g=0 then writeln(’O.K.-’) else writeln(r.e.g) Обратите внимание: предложение CASE BOOLEAN OF в записи, определяемой в поле Е, объявляет ключом выбора логический тип, который, как известно, имеет лишь два значения: TRUE и FALSE. Константы же выбора следующих далее вариантов содержат совер- шенно не свойственные этому типу значения, и, кроме того, две из них повторяются, а общее количество вариантов - три, а не два, как следовало бы ожидать. Имена полей должны быть уникальными в пределах той запи- си, где они объявлены, однако если записи содержат поля-записи, т.е. вложены одна в другую, имена могут повторяться на разных уровнях вложенности (см. поле С в последнем примере). Для иллюстрации приемов работы с записями допустим, что Вы собираетесь создать электронную картотеку публикаций по ка- кой-либо интересующей Вас теме. Этими публикациями могут быть книги, статьи из сборников и журналов, рефераты из реферативного журнала. Составим следующую структуру данных (для облегчения 4 - Фаронов 97
понимания примера данные именуются по-русски с указанием про- граммного эквивалента): Публикация (publication) Книга (book) Автор (author) Название (title) Год (year) Издательство (PublishingHouse) Страницы (pages) Статья (article) Автор (author) Название (title) Год (year) Журнал (journal) Номер журнала (NumberOfJournal) Начальная страница (BegPageJ) Конечная страница (EndPageJ) Реферат (paper) Автор (author) Название (title) Год (year) Номер реферативного журнала (NumberOfReferenceJournal) Начальная страница (BegPageRJ) Конечная страница (EndPageRJ) Как видим, три параметра - автор, заголовок и год - повторя- ются для каждого вида публикации, поэтому их можно вынести в часть записи с фиксированными полями, остальные - в. вариантную часть. Приводимая в примере 13 программа запрашивает вид публика- ции и вводит ее параметры. Программа не имеет самостоятельной ценности, ибо никак не использует вводимую информацию. При желании, к ней можно добавить средства сохранения данных на ди- ске и средства вывода информации. Пример 13 PROGRAM ExampleOfRecordllsing; type KindOfPublication = (book,article,paper); Publication = record { фиксированная часть записи: } author,title : string; year : word; kindp : KindOfPublication; { вариантная часть записи: } case KindOfPublication of 98
book : (PublishingHouse : string; . pages : word); article : (journal string; numberOfJourna1 : byte; BegPageJ,EndPageJ : : word); paper : (numberOfReferanceJourna 1: byte BegPageRJ,EndPageRJ : word) end; var pub : publication; j : word; BEGIN repeat write(’ Вид публикации (0..2) = r); readln(j); if j<=2 then with pub do begin kindp:=kindOfPub 1ication(j) ; write(' Автор : ’); readln(author); write(' Название : ’); readln(title); write(’ Год : ’); readln(year); case kindp of book : begin write(' Издательство: ’); read In(Pub 1ishingHouse); write( ’ Объем, стр. : '); readln(pages) end; article : begin write(' Название журнала: ’); readln(Name); write(’ Номер : ’); readln(NumberOfJournal); write(’ Страницы : ’); readln(BegPageJ,EndPageJ) end; paper : begin . write(’ Номер p.ж. : '); readln(NumberOfPaper); write( ’ Страницы : ’); readln(BegPageP,EndPageP) end end {case} end {with pub}; 99
until k>2 {повторять до ввода недопустимого вида публикации} END. 5.2.3. Множества Множества - это наборы однотипных объектов, каким-либо обра- зом связанных друг с другом. Характер связей между объектами лишь подразумевается программистом и никак не контролируется Турбо-Паскалем. Количество элементов, входящих в множество, мо- жет меняться в пределах от 0 до 256 (множество, не содержащее элементов, называется пустым). Именно переменностью количества своих элементов множества отличаются от массивов и записей. Два множества считаются эквивалентными тогда и только тогда, когда все их элементы одинаковы, причем порядок следования эле- ментов в множестве безразличен. Если все элементы одного множе- ства входят также и в другое, говорят о включении первого множе- ства во второе. Пустое множество включено в любое другое. Пример определения и задания множеств: type digitChar = set of 'O'..'9'; digit = set of 0..9; var sl,s2,s3 : digitChar; s4,s5,s6 : digit; si := ['1', ’2', '3']; s2 := ['3', ’2', '1']; S3 := ['2', '3'] ; s4 := [0..3, 6]; s5 := [4, 5]; s6 := [3. .9] ; В этом примере множества S1 и S2 эквивалентны, а множество S3 включено в S2, но не эквивалентно ему. ' Описание типа множества имеет вид <имя типа> = SET OF <баз.тип> Здесь <имя типа>- правильный идентификатор; SET, OF - ключевые слова (англ.: множество, из); <баз.тип> - базовый тип элементов множества, в качестве которого может использоваться любой порядковый тип, кроме WORD, INTEGER, LONGINT. Для задания множества используется так называемый конструк- тор множества: список спецификаций элементов множества, отделя- емых друг от друга запятыми; список обрамляется квадратными скобками. Спецификациями элементов могут быть константы или выражения базового типа, а также тип-диапазон того же базового типа. 100
Над множествами определены следующие операции: * - пересечение множеств; результат содержит элементы, общие для обоих множеств; например, S4*S6 содержит [3], S4*S5 - пустое множество (см. выше); + - объединение множеств; результат содержит элементы перво- го множества, дополненные недостающими элементами из второго множества: S4+S5 содержит [0,1,2,3,4,5,6]; S5 + S6 содержит [3,4,5,6,7,8,9]; - - разность множеств; результат содержит элементы из первого множества, которые не принадлежат второму: S6-S5 содержит [3,6,7,8,9]; S4-S5 содержит [0,1,2,3,6]; = -проверка эквивалентности; возвращает TRUE, если оба мно- жества эквивалентны; < > - проверка неэквивалентности; возвращает TRUE, если оба множества неэквивалентны; < = - проверка вхождения; возвращает TRUE, если первое мно- жество включено во второе; > = - проверка вхождения; возвращает TRUE, если второе мно- жество включено в первое; in - проверка принадлежности; в этой бинарной операции пер- вый элемент - выражение, а второй - множество одного и того же типа; возвращает TRUE, если выражение имеет значение, принадле- жащее множеству: 3 in s6 возвращает TRUE; \ 2*2 in si возвращает FALSE . В примере 14, иллюстрирующем приемы работы с множествами, реализуемся алгоритм выделения из первой сотни натуральных чи- сел всех простых чисел. ^Алгоритм с небольшими изменениями за- имствован из [3]. В его основе лежит прием, известный под назва- нием ’’решето Эратосфена". Пример 14 PROGRAM DetectOfPrimerNumbers; const z N = 100; {количество элементов исходного множества} type SetOfNumber = set of 1..N; var nl,next,i : word; {вспомогательные переменные} BeginSet, {исходное множество} PrimerSet : SetOfNumber; {множество простых чисел} * BEGIN BeginSet := [2..N]; {создать исходное множество} PrimerSet:= [1]; {первое простое число} next := 2; {следующее простое число} while BeginSet <> [] do {начало основного цикла} beg i n nl := next; {nl - число,кратное очередному простому (next)} 101
while nl <= N do {цикл удаления из исходного множества непростых чисел} begin BeginSet := BeginSet - [nl]; nl := nl + next {следующее кратное} .end; {конец цикла удаления} PrimerSet := PrimerSet + [next]; repeat {получить следующее простое число, которое есть первое невычеркнутое из исходного множества} inc(next) until (next in BeginSet) or (next > N) end; {конец основного цикла} { вывод результата: } for i := 1 to N do if i in PrimerSet then write(i:8); writein END. 5.3. СТРОКИ Тип STRING (строка) в Турбо-Паскале широко используется для обработки текстов и во многом похож на одномерный массив сим- волов ARRAY [0..N] OF CHAR. Однако в отличие от массива коли- чество символов в строке-переменной может меняться от 0 до N, где N - максимальное количество символов в строке. Значение N определяется объвлением типа STRING [N] и может быть любой константой порядкового типа, но не больше 255. Турбо-Паскаль раз- решает не указывать N, и в этом случае длина строки принимается максимально возможной, а именно N=255. Строка в Турбо-Паскале трактуется как цепочка символов. К лю- бому символу в строке можно обратиться точно так же, как к эле- менту одномерного ‘массива ARRAY [0..N] OF CHAR. Например: var st : string; if st [5] = 'A' then ... Самый первый байт в строке имеет индекс 0 и содержит теку- щую длину строки. Первый значащий символ строки занимает вто- рой байт и имеет индекс 1. Над длиной строки можно осуществ- лять необходимые действия и таким способом изменять длину, на- пример: var st str ing [10] ; i : word; i := 10; while st [i] = ’ ’ do begin dec(i) ; 102
st[0] := chr(i) end; Значение ORD(st[0]), т.е. текущую длину строки, можно полу- чить и с помощью функции LENGTH(st), например: while (length(st) <> 0) and (st[length(st)] = ' ’) do st [0] := chr(length(st)-l) I К строкам можно применять операцию "+" - сцепление, напри- мер: st := 'а’ + 'b'; st := st + 'с'; {st содержит abc} Если длина сцепленной строки превысит максимально допусти- мую длину N, то "лишние" символы отбрасываются. Следующая программа, например, напечатает символ 1: var st: str i ng [1] ; begin st:=’123’; writeln(st) end. Все остальные действия над строками и символами реализуются с помощью встроенных процедур и функций: CONCAT(S1 [,S2, ... ,SN]) - функция типа STRING; возвращает строку, представляющую собой сцепление строк-параметров SI, S2, ... , SN. COPY(ST, INDEX, COUNT) - функция типа STRING; копирует из строки ST COUNT символов начиная с символа с номером INDEX. DELETE(ST, INDEX, COUNT) - процедура; удаляет COUNT сим- волов из строки ST начиная с символа с номером INDEX. INSERT(SUBST, ST, INDEX) - процедура; вставляет подстроку SUBST в строку ST начиная с символа с номером INDEX. LENGTH(ST) - функция типа INTEGER; возвращает длину стро- ки ST. POS(SUBST, ST) - функция типа INTEGER; отыскивает в строке ST первое вхождение подстроки SUBST и возвращает номер пози- ции, с которой она начинается; если подстрока не найдена, возвра- щается нуль. STR(X [:WIDTH [:DECIMALS]], ST) - процедура; преобразует чис- ло X типа REAL или INTEGER в строку символов ST так, как это делает процедура WRITELN перед выводом; параметры WIDTH и DECIMALS, если они присутствуют, задают формат преобразования: WIDTH определяет общую ширину поля, выделенного под соответ- ствующее символьное представление вещественного или целого чис- ла X, a DECIMALS - количество символов в дробной части (параметр DECIMALS допускается использовать только в том случае, когда X - вещественное число).Подробнее о параметрах WIDTH и DECIMALS см. на с. 123. VAL(ST, X, CODE) - процедура; преобразует строку символов ST во внутреннее представление целого или вещественного числа; па- раметр CODE содержит нуль, если преобразование прошло успеш- но, в противном случае он содержит номер позиции в строке ST, 103
где обнаружен ошибочный символ; ведущие пробелы в строке ST должны отсутствовать. UPCASE(CH) - функций типа CHAR ; возвращает символ СН в верхнем регистре, если он определен для него, либо сам символ СН, если для него нет верхнего регистра. Например, UPCASE (’а’) вернет А, в то время как UPCASE (’2’) вернет 2. Примеры: var х : real; у : integer; strstl: string; st:=concat(’12','345’); {строка st содержит 12345} stl:=copy(st,3,length(st)-2); { stl содержит 345} insert(, stl, 2); {строка stl содержит 3-45} delete(st,pos('2',st),3); {строка st содержит 15} str(pi:6:2,st); {строка st содержит 3.14} stl:='3,1415’; val (x,stl,y); {у содержит 2, x остался без изменения} Операции отношения = , о, >, <, >=, <= выполняются над двумя строками посимвольно, слева направо с учетом внутрен- ней кодировки символов (см. табл.7). Если одна строка меньше дру- гой по длине, недостающие символы короткой строки заменяются значением CHR(O). Следующие операции отношения дадут значение TRUE : 'Turbo1 < 'Turbo-Pascal' ’А’ <> ’IFF’ 'ПАскаль' < 'Паскаль' 5.4. СОВМЕСТИМОСТЬ И ПРЕОБРАЗОВАНИЕ ТИПОВ Как уже неоднократно отмечалось, Турбо-Паскаль - это типизиро- ванный язык. Ё нем строго соблюдается концепция типов, в соот- ветствии с которой все применяемые в языке операции определены только над операндами совместимых типов. При обсуждении опера- ций над вещественными данными мы уже затрагивали проблему совместимости вещественных и целых типов. Аналогичные пробле- мы возникают при операциях над строками разной длины, строка- ми и символами и т.д. Ниже приводится более полное определение совместимости типов. Два типа считаются совместимыми, если: - оба они есть один и тот же тип; - оба они вещественные; - оба они целые; - один тип есть тип-диапазон второго типа; - оба они являются типами-диапазонами одного и того же базо- вого типа; - оба они являются множествами, составленными из элементов одного и того же базового типа; - оба они являются упакованными строками (определены с 104
предшествующим словом PACKED) одинаковой максимальной дли- ны; - один тип есть тип-строка, а другой - тип-строка, упакованная строка или символ; - один тип есть любой указатель, а другой - нетипизированный указатель; - оба они есть процедурные типы с одинаковым типом резуль- тата (для типа-функции), одинаковым количеством параметров и одинаковым типом взаимно соответствующих параметров. Особое значение приобретает совместимость типов в операторах присваивания. Пусть Т1 - тип переменной, а Т2 - тип выражения, т.е. выполнется присваивание Т1 := Т2. Это присваивание возможно в следующих случаях: - Т1 и Т2 есть один и тот же тип и этот тип не относится к файлам, массивам файлов, записям, содержащим поля-файлы, мас- сивам таких записей; - Т1 и Т2 являются совместимыми порядковыми' типами и зна- чение Т2 лежит в диапазоне возможных значений Т1; - Т1 и Т2 являются вещественными типами и' значение. Т2 ле- жит в диапазоне возможных значений Т1; - Т1 - вещественный тип и Т2 - целый тип; - Т1 - строка и Т2 - символ; - Т1 - строка и Т2 - упакованная строка; - Т1 и Т2 - совместимые упакованные строки; - Т1 и Т2 - совместимые множества и все члены Т2 принадле- жат множеству возможных значений Т1; - Т1 и Т2 - совместимые указатели; - Т1 и Т2 - совместимые процедурные типы. В Турбо-Паскалевой программе данные одного типа могут преоб- разовываться в данные другого типа. Такое преобразование может быть явным или неявным. При явном преобразовании типов используются вызовы специ- альных функций преобразования, аргументы которых принадлежат одному типу, а значение - другому. Таковыми являются уже рас- смотренные функции ORD, TRUNC, ROUND, CHR. В гл. 7 описы- вается функция PTR, преобразующая четырехбайтный целочислен- ный аргумент к типу-указателю. В Турбо-Паскале может использоваться и более общий механизм преобразования типов, согласно которому преобразование достигает- ся применением идентификатора (имени) стандартного типа или определенного пользователем типа в качестве идентификатора функ- ции преобразования к выражению преобразуемого типа (так называ- емое автоопределенное преобразование типов). Например, допусти- мы следующие вызовы функций: type МуТуре = (a, b, с, d); МуТуре (2) integer (*D') pointer (longint(a) + $FF) 105
ci>ar (127 mod с) byte (к) При автоопределенном преобразовании типа выражения может Произойти изменение длины его внутреннего представления (длина может увеличиться или уменьшиться). В Турбо-Паскале определен и еще один явный способ преобра- зования данных: в ту область памяти, которую занимает перемен- ная некоторого типа, можно поместить значение выражения другого типа, если только длина внутреннего пресдтавления вновь размеща- емого значения в точности равна длине внутреннего представления переменной. С этой целью вновь используется автоопределенная функция преобразования типов, но уже в левой части оператора присваивания: type byt = array [1..2] of byte; int = array [1..2] of integer; rec = record x, у : integer end; var vbyt : byt; vint : int; vrec : rec; begin byt (vint [1] ) [2] := 0; int (vrec) [1] := 256 end. Неявное преобразование типов возможно только в двух случаях: - в выражениях, составленных из вещественных и целочислен- ных переменных, последние автоматически преобразуются к веще- ственному типу, и все выражение в целом приобретает веществен- ный тип; - одна и та же область памяти попеременно трактуется как со- держащая данные то одного, то другого типа (совмещение в памя- ти данных разного типа). Совмещение данных может произойти при использовании запи- сей с вариантными полями (см. 5.2.2), типизированных указателей, содержащих одинаковый адрес (см. гл. 7), а также при явном раз- мещении данных разного типа в одной и той же области памяти. В последнем случае переменная описывается с зарезервированным словом ABSOLUTE, за которым помещается либо абсолютный ад- рес, либо идентификатор ранее определенной переменной. Абсолют- ный адрес указывается парой чисел типа WORD, разделенных двое- точием; первое число трактуется как сегмент, второе - смещение ад- реса (см. гл. 7), например: b : byte absolute $0000:$0055; w : longint absolute 128:0; Если за словом ABSOLUTE указан идентификатор ранее опреде- ленной переменной, то происходит совмещение в памяти данных разного типа, причем первые байты внутреннего представления 106
этих данных будут располагаться по одному и тому же абсолютно- му адресу, например: var х : real; , у : array [1..3] of integer absolute, x; В этом примере переменные X и Y будут размещены начиная с одного и того же абсолютного адреса.
Г л а в a 6 ФАЙЛЫ Под файдом понимается либо именованная область внешней па- мяти ПЭВМ (жесткого диска, гибкой дискеты, электронного "вирту- ального" диска), либо логическое устройство - потенциальный источ- ник или приемник информации. Любой файл имеет три характерных особенности. Во-первых, у него есть имя, что дает возможность программе работать одновре- менно с несколькими файлами. Во-вторых, он содержит компонен- ты одного типа. Типом компонентов может быть любой тип Турбо- Паскаля, кроме файлов. Иными словами, нельзя создать "файл файлов". В-третьих, длина вновь создаваемого файла никак не огова- ривается при его объявлении и ограничивается только емкостью ус- тройств внешней памяти. Файловый тип или переменную файлового типа можно задать одним из трех способов: <имя> = FILE OF <тип>; <имя> = TEXT; <имя> = FILE; . Здесь < имя > - имя файлового типа или файловой переменной (правильный идентификатор); FILE, OF, TEXT - кодовые слова (англ.-. файл, из, текст); <тип> - любой тип Турбо-Паскаля, кроме файлов. Например: type product = record name : string; code : word; cost : comp end; text80 = file of string[80]; var fl : file of char; f2 : text; f3 : file; f4 : text80; f5 : f ile of product; 108
В зависимости от способа объявления можно выделить три вида файлов: - типизированные (задаются предложением FILE OF ...); - текстовые (задаются предложением TEXT); - нетипизированные (задаются предложением FILE). В наших примерах Fl, F4 и F5 - типизированные файлы, F2- текстовый файл, ‘ F3 - нетипизированный файл. Вид файла, вообще говоря, определяет способ хранения информации в файле. Однако в Турбо-Паскале нет средств контроля вида ранее созданных файлов. При объявлении уже существующих файлов программист должен сам следить за соответствием вида объявления характеру файла. 6.1. ДОСТУП К ФАЙЛАМ Любой Турбо-Паскалевой программе доступны два предваритель- но объявленных файла со стандартными файловыми переменными: INPUT - для чтения данных с клавиатуры и OUTPUT - для вывода на экран. Стандартный Паскаль требует обязательного упоминания этих файлов в заголовке программы, например, так: PROGRAM NameOfProgram(input,output); В Турбо-Паскале это необязательно, вот почему заголовок програм- мы можно опускать. Любые другие файлы, а также логические устройства становятся доступны программе только после выполнения особой процедуры открытия файла (логического устройства). Эта процедура заключает- ся в связывании ранее объявленной файловой переменной с име- нем .существующего или вновь создаваемого файла, а также в указа- нии направления обмена информации: чтение из файла или запись в него. Связывание файловой переменной с именем файла осуществля- ется обращением к встроенной процедуре ASSIGN: ASSIGN (<ф.п.>, <имя файла или л.у.>). Здесь < ф.п. > - файловая переменная (правильный идентифика- тор, объявленный в программе как перемен- ная файлового типа); < имя файла или л.у. > - текстовое выражение, содержащее имя файла или логическое устройство. Если имя файла задается в виде пустой строки, например, ASSIGN(f,”), то файловая переменная связывается со стандартным файлом INPUT или OUTPUT. 6.1.1. Имена файлов Имя файла - это любое выражение строкового типа, которое строится по правилам определения имен в дисковой операционной системе (ДОС) ПЭВМ: - имя содержит до восьми разрешенных символов (разрешенные символы - это произвольные символы с кодами от 33 до 255, кроме символов пробел, точка, запятая, двоеточие, звездочка, знак вопроса, 109
обратная косая черта, а также символ "Забой" - код 127 по стандарту ASCII); - имя начинается с любого разрешенного символа; - за именем может следовать расширение - последовательность до трех разрешенных символов; расширение, если оно есть, отделя- ется от имени точкой. Перед именем может ставиться так называемый путь к файлу - имя диска и/или имя текущего каталога и имена каталогов выше- стоящих уровней. Имя диска содержит одну из латинских букв A...Z, после кото- рой ставится двоеточие. Имена А: и В: относятся к дисковым нако- пителям на гибких дискетах, имена С:, D: и т.д. - к жестким дис- кам. Эти имена могут относиться также к одному или нескольким виртуальным дискам, созданным в операционной памяти ПЭВМ специальной командой VDISK в ходе выполнения файла автоуста- новки параметров ДОС CONFIG.SYS. Если имя диска не указано, подразумевается устройство по умолчанию - то, которое было установление в операционной систе- ме перед началом работы программы. За именем диска может указываться имя каталога, содержащего файл. Если имени каталога предшествует обратная косая черта, то путь к файлу начинается из корневого каталога, если черты нет- из текущего каталога, установленного в системе по умолчанию. За именем каталога может следовать одно или несколько имен катало- гов нижнего уровня. Каждому из них должна предшествовать обрат- ная косая черта. Весь путь к файлу отделяется от имени файла об- ратной косой чертой, например: var finp : text; fout : file of string; const name = 'c:\dir\subdir\out.txt'; assign(finp, '123.dat'); assign(fout, name); Максимальная длина имени вместе с путем - 79 символов. 6.1.2. Логические устройства Стандартные аппаратные средства ПЭВМ, такие как клавиатура, экран терминала, печатающее устройство (принтер) и коммуникаци- онные каналы ввода-вывода, определяются в Турбо-Паскале специ- альными именами, которые называются логическими устройствами. Все они в Турбо-Паскале рассматриваются как потенциальные ис- точники или приемники текстовой информации. CON означает консоль - клавиатуру или экран терминала. Турбо- Паскаль устанавливает различие между этими физическими устрой- ствами по направлению передачи данных: чтение данных возможно 110
только с клавиатуры, а запись данных - только на экран. Таким об- разом, с помощью логического устройства CON нельзя, например, прочитать данные с экрана ПЭВМ, хотя такая аппаратная возмож- ность существует (см. гл. 12). Ввод с клавиатуры буферируется: символы по мере нажатия на клавиши помещаются в специальный строковый буфер, который передается программе только после нажатия на клавишу "Ввод". Сам символ "Ввод" (код 13) в буфер не помещается и программе не передается. Буферизация ввода обеспечивает возможность редак- тирования вводимой строки стандартными средствами ДОС. При вводе символов осуществляется их эхо-повтор на экране ПЭВМ. В Турбо-Паскале можно прочитать любой символ клавиатуры, в том числе и "Ввод", сразу после нажатия на соответствующую клавишу без эхо-повтора (см. гл. 14). PRN - логическое имя принтера. Если к ПЭВМ подключено не- сколько принтеров, доступ к ним осуществляется по логическим именам LPT1, LPT2 и LPT3. Имена PRN и LPT1 первоначально си- нонимы. Средствами ДОС можно переназначить имя PRN на лю- бое другое логическое устройство, способное принимать информа- цию. Стандартный библиотечный модуль PRINTER, входящий в биб- лиотеку TURBO.TPL, объявляет имя файловой переменной LST и связывает его с логическим устройством LPT1. Это дает возмож- ность использовать простое обращение к принтеру. Например, про- грамма PROGRAM Print; Uses Printer; BEGIN writeln(LST, 'Привет, мир!') END. выведет на принтер фразу "Привет, мир!", а все необходимые опе- рации по открытию логического устройства выполнит библиотеч- ный модуль PRINTER (подробности работы с модулями см. в гл.10). AUX - логическое имя коммуникационного канала, который обычно используется для связи ПЭВМ с другими машинами. Ком- муникационный канал может осуществлять и прием, и передачу данных, однако в Турбо-Паскалевой программе в каждый момент времени ему можно назначить только одну из этих функций. Как правило, в составе ПЭВМ имеются два коммуникационных канала, которым даются имена логических устройств СОМ1 и COM2. Пер- воначально имена AUX и СОМ1 синонимы. NUL - логическое имя "пустого" устройства. Это устройство чаще всего используется в отладочном режиме и трактуется как устройст- во неограниченной емкости - приемник информации. При обраще- нии к NUL как к источнику информации выдается признак конца файла EOF. Ill
Связывание логического устройства с файловой переменной осу- ществляется процедурой ASSIGN, например: var fi,fo : text; ass ign(fi, 'AUX ’); assign(fo,'LPT2 '); Турбо-Паскаль никогда не связывает имена логических устройств с дисковыми файлами, и в этом смысле можно считать эти имена зарезервированными. Иными словами, нельзя, например, обратиться к дисковому файлу с именем PRN - Турбо-Паскаль всегда интерпре- тирует такой запрос как обращение к принтеру. 6 Л 3^ Инициация файла Инициировать файл означает указать для этого файла направле- ние передачи данных. В Турбо-Паскале можно открыть файл толь- ко для чтения, только для записи, а также для чтения и записи информации одновременно. Для чтения файл инициируется с помощью встроенной проце- дуры: RESET (<ф.п.>); Здесь <ф.п.> - файловая переменная, связанная ранее процеду- рой ASSIGN с уже существующим файлом или логическим устройством - источником информации. При выполнении этой процедуры дисковый файл или логиче- ское устройство подготавливается к чтению информации. Внутрен- няя Турбо-Паскалевая переменная-указатель, связанная с этим фай- лом, указывает на начало файла,, т.е. на компонент с порядковым номером 0. Если делается попытка инициировать чтение из несуществующе- го файла или логического устройства PRN, возникает ошибка перио- да исполнения, которая может быть сообщена программе с по- мощью встроенной функции IORESULT типа WORD, которая в этом случае имеет ненулевое значение. Например, следующий фрагмент программы позволяет установить, существует ли требуе- мый файл на диске: var f .: file of char; assign(f,'myf ile.dat’ )\ {$!-} {отключить контроль ошибок ввода-вывода} reset(f); {$!+} {включить контроль ошибок ввода-вывода} if lOResult <> 0 then .... {файл не существует} else .... {файл существует} 112
В этом фрагменте с помощью директивы компилятора {$!-} от- ключается автоматический контроль ошибок ввода-вывода. Если это- го не сделать, то отсутствие файла 'приведет к аварийному заверше- нию программы. Турбо-Паскаль допускает к типизированным файлам, открытым с помощью процедуры RESET (т.е. для чтения информации), обра- щаться с помощью процедуры WRITE (т.е. для записи информа- ции). Такая возможность позволяет легко обновлять ранее создан- ные типизированные файлы и при необходимости расширять их. Встроенная процедура REWRITE (<ф.п.>) инициирует запись информации в файл или логическое устройство, связанное ранее с файловой переменной <ф.п.>. Процедурой REWRITE нельзя ини- циировать запись информации в ранее существовавший дисковый файл: при выполнении этой процедуры старый файл уничтожается и никаких сообщений об этом в программу не передается. Новый файд подготавливается к приему информации и его указатель уста- навливается в нуль. Встроенная процедура APPEND (<ф.п.>) инициирует запись в ранее существовавший текстовый файл для его расширения - указа- тель файла устанавливается в его конец. Не следует забывать, что процедура APPEND применима только к текстовым файлам, то есть объявленным предложением TEXT (см, выше). Процедурой APPEND нельзя инициировать запись в типизированный или нети- пизиро&анный файл. Если текстовый файл ранее уже был открыт с помощью RESET или REWRITE, использование процедуры APPEND приведет к закрытию этого файла и открытию его вновь, но уже для добавления записей. 6.2. ПРОЦЕДУРЫ И ФУНКЦИИ ДЛЯ РАБОТЫ С ФАЙЛАМИ Ниже- описываются процедуры и функций, которые можно ис- пользовать с файлами любого вида. Специфика работы с типизиро- ванными, текстовыми и нетипизированными файлами рассматрива- ется в следующих разделах. Процедура CLOSE. Закрывает файл, однако связь файловой пере- менной с именем файла, установленная ранее процедурой ASSIGN, сохраняется. Формат обращения CLOSE ( <ф.п. > ) При создании нового или расширении старого файла процедура обеспечивает сохранение в файле всех новых записей и регистра- цию файла в каталоге. Функции процедуры CLOSE выполняются автоматически по отношению ко всем открытым файлам при нор- мальном завершении Турбо-Паскалевой программы. Поскольку связь файла с файловой переменной сохраняется, файл можно по- вторно открыть без дополнительного использования процедуры ASSIGN. Процедура RENAME. Переименовывает файл; формат обращения RENAME (<ф.п.>, < новое имя>) 113
Здесь < новое имя > - строковое выражение, содержащее новое имя файла. Перед выполнением процедуры необходимо закрыть файл, если он ранее был открыт процедурами RESET, REWRITE или APPEND. Процедура ERASE. Уничтожает файл; формат обращения ERASE (<ф.п.>) Перед выполнением процедуры необходимо закрыть файл, если он ранее был открыт процедурами RESET, REWRITE или APPEND. Следующий фрагмент программы показывает, как можно ис- пользовать процедуры RENAME и CLOSE при работе с файлами. Предположим, что требуется отредактировать файл, имя которого содержит переменная NAME. Перед редактированием необходимо убедиться, что нужный файл имеется на диске, и переименовать его-заменить расширение этого файла на .ВАК (страховочная ко- пия). Если файл с таким расширением уже существует, то этот файл надо стереть. var fi : text; {исходный файл'} fo : text; {отредактированный файл} name : string; name_bak: string; k, i : word; const bak = ’.bak'; ' t { получить в name_bak имя файла с расширением .ВАК: } k := pos('.’,name); if к = 0 then к := length(name) + 1; name_bak := copy(name,1,к-1) + bak; { проверить существование исходного файла: } assign(fi,name); {$!-} reset(fi); if lOResult <> 0 then halt; { файл не существует } close(fi); { проверить существование .ВАК-файла: } assignffo,name_bak); reset(fo); {$!+} if lOResult - 0 then t begin {файл .ВАК существует:} close(fo); erase(fo) end; { проверки закончены, подготовка к работе: } rename(fi,name_bak); reset(fi); ass ign(fo,name); rewrite(fo); 114
Обратите внимание: проверка на существование .ВАК-файла в данном примере необходима, так как процедура RENAME(fi,name-bak) приведет к возникновению ошибки в случае, если такой файл существует. Процедура FLUSH. Очищает внутренний буфер файла и, таким образом, гарантирует сохранность всех последних изменений файла на диске. Формат обращения FLUSH ( <ф.п.> ) Любое обращение к файлу в Турбо-Паскале осуществляется че- рез некоторый внутренний буфер, что необходимо для согласования внутреннего представления файлового компонента - записи с приня- тым в ДОС форматом хранения данных на диске. В ходе выполне- ния процедуры FLUSH все новые записи будут действительно запи- саны на диск. Процедура игнорируется, если файл был иницииро- ван для чтения процедурой RESET. Ценность этой процедуры сомнительна, так как все ее функции реализуются процедурой CLOSE. Функция EOF ( <ф.п.> ): BOOLEAN. Логическая функция, тес- тирующая конец файла. Возвращает TRUE, если файловый указатель стоит в конце фай- ла. При записи это означает, что очередной компонент будет добав- лен в конец файла, при чтении - что файл исчерпан. Процедура CHDIR. Изменение текущего каталога; формат обра- щения CHDIR ( <путь> ) Здесь < путь >-строковое выражение, содержащее путь к устанав- ливаемому по умолчанию каталогу. Процедура GETDIR. Позволяет определить имя текущего катало- га (каталога по умолчанию); формат обращения: GETDIR ( < ус-во >, < каталог > ) Здесь < ус-во >-выражение типа WORD, содержащее номер уст- ройства: 0 - устройство по умолчанию, 1 - диск А, 2 - диск В и т.д. < каталог >-переменная типа STRING, которая определяет путь к текущему каталогу на указанном диске. Процедура MKDIR. Создает новый каталог на указанном диске; формат обращения: MKDIR ( < каталог > ) Здесь < каталог >-выражение типа STRING, задающее путь к ката- логу. Последним именем в пути, т.е. именем вновь создаваемого ката- лога, не может быть имя уже существующего каталога. Процедура RMDIR. Удаляет каталог; формат обращения RMDIR ( < каталог > ) Удаляемый каталог должен быть пустым, т.е. не содержать фай- лов или имен каталогов нижнего уровня. 115
Функция IORESULT: WORD. Возвращает условный признак по- следней операции ввода-вывода. Если операция завершилась успешно, функция возвращает нуль. Следует помнить, что IORESULT становится доступной только при отключенном автоконтроле ошибок ввода-вывода. Директива компи- лятора {$!-} отключает, а {$! + } включает автоконтроль. Если авто- контроль отключен и операция ввода-вывода привела к возникнове- нию ошибки, устанавливается флаг ошибки и все последующие об- ращения к вводу-выводу блокируются, пока не будет вызвана функ- ция IORESULT. Ряд полезных файловых процедур и функций становится до- ступным при использовании библиотечного модуля DOS, входящего в стандартную библиотеку TURBO.TPL. Эти процедуры и функции указаны ниже. Следует помнить, что доступ к ним возможен толь- ко после объявления USES DOS в начале программы (подробнее о работе с модулями см. гл. 10). Функция DISKFREE ( <диск> ): LONGINT. Возвращает объем в байтах свободного пространства на указанном диске. В этой функ- ции <диск> - номер диска: 0-устройство по умолчанию, 1-диск А, 2-диск В и т.д. Функция возвращает значение -1, если указан номер несуществу- ющего диска. Функция DISKSIZE ( <диск> ): LONGINT. Возвращает полный объем диска в байтах; <диск> - см. выше. Функция возвращает значение -1, если указан номер несуществу- ющего диска. Процедура FINDFIRST. Возвращает атрибуты первого из файлов, зарегистрированных в указанном каталоге; формат обращения FINDFIRST ( < маска >, <атр-ты>, <имя> ) Здесь < маска > - строковое выражение, содержащее маску файла; <атр-ты> - выражение типа BYTE, содержащее уточнение к маске; < имя >-переменная типа SEARCHREC, в которой будет воз- вращено имя файла. При формировании маски файла используются следующие два ключа выбора группы файлов: * означает, что на месте этого символа может стоять сколько угодно разрешенных символов имени или расширения файла; ? означает, что на месте этого символа может стоять один из разрешенных символов, например: *.* - выбирает все файлы из каталога; d*.* - выбирает все файлы с именами, начинающимися на d, (dl.pas, dcl2345, d.dat и т.д.); a?.dat - выбирает имена файлов типа aO.dat, az.dat и т.д. Маске может предшествовать путь, например, c:\dir\subdir\*.pas означающий указание выбирать все файлы с расширением .PAS из 116
каталога SUBDIR, находящемся на диске С: ; каталог SUBDIR заре- гистрирован в каталоге верхнего уровня DIR, который, в свою оче- редь, входит в корневой каталог. Байт <атр-ты> содержит биты, уточняющие; к каким именно файлам разрешен доступ при обращении ,к процедуре FINDFIRST. Вот как объявляются файловые атрибуты в модуле DOS: const Readonly = $01; {только чтение} Hidden = $02; {скрытый файл} SysFile = $04; {системный файл} VolumelD = $08; {идентификатор тома} Directory = $10; {имя подкаталога} Archive = $20; {архивный файл} AnyFile = $3F; {любой файл} Комбинацией битов в этом байте можно указывать самые раз- ные варианты, например $06-выбирать все скрытые и/или систем- ные файлы. Результат работы процедуры FINDFIRST возвращается в пере- менной типа SEARCHREC. Этот тип в модуле DOS определяется следующим образом: type SearchRec = record / Fill : array [1..21] of byte; Attr : byte; Time : longint; Size : longint; Name : string[12] end; Здесь Attr - атрибуты файла (см. выше); Time - время создания или последнего обновления файла; возвра- щается в упакованном формате; распаковать параметр можно про- цедурой UNPACKTIME (см.ниже); Size-длина файла, байты; Name - имя и расширение файла. х Для распаковки параметра TIME используется процедура UNPACKTIME(Time:longint; var T:DateTime). В модуле DOS объявлен следующий тип: type DateTime = record year : word; {год в формате . 19XX} month : word; {месяц 1... 12} day : word; {день 1. . .31} hour : word; {час 0...23} min : word; {минуты 0...59} sec : word {секунды 0...59} end; 117
Результат обращения к процедуре FINDFIRST можно проконтро- лировать с помощью функции DOSERROR типа WORD, которая возвращает значения: » О -нет ошибок; 2 - не найден каталог; 18 - каталог пуст (нет указанных файлов). Процедура FINDNEXT. Возвращает имя следующего файла в ка- талоге; формат обращения FINDNEXT ( <сл.файл> ) Здесь <сл.файл> - запись типа SEARCHREC (см. выше), в которой возвращается информация о следующем файле. Приводимая в примере 15 программа выводит на печать список нужных файлов из требуемого каталога. Программа запрашивает маску файлов, выводимых на печать. Здесь можно указать любую маску и при необходимости - путь. По умолчанию (т.е. если в ответ на запрос просто нажать "Ввод’') будут печататься имена4 и размер всех файлов с расширением .PAS из текущего каталога. Пример 15 PROGRAM PrintOfDirectory; Uses DOS, Printer; var Dirinfo : SearchRec; {файловая информация} DirName : string; {имя каталога} Maska : string; {маска файлов} i : integer; {счетчик для выравнивания колонок} Т : DateTime; {время последнего обновления файла} const mas - ’*.pas’; {маска по умолчанию} LF = #10; {символ перевода строки} BEGIN write('Маска файлов (по умолчанию ,'pas,') ? '); Маска : = ' ' ; readin(Maska); if Maska = ’’ then Maska := mas; GetDir(0, DirName); writeln(LST, LF, '===== DirName,' =====',#10); FindFirst(Maska, Archive, Dirinfo); whi.le DOSError = 0 do begin UnPackTime(Dirinfo.time, T); write(LST, Dirinfo.name); i := 0; while length(Dirlnfo.name) + i < 12 do begin write(LST,' ’); inc(i) end; with T do writeln(LST, Dirinfo.size:6, 118
day:4,’-', month:2,'-'.year:2, hour:4,’:’,min:2,’:’,sec:2); FindNext(Di rlnfo) end; writeln(LST, LF,'Емкость диска ' .DiskSize(O):8,' байт'); writeln(LST, 'Из них свободно'.DiskFree(O):8,' байт') END. Процедура GETFTIME. Возвращает время создания или послед- него обновления файла. Обращение: GETFTIME ( <ф.п.>, < время > ) Здесь < ф.п. > - файловая переменная; < время >-переменная типа LONGINT, в которой возвращается время в упакованном формате. Распаковать время в запись типа DATETIME можно с помощью процедуры UNPACKTIME (см. выше). Процедура SETFTIME. Устанавливает новую дату создания или обновления файла. Формат обращения: SETFTIME ( <ф.п.>, < время > ) Здесь < время > - время и дата в упакованном формате. Упаковать запись типа DATETIME в переменную типа LONGINT можно процедурой PACKTIME (var T:DateTime; var Timedongint) (описание типа DATETIME см. выше). Процедура GETFATTR. Позволяет получить атрибуты файла; формат обращения: GETFATTR ( <ф.п.>, <атр-ты> ) Здесь <атр-ты> - переменная типа WORD, в младшем байте кото- рой возвращаются атрибуты файла (см. выше FINDFIRST). Процедура SETFATTR. Позволяет установить атрибуты файла; формат обращения SETFATTR ( <ф.п.>, <атр-ты> ) Функция FSEARCH типа PATHSTR. Ищет файл в списке ката- логов; вызов функции FSEARCH ( <имя>, <сп.каталогов> ) Здесь <имя>-имя отыскиваемого файла (строковое выраже- ние или переменная типа PATHSTR=STRING[79]; имени может предшествовать путь); <сп.каталогов> - список каталогов, в которых ищется файл (строковое выражение или переменная типа STRING); имена каталогов разделяются точкой с запятой. Результат поиска возвращается функцией FSEARCH в виде стро- ки типа PATHSTR = STRING[79], В строке содержится путь и имя файла, если поиск был успешным, в противном случае возвращает- ся пустая строка. Следует учесть, что поиск файла всегда начинается в текущем каталоге и только после этого продолжается в тех, что перечислен- ны в <сп.каталогов>. Если файл обнаружен, дальнейший поиск прекращается, даже если часть каталогов осталась непросмотренной. 119
В частности, если файл зарегистрирован в текущем каталоге, он "за- слонит" собой одноименные файлы в других каталогах. Пусть, например, на диске имеется файл \SUBDIR\MYFILE.PAS: Тогда, если текущий каталог - корневой, обращение FSEARCH (’MYFILE,PAS’,’\SUB; \SUBDIR’) вернет строку \SUBDIR\MYFILE.PAS, .а обращение FSEARCH (’MYFILE.PAS’,’\SUB’) вернет пустую строку. Однако если текущим установлен каталог SUBDIR, то в обоих случаях вернется строка MYFILE.PAS (когда файл находится в текущем каталоге, в выходной строке не указыва- ется путь к нему). Процедура FSPLIT. "Расщепляет" имя файла, т.е. возвращает в > качестве отдельных параметров путь к файлу, его имя и расшире- i ние; формат обращения FSPLIT (<файл>, <путь>, <имя>, < расширение >) Здесь < файл> - строковое выражение, содержащее спецификацию файла (имя с расширением и, возможно, с предшествующим пу- тем); < путь >-переменная типа DIRSTR=STRING[67], в которой воз- вращается путь к файлу; < имя> - переменная типа NAMESTR=STRING[8], в которой воз- вращается имя файла; < расширение >-переменная типа EXTSTR=STRING[4], в ко- торой возвращается расширение с предшествующей ему точкой. Процедура не проверяет наличие на диске указанного файла. В качестве входного параметра может использоваться переменная типа PATHSTR = STRING[79]. Функция FEXPAND типа PATHSTR. Дополняет файловое имя до полной спецификации, т.е. с указанием устройства и пути; фор- мат вызова FEXPAND ( <файл> ) Здесь < файл >-строковое выражение или переменная типа PATHSTR. Функция не проверяет наличие указанного файла на диске, а просто дополняет имя файла недостающими параметрами - теку- щим устройством и путем к текущему каталогу. Результат возвра- щается в строке типа PATHSTR = STRING[79]. 6.3. ТЕКСТОВЫЕ ФАЙЛЫ Текстовые файлы объявляются предложением TEXT и предназ- начены для хранения текстовой информации. Именно в такого ти- па файлах хранятся, например, исходные тексты Турбо-Паскалевых программ. Компоненты (записи) текстового файла могут иметь пе- ременную длину, что существенно влияет на характер работы с ни- ми. Текстовый файл трактуется в Турбо-Паскале как совокупность строк переменной длины. К каждой строке возможен лишь после- 120
довательный доступ, начиная с первой. При создании текстового файла в конце каждой записи (строки) ставится специальный при- знак EOLN (от англ. End Of LiNe-конец строки), а в конце всего файла - признак EOF (от англ. End Of File - конец файла). Эти при- знаки можно протестировать одноименными логическими функция- ми (см. ниже), но можно также использовать, следующие системные соглашения: EOLN - это последовательность кодов ASCII 13 (CR) и 10 (LF); EOF-это код ASCII 26 (Ctrl-Z) . Для доступа к записям применяются процедуры READ, READLN, WRITE, WRITELN. Все они отличаются возможностью об- ращения к ним с переменным числом фактических параметров, в качестве которых могут использоваться символы, строки и числа. Первым параметром в любой из этих процедур может стоять фай- ловая переменная. В этом случае осуществляется обращение к дис- ковому файлу или логическому устройству, связанному с перемен- ной процедурой ASSIGN. Если файловая переменная не указана, происходит обращение к стандартным файлам INPUT и OUTPUT. Процедура READ. Обеспечивает ввод символов, строк и чисел; формат обращения: READ ( <ф.п.>, <сп.ввода> ) или READ ( <сп.ввода> ) Здесь < сп.ввода >-список ввода: последовательность из одной или более переменных типа CHAR, STRING, а также любого целого или вещественного типа. При обработке переменных типа CHAR выполняется чтение од- ного символа из файла и присваивание считанного значения пере- менной. Если перед выполнением чтения указатель файла достиг конца очередной строки, то результатом чтения будет символ CR (ASCII код 13), а если достигнут конец файла, то символ EOF (код 26). С клавиатуры символ CR вводится при нажатии на "Ввод", а символ EOF - при одновременном нажатии CTRL и Z. При вводе переменных типа STRING количество считанных процедурой и помещенных в строку символов равно максимальной длине строки, если только раньше не встретились символы CR или EOF. В этом случае сами символы CR и EOF в строку не помеща- ются. Если количество- символов во входном потоке больше макси- мальной длины строки, "лишние" символы до конца строки отбра- сываются, а новое обращение к READ вернет пустую строку. Таким образом, процедура READ не в состоянии прочесть последователь- ность строк, т.е. первая строка будет прочитана нормально, а все последующие окажутся пустыми. Для ввода последовательности строк нужно использовать процедуру READLN (см. ниже). При вводе числовых переменных процедура READ вначале вы- деляет подстроку во входном потоке по следующему правилу: все ведущие пробелы, символы табуляции и маркеры конца строк EOLN пропускаются; после выделения первого значащего символа, наоборот, любой из перечисленных символов или символ EOF слу- жат признаком конца подстроки. Выделенная таким образом под- 121
строка затем рассматривается как символьное представление число- вой константы соответствующего типа и преобразуется во внутрен- нее представление, а полученное значение присваивается перемен- ной. Если в подстроке был нарушен требуемый формат представле- ния численной константы, возникает ошибка ввода-вывода. Если при пропуске ведущих пробелов встретился символ EOF, перемен- ная получает значение 0. Отметим, что в Турбо-Паскале не предус- мотрен ввод шестнадцатиричных констант. При использовании процедуры READ применительно к стан- дартному файлу INPUT, т.е. при вводе с клавиатуры, символьные строки запоминаются в буфере, который передается процедуре толь- ко после нажатия на "Ввод" и используется ею как источник ин- формации. Это позволяет редактировать данные при их вводе. Для редактирования используются следующие клавиши: "Забой", Ctrl-H, перевод курсора влево - стирают символ слева от курсора; перевод курсора вправо - восстанавливает символ за символом предыдущую строку ввода; Ctrl-Z, "Ввод" - завершает ввод по процедуре, READ; оставшиеся "лишние" символьные параметры принимают значение CHR(26), строки возвращаются пустыми, а численные переменные остаются без изменения. Максимальная длина буфера ввода при работе с клавиатурой со- ставляет 127 символов. Ввод с клавиатуры по процедуре READ со- провождается эхо-повтором вводимых символов на экране ПЭВМ (ввод без эхо-повтора рассматривается в гл. 14). Процедура READLN. Обеспечивает ввод символов, строк и чисел; идентична процедуре READ за исключением того, что после считы- вания последней переменной оставшаяся часть строки до маркера EOLN пропускается, так что следующее обращение к READLN или READ начнется с первого символа новой строки. Кроме того, эту процедуру можно вызвать без параметра <сп.ввода> (см. процеду- ру READ), что приведет к пропуску всех символов текущей строки вплоть до EOLN. Если процедура используется для чтения с клавиатуры, нажатие на "Ввод” отобразится на экране как последовательность CR + LF и курсор будет помещен в начало следующей строки, в то время как в процедуре READ эхо-повтором клавиши "Ввод" является символ CR и курсор помещается в начало текущей строки. Процедура WRITE. Обеспечивает вывод информации в текстовый файл или передачу ее на логическое устройство; формат обращения WRITE (<ф.п.>, <сп.вывода>) или WRITE (<сп.вывода>) Здесь < сп .вывода >-список вывода, содержащий одно или более выражений типа CHAR, STRING, BOOLEAN, а также любого целого или вещественного типа. Файловая переменная <ф.п.>, если она указана, должна быть предварительно описана как переменная типа TEXT и связана с именем файла или логическим устройством процедурой ASSIGN. 122
Если файловая переменная отсутствует, подразумевается вывод в стандартный файл OUTPUT, который обычно связан с экраном ПЭВМ. Любой выводимый параметр может иметь форму OutExpr [ : MinWidth [ : DecPlaces ] ] Здесь OUTEXPR - выводимое выражение; MINWIDTH, DECPLACES - выражения типа WORD (квадратные скобки означают, что заключенные в них параметры могут отсутст- вовать). Подпараметр MINWIDTH, если он присутствует, указывает мини- мальную ширину поля, в которое будет записываться символьное представление значения OUTEXPR. Если символьное представление имеет меньшую длину, чем MINWIDTH, оно будет дополнено слева пробелами; если большую длину, то подпараметр MINWIDTH игно- рируется и выводится необходимое количество символов. Подпараметр DECPLACES задает количество десятичных знаков в дробной части вещественного числа. Он может использоваться только совместно с MINWIDTH и только по отношению к выводи- мому выражению одного из вещественных типов. Если ширина поля вывода не указана, соответствующий пара- метр выводится вслед за предыдущим параметром без какого-либо их разделения. Символы и строки передаются выводному файлу без изменений, но снабжаются ведущими пробелами, если задана ширина поля вы- вода и эта ширина больше требуемой для вывода. Логические выражения в зависимости от их значения выводятся строкой TRUE или FALSE. (Ввод логических констант процедурами READ или READLN не предусмотрен.) Вещественные числа выво- дятся в экспоненциальном формате, если не указан подпараметр DECPLACES, в противном случае выбирается формат представления числа с фиксированной точкой. Экспоненциальный формат пред- ставляет вещественное число в виде -s#.##############E*#### , где - - пробел; s - пробел для положительного и знак для отрицательного числа; # - десятичная цифра; Е - символ десятичного основания; *-знак "+" или в зависимости от знака десятичного порядка числа. Если подпараметр MINWIDTH опущен, принимается его значе- ние по умолчанию 23. Если MINWIDTH меньше 10, считается, что он равен 10. Если подпараметр DECPLACES равен нулю, ни дробная часть числа, ни десятичная точка не выводятся. При отрицательном зна- чении DECPLACES он игнорируется и число выводится в экспонен- циальном формате с учетом MINWIDTH. Если значение DECPLACES больше 18, принимается значение 18. Следует учесть, что при указанном подпараметре DECPLACES вещественное число всегда будет выводиться в формате с фиксированной точкой и тре- 123
буемым количеством знаков в дробной части, даже если значение подпараметра MINWIDTH окажется недостаточным для размещения целой части: в этом случае значение MINWIDTH будет увеличено до необходимого. Когда длина выводимой на экран последовательности символов превышает ширину экрана или созданного на нем окна (см. гл. 12), "лишние" символы переносятся на следующую экранную строку. При заполнении экрана или окна его содержимое сдвигается вверх на одну строку. Процедура WRITELN. Полностью идентична процедуре WRITE за исключением того, что выводимая строка символов завершается кодами CR и LF. При вызове WRITELN допускается опускать пара- метр <сп.вывода>: в этом случае в файл передается маркер EOLN (при выводе на экран это приведет к переводу курсора в начало следующей строки). Простая программа примера 16 иллюстрирует возможности вы- вода вещественных чисел в различных форматах. Программа чита- ет два целых числа, которые затем используются как подпараметры MINWIDTH и DECPLACES при выводе различных вещественных чисел. Для выхода из программы нужно ввести два нулевых значе- ния. Пример 16 PROGRAM UsingWrite; var MinWidth, DecPlaces : word; i : word; a : real; BEGIN repeat write('MinWidth, DecPlaces = ? ’); readln(MinWidth, DecPXaces); if MinWidth <> 0 then begin a := sqr(pi); for i := 1 to 5 do begin writelnf’ exp: a:MinWidth, ' fix: a:MinWidth:DecPlaces); a := sqr(a) end end unt i 1 MinWidth = 0 END. Логическая функция EOLN. Возвращает TRUE, если во входном текстовом файле достигнут маркер конца строки; формат обраще- ния EOLN ( <ф.п.> ) Если параметр <ф.п.> опущен, функция проверяет стандарт- ный файл INPUT. 124
Имеется некоторое отличие в работе функций EOLN и EOF с дисковыми файлами и логическими устройствами. Дело в том, что для логического устройства невозможно предвидеть, каким будет ре- зультат чтения очередного символа. Поэтому при работе с логиче- ским устройством функция EOLN возвращает TRUE, если послед- ним считанным с устройства символом был CR или CTRL-Z, в то время как при чтении с диска TRUE возвращается в случае, если следующим считываемым символом будет CR или CTRL-Z. Анало- гичное различие наблюдается и в функции EOF: для логического устройства TRUE возвращается в случае, если последним символом был CTRL-Z, а при чтении с диска-если следующим считывае- мым символом будет CTRL-Z. Чтобы оценить эти отличия, проде- лайте простой эксперимент: в программе из примера 16 поставьте в качестве условия завершения оператор UNTIL EOF вместо UNTIL MINWIDTH = 0 и сделайте еще один прогон программы. Вы обна- ружите, что после перврго прохода программа перестала выводить приглашение MinWidth, DecPlaces = ?. Логическая функция SEEKEOLN. Пропускает все пробелы и зна- ки табуляции до маркера конца строки EOLN или до первого зна- чащего символа и возвращает TRUE, если маркер обнаружен. Фор- мат обращения SEEKEOLN ( <ф.п.> ) Если параметр <ф.п.> опущен, функция проверяет стандарт- ный файл INPUT. Логическая функция SEEKEOF. Пропускает все пробелы, знаки табуляции и маркеры конца строки EOLN до маркера конца файла или до первого значащего символа и возвращает TRUE, если мар- кер обнаружен. Формат обращения SEEKEOF ( <ф.п.> ) Если параметр <ф.п.> опущен, функция проверяет стандарт- ный файл INPUT. Рассмотрим типичные ситуации, возникающие при работе с тек- стовыми файлами. Прежде всего отметим, что процедура READ прекрасно приспо- соблена к вводу чисел. При обращении к ней за вводом очередного целого или вещественного числа процедура "перескакивает" маркеры конца строк, т.е. фактически весь файл рассматривается ею как од- на длинная строка, содержащая текстовое представление чисел. В сочетании с проверкой конца файла функцией EOF процедура READ позволяет организовать простой ввод массивов данных, на- пример, так: const N = 1000; { максимальная длина ввода } var f : text; m : arra'y [1. . N] of rea 1; i : integer; BEGIN assign(f, ’prog.datr); 125
reset(f); i := 1; while not EOF(f) and (i <= N) do begin read(f,m [i]); inc(i) end; Процедуру READLN удобно использовать при вводе текстовых строк. Программа в примере 17 запрашивает имя текстового файла и организует его печать на принтере с делением на страницы по 50 строк. В начале каждой страницы (кроме первой) выводится ее номер, в конце страницы раздается звуковой сигнал и программа приостанавливает свою работу для заправки принтера. Пример 17 PROGRAM PrintOfTextFile; Uses Printer; var f : text; c : char; s : string; i, p : word; name : string[20]; const bel = #7; FF = #12; BEGIN {файловая переменная} /признак продолжения вывода} {вводимая строка} {счетчик строк} {счетчик страниц} {имя файла} {звонок} {прогон страницы} writeln('=== Распечатка текстового файла -=='); writein; wr ite(' Имя файла = ’); {ввести имя файла; если пустой ввод - завершить программу} readln(name); if name <> ’' then {ввод непустой; проверить существование файла} begin assign(f, name); {$!-} reset(f); {$14 if lOResult = 0 then {файл существует; начать основную работу} begin i : = 1; р : = 1; repeat if i mod 50 = 0 then {начало очередной страницы} begin writeln(LST, FF); {прогон бумаги} {запросить разрешение на продолжение работы} 126
write(bel, ' Страница’, p:3, r закончилась' Продолжать (У/N) ? bel); readln(c); if UpCase(c) <> ’Y’ then beg i n close(f); {нет разрешения на продолжение} halt {завершить программу} end; {Подтверждение получено; вывести номер страницы.} inc(p); for i := 1 to 35 do write(LST,' '); writeln(LST,,p,; writeln(LST); i := 1; end; {if i mod 50 = 0} {основной ввод-вывод} readln(f, s); writeln(LST, s); inc(i) until EOF(f); {продолжать до конца файла} writeln(LST, FF); close(f) end {if lOResult = 0} end {if name ’ ’} END. 6.4. ТИПИЗИРОВАННЫЕ ФАЙЛЫ Длина любого компонента типизированного файла строго посто- янна, что дает возможность организовать прямой доступ к каждому компоненту. Перед первым обращением к процедурам ввода-вывода указатель файла стоит в его начале и указывает на первый компонент с но- мером 0. После каждого чтения или записи указатель сдвигается к следующему компоненту файла. Переменные в списках ввода-выво- да должны иметь тот же тип, что и компоненты файла. Если этих переменных в списке несколько, указатель будет смещаться после каждой операции обмена данными между переменными и диско- вым файлом. Такой способ доступа называется последовательным. В Турбо-Па- скале имеется также возможность организации прямого доступа к компонентам типизированного файла (см. процедуру SEEK). Процедура READ. Обеспечивает чтение очередных компонентов типизированного файла; формат обращения READ ( <ф.п.>, <сп.ввода> ) Здесь <сп.ввода>- список ввода, содержащий одну или более пере- менных такого же типа, что и компонент файла. 127
Файловая переменная должна быть объявлена предложением FILE OF ... и связана с именем файла процедурой ASSIGN. Файл необходимо открыть процедурой RESET. Если файл. исчерпан, обращение к READ вызовет ошибку ввода- вывода. Процедура WRITE. Используется для записи данных в типизи- рованный файл; формат обращения WRITE <ф.п.>, <сп.вывода> ) Здесь < ф.п. > - файловая переменная; <сп.вывода»- список вывода, содержащий одно или более выра- жений того же типа, что и компонент файла. Файловая переменная должна быть объявлена предложением FILE OF ... и связана с именем файла процедурой ASSIGN. Файл необходимо открыть процедурой REWRITE или RESET. Процедура SEEK. Смещает указатель файла к требуемому компо- ненту; формат обращения SEEK ( <ф.п.>, <N компонента > ) Здесь <N компонента >- выражение типа LONGINT, указывающее номер компонента. Файловая переменная должна быть объявлена предожением FILE OF ... и связана с именем файла процедурой ASSIGN. Файл необхо- димо открыть процедурой REWRITE или RESET. Первый компонент имеет номер 0. Процедуру нельзя приме- нять к текстовым файлам. Функция FILESIZE. Возвращает значение типа LONGINT, кото- рое содержит количество компонентов файла; формат обращения: FILESIZE ( <ф.п.> ) Файловая переменная должна быть объявлена предложением FILE OF ... и связана с именем файла процедурой ASSIGN. Файл необходимо открыть процедурой REWRITE или RESET. Функцию нельзя использовать для текстовых файлов. Чтобы пе- реместить указатель в конец файла, можно написать seek (FileVar, FileSize(Fi leVar)) , где FileVar - файловая переменная. Функция FILEPOS. Возвращает значение типа LONGINT, содер- жащее порядковый номер того компонента файла, который будет обрабатываться следующей операцией ввода-вывода; формат обраще- ния FILEPOS ( <ф.п.> ) Файловая переменная должна быть объявлена предложением FILE OF ... и связана с именем файла процедурой ASSIGN. Файл необходимо открыть процедурой REWRITE или RESET. Функцию . нельзя использовать для текстовых файлов. Первый компонет файла имеет порядковый номер 0. 6.5. НЕТИПИЗИРОВАННЫЕ ФАЙЛЫ Нетипизированные файлы объявляются предложением FILE и отличаются тем, что для них не указан тип компонентов. Отсутст- вие типа делает эти файлы, с одной стороны, совместимыми с лю- 128
быми другими файлами, а с другой, - позволяет организовать высо- коскоростной обмен данными между диском и памятью. При инициации нетипизированного файла процедурами RESET или REWRITE можно указать длину (в байтах) записи нетипизиро- ванного файла, например: var f : file; ass ign(f,'myfile.dat ’); reset(f,512); Длина записи нетипизированного файла указывается вторым па- раметром при обращении к процедурам RESET или REWRITE. В качестве этого параметра может использоваться выражение типа WORD. Если длина записи не указана, она принимается равной Турбо-Паскаль не накладывает каких-либо ограничений на длину записи нетипизированного файла, за исключением требования поло- жительности и ограничения максимальной длины 65535 байтами (емкость целого типа WORD). Однако для обеспечения максималь- ной скорости обмена данными следует задавать длину, которая бы- ла бы кратна длине физического сектора дискового носителя ин- формации (512 байт). Более того, фактически пространство на диске выделяется любо- му файлу порциями - кластерами, которые в зависимости от типа диска могут занимать 2 или более смежных сектора. Для гибких дискет длина кластера равна двум секторам (1024 байт), для жест- ких дисков емкостью 10...32 Мбайт-4 или 8 смежных сектора (2048 или 4096 байт). Кластер может быть прочитан или записан за один оборот диска, поэтому наивысшую скорость обмена данными можно получить, если указать длину записи равной длине кластера. При работе с нетипизированными файлами могут применяться все процедуры и функции, доступные типизированным файлам, за исключением READ и WRITE, которые заменяются соответственно высокоскоростными процедурами BLOCKREAD И BLOCKWRITE. Для вызова этих процедур используются следующие предложения: BLOCKREAD ( <ф.п.>, <буф>, <N> [,<NN>] ) BLOCKWRITE( <ф.п.>, <буф>, <N> [,<NN>] ) Здесь < буф >-буфер - имя переменной, которая будет участвовать в обмене данными с дисками; < N > - количество записей, которые должны быть прочитаны или записаны за одно обращение к диску; < NN> - необязательный параметр, содержащий при выходе из процедуры количество фактически обработанных записей. Файловая переменная должна быть объявлена как нетипизиро- ванный файл, и связана с дисковым файлом процедурой ASSIGN. Файл необходимо открыть процедурой RESET или REWRITE. За одно обращение к процедурам может быть передано до N*RECS байт, где RECS-длина записи нетипизированного файла. 5 - Фаронов 129
Передача идет начиная с первого байта переменной <буф>. Про- граммист должен позаботиться о том, чтобы длина внутреннего представления переменной <буф> была достаточна для размеще- ния всех N*RECS байт при чтении информации с диска. Если при чтении указана переменная <буф> недостаточной длины или если в процессе записи на диск не окажется нужного свободного про- странства, возникает ошибка ввода-вывода, которую можно заблоки- ровать, указав необязательный параметр <NN> (переменная типа WORD). После завершения процедуры указатель смещается на <NN> записей. Процедурами SEEK, FILEPOS и FILESIZE можно обеспе- чить доступ к любой записи нетипизированного файла (см. 6.4). В примере 18 приводится простая программа, которая копирует дисковый файл с максимально возможной скоростью. Программа передает данные кластерами по 2 сектора, что соответствует копиро- ванию дискетных файлов. Для обмена данными между файлами на дисках другого типа можно (но необязательно) соответствующим об- разом изменить константу RECS. I Пример 18 PROGRAM FileCopy; uses dos; const RecS = 1024; {длина кластера дискеты} var fi, fo : fi le; Nameln, NameOut : string; buf : array [l..RecS] of byte;' i : word; BEGIN {ввести имя файла-источника} writeIn('===== Копирование файла ====='); write(’ Файл-источник : '); readln(Nameln); {проверить существование} assign(fi,Nameln); {$!-} reset(fi,RecS); {$!+} z if lOResblt <> 0 then begin {нет файла:} writeln(#7, ' Нет файла Nameln); {сообщить} halt {остановиться} end; {ввести имя файла-приемника} write(’ Файл-приейник : ’); 130
readln(NameOut); assign(fo,NameOut); rewrite(fo,RecS); {основной цикл} while not EOF(fi) do beg i n BlockRead(fi,buf, 1, i); BlockWrite(fo,buf , 1) r end; {закрыть файлы} close(fi); close(fo) END. 5
Г л а в a 7 УКАЗАТЕЛИ И ДИНАМИЧЕСКАЯ ПАМЯТЬ 7.1. ДИНАМИЧЕСКАЯ ПАМЯТЬ Все глобальные переменные и типизированные константы, объ- явленные в Турбо-Паскалевой программе, размещаются в одной не- прерывной области оперативной памяти, которая называется сегмен- том данных. Длина сегмента данных определяется архитектурой процессора 8086 и составляет 65536 байт, что может вызвать извест- ные затруднения при обработке больших массивов данных. С дру- гой стороны, объем памяти ПЭВМ (640 Кбайт) достаточен для ус- пешного решения задач с большой размерностью данных. Выходом из положения может служить использование так называемой дина- мической памяти. Динамическая память - это оперативная память ПЭВМ, предо- ставляемая Турбо-Паскалевой программе при ее работе, за вычетом сегмента данных (64 Кбайта), стека (обычно 16 Кбайт) и собственно тела программы. Размер динамической памяти можно варьировать в широких пределах (см. гл. 3). По умолчанию этот размер опреде- ляется всей доступной памятью ПЭВМ и, как правило, составляет не менее 200...300 Кбайт. Таким образом, динамическая память-это фактически единственная возможность обработки массивов данных большой размерности. Существуют и другие задачи, которые трудно или невозможно решить без использования динамической памяти. В частности, тако- го рода проблемы возникают при разработке систем автоматизиро- ванного проектирования (САПР): размерность математических моде- лей, используемых в САПР, может значительно отличаться в раз- ных проектах; статическое (т.е. на этапе разработки САПР) распре- деление памяти в этом случае, как правило, невозможно. Наконец, динамическая память широко используется для вре- менного запоминания данных при работе с графическими и звуко- выми средствами ПЭВМ. Динамическое размещение данных означает использование дина- мической памяти непосредственно при работе программы. В отли- 132
! чие от этого статическое размещение осуществляется компилятором | Турбо-Паскаля в процессе компиляции программы. При динамиче- ском размещении заранее не известны ни тип, ни количество раз- мещаемых данных, к ним нельзя обращаться по именам, как к статическим переменным. Как известно, оперативная память ПЭВМ представляет собой со- I вокупность элементарных ячеек для хранения информации - байтов, I каждый из которых имеет собственный номер. Эти номера назы- 1 ваются адресами, они позволяют обращаться к любому байту па- I мяти. । Турбо-Паскаль предоставляет в распоряжение программиста гиб- । кое средство управления динамической памятью - так называемые | указатели. Указатель - это переменная, которая в качестве своего значения содержит адрес байта памяти. | В ПЭВМ адреса задаются совокупностью двух шестнадцатираз- рядных слов - сегмента и смещения. Сегмент - это участок памяти, ! имеющий максимальную длину 65536 байт (64 Кбайта) и начинаю- щийся с физического адреса, кратного 16 (т.е. О, 16, 32, 48 и т.д.). Смещение указывает, сколько байтов от начала сегмента нужно пропустить, чтобы обратиться по нужному адресу. Фрагмент памяти в 16 байт называется параграфом, поэтому можно сказать, что сег- ' мент адресует память с точностью до параграфа, а смещение - с точностью до байта. Каждому сегменту соответствует непрерывная и | отдельно адресуемая область памяти. Сегменты могут следовать в памяти один за другим без промежутков, или с некоторым интерва- j лом, или, наконец, перекрывать друг друга. I Таким образом, по своей внутренней структуре любой указатель । представляет собой совокупность двух слов (данных типа WORD), ! трактуемых как сегмент и смещение. С помощью указателей можно 1 размещать в динамической памяти любой из известных в Турбо- I Паскале типов данных. Лишь некоторые из них (BYTE, CHAR, । SHORTINT, BOOLEAN и перечисляемый тип) занимают во внутрен- I нем представлении один байт, остальные - несколько смежных. Поэ- I тому на самом деле указатель адресует лишь первый байт типа J данных. | 7.2. ОБЪЯВЛЕНИЕ УКАЗАТЕЛЕЙ * Как правило, в Турбо-Паскале указатель связывается с некото- рым типом данных. Такие указатели будем называть типизирован- ными. Для объявления типизированного указателя используется значок л , который помещается перед соответствующим типом, на- пример: var pl : "integer; I p2 : "real; I type I PerconPointer = "PerconRecord; PerconRecord = record | Name : string; ( 133 1
Job : string; Next : PerconPointer end; Обратите внимание: при объявлении типа PERCONPOINTER мы сослались на тип PERCONRECORD, который предварительно в про- грамме объявлен не был. Как уже отмечалось, в Турбо-Паскале по- следовательно проводится в жизнь принцип, в соответствии с кото- рым перед использованием какого-либо объекта он должен быть описан. Исключение сделано только для указателей, которые содер- жат ссылку на еще не объявленный тип данных. Это исключение сделано не случайно. Действительно, динамическая память позволя- ет реализовать широко используемую в некоторых программах орга- низацию данных в виде списков. Каждый элемент списка имеет в своем составе указатель на соседний элемент (рис. 16), что обеспе- чивает возможность просмотра и коррекции списка. Если бы в Тур- бо-Паскале не было сделано обсуждаемое исключение, реализация списков была бы значительно затруднена. Рис. 16. Пример списочной структуры данных В Турбо-Паскале можно объявлять указатель и не связывать его при этом с каким-либо конкретным типом данных; для этого слу- жит кодовое слово POINTER, например: var рр : pointer; Указатели такого рода будем называть нетипизированными. По- скольку нетипизированные указатели не связаны с конкретным ти- пом, с их помощью удобно динамически размещать данные, струк- тура которых меняется в ходе работы программы. Как уже говорилось, значениями указателей являются адреса пе- ременных в памяти, поэтому следовало бы ожидать, что значение одного указателя можно передавать другому указателю. На самом деле это не совсем так. В Турбо-Паскале можно передавать значе- ния только между указателями, связанными с одним и тем же ти- пом данных. Если, например, 134
. var , pl, p2 : "integer; p3 : "real; pp : pointer , то присваивание pl := p2 вполне допустимо, в то время как pl := рЗ, запрещено, так как pl и рЗ указывают на разные типы данных. Это ограничение, однако, не распространяется на нетипизирован- ные указатели, поэтому при желании мы могли бы записать рр ;= рЗ; pl := рр и тем самым достичь нужного результата. Читатель вправе задать вопрос, стоило ли вводить ограничения- и тут же давать средства для их обхода? Все дело в том, что любое ограничение, с одной стороны, вводится для повышения надежно- сти программ, но, с другой стороны, уменьшает мощность языка, делает его менее пригодным для каких-то применений. В Турбо- Паскале немногочисленные исключения в отношении типов данных придают языку необходимую гибкость, но их использование требует от программиста дополнительных усилий и свидетельствует о впол- не осознанном действии. 7.3. ВЫДЕЛЕНИЕ И ОСВОБОЖДЕНИЕ ДИНАМИЧЕСКОЙ ПАМЯТИ Вся динамическая память в Турбо-Паскале рассматривается как подобная стеку структура, называемая кучей. Физически куча распо- лагается в старших адресах сразу за областью памяти, которую за- нимает, тело программы. Начало кучи хранится в переменной HEAPORG, конец - в пере- менной FREEPTR. Текущую границу незанятой динамической памя- ти указывает указатель HEAPPTR (рис. 17). Рис. 17. Расположение кучи в памяти ПЭВМ 135
Для выделения памяти под любую переменную используется процедура NEW. Единственным параметром этой процедуры явля- ется типизированный указатель, например: var i, j : "integer; г : "real; begin new(i); После выполнения этого фрагмента указатель I приобретет зна- чение, которое перед этим имел указатель кучи HEAPPTR, а сам HEAPPTR увеличит свое значение на 2, так как длина внутреннего представления типа INTEGER, с которым связан указатель I, состав- ляет 2 байта. Оператор new(r); вызовет еще раз смещение указателя HEAPPTR, но теперь уже на 6 байт, потому что такова длина внут- реннего представления типа REAL. Аналогичным образом выделя- ется память и для переменной любого другого типа. После того как указатель приобрел некоторое значение, т.е. стал указывать на конкретный физический байт памяти, по этому адресу можно разместить любое значение соответствующего типа, напри- мер: 1" := 2; {в область памяти i помещено значение 2} г" := 2*pi; {в область памяти г помещено значение 6.28} Значение некоторого типа, на которое ссылается указатель, обоз- начается символом Л , который ставится сразу за указателем. Такое значение можно использовать в любом месте Турбо-Паскалевой программы, где это допустимо для констант и переменных того же типа, например: г" := sqr(r") + i" - 17; Разумеется, совершенно недопустим оператор г := sqr(r") + i" - 17; так как указателю R нельзя присвоить значение вещественного вы- ражения. Динамическую память можно не только забирать из кучи, но и возвращать ее обратно. Для этого используется процедура DISPOSE. Например, операторы dispose(r); dispose(i); вернут в кучу 8 байт, которые ранее были выделены указателям I и R (см. выше). Отметим, что процедура DISPOSE(PTR) не изменяет значение указателя PTR, а лишь возвращает в кучу память, ранее связанную с этим указателем. Однако повторное применение процедуры к “свободному" указателю приведет к возникновению ошибки периода исполнения. Чтобы пометить освободившийся указатель, програм1 мист может использовать зарезервированное слово NIL. К указателям можно применять операции отношения, в том числе и сравнение с NIL, например: 136
const p : "real - NIL; if p = NIL then new(p); di spose(p); p := NIL; Этот фрагмент иллюстрирует предпочтительный способ объявле- ния указателя в виде типизированной константы с одновременным присвоением ему значения NIL (см. гл. 8). Следует учесть, что на- чальное значение указателя (при его объявлении в разделе пере- менных) может быть произвольным. Использование указателей, ко- торым не присвоено значение процедурой NEW или каким-либо другим способом, никак не контролируется системой и может при- вести к непредсказуемым результатам. Чередование обращений к процедурам NEW и DISPOSE обычно приводит к ’’ячеистой" структуре памяти. Дело в том, что все опера- ции с кучей выполняются под управлением особой программы, ко- торая ведет учет всех свободных фрагментов в куче. При очеред- ном обращении к процедуре NEW эта программа отыскивает наи- меньший свободный фрагмент, в котором еще может разместиться требуемая переменная. Адрес начала найденного фрагмента возвра- щается в указателе, а сам фрагмент или его часть нужной длины помечается как занятая часть кучи. Другая возможность состоит в освобождении целого фрагмента кучи. С этой целью перед началом выделения динамической памя- ти текущее значение указателя HEAPPTR запоминается в перемен- ной-указателе с помощью процедуры MARK. Теперь можно в лю- бой момент освободить фрагмент кучи от этого адреса до конца ди- намической памяти, если обратиться к процедуре RELEASE, напри- мер: var р,pl,р2,рЗ,р4,р5 : "integer; begin new(pl); new(p2); mark(p); new(p3); new(p4); new(p5); release(p); В этом примере процедурой MARK(P) в указатель Р было поме- щено текущее значение HEAPPTR, однако память под переменную не резервировалась. Обращение RELEASE(P) освободило динамиче- скую память от помеченного места до конца кучи. Рис. 18 иллюст- рирует механизм работы процедур DISPOSE и MARK, RELEASE для рассмотренного примера и случая, когда вместо оператора RELEASE(P) используется, например, DISPOSE(P3). 137
Рис. 18. Механизм освобождения памяти: а) перед освобождением; Ь) после DISPOSE(P3); с) после RELEASE(P) Следует учесть, что вызов RELEASE уничтожает список свобод- ных фрагментов в куче, созданных до этого процедурой DISPOSE, поэтому совместное использование обоих механизмов освобождения памяти в рамках одной программы не рекомендуется. Как уже от- мечалось, параметром процедуры NEW может быть только типизи- рованный указатель. Для работы с нетипизированными указателями используются процедуры: GETMEM (Р, SIZE) - резервирование памяти; FREEMEM(P, SIZE) - освобождение памяти. Здесь Р - нетипизированный указатель; SIZE - размер требуемой или освобождаемой части кучи, байт. За одно обращение к куче процедурой GETMEM можно заре- зервировать до 65521 байт динамической памяти. Использование ' процедур GETMEM - FREEMEM, как и вообще вся работа с дина- мической памятью, требует особой осторожности и тщательного со- блюдения простого правила: освобождать нужно ровно столько па- । мяти, сколько ее было зарезервировано, и именно с того адреса, с которого она была зарезервирована. Нетрудно обнаружить, что нали- । чие нетипизированных указателей в Турбо-Паскале (в стандартном Паскале их нет) открывает широкие возможности неявного преобра- зования типов. К сожалению, трудно обнаруживаемые- ошибки в программе, связанные с некорректно используемыми обращениями к процедурам NEW и DISPOSE также могут привести к нежелатель- ному преобразованию типов. В самом деле, пусть имеется програм- ма 138
var i,j : ’integer; г : ’real; BEGIN new(i); {i := HeapOrg; HeapPtr := HeapOrg + 2} j := i; {j := HeapOrg} j* 2; dispose(i); {HeapPtr := HeapOrg} riew(r); ' {r := HeapOrg; HeapPtr := HeapOrg + 6} r* ;= pi; writeln(j’) { ?? } END. Что будет выведено на экран дисплея? Чтобы ответить на этот вопрос, проследим за значениями указателя HEAPPTR. Перед испо- лнением программы этот указатель имел значение адреса начала кучи HEAPORG, которое и было передано указателю I, а затем и J. После выполнения DISPOSE(I) указатель кучи вновь приобрел зна- чение HEAPORG, этот адрес передан указателю R в процедуре NEW(R). После того как по адресу R разместилось вещественное число PI, первые 2 байта кучи оказались заняты под часть внутрен- него представления этого числа. В то же время J все еще сохраня- ет адрес HEAPORG, поэтому оператор WRITELN(K) будет рассмат- ривать 2 байта числа PI как внутреннее представление целого числа (ведь J - эго указатель на тип INTEGER) и выведет 8578. 7.4. ИСПОЛЬЗОВАНИЕ УКАЗАТЕЛЕЙ Подведем некоторые итоги. Динамическая память составляет 200...300 Кбайт или больше, ее начало хранится в переменной HEAPORG, а конец соответствует адресу переменной FREEPTR. Те- кущий адрес свободного участка динамической памяти хранится в указателе HEAPPTR. Посмотрим, как можно использовать динамическую память для размещения крупных массивов данных. Пусть, например, требуется обеспечить доступ к элементам прямоугольной матрицы 100x200 ти- па EXTENDED. Для размещения такого массива требуется память 100*200*10 = 200000 байт. Казалось бы, эту проблему можно решить следующим образом: var i,j : integer; PtrArr : array [1..100, 1..200] of ’real; for i := 1 to 100 do for j := 1 to 200 do new(PtrArr [i, j]); Теперь к любому элементу вновь созданного динамического мас- сива можно обратиться по адресу, например: 139
PtrArr[l, 1] ~ := 0; if PtrArr [ifj*2]л > 1 then .. Вспомним, однако, что длина внутреннего представления указа- теля составляет 4 байта, поэтому для размещения массива PTRARR потребуется 100*200*4 = 80000 байт, что превышает размер сегмента данных (65536 байт), доступный, как уже говорилось, Турбо-Паскале- вой программе для статического размещения данных. Выходом из положения могла бы послужить адресная арифме- тика, т.е. арифметика над указателями, потому что в этом случае можно было бы отказаться от создания массива указателей PTRARR, а вычислять адрес любого элемента прямоугольной мат- рицы непосредственно перед обращением к нему. Однако в Турбо- Паскале над указателями не определены никакие операции, кроме операций присвоения и отношения. Тем не менее решить указанную задачу все-таки можно. Как мы уже знаем, любой указатель состоит из двух слов типа WORD, в ко- торых хранятся сегмент и смещение. В Турбо-Паскале определены две встроенные функции типа WORD, позволяющие получить со- держимое этих слов: SEG(X) - возвращает сегментную часть адреса; OFS(X) - возвращает смещение. Аргументом X при обращении к этим функциям может служить любая переменная, в том числе и та, на которую указывает указа- тель. Например, если имеем var р : 'real; new(p); р* := 3.14; , то функция SEG(P) вернет сегментную часть адреса, по которо- му располагается четырехбайтный указатель Р, в то время как SEG(P^) - сегмент шестибайтного участка кучи, в котором хранится число 3.14. С другой стороны, с помощью встроенной функции PTR ( SEG, OFS : WORD ) : POINTER можно создать значение указателя, совместимое с указателями лю- бого типа. Таким образом, возможна такая последовательность дей- ствий. Вначале процедурой GETMEM из кучи забираются несколько фрагментов подходящей длины (напомню, что за одно обращение к процедуре можно зарезервировать не более 65521 байт динамиче- ской памяти). Для рассматриваемого примера удобно резервировать фрагменты такой длины, чтобы в них могли, например, разме- ститься строки прямоугольной матрицы, т.е. 200*10 = 2000 байт. На- чало каждого фрагмента, т.е. фактически начало размещения в па- мяти каждой строки, запоминаются в массиве PTRSTR из 100 указа- телей. Теперь для доступа к любому элементу строки нужно вычис- лить смещение этого элемента от начала строкй и сформировать соответствующий указатель: 140
var i,j : integer; PtrStr : array [1..100] of pointer; pr : "real; const SizeOfReal = 6; • begin for i := 1 to 100. do GetMem(PtrStr[i],SizeOfRea 1*200); {обращение к элементу матрицы [i,j]} pr := ptr(seg(PtrStr[i]"), ofs(PtrStr[i]") + (j-1)*S izeOfRea1); if pr" > 1 then .... Поскольку оператор вычисления адреса PR := PTR... будет, судя по всему, использоваться в программе неоднократно, полезно ввести вспомогательную функцию GETR, возвращающую значение элемен- та матрицы, и процедуру PUTR, устанавливающую новое значение элемента (правила объявления процедур и функций изложены в гл. 9). Каждая из них, в свою очередь, обращается к функции ADDRR для вычисления адреса. Вот как может выглядеть програм- ма, создающая в памяти матрицу из NxM случайных чисел и вы- числяющая их среднее значение (пример 19). Пример 19 PROGRAM РоinterDemons; const SizeOfReal = 6; {длина переменной типа REAL} N = 100; {количество столбцов} М « 200; {количество строк} var i,j : integer; PtrStr : array [1..N] of pointer; s : real; type . RealPoint = "real; {---------------------------} FUNCTION AddrR(i,j : word) : RealPoint; BEGIN AddrR := ptr(seg(PtrStr[i]"), ofs(PtrStr[i]") + (j-1)*S izeOfRea1) END {AddrR}; {—,-------------------------} FUNCTION GetR(i,j : integer) : real; BEGIN GetR := AddrR(i,j)" END {GetR}; {---------------------------} PROCEDURE PutR(i,j : integer; x : real); BEGIN AddrR(i , j)" : = x 141
END {PutR}; {------------------------} BEGIN {Main} for i :=1 to N do begin GetMem(PtrStr [i] M*SizeOfReal); for j := 1 to M do PutR(i, j, Random) end; s := 0; for i := 1 to N do for j := 1 to M do s := s + GetR(i,j); writejn(s / (N * M) : 12:10) ENO {Main}. В рассмотренном примере предполагается, что каждая строка размещается в куче начиная с границы параграфа и смещение для каждого указателя PTRSTR равно нулю. В действительности при по- следовательных обращениях к GETMEM начало очередного фраг- мента следует сразу за концом предыдущего и может не попасть на границу сегмента. В результате при размещении фрагментов максимальной длины (65521 байт) может возникнуть переполнение при вычислении смещения последнего байта. ' Л 7.5. ПРОЦЕДУРЫ И ФУНКЦИИ ДЛЯ РАБОТЫ С ДИНАМИЧЕСКОЙ ПАМЯТЬЮ Ниже приводится описание как уже рассмотренных процедур и функций, так и некоторых других, которые могут оказаться полез- ными при обращении к динамической памяти. Функция ADDR. Возвращает результат типа POINTER, в котором содержится адрес аргумента; формат обращения ADDR ( X ) Здесь X-любой объект программы (имя любой переменной, проце- дуры, функции). Возвращаемый адрес совместим с указателем любого типа. От- метим, что аналогичный результат возвращает операция @ (см. гл.4). Функция CSEG. Возвращает значение, хранящееся в регистре CS микропроцессора (в начале работы программы в CS содержится сег- мент начала кода программы); формат обращения CSEG Результат возвращается в слове типа WORD. Процедура DISPOSE. Возвращает в кучу фрагмент динамической памяти, который ранее был зарезервирован за типизированным ука- зателем; формат обращения DISPOSE ( ТР ) Здесь ТР - типизированный указатель. При повторном использовании процедуры применительно к уже освобожденному фрагмейту возникает ошибка периода исполнения. 142
Функция DSEG. Возвращает значение, хранящееся в регистре DS микропроцессора (в начале работы программы в DS содержится сегмент начала данных программы); формат обращения DSEG Результат возвращается в слове типа WORD. Процедура FREEMEM. Возвращает в кучу фрагмент динамиче- ской памяти, который ранее был зарезервирован за нетипизирован- ным указателем; формат обращения FREEMEM ( Р, SIZE ) Здесь Р - нетипизированный указатель; SIZE-длина освобождаемого фрагмента, байт. При повторном использовании процедуры применительно к уже освобожденному, фрагменту возникает ошибка периода исполнения. Процедура GETMEM. Резервирует за нетипизированным указате- лем фрагмент динамической памяти требуемого размера; формат обращения: GETMEM ( Р, SIZE ) Здесь Р- нетипизированный указатель; SIZE- длина резервируемого фрагмента, байт. За одно обращение к процедуре можно зарезервировать не более 65521 байт динамической памяти. Если нет свободной памяти тре- буемого размера, возникает ошибка периода исполнения. Если па- мять не фрагментирована, последовательные обращения к процеду- ре будут резервировать последовательные участки памяти, так что начало следующего будет располагаться сразу за концом предыду- щего. Процедура MARK. Запоминает текущее значение указателя кучи HEAPPTR; формат обращения: MARK ( PTR ) Здесь PTR - указатель любого типа, в котором будет возвращено те- кущее значение-HEAPPTR. Используется совместно с RELEASE для освобождения части ку- чи. Функция MAXAVAIL. Возвращает размер (в байтах) наибольшего непрерывного участка кучи. Обращение MAXAVAIL Результат имеет тип LONGINT. За один вызов процедуры NEW или GETMEM нельзя зарезервировать памяти больше, чем значе- ние, возвращаемое этой функцией. Функция MEMAVAIL. Возвращает размер (в байтах) общего сво- бодного пространства кучи. Обращение МЕМА VAIL Результат имеет тип LONGINT. Процедура NEW. Резервирует фрагмент кучи для размещения переменной; формат обращения NEW ( ТР ) . Здесь ТР - типизированный указатель. За одно обращение к процедуре можно зарезервировать не более 65521 байт динамической памяти.' Если нет свободной памяти тре- 143
буемого размера, возникает ошибка периода исполнения. Если па- мять не фрагментирована, последовательные обращения к процеду- ре будут резервировать последовательные участки памяти, так что начало следующего будет располагаться сразу за концом предыду- щего. Функция OFS. Возвращает значение типа WORD, содержащее смещение адреса указанного объекта; формат обращения OFS ( X ) Здесь X - выражение любого типа или имя процедуры. Функция PTR. Возвращает значение типа POINTER по заданно- му сегменту SEG и смещению OFS; формат обращения PTR ( SEG, OFS ) Здесь SEG - выражение типа WORD, содержащее сегмент; OFS - выражение типа WORD, содержащее смещение. Значение, возвращаемое функцией, совместимо с -указателем лю- бого типа. Процедура RELEASE. Освобождает участок кучи; формат обраще- ния RELEASE ( PTR ) Здесь PTR - указатель любого типа, в котором предварительно было сохранено процедурой MARK значение указателя кучи. Освобождается участок кучи от адреса, хранящегося в PTR, до конца кучи. Одновременно уничтожается список всех свободных фрагментов, которые, возможно, были созданы процедурами DISPOSE или FREEMEM. Функция SEG. Возвращает значение типа WORD, содержащее сегмент адреса указанного объекта; формат обращения SEG ( X ) Здесь X г- выражение любого типа или имя процедуры. Функция SIZEOF. Возвращает длину (в байтах) внутреннего представления указанного объекта; формат обращения SIZEOF ( X ) Здесь X - имя переменной, функции или типа. Так, везде в программе из примера 19 вместо константы SIZEOFREAL можно было бы использовать обращение SIZEOF(REAL).
Г л а в a 8 | ТИПИЗИРОВАННЫЕ КОНСТАНТЫ I 1 В Турбо-Паскале допускается использование типизированных * констант. Типизированные константы описываются в разделе объяв- ' ления констант следующим образом: «идентификатор» : <тип> = <°начение> j Типизированным константам можно переприсваивать другие зна- । чения в ходе выполнения программы, поэтому фактически они представляют собой переменные с начальными значениями. Типи- зированная константа приобретает указанное в ее объявлении значе- ние, т.е. инициализируется, лишь один раз-к моменту начала ра- ’ боты программы. При повторном входе в блок (процедуру или , функцию), в котором она объявлена, переинициализация типизиро- ванной константы не производится и она сохраняет то значение, ! которое имела к моменту выхода из блока. Типизированные константы могут быть любого типа, кроме фай- лов. Нельзя также объявить типизированную константу-запись, если хотя бы одно из ее полей является полем файлового типа. Поскольку типизированная константа практически не отличается от переменной, ее нельзя использовать в качестве значения при объявлении других констант или границ типа-диапазона. 8.1. ТИПИЗИРОВАННЫЕ КОНСТАНТЫ ПРОСТЫХ ТИПОВ И ТИПА STRING Объявление таких констант обычно не вызывает трудностей, так как в качестве их значения используются уже рассмотренные в гл.4 нетипизированные константы или их идентификаторы. Примеры объявлений: type colors = (white, red, black); {Правильные объявления:} const CurrCol : colors = red; name : string = ’Вирт ,H year : word = 1989; X : real = 0.1; 145 J
end; const origon : point = (x : 0; у : -1); line : vector = ((x : -3.1; у : 1.5), (x : 5.9; у : 3.0)); SomeDay : date = (d : 16; m : Mar; у : 1989); Поля должны указываться в той последовательности, в какой они перечислены в объявлении типа. Если в записи используется хотя бы одно поле файлового типа, такую запись нельзя объявить типизированной константой. Для записей с вариантными полями указывается только один из возможных вариантов констант, напри- мер: type forma = record case Boolean of true : (Birthplace : string[40]); false : (Country : string[20]; Entryport : string[20]; EntryDate : array [1..3] of word; count : word) end; const Perconl Percon2 forma = (Country : ’Польша’; EntryPort : 'Ленинград’; EntryDate : (16, 3, 89); count : 12); forma = (Birthplace : ’Москва’); 8.4. ТИПИЗИРОВАННЫЕ КОНСТАНТЫ-МНОЖЕСТВА Значение типизированной константы-множества задается в виде правильного конструктора множества, например: type days = set of 1.. 31; 4 digc = set .of '0' . . '9 '; / error = set of 1..24; const WorkDays : days = [1..5, 8..12, 15..19, 22..26, 29, 30]; EvenDigits: digc = [’O’, '2’, '4’, '6’, ’8’]; ErrorFlag : error= [] ; 8.5. ТИПИЗИРОВАННЫЕ КОНСТАНТЫ-УКАЗАТЕЛИ Единственным значением типизированной константы-указателя может быть только NIL, например: > const pr : "real = NIL; р : pointer = NIL; 148
Г л а в a 9 ПРОЦЕДУРЫ И ФУНКЦИИ В гл. 2 говорилось о том, что процедуры и функции представ- ляют собой относительно самостоятельные фрагменты программы, оформленные особым образом и снабженные именем. Упоминание этого имени в тексте программы называется вызовом процедуры (функции). Отличие функции от процедуры заключается в том, что результатом исполнения операторов, образующих тело функции, всегда является некоторое единственное значение того или иного типа, поэтому обращение к функции можно использовать в соответ- ствующих выражениях наряду с переменными и константами. Ус- ловимся далее называть процедуру или функцию общим именем блок, еслй только для излагаемого материала указанное отличие не имеет значения. Блоки представляют собой инструмент, с помощью которого лю- бая Турбо-Паскалевая программа может быть разбита на ряд в из- вестной степени независимых друг от друга частей. Такое разбиение необходимо по двум причинам. Во-первых, это средство экономии памяти: каждый блок сущест- вует в программе в единственном экземпляре, в то время как обра- щаться к нему можно многократно из разных точек программы. При вызове блока активизируется последовательность операторов, образующих блок, а с помощью передаваемых блоку параметров нужным образом модифицируется реализуемый в нем алгоритм. Во-вторых, блочная структура позволяет применять современные методы проектирования программ. Дело в том, что при программи- ровании достаточно сложной задачи решить ее "одним махом", т.е. написать последовательно всю программу от начала до конца, обычно невозможно. Процесс разработки программы - это творче- ский процесс, проходящий в несколько этапов. Вначале стараются проработать наиболее общий, генеральный алгоритм, не останавли- ваясь на технических деталях его реализации. В результате такой проработки алгоритм представляется в виде последовательности от- носительно крупных блоков, реализующих более или менее само- стоятельные смысловые части алгоритма. Блоки, в свою очередь, могут разбиваться на менее крупные подблоки, те - на блоки ниж- 149
min : integer = 0; . max • : integer = 10; days : 1. . 31 = 1; answer : cha_r = 'Y'; { Неправильные объявления: } mass : array [min..max] of real; {нельзя использовать типизированные константы в качестве границ диапазона} a,b,c : byte = .0; {нельзя использовать список идентификаторов} х : real = pi; {нельзя использовать в качестве значения вызов функции (pi - функция!)} var NameF : string[22] = 'prog.pas'; {нельзя объявлять типизированную константу в -разделе переменных} 8.2. ТИПИЗИРОВАННЫЕ КОНСТАНТЫ-МАССИВЫ В качестве начального значения типизированной константы-мас- сива используется список констант, отделенных друг от друга запя- тыми; список заключается в круглые скобки, например: type colors = (white, red, black); const ColStr : array [colors] of string[5] = ('white', 're.d', 'black'); vector : array [1..5] of byte = (0,0,0,0,0); При объявлении массива символов можно использовать то об- стоятельство, что все символьные массивы и строки в Турбо-Паска- ле хранятся в упакованном формате, поэтому в качестве значения массива-константы типа CHAR допускается указывать символьную строку соответствующей длины. Два следующих объявления иден- тичны: const digit : array [0..9] of char = ('O’,'Г,'2','3','4','5','6','7','8','9'); digchr: array [0..9] of char = '0123456789'; При объявлении многомерных констант-массивов множество констант, соответствующих каждому измерению, заключается в до- полнительные круглые скобки и отделяется от соседнего множества запятыми. В результате образуются вложенные структуры множеств, причем глубина вложения должна соответствовать количеству изме- рений (размерности) массива. Множество констант с максимальной глубиной вложения связывается с изменением самого правого ин- декса массива. Следующая программа выведет на экран три строки с монотон- но увеличивающимися целыми числами: var i, j, k, 1 : byte; const matr : array [1..3, 1..5] of byte = 146
((О, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10,11,12,13,14)); cube : array [0..1, 0..1, 0..2] of integer = (((0 ,1 ,2 ),(3 ,4 ,5 )), ((6 ,7 ,8 ), (9 ,10,11))); mas4 : array [0..1, 0..1, 0..1, 0..1] of word = ((((0 ,1 ),(2 ,3 )), ((4 ,5 ),(6 ,7 ))), (((8 ,9 ),(10,11)), ((12,13),(14,15)))); BEGIN for i := 1 to 3 do for j := 1 to 5 do write(matr[i,j]:3); writein; for i := 0 to 1 do for j := 0 to 1 do for к := 0 to 2 do write(cube [i,j,k]:3); writein; for i := 0 to 1 do for j := 0 to 1 do for к := 0 to 1 do for 1 := 0 to 1 do write(mas4[i,j,k,1]:3); writein END. Количество переменных в списке констант должно строго соот- ветствовать объявленной длине массива по каждому измерению. 8.3. ТИПИЗИРОВАННЫЕ КОНСТАНТЫ-ЗАПИСИ Определение константы-записи имеет следующий вид: <ид-р> : <тип> = ( <сп.знач.полей> ) Здесь <ид-р> - идентификатор константы; <тип> - предварительно объявленный тип записи; <сп.знач.полей> - список значений полей. Список значений полей представляет собой список из последова- тельностей следующего вида: имя поля, двоеточие и константа; эле- менты списка отделяются друг от друга двоеточиями, например: type point = record х, у : real end; vect '= array [0..1] of point; month = (Jan, Feb, Mar, Apr, May, Jun, Jly, Aug, Sep, Oct, Nov, Dec); date = record d : 1..31; m : month; у : 1900..1999 147
него уровня и т.д. (рис. 19). Процесс последовательного структури- рования программы продолжается до тех пор, пока реализуемые блоками алгоритмы не станут простыми и легко программируемы- ми. Блок С Рис. 19. Пример структурированной программы Достоинства описанного подхода, получившего название нисходя- щего программирования [4], очевидны: блоки повышают надежность программы (каждый блок можно реализовывать независимо от дру- гих и автономно его тестировать), облегчают ее понимание и позво- ляют вести разработку программы коллективом программистов. В этой главе подробно рассматриваются различные аспекты ис- пользования блоков в Турбо-Паскале. В прилож. 2 перечислены в алфавитном порядке все стандартные для Турбо-Паскаля процедуры и функции. 9.1. ЛОКАЛИЗАЦИЯ ИМЕН Как уже говорилось, вызов блока осуществляется простым упо- минанием имени процедуры в операторе вызова процедуры или имени функции в выражении. Но любое имя в Турбо-Паскалевой программе должно быть обязательно описано перед тем, как оно появится среди исполняемых операторов. Не делается исключения и в отношении блоков: каждую свою процедуру и функцию про- граммисту необходимо описать в разделе описаний. Описать блок - это значит указать его заголовок и тело. В заго- ловке объявляется имя блока и формальные параметры, если они есть. Для функции, кроме того, объявляется тип возвращаемого ею результата. За заголовком следует тело блока, которое, подобно про- 150
грамме, состоит из раздела описаний и раздела исполняемых опера- торов. В разделе описаний блока могут встретиться описания бло- ков низшего уровня, в тех - другие блоки и т.д. Вот какую иерархию описаний получим, например, для про- граммы, структура которой была изображена на рис.19 (для просто- ты считается, что все блоки представляют собой процедуры без па- раметров): Program ...; Procedure А; Procedure Al; beg i n end {Al}; Procedure A2; begin end {A2}; begin {A} end {A}; Procedure B; Procedure Bl; beg i n end {Bl}; Procedure B2; Procedure B21; I И Т.Д. Блок любого уровня имеет обычно множество имен констант, переменных, типов и вложенных в него блоков низшего уровня. Считается, что все имена, описанные внутри блока, локализуются в нем, т.е. они как бы невидимы извне блока. Таким образом, со сто- роны операторов, использующих обращение к блоку, он трактуется как ’черный ящик”, в котором реализуется тот или иной алгоритм. Все детали этой реализации скрыты от глаз пользователя блока и потому недоступны ему. В рассмотренном примере из основной программы можно обра- титься к процедурам А, В и С, но нельзя вызвать ни одну из вло- женных в них процедур Al, А2, В1 и т.д. Сказанное относится не только к именам блоков, но и вообще к любым именам, объявленным в них: типам, константам, перемен- ным и меткам. Все имена в пределах блока, в котором они объяв- лены, должны быть уникальными и не могут совпадать с именем самого блока; При входе в блок низшего уровня не только становятся доступ- ными объявленные в нем имена, но и сохраняется доступ ко всем 151
именам верхнего уровня. Образно говоря, любой блок как бы окру- жен полупрозрачными стенками: снаружи блока мы не видим его внутренности, но, попав в блок, можем наблюдать все, что делается вне его. Так, из блока В21 мы можем обратиться к блоку А, ис- пользовать имена, объявленные в основной программе, в блоках В и В2, и даже обратиться к ним. Любой блок может, наконец, вы- звать сам себя-такой способ вызова называется рекурсией и рас- сматривается дальше в этой главе. Пусть имеем такое описание: Progpam ..; var VI : ...; Procedure А; var V2 : ...; end {A}; Procedure B; var V3 : ... ; Procedure Bl; . var V4 : ...; Procedure Bll; var V5; Из блока Bll доступны все пять переменных VI...V5, из блока В1 доступны V1...V4, из центральной программы - только VI. При взаимодействии блоков одного уровня иерархии вступает в силу основное правило Турбо-Паскаля: любой блок перед его ис- пользованием должен быть описан. Поэтому из блока В можно об- ратиться к блоку А, но из А обратиться к В невозможно (точнее, такая возможность появляется только с использованием опережаю- щего описания, см. 9.6). Продолжая образное сравнение, можно те- перь уподобить блок ящику с непрозрачными стенками и дном и полупрозрачной крышей: из блока можно смотреть только "вверх" и нельзя "вниз", т.е. блоку доступны только те объекты верхнего уров- ня, которые описаны до появления данного блока. Эти объекты на- зываются глобальными по отношению к блоку. В отличие от стандартного Паскаля в Турбо-Паскале допускается произвольная последовательность описания констант, переменных, типов, меток и блоков. Так, раздел VAR описания переменных мо- жет появляться в разделе описаний одного и того же блока много раз и перемежаться с объявлениями других объектов и блоков. Для Турбо-Паскаля совершенно безразличны порядок следования и ко- личество разделов VAR, CONST, TYPE, LABEL, но при определении области действия этих описаний следует помнить, что имена, опи- санные ниже по тексту программы, нельзя вызвать из ранее опи- санных блоков, например: var VI : ...; Procedure S; var V2 : ...; end {S}; 152
var V3 : Из S можно обратиться к переменным VI и V2, но нельзя ис- пользовать V3, так как описание V3 следует в программе за описа- нием блока S. Имена, локализованные в блоке, могут совпадать с ранее объяв- ленными глобальными именами. В этом случае считается, что ло- кальное имя "закрывает" глобальное и делает его недоступым, на- пример: var i : integer; PROCEDURE P; var i : integer; beg i n wr i teIn(i) end {P}; BEGIN i := 1; P END. Что напечатает эта программа? Все что угодно, так как значение внутренней переменной I при входе в процедуру Р неопределено, хотя одноименная глобальная переменная имеет значение 1. Ло- кальная переменная "закроет" глобальную, и на экран будет выведе- но произвольное значение, содержащееся в неинициированной внут- ренней переменной. Если убрать описание var i : integer; из процедуры Р, то на экран будет выведено значение глобальной переменной I, т.е. 1. Таким образом, одноименные глобальные и локальные перемен- ные - это разные переменные. Любое обращение к таким перемен- ным в теле блока трактуется как обращение к локальным перемен- ным, т.е. глобальные переменные в этом случае попросту недоступ- ны. 9.2. ПАРАМЕТРЫ Описание блока состоит из заголовка и тела блока. Заголовок процедуры имеет вид PROCEDURE <имя> [ ( <сп.ф.п.> ) ]; Заголовок функции FUNCTION <имя> [ ( <сп.ф.п> ) ] : <тип>; Здесь <имя>-имя блока (правильный идентификатор); <сп.ф.п.> - список формальных параметров; < тип > - тип возвращаемого функцией результата. Список формальных параметров необязателен и может отсутст- 153
вовать. Если же он есть, то в нем должны быть перечислены име- на формальных параметров и их тип, например: Procedure SB (а : real; b : integer; с : char); Как видно из примера, параметры в списке отделяются друг от друга точкой с запятой. Несколько следующих подряд однотипных параметров можно объединять в подсписки, например, вместо Function F (а : real; b : real) : real; можно написать проще: Function F (ar b : real) : real; В пределах блока список формальных параметров рассматрива- ется как своеобразное расширение раздела описаний: все перемен- ные из этого списка могут использоваться в любых выражениях. Таким способом осуществляется настройка алгоритма блока на кон- кретную задачу. Рассмотрим следующий пример. В языке Турбо-Паскаль, как уже говорилось, нет операции возведения в степень, однако с по- мощью встроенных функций LN(X) и ЕХР(Х) нетрудно реализовать новую функцию с именем, например, POWER, осуществляющую возведение любого вещественного числа в любую вещественную же степень. В программе (пример 20) вводится пара чисел X и Y и выводится на экран дисплея результат возведения X сначала в сте- пень Y, а затем в степень (-Y). Для выхода из’ программы нужно ввести CTRL-Z и "Ввод”. Пример 20 PROGRAM PowerDem; ' var х ,у : rea 1; {-----------------} * FUNCTION Power(a,b : real) : real; begin {Power} if a > 0 then Power': = exp(b * ln(a)) else if a < 0 then Power := exp(b * ln(abs(a))) else if b=0 then Power : = 1 else Power : = 0 end {Power}; • {-----------------} BEGIN {main} repeat readln(x,y); writeln(power(x,y):12:10, power(x,-y):15:10) until EOF END {main}. Для вызова функции POWER мы просто указали ее в качестве параметра при обращении к встроенной процедуре WRITELN. Пара- метры X и Y в момент обращения к функции - это фактические параметры. Они подставляются вместо формальных параметров А 154
и В в заголовке функции, и затем над ними осуществляются нуж- ные действия. Полученный результат присваивается идентификато- ру функции - именно он и будет возвращен как значение функции при выходе из нее. В программе функция POWER вызывается дважды - сначала с параметрами X и Y, а затем X и -Y, поэтому будут получены два разных результата. Механизм замены формальных параметров на фактические поз- воляет нужным образом настроить алгоритм, реализованный в бло- ке. Турбо-Паскаль следит за тем, чтобы количество и тип формаль- ных параметров строго соответствовали количеству и типам факти- ческих параметров в момент обращения к блоку. Смысл используе- мых фактических параметров зависит от того, в каком порядке они перечислены при вызове блока. В рассмотренном примере первый по порядку фактический параметр будет возводиться в степень, за- даваемую вторым параметром, а не наоборот. Пользователь должен сам следить за правильным порядком перечисления фактических параметров при обращении к блоку. Любой из формальных параметров блока может быть либо пара- метром-значением, либо параметром-переменной. В предыдущем примере параметры А и В определены как параметры-значения. Если какие-либо параметры определяются как параметры-перемен- ные, перед ними необходимо ставить кодовое слово VAR, напри- мер: Function Power (var х : real; у : real) : real; Здесь параметр X - параметр-переменная, a Y - параметр-значение. Определение формального параметра тем или иным способом существенно только для вызывающей программы: если формаль- ный параметр объявлен как параметр-переменная, то при вызове блока ему должен соответствовать фактический параметр в виде пе- ременной нужного типа; если формальный параметр объявлен как параметр-значение, то при вызове ему может соответствовать произ- вольное выражение. Контроль за неукоснительным соблюдением этого правила осуществляется компилятором Турбо-Паскаля. Если для предыдущего примера был бы использован такой заголовок функции: Function Power ( var хг у : real) : real; , то при втором обращении к функции компилятор указал бы на не- соответствие типа фактических и формальных параметров (пара- метр -Y есть выражение, в то время как соответствующий ему фор- мальный параметр описан как параметр-переменная). Для того чтобы понять, в каких случаях использовать парамет- ры-значения, а в каких параметры-переменные, рассмотрим, как осуществляется замена формальных параметров на фактические в момент обращения к блоку. Если параметр определен как параметр-значение, то перед вызо- вом блока это значение вычисляется, полученный результат поме- щается во временную память и передается блоку. Важно учесть, что даже если в качестве фактического параметра указано простей- шее выражение в виде переменной или константы, все равно блоку 155
будет передана лишь копия переменной (константы). Если же пара- метр определен как параметр-переменная, то при вызове блока пе- редается сама переменная, а не ее копия. Таким образом, любые возможные изменения в блоке параметра-значения никак не вос- принимаются вызывающей программой, так как изменяется копия фактического параметра, в то время как изменение параметра-пере- менной приводит к изменению фактического параметра в вызываю- щей программе. Пример 21 поясняет сказанное. В программе задаются два це- лых числа 5 и 7 и эти числа передаются процедуре INC2, в кото- рой они удваиваются. Один из параметров передается как параметр- переменная, другой - как параметр-значение. Значения параметров до и после вызова процедуры, а также результат их удвоения выво- дятся на экран. Пример 21 PROGRAM TestOfParameters; .const a : integer =5; b : integer = 7; {-----------------} PROCEDURE Inc2 ( var a : integer; b : integer); begin {Inc2} a := a + a; b :=b+b; writeIn('удвоенные a:5, b:5) end {inc2}; {-----------------} BEGIN {main} writeln('исходные a:5, b:5); Inc2(a,b); writeln('результат a:5, b:5) END {main}. На экран в результате прогона программы будет выведено: исходные : 5 7 удвоенные : 10 14 результат 10 * 7 Как видно из примера 21, удвоение второго формального пара- метра в процедуре INC2 не вызвало изменения фактической пере- менной В, так как этот параметр описан в заголовке процедуры как параметр-значение. Этот пример поучителен еще и как иллюстра- ция механизма "закрывания" глобальной переменной одноименной локальной: хотя переменная В объявлена как глобальная (она опи- сана в вызывающей программе перед описанием процедуры), в те- ле процедуры ее "закрыла" локальная переменная В, объявленная как параметр-значение в заголовке процедуры. Итак, параметры-переменные используются как средство связи алгоритма, реализованного в блоке, с "внешним миром": с помощью этих параметров блок может передавать результаты своей работы вызывающей программе. Разумеется, в распоряжении программиста 156
всегда есть и другой способ передачи результатов - через глобаль- ные переменные. Однако злоупотребление глобальными связями де- лает программу, как правило, запутанной, трудной в понимании и сложной в отладке. Рекомендуется там, где это возможно, использо- вать передачу результатов через фактические параметры-значения. С другой стороны, описание -всех формальных параметров как параметров-переменных нежелательно по двум причинам. Во-пер- вых, это исключает возможность вызова блока с фактическими па- раметрами в виде выражений, что делает программу менее компак- тной. Во-вторых, и это главное, в блоке возможно случайное ис- пользование формального параметра, например, для временного хранения промежуточного результата, в качестве параметра цикла и т.п., т.е. всегда существует опасность непреднамеренно "испортить" фактическую переменную. Вот почему следует объявлять параметра- ми-переменными только те, через которые блок в действительности передает результаты вызывающей программе. Чем меньше парамет- ров объявлено параметрами-переменными и чем меньше в блоке используется глобальных переменных, тем меньше опасность полу- чить не предусмотренные программистом побочные эффекты, свя- занные с вызовом блока, тем проще программа в понимании и от- ладке. По той же причине не рекомендуется использовать парамет- ры-переменные в заголовке функции: если результатом работы фун- кции не может быть единственное значение, то логичнее использо- вать процедуру или нужным образом декомпозировать алгоритм на несколько блоков. Существует одно • обстоятельство, которое следует учитывать при выборе того или иного характера формальных параметров. Как уже говорилось, при объявлении параметра-значения осуществляется ко- пирование фактического параметра во временную память. Если этим параметром будет массив большой размерности, то существен- ные затраты времени и памяти на копирование при многократных обращениях к блоку могут стать решающим доводом для объявле- ния такого параметра параметром-переменной или передачи его в качестве глобальной переменной. 9.3. ПАРАМЕТРЫ-МАССИВЫ И ПАРАМЕТРЫ-СТРОКИ Может сложиться впечатление, что объявление переменных в списке формальных параметров блока ничем не отличается от объ- явления их в разделе описания переменных. Действительно, в обо- их случаях много общего, но есть одна существенная деталь: типом любого параметра в списке формальных параметров может быть только стандартный или ранее объявленный тип. Поэтому нельзя, например, объявить следующую процедуру: Procedure S (а : array [1..10] of real); так как в списке формальных параметров фактически объявляется тип-диапазон, указывающий границы индексов массива. Если мы хотим передать какой-то элемент массива, то проблем, 157
как правило, не возникает, но если в блок передается весь массив, то следует первоначально описать его тип, например: type atype - array Cl..10] of real; PROCEDURE S (a : atype); Поскольку строка является фактически своеобразным массивом, ее передача в блок осуществляется аналогичным образом: type intype = string [15]; outype = str i ng [30] ; FUNCTION St (s : intype) : outype; Требование описать любой тип-массив или тип-строку перед объявлением блока на первый взгляд кажется несущественным. Действительно, в рамках простейших вычислительных задач обыч- но заранее известна структура всех используемых в программе дан- ных, поэтому статическое описание массивов не вызывает проблем. Однако разработка программных средств универсального назначе- ния, например типа широко используемых в среде Фортрана ОС ЕС библиотек подпрограмм для научных и инженерных расчетов, сразу наталкивается на существенные трудности. По существу, речь идет о том, что в Турбо-Паскале невозможно использовать в блоках массивы с ’’плавающими” границами изменения индексов. Если мы разработали программу, обрабатывающую, скажем, матрицу разме- ром 10x10, то для обработки матрицы размером 9x11 мы вынужде- ны переопределить тип, т.е. перекомпилировать всю программу (сейчас идет речь не о динамическом размещении массивов в куче, а о статическом описании массивов и передаче их как параметров в блоки). Этот недостаток, как и отсутствие в языке средств обработ- ки исключительных ситуаций (прерываний), унаследован из стан- дартного Паскаля и представляет собой объект постоянной и впол- не заслуженной его критики. Разработчики Турбо-Паскаля не риск- нули кардинально изменить свойства базового языка, но, тем не ме- нее, включили в него некоторые средства, позволяющие в известной степени смягчить отмеченные недостатки. Прежде всего с помощью специальной опции VAR-STRING CHECKING в среде Турбо-Паскаля можно установить режим компи- ляции, при котором отключается контроль за совпадением длины фактического и формального параметра-строки (состояние опции RELAXED отключает, a STRICT включает контроль, см. гл. 3). Это позволяет легко решить вопрос о передаче блоку строки произволь- ной длины. Программа в примере 22 иллюстрирует передачу в блок строки меньшего размера, чем указанный у формального па- раметра. Пример 22 PROGRAM TestOfStringPass; type stype = string [20]; var 158
s : str i ng [6]; ' {$V-} {отключение контроля} {-----------------------} FUNCTION Pas (var s : stype) : string; begin Pas : = s + ’ . pas ’ end; . {-----------------------} BEGIN {main} s := 'myf i ler; wr i te 1 n(Pas(s)) END. Передача строки большего размера просто приведет к ее усече- нию до размера формального параметра. Следует сказать, что контроль включается только при передаче строки, объявленной как формальный параметр-переменная. Если соответствующий параметр объявлен параметром-значением, эта оп- ция игнорируется и длина не контролируется. Вы можете убедиться в этом, убрав слово VAR в заголовке функции PAS и перекомпили- ровав программу с опцией ,{$V+}. Значительно сложнее обстоит дело с передачей массивов произ- вольной длины. Наиболее универсальным приемом в этом случае будет, судя по всему, работа с указателями и использование адресной арифметики. Пусть необходима процедура, осуществляющая сложение двух целочисленных квадратных матриц размером N х N , причем дли- на N должна варьироваться. Возможный вариант реализации этой процедуры приводится в примере 23. Пример 23 PROCEDURE SumMatf (pa, pb, pc : pointer; N : integer); var SI, i, j : integfer; a, b, c -/integer; BEGIN SI := SizeOf(integer); for i := 0 to N-l do for j := 0 to N-l do begin a := ptr(seg(pa"), ofsfpa*) + (i * N 1 h j) : ‘ SI) b := ptr(seg(pb~), ofs(pb~) + (i * N 4 > j) : * SI) c := ptr(seg(pc~), ofs(pc") + (i z c~ := a~ + b~ * N + j) ; * SI) end । END {SumMatr}; Такую процедуру можно вызвать так: const N = 5; var a, b, c : array [1..N, 1. . H] of integer; 159
SumMatr(@a, @b, @c, N); R процедуру передаются адреса массивов А, В и С и их размер N. С помощью арифметики указателей, описанной в гл. 7, вычис- ляется смещение текущего элемента массива относительно его на- чала. Процедуру можно несколько упростить, если ввести вспомога- тельную функцию PR, в которой вычисляется смещение. В про- грамме примера 24 вычисляется сумма двух матриц, составленных случайным образом. Пример 24 PROGRAM TestOfArrayPass; ' const N = 5; z var a, b, c : array [1..N, 1..N] of integer; i, 'j : integer; {----------------} PROCEDURE SumMatr ( pa, pb, pc : pointer; N : integer); type pint = "integer; var SI, i , j : integer; {----------------} FUNCTION PR (p : pointer) : pint; BEGIN {PR} PR := ptr(seg(p"), ofs(p") + (i * N + j) * SI) END {PR}; {----------------} BEGIN {SumMatr} SI := SizeOf(integer); for i := 0 to N-l do for j := 0 to N-l do PR(pc)" := PR(pa)" + PR(pb)" END {SumMatr}; {----------------} BEGIN {main} for i := 1 to N do for j := 1 to N do begin a [i, j] := random(lO); b [i, j] : = random(10) end; SumMatr(@a,@b,@c,N); writeln(' A В', c’); wr i teIn; for i := 1 to N do 160
begin for j := 1 to N do write(a[i, j]:3); write(’ ’); ' for j := 1 to N do write(b[i, j] :3); write(’ ’); for j := 1 to N do write(c[i, j] :3); writein end END {main}. Даже после введения вспомогательной функции PR процедура остается сложнее, чем простая реализация алгоритма в виде for i := 1 to N do for j := 1 to*N do c[i, j] := a[i, j] + b[i, j] ; Усложнение программы - неизбежная плата за . ее универсаль- ность; оно вызвано использованием арифметики указателей. Не- сколько проще можно решить эту проблему при помощи нетипи- зированных параметров (см. 9.5). 9.4. ПРОЦЕДУРНЫЕ ТИПЫ. ПАРАМЕТРЫ-ФУНКЦИИ И ПАРАМЕТРЫ-ПРОЦЕДУРЫ । Процедурные типы - это нововведение фирмы Borland (в стан- дартном Паскале таких типов нет). Основное назначение этих ти- пов-дать программисту гибкие средства передачи функций и про- цедур в качестве фактических параметров обращения к другим про- цедурам и функциям. Для объявления процедурного типа используется заголовок про- цедуры (функции), в котором опускается ее имя, например: type Procl = Procedure•(а, b, с : real; var d : real); Proc2 = Procedure (var a, b); РгосЗ = Procedure; Fund = Function : string; Func2 = Function (var s : string) : real; Как видим, существует два процедурных типа: тип-процедура и тип-функция. Пример 25 иллюстрирует механизм передачи процедур в качест- ве фактических параметров вызова. Программа выводит на экран таблицу двух функций: SIN1(X) = (SIN(X) + 1)*EXP(-X) и COS1(X) = (COS(X) + 1)*EXP(-X). Вычисление и печать значений этих функций реализуется в процедуре PRINTFUNC, которой в качестве параметра передается номер позиции N на экране, куда будет выво- диться очередной результат (с помощью этого параметра реализует- ся вывод в две колонки), и имя нужной функции. Пример 25 PROGRAM DemOfProcedureType; Uses CRT; 1 type 6 - Фаронов 161
ное копирование переменной VI в переменную V2 и наоборот, т.е. * меняет местами значения двух переменных. Пример 26 ; PROGRAM UntypedParDem; var V I, V2 : array [l.<3] of integer; i : byte; {------------------------} PROCEDURE SwapVar fvar a, b; size : longint); type tp = array [1..Maxint] of byte; var V I : tp absolute a; V 2 : tp absolute b; i : longint; % Tmp: byte; BEGIN {SwapVar} for i := 1 to size do begin Tmp := VI[i]; Vl [i] := V2[i] ; V2[i] := Tmp end END {SwapVar}; {---------------} BEGIN {main} for i := 1 to 3 do begin vl[i] := 0; v2[i] := i end; SwapVar(vl, v2, SizeOf(vl)); for i := 1 to 3 do write (vl[i]:3); writein; for i := 1 to 3 do write (v2[i]:3); writein END {main}. Заметим, что нетипизированными могут быть только парамет- ры-переменные. / Нетипизированные параметры в сочетании с механизмом совме- щения данных в памяти (см. 5.4) очень удобно использовать для передачи процедуре (функции) одномерных массивов переменной длины. В примере 27 функция NORMA вычисляет норму вектора, длина которого меняется случайным образом. Как и в предыдущем 164
примере, идентификатор MAXINT обозначает предварительно опре- деленную в компиляторе константу величиной 32767. Пример 27 PROGRAM DemOfArrayPass; const NN = 100; {максимальная длина вектора} var а : array [1..NN] of real; i, j, N : integer; {---------------} FUNCTION Norma (var x; N : integer) : real; var a- : array [1..2*MaxInt div SizeOf(rea 1)] of real absolute x; i : integer; s : rea 1; .BEGIN {Norma} s : = 0; for i := 1 to N do s := s + sqr(a[i]); Norma : = sqrt(s) END {Norma}; {---------------} BEGIN {main} for i := 1 to 10 do beg i n N := Random(NN) + 1; {текущая длина вектора} for j := 1 to N do a [j] := Random; writein (’N = N:2, ’ норма =’, Norma(a, N) : 10:7) end END {main}. Следует учесть, что при обращении к функции NORMA массив X помещается в стек и передается по ссылке, поэтому описание в этой функции локальной переменной А в виде одномерного масси- ва максимально возможной длины в 65535 байт, совпадающего с X, на самом деле не приведет к выделению дополнительного объема памяти под размещение этой переменной. Иными словами, локальная переменная А-фиктивная переменная, размер которой никак не влияет на объем используемой памяти. С таким же успе- хом мы могли бы объявить ее в виде массива из 1 элемента, прав- да в этом случае необходимо позаботиться об отключении контроля выхода индекса за границы диапазона. Как видно из рассмотренного примера, передача одномерных массивов переменной длины не вызывает никаких трудностей. Сложнее обстоит дело с многомерными массивами, однако и в этом случае использование описанного приема (нетипизированный параметр и совмещение его в памяти с фиктивной переменной) все-таки проще, чем описанная в гл. 7 индескная арифметика указателей. Вот как, например, могла бы выглядеть процедура 165
Func = Function (x : real) : real; {$F+} {дальняя модель памяти} {---------------------} FUNCTION Sinl(x : real) : real; BEGIN sinl := (sin(x) + 1) * exp(-x) END; {---------------------} FUNCTION Cosl(x : rea*l) : real; BEG LN cosl := (cos(x) + 1) * exp(-x) END; {------------------—} PROCEDURE PrintFunc(n : byte; F : Func); const np = 20; {количество вычислений функций} var x : re*a 1; i : integer; BEGIN {PrintFunc} for i := 1 to np do begin x i * (2 * pi / np); GotoXY (n, WhereY); writein (x:5:3, F(x):18:5) end END; {PrintFunc} {---------------------} BEGIN {основная программа} ClrScr; {очистить экран} PrintFunc (1, sinl); GotoXY (1,1); {курсор в левый верхний угол} PrintFunc (40, cosl) END. Обратите внимание, что для установления правильных связей функций SIN1 и COS1 с процедурой PRINTFUNC они должны ком- пилироваться с установленной опцией OPTIONS/COMPILER/FORCE FAR (так называемая дальняя модель памяти, см. гл.З), вот почему в программу вставлена директива компилятора {$F+}. В таком ре- жиме должны компилироваться любые процедуры (функции), кото- рые будут передаваться в качестве фактических параметров вызова. Стандартные процедуры (функции) Турбо-Паскаля не могут пе- редаваться рассмотренным способом. В программе могут быть объявлены переменные процедурных типов, например, так: 162
var pl : Procl; fl, f2 : Func2; ap‘ : array [1..N] of Proc2; Переменным процедурных типов допускается присваивать в ка- честве значений имена соответствующих процедур (функций). По- сле такого присваивания имя переменной становится синонимом имени соответствующей процедуры (функции), например: type Proc = Procedure (n : word; var a : byte); var ProcVar : Proc; x, у : byte; {$F+} , Procedure Procl(x : word; var у rbyte); begin if x > 255 then у := x mod 255 else у := byte(x) end; {$F-}. begin ProcVar := Procl; , for x := 150 to 180 do begin ' ProcVar (x + 100f у); write (y:8) end end. Отметим, что, в отличие Ьт стандартного Паскаля, в Турбо-Пас- кале разрешается использовать в передаваемой процедуре (функ- ции) как параметры-значения, так и параметры-переменные. 9.5. НЕТИПИЗИРОВАННЫЕ ПАРАМЕТРЫ-ПЕРЕМЕННЫЕ Еще одно и очень полезное нововведение фирмы Borland-воз- можность использования нетипизированных параметров. Если тип формального параметра-переменной в заголовке блока не указан, то считается, что этот параметр нетипизирован, а соот- ветствующий ему фактический параметр может быть переменной любого типа. Нетипизированные параметры обычно используются в случае, когда тип данных несущественен. Такие ситуации чаще всего воз- никают при разного рода копированиях одной области памяти в другую, например, с помощью процедур BLOCKREAD; BLOCK- WRITE, MOVE и т.п. Программа из примера 26 иллюстрирует использование нетипи- зированных параметров. Процедура SWAPVAR осуществляет побайт- 6* 163
SUMMATR из примера 23, в которой суммируются две квадратные целочисленные матрицы А и В размерностью NxN и результат по- мещается в матрицу С: PROCEDURE SumMatr (var a., b, с; N : integer); var x : array [1..1] of integer absolute a; у : array [1..1] -of integer absolute b; z : array [1..1] of integer absolute c; i, j : integer; BEGIN {$R-} for i := 0 to N-l do for j := 0 to N-l do z[i*N+j] := x[i*N+j] + у [i*N+j] {$R+} END; Еще раз напомню, что в случае многомерных массивов их эле- менты располагаются в памяти так, что при переходе от младших адресов к старшим наиболее быстро меняется самый правый ин- декс массива. Объявление фиктивных переменных X, Y и Z в виде одномерных массивов длиной в 1 элемент каждый потребовало от- ключения контроля границ диапазона (директива {$R-}, см. гл.З). 9.6. РЕКУРСИЯ И ОПЕРЕЖАЮЩЕЕ ОПИСАНИЕ Рекурсия - это такой способ организации вычислительного про- цесса, при котором процедура или функция в ходе выполнения со- ставляющих ее операторов обращается сама к себе. Рассмотрим пример 28, который можно считать классическим - вычисление факториала. ъ ' Пример 28 PROGRAM Factorial; {$N+,E+} { Эмуляция сопроцессора } var n : integer; FUNCTION Fac (n : integer) : extended; BEGIN {Fac} if n < 0 then writein ('Ошибка в задании N’) else if n = 0 then Fac := 1 else Fac := n * Fac(n-l) END {Fac}; {----------------} BEGIN {main} repeat readln(n); writeln( ’n! = ’,Fac(n)) until EOF END {main}. 166
Программа вводит с клавиатуры целое число N и выводит на экран значение N!, которое вычисляется с помощью рекурсивной функции FAC. Для выхода из программы необходимо либо ввести достаточно большое целое число, чтобы вызвать переполнение разрядной сетки ПЭВМ при умножении чисел с плавающей запя- той, либо ввести CTRL-Z и ’’Ввод”. Для расширения границ N, при которых программа еще может вычислить факториал, в ней ис- пользуется тип EXTENDED, поэтому компиляция программы долж- на проходить в режиме использования или эмуляции сопроцессора (см. гл. 3). При выполнении правильно организованного рекурсивного блока осуществляется многократный последовательный переход от некото- рого текущего уровня организации алгоритма к нижнему уровню до тех пор, рока, наконец, не будет получено тривиальное решение по- ставленной задачи. В приведенном примере решение при N=0 три- виально и используется для остановки рекурсии. Рекурсивная форма организации алгоритма обычно выглядит изящнее итерационной и дает более компактный текст программы, но при выполнении, как правило, медленнее и может вызвать пе- реполнение стека (при каждом входе в блок его локальные пере- менные размещаются в особым образом организованной области па- мяти, называемой программным стеком). Рекурсивный вызов может быть косвенным. В этом случае блок обращается к себе опосредованно, путем вызова другого блока, в ко- тором содержится обращение к первому, например: Procedure А (1 : byte); begin В (i); end; Procedure В (j : Byte); begin A(j); end; Если строго следовать правилу,согласно которому каждый иден- тификатор перед употреблением должен быть описан, то такую про- граммную конструкцию использовать нельзя. Чтобы такого рода вы- зовы стали возможны, вводится опережающее описание: Procedure В (j : byte); forward; Procedure A (i : byte); begin В (i) ; 167
end; Procedure В; begin A(j); end; Как видим, опережающее описание заключается в том, что объ- является лишь заголовок процедуры В, а ее тело заменяется заре- зервированным словом FORWARD. Теперь в процедуре А можно использовать обращение к В - ведь она уже описана, точнее, извест- ны ее формальные параметры, и компилятор может правильным образом организовать ее вызов. Обратите внимание на то, что тело процедуры В начинается заголовком, в котором уже не указываются описанные ранее формальные параметры.
Г л а в а 10 МОДУЛИ Стандартный Паскаль [3] не предусматривает никаких механиз- мов раздельной компиляции частей программы с последующей их сборкой перед выполнением. Более того, последовательное проведе- ние в жизнь принципа обязательного описания любого объекта пе- ред его использованием делает фактически невозможным разработку разнообразных библиотек прикладных программ. Точнее, такие биб- лиотеки в рамках стандартного Паскаля могут существовать только в виде исходных текстов и программист должен сам включать в программу подчас весьма обширные тексты различных поддержива- ющих процедур, таких, как, например, процедуры матричной алгеб- ры, численного интегрирования, математической статистики и т.п. Вполне понятно поэтому стремление разработчиков коммерче- ских компиляторов Паскаля включать в язык средства, повышаю- щие его модульность. Чаще всего таким средством является разре- шение использовать внешние процедуры и функции, тела которых заменяются зарезервированным словом EXTERNAL. Разработчики Турбо-Паскаля пошли в этом направлении еще дальше, включив в язык механизм так называемых модулей. Мо- дуль - это автономно компилируемая программная единица, вклю- чающая в себя различные компоненты раздела описаний (типы, константы, переменные, процедуры и функции) и, возможно, неко- торые исполняемые операторы инициирующей части. По своей ор- ганизации и характеру использования в программе модули Турбо- Паскаля близки к модулям-пакетам (PACKAGE) языка программи- рования Ада [9]: в них также явным образом выделяется некото- рая "видимая" интерфейсная часть, в которой сконцентрированы описания глобальных типов, констант и переменных, а кроме того, приводятся заголовки глобальных процедур и функций. Появление объектов в интерфейсной части делает их доступными для других модулей и основной программы. Тела процедур и функций распо- лагаются в исполняемой части модуля, которая может быть скрыта от пользователя. Насколько сильно’ изменяются свойства языка Паскаль при вве- дении механизма модулей, свидетельствует следующее замечание 169
его автора Н.Вирта, сделанное им по поводу более позднего языка Модула-2: "Модули - самая важная черта, отличающая язык Моду- ла-2 от его предшественника Паскаля" [1, с.89]. Кстати, из пяти ос- новных отличий Модулы-2 от Паскаля, сформулированных Н.Вир- том в предисловии к книге ]1], три: модули, средства программиро- вания низшего уровня и процедурные типы - реализованы в Турбо- Паскале. Модули представляют собой прекрасный инструмент для разра- ботки библиотек прикладных программ и мощное средство модуль- ного программирования. Важной особенностью модулей является то обстоятельство, что компилятор Турбо-Паскаля размещает их про- граммный код в отдельном сегменте памяти. Максимальная длина сегмента не может превышать 64 Кбайт, однако количество одновре- менно используемых модулей ограничивается лишь доступной па- мятью, что дает возможность создавать весьма крупные программы. 10.1. СТРУКТУРА МОДУЛЕЙ Модуль имеет следующую структуру: UNIT <имя>; INTERFACE < интерфейсная часть > IMPLEMENTATION < исполняемая часть > [ BEGIN < инициирующая часть > ] END. Здесь UNIT - кодовое слово (англ, модуль); начинающее заголовок мо- дуля; < имя > - имя модуля (правильный идентификатор); INTERFACE - кодовое слово, начинающее интерфейсную часть модуля; IMPLEMENTATION - кодовое слово (англ. выполнение); начинает исполняемую часть; BEGIN - кодовое слово, начинающее инициирующую часть; (часть модуля BEGIN < инициирующая часть > необязательна); END - признак конца модуля. Таким образом, модуль состоит из заголовка и трех составных частей, любая из которых может быть пустой. 10.2. ЗАГОЛОВОК МОДУЛЯ И СВЯЗЬ МОДУЛЕЙ ДРУГ С ДРУГОМ Заголовок модуля состоит из кодового слова UNIT и следующего за ним имени модуля. Для правильной работы среды Турбо-Паска- ля и возможности подключения средств, облегчающих разработку крупных программ (см. 10.6), это имя должно совпадать с именем дискового файла, в который помещается исходный текст модуля. 170
Если, например, имеем заголовок Unit Global; то исходный текст соответствующего модуля должен размещаться в дисковом файле GLOBAL.PAS. Имя модуля служит для его связи с другими модулями и основной программой. Эта связь устанавлива- ется специальным предложением USES < сп.модулей > Здесь USES - кодовое слово (англ, использует); <сп.модулей> - список модулей, с которыми устанавливается связь; элементами списка являются имена модулей, отделяемые друг от друга запятыми, например: Uses CRT, Graph, Global; Если предложение USES... используется, оно должно открывать раздел описаний основной программы или следовать сразу за кодо- вым словом INTERFACE в модуле. 10.3. ИНТЕРФЕЙСНАЯ ЧАСТЬ Интерфейсная часть открывается кодовым словом INTERFACE. В этой части содержатся объявления всех глобальных объектов мо- дуля (типов, констант, переменных и блоков), которые должны стать доступными основной программе и/или другим модулям. При объявлении глобальных блоков в интерфейсной части указыва- ется только их заголовок, например: UNIT Cmplx; INTERFACE- type complex = record re, im : real end; Procedure AddC (x, у : complex; var z : complex); Procedure MulC (x, у : complex; var z : complex); Если теперь в основной программе написать предложение Uses Cmplx; то в программе станут доступными тип COMPLEX и две процеду- ры - ADDC и MULC из модуля CMPLX. Следует учесть, что все константы и переменные, объявленные в интерфейсной части модуля, равно как и глобальные объекты ос- новной программы, помещаются компилятором Турбо-Паскаля в общий сегмент данных (максимальная длина сегмента 65521 байт). Порядок появления различных разделов объявлений и их количест- во могут быть произвольными. Если в интерфейсной части объяв- ляются внешние блоки или блоки в машинных кодах (см. гл. 11), их тела (т.е. зарезервированное слово EXTERNAL в первом случае и машинные коды вместе со словом INLINE во втором) должны следовать сразу за их заголовками. В интерфейсной части модулей нельзя использовать опережающее описание. 171
10.4. ИСПОЛНЯЕМАЯ ЧАСТЬ Исполняемая часть начинается кодовым словом IMPLEMENTA- TION и содержит тела процедур и функций, объявленных в интер- фейсной части. В этой части могут также объявляться локальные для модуля объекты: вспомогательные типы, константы, перемен- ные и блоки, а также метки, если они используются в инициирую- щей части. Ранее объявленные в интерфейсной части глобальные процеду- ры и функции должны описываться в той же последовательности, в какой появляются их заголовки в интерфейсной части. Описанию глобального блока в исполняемой части должен предшествовать за- головок, в котором разрешается опускать список формальных пере- менных (и тип результата для функции), так как они уже описаны в интерфейсной части. Но если заголовок блока приводится в пол- ном виде, т.е. со списком формальных параметров и объявлением результата, он должен совпадать с заголовком, объявленным в ин- терфейсной части, например: Unit Cmplx; Interface type complex = record 1 re, im : real end; Procedure AddC (x, у : complex; var z : complex); Implementation Procedure Addc; begin z.re := x.re + y.re; z.im := x.im + у.im end; end. Локальные переменные и константы, а также все программные коды, порожденные при компиляции модуля, помещаются в общий сегмент памяти. 10.5. ИНИЦИИРУЮЩАЯ ЧАСТЬ Инициирующая часть завершает модуль. Она может отсутство- вать вместе с начинающим ее словом BEGIN или быть пустой - тогда за BEGIN сразу следует признак конца модуля (кодовое слово END и следующая за ним точка). В инициирующей части помеща- ются исполняемые операторы, содержащие некоторый фрагмент программы. Эти операторы исполняются до передачи управления основной программе и обычно используются для подготовки ее ра- боты. Например, в них могут инициироваться переменные, откры- ваться нужные файлы, устанавливаться связь с другими ПЭВМ по коммуникационным каналам и т.п.: 172
Unit Fi leText; Interface Procedure Print(s : string); Implementation var f : text; / const name = ’output.txt ’; Procedure Print; beg i n writeln(f, s) end; { начало инициирующей части: } begin assign(f, name); rewrite(f); { конец инициирующей части } end. 10.6. КОМПИЛЯЦИЯ МОДУЛЕЙ В среде ^Турбо-Паскаля имеются средства, управляющие способа- ми компиляции Модулей и облегчающие разработку крупных про- граммных проектов. В частности, определены три режима компиля- ции: COMPILE, МАКЕ и BUILD (см. гл.З). Режимы отличаются только способом связи компилируемого модуля или компилируемой основной программы с другими модулями, объявленными в пред- ложении USES. При компиляции модуля или основной программы в режиме COMPILE все упоминающиеся в предложении USES модули долж- ны быть предварительно откомпилированы и результаты их компи- ляции должны быть помещены в одноименные файлы с расшире- нием .TPU. Например, если в программе (в модуле) имеется пред- ложение Uses Global; то на диске в каталоге, объявленном опцией UNIT DIRECTORIES (см. гл.З), уже должен находиться файл GLOBAL.TPU. Файл с рас- ширением .TPU (от англ. Turbo-Pascal Unit) создается в результате компиляции модуля. В режиме МАКЕ компилятор проверяет наличие TPU-файлов для каждого объявленного модуля. Если какой-либо из файлов не обнаружен, система пытается отыскать одноименный файл с расши- рением .PAS, т.е. файл с исходным текстом модуля, и, если PAS- файл найден, приступает к его компиляции. Кроме того, в этом ре- жиме система следит за возможными изменениями исходного тек- ста любого используемого модуля. Если внесены какие-либо измене- ния в PAS-файл (исходный текст модуля), то независимо от того, есть ли уже в каталоге соответствующий TPU-файл или нет, систе- ма осуществляет его компиляцию перед компиляцией основной 173
программы, Е>рлее того, если изменения внесены в интерфейсную часть модуля, то будут перекомпилированы также и все другие мо- дули, обращающиеся к нему. Режим МАКЕ, таким образом, сущест- венно облегчает процесс разработки крупных программ с множест- вом модулей: программист избавляется от необходимости следить за соответствием существующих TPU-файлов и их исходного текста, так как система делает это автоматически. В режиме BUILD существующие TPU-файлы игнорируются, и система пытается отыскать (и компилировать) соответствующий PAS-файл для каждого объявленного в USES модуля. После компи- ляции в режиме BUILD программист может быть уверен в том, что учтены все сделанные им изменения в любом из модулей. Подключение модулей к основной программе и их возможная ком- пиляция осуществляется в порядке их объявления в предложении USES. При переходе к очередному модулю система предварительно отыскивает все модули, на которые он ссылается. Ссылки модулей друг на друга могут образовывать древовидную структуру любой сложности, однако запрещается явное или косвенное обращение мо- дуля к самому себе. Например, недопустимы следующие объявле- ния: Unit A; Unit B; Unit C; Interface Interface Interface Uses B; Uses A; Uses C; end. end. end. 10.7. ДОСТУП К ОБЪЯВЛЕННЫМ В МОДУЛЕ ОБЪЕКТАМ Пусть, например, создан модуль, реализующий арифметйку ком- плексных чисел (напомню, что такая арифметика ни в стандартном Паскале, ни в Турбо-Паскале не предусмотрена). К сожалению, в Турбо-Паскале нельзя использовать функции, значения которых имели бы структурированный тип (запись, например), поэтому арифметика комплексных чисел реализуется четырьмя приведенны- ми ниже процедурами. UNlt Cmplx; { } INTERFACE { } type complex = record re, im rea 1 end; Procedure AddC (x, у : complex; var z : complex); Procedure SubC (x, у : complex.; var z : complex); Procedure MulC (x,. у : complex; var z : complex); Procedure DivC (x, у : complex; var z : complex); const c : complex = (re : 0.1; im : -1); 174
{-----------------------------------------------} IMPLEMENTATION {-----------------------------------------------} PROCEDURE AddC; begin z. re : = x. re + у. re; z.im := x.im + у.im end {AddC}; PROCEDURE SubC; begin z. re : = x. re - у. re; z.im := x.im - у.im end {SubC}; PROCEDURE MulC; begin . z.re := x.re * y.re - x.im * y.im; z.im := x.re * y.im + x.im * y.re end {MulC}; PROCEDURE d'WC; var zz : real; begin zz := sqr(y.re) + sqr(y.im); z.re := (x.re * y.re + x.im * y.im) / zz; z.im := (x.re * y.im - x.im * y.re) I zz end {DivC}; END. Текст этого модуля нужно поместить в файл CMPLX.PAS. Те- перь можно написать программу, приведенную в примере 29. Пример 29 PROGRAM TestOfComplex; Uses Cmplx; var a, b, c : complex; BEGIN a.re := 1; a.im := 1; b.re := 1; b.im := 2; AddC(a, b, c); writeln('Сложение : \ c.re:5:l, c.im:5:1, ' i '); SubC(a, b, c); writeln('Вычитание ; ', c.re:5:l, c.im:5:1, ’ i'); MulC(a, b, c); writeln('Умножение : ', c.re:5:l, c.im:5:1, ’ i ’); DivC(a, b, c); writeln('Деление : c.re:5:l, c.im:5:1, ’ i '); END. Как видим, программе стали доступны все объекты, объявлен- ные в интерфейсной части. При необходимости мы можем переоп- ределить любой из этих объектов, как это произошло, например, с объявленной в модуле типизированной константой С. Переопределе- 175
ние Объекта означает, что вновь объявленный объект ’’закрывает’ ранее определенный в блоке одноименный объект. При необходи- мости мы можем получить доступ к ’’закрытому” объекту. Для этого нужно перед именем объекта поставить имя модуля и точку. Так,, j оператор < writeln(cmplx.c.re:5:1, cmplx.c.im:5:l,'i’) выведет на экран содержимое "закрытой" типизированной константы из примера 29. 1 10.8. СТАНДАРТНЫЕ МОДУЛИ В Турбо-Паскале имеется восемь стандартных модулей, в кото- рых содержится большое число разнообразных типов, констант, < процедур и функций. Этими модулями являются SYSTEM, DOS, CRT, PRINTER, GRAPH,» OVERLAY, TURBO3 и GRAPH3. Модули GRAPH, TURBO3 и GRAPH3 содержатся в одноименных TPU- файлах, остальные входят * в состав библиотечного файла TURBO.TPL. Лишь один модуль SYSTEM подключается к любой Турбо-Паскалевой программе автоматически, все остальные стано- вятся доступны только после указания их имен в списке, следую- i щем за кодовым словом USES. Ниже приводится краткая характеристика стандартных модулей. , В модуль SYSTEM входят все процедуры и функции стандарт- ного Паскаля, а также встроенные процедуры и функции Турбо-Па- j скаля, которые не вошли в другие стандартные модули (например, INC, DEC, GETDIR и т.п.). Как уже говорилось, модуль SYSTEM подключается к любой Турбо-Паскалевой программе независимо от | того, объявлен ли он в предложении USES или нет. Модуль PRINTER упрощает вывод текстов на матричный при- j нтер. В нем определяется файловая переменная LST типа TEXT, которая связывается с логическим устройством PRN. После подклю- чения модуля может быть выполнена* например, такая программа: Uses Printer; begin writein (LST, 'Турбо-Паскаль') end. ’ В модуле CRT сосредоточены процедуры и функции, обеспечи- вающие управление текстовым режимом работы экрана (см. гл.12). i С помощью входящих в модуль блоков можно перемещать курсор j в произвольную позицию экрана, менять цвет выводимых символов ) и окружающего их фона, создавать окна. Кроме того, в модуль включены также процедуры "слепого” чтения клавиатуры и управле- ния звуком. Модуль GRAPH содержит обширный набор типов, констант, про- j цедур и функций для управления графическим режимом работы экрана (см. гл. 13). С помощью блоков, входящих в модуль GRAPH, можно создавать самые разнообразные графические изобра- жения и выводить на экран текстовые надписи стандартными или 176
разработанными программистом шрифтами. Программы модуля GRAPH после соответствующей настройки могут поддержать любой тип аппаратных графических средств (CGA - на большинстве ПЭВМ, EGA - на IBM АТ последних выпусков, VGA - на IBM PS/2 и др.). Настройка на имеющиеся в распоряжении программиста технические средства графики осуществляется специальными про- граммами - драйверами, которые не входят в библиотечный файл TURBO.TPL, но поставляются вместе с ним. В модуле DOS собраны процедуры и функции, открывающие доступ Турбо-Паскалевым программам ко всем средствам дисковой операционной системы PC DOS (MS DOS). Блоки модуля OVERLAY понадобятся при разработке громозд- ких программ с перекрытиями. Как уже говорилось, Турбо-Паскаль обеспечивает создание программ, длина которых ограничивается лишь доступной памятью. Для большинства отечественных IBM- совместимых ПЭВМ доступная программе память составляет около 550 Кбайт (без инструментальных оболочек типа Norton Commander и без самой системы Турбо-Паскаль). Память такого размера доста- точна для большинства прикладных задач, тем не менее использо- вание программ с перекрытиями (см. гл. 11) снимает и это ограни- чение. Два библиотечных модуля TURBO3 и GRAPH3 введены для со- вместимости с ранней версией 3.0 системы Турбо-Паскаль.
Глава 11 ДРУГИЕ ВОЗМОЖНОСТИ ТУРБО-ПАСКАЛЯ 11.1. ВНЕШНИЕ ПРОЦЕДУРЫ (ФУНКЦИИ) С помощью внешних процедур (функций) можно вызывать из Турбо-Паскалевой программы процедуры или функции, написанные на языке ассемблера. Машинно-ориентированный язык ассемблера предоставляет квалифицированному программисту богатейшие воз- можности использования всех особенностей архитектуры ПЭВМ. Ас- семблерные программы выполняются значительно быстрее и зани- мают меньший объем памяти, чем Турбо-Паскалевые программы, однако низкий уровень языка существенно снижает производитель- ность труда программиста и резко усложняет отладку программ. Как правило, на языке ассемблера пишутся сравнительно неболь- шие фрагменты программ, учитывающие особенности архитектуры ПЭВМ, которые нельзя использовать в Турбо-Паскалевой програм- ме. Внешняя процедура (функция) в Турбо-Паскалевой программе объявляется заголовком, за которым следует зарезервированное сло- во EXTERNAL, например: Function LoCase (ch : char) : char; external; Procedure Swapping (var a, b ; N : word); external; Как видим, тело внешней процедуры (функции) отсутствует- его заменяет кодовое слово EXTERNAL. Для подключения ассемб- лерной программы необходимо предварительно ее откомпилировать и получить OBJ-файл с перемещаемым кодом программы. Непос- редственно перед описанием внешней процедуры (функции) в тело основной (Турбо-Паскалевой) программы вставляется директива компилятора {$L <имя файла>}, где <имя файла>-имя OBJ- файла. Диск и каталог, в котором следует искать этот файл, если он не обнаружен в текущем каталоге, указывается опцией OPT1ONS/DIRECTORIES/OBJECT DIRECTORIES (см. гл. 3). 178
Разумеется, в рамках этой книги совершенно невозможно рас- смотреть методику разработки программ на языке ассемблера - это предмет самостоятельной книги, например [2]. Тем не менее, риск- ну дать один совет. Прежде чем браться за разработку ассемблер- ной процедуры, тщательно взвесьте специфические потребности раз- рабатываемой программы и возможности Турбо-Паскаля: может ока- заться, что, оставаясь только в рамках Турбо-Паскаля, Вы решите задачу намного проще и быстрее, хотя, разумеется, Ваша програм- ма и не будет оптимальной по скорости вычислений и расходуемой памяти. Замечено, что производительность труда программиста поч- ти не зависит от языка и составляет 10...20 операторовх отлаженной программы в день [4]. Один оператор Турбо-Паскаля реализуется десятками машинных команд, вот почему использование языков высокого уровня, к । которым относится Турбо-Паскаль, значительно ускоряет разработку программ. Перед передачей управления внешней процедуре (функции) Турбо-Паскалевая программа заталкивает параметры обращения в программный стек в том порядке, как они перечислены в заголовке процедуры (функции). Ассемблерная процедура должна сохранить регистры BP, SP, SS и DS центрального процессора в самом начале своей работы и восстановить содержимое этих регистров перед воз- вратом управления в Турбо-Паскалевую программу. Остальные реги- стры можно не сохранять и соответственно не восстанавливать. Параметры могут передаваться по ссылке или по значению. Ес- ли параметр передается по ссылке, в стек заталкивается указатель, содержащий абсолютней адрес параметра; если по значению - в стек заталкивается сам параметр, точнее - его значение. Все пара- метры-переменные, т.е. параметры, объявленные в заголовке с предшествующим словом VAR, всегда передаются по ссылке. Пара- метры-значения могут передаваться по ссылке или по значению, в зависимости от длины внутреннего представления соответствующего параметра. В общем случае используется, следующее правило: если длина внутреннего представления параметра-значения составляет 1, 2 или 4 байта, само значение параметра заталкивается в стек, Точ- но так же через стек передаются и все вещественные данные дли- ной в 4, 6, 8 и 10 байт (в версии 4.0 эти данные передаются через стек сопроцессора 8087/80287, в версии 5.0 - через стек центрального процессора 8086/80826). Во всех остальных случаях, если длина внутреннего представления больше 4 байт, соответствующий пара- метр передается по ссылке. Ассемблерные функции должны возвращать результат „с по- мощью регистров центрального процессора или сопроцессора в за- висимости от длины внутреннего представления результата по< сле- дующим правилам: - длиной в 1 байт - в регистре AL; - длиной в 2 байта - в регистре АХ; . , - длиной в 4 байта - в регистрах DX:AX (старшее слово в DX); ’ - тип REAL (6 байт) - в регистрах DX.BX.AX; « , - типы SINGLE, DOUBLE, EXTENDED и COMP - через съек со- процессора 8087/80287; 179
- указатели - в регистрах DX:AX (сегмент в DX); - строки возвращаются по ссылке: адрес начала строки помеща- ется в DX:AX (сегмент в DX). Все ассемблерные процедуры должны размещаться в сегменте с именем CODE, а имена процедур и функций должны быть объяв- лены директивой PUBLIC. Локальные переменные необходимо раз- мещать в сегменте с именем DATA. Все имена, объявленные в ин- терфейсной части модулей Турбо-Паскалевой программы, становятся доступны ассемблерной процедуре (функции) после их объявления директивой EXTRN. В 4-й части книги, в 16.2, описывается модуль TEXTCRT.PAS, в состав которого входит ассемблерная программа VIDEOMEMASM. Эта программа может служить примером, иллюстрирующим сказан- ное. 11.2. ИСПОЛЬЗОВАНИЕ ВСТРОЕННЫХ МАШИННЫХ КОДОВ В Турбо-Паскале имеется возможность включения в программу небольших фрагментов, написанных непосредственно в машинных кодах. Для этого используется зарезервированное слово INLINE, за которым в круглых скобках следует один или несколько элементов машинного кода, разделяемых косыми чертами. Элемент кода, в свою очередь, строится из одного или более элементов данных, раз- деленных знаками или В качестве элемента данных может использоваться целая кон- станта, идентификатор (переменной, константы или функции) или ссылка на счетчик адреса (”*”). Каждый элемент данных вызывает генерацию 1 или 2 байт кода программы. Значение этого кода по- лучается сложением или вычитанием элементов данных в соответ- ствии с разделяющим их знаком. Значением идентификатора пере- менной, константы, функции является адрес соответствующего объ- екта, значением ссылки на счетчик адреса является тот адрес, по которому будет размещаться следующий байт кода. Элемент кода будет генерировать 1 байт кода, если этот элемент состоит только из целых констант и значение результата не превы- шает 1 байта, т.е. находится в диапазоне от 0 до 255. Если значе- ние превышает 255 или элемент кода содержит ссылку на счетчик адреса, генерируются 2 байта. Знаки “>” и "<” могут использовать- ся для отмены автоматического выбора размера генерируемого кода. Если элемент кода начинается со знака "<", в код заносится только один младший байт, даже если само значение занимает 2 байта. Наоборот, если элемент начинается со знака ">", в код заносится 2 байта (старший байт может оказаться нулевым). Значением идентификатора, как уже говорилось, является сме- щение соответствующего объекта. Если переменная, глобальная, сме- щение задается относительно сегмента данных, хранящегося в реги- стре DS, если переменная локальная, - относительно сегмента стека (регистр SP). Базовым сегментом типизированной константы являет- ся сегмент кода (регистр CS). В следующем примере приводятся две короткие процедуры, с 180
помощью которых можно ввести или вывести данные через любой порт ПЭВМ: FUNCTION InPort(Port: Word): Word; var pp: Word; cc:Char; BEGIN pp:=port; in 1ine( $8b/$96/pp/ { mov DX,pp[bp] } $EC/ { IN ( AX,DX } $88/$86/cc); { mov cc[bp],AX } InPort:=ord(cc); END; PROCEDURE OutPort(Port,Bt: Word); var pp: Word; cc:Char; BEGIN ' pp:=port; cc:=chr(Bt); in 1ine( $8a/$86/cc/ { mov AX,cc[bp] } $8b/$96/pp/ { mov DX,pp[bp] } $EE) { OUT DX,AX } END; Операторы INLINE могут произвольным образом смешиваться с другими операторами Турбо-Паскаля, однако при выходе из проце- дуры (функции) содержимое регистров BP, SP, DS и SS должно быть таким же, как и при входе в нее. 11.3. ОБРАЩЕНИЕ К ФУНКЦИЯМ ОПЕРАЦИОННОЙ СИСТЕМЫ Турбо-Паскаль предоставляет программисту практически неогра- ниченные возможности использования любых функций стандартной операционной системы PC DOS (MS DOS)1. При внимательном ана- лизе материала этой книги Вы, очевидно, заметите, что большую его часть составляет описание многочисленных библиотечных про- цедур и функций. Собственно язык Паскаль весьма прост и лако- ничен, что, по мнению многих специалистов, и послужило одной из причин его широкого распространения. Значительная же часть библиотечных процедур и функций является по существу своеобраз- ным интерфейсом между языковыми средствами Турбо-Паскаля и функциями операционной системы. Разумеется, можно только при- ветствовать усилия разработчиков Турбо-Паскаля по созданию мощ- ных библиотек TURBO.TPL и GRAPH.TPU, однако ясно, что таким способом невозможно запрограммировать все допустимые обраще- ния к средствам ДОС. Вот почему в Турбо-Паскаль включены две процедуры, с помощью которых программист может сам сформиро- вать вызов той или иной функции ДОС. 1 Personal Computer Disk Operation Sistem (Microsoft DOS) - название двух прак- тически совпадающих операционных систем для IBM-совместимых ПЭВМ. 181
Следует учесть, что единственным механизмом обращения к функциям операционной системы является инициация программно- го прерывания. Прерывание - это особое состояние вычислительного процесса. В момент Прерывания нарушается нормальный порядок выполнения команд программы и управление передается специаль- ной процедуре, которая входит в состав ДОС и называется процеду- рой обработки прерывания. Каждое прерывание характеризуется в рамках ДОС порядковым номером и связано со своей процедурой обработки. В архитектуре центрального процессора ПЭВМ предус- мотрены прерывания двух типов: аппаратные и программные. Ап- паратные прерывания создаются схемами контроля и управления ПЭВМ и сигнализируют операционной системе о переходе какого- либо устройства в новое состояние или о возникновении неисправ- ности. Программные прерывания инициируются при выполнении одной из двух специальных команд (INT или INTO) и служат для обращения к средствам ДОС. Всего в ДОС имеется около 40 программных прерываний, каж- дое из которых может активизировать одну или несколько функций ДОС. Одно из прерываний - с номером 33 ($21) обеспечивает доступ к 85 функциям, а всего в ДОС имеется более 200 разнообразных функций. Описываемые ниже процедуры входят в состав библиотечного модуля DOS и становятся доступными после объявления USES DOS. При возникновении программного прерывания в большинстве случаев необходимо передать процедуре обработки прерывания не- которые параметры, в которых конкретизируется запрос нужной функции. Эти параметры, а также выходная информация передают- ся от программы к процедуре и обратно через регистры централь- ного процессора. В модуле DOS для этих целей определен специ- альный тип: type Registers = record , ‘ case integer of 0 : (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags : word); Г : (AL, AH, BL, BH, CL, CH, DL, OH : byte) end; Как видим, этот тип имитирует регистры центрального процес- сора и дает возможность обращаться к ним как к 16-битным или 8- битным регистрам. Процедура INTR. С ее помощью инициируется программное прерывание с требуемым номером; формат обращения INTR ( <N>, «регистры» ) Здесь < N > - выражение типа BYTE, означающее номер прерыва- ния; < регистры > - переменная типа REGISTERS, в которой процеду- ре обработки прерывания передается содержимое регистров и воз- вращается выходная информация. Так, прерывание с номером 18 ($12) возвращает в регистре АХ объем оперативной памяти ПЭВМ. Короткая программа (пример 30) выведет на экран сообщение об этом объеме. 182
Пример 30 PROGRAM IntrDem; Uses DOS; var r : registers; BEGIN Intr ($12, r); writein (’Объем памяти = ’, г.AX, ’ Кбайт') END. Процедура MSDOS. Инициирует прерывание с номером 33 *($21); формат обращения MSDOS ( < регистры > ) Здесь < регистры >-переменная типа REGISTERS, содержащая значения регистров на входе и выходе процедуры обработки преры- вания. Программное прерывание с номером 33 ($21) стоит особняком: как уже говорилось, оно дает доступ к большому количеству функ- ций ДОС (этим прерыванием вызывается 85 функций). Рассматри- ваемая процедура полностью эквивалентна вызову процедуры INTR с номером прерывания 33. Программа примера 31 выведет на эк- ран версию операционной системы: Пример 31 PROGRAM MsDosDemo; Uses DOS; var r : registers; BEGIN r.AH := $30; MsDos (r); writein (’Версия операционной системы: r.AL, '.’, r.AH) END. 11.4. ПОДДЕРЖКА ПРОЦЕДУР ОБРАБОТКИ ПРЕРЫВАНИЙ При написании процедур обработки прерываний существенными являются два обстоятельства. Во-первых, процедура обработки пре- рывания не должна искажать работу прерванной программы. Для этого необходимо сначала сохранить регистры центрального процес- сора, а перед выходом из процедуры - восстановить их. Во-вторых, процедура должна строиться по принципу реентерабельности (по- вторной входимости): ее работа может быть прервана в любой мо- мент другими прерываниями и ДОС может обратиться к соответст- вующей функции до завершения обработки предыдущего прерыва- ния. Турбо-Паскаль предоставляет программисту возможность напи- сать процедуру обработки прерывания на языке высокого уровня, хотя обычно такие процедуры пишутся на языке ассемблера. 183
Турбо-Паскалевая процедура обработки прерывания должна на- чинаться зарезервированным словом INTERRUPT (англ, прерыва- ние), например: Procedure IntProc (Flags, CS, IP, AX, BX, CX, DX, SI, DF, DS, ES, BP : word); inerrupt; begin end; ' Список формальных параметров должен быть именно таким, как в примере: через эти параметры вызова все регистры прерван- ной программы становятся доступны процедуре обработки прерыва- ния. Кодовое слово INTERRUPT приводит к генерации специаль- ных машинных кодов, обеспечивающих заталкивание регистров в стек при входе в процедуру и извлечение их из стека перед выхо- дом из нее. При входе в процедуру: ‘ push ax push bx push ex push . dx push s i push d i push ds push es push bp mow bp, si sub sp, LocalSize mov ax, SEG ^ATA mov ds, ax При выходе из процедуры: . mov sp, bp pop bp pop es pop ds pop di pop si pop dx pop ex pop bx pop ax i rep В самой процедуре обработки прерывания не рекомендуется об- ращение к другим функциям ДОС, так как некоторые из них, в том числе все функции ввода-вывода, нереентерабельны. Для связи с любыми процедурами прерываний, а следовательно, и с процедурами, написанными программистом, используются век- торы прерываний - четырехбайтные абсолютные адреса точек входа в эти процедуры. Векторы прерываний располагаются в младших адресах оперативной памяти, начиная с нулевого адреса: прерыва- 184'
ние номер 0-по адресу 0, номер 1-по адресу 1*4=4, номер N-no адресу N*4. С помощью следующих двух процедур программист может прочитать содержимое любого вектора или установить его новое значение. Процедура GETINTVEC. Возвращает вектор прерывания с указан- ным номером; формат обращения GETINTVEC ( <N>, <вектор> ) Здесь < N > - выражение типа BYTE, содержащее номер прерыва- ния; < вектор > - переменная типа POINTER, в которой возвращается адрес точки входа в процедуру обработки прерывания. Программа примера 32 выводит на экран содержимое всех нену- левых векторов прерываний. t Пример 32 Uses DOS; I var i : byte; p ; pointer; BEGIN for i := 0 to 255 do begin GetlntVec (i, p); if (Seg (p'j <> 0) or (Ofs (p") <> 0) then writein (' N =', i:3, ' Seg =', Seg (р*):5, ’ Ofs =', Ofs (p".):5) end END. Процедура SETINTVEC. Устанавливает новое значение вектора прерывания; формат обращения SETINTVEC ( <N>, <адрес> ) Здесь < адрес >-выражение типа POINTER, в котором задается ад- рес точки входа в процедуру обработки прерывания. При нормальном завершении Турбо-Паскалевой программы она выгружается из памяти, что делает невозможным разработку рези- дентных в памяти процедур обработки прерываний. Вы можете прекратить работу программы и оставить ее в памяти, если вос- пользуетесь процедурой KEEP. Процедура KEEP. Завершает работу программы и оставляет ее в памяти; формат обращения KEEP ( <код> ) Здесь < код > - выражение типа WORD, в котором содержится код завершения программы. Код завершения представляет собой фактически единственный механизм передачи сообщений от запущенной программы к про- грамме, которая ее запустила. Он может быть проанализирован в вызывающей программе с помощью функции DOSEXITCODE. Функция DOSEXITCODE. Возвращает значение типа WORD - код завершения подчиненной программы. Обращение DOSEXITCODE 185
11.5. ЗАПУСК ВНЕШНИХ ПРОГРАММ Из Турбо-Паскалевой программы можно запустить любую дру- гую готовую к работе программу. Для этого используется процедура ЕХЕС из библиотечного модуля DOS; формат обращения к проце- дуре ЕХЕС ( <имя>, < параметры > ) Здесь < имя > - выражение типа STRING, в котором задается имя файла с вызываемой программой; < параметры > - выражение типа STRING, в котором передаются параметры вызова. Имени запускаемой программы должен предшествовать путь к файлу. Параметры передаются запускаемой программе в виде тек- стовой строки и могут быть проанализированы ею с помощью двух следующих функций. Функция PARAMCOUNT. Возвращает общее количество парамет- ров вызова программы (значение типа WORD). Обращение PARAMCOUNT Параметры вызова обычно следуют в командной строке ДОС сразу за именем вызываемой программы и отделяются от этого имени и друг от друга пробелами, например: C:\TURBO MYPROG.PAS C:\SIAM A:\SYSTEM1.SIA Здесь MYPROG.PAS и A:\SYSTEMl.SIA-napaMeTpbi, передаваемые программам TURBO и SIAM. При вызове программы непосредственно из среды Турбо-Паска- ля ей можно передать параметры с помощью опции OPTIONS/ PARAMETERS (см. гл. 3). Функция PARAMSTR. Возвращает значение типа STRING, соот- ветствующее нужному параметру вызова, формат обращения PARAMSTR ( <N> ) Здесь <N> - выражение типа WORD, задающее порядковый номер параметра. Использование процедуры ЕХЕС имеет ряд специфических осо- бенностей. Прежде всего необходимо отметить, что сама Турбо-Пас- калевая вызывающая программа остается резидентной в памяти, поэтому она не должна занимать всю оперативную память. Объем выделяемой программе памяти регулируется опцией OPTIONS/ COMPILER/MEMORY SIZE (см. гл. 3). По умолчанию параметры LOW HEAP LIMIT и HIGH HEAP LIMIT этой опции таковы (0 и 655360 байт соответственно), что Турбо-Паскалевая программа зани- мает весь доступный объем памяти и вызываемая программа не будет загружена. Полезно включить в текст вызывающей програм- мы директиву компилятора, в которой изменяются принятые по умолчанию размеры памяти. Это можно сделать, например, так: {$М 2048, 0, 0} Такая директива ограничивает используемую программой область стека объемом 2 Кбайт и исключает возможность использования в 186
ней динамической памяти. Разумеется, Вы можете установить и другие значения параметров в этой директиве. Далее, специфические особенности исполнения Турбо-Паскалевых программ требуют изменения стандартных значений некоторых век- торов прерываний. К ним относятся векторы со следующими шест- надцатиричными номерами: $00, $02, $18, $23, $24, $34, $35, $36, $37, $38, $39, $ЗА, $ЗВ, $3C, '$3D, $ЗЕ, $3F, $75. Начальные значения этих векторов сохраняются в восемнадцати переменных с именами SAVEINTXX из библиотечного модуля SYSTEM, где XX - шестнадцатиричный номер прерывания. Поэтому непосредственно перед запуском внешней программы и сразу после возврата из нее рекомендуется вызвать библиотечную процедуру без параметров SWAPVECTORS, которая обменивает содержимое векто- ров прерывания и перечисленных переменных. Программа примера 33 воспринимает с клавиатуры любую ко- манду ДОС, затем вызывает командный процессор COMMAND.COM операционной системы и передает ему эту команду. Пример 33 PROGRAM ExecDemo; {$М 1024, 0, 0} Uses DOS; var st : string [79]; BEGIN write (’Введите команду ДОС: ’); readin (st); if st <> ’ ’ then begin st := ’/С ’+st; SwapVectors; Exec (GetEnv (’COMSPEC’), st); SwapVectors end END. Обратите внимание: для указания файла COMMAND.COM и пу- ти к нему используется обращение к библиотечной функции GETENV, с помощью которой можно получить параметры настрой- ки операционной системы. В частности, параметр COMSPEC опре- деляет спецификацию файла, содержащего командный процессор. С помощью несложной программы из примера 34 можно выве- сти на экран Вашей ПЭВМ список всех параметров настройки ДОС. Пример 34 PROGRAM EnvParDemo; Uses DOS; var i : integer; 187
BEGIN > for i :=. 0 to EnvCount do writein (EnvStr (i)) ' END. Функция ENVCOUNT. Возвращает значение типа INTEGER, в ко- тором содержится общее количество установленных в ДОС парамет- ров. Обращение ENVCOUNT Функция ENVSTR. Возвращает значение типа STRING, содержа- щее имя и значение нужного параметра настройки операционной системы; формат обращения ENVSTR ( <N> ) Здесь <N> - выражение типа INTEGER, в котором указывается но- мер параметра. Эта функция возвращает строку типа NAME = VALUE, где NAME - имя, a VALUE - значение соответствующего параметра на- стройки. Функция GETENV. Возвращает значение типа STRING, в кото- ром содержится параметр настройки ДОС; формат обращения GETENV ( < имя > ) Здесь < имя >-выражение типа STRING, определяющее имя пара- метра. Эта функция имеет параметр обращения NAME и возвращает значение VALUE (см. функцию ENVSTR). 11.6. ОВЕРЛЕЙ Как уже говорилось в гл.10, максимальный размер модуля не может превышать 64 Кбайт, однако количество модулей не ограни- чено, что дает возможность разрабатывать весьма крупные програм- мы, занимающие, например, всю доступную оперативную память ПЭВМ (приблизительно 580 Кбайт). Однако в некоторых случаях и этот объем может оказаться недостаточным. Турбо-Паскаль предо- ставляет в распоряжение программиста простой и достаточно эф- фективный механизм оверлея, с помощью которого Вы сможете со- здавать программы практически неограниченной длины (следует оговориться, что речь идет только о длине кода программы; два важных размера - длина стека данных и программного стека - в Турбо-Паскале не может превышать 64 Кбайт независимо от струк- туры программы). Оверлей - это такой способ использования оперативной памяти, при котором в один и* тот же участок памяти, называемый овер- лейным буфером, попеременно по мере надобности загружаются различные оверлейные (перекрывающиеся) модули. При этом все оверлейные модули в готовом к работе виде хранятся на диске, а в оперативной памяти в каждый момент находится лишь один актив- ный модуль и, возможно, небольшое число других неактивных мо- дулей. 188
Пусть, например, программа состоит из главной части MAIN и двух модулей А и В. Пусть также LM, LA и LB - длина соответст- венно главной части и обоих модулей, причем LA>LB. Тогда нео- верлейная программа займет в памяти LM + LA+LB байт, в то вре- мя как оверлейная программа лишь LM+LA (рис. 20). Рис.20. Структуры неоверлейной (а) и оверлейной (Ь) программ При исполнении оверлейной программы в память первоначаль- но загружается главная часть и один из модулей, например А. Ес- ли в процессе исполнения программы встретится обращение к мо- дулю В, программа приостановит свою работу, с диска в оверлей- ный буфер будет загружен модуль В (модуль А при этом будет ча- стично уничтожен), после чего программа продолжит свою работу. Если в дальнейшем встретится обращение к А, точно таким же об- разом будет загружен модуль А, причем загрузка нужных модулей в оверлейный буфер осуществляется автоматически и программисту не нужно об этом заботиться. Описанный механизм выявляет главное преимущество оверлей- ной структуры: объем памяти, занимаемой оверлейной программой, определяется длиной главной части и наибольшего из перекрываю- щихся модулей, в то время как при неоверлейной структуре в этот объем входит суммарная длина всех модулей. Чём больше в про- грамме оверлейных модулей и чем меньше длина наибольшего из них, тем больший выигрыш в памяти дает оверлейная структура. Однако совершенно очевиден и главный недостаток таких структур: на каждую загрузку оверлейного модуля с диска в оверлейный бу- фер требуется дополнительное время, поэтому оверлейная програм- ма будет в общем случае исполняться с меньшей скоростью. Работа оверлейных программ обеспечивается с помощью проце- дур и функций библиотечного модуля OVERLAY, входящего в биб- лиотечный файл TURBO.TPL. Оверлейные программы нужно создавать в такой последователь- ности: 1) Вначале необходимо выделить главную часть программы и разбить оставшуюся часть на несколько модулей. Отметим, что ни- каких дополнительных ограничений на модули по сравнению с описанными в гл. 10 не накладывается, за одним исключением: в оверлейных модулях нельзя использовать процедуры обработки прерываний. Желательно продумать состав модулей таким образом. 189
чтобы минимизировать количество их перезагрузок в буфер в про- цессе исполнения программы. 2) В главной части программы необходимо указать с помощью директив компилятора вида {$О <имя>} те модули, которые бу- дут оверлейными, например: Program Main; Uses CRT, DOS, Graph, Overlay, UnitA, UnitB; {$0 DOS} {$0 UnitA} {$0 UnitB} Учтите, что из всех стандартных библиотечных модулей, описан- ных в гл. 10, только один модуль DOS может быть оверлейным, ос- тальные модули (CRT, Graph, Printer и т.д.) не могут объявляться оверлейными. 3) Необходимо предусмотреть перед первым по логике работы программы обращением к какому-либо оверлейному модулю вызов процедуры инициализации оверлея OVRINIT. Здесь же, если это необходимо, следует установить размер оверлейного буфера и ука- зать возможность использования расширенной памяти (см. ниже). 4) Наконец, в начале главной программы и каждого оверлейного модуля необходимо поместить директивы компилятора {$0 + } и {$F+} или установить опции OPTIONS/COMPILE/FORCE FAR CALLS и OPTIONS/COMPILE/OVERLAYS ALLOWED (см. гл. 3) в состояние ON, после чего следует откомпилировать программу на диск. Программа готова к работе. Таким образом, все процедуры и функции в оверлейной про- грамме должны использовать дальнюю модель вызова - это обяза- тельное условие. Директива {$0 + }, строго говоря, не является обя- зательной. Если она указана, то при вызове любой процедуры или функции программа будет помещать все фактические параметры обращения в резидентную (неоверлейную) часть памяти, что позво- ляет из одного оверлейного модуля вызывать процедуры и функ- ции любого другого оверлейного же модуля. Процедура OVRINIT. Инициализирует оверлейный файл; формат обращения: OVRINIT ( <имя> ) Здесь < имя > - выражение типа STRING, означающее имя файла с оверлейной частью программы. При компиляции оверлейной программы создается специальный файл. с именем, совпадающим с именем главной программы, и расширением .OVR. В этот файл компилятор помещает все овер- лейные модули, из него же эти модули будут загружаться в овер- лейный буфер в процессе исполнения программы. Файл с оверлей- ной, частью программы должен размещаться в том же каталоге, что и файл с главной частью (с расширением .EXE). Проиллюстрируем сказанное примером 35. Пусть файл с глав- ной частью программы называется MAIN.PAS; в программе исполь- зуются два оверлейных модуля, помещаемые в файлы UNITA.PAS и UNITB.PAS. 190
I Пример 35 PROGRAM OverlayDemo; {Текст главной программы нужно поместить в файл MAIN.PAS.} {$F+,0+} Uses Overlay, UnitA, UnitB; {$0 UnitA} {$0 UnitB} BEGIN Ovrlnit (’MAIN.OVR'); SubA END. {--------------------------------------------------} %UNIT UnitA; {Текст модуля нужно поместить в файл UNITA.PAS.} {$F+,0+} INTERFACE Uses UnitB; Procedure SubA; IMPLEMENTATION PROCEDURE SubA; const st = 'Работает модуль ’; BEGIN writein (st, 'A'); SubB (st) END; END. {------------------------------------------------------------------} UNIT UnitB; {Текст модуля нужно поместить в файл UNITB.PAS.} {$F+,0+} . INTERFACE Procedure SubB (s : string); IMPLEMENTATION PROCEDURE SubB; BEGIN writein (s, ’B') END; END. Обычно размер оверлейного буфера автоматически определяется таким, что в нем может разместиться самый крупный из всех овер- лейных модулей. Программист может увеличить размер буфера. Тогда при загрузке в буфер очередного модуля программа проверит, достаточно ли в буфере свободного места, и, если места достаточно, загрузит новый модуль сразу за старым, который таким образом не будет уничтожен. Такой механизм способствует минимизации по- терь времени на перезагрузку модулей. Если установлен очень большой размер буфера, то в нем, возможно, смогут разместиться все оверлейные модули и потери времени будут сведены к нулю, 191
однако в этом случае оверлейная структура становится просто не- нужной. Процедура OVRSETBUF. Устанавливает больший, чем по умолча- нию, размер оверлейного буфера; формат обращения OVRSETBUF ( < длина > ) Здесь < длина >-выражение типа LONGINT, определяющее новую длину буфера. Новая длина задается в байтах и не может быть меньше той, что устанавливает сама система автоматически. Расширение буфера идет за счет соответствующего уменьшения доступной динамиче- ской памяти, поэтому к моменту вызова этой процедуры куча дол- жна быть пустой. 1 Функция OVRGETBUE. Возвращает значение тийа LONGINT, со- держащее текущий размер кучи. Обращение OVRGETBUF Если Ваша ПЭВМ относится к классу компьютеров типа IBM АТ и в ней имеется расширенная память (общий объем памяти свыше 1024 Кбайт), Вы можете использовать эту память для разме- щения в ней оверлейного'файла .OVR. Поскольку время доступа к расширенной памяти значительно меньше времени чтения с диска, такое размещение увеличивает скорость исполнения оверлейной программы. Процедура OVRINITEMS. Обеспечивает использование расширен- ной памяти. Обращение OVRINITEMS При обращении к этой процедуре программа прежде всего про- верит, подключена ли к Вашей ПЭВМ так называемая EMS-память (от англ. Expanded Memory Specification - расширенная память, удов- летворяющая стандарту фирм Lotus/Microsoft/Intel) нужного для раз- мещения оверлейной части объема. Если это так, то оверлейный файл будет считан в расширенную память, сам файл будет закрыт и программа будет считывать оверлейные модули из этой памяти. Если же расширенная память отсутствует или ее объем недостато- чен для размещения оверлейного файла, обращение к процедуре игнорируется и программа будет считывать оверлейные модули с диска. 11 .7. ПРЯМОЕ ОБРАЩЕНИЕ К ПАМЯТИ И ПОРТАМ ВВОДА-ВЫВОДА В Турбо-Паскале имеется пять предварительно объявленных массивов: MEM, MEMW, MEML, PORT и PORTW. Первые три обеспечивают доступ к любому участку оперативной памяти по аб- солютному адресу, два другие - доступ к портам ввода-вывода. Компонентами массива МЕМ являются данные типа BYTE, мас- сива MEMW - типа WORD, массива MEML - типа LONGINT. Обра- щение к элементам этих массивов, т.е. их индексация, имеет спе- циальный вид: каждый индекс представляет собой абсолютный ад- рес и состоит из двух выражений типа WORD; первое дает сегмент- 192
ную часть адреса, второе - смещение; выражения разделяются двое- точием, например: Мет [$0000:$1000] := 0; DataMem : = MemW [Seg(р):Ofs(р)]; MemLong := MemL [64:i*SizeOf(rea 1)] ; Как следует из технического описания операционной системы PC DOS (MS DOS), в ПЗУ по адресу $F000:$FFFE зашит байт-указа- тель типа компьютера. Программа из примера 36 прочтет этот байт и выведет на экран тип Вашей ПЭВМ. Пример 36 PROGRAM DMA_Demo; BEGIN Write (’ Тип компьютера: ’); case Mem [$F000:$FFFE] of $FF : wr i te In ( ’ PC ’); $FE : writein ('XT'); $FD : writein ( ’PCjr’); $FC : writein (’AT’); $F9 : writein ('совместимый c PC') end END. Компонентами массива PORT являются байты (тип BYTE), а массива PORTW - слова (тип WORD). Индексами этих массивов должно быть выражение типа BYTE, указывающее номер нужного порта. Присвоение ’значения элементу массива PORT или PORTW приведет к записи в порт, упоминание элемента в выражении - к чтению из порта. Компоненты массивов PORT и PORTW нельзя передавать в качестве параметров процедурам или функциям. Эти идентификаторы нельзя употреблять без индексных выражений. 7 - Фаронов
ЧАСТЬ III УПРАВЛЕНИЕ ТЕХНИЧЕСКИМИ СРЕДСТВАМИ ПЭВМ
Г л а в a 12 УПРАВЛЕНИЕ ЭКРАНОМ В ТЕКСТОВОМ РЕЖИМЕ Весь рассматривавшийся ранее материал не затрагивал вопросов о том, как устроены и работают аппаратные средства ПЭВМ: нам было достаточно знать, что делает та или иная функция, и мы не обращали внимания на то, как она реализуется. Начиная с этой главы, мы будем рассматривать такие аспекты Турбо-Паскаля, кото- рые в значительной степени связаны со спецификой аппаратных средств ПЭВМ. Поэтому мне придется в начале этой и каждой сле- дующей главы более или менее подробно пояснять принцип рабо- ты тех или иных устройств ПЭВМ, а также касаться основных со- глашений дисковой операционной системы PC DOS. Те из читате- лей, которые сочтут эти сведения ненужными, могут, разумеется, их пропустить и сразу перейти к специфическим процедурам и функциям Турбо-Паскаля. Однако я убежден, что программы высо- кого класса нельзя создавать для некоей абстрактной машины, кото- рая "как-то там" делает то, что нам нужно. Только детальный и полный учет специфики аппаратуры делает программу по-настоя- щему ценной. 12.1. ТЕХНИЧЕСКИЕ ОСОБЕННОСТИ ВИДЕОТЕРМИНАЛОВ Видеотерминал включает в себя экран ПЭВМ с электронно-луче- вой трубкой, а также комплекс технических средств, обеспечиваю- щих появление на экране изображения. В основе его работы есть много общего с телевизором: в результате соударения пучка элект- ронов с поверхностью экрана, покрытой светящимся веществом (люминофором), образуется светящаяся точка - пиксель, причем ин- тенсивность ее свечения зависит от энергии электронного пучка. Изменяя энергию пучка, можно получить разную яркость пиксе- ля - от темной до максимальной. Электронный луч обегает экран слева направо и сверху вниз 25 раз в секунду, последовательно фор- мируя множество близко расположенных пикселей, которые, слива- ясь друг с другом, воспринимаются глазом как единое целое. Пожалуй, единственным и наиболее важным отличием описан- ного механизма от способа образования изображения на экране 7 195
обычного телевизора является то обстоятельство, что программист может управлять светимостью экрана в любом его месте вплоть до отдельного пикселя. Такая возможность достигается ценой сущест- венного усложнения электронных компонентов видеодисплея по сравнению с бытовым телевизором. Действительно, если допустить, что на экране одновременно демонстрируется 400 строк по 700 пик- селей в каждой строке (это параметры высококачественного бытово- го телевизора), то для управления экраном понадобится выдавать 25 раз в секунду по 700x400 = 280000 команд на установку той или иной яркости каждого очередного пикселя. Иными словами, процессор должен обладать производительностью 700x400x25 = 1120000 команд в секунду. Совершенно ясно, что программный способ непосредствен- ного управления светимостью пикселей вряд ли приемлем, так как в этом случае центральный процессор будет практически постоянно занят регенерацией изображения. Разработчики ПЭВМ решили задачу довольно просто. Между программой и схемами электронной развертки изображения они по- местили буферную память на два входа (порта). Программа может обращаться к своему порту, чтобы поместить нужное значение в видеопамять или прочитать из нее ранее установленное значение точно так, как это делается с обычной оперативной памятью ПЭВМ. Одновременно к своему порту обращаются за чтением ин- формации из видеопамяти схемы развертки изображения. Относи- тельно медленная программа неторопливо ’’кладет" в видеопамять отдельные фрагменты изображения, в то время как быстрые схемы развертки непрерывно формируют из этих фрагментов цельное изо- бражение. Существуют два вида информации, появляющейся на экране ви- деодисплея: текстовая, т.е. состоящая из знаков алфавита, цифр и специальных символов, и графическая - чертежи, рисунки, графики. Хотя описанный выше механизм формирования изображений - об- щий для обоих видов информации, имеются существенные разли- чия в том, как используется видеопамять. Эти различия настолько существенны, что говорят о двух режимах работы видеодисп- лея - текстовом и графическом. В этой главе рассматриваются осо- бенности текстового режима. Следующая глава посвящена графиче- скому режиму. Чтобы понять, почему воспроизведение текстовой и графической информации осуществляется существенно различными способами, посмотрим, как преобразуется информация, хранящаяся в видеопа- мяти, в соответствующее изображение. Допустим вначале, что мы собираемся воспроизводить на экране черно-белую информацию, а яркость любого пикселя может быть либо максимальной, либо ми- нимальной (т.е. пиксель либо светится, либо нет). В такой ситуации разумно рассматривать всю видеопамять как одно очень длинное двоичное слово, каждому разряду которого соответствует один из пикселей экрана. Если значение разряда нулевое, пиксель не све- тится, если единичное - светится. Типичный экран ПЭВМ имеет 200 строк по 640 пикселей в строке, т.е. 128000 пикселей. Для хране- ния такого количества двоичных разрядов понадобится видеопамять 196
объемом 128000/8 = 16000 байт. Но ведь мы рассмотрели только про- стейший случай черно-белого изображения. Многие ПЭВМ оснаща- ются цветными мониторами. Появление цвета резко увеличивает требования к памяти, так как в этом случае один пиксель форми- руется как совокупность трех цветных точек: красной, зеленой и си- ней. Увеличение объема видеопамяти приводит к пропорциональ- ному увеличению ее стоимости и, следовательно, увеличивает сто- имость всей ПЭВМ. Появление двух режимов: текстового и графического - стало следствием простого и остроумного решения, найденного разработ- чиками фирмы IBM. Это решение позволило в текстовом режиме воспроизводить на экране 16 цветов и 640x200 черно-белых пикселей в графическом режиме, используя в видеопамяти только 16 Кбайт. Решение основывается на том очевидном факте, что при работе с текстами на экран выводятся символы, причем каждый символ представляет собой один из 256 заранее обусловленных знаков. Для размещения символа на экране используется прямоугольная матри- ца 8x8=64 пикселей, которая называется знакоместом. Каждое знако- место располагается на экране в соответствии со следующим прави- лом: первые 8 строк развертки образуют 80 знакомест первой тек- стовой строки, вторые 8 строк образуют вторую текстовую строку из 80 знакомест и т.д. Всего на экране может быть 200/8=25 текстовых строк. Поскольку очертания любого выводимого символа заранее определены, для хранения информации о нем вовсе необязательно использовать матрицу 8x8 разрядов, т.е. 8 байт видеопамяти. Доста- точно указать лишь номер символа, т.е. поместить в видеопамять только 1 байт. Используя этот байт как ключ, электронные схемы развертки отыщут в специальном цостоянном запоминающем уст- ройстве (ПЗУ) одну из 256 матриц 8x8 разрядов и, согласуясь с ней, вычертят требуемый символ на экране. На самом деле в виде- опамяти приходится хранить не один, а два байта информации о каждом знакоместе: дополнительный байт используется для указа- ний о цвете символа и окружающего его фона. Таким образом, для хранения ’карты" экрана в текстовом режи- ме при использовании 16 цветов требуется теперь только 80х25х х2 = 4000 байт видеопамяти. Для хранения информации о каждом пикселе в отдельности при тех же 16 цветах и с учетом дополни- тельного бита мерцания понадобилось бы 80x25x8x5 = 80000 байт, т.е. в 20 раз больше. Как уже отмечалось, объем видеопамяти ПЭВМ обычно состав- ляет не менее 16000 байт (имеется в виду наиболее распространен- ный так называемый CGA-дисплей, см. ниже). Целиком эта память необходима только при выводе графиков, в текстовом же режиме одновременно используется лишь некоторая ее часть, которая назы- вается страницей текстового экрана. Если ёмкость экрана составляет 25 строк по 80 символов в строке, то одна страница видеопамяти занимает немного меньше 4 Кбайт. Аппаратные средства ПЭВМ могут использовать в качестве монитора обычный бытовой телеви- зор, который подключается через специальное гнездо. Поскольку разрешающая способность телевизора обычно значительно хуже, 197
чем у мониторов дисплея, при работе с телевизором используется режим отображения 40x25 символов. В этом режиме емкость тексто- вой экранной страницы составляет 2 Кбайт. Таким образом, емкость видеопамяти CGA-дисплея составляет 4 текстовые страницы в режи- ме 80x25 символов и 8 страниц в режиме 40x25. Электронные схе- мы развертки могут по командам программы переключаться с од- ной страницы на другую, что позволяет менять информацию на текстовом экране почти мгновенно: программа может готовить одну из невидимых страниц постепенно, а затем одной единственной ко- мандой сделать ее видимой. Наиболее важные электронные компоненты видеодисплея - конт- роллер (схема управления) электронно-лучевой трубки, программи- руемые порты ввода-вывода, матричный ПЗУ-генератор символов и буферная видеопамять - располагаются на одной печатной плате, ко- торая называется дисплейным адаптером. Фирма IBM разработала и выпускает несколько вариантов плат дисплейного адаптера, отлича- ющихся в основном емкостью видеопамяти и, следовательно, воз- можностью управления меньшим или большим количеством пиксе- лей. Все приведенные выше цифры касались наиболее популярного в СССР и одного из наименее дорогих вариантов адаптера - так на- зываемого цветного графического адаптера CGA (от англ. Color Graphics Adapter - цветной графический адаптер). Наличие цветного адаптера на Вашей ПЭВМ еще не гарантирует появление цвета на экране, так как для этого требуется также, чтобы монитор был ос- нащен цветной электронно-лучевой трубкой. Многие ПЭВМ работа- ют с цветными адаптерами и монохромными мониторами. Разуме- ется, на таком мониторе сигнал цветности, вырабатываемый адапте- ром, попросту игнорируется. С монохромными мониторами некоторые западные фирмы иногда поставляют усовершенствованные монохромные адаптеры Hercules, дающие 720x348 точек. Почти удвоенное разрешение по вертикали позволяет создать на таком экране символы со значи- тельно меньшей "зернистостью", что намного приятнее для глаз. Многие ПЭВМ оснащаются адаптерами EGA (от англ. Enhanced Graphics Adapter - усиленный графический адаптер), обеспечиваю- щими вывод 640x350 точек при 16 цветах и двух страницах в гра- фическом режиме и 80x43 знакомест на текстовом экране. Еще бо- лее качественными параметрами обладают VGA-адаптеры новейшей серии ПЭВМ фирмы IBM, так называемой системы PS/2 (VGA- от англ. Video Graphics Array - видеографический массив). Для указания выводимого символа, а также цвета символа и цвета фона в видеопамять помещается два байта. Первый из них (имеющий четный адрес) указывает собственно выводимый символ, второй называется байтом атрибутов и содержит информацию о цвете. На рис. 21 показана структура этого байта (старший разряд слева на рисунке). Цвет символа и фона задается группами из трех разрядов (BACK для фона и TEXT для символа). Каждый разряд в этих триадах связан с одним из трех основных цветов монитора - красным, зеленым и синим. Смешение основных цветов дает 8 воз- можных комбинаций: если все разряды нулевые, все три цвета от- 198
сутствуют, что соответствует черному цвету, если разряды единич- ные - белому; смешение красного и синего дает фиолетовый, синего с зеленым - голубой и т.д. 7 6 5 4 3 2 1 0 в 1 1 back 1 । Н — Г • 1 text J 1 Рис, 21. Структура байта атрибутов Третий разряд байта атрибутов (на рисунке обозначен латинской буквой Н) управляет интенсивностью цвета. Этот бит относится только к цвету символа и в сочетании с тремя другими разрядами обеспечивает 16 цветов. Таким образом, для символа можно устано- вить как темные, так и светлые цвета, в то время как для фона- только темные. Это позволяет, например, на цветном экране уве- ренно различать белый символ на белом же фоне. Особую роль играет старший разряд байта атрибутов (на рисунке обозначен латинской буквой В). Он управляет мерцанием символа: если в этом разряде установлена единица, символ мигает с часто- той около 2 Гц (фон при этом не мигает). Хотя видеопамять, безусловно, непохожа на основную оператив- ную память ПЭВМ, к ней можно обращаться из программы с по- мощью адресов. Для видеопамяти в ПЭВМ зарезервированы адреса с сегментной частью от $А000 до $DFFF, т.е. 256 Кбайт. Монитор CGA занимает из них только 16 Кбайт, начиная с адреса $В800. В табл. 10 приводятся адреса начала текстовых страниц для этого мо- нитора. Таблица 10 Начальные адреса текстовых страниц CGA-монитора N страницы Режим работы 80x25 40x25 0 SB800 SB800 1 SB900 SB880 . 2 SBA00 SB900 3Z SBB00 SB980 • 4 — SBA00 5 — SBA80 6 — $ВВ00 7 - SBB80 Экскурс в область техники управления дисплеем был бы непол- ным, если бы мы не рассмотрели соответствующие возможности ДОС Для работы с видеотерминалом используется прерывание ДОС с шестнадцатиричным номером 10 (десятичный номер 16). Подробно- 199
сти использования прерываний для обращения к средствам ДОС описаны в гл.11 и здесь мы не будем их обсуждать, однако некото- рые функции этого прерывания уместно рассмотреть именно здесь (табл.11). Таблица 11 Управление текстовым режимом с помощью прерывания $10 Содержимое регистра АН Действие Содержимое регистров 0 Установить режим: черно-белый, 40x25 цветной, 40x25 черно-белый. 80x25 цветной, 80x25 >> > > II II II II W К) н о 1 Установить размер курсора СН = начальная строка CL-конечная строка 2 Установить положение курсора DH = номер строки DL=номер позиции ВН = номер страницы 3 Получить положение и размеры курсора ВН = номер страницы СН = начальная строка курсора CL=конечная строка курсора DH = номер строки положения DL = номер позиции положения 5 Установить страницу AL=номер страницы 6 Сдвинуть вверх AL=количество сдвигаемых строк СН = у1 левый верхний угол CL=xl то же 7 Сдвинуть вниз DH = y2 правый нижний угол „ DL = х2 то же ВН = атрибуты пустых строк 8 Читать символ и его атрибуты ВН = номер страницы AL=символ АН = атрибуты 9 Записать символ с атрибутами AL = символ f BL = атрибуты ВН = номер страницы СХ = количество копий символа 10 Записать символ без атрибутов AL=символ ВН = номер страницы СХ = количество копий символа 14 Записать символ и сдвинуть курсор AL = символ ВН = номер страницы 200
Функция АН=1 устанавливает размер текстового курсора. Этот размер задается номерами верхней и нижней строк знакоместа в диапазоне 0...7, которые ограничивают изображение курсора на экра- не. Если СН=0 и CL=7, курсор имеет максимально возможный раз- мер и занимает целое знакоместо. Стандартным является состояние СН=6 и СН=7, т.е. курсор занимает две нижние строки знакоместа. Если установить СН=$20, то курсор не будет выводиться. Функция АН=2 устанавливает положение курсора. Отметим, что, в отличие от принятого во встроенных процедурах Турбо-Паскаля способа позиционирования курсора в координатах от 1 до 80 по го- ризонтали и от 1 до 25 по вертикали, во всех функциях прерыва- ния $10 используются координаты от 0 до 79 и от 0 до 24; номера текстовых страниц - от 0 до 3 для режима 80x25 и от 0 до 7 для режима 40x25. Для каждой страницы имеется свой закрепленный за этой страницей курсор. При обращении к функции АН=3 в регистре ВН следует указать номер той страницы, для которой запрашиваются координаты и размер курсора. При возврате из процедуры обработки прерывания $10 в регистрах СН и CL содержатся номера начальной и конечной строки знакоместа (в диапазоне 0...7), которые определяют размер курсора по вертикали, в регистре DH - номер текстовой строки (0...24), а в DL - номер позиции в строке (0...79 или 0...39), в кото- рых формируется курсор. Функция АН=5 используется для того, чтобы сделать видимой нужную текстовую страницу. При обращении к функции номер этой страницы указывается в регистре AL. Необходимо иметь в ви- ду, что независимо от номера видимой на экране текстовой страни- цы стандартные процедуры WRITE и WRITELN всегда формируют изображение в странице с номером 0. Таким образом, если Вы хо- тите вывести символы в любую другую страницу (которая может быть и невидимой в данный момент), Вам нужно использовать функции АН-9, АН=10 или АН = 14 (см. ниже). Функции АН=6 и АН=7 обеспечивают "прокрутку" содержимого окна вверх или вниз. Окно представляет собой прямоугольную об- ласть на экране, которая задается координатами своего левого верх- него (XI для горизонтали и Y1 для вертикали) и правого нижнего (Х2 и Y2) углов. Использование окон с "прокруткой" в них текста представляет собой одну из интереснейших возможностей аппарат- ных средств ПЭВМ. Встроенная процедура WINDOW (см. ниже) ус- танавливает окно на экране, однако описываемые функции преры- вания $10 дают более богатые средства управления, в том числе "прокрутку" изображения вниз. В режиме "прокрутки" можно сме- стить содержимое окна на любое число строк вверх (АН=6) или вниз (АН=7). Выталкиваемые за границы окна строки безвозвратно теряются, освободившиеся строки заполняются в соответствии с ат- 201
рибутом, заданным ,в ВН. Этот атрибут обычно задают отличным от атрибута остальной части экрана, чтобы можно было легко визу- ально выделить установленное окно. В AL указывается количество пустых строк, помещаемых в окно в ходе "прокрутки". Если AL=0, то все окно очищается. То же самое произойдет, если задать в AL число, превышающее вертикальный размер окна. При обращении к функции АН = 8 (чтение символа из видеопа- мяти) необходимо поместить в ВН номер той страницы (возможно, невидимой), в которой требуется прочитать символ и его атрибуты. Читается тот символ, на который указывает курсор. При записи символа с помощью функций АН = 9 и АН = 10 кур- сор не перемещается, а записываемый символ и его копии (в реги- стре СХ указывается количество копий) появляются на месте курсо- ра и справа от него. При достижении правого края экрана органи- зуется переход на новую строку. Функция АН = 14 имитирует вывод на экран в режиме пишущей машинки. Именно этот режим стандартно реализуется процедурами WRITE и WRITELN. 12.2. СТАНДАРТНЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ УПРАВЛЕНИЯ ТЕКСТОВЫМ ЭКРАНОМ Все описываемые в этом разделе процедуры и функции входят в стандартный модуль CRT библиотечного файла TURBO.TPL и становятся доступными только после указания предложения USES CRT в самом начале программы (см. гл.10). Эти процедуры и фун- кции относятся к нулевой странице видеопамяти, организовать мно- гостраничную работу с помощью этих процедур нельзя (многостра- ничная работа поддерживается процедурами, описываемыми в 16.2). При обращении к ним координаты курсора определяются относи- тельно левого верхнего угла активного окна (или экрана, если окно не установлено), который имеет координаты 1,1. Координаты окон всегда задаются относительно левого верхнего угла всего экрана. При выводе на экран текстовой строки процедурами WRITE и WRITELN следующие четыре символа используются особым обра- зом: # 7 - короткий звуковой сигнал; # 8 - перемещение курсора влево на один символ; если курсор уже находится на левой границе окна (или экрана), символ игнори- руется; # 10-смещение курсора на одну строку вниз при той же гори- зонтальной позиции; если курсор к этому моменту уже находился на нижней строке окна (экрана), содержимое окна (экрана) "прокру- чивается" вверх на одну строку; # 13 - перемещение курсора в начало следующей строки; при не- обходимости организуется "прокрутка" окна (экрана). 202
Любой другой символ выводится на экран в ту позицию, на ко- торую в данный момент указывает курсор, после чего курсор сме- щается вправо. Если курсор находился у правой границы окна (эк- рана), он перемещается в начало следующей строки, а если к тому же был на последней строке, организуется "прокрутка". В модуле CRT определены следующие константы для процеду- ры TEXTMODE : const BW40 = 0; {черно-белый режим 40x25} С040 = 1; {цветной режим 40x25} BW80 =. 2; {черно-белый режим 80x25} С080 = 3; {цветной режим 80x25} Mono = 7; {используется с монохромным дисплеем} Font8x8 = 256; {используется для загружаемого шрифта в режиме 43 или 50 строк только с адап- терами соответственно EGA или VGA} С40 = С040; {введена для совместимости с версией 3.0} С80 = С080; {введена для совместимости с версией 3.0} Для процедур TEXTCOLOR и TEXTBACKGROUND определены такие константы цветов: const Black = 0; {черный} Blue = 1; {синий} Green = 2; {зеленый} Cyan = 3; {голубой} Red = 4; {красный} Magenta = 5; {фиолетовый} Brown = 6; {коричневый} L ightGray = 7; {светло-серый} DarkGray = 8; {темно-серый} L ightBlue = 9; {ярко-синий} L ightGreen = 10; {ярко-зеленый} LightCyan = И; {ярко-голубой} L ightRed = 12; {розовый} L i ghtMagenta = 13; {малиновый} Yellow = 14; {желтый} White = 15; {белый} Blink =128; {мерцание символа} Кроме того, определены следующие переменные: var WindMin, WindMax : word; TextAttr : byte; LastMode : word; DirectVideo : Boolean; CheckSnow : Boolean; В переменные WINDMIN и WINDMAX процедура WINDOW за- носит координаты текущего окна: Lo(WindMin) дает координату XI левого верхнего угла, a Hi(WindMax) - координату Y2 правого нижне- го угла. В отличие от обращения к процедуре WINDOW координа- ты соответствуют началу отсчета 0,0. 203
В переменной TEXTATTR содержится байт атрибутов для теку- щего способа вывода (см. 12.1). Переменная 5 ASTMODE сохраняет код предыдущего текстового режима: при каждом обращении к процедуре TEXTMODE в эту пе- ременную заносится код текущего режима работы видеодисплея. Переменная DIRECTVIDEO обычно имеет значение TRUE, что позволяет стандартным процедурам WRITE и WRITELN обращаться непосредственно к видеопамяти. При установке переменной в FALSE эти процедуры будут использовать прерывание $10 и вывод на экран будет осуществляться с меньшей скоростью. Переменная CHECKSNOW имеет по умолчанию значение TRUE. Эта переменная управляет синхронизацией прямого обращения к видеопамяти с помощью процедур WRITE и WRITELN с периодом строчной развертки монитора. При состоянии переменной TRUE доступ к видеопамяти осуществляется только в момент обратного хода луча, т.е. в паузе между двумя последовательными обращения- ми к видеопамяти электронных схем развертки. При состоянии пе- ременной FALSE скорость вывода на .экран возрастает, однако в CGA-адаптере это будет сопровождаться появлением помех в виде "снега" на экране. Процедура TEXTCOLOR. Устанавливает текущий цвет для выво- димых символов; формат обращения TEXTCOLOR ( <цвет> ) Здесь <цвет> - выражение типа BYTE, задающее текущий цвет символов. Если значение этого выражения больше 15, то цвет опре- деляется как остаток от деления выражения на 16 и добавляется бит мерцания. Программа примера 37 поможет Вам оценить цветовые возмож- ности Вашей ПЭВМ. Программа вводит с клавиатуры целое число, устанавливает цвет в соответствии с этим числом и выводит конт- рольное сообщение на экран. Для выхода из программы введите ошибочное число, например какую-либо букву. Пример 37 PROGRAM ColorScreen; Uses CRT; var color : byte; BEGIN repeat readln(color); TextColor(color); writeln('Цвет = color) until EOF END; Процедура TEXTBACKGROUND. Устанавливает текст фона, на ко- торый будут выводиться символы; формат обращения TEXTBACKGROUND ( <цвет> ) 20«
Здесь < цвет >-переменная типа BYTE, определяющая текущий цвет фона. Если значение этого выражения больше 7, цвет опреде- ляется остатком от деления выражения на 8. Процедура GOTOXY. Переводит курсор в требуемое положение на экране. Обращение GOTOXY ( X, Y ) Здесь X - горизонтальная координата нового положения курсора; Y - вертикальная координата. Координаты задаются выражениями типа BYTE и определяются относительно левого верхнего угла активного окна (или экрана, если окно не установлено), который имеет координаты 1,1. Если какая- либо координата выходит за границы активного окна (экрана), вы- зов процедуры игнорируется. Программа примера 38 дает любопытные цветовые эффекты на экране монитора. Для выхода из программы достаточно нажать на любую алфавитно-цифровую клавишу. Пример 38 PROGRAM TextColorScreen; Uses CRT,dos; const MaxX =80; MaxY = 25; MaxColor = 16; ch = 'o '; var x, у : byte; ' BEGIN repeat x := Succ(Random(MaxX)); • у := Succ(Random(MaxY)); if (x=MaxX) and (y=MaxY) then dec(x); GotoXY(x, y); TextColor(Random(MaxColor)); TextBackGround(Random(MaxColor)); write(ch) until KeyPressed END. Для выхода из программы используется логическая функция KEYPRESSED, которая также входит в модуль CRT. Эта функция возвращает значение TRUE, если к моменту обращения к ней была нажата какая-либо клавиша (подробнее см. гл. 14). Обратите внимание на проверку условия if (х=МахХ) and (y=MaxY) then dec(x); Что будет, если эту проверку убрать? Проделайте несложный эксперимент - уберите этот оператор, запустите программу заново и понаблюдайте достаточно долго за экраном. Вы заметите, что иног- да осуществляется "прокрутка" экрана вверх на одну строку. Связано это с уже упоминавшейся особенностью работы стандартных проце- 205
дур WRITE и WRITELN: после вывода очередного символа курсор сдвигается на одну позицию вправо. Если курсор находился в пра- вом нижнем углу экрана, осуществляется "прокрутка", поэтому выве- сти какой-либо символ в правый нижний угол экрана или активно- го окна стандартной процедурой WRITE невозможно. Первопричи- ной этого является то обстоятельство, что эта процедура имитирует (или использует - в зависимости от значения DIRECTVIDEO) функ- цию АН = 14 прерывания $10 (см. 12.1). Использование для вывода функций АН=9 или АН = 10 дает возможность избавиться от этого недостатка (см. 12.1). Процедура CLRSCR. Очищает экран или активное окно. Обраще- ние CLRSCR При очистке окно (экран) заполняется текущим цветом фона. После обращения к процедуре курсор всегда помещается в верхний левый угол окна (экрана). Процедура WINDOW. Устанавливает границы активного окна для вывода данных. Обращение WINDOW ( XI, Yl, Х2, Y2) Здесь XI - горизонтальная координата левого верхнего угла экрана; Y1 - вертикальная координата левого верхнего угла экрана; Х2 - горизонтальная координата правого нижнего угла экрана; Y2 - вертикальная координата правого нижнего угла экрана. Координаты окна задаются выражениями типа BYTE и опреде- ляются относительно левого верхнего угла экрана, который имеет координаты 1,1. Если какая-либо координата выходит за границы экрана, обращение к процедуре игнорируется. Координаты Х2 и Y2 должны быть хотя бы на единицу больше соответственно коорди- нат XI и Y1. Несложная программа в примере 39 способна создать на цвет- ном экране некоторое подобие шедевров художников-абстракциони- стов. Для выхода из программы достаточно нажать на любую кла- вишу. Пример 39 -PROGRAM ScreenWindows; Uses CRT; const MaxX = 80; MaxY = 25; MaxColor = 8; var xl, yl, x2, y2 : byte; BEGIN repeat xl := Random(MaxX); yl : = Random(MaxY); 206
х2 ?= xl + Succ (Random(MaxX)) if х2 > МахХ then х2 := МахХ; у2 := yl + Succ(Random(MaxY)); if у2 > MaxY then‘у2 := MaxY; ' TextBackGround( Succ (Random(MaxColor - 1))'); Window(xl, yl, x2, y2); delay(lOO); ClrScr until KeyPressed HMD. Функция WHEREX. Возвращает горизонтальную координату теку- щего положения курсора. Обращение WHEREX Значение, возвращаемое функцией, имеет тип BYTE и определя- ет текущее положение курсора относительно верхнего левого угла активного окна или экрана, если окно не установлено. Функция WHEREY. Возвращает вертикальную координату теку- щего положения курсора. Обращение WHEREY Значение, возвращаемое функцией, имеет тип BYTE и определя- ет текущее положение курсора относительно верхнего левого угла активного окна или экрана, если окно 'не установлено. Процедура DELLINE. Стирает строку, на которой располагается курсор. Обращение DELLINE Осуществляется сдвиг всех строк в окне (на экране), располо- женных ниже стираемой, вверх на одну строку. Текущая строка стирается, остальные строки не меняются. Процедура INS LINE. Вставляет пустую строку на место той стро- ки, на которой располагается курсор. Обращение INSLINE Строка, на которую указывал курсор перед обращением к проце- дуре, и все строки в окне (на экране), расположенные ниже ее, сдвигаются вниз на одну строку. Строка, вытолкнутая за нижнюю границу окна (экрана), безвозвратно теряется. Текущее положение курсора не меняется. Процедура CLREOL. Стирает часть строки от позиции, на кото- рой расположен курсор, вправо до конца строки. Обращение CLREOL Текущее положение курсора не меняется. Процедура TEXTMODE. Устанавливает новый текстовый режим работы видеотерминала; формат обращения TEXTMODE ( < режим > ) ♦ Здесь < режим > - выражение типа SHORTINT, указывающее но- вый режим. После обращения к процедуре сбрасывается установка активного окна и курсор устанавливается в левый верхний угол экрана. На- пример: 207
Uses CRT; var ch : char; BEGIN Window(20,5,60,15); TextBackGround(blue); ClrScr; writeln(’9T0 текстовый режим 80x25 ...’); ch := ReadKey; TextMode(C040); writein (’Это режим 40x25 ch := ReadKey; TextMode(C080); writeln('Возврат в текстовый режим 80x25’); ch := ReadKey END. Процедуры LOWVIDEO, NORMVIDEO и HIGHVIDEO устанавли- вают, соответственно, пониженную, нормальную и повышенную яр- кость изображения. Заметим, что на CGA-дисплее разница между пониженной и нормальной яркостью незаметна. Например: Uses CRT; BEGIN ClrScr; LowVideo; writeln('Пониженная яркость’); NormVideo; writeln('Нормальная яркость’); HighVideo; writein('Повышенная яркость’) END. Процедура ASSIGNCRT. Связывает текстовый файл с видеотер- миналом; формат обращения ASSIGNCRT ( <файл> ) Здесь <файл> - имя переменной Типа текстовый файл. Обращение к процедуре эквивалентно связыванию текстового файла со стандартным выводным файлом OUTPUT. Например, сле- дующие два назначения текстовых файлов эквивалентны: Uses CRT; var fl, f2 : text; begin ass ign(f1, 'Output'); AssignCrt(f2) end. 208
Г л а в a 13 УПРАВЛЕНИЕ ЭКРАНОМ В ГРАФИЧЕСКОМ РЕЖИМЕ 13.1. ТЕХНИЧЕСКИЕ ОСОБЕННОСТИ ВОСПРОИЗВЕДЕНИЯ ГРАФИКИ Как уже отмечалось в 12.1, в ПЭВМ принят растровый способ формирования изображений, т.е. любая информация на экране мо- нитора представляет собой совокупность светящихся точек - пиксе- лей. Каждый пиксель определяется своими координатами - положе- нием относительно левого верхнего угла экрана, который, в свою очередь, имеет координаты 0,0 (в текстовом режиме, напомню, ко- ординаты левого верхнего угла - 1,1 , см. 12.2). Программист может управлять светимостью и/или цветом любого пикселя, что позволя- ет формировать на экране любые изображения, в том числе рисун- ки, графики, чертежи, символы. Как и в текстовом режиме, в графическом используется буфер- ная видеопамять, содержимое которой представляет собой "карту" экрана. В популярном CGA-адаптере (см. 12.1) объем видеопамяти составляет 16 Кбайт. Этот объем достаточен для отображения 640x200 пикселей, если с каждым пикселем связывать лишь один разряд (бит) видеопамяти. Ясно, что информационная емкость од- ного бита, а следовательно, и возможности управления пикселем в этом случае крайне ограничены: ведь каждый разряд может иметь либо нулевое значение, либо единичное. В ПЭВМ принято считать, что нулевое состояние какого-либо разряда видеопамяти означает сигнал об отсутствии светимости соответствующего пикселя и, нао- борот, единичное состояние - команду высветить нужный пиксель. Таким образом, объем4 16 Кбайт видеопамяти позволяет получить на экране размером 640x200 пикселей только двухцветное изображе- ние - черный цвет соответствует состоянию 0 бита видеопамяти, све- тимость каким-либо заранее выбранным цветом - состоянию 1. В рамках CGA-адаптера такой режим называется режимом высокого разрешения. Существует и другой режим CGA-адаптера - режим среднего раз- решения. В этом режиме на каждый пиксель выделяется по 2 бита 209
видеопамяти, однако это достигается ценой двукратного ухудшения разрешающей способности экрана: 320x200 пикселей. Информацион- ная емкость двух бит достаточна для кодировки четырех цветов. Конечно, это значительно меньше 16 цветов, которые можно полу- чить на экране ПЭВМ в текстовом режиме. Более того, возникает некоторая неопределенность выбора цветов. Действительно, в тексто- вом режиме кодировка цвета осуществляется тремя битами для фо- на и четырьмя для символов (см. 12.1). Это позволяет связать каж- дый бит с одним из трех. основных цветов монитора (фактически каждый бит управляет работой “своей" электронно-лучевой пушки - красной, зеленой и синей). При использовании двух бит для коди- ровки цвета возникает проблема: то ли связать с каждым разрядом свой основной цвет, как это сделано в текстовом режиме (тогда все равно не ясно, какие именно цвета связываются с тем или иным битом), то ли считать состояния 0...3 этих бит как указание на вы- бор одного из четырех заранее обусловленных цветов, образующих некоторую фиксированную палитру. Фирма IBM пошла вторым пу- тем: в режиме среднего разрешения программист может связать один цвет палитры с одним из любых 16 цветов экрана, в то вре- мя как три другие цвета он может выбирать лишь в сочетании друг с другом как одну из четырех возможных палитр. В режиме низкого разрешения CGA-адаптера изображение> фор- мируется в виде матрицы из 160x100 пикселей. Аппаратные воз- можности для этого существуют, однако программная поддержка этого режима со стороны операционной системы отсутствует, поэто- му режим низкого разрешения на П^ВМ практически не использу- ется. В более совершенных адаптерах EGA и VGA объем видеопамя- ти увеличен и составляет от 64 до 256 Кбайт. Это позволяет полу- чать на экране с разрешением 640x350 пикселей или больше до 16 цветов одновременно. Итак, растровый экран ПЭВМ формируется из множества близко расположенных пикселей, цвет и светимость которых регулируется программным путем. Состояние каждого пикселя кодируется одним или несколькими разрядами (битами) в видеопамяти. К видеопамя- ти имеют возможность одновременного доступа как программа для переустановки состояния любого пикселя, так и электронные схемы развертки изображения на экране. Эти схемы рассматривают видео- память как одну очень длинную строку бит, хранящую команды на установку светимости и цвета того или иного пикселя. Электронные схемы опрашивают видеопамять последовательно бит за битом в темпе электронного луча, обегающего экран. Изображение стандартного телевизионного кадра формируется из двух полукадров. В первом полукадре луч обегает по всем четным строкам, во втором - по всем нечетным. Соответствующим образом хранится информация и в видеопамяти: в первой ее половине (с меньшими адресами) должна быть расположена информация о чет- ных строках, во второй половине (адрес начала этой части должен быть кратным 1024) - о нечетных. Встроенные процедуры и функ- ции, поддерживающие графику, учитывают эту особенность. При 210
прямом обращении к видеопамяти этот учет возлагается на про- граммиста. В графическом режиме имеется возможность вывода символьной информации. Разумеется, символы любой конфигурации пользова- тель может формировать самостоятельно и представлять их просто как некоторые пиктограммы, занимающие определенную часть гра- фического экрана (по аналогии с текстовым режимом будем назы- вать часть экрана, занятую символом, знакоместом). Однако такой подход исключает возможность использования стандартных проце- дур WRITE и WRITELN. Другой подход состоит в имитации средств, реализованных в текстовом режиме. Напомню, что в тек- стовом режиме используется помещенная в ПЗУ таблица графиче- ских образов всех символов, которая управляет работой схем генера- ции текста. В графическом режиме при выводе символов также применяется таблица знакогенератора, однако размещенная в ПЗУ таблица подключается только при выводе первой половины симво- лов (коды от 0 до 127), при выводе второй половины символов (с кодами от 128 до 255) используется загружаемая в оперативную па- мять часть таблицы. Эту очень важную особенность фирма IBM ввела намеренно: ведь вторая половина ASCII-кодов используется для формирования символов национального алфавита, и в том чис- ле кириллицы. Реализация этой части кодов х помощью загружае- мой в память таблицы знакогенератора резко упрощает проблему ’’переучивания" ПЭВМ под любой национальный алфавит. К сожа- лению, для текстового режима возможность загрузки таблицы знако- генератора имеется лишь на усовершенствованных адаптерах EGA и VGA, при работе с распространенным адаптером CGA ’’переучива- ние" текстового режима работы ПЭВМ достигается только путем пе- репрограммирования микросхемы ПЗУ. В графическом же режиме работы CGA-адаптера заставить ПЭВМ. выводить текстовые сообще- ния по-русски не представляет проблемы. Для этого лишь надо за- грузить в оперативную память шрифт в виде таблицы из 128 мат- риц размером 8x8 бит и каким-то образом сообщить адрес начала этой таблицы схемам развертки. Каждая матрица 8x8 бит (8 байт) содержит графический образ выводимого символа, причем первые 8 байт используются при выводе символа с кодом 128, вторые 8 байт-с кодом 129 и т.д. Каждый байт матрицы кодирует одну строку развертки из 8 смежных пикселей: если первый бит в байте имеет значение 1, первый пиксель этой строки будет светиться, если 0 - не будет светиться. Состояние второго пикселя определяет- ся содержимым второго бита и т.д. Чтобы указать схемам развертки адрес начала загруженной части таблицы знакогенератора, использу- ется вектор прерывания $1F (десятичное значение вектора 31). Сра- зу после включения ПЭВМ содержимое этого вектора обнуляется, поэтому попытка вывода в графическом режиме CGA-адаптера символов с кодами 128 и выше приведет к формированию "пустых" знакомест. После загрузки таблицы и записи адреса ее начала в четыре смежных байта вектора 31 (начиная с адреса 31x4=124) схемы развертки будут в графическом режиме использовать эту таблицу. 211
В графическом режиме CGA-адаптер формирует 640/8 = 80 знако- мест в горизонтальном ряду и 200/8=25 знакомест в вертикальном, т.е. 80x25 = 2000 знакомест, каждое в виде матрицы из 8x8 пикселей. В адаптере EGA знакоместо может быть либо в виде такой же мат- рицы 8x8, и тогда по вертикали можно сформировать 350/8 = 43 пол- ных знакоместа (разрешающая способность адаптера EGA составля- ет 640x350 пикселей), либо в виде матрицы 8x14 пикселей' что дает 350/14= 25 знакомест по вертикали. Таким образом, графический ре- жим работы EGA-адаптера формирует текстовые сообщения из 80x43 символов или из 80x25 символов в зависимости от загружен- ного шрифта. Заметим, что качество изображения во втором случае значительно выше. Четыре функции прерывания $10 относятся к графическому ре- жиму (табл. 12). Поскольку все эти функции поддерживаются теми или иными процедурами стандартного модуля GRAPH.TPU (см. ни- же), необходимость в непосредственном обращении к ним обычно не возникает. Таблица 12 Функции прерывания $10, поддерживающие графический режим Содержимое регистра АН Действие Содержимое регистров 0 Установить режим: цветной графический, 320x200 графический^ 640x200 AL=4 AL=6 11 Установить палитру ВН = указатель палитры ВЬ=цвет или палитра 12 Записать пиксель АЬ=цвет пикселя DL=номер строки СХ=номер столбца 13 Читать пиксель AL=возвращаемый цвет пикселя DL=номер строки СХ=номер столбца 13.2. СТАНДАРТНЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ УПРАВЛЕНИЯ ГРАФИЧЕСКИМ ЭКРАНОМ Описываемые ниже процедуры и функции включены в стан- i дартный модуль GRAPH.TPU, входящий в комплект поставки Тур- ' бо-Паскаля, и становятся доступны только после объявления в про- ' грамме предложения USES GRAPH. । В общей сложности в модуль входят 73 процедуры и функции, предоставляющие пользователю самые разнообразные возможности i управления графическим экраном. Для облегчения знакомства с ’ графическими средствами Турбо-Паскаля все процедуры и функции 212
сгруппированы по функциональному назначению. Почти все ohi иллюстрируются короткими примерами, часть которых заимствован (после некоторой переработки) из стандартного пакета поставки Тур бо-Паскаля, однако большая часть написана автором. При разработке примеров, помещенных в этой главе, а такж< графических программ, помещенных в четвертой части книги предполагалось, что технические характеристики большинства рас пространенных в СССР ПЭВМ соответствуют средствам цветногс графического адаптера CGA. Однако во всех случаях я стремился сделать их максимально независимыми от технических средств, ис- пользуя режим автоматического определения типа адаптера. Там. где иллюстрируются цветовые возможности Турбо-Паскаля, прину- дительно устанавливается режим работы адаптера с номером 0, что гарантирует цвет на цветном экране с любым адаптером, однако несколько сужает возможности адаптеров EGA и VGA. Если Ваша ПЭВМ имеет адаптер EGA или VGA и Вы захотите использовать все возможности этих адаптеров или если Ваш компьютер оснащен монохромным адаптером, Вам понадобится незначительная перера- ботка этих программ. Настройка графических процедур на работу с конкретным адап- тером достигается за счет подключения нужного графического драй- вера. Драйвер - это специальная программа, осуществляющая управ- ление теми или иными техническими средствами ПЭВМ. Графиче- ский драйвер, как об этом не трудно догадаться, управляет графиче- ским адаптером. Графические драйверы разработаны фирмой Borland практически для всех адаптеров. Обычно они располагаются на диске в ртдельном подкаталоге BGI в виде файлов с расширени- ем .BGI (от англ. Borland Graphics Interface - графический интерфейс фирмы Borland). Например, CGA.BGI - драйвер для CGA-адаптера, EGAVGA.BGI - драйвер для адаптеров EGA и VGA и т.п. 0,0 639,0 X У О 320,100 0,199 639,199 Рис. 22. Координаты графического экрана для CGA-адаптера в режиме высокого разрешения 213
Многие графические процедуры и функции, используют внутрен- ний указатель текущей позиции на экране, который в отличие от текстового курсора невидим. Положение этого указателя, как и вооб- ще любые координаты на графическом экране, задается относитель- но левого верхнего угла, который, в свою очередь, имеет координа- ты 0,0. Таким образом, горизонтальная координата экрана увеличи- вается слева направо, а вертикальная - сверху вниз (рис. 22). 13.2.1 Переход в графический режим и возврат в текстовый Стандартное состояние ПЭВМ после ее включения, а также к моменту запуска программы из среды Турбо-Паскаля соответствует работе экрана в текстовом режиме, поэтому любая программа, ис- пользующая графические средства компьютера, должна определен- ным образом инициировать графический режим работы адаптера. После завершения работы программы ПЭВМ возвращается в тек- стовый режим. Процедура INITGRAPH. Инициирует графический режим работы адаптера; формат обращения INITGRAPH ( < драйвер >, < режим >, <путь> ) Здесь < драйвер >-переменная типа INTEGER, определяющая тип графического драйвера; < режим > - переменная типа INTREGER, определяющая режим работы графического адаптера; < путь >-выражение типа STRING, содержащее путь к файлу драйвера. К моменту обращения к процедуре на одном из дисковых носи- телей информации должен находиться файл, содержащий нужный графический драйвер. Процедура загружает этот драйвер в оператив- ную память и переводит адаптер в графический режим работы. Тип драйвера должен соответствовать типу графического адаптера. Для указания типа драйвера в модуле предопределены следующие константы: const Detect 0; {режим автоопределения типа} CGA = 1; MCGA = 2; EGA = 3; EGA64 = 4; EGAMono = 5; IВМ8514 = 6; НегсМопо = 7; ATT400 = 8; VGA = 9; PC3270 = 10; Большинство адаптеров могут работать в различных режимах. Чтобы указать адаптеру требуемый режим работы, используется пе- ременная < режим >, значением которой могут быть такие констан- ты: 214
const CGACO = 0; {320x200, 1 страница, 4 цвета, палитра 0 ярко-зеленый, ярко-красный, желтый} CGAC1 = 1; {320x200, 1 страница, 4 цвета, палитра ярко-голубой, малиновый, белый} 1 CGAC2 = 2; {320x200, 1 страница, 4 цвета, палитра, зеленый, красный, коричневый} 2 CGAC3 = 3; {320x200, 1 страница, 4 цвета, палитра голубой, фиолетовый, светло-серый} 3 CGAHi = 4; {640x200, 1 страница-, 1 цвет} MCGACO = 0; {320x200, 1 страница, 4 цвета, палитра ярко-зеленый, ярко-красный, желтый} 0 MCGAC1 = 1; {320x200, 1 страница, 4 цвета^ палитра ярко-голубой, малиновый, белый} 1 MCGAC2 = 2; {320x200, 1 страница, 4 цвета, палитра зеленый, красный, коричневый} 2 MCGAC3 = 3; {320x200, 1 страница, 4 цвета, палитра голубой, фиолетовый, светло-серый} 3 MCGAMed = 4; {640x200, 1 страница, 1 цвет} MCGAHi = 5; {640x480, 1 страница, 1 цвет} EGALo = 0; {640x200, 4 страницы, 16 цветов} EGAHi = 1; {640x350, 2 страницы, 16 цветов} EGAMonoHi = 3; {640x350} HercMonoHi = 0; {720x348, 2 страницы, 1 цвет} ATT400C0 = 0; {320x200, 1 страница, 4 цвета, палитра ярко-зеленый, ярко-красный, желтый} 0 ATT400C1 = 1; {320x200, 1 страница,-4 цвета, палитра ярко-голубой, малиновый, белый} 1 ATT400C2 = 2; {320x200, 1 страница, 4 цвета, палитра зеленый, красный, коричневый} . 2 ATT400C3 = 3; {320x200, 1 страница, 4 цвета, палитра голубой, фиолетовый, светло-серый} 3 ATT400Med = 4; {640x200, 1 страница, 1 цвет} ATT400Hi = 5; {640x400, 1 страница, 1 цвет} VGALo = 0; {640x200, 4 страницы, 16 цветов} VGAMed = 1; {640x350, 2 страницы, 16 цветов} VGAHi = 2; {640x480, 1 страница, 16 цветов} PC3270H i = 0; {720x350, 1 страница, 1 цвет} IBM8514LO = 0; {640x480, 256 цветов} IBM8514Hi = 1; {1024x768, 256 цветов} Пусть, например, драйвер CGA.BGI находится в каталоге ТР на диске С и устанавливается режим работы 320x200 с палитрой 2. Тог- да обращение к процедуре будет таким: Uses Graph; var Driver, Regim : integer; begin Driver := CGA; Regim := CGAC2; InitGraph(Oriver, Regim, 'C:\TP'); 215
Если тип адаптера ПЭВМ неизвестен или если программа рас- считана на работу с любым адаптером, используется обращение к процедуре с требованием автоматического определения типа адапте- ра: Driver : = Detect; InitGraph(Driver, Regim, ’C:\TP’); После такого обращения установится графический режим работы экрана, а переменные DRIVER и REGIM будут содержать констан- ты, определяющие тип драйвера и режим его работы. При этом для адаптеров, способных работать в нескольких режимах, выбира- ется старший режим, т.е. тот, что закодирован максимальной циф- рой. Так, при работе с CGA-адаптером обращение к процедуре со значением DRIVER = DETECT вернет в переменной REGIM значе- ние 4 (CGAHi), а такое же обращение к адаптеру VGA вернет REGIM = 2 (VGAHi). Функция GRAPHRESULT. Возвращает значение типа INTEGER, в котором закодирован результат последнего обращения к графиче- ским процедурам. Обращение GRAPHRESULT Если ошибка не обнаружена, значением функции будет ноль, в противном случае - отрицательное число, указывающее номер ошиб- ки. При анализе ошибок можно использовать следующие мнемони- ческие константы, определенные в модуле GRAPH: const grOk = 0; {нет ошибок} grlnitGraph = -1; {не инициирован графический режим} grNotDetected = -2; {не определен тип драйвера} grFi leNotFind =-3; {не найден графический драйвер} grlnval idDriver = -4; {неправильный тип драйвера} grNoLoadMem =-5; {нет памяти для размещения драйвера} grNoScanMem =-6; {нет памяти для просмотра областей} grNoFloodMem =-7; {нет памяти для закраски областей} grFontNotFound =-8; {не найден файл со шрифтом} grNoFontMem = -9; {нет памяти для размещения шрифта} grlnva1idMode =-10; {неправильный графический режим} grError =-11; {общая ошибка} grIOError =-12; {ошибка ввода-вывода} grlnva 1 idFont =-13; {неправильный формат шрифта} grlnva1idFontNum=-14;{неправильный номер шрифта} После обращения к функции GRAPHRESULT признак ошибки сбрасывается, поэтому повторное обращение к ней вернет ноль. Функция GRAPHERRORMSG. Возвращает значение типа STRING, в котором по указанному коду ошибки дается соответствующее тек- стовое сообщение; формат обращения GRAPHERRORMSG ( <код> ) Здесь < код > - код ошибки, возвращаемый функцией GRAPH- RESULT. Например, типичная последовательность операторов для инициа- ции графического режима с автоматическим определением типа 216
драйвера и установкой максимального разрешения имеет следую- щий вид: var Driver, Regim, Error : integer; begin Driver := Detect; InitGraph(Driver, Regim, Error := GraphResult; if Error <> grOk then begin { ошибка в процедуре инициации: } writeln(GraphErrorMsg(Error)); end else { нет ошибки: } Чаще всего причиной возникновения ошибки при обращении к процедуре INITGRAPH является неправильное указание местополо- жения файла GRAPH.TPU, содержащего библиотеку графических процедур, или файла с драйвером графического адаптера (напри- мер, файла CGA.BGI для адаптера CGA). Чтобы компилятор Турбо- Паскаля мог отыскать файл GRAPH.TPU, необходимо соответствую- щим образом настроить среду Турбо-Паскаля. Для этого в режиме OPTIONS среды (см. гл. 3) в опции DIRECTORIES, в подопции UNIT DIRECTORIES, необходимо указать каталог, в котором разме- щен этот файл. Настройка на местоположение драйвера осуществ- ляется, как уже говорилось, указанием пути к нужному файлу в ка- честве фактического параметра вызова процедуры INITGRAPH. Ес- ли, например, драйвер зарегистрирован в подкаталоге DRIVERS ка- талога PASCAL на диске D, то нужно использовать вызов InitGraph(Driver, Regim, 'd:\Pascal\Drivers') Процедура CLOSEGRAPH. Прекращает работу адаптера в графи- ческом режиме и восстанавливает текстовый режим работы экрана. Обращение CLOSEGRAPH Процедура RESTORECRTMODE. Служит для кратковременного возврата в текстовый режим. Обращение RESTORECRTMODE В отличие от процедуры CLOSEGRAPH не сбрасываются уста- новленные параметры графического режима и не освобождается па- мять, выделенная для размещения графического драйвера. Функция GETGRAPHMODE. Возвращает значение типа INTEGER, в котором содержится код установленного режима работы графиче- ского адаптера. Обращение GETGRAPHMODE Процедура SETGRAPHMODE. Устанавливает новый режим рабо- ты графического адаптера; формат обращения SETGRAPHMODE ( < режим > ) 217
Здесь < режим > - выражение типа INTEGER, содержащее код устанавливаемого режима. Программа примера 40 иллюстрирует переход из графического режима в текстовый и обратно. Пример 40 PROGRAM Text_Graph_Mode; Uses Graph; var Driver, Regim, Error': integer; BEGIN {инициация графического режима:} • Driver := Detect; InitGraph(Driver, Regim, ’’); Error := GraphResult; if Error <> grOk then write In(GraphErrorMsg(Error)) else begin writein ('That’’s the Graphics Mode.’); writein (’ Press ’’Enter’'...’); readln; {переход в текстовый режим:} RestoreCRTMode; writein (' А это текстовый режим...'); readln; {возврат в графический режим:} SetGraphMode (GetGraphMode); writein ('That'*s again the Graphics Mode...’); readln; CloseGraph end END. В этом примере для вывода сообщений как в графическом, так и в текстовом режиме используется стандартная процедура WRITELN. Как отмечалось в 13.1, при выводе текстов в графиче- ском режиме необходима загрузка в память второй половины таб- лицы знакогенератора. Поскольку в этой простой программе не предусмотрена данная операция (проблема создания шрифтов и процедура их загрузки в память обсуждается в 13.4.1), все сообще- ния в графическом режиме выводятся на английском языке. Процедура DETECTGRAPH. Возвращает тип драйвера и режим его работы; формат обращения DETECTGRAPH ( < драйвер >, < режим > ) Здесь < драйвер >-переменная типа INTEGER, означающая тип драйвера; < режим >-переменная типа INTEGER, определяющая режим работы. В отличие от процедуры GETGRAPHMODE описываемая проце- дура возвращает в параметре < режим > максимально возможный для данного адаптера номер графического режима. 218
Функция GET DRIVERNAME. Возвращает значение типа STRING содержащее имя загруженного графического драйвера. Обращение GETDRIVERNAME Функция GETMAXMODE. Возвращает значение типа INTEGER содержащее количество возможных режимов работы адаптера. Обра щение GETMAXMODE Функция GETMODENAME. Возвращает значение типа STRING содержащее имя установленного режима работы адаптера. Обраще ние GEfMODENAME ( N ) Здесь N - выражение типа INTEGER, указывающее номер режи ма работы графического адаптера. Пример использования функции: var d,r,i : integer; begin d := Detect; InitGraphfd, r, ''); for i :=0 to GetMaxMode do writein (GetModeName(i)); readln; C loseGraph end. Процедура GETMODERANGE. Возвращает диапазон возможных режимов работы заданного графического адаптера; формат обраще- ния GETMODERANGE ( <тип>, <мин>, <макс> ) Здесь < тип> - выражение типа INTEGER, означающее тип адаптера; < мин > - переменная типа INTEGER, в которой возвращается нижнее возможное значение номера режима; < макс >-переменная типа INTEGER, указывающая верхнее значение номера. Если задано неправильное значение параметра <тип>, процеду- ра вернет значение -1 в обоих параметрах. Перед обращением к процедуре можно не устанавливать графический режим работы эк- рана. Программа примера 41 выведет на экран названия всех адапте- ров и диапазоны возможных номеров режимов их работы. Пример 41 Program GraphAdapterTable; Uses Graph; var D, L, H : integer; const N : array [1..11] of string[8] = (’CGA 'MCGA 'EGA 'EGA64 'EGAMono 'IBM8514 'HercMono', 'ATT400 ' , 'VGA ’ PC3270 ’, ’Ошибка ’); 219
begin writeln('Адаптер Мин. Макс.'); for D := 1 to 11 do beg i n GetModeRange(D, L, H); writeln(N[D], L: 7, H:10) end end. 13.2.2. Координаты, окна, страницы Функции GETMAXX и GETMAXY. Возвращают значения типа INTEGER, содержащие максимальные координаты экрана в теку- щем режиме работы соответственно по горизонтали и вертикали. Обращение ; GETMAXX или GETMAXY Пример использования функций: Uses Graph; var a, b : integer; begin a := Detect; InitGraph(a, b, ’’); writeln(GetMaxX, GetMaxY:5); readln; C loseGraph end. Функции GETX и GETY. Возвращают значения Типа INTEGER, содержащие текущие координаты курсора соответственно по гори- зонтали и вертикали. Обращение: GETX или GETY Координаты определяются относительно левого верхнего угла ок- на или, если окно не установлено, экрана. Процедура SETVIEWPORT. Устанавливает прямоугольное окно на графическом экране; формат обращения SETVIEWPORT (Xl, Yl, Х2, Y2, < отсечка > ) Здесь Xl, Y1- выражения типа INTEGER, задающие координаты лево- го верхнего угла окна; Х2, Y2 - выражения типа INTEGER, задающие координаты пра- вого нижнего угла окна; < отсечка >-выражение типа BOOLEAN, определяющее "отсеч- ку" не умещающихся в окне элементов изображения. Координаты задаются всегда относительно левого верхнего угла экрана. Если параметр < отсечка > имеет значение TRUE, элемен- ты изображения, не умещающиеся в пределах окна, отсекаются, в противном случае границы окна игнорируются. Для управления 220
( этим параметром можно использовать определенные в модуле кон- станты: const ClipOn = true; ClipOff = false; Пример 42 иллюстрирует действие параметра < отсечка >. Про- грамма строит два прямоугольных окна с разным значением этого параметра и выводит в них окружности одинакового радиуса. Для большей наглядности окна обводятся рамками. Пример 42 PROGRAM Cl i pO,N_Of f_Demonstrat ion; Uses Graph; var x, y, e : integer; const 1 xll = 0; yll = 20; xl2 = 120; yl2 = 65; x21 =200; y21 = yll; x22 =320; y22 = yl2; BEGIN x := Detect; InitGraph(x, y, ' '); e := GraphResult; if e <> grOk then wrjteln(GraphErrorMsg(e)) else begin writeln(' ClipOn: ClipOff:’); Rectangle(xll, yll, xl2, yl2); Rectangle(x21, y21, x22, y22); SetViewPort(xll, у11,эс12, yl2, ClipOn); Circle(20,20,60); SetViewPort(x21, y21, x22, y22, ClipOff); Circ le(20,20,60); readln; CloseGraph end END. Процедура GETVIEWSETTINGS. Возвращает координаты и при- знак отсечки текущего графического окна; формат обращения GETVIEWSETTINGS ( <окно> ) Здесь < окно >-переменная типа VIEWPORTTYPE. В модуле GRAPH определен тип: type ViewPortType = record xl, yl, x2, y2 : integer; Clip : Boolean end; Здесь xl, yl - координаты левого верхнего угла окна; х2, у2 - координаты правого нижнего угла; CLIP - признак отсечки изображения. 221
Программа примера 43 выведет на экран максимальные коорди- наты графического экрана Вашей ПЭВМ. Пример 43 Program GetV iewSetti ngsDemonst rat ion; Uses Graph; var ' ‘d, r : integer; vp : ViewPortType; BEGIN d := Detect; InitGraph(d, r, ’’); t if GraphResult = grOK then begin GetV iewSett ings(vp); CloseGraph; with vp do writeln(xl, yl:5, x2:5, y2:5r Clip:10) end END. Процедура MOVETO. Устанавливает новое текущее положение указателя. Обращение MOVETO (X, Y) Здесь X, Y - выражения типа INTEGER, задающие новые коор- динаты указателя соответственно по горизонтали и вертикали. Координаты определяются относительно левого верхнего угла ок- на или, если окно не установлено, экрана. Процедура MOVEREL. Устанавливает новое положение указателе в относительных координатах. Обращение MOVEREL (DX, DY) Здесь DX, DY - выражения типа INTEGER, задающие прираще ния новых координат указателя соответственно по горизонтали г вертикали. Приращения задаются относительно того положения, которое за нимал указатель к моменту обращения к процедуре. Процедура CLEARDEVICE, Очищает графический экран. Обраще ние CLEARDEVICE После обращения к процедуре все устанавливаемые в графиче ских процедурах и функциях параметры приобретают значения п< умолчанию. В частности, указатель устанавливается в левый верх ний угол экрана, отменяется назначение окон и т.д. Процедура CLEARVIEWPORT, Очищает графическое окно. Обра щение CLEAR VIEWPORT При очистке окно заполняется цветом с номером 0 из текуще! палитры(цветом фона). Процедура GET ASP ЕСТ RATIO. Возвращает два числа, позволяю щие оценить соотношение сторон экрана. Обращение GETASPECTRATIO (X, Y) Здесь X, Y - переменные типа WORD. Значения, возвращаемые в переменных X и Y, позволяют вы 222
числить отношение сторон графического экрана в пикселях. Этот коэффициент может использоваться при построении правильных геометрических фигур, таких как окружности, квадраты и т.п. Пример 44 показывает, как можно построить квадрат. Пример 44 PROGRAM QuadroDem; Uses Graph; const 1 = 100; var d, r, e : integer; a, b : word; BEGIN d := Detect; InitGraph(d, r, ' e := GraphResult; if e <> grOk then writeln(GraphErrorMsg(e)) else begin GetAspectRatio(a, b); writeln(GetModeName(GetGraphMode)) ; Rectangle(20, 20, round(l * (b / a)), 1); readln; if GetMaxMode <> O’ then, begin SetGraphMode(0) ; GetAspectRatio(a, b); writeln(GetModeName(GetGraphMode)); > Rectangle(20, 20, round(l * (b / a)), 1); , readln; CloseGraph end end END. Программа строит квадрат дважды: сначала для установленного автоматически режима работы адаптера, затем (если это допустимо для адаптера) - в режим.е меньшего разрешения. Процедура SETASPECTRATIO. Устанавливает масштабный коэф- фициент отношения сторон графического экрана. Обращение SETASPECTRATIO (X, У) Здесь X, Y - устанавливаемое соотношение сторон. В примере 45 программа строит 10 окружностей при разных со- отношениях сторон экрана. Пример 45 PROGRAM DemAspectRatio; Uses Graph , CRT; • const R = 50; dx = 700; var 223
d, m, e, i : integer; Xasp, Yasp : word; BEGIN ' d := Detect; InitGraph(d, m, ’’); e := GraphResult; if e <> grOk then writeln(GraphErrorMsg(e)) else beg i n GetAspectRatio(Xasp, Yasp); for i ;= 0 to 20 do beg i n SetAspectRatio(Xasp + i * dx, Yasp); Circle(GetMaxX div 2,‘GetMaxY div 2, £) end; while not KeyPressed do ; C loseGraph end END. Процедура SETACTIVEPAGE. Делает активной указанную страни- цу видеопамяти; формат обращения SETACTIVEPAGE ( <N стр> ) Здесь < N стр > - выражение типа WORD, означающее номер страницы. Процедура может использоваться только с адаптерами, поддер- живающими многостраничную работу (EGA, VGA и т.п.). Фактиче- ски процедура просто переадресует графический вывод в другую об- ласть видеопамяти, однако вывод текстов с помощью WRITE (WRITELN)' всегда осуществляется только на страницу, которая яв- ляется видимой в данный момент (активная страница может быть невидимой). Нумерация страниц начинается с нуля. Процедура SETVISUALPAGE. Делает видимой страницу с указан- ным номером; формат обращения SETVISUALPAGE ( <N стр > ) Здесь < N стр > - выражение типа WORD, означающее номер страницы. Процедура может использоваться только с адаптерами, йоддер- живающими многостраничную работу (EGA, VGA и т.п.). Нумера- ция страниц начинается с нуля. Программа примера 46 будет правильно работать только с адап- тером EGA или VGA. Программа сначала рисует квадрат в невиди- мой странице^ затем делает эту страницу видимой и рисует окруж- ность в другой, невидимой странице. Пример 46 PROGRAM Mu 1tyPageWork; Uses Graph; var d, r ,e : integer; BEGIN d := Detect; InitGraph(d, r, ’ ’); 224
е := GraphResult; if e <> grOk then writein- (GraphErrorMsg(e)) ч else begin SetGraphMode( 0 ); {гарантирует многостранич- ный режим для EGA/VGA} SetAct ivePage(1); Re‘ctangle(O, 0, GetMaxX div 2, GetMaxY div 2); SetVisua1 Page(1); SetAct ivePage{0); Circle(GetMaxX div 2, GetMaxY div 2, 100); readln; SetVisualPage(O); readln; C loseGraph end END. 13.2.3. Линии и точки Процедура PUT PIXEL. Выводит заданным цветом пиксель по указанным координатам; формат обращения PUTPIXEL (X, Y, <цвет> ) Здесь X, Y-выражения типа INTEGER, определяющие координаты; <цвет> - выражения типа WORD, определяющее цвет. Координаты задаются относительно левого верхнего угла окна или, если окно не установлено, относительно левого верхнего угла экрана. л Программа примера 47 периодически выводит на экран ’’звезд- ное небо" и затем гасит его. Для прекращения работы программы достаточно нажать на любую клавишу. Пример 47 PROGRAM DemPutPixel; Uses CRT, Graph; type PixelType = record x, у : integer; end; const N = 5000; {количество '’звезд"} var d, r, e, i : integer; a : array [1..N] of PixelType; BEGIN d := Detect; InitGraph(d, r, ’’); e := GraphResult; 4 if e <> grOk then 8 - Фаронов 225
writeln(GraphErrorMsg(e)) else begin SetGraphMode(0); {установить цветной режим} for i := 1 to N do with a[i] do ' begin ( • x := Random(GetMaxX); у := Random(GetMaxY) end; repeat | for i := 1 to N do with a[i] do PutPixel(x, y, succ(Random(white))); if not KeyPressed then for i := 1 to N do with a[i] do PutPixelfx, y, black) until KeyPressed; CloseGraph end; END. Функция GETPIXEL. Возвращает значение типа WORD, содержа- щее цвет пикселя с указанными координатами. Обращение GETPIXEL (! X, Y ) Здесь X, Y - выражения типа INTEGER; координаты пикселя. Процедура LINE. Вычерчивает линию с указанными координата- ми начала и конца. Обращение LINE ( Xl, Yl, Х2, Y2 ) Здесь Xl, Y1 - выражения типа INTEGER, задающие координаты нача- ла линии; ' Х2, Y2 - выражения типа INTEGER, задающие координаты конца линии. Линия вычерчивается текущим стилем и текущим цветом. В программе примера 48 экран расчерчивается случайными ли- ниями. Пример 48 PROGRAM DemOfLine; Uses CRT, Graph; var d,‘ r, e : integer; BEGIN d := Detect; InitGraph(d, • r, ’ ’) ; e := GraphResult; if e <> grOk then ' writeln(GraphErrorMsg(e)) else beg i n SetGraphMode(O); {установить цветной режим} 226
repeat SetColor(succ(Random(4))) ; Line(Random(GetMaxX) , Random(GetMaxY), Random(GetMaxX)f Random(GetMaxY)) unt i1 KeyPressed; CloseGraph end END. Процедура LINETO. Вычерчивает линию от текущего положения указателя до положения, заданного новыми его координатами. Обра- щение: LINETO ( X, Y ) Здесь X, Y - выражения типа INTEGER, означающие координаты нового положения указателя, они же - координаты второго конца линии. Процедура LINEREL. Вычерчивает линию от текущего положе- ния указателя до положения, заданного приращениями его коорди- нат. Обращение: LINEREL ( DX, DY ) Здесь DX, DY - выражения типа INTEGER, задающие прираще- ния координат нового положения указателя. В процедурах LINETO и LINEREL линия вычерчивается теку- щим стилемь и текущим цветом. Процедура SETLINESTYLE. Устанавливает новый стиль вычерчи- ваемых линий; формат обращения SETLINESTYLE ( <вид>, < образец >, < толщина > ) Здесь < вид> - выражение типа WORD, означающее вид линии; < образец >-выражение типа WORD, указывающее образец ли- нии; < толщина >-выражение типа WORD, задающее толщину ли- нии. Вид линии определяется следующими константами: const SolidLn = 0; {слошная линия} DottedLn = 1; {точечная линия} CenterLN = 2; {штрихпунктирная линия} DashedLn = 3; {пунктирная линия} UserBitLn = 4; {вид линии определяется пользователем} Параметр < образец > учитывается только для линий, вид кото- рых определяется пользователем (т.е/ в случае, когда <вид> = = UserBitLn). При этом два байта параметра < образец > определяют образец линии: каждый установленный в единицу бит этого слова соответствует светящемуся пикселю в лрнии, нулевой бит - несветя- щемуся пикселю. Таким образом, параметр < образец > определяет отрезок линии длиной в 16 пикселей. Этот образец периодически повторяется по всей длине линии. Параметр < толщина > может принимать одно из двух значе- ний: 8* 227
const NormWidth = 1; {толщина в один пиксель} ThickWidth = 3; {толщина в три пикселя} Отметим, что установленный процедурой стиль линий (текущий стиль) используется также при построении прямоугольников и мно- гоугольников. В примере 49 демонстрируются линии всех стандартных стилей, затем вводится слово-образец и демонстрируется линия с этим об- разцом заполнения. Для выхода из программы введите нуль. Пример 49 PROGRAM DemOfLine; Uses CRT, Graph; const style : array [0..4] of string[9] = ( 'SolidLn 'DottedLn ', 'Centerin', 'DashedLn', 'UserBitLn '); width : array [0.,1] of string[ll] = ('NormWidth:', 'ThickWidth:'); vaf1 d, r, e, i, j : integer; p : word; BEGIN d := Detect; InitGraph(d, r, ’’); e := GraphResult; if e <> grOk then writein (GraphErrorMsg(e)) else begin DirectVideo := false;, for j := 0 to 1 do begin 0utTextXY(0,j*40,width [j]); for i := 0 to 3 do begin SetLineStyle(i, 0, j * 2 + 1); Line(0, i * 8 + j * 40 + 12, 100, i* 8 + j * 40 + 12); OutTextXY (16*8, j*40+12+i*8, style[i]) end end; * j := 0; repeat OutTextXY (320, j+1, 'Pattern : '); GotoXY(50, j div 8 + 1); readln (p); if p *<> 0 then begin SetLineStyle(UserBitLn, p, NormWidth); L i ne(420, j + 4, 512, j + 4); inc(j, 8) 228
end unt i1 p = 0; CloseGraph end END. Процедура GETL1NESETTINGS. Возвращает текущий стиль ли- ний; формат обращения GETLINESETTINGS ( < стиль > ) Здесь < стиль >-переменная типа LINESETTINGSTYPE, в кото- рой возвращается текущий стиль линий. В модуле GRAPH определен тип: । type LineSettingsType = record LineStyle : word; {вид линии} Pattern : word; {образец} Thickness : word {толщина} end; < Процедура SETWRITEMODE. Устанавливает способ взаимодейст- вия в!новь выводимых линий с уже существующим на экране изо- бражением; формат обращения SfiTWRITEMODE ( < способ > ) Здесь < способ >--выражение типа INTEGER, задающее способ взаимодействия выводимых линий с изображением. Если параметр < способ > имеет значение 0, выводимые линии накладываются на существующее изображение обычным образом. Если значение 1,| то это наложение осуществляется с применением логической операции XOR (исключительное ИЛИ): в точках пересечения выводимой ли- нии с имеющимся на экране изображением светимость пикселей инвертируется на обратную, так что два следующих друг за другом вывода одной и той же линии на экран не изменят его вид. Действие процедуры распространяется на процедуры DRAWPOLY, LINE, LINEREL, LINETO и RECTANGLE. Для задания параметра < способ > можно использовать предварительно опреде- ленные в модуле константы: const CopyPut = 0; {наложение оператором MOV} ‘ XORPut ’= 1; {наложение оператором XOR} В примере 50 на экране имитируется вид часового циферблата с бегущей секундной стрелкой. Для выхода из программы нажмите на любую клавишу. Пример 50 Program СlockEmulation; Uses Graph, CRT; var d, r, i, xO, yO, xl, yl, x2, y2 : integer; Xasp, Yasp : word; const dr = 0.9; BEGIN d := detect; InitGraph(d, r, 229
i := GraphResult; if i <> grOK then writeln(GraphErrorMSG(i)) else begin xO := GetMaxX div 2; yO := GetMaxY div 2; GetAspectRatio(Xasp, Yasp); r := yO; . Circle(xO, yO, r); Circle(xO, yO, Round(r*dr)); for i:=0 to 59 do begin xl:=x0 + Round(dr*r*sin(2*pi*i/60)); x2:=x0 + Round(r*sin(2*pi*i/60)); yl:=yO - Round(dr*r*Xasp*cos(2*pi*i/60)/Yasp); y2:=y0 - Round(r*Xasp*cos(2*pi*i/60)/Yasp); Line(xl, yl, x2, y2) end; FloodF-i 11 (xO, yO, white); SetWriteMode(XORPut); repeat for i := 0 to 59 do if not KeyPressed then begin x2:=x0+Round(dr*r*sin(2*pi*i/60)); y2:=y0-Round(dr*r*Xasp*cos(2*pi*i/60)/Yasp); Line(x0, yO, x2, y2); delay(lOOO); Line(x0, yO, x2, y2) end until KeyPressed end ' END. 13.2.4. Многоугольники Процедура RECTANGLE. Вычерчивает прямоугольник с указанг ными координатами углов; формат обращения RECTANGLE ( Xl, Yl, Х2, Y2 ) Здесь Xl, Y1 - выражения типа INTEGER, задающие координаты левого верхнего угла; Х2, Y2 - выражения типа INTEGER, задающие координаты пра- вого нижнего угла. 230
Прямоугольник вычерчивается с использованием текущего цвета и текущего стиля линий (пример 51). Пример 51 PROGRAM RectangleDem; Uses Graph, CRT; var d, r, e, xl, yl, x2, y2 : integer; BEGIN d := Detect; InitGraph(d, r, ’’); ; e := GraphResult; if e <> grOK then wr'iteln(GraphErrorMsg(e)) else begin / SetGraphMode(O); {установить цветной режим} repeat - SetColor(succ(Random(white))); SetLineStyle(Random(4), 0, 2 * Random(2) + 1); xl := Random(GetMaxX); yl := Random(GetMaxY); x2 := x). + Random(GetMaxX); y2 := yl + Random(getMaxY); if x2 > GetMaxX then x2 := GetMaxX; if y2 > GetMaxY then y2 := GetMaxY; Rectangle(xl, yl, x2, y2); delay(lOO) until KeyPressed; CloseGraph end END. Процедура BAR3D. Вычерчивает трехмерное изображение парал- лелепипеда и закрашивает его переднюю грань; формат обращения BAR3D ( Xl, Yl, Х2, Y2, < глубина >, <в.грань> ) Здесь X l, Y1-выражения типа INTEGER, задающие координаты лево- го верхнего угла передней грани; Х 2, Y2- выражения типа INTEGER, задающие координаты пра- вого нижнего угла передней грани; < глубина >- выражение типа INTEGER, задающее третье изме- рение трехмерного изображения (“глубину”) в пикселях; < в.грань >-выражение типа BOOLEAN, определяющее способ изоражения верхней грани. Если параметр < в.грань > имеет значение TRUE, верхняя грань параллелепипеда вычерчивается, в противном случае - не вычерчи- вается (этот вариант используется для изображения поставленных друг на друга параллелепипедов, см. пример 52). В качестве значе- 231
ния параметра <в,грань> может использоваться одна из следую- щих констант, определенных в модуле GRAPH: ’ const TopOn = true; TopOff = false;. При вычерчивании используется текущий стиль линий и теку- щий цвет. Передняя грань закрашивается текущим стилем закра- ски. Процедура обычно используется для вывода различных диаг- рамм. Следует учесть, что параллелепипед ’’прозрачен”, т.е. за его незакрашенными гранями могут быть видны другие элементы изо- бражения (пример 52). Пример 52 PROGRAM Bar3D_Demonstrat ion; Uses Graph; var dr rr e : integer; , BEGIN d := Detect; InitGraph(d, r, ’’); e := GraphResult; if e <> grOk then writeln(GraphErrorMsg(e)) else begin Bar3D (80, 100, 120, 180, 15, TopOn); Bar3D (150, 150, 190, 180, 15, TopOff) Bar3D (230, 50, 250, 150, 15, TopOn); Bar3D (220, 150, 260, 180, 15, TopOn); Bar3D (300, 150, 340, 180, 15, TopOff) Bar3D (300, 50, 340, 150, 15, TopOn); Readln; CloseGraph end END. Процедура DRAWPOLY. Вычерчивает произвольную ломаную ли- нию, заданную координатами точек излома; формат обращения DRAWPOLY ( N, < координаты > ) Здесь N - выражение типа WORD, задающее количество точек излома, включая обе крайние точки; < координаты >- выражение типа POINTTYPE, содержащее коор- динаты точек излома. Координаты точек излома задаются парой значений типа WORD: первое определяет горизонтальную, второе * вертикальную координату. Для них можно использовать следующий определённый в модуле тип: type 7; PointType с record х, у : word end; • 232
При вычерчивании используются текущий цвет и текущий стиль линий. В примере 53 показано, как можно с помощью этой процедуры вывести на экран график синуса: Пример 53 . PROGRAM Poly_Sinus; Uses Graph; const N .f 100; var , d„ r, e : integer; m : array [O..(N + 1)] of PointType; i : word; BEGIli d := Detect; InitGraph(d, r, ''); e := GraphResult; if e <> grOk then writeln(GraphErrorMsg(e)) else begin for i := 0 to N do with m[i] do begin x := trunc(i * GetMaxX / N); у := trunc(GetMaxY * (-sin(2*Pi*i/N)+l)/2) end; m[succ(N)] .x := m[0] .x; m[succ(n)].y := m[0].y; DrawPoly(N + 2, m); Readln; CloseGraph end END. В этом примере для проведения горизонтальной прямой ис- пользуется "замыкание” ломаной - первая и последняя координаты ее точек излома выбраны совпадающими. 13.2.5. Дуги, окружности, эллипсы Процедура CIRCLE. Вычерчивает окружность; формат обращения CIRCLE ( X, Y, < радиус > ) Здесь X, Y-выражения типа INTEGER, означающие координаты центра; < радиус > - выражение типа WORD, указывающее радиус в пик- селях. Окружность выводится текущим цветом. Толщина линии уста- навливается текущим стилем, вид линии всегда SOLIDLN (сплош- ная). Процедура вычерчивает правильную окружность (с учетом из- менения линейного размера радиуса в зависимости от его направле- 233
ния относительно сторон графического экрана, т.е. с учетом коэф- фициента GETASPECTRATIO). В связи с этим параметр < радиус > определяет количество пикселей в горизонтальном направлении. В примере 54 экран постепенно заполняется случайными окруж- ностями (для выхода нажать на любую клавишу). Пример 54 PROGRAM CircleDem; Uses Graph, CRT; var d, r, e, x, у : integer; BEGIN d := Detect; InitGraph(d, r, ’ ') ; e := GraphResult; ' if e <> grOK then writeln(GraphErrorMsg(e)) else begin SetGraphMode(0); {установить цветной режим} repeat SetCo lor(succ(Random(white))); SetLineStyle(Random(4), 0, 2 * Random(2) + 1); x := Random(GetMaxX); у := Random(GetMaxY); Circle (x, y,_ Random(GetMaxY div 2)); delay(500) unt i 1 ‘KeyPressed; C loseGraph • '!•’' end - ' ’ • END. Процедура ARC. Чертит дугу окружности; формат обращения ; ARC ( X, Y, <нач.угол>, <кон.угол>, <радиус> Здесь v X, Y - выражения типа INTEGER, указывающие координаты цен- тра; < нач.угол >- выражение типа WORD, означающее начальный угол; < кон.угол> - выражение типа WORD, означающее конечный угол; < радиус > - выражение типа WORD, задающее радиус. Углы отсчитываются против часовой стрелки и указываются в градусах. Нулевой угол соответствует горизонтальному направлению вектора слева направо. Если задать значение начального угла О; и конечного 359, то будет выведена полная окружность. ;* q Вид, толщина и цвет линии те же, что и в процедуре CIRCLE. •) Вот как выглядят две дуги: одна с углами 0 и 90, вторая - 270 и 450 градусов (пример 55). Пример 55 PROGRAM ArcDem; Uses Graph, CRT; 234
var d, r, e, dr : integer; Xasp, Yasp : word; const rad = 90; rlO = rad+10; BEGIN d := Detect; InitGraph(d, r, ’’); e := GraphResult; if e <> grOK then writeln(GraphErrorMsg(e)) else begin GetAspectRatiо(Xasp, Yasp); dr:=Round(longint(rlO)*Xasp/Yasp); Line (0, dr, 2*rl0, dr); Line (rlO, 0, rlO, 2*dr); Arc' (rlO, dr, 0, 90, rad); OutTextXY(rlO, 2*dr+8, ’0-90’); Line (3*rl0, dr, 5*rl0, dr); Line (4*rl0, 0, 4*rl0, 2*dr); Arc (4*r10,dr,270,450,rad); OutTextXY(4*r 10, 2*dr+8, '27,0-450'); while not KeyPressed do ; CloseGraph end END. Процедура GETARCCOORDS. Возвращает координаты трех точек: центра, начала и конца дуги; формат обращения GETARCCOORDS ( < координаты > ) Здесь < координаты > - переменная типа ARCCOORDSTYPE, воз- вращающая координаты центра, начала и конца дуги. В модуле GRAPH определен следующий тип: type ArcCoordsType = record X, Y : integer; {координаты центра} Xstart, {координаты} Ystart : integer; {начала дуги} Xend, {координаты} Yend : integer; {конца дуги} end; Совместное использование процедур ARC и GETARCCOORDS позволяет вычерчивать сопряжение двух прямых с помощью дуги. Обратите внимание на коррекцию длины радиуса в примере 56 (вычерчивается прямоугольник со скругленными углами). Пример 56 PROGRAM Arc_and_GetArcCoordS-Demonstrat ion; Uses Graph; const 235
RadX = 50; {горизонтальный радиус} lx = 400; ly = 100; var d, r, e : integer; coo : ArcCoordsType; xl, yl : integer; xa, ya : word; RadY : integer; {вертикальный радиус} BEGIN d := Detect; InitGraph(d, r, ’’); e := GraphResult; if e <> grOK then writeln(GraphErrorMsg(e)) else begin GetAspectRatio(xa, ya); { Найти вертикальный радиус с учетом масштабного коэффициента } RadY := round(RadX * (ха / уа)); xl := (GetMaxX - lx) div 2; yl := (GetMaxY - 2 * RadY - ly) div 2; L ine (xl, yl, xl + lx, yl); Arc (xl + lx, yl + RadY, 0, 90, RadX); , GetArcCoords(coo); with coo do begin Line (Xstart, Ystart, Xstart, Ystart+ly); Arc (Xstart-RadX, Ystart+ly, 270, 0, RadX); GetArcCoords (coo); Line (Xstart, Ystart, Xstart-lx, Ystart); Arc (Xstart-lx, Ystart-RadY, 180,^270, RadX); GetArcCoords (coo); Line (Xstart, Ystart, Xstart, Ystart-ly); Arc (Xstart+RadX, Ystart-ly, 90, 180, RadX) end; readln; CloseGraph end END. Процедура ELLIPSE. Вычерчивает эллипсную дугу; формат обра- щения ELLIPSE ( X, Y, <нач.угол>, <кон.угол>, RX, RY ) Здесь X, Y-выражения типа INTEGER, определяющие координаты центра; < нач.угол >, < кон.угол > - выражения типа WORD, задающие начальный и конечный углы; RX, RY - выражения типа WORD, определяющие горизонталь- ный и вертикальный радиусы эллипса в пикселях. 236
При вычерчивании дуги эллипса вид, толщина и цвет те же, что и в процедуре CIRCLE, отсчет углов аналогичен принятому в процедуре ARC. Если радиусы согласовать с учетом масштабного коэффициента GETASPECTRATIO, будет вычерчена правильная ок- ружность. В примере 57 на экран выводятся эллипсы с разным отноше- нием радиусов. Для прекращения работы программы достаточно нажать на любую клавишу. Пример 57 PROGRAM EllipseDem; Uses Graph, Crt; var d, r, e : integer; xa, ya : word; BEGIN d := Detect; InitGraph(d, r, ''); e := GraphResult; if e <> grOK then writeln(GraphErrorMsg(e)) else i begin Line (0, 100, 160, 100); Line (80, 55, 80, 145); Ellipse (80, 100, 0, 359, 40, 40); OutTextXY(18*8, 8*8, 'RX=RY'); Line (190, 100, 410, 100); Line (300, 55, 300, 145); Ellipse (300, 100, 0, 359, 100, 20); OutTextXY(45*8, 8*8, 'RX=5*RY'); Line (440, 100, 600, 100); Line (520, 55, 520, 145); GetAspectRatio(xa, ya); Ellipse (520, 100, 0, 270, 40, round(40*(xa/ya))); * 1 0utTextXY(69*8, 8*8, ’AspectRatio'); while not KeyPressed do ; CloseGraph end END. 13.2.6. Краски, палитра, заполнения Процедура SETCOLOR. Устанавливает текущий цвет для ' выво- димых линий и символов; формат обращения SETCOLOR ( <цвет> ) 237
Здесь < цвет >-выражение типа WORD, задающее текущий цвет. В модуле GRAPH определены следующие константы для зада- ния цвета: const Black = 0; {черный} Blue = 1; {синий} Green = 2; {зеленый} Cayn = 3; {голубой} Red = 4; {красный} Magenta = 5; {фиолетовый} Brown = 6; {коричневый} L ightGray = 7; {светло-серый} DarkGray = 8; {темно-серый} L ightBlue = 9; {ярко-синий} L ightGreen = 10; {ярко-зеленый} L i ghtCyan = 11; {ярко-голубой} L ightRed = 12; {розовый} L ightMagenta = 13; {малиновый} Yellow = 14; {желтый} White = 15; {белый} Функция GETCOLOR. Возвращает значение типа WORD, содер- жащее код текущего цвета. Обращение GETCOLOR Функция GETMAXCOLOR. Возвращает значение типа WORD, со- держащее максимально доступный код цвета, который можно ис- пользовать для обращения к SETCOLOR. Обращение GETMAXCOLOR Процедура SETBKCOLOR. Устанавливает цвет фона; формат об- ращения SETBKCOLOR ( <цвет> ) Здесь <цвет> - выражение типа WORD, задающее цвет фона. • Установка нового цвета фона немедленно изменяет цвет 1рафи- ческого экрана. Для CGA-адаптера в режиме высокого разрешения установка цвета фона изменяет цвет активных пикселей. Если Ваша ПЭВМ оснащена цветным экраном, программа при- мера 58 продемонстрирует сказанное (для выхода из программы до- статочно нажать на любую клавишу). Пример 58 PROGRAM SetBkColorDem; Uses Graph, CRT; const textl ='B А С К G R 0 U N Dr; text2 =’C 0 L 0 Rr; var d, r, e, i, color : integer; BEGIN d Detect; InitGraph(d, r, e := GraphResult; 238
if e <> grOK then ~~ writeln(Graph£rrorMsg(e)) else begin OutTextXY((GetMaxX-TextWidth(textl)) div 2, (GetMaxY-3*8) div 2, textl); OutTextXY((GetMaxX-TextWidth(text2)) .div 2,- (GetMaxY-3*8) div 2+3*8, text2); for i := 0 to GetMaxY div 10 do Rectangle(i*3, i, GetMaxX-i*3, GetMaxY-i); color := black; repeat SetBkColor(color); de 1 ay(500); inc(color); if color > white then color := black until KeyPressed; CloseGraph end END. Функция GETBKCOLOR. Возвращает значение типа WORD, со- держащее текущий цвет фона.Обращение GETBKCOLOR Процедура SET PALETTE. Заменяет один из цветов палитры на новый цвет; формат обращения SETPALETTE ( N, <цвет> ) Здесь N- выражение типа WORD, указывающее номер цвета в палит- ре; < цвет >-выражение типа SHORTINT, определяющее номер вновь устанавливаемого цвета. Следует заметить, что мне так и не удалось добиться ^работы этой процедуры с CGA-адаптером (см. комментарий к примеру 60), хотя с адаптерами EGA и VGA программа примера 59 дает нуж- ный результат: выводит на экран ряд прямых разного цвета и за- тем случайным образом меняет цвета палитры (для CGA-адаптера нет изменения цветов). Пример 59 PROGRAM SetPaletteDem; Uses Graph, CRT; var d, r, e, N, i, color : integer; BEGIN d := Detect; InitGraph(d, r, e := GraphResult; if e <> grOK then writeln(GraphErrorMsg(e)) else begin 239
SetGraphMode(0); {установить цветной режим} SetLineStyle(So 1idLn, 0, ThickWidth); for color := 0 to GetMaxColor do begin SetColor(color); Line (GetMaxX div 3, color * 10, 2 * GetMaxX div 3, color * 10) end; repeat de lay(200); SetPalette(random(GetMaxColor), random(GetMaxColor)) until KeyPressed end END. Процедура GETPALETTE. Возвращает размер и цвета текущей палитры; формат обращения GETPALETTE ( < палитра > ) Здесь < палитра > - переменная типа PALETTETYPE, возвращаю- щая размер и цвета палитры. В модуле GRAPH.TPU определена константа const MaxColors - 15; И ТИП type Ра letteType=record size :word; {количество цветов в палитре} colors:array [0..MaxCoTors] of shortint {коды входящих в палитру цветов} end; х С помощью программы примера 60 можно вывести на экран коды всех цветов, доступные в текущей палитре. Пример 60 PROGRAM GetPaletteDem; Uses Graph, Crt; var Palette : PaletteType; d, r, e, i : integer; s : string; BEGIN d := Detect; InitGraph(d, r, ’’); e := GraphResult; if e <> grOk then writeln(GraphErrorMsg(e)) else begin GetPalette(Palette); with Palette do for i := 0 to size-1 do 240
begin ' str(colors[i]:3,s); OutTextXY(i*3*8,8,s) end; while not KeyPressed do; end END. Если Вы исполните эту программу на ПЭВМ с CGA-адаптером, то с удивлением обнаружите на экране строку из 16 чисел от 0 до 15, хотя дисплей будет работать в монохромном режиме высокого разрешения CGAHi, в котором программе доступны лишь два цве- та. Конечно, можно считать, что в палитру включаются вообще все доступные цвета - ведь один из цветов палитры можно установить произвольно (для CGA-адаптера - это цвет фона, см. пример 56), Однако согласитесь, что в этом случае теряется смысл процедуры, так как любой другой адаптер, кроме IBM8514, имеет те же 16 до- ступных цветов, и, следовательно, процедура всегда будет возвра- щать один и тот же результат. У меня была возможность прове- рить работу этой программы на ПЭВМ ЕС 8531.М2 производства НРБ и на Mazovia (СМ 1914) производства ПНР. Так как результат в обоих случаях был одинаков, можно говорить об одной из очень небольшого числа недоработок Турбо-Паскаля версии 5.0: стандарт- ный драйвер CGA.BGI неправильно обрабатывает обращения проце- дур SETPALETTE (см. пример 59) и GETPALETTE. (Кстати, проце- дура GETPALETTE в предыдущей версии Турбо-Паскаля 4.0 работа- ет так, как и следует от нее ожидать, и программа из примера 58 "честно" выводит на экран единственное число 0. Чуть по-другому ведет себя и программа из примера 59: она периодически меняет цвет фона, т.е. цвет с номером 0 в палитре, хотя остальные цвета остаются без изменения.) Процедура SETALLPALETTE. Изменяет одновременно несколько цветов палитры; формат обращения SETALLPALETTE ( <уст.цвета> ) Здесь <уст.цвета> - устанавливаемые цвета. Параметр <уст.цвета> в заголовке процедуры описан как нети- пизированный параметр. Первый байт этого параметра должен со- держать длину палитры N, остальные N байт - номера вновь уста- навливаемых цветов в диапазоне от -1 до MaxColors. Код -1 означа- ет, что соответствующий цвет исходной палитры не меняется. При работе с CGA-адаптером процедура устанавливает только цвет фона. В отличие от примера 59 программа примера 61 меняет одно- временно все цвета палитры. Пример 61 PROGRAM SetAlIPaletteDem; Uses Graph, CRT; var Palette : array [0..MaxCoTors] of shortint; d, r, e, i : integer; BEGIN 241
d := Detect; InitGraph(d, r, ' '); e := GraphResult; if e <> grOk then writeln(GraphErrorMsg(e)) else begin if d = CGA then SetGraphMode(01; SetLineStyle(SolidLn, 0, ThickWidth); \ . for i := 1 to GetMaxColor do beg i n SetColor(i); Line(GetMaxX div 3, i*10, 2*GetMaxX div 3, i*10) end; PalettefO] := MaxColors; repeat, for i := 1 to MaxColors do 1 Palette[i] := Random(succ(MaxCoLors)); 1 SetAl 1 Palette(Palette); de lay (10'0); until KeyPressed; CloseGraph end end. . । Функция GET PALETTESIZE. Возвращает значение типа INTEGER, содержащее размер палитры (максимальное количество доступных цветов). Обращение GETPALETTESIZE При работе с CGA-адаптером процедура возвращает значение 16, хотя в CGA-палитре может быть не более четырех цветов.- Все это подтверждает замечание, сделанное при описании процедуры GETPALETTE, о неточной работе драйвера CGA.BGI. Процедура GET DEFAULT PALETTE. Возвращает структуру палит- ры, устанавливаемую по умолчанию (в режиме автонастройки); фор- мат обращения GETDEFAULTPALETTE ( < палитра > ) Здесь < палитра >- переменная типа PALETTETYPE (см. проце- дуру GETPALETTE); возвращает размер и цвета палитры. Процедура SETFILLSTYLE. Устанавливает стиль (тип и цвет) штриховки; формат обращения SETFILLSTYLE ( < штриховка >, <цвет> ) Здесь < штриховка >-переменная типа WORD, определяющая тип штриховки; 242
<цвет> - переменная типа WORD; цвет штриховки. С помощью штриховки можно покрывать какие-либо фрагменты изображения периодически повторяющимся узором. Для указания типа штриховки используются следующие предварительно опреде- ленные константы: const EmptyFi11 = 0; {штриховка фоном (узор отсутствует)} SolidFill LineF i11 LtSlashFill SlashFill = 1; {сплошная штриховка} = 2; {штриховка линиями} = 3; {штриховка ///////} = 4; {штриховка утолщенными ///////} BkSlashFill LtBkSlashFi11 = 5; {штриховка \\\\\\\} = 6; {штриховка утолщенными \\\\\\\} HatchFi11 XHatchFi11 InterleaveF i1 = 7; {штриховка +++++++} = 8; {штриховка ххххххх} 1= 9; {штриховка в прямоугольную клеточку} WideDotFi11 = 10;{штриховка редкими точками} CloseDotFi11 = 11;{штриховка частыми точками} UserFill = 12;{штриховка определяется пользователем} Программа из примера 62 продемонстрирует Вам все стандарт- ные типы штриховки. Пример 62 PROGRAM FillStyleDem; Uses Graph, CRT; var d, r, e, i, j, x, у : integer; BEGIN d := Detect; I.nitGraph(d, r, '!); ’ e := GraphResuIt; if e <> grOk then writeln(GranhFrrorMsg(e)) else begin SetGraphMode(O); x := GetMaxX div 9; у := GetMaxY div 7; for j := 0 to 2 do for i := 0 to 3 do begin Rectanglef(2*i+l)*x, (2*j+l)*y, (2*i+2)*x, (2*j+2)*y); SetFi 1 IStyle( i + j*4, j + 1); Barf(2*i+l)*x+l, (2*j+l)*y+l, (2*i+2)*x-l, (2*j+2)*y-l) end; 243
while not KeyPressed do ; CloseGraph end END. Если параметр < штриховка > имеет значение 12 (UserFill), то рисунок узора определяется программистом путем обращения к процедуре SETFILLPATTERN. Процедура SETFILLPATTERN. Устанавливает образец риСунка и цвет штриховки; формат обращения SETFILLPATTERN ( < образец >, <цвет> ) Здесь < образец > - выражение типа FILLPATTERNTYPE, устанав- ливающее образец рисунка для < штриховка >= UserFill в процедуре SETFILLSTYLE; < цвет >~ переменная типа WORD, определяющая цвет штри- ховки. Образец рисунка задается в виде матрицы из 8x8 пикселей и может быть представлен массивом из 8 байт следующего типа: type Fi 1 IPatternType « array [1..8] of byte; Каждый разряд любого из этих байтов управляет светимостью пикселя, причем первый байт определяет 8 пикселей первой строки на экране, второй байт - 8 пикселей второй строки и т.д., например: Образец Значение байта Образец Значение байта -0--0--0 $49 ________ $00 O--O--O- $92 -- -00--- $18 -0--0--0 $49 --О--О-- $24 O--O--O- $92 -О----О- $42 -0-- 0--0 $49 -О----О- $42 0--0--0- $92 --Q--Q-- $24 -0--0--0 $49 ---00--- $18 O--O--O- $92 ________ . $00 • Можно разрисовать экран этими образцами, как в программе примера 63. Пример 63 PROGRAM Fi1IPatternDem; Uses Graph, CRT; • const pattl : Fi 1 IPatternType = ($49, $92, $49, $92, $49, $92, $49, $92); patt2 : Fi1IPatternType = ($00, $18, $24, $42,- $42, $24, $18, $00); var d, r, e, i, j, x, у : integer; BEGIN d := Detect; , InitGraph(d, r, ’’); e := GraphResult; 244
if e <> grOk then writeln(GraphErrorMsg(e)) else begin SetGraphMode(O); SetFi 1 IStyle(UserFi 11, 1); SetFi1IPattern(pattl, 1); Bar(0,0,GetMaxX diy 2, GetMaxY div.2); SetFi11 Pattern(pat12 , 2); Bar(GetMaxX div 2, GetMaxY div 2, GetMaxX,GetMaxY); while not KeyPressed do ; CloseGraph end END. Если при обращении к процедуре указан недопустимый код цвета, вызов процедуры игнорируется и сохраняется ранее установ- ленный образец (штриховки. В частности, если в предыдущем при- мере убрать, оператор SetGraphMode(0); устанавливающий цветной режим работы CGA-адаптера, на экран ПЭВМ, оснащенной адаптером этого типа, будут выведены два оди- наковых прямоугольника, так как обращение SetFi 11 Pattern(pat12, 2); содержит недопустимо большой для данного режима код цвета и обращение игнорируется. Сказанное, однако, не относится к проце- дуре SETFILLSTYLE для значения параметра < штриховка > в диа- пазоне от 0 до 11: программа будет нормально работать и в режиме высокого разрешения CGA-адаптера, причем все цвета палитры, кроме цвета фона, будут при этом заменяться белым цветом. Процедура GET FILLPATTERN. Возвращает образец закраски, уста- новленный ранее процедурой SETFILLPATTERN; формат обращения GETFILLPATTERN ( < образец > ) Здесь < образец >-переменная типа FILLPATTERNTYPE, в кото- рой возвращается образец закраски. Если программа не устанавливала образец с помощью процеду- ры SETFILLPATTERN, массив < образец > заполняется байтами со значением 255 ($FF). Процедура GETFILLSETTINGS. Возвращает текущий стиль штри- ховки; формат обращения GETFILLSETTINGS ( < стиль > ) Здесь < стиль >-переменная типа FILLSETTINGSTYPE, в кото- рой возвращается текущий стиль штриховки. В модуле GRAPH.TPU определен тип type Fi 1 ISettingsType = record Pattern : word; {образец} Color : word {цвет} end; 245
Поля PATTERN и COLOR в этой записи имеют то же назначе- чение, что и параметры < образец > и <цвет> при обращении к процедуре SETFILLSTYLE. Процедура SETRGBPALETTE. Устанавливает цветовую гамму при работе с дисплеем IBM 8514 и адаптером VGA. Обращение SETRGBPALETTE ( NUM, RED, GREEN, BLUE ) Здесь NUM, RED, GREEN, BLUE - выражения типа INTEGER, определяющие соответственно номер цвета, интенсивность красной, зеленой и синей составляющих цвета. Новая разработка фирмы IBM - дисплей 8514 способен воспроиз- водить на экране до 256 цветов с разрешением 1024x768. Процедура FLOODFILL. Штрихует произвольную замкнутую фи- гуру; формат обращения FLOODFILL ( X, Y, <цв.границы> ) Здесь X, Y - выражения типа INTEGER; координаты любой точки внутри замкнутой фигуры; < цв.границы > - выражение типа WORD; цвет граничной линии. Процедура штрихует замкнутую фигуру текущим образцом штриховки и текущим цветом (устанавливаются процедурой SETFILLSTYLE). Если штрихуемая фигура не замкнута, штриховка "разольется" по всему экрану. На цветном экране программа примера 64 продемонстрирует за- краску случайных окружностей. Пример 64 PROGRAM FloodFi llDem; ' Uses Graph, CRT; var d, r, e, x, у, c : integer; BEGIN d := Detect; InitGraph(d, r, ’’); e := GraphResult; if e '<> grOk then writeln(GraphErrorMsg(e)) else ' 1 begin SetGraphMode(0); \ repeat SetFi1lStyle(Random(12), Random(succ(GetMaxColor))); x := Random (GetMaxX); у := Random (GetMaxY); c := Random (succ(GetMaxColor)); SetColor(c); Circle(x, y, Random(GetMaxY div 2)); FloodFi11 (x, y, c) unt i 1 KeyPressed; CloseGraph end END. 246
Процедура BAR. Штрихует прямоугольную область экрана. Обра- щение BAR ( Xl, Yl, Х2, Y2 ) Здесь Xl, Y1 и Х2, Y2-выражения типа INTEGER, определяю- щие координаты левого верхнего и правого нижнего углов закраши- ваемой области. Процедура штрихует (но не обводит) прямоугольник текущим образцом штриховки и текущим цветом, которые устанавливаются пррцедурой SETFILLSTYLE. Программа примера 65 дает красивые цветовые эффекты (закра- ска случайных прямоугольников). Пример 65 PROGRAM BarDem; ’ Uses.Graph, CRT; var d, r, e : integer; ‘•BEGIN ‘d := Detect; InitGraph(d, r, ’’); 7 e := GraphResult; if e grOk then wr i teIn(GraphErrorMsg(e)) , else begin SetGraphMode(O); repeat SetFi1IStyle(Random(12), Random(succ(GetMaxColor))); Bar CRandom(GetMaxX), Random(GetMaxY), Random(GetMaxX), Random(GetMaxY)); . until KeyPressed; CloseGraph end END. v Процедура FILLPOLY. Обводит линией и штрихует замкнутый многоугольник; формат обращения FILLPOLY ( N, < координаты > ) Здесь N - выражение типа WORD, задающее количество вершин замк- нутого многоугольника; < координаты > - выражение типа POINTTYPE, содержащее коор- динаты вершин. Координаты вершин задаются парой значений типа WORD: пер- вое определяет горизонтальную, второе - вертикальную координату. Для них можно использовать следующий определенный в модуле тип: type PointType = record х, у : integer end; 247
Стиль и цвет линии контура задается процедурами SETLINE- STYLE и SETCOLOR, стиль и цвет штриховки - процедурой SET- FILLSTYLE. В примере 66 на экране штрихуются случайные многоугольники, Пример 66 PROGRAM FillPolyDem; Uses Graph, CRT; var d, r, e : integer; p : array [1..6] of PointType; n, i : word; BEGIN d := Detect; InitGraphfd^ r, ’’); e := GraphResult; if e <> grOk then writein(GraphErrorMsg(e)) else beg i n SetGraphMode(O) ; repeat SetFi1lStyle(Random(12), Random(succ(GetMaxColor))); SetColor (Random(succ(GetMaxColor))); n := Random (4) + 3; / for i := 1 to n do with p[i] do begin x := Random (GetMaxX); у := Random (GetMaxY) end; Fi 1 IPoly (n, p) until KeyPressed; C loseGraph end END. Процедура FILLELLIPSE. Обводит линией и штрихует эллипс; формат обращения FILLELLIPSE ( X, Y, <нач.угол>, <кон.угол>, RX, RY ) Здесь X, Y - выражения типа INTEGER, задающие координаты центра; <нач.угол> и <кон.угол>, RX и RY- выражения типа WORD, определяющие соответственно начальный и конечный углы, гори- зонтальный и вертикальный радиус эллипса в пикселях. Эллипс обводится линией, заданной процедурами SETLINE- STYLE и SETCOLOR, и заполняется штриховкой, заданной процеду- рой SETFILLSTYLE. Процедура SECTOR. Вычерчивает и штрихует эллипсный сектор; формат обращения SECTOR ( X, Y, <нач.угол>, <кон.угол>, RX, RY ) Параметры обращения см. выше. 248
Сектор обводится линией, заданной процедурами SETLINESTYLE и SETCOLOR, и заполняется штриховкой, заданной процедурой SETFILLSTYLE. Программа из примера 67 демонстрирует случайные закрашен- ные секторы. Пример 67 PROGRAM SectorDemo; Uses Graph, CRT; var d, r, e : integer; BEGIN d := Detect; InitGraph(d, r, ’’); e := GraphResult; if e <> grOk then writel.n(GraphErrorMsg(e)) else begin SetGraphMode(0); repeat SetF i1IStyle(Random(12), Random(suec(GetMaxColor))); SetCo lor (Random(suec(GetMaxColor))); Sector (Random(GetMaxX), Random(GetMaxY), Random(360), Random(360), Random(GetMaxX), Random(GetMaxY)); Fi llEllipse (RandonffGetMaxX), Random(GetMaxY), Random(GetMaxX), Random(GetMaxY)) until KeyPressed; CloseGraph end END. Процедура PIESLICE, Вычерчивает и штрихует сектор окружно- сти; формат обращения PIESLICE ( X, Y, <нач.угол>, <кон.угол>, R ) Здесь R-выражение типа WORD, определяющее радиус окруж- ности. Сектор обводится линией, заданной процедурами SETLINESTYLE и SETCOLOR, и заполняется штриховкой, заданной процедурой SETFILLSTYLE. Процедуру PIESLICE удобно использовать при по- строении круговых диаграмм, как, например, в программе примера 68. Пример 68 PROGRAM PieSliceDem; Uses Graph, CRT; var d, r, e : integer; BEGIN d := Detect; InitGraph(d, r, ''); e := GraphResult; if e <> grOk then 249
writeln(GraphErrorMsg(e)) ' else • f ' : begin , SetGraphMode(O); DirectVideo := false; SetFillStyle (SolidFill, 3)r SetColor (3); . PieSlice (GetMaxX div 2+5, GetMaxY div 2 + 4, 270, 360, 100); SetFillStyle ('SolidFill, 2); PieSlice (GetMaxX div 2, GetMaxY di.v 2, 0, .270, 100); ' GotoXY (15, 10); write (’75%’); GotoXY (25, 17); write('25%'); while not KeyPressed do ; CloseGraph end । END. . ' ' ' 13.2.7. Сохранение и выдача изображений Функция IMAGESIZE. Возвращает размер памяти в байтах, необ- ходимый для размещения прямоугольного фрагмента изображения. Обращение IMAGESIZE ( Xl, Yl, Х2, Y2 ) Здесь Xl, Y1-и Х2, Y2-выражения типа INTEGER, определяю- щие координаты левого верхнего и правого нижнего углов фрагмен- та изображения. Тип возвращаемого функцией значения - WORD. Процедура GETIMAGE. Помещает в память копию прямоуголь- ного фрагмента изображения; формат обращения GETIMAGE ( Xl, Yl, Х2, Y2, < буфер > ) Здесь < буфер >-переменная или участок кучи, куда будет по- мещена копия видеопамяти с фрагментом изображения. Процедура PUTIMAGE. Выводит в заданное место экрана копию фрагмента изображения, ранее помещенную в память процедурой GETIMAGE; формат обращения PUTIMAGE ( X, Y, < буфер >, <вид> ) Здесь X, Y - выражения типа INTEGER, определяющие координаты ле- вого верхнего угла того места на экране, куда будет скопирован фрагмент изображения; < буфер > - переменная или участок кучи, откуда будет копиро- ваться изображение; < вид >-выражение типа WORD, определяющее способ копиро- вания. Как видим, координаты правого нижнего угла не указываются, 250
так как они полностью определяются размерами выводимой на эк7 ран копии из памяти. Координаты левого верхнего угла могут быть какими угодно, лишь бы только выводимая копия уместилась в пределах экрана (если копия не может разместиться на экране, она не выводится и экран остается без изменений). Параметр <вид> определяет способ взаимодействия вновь раз- мещаемой копии с уже имеющимся на экране изображением. Взаи- модействие осуществляется путем применения кодируемых этим параметром логических операций к каждому биту копии и изобра- жения. Для указания применяемой логической операции можно исполь- зовать одну из следующих предварительно определенных констант: const NormalPut = 0; {замена существующего изображения на копию} XorPut = 1; {исключительное ИЛИ} • OrPut = '2; {объединительное ИЛИ} AndPut = 3; {логическое И} , NotPut = 4; {инверсия изображения} Наиболее часто используются операции NORMALPUT, XORPUT и NOTPUT. Первая из них просто стирает часть экрана и на это место помещает копию из памяти в том виде, как она там сохра- няется. Операция NOTPUT делает то же самое, но копия выводится в инверсном виде, т.е. светящиеся пиксели заменяются на темные и наоборот. Повторный вывод с параметром XORPUT фрагмента изображения на то же место экрана, откуда была получена копия, сотрет эту часть экрана. Если операцию применить дважды к одно- му и тому же участку экрана, вид изображения на экране не изме- нится. Таким способом можно довольно, просто перемещать изобра- жения по экрану, создавая иллюзию движения. Программа примера 69 рисует “летающую тарелку” на звездном фоне. Пример 69 PROGRAM UndefinitFlightObject; Uses Graph, CRT; const r = 20; {характерный размер тарелки} pause = 10; {длительность паузы} col = white; {цвет тарелки} var d, m, e : integer; xm, ym, x, y, lx, ly, rx, ry, size, i, dx, dy, width, height : integer; saucer : pointer; . label loop; BEGIN d := Detect; InitGraph(d, m, ’’); e := GraphResult; if e <> grOk then writeln(GraphErrorMsg(e)) 251
else begin SetGraphMode(O); . x : = r * 5; у : = r * 2; xm := GetMaxX; ym : = GetMaxY; ' {Создать блюдце из двух эллипсов с усами антенн} SetColor (col); El 1 ipse (x, у, 0, 360, r, r div 3 + 2); El 1 ipse (x, у - 4, 190, 357, г, г div 3) Line (x + 7, у - 6, x + Ю, у - 12); Line (x - 7, у - 6, x - Ю, у - 12); Circle (x + 10, у - 12, 2); Circle (x - 10, У - 12, 2); SetFillStyle (SolidFill, col); FloodFi11 (x + 1, у + 4, col); {Определить его габариты и поместить в кучу} 1х : = х - г - 1; 1у := у - 14; гх := х + г + 1; гу := у + г div 3 + 3; width := гх - lx + 1; height:= гу - 1у + 1; size :« ImageSize(lx, ly, гх, гу); GetMem (saucer, size); Getlmage (lx, ly, rx, ry, saucer"); {стереть построенное} Putlmage (lx, ly, saucer", XorPut); {создать звездное небо} for i:=1 to 1000 do. PutPixel (Random(xm), Random(ym), Random(succ(GetMaxColor))); x := xm div 2; у := ym div 2; dx := GetMaxX div 100 - Random (GetMaxX div 50); dy := GetMaxY div 40 - Random (GetMaxY div 20); {Основной цикл: вывести - пауза - стереть } repeat Putlmage (х, у, saucer", XorPut); Delay (pause); Putlmage (x, у, saucer", XorPut); { получить новые координаты } loop: x := x + dx; У := У + dy; if (x < 0) or (x + width + 1 > xm) , or (y < 0) or (y + Height + 1 > ym) then beg i n 252
х := х - dx; dx.:= GetMaxX div 10 - Random(GetMaxX div 5); dy := GetMaxY div 40 - Random(GetMaxY div 20); goto loop end until KeyPressed; .CloseGraph end END. 13.2.8. Вывод текста Описываемые ниже стандартные процедуры и функции поддер- живают вывод текстовых сообщений в графическом режиме. Специ- ально для графического режима разработаны процедуры, обеспечи- вающие вывод сообщений различными шрифтами, в горизонталь- ном или вертикальном направлении, с изменением размеров и т.д. Однако в стандартных шрифтах, разработанных для этих целей фирмой Borland, отсутствует кириллица, что исключает вывод сооб- щений на русском языке. Процедуры WRITE и WRITELN после загрузки в память второй половйны таблицы знакогенератора способны выводить сообщения на любом национальном языке,. но не обладают мощными возмож- ностями специальных процедур. Как разработать и загрузить нацио- нальный шрифт - эти вопросы рассматриваются в 13.4.1. Процедура OUTTEXT. Выводит текст, начиная с текущего поло- жения указателя; формат обращения OUTTEXT ( < текст > ) Здесь < текст > - выражение типа STRING или CHAR. При горизонтальном направлении вывода указатель смещается в конец выведенного текста, при вертикальном - не меняет своего по- ложения. Текст выводится в соответствии с установленным стилем и выравниванием. Если текст выходит за границы экрана, то при использовании штриховых шрифтов он отсекается, а 'в случае стан- дартного шрифта не выводится. Процедура OUTTEXTXY. Выводит текст, начиная с заданного ме- ста; формат обращения OUTTEXTXY ( X, Y, < текст > ) Здесь X, Y - выражения типа INTEGER, определяющие координаты точки вывода; < текст > - выражение типа STRING или CHAR. Отличается от процедуры OUTTEXT только координатами выво- да. Указатель не меняет своего положения. Процедура SETTEXTSTYLE. Устанавливает стиль текстового вы- вода на графический экран; формат обращения SETTEXTSTYLE ( < шрифт >, < направление >, < размер > ) 253
Здесь <шрифт>, <направление>,<размер>-выражения типа WORD, означающие код (номер) соответственно шрифта, направле- ния и размера шрифта. Для указания кода шрифта можно использовать следующие предварительно определенные константы: const ‘ DefaultFont = 0; {стандартный шрифт 8x8} I TriplexFont = 1; {шрифт типа триплекс; файл TRIP.CHR} SmallFont = 2; {уменьшенный шрифт; файл LITT.CHR} SansSerifFont = 3; {прямой шрифт; файл SANS.CHR} J GothicFont = 4; {готический шрифт; файл GOTH.CHR} Стандартный шрифт входит в модуль GRAPH.TPU и доступен в j любой момент. Это единственный матричный шрифт, т.е. его сим-1 волы создаются из матриц 8x8 пикселей. Все остальные шрифты - I штриховые: их элементы формируются как совокупность векторов ; (штрихов), характеризующихся направлением и размером. Штрихо- । вые шрифты отличаются более богатыми изобразительными воз- можностями, но главная их особенность заключается в легкости из-1 менения размеров без ухудшения качества изображения. Каждый из | этих шрифтов размещается в отдельном дисковом файле. Если Вы j собираетесь использовать какой-либо штриховой шрифт, соответству- j ющий файл должен находиться в Вашем каталоге, в противном II случае вызов этого шрифта игнорируется и подключается стандарт-1 ный шрифт (проблема включения шрифта в готовую программу обсуждается в 13.4.2). Для задания направления выдачи текста можно использовать константы: const HorizDir = 0; {слева направо} VertDir = 1; {снизу вверх} Каждый шрифт способен десятикратно изменять свои размеры. Размер выводимых символов кодируется параметром < размер >, который может иметь значение в диапазоне от 1 до 10 (стандарт- ный шрифт - в диапазоне от 1 до 32). Если значение параметра равно 0, устанавливается размер 1, если больше 10-размер 10. Ми- нимальный размер шрифта, при котором еще отчетливо раздйча- ются все его детали, равен 4 (для стандартного шрифта 1). Программа примера 70 демонстрирует различные шрифты, их размер и направление выдачи. Пример 70 ' PROGRAM SetTextStyleDem; Uses Graph, CRT; const text : array [1..4] of string[14] = ( ’TriplexFont’, ’SmallFont’, ’SansSerifFont’, ’GothicFont’); s4 = ’, size 4 ’; s5 = ’and 5’; var d, r, e, i : integer; 254
BEGIN, d := Detect; InitGraph(d, r, e := GraphResult; if e <> grOk then writeln(GraphErrorMsg(e)) else begin SetTextStyle (DefauItFont, HorizDir, 1); OutText ('Defau ItFont, size 1’); SetTextStyle (0, 0, 2); OutText (’ and 2 ’); for i := 1 to 4 do' begin SetTextStyle (i, 0, 4); MoveTo (10, i* 40); OutText (text [i]+s4); SetTextStyle (i, 0, 5); OutText (s5) end; for i := 1 to 4 do begin SetTextStyle (i, 1, 4); MoveTo (GetMaxX div 2 + i * 40 + 100, 0); OutText (text [i]) end; • while not KeyPressed do ; CloseGraph end END. Процедура SETTEXTJUSTIFY. Указывает выравнивание выводи- мого текста по отношению к текущему положению указателя или к заданным координатам; формат обращения SETTEXTJUSTIFY ( <гориз.>, <верт.> ) Здесь < гориз. >, < верт. > - выражения типа WORD, определяю- щие горизонтальное и вертикальное выравнивание. Выравнивание определяет как будет размещаться текст-левее или правее указанного места, выше или ниже, по центру. Здесь можно использовать такие константы: const LeftText =0;{указатель расположится слева от текста} CenterText=l;{указатель расположится симметрично слева и справа, сверху и снизу} RightText =2;{указатель расположится справа от текста} BottomText=0;{указатель расположится снизу от текста} TopText =2;{указатель расположится сверху от текста} Все эти константы задают выравнивание текста относительно те- кущего положения невидимого графического указателя. Например, LEFTTEXT задает вывод справа от указателя, ТОРТЕХТ - снизу и т.д. 255
Эти константы, разумеется, можно использовать и при обраще- нии к процедуре OUTTEXTXY, в этом случае выравнивание будет осуществляться относительно координат, указанных в обращении. В примере 71 программа иллюстрирует различные способы вы- равнивания относительно центра Графического экрана. Пример 71 PROGRAM TexJustifyDem; Uses Graph, CRT; var d, r, e : integer; BEGIN d := Detect; InitGraph(d, r, '’); e := GraphResult; if e <> grOk then write In(GraphErrorMsg(e)) else begin Line (0, GetMaxY div 2, GetMaxX, GetMaxY div 2); Line (GetMaxX div 2, 0, GetMaxX div 2, GetMaxY); SetTextStyle (SansSerifFont, HorizDir, 4); • SetTextJustify (LeftText, BottomText); OutTextXY (GetMaxX div 2, GetMaxY div 2, 'LeftText, BottomText'); SetTextJust ifу (RightText, TopText); OutTextXY (GetMaxX div 2, GetMaxY div 2, 'RightText, TopText'); while not KeyPressed do ; CloseGraph end END. Процедура SETUSERCHARSIZE. Изменяет размер выводимых символов в соответствии с заданными пропорциями. Обращение: SETUSERCHARSIZE ( Xl, Х2, Yl, Y2 ) Здесь Xl, Х2 и Yl, Y2 - выражения типа WORD, определяющие пропорции по горизонтали и вертикали. Процедура применяется только по отношению к штриховым шрифтам. Пропорции задают масштабный коэффициент, показыва- ющий, во сколько раз увеличится ширина и высота выводимых символов по отношению к стандартно заданным значениям. Коэф- фициент по горизонтали находится как отношение XI к Х2, по вер- тикали - как отношение Y1 к Y2. Чтобы, например, удвоить шири- ну символов, необходимо задать XI=2 и Х2 = 1. Стандартный размер символов устанавливается процедурой SETTEXTSTYLE, которая от- меняет предшествующее ей обращение к SETUSERCHARSIZE. В примере 72 демонстрируется изменение пропорций уменьшен- ного шрифта. Пример 72 PROGRAM UserCharSizeDem; Uses Graph, CRT; * 256
var d, г, e : integer; BEGIN d := Detect; InitGraph(d, r, ’ ; e := GraphResult; if e <> grOk then. writeln(GraphErrorMsg(e)) else begin MoveTo (0, GetMaxY div 2); SetTextStyle (SmallFont, HorizDir, 5); SetTextJustify (LeftText, BottomText); OutText ('Normal Width,’); SetUserCharSize (2, 1, 1, 1); OutText (' Double Width, '); SetUserCharSize- (1, 1, 2, 1); OutText ('Double Height,'); SetUserCharSize (2, 1, 2, 1); OutText (' Double Width and Height'); while not KeyPressed do ; CloseGraph end END. Функция TEXTWIDTH. Возвращает значение типа WORD, содер- жащее длину в пикселях выводимой текстовой строки; формат об- ращения TEXTWIDTH ( < текст > ) Здесь <текст> - выражение типа STRING. Учитываются текущий стиль вывода и коэффициенты измене- ния размеров символов, заданные, соответственно, процедурами SETTEXTSTYLE и SETUSERCHARSIZE. Функция TEXTHEIGHT. Возвращает значение типа WORD, со- держащее высоту в пикселях самого высокого символа в выводимой текстовой строке; формат обращения TEXTHEIGHT ( < текст > ) Здесь < текст >-выражение типа STRING. Учитываются текущий стиль вывода и коэффицинты изменения размеров символов, установленные соответственно процедурами SETTEXTSTYLE и SETUSERCHARSIZE. Функция GETTEXTSETTINGS. Возвращает текущий стиль и - выравнивание текста; формат обращения GETTEXTSETTINGS ( <инф.текст> ) Здесь <инф.текст> - переменная типа TEXTSETTINGSTYPE. В модуле GRAPH.TPU определен тип type TextSettingsType = , record Font : word; {шрифт} Direction: word; {направление} 9 - Фаронов 257
CharSize : word; {размер} Horiz : word; {горизонтальное выравнивание} Vert : word {вертикальное выравнивание} end; 13.3. ЧЕРЕПАХОВАЯ ГРАФИКА В одной из ранних версий Турбо-Паскаля - версии 3.0 - использо- вались интересные процедуры и функции, получившие название черепаховой графики. Основная идея этой графики - отказ от пря- моугольных координат и использование образа небольшой черепахи, которая может ползать по графическому экрану, поворачивать на- право и налево и оставлять за собой линию - след перемещения. Главным отличием черепахи от графического указателя является то, что черепаха имеет голову и может указывать направление впе- ред и назад. Кроме того, черепаха может быть сделана видимой, в то время как графический указатель всегда невидим. Перемещение черепахи ограничивается размерами экрана или установленного,ок- на. В центре экрана (окна) располагается дом черепахи, имеющий координаты 0,0. Координата X увеличивается слева направо} а коор- дината Y - снизу вверх. При разработке версии 5.0 процедуры и функции черепаховой графики были включены в библиотечный модуль GRAPH3.TPU. Они становятся доступны в любой Турбо-Паскалевой программе по- сле указания предложения Uses Graph3; К сожалению, черепаховая графика не может использоваться од- новременно с остальными стандартными графическими процедура- ми и функциями, входящими в модуль GRAPH.TPU. В частности, при работе с черепаховой графикой должны применяться другие процедуры инициации графического режима, выбора палитры, уста- новки цвета и т.п. Все они рассчитаны на CGA-адаптер. Ниже кратко характеризуются все процедуры и функции из мо- дуля GRAPH3, в том числе и не относящиеся прямо к черепаховой графике - ведь только они могут использоваться одновременно с ней. Как нетрудно обнаружить, все они (за исключением, разумеет- ся, процедур и функций черепаховой графики) имеют более совер- шенные аналоги в библиотечном модуле GRAPH.TPU версии 5J0 и поэтому не иллюстрируются примерами. 13.3.1. Процедуры и функции общего назначения Процедура GRAPHMODE. Устанавливает монохроматический ре- жим работы с разрешением 320x200. Обращение GRAPHMODE Процедура GRAPHCOLORMODE. Устанавливает цветной режим работы с разрешением 320x200. Обращение GRAPHCOLORMODE 258
Процедура HIRES. Устанавливает монохроматический режим ра- боты с разрешением 640x200. Обращение HIRES Процедура HIRESCOLOR. Устанавливает цвет изображения в ре- жиме 640x200; формат обращеция HIRESCOLOR ( <цвет> ) Здесь < цвет >-выражение типа . INTEGER, устанавливающее цвет. Процедура PALETTE. Устанавливает код палитры. Обращение PALETTE ( N ) Здесь N - выражение типа INTEGER, означающее код палитры. Процедура GRAPHBACKGROUND. Устанавливает цвет фона; фор- мат обращения GRAPHBACKGROUND ( <цврт> ) Здесь < цвет > - выражение типа INTEGER, определяющее уста- навливаемый цвет. Процедура GRAPHWINDOW. Устанавливает графическое окно. Обращение GRAPHWINDOW ( Xl, Yl, Х2, Y2 ) Здесь Xl, Y1 и Х2, Y2-выражения типа INTEGER, определяю- щие координаты левого верхнего и правого нижнего углов окна. Процедура PLOT. Выводит точку с заданным цветом; обращение: PLOT ( X, Y, <цвет> ) Здесь X, Y-выражения типа INTEGER, задающие координаты точки. Процедура DRAW. Вычерчивает прямую; формат обращения DRAW ( Xl, Yl, Х2, Y2, <цвет> ) Здесь Xl, Y1 и Х2, Y2, <цвет> - выражения типа INTEGER, за- дающие координаты одного и другого концов прямой и ее цвет. Процедура COLORTABLE. Задает таблицу преобразования цветов; формат обращения COLORTABLE ( Cl, С2, СЗ, С4 ) Здесь С1,...,С4 - выражения типа INTEGER, задающие цвета. Процедура позволяет произвольным образом менять цвета в пределах установленной палитры. Если, например, задать ColorTable (3, 2, 1, 0); то будет установлено следующее преобразование цветов палитры 0: Номер цвета в палитре Выводимый цвет 0 3 (желтый) 1 2 (красный) 2 • 1 (зеленый) 3 0 (цвет фона) Процедура PUTPIC всегда * использует таблицу преобразования цветов, все другие процедуры используют эту таблицу только в том случае, когда цвет задается как -1, например, Plot (100, 100, -1); Процедура ARC. Вычерчивает дугу окружности; формат обраще- ния ARC ( X, Y, <угол>, <радиус>, <цвет> ) 9* 259
Здесь X, Y, <угол>, < радиус >, < цвет >,-выражения типа INTEGER, задающие координаты начала дуги, ее угол, радиус и цвет. Если указан положительный угол, дуга вычерчивается против часовой стрелки, в противном случае - по часовой. Процедура CIRCLE. Вычерчивает окружность; формат обращения CIRCLE (X, Y, < радиус >, <цвет> ) Параметры процедуры см. выше. , В отличие от аналогичной процедуры из основной графической ' библиотеки, описываемая процедура не корректирует радиус с уче- ; том коэффициента отношения сторон, поэтому вместо окружности на экране CGA-дисплея получается эллипс, вытянутый в вертикаль- I ном направлении (пример 73). I Пример 73 PROGRAM Graph3_Circle_Dem; Uses Graph3; BEGIN HiRes; writein (’640 x 200’); Circle(300, 100, 90, 1); readln; GraphColorMode; writein (’320 x 200’); Circle(150, 100, 90, 3); readln ENO. Процедура GETPIC. Копирует содержимое прямоугольной обла- сти экрана в оперативную память; формат обращения GETPIC ( < буфер >, Xl, Yl, Х2, Y2 ) Здесь < буфер >-переменная любого типа, имеющая достаточ- ную длину внутреннего представления; Xl, Y1, и Х2, Y2-выражения типа INTEGER, определяющие ко- ординаты левого верхнего и нижнего правого углов. Минимальный размер буфера, необходимый для размещения копии, определяется следующими формулами: Режим 320x200 размер = ((ширина+3) div 4)*высота*2+6 . Режим 640x200 / размер = ((ширина + 7) div 8)*высота+6 . Здесь ширина = abs(Xl-X2) +1; высота=abs(Yl-Y2) + l . Программист должен сам позаботиться о том, чтобы буфер имел достаточные размеры. После выполнения процедуры первые два байта буфера (первое слово) содержит код режима (1 для режима 320x200 и 2 для режима 640x200), второе слово - ширину образа в пикселях и третье слово - высоту образа. Оставшиеся байты хранят образ экрана. Процедура PUTPIC. Выводит копию прямоугольного фрагмента изображения из памяти на экран; формат обращения ’ PUTPIC ( < буфер >, X, Y ) 260
Здесь < буфер > - переменная любого типа, в которой ранее процеду- рой GETPIC был сохранен фрагмент изображения; X, Y - выражения типа INTEGER, определяющие координаты ле- вого нижнего угла нового положения фрагмента на экране. Функция GETDOTCOLOR. Возвращает значение типа INTEGER с кодом того цвета, которым обладает заданный пиксель. Обращение: GETDOTCOLOR ( X, Y ) Здесь X, Y - выражения типа INTEGER, задающие координаты точки. Процедура FILLSCREEN. Заполняет графическое окно или весь экран нужным цветом. Обращение: FILLSCREEN ( <цвет> ) Процедура FILLSHARE. Заполняет замкнутую область нужным цветом. Обращение: FILLSHARE ( X, Y, <цвет>, < граница > ) Здесь X, Y, <цвет>, < граница >-выражения типа INTEGER, определяющие координаты внутренней точкй, выводимый цвет и цвет граничной линии. Процедура FILLPATTERN. Штрихует заданную прямоугольную область экрана; формат обращения FILLPATTERN ( Xl, Yl, Х2, Y2, <цвет> ) Здесь Xl, Y1 и Х2, Y2, < цвет >-выражения типа INTEGER, определяющие координаты левого верхнего и правого нижнего уг- лов области и цвет штриховки. Процедура PATTERN. Определяет образец штриховки; формат обращения PATTERN ( < образец > ) Здесь < образец > - переменная любого типа, имеющая длину 8 байт и определяющая образец. Процедура аналогична процедуре SETFILLPATTERN из стандарт- ной библиотеки GRAPH.TPU, поэтому о том, как строится образец, см. с.256. 13.3.2. Процедуры и функции черепаховой графики Процедура SHOWTURTLE. Делает черепаху видимой (в виде не- большого треугольника с заштрихованным углом, обозначающим го- лову). Обращение SHOWTURTLE В примере 74 программа показывает черепаху в двух режимах работы экрана. Пример 74 PROGRAM ShowTurtleDem; Uses Graph3; BEGIN HiRes; writein ('640x200'); ShowTurtle; readln; GraphColorMode; writein ('320x200'); 261
ShowTurtle; readln END. Обратите внимание: при смене графического режима экран очи- щается и черепаху приходится показывать еще раз. Процедура HIDETURTLE. Делает., черепаху невидимой. Обраще- ние HIDETURTLE Начальное состояние черепахи - невидимое. Функция TURTLETHERE. Возвращает значение типа BOOLEAN, указывающее, видна ли черепаха. Обращение TURTLETHERE Значение TRUE соответствует видимой черепахе. Процедура FORWD. Перемещает черепаху вперед, т.е. в направ- лении головы; формат обращения 4 FORWD ( <путь> ) , ' Здесь < путь >-выражение типа INTEGER, определяющее , рас- стояние в пикселях, на которое должна переместиться черепаха. Черепаха перемещается относительно текущего положения. Если параметр <путь> имеет отрицательное значение, черепаха будет перемещаться назад. Процедура BACK. Перемещает черепаху назад по отношению к направлению, указываемому головой: формат обращения BACK ( <путь> ). Черепаха перемещается относительно текущего положения. Если параметр <путь> имеет отрицательное значение, черепаха будет перемещаться вперед, по направлению головы. Процедура TURTLEDELAY. Устанавливает задержку в миллисе- кундах между двумя последовательными обращениями к черепахе; формат обращения TURTLEpELAY ( < задержка > ) Здесь < задержка >-выражение типа INTEGER, означающее ус- танавливают задержку в миллисекундах. Процедура SETHEADING. Ориентирует черепаху по заданному направлению; формат обращения SETHEADING ( <угол> ) Здесь < угол > - выражение типа INTEGER, задающее направле- ние в градусах, куда должна ориентироваться голова черепахи. Угол 0 устанавливает направление головы черепахи вверх, поло- жительные углы возрастают по часовой стрелке. Для указания угла можно использовать одну из следующих предопределенных кон- стант: > . const North = 0; {вверх} East = 90; {вправо} South = 180; {вниз} West = 270; {влево}
Пример 75 показывает построение прямоугольника: после выво- да очередной стороны черепаха поворачивается на 90 градусов, и та- ким способом строится прямоугольник. Пример 75 PROGRAM SetHeadingDem; Uses Graph3; var i : integer; BEGIN GraphColorMode; ShowTurtle; TurtleDelay (1000); for i := 1 to 4 do begin Forwd (70); SetHeading (i * 90) end END. Функция HEADING. Возвращает значение типа INTEGER в диа- пазоне от 0 до 359, показывающее направление, куда смотрит чере- паха. Обращение HEADING Значение 0 указывает направление вверх, возрастающие значе- ния соответствуют углам в направлении по часовой стрелке. Процедура SETPOSITION. Перемещает черепаху в заданное мес- то экрана без вычерчивания линии. Обращение SETPOSITION ( X, Y ) Здесь X, Y - выражения типа INTEGER, указывающие координа- ты того места, куда нужно переместить черепаху. Координаты отсчитываются относительно домика черепахи, кото- рый располагается в центре окна или экрана. В программе примера 76 вычерчиваются два вложенных квадра- та. Пример 76 PROGRAM SetPos i t i'onDem; Uses Graph3; var i : integer; BEGIN GraphColorMode; ShowTurtle; TurtleDelay (500); SetPosition (-50, 50); for i := 1 to 4 do begin TurnRight (90); Forwd (100) ‘ end; 263
SetPosition (-40, 40); for i := 1 to 4 do begin ' TurnRight (90); Forwd (80) end ENO, Процедура HOME. Помещает черепаху в домик, т.е. в позицию с координатами 0,0. Обращение НОМЕ Функции XCOR и YCOR. Возвращают значения типа INTEGER, определяющие положение черепахи соответственно по горизонтали и вертикали. Обращение XCOR или YCOR Процедура TURNRIGHT. Поворачивает черепаху направо (по ча- совой стрелке) на заданный угол; формат обращения TURNRIGHT ( <угол> ) Здесь < угол >-выражение типа INTEGER, означающее угол в градусах, на который нужно повернуть черепаху. Поворот осуществляется относительно текущего направления че- репахи. Если значение параметра <угол> меньше нуля, черепаха повернется налево (против часовой стрелки). Процедура TURNLEFT. Поворачивает черепаху налево (против часовой стрелки), на заданный угол; формат обращения TURNLEFT ( <угол> ) Поворот осуществляется относительно текущего направления че- репахи. Если значение параметра <угол> меньше нуля, черепаха повернется направо (по часовой стрелке). С помощью применения процедур поворота в программе при- мера 77 строится "змейка”. Пример 77 PROGRAM Left_Right_Turn_Dem; Uses Graph3; var i : integer; BEGIN GraphColorMode; ShowTurtle; TurtleDelay (500); TurnLeft (45); for i := 1 to 8 do begin Forwd(30); if odd(i) then TurnLeft(90) else TurnRight(90) end END. Процедура PEN DOWN. Опускает перо, связанное с черепахой. Обращение: PENDOWN 264
Движение черепахи с опущенным пером оставляет на экране линию (след черепахи). Состояние опущенного пера - исходное со- стояние черепахи. Процедура PENUP. Поднимает перо. Движение черепахи с под- нятым пером осуществляется без вычерчивания линии. Обращение PENUP Процедура SETPENCOLOR. Выбирает цвет той линии, которая вычерчивается черепахой; формат обращения SETPENCOLOR ( <цвет> ) Здесь <цвет> - выражение типа INTEGER, устанавливающее цвет. Значение параметра <цвет> должно находиться в диапазоне от -1 до 3. Если значение равно -1, цвет выбирается с учетом таблицы преобразования цветов, в противном случае-из основной палитры. В примере 78 строится треугольник с разноцветными сторонами. Пример 78 PROGRAM SetPenColorDem; Uses Graph3; var i : integer; BEGIN GraphColorMode; ShowTurtle; TurtleDelay (1000); for i := 1 to 3 do begin SetPenColor (i); ' Forwd (90); TurnRight (120) end END. Процедура TURTLEWINDOW. Определяет графическое окно для черепаховой графики; формат обращения TURTLEWINDOW ( X, Y, < ширина >, < высота > ) Здесь X, У, <ширина>,<высота>- выражения типа INTEGER, определяющие координаты центра, ширину и высоту окна. Координаты центра всегда задаются относительно левого верхне- го угла экрана, который имеет координаты 0,0. Горизонтальная ко- ордината увеличивается слева направо, вертикальная - сверху вниз. В исходном состоянии окно черепахи занимает весь экран и имеет следующие параметры: 159, 99, 320, 200-для режима 320x200; 319, 99, 640, 200-для режима 640x200. Если окно определяется так, что оно частично выходит за пределы экрана, оно отсекается границами экрана. Если черепаха при своем перемещении выходит за границы окна, то в режиме WRAP (см. дальше) она появляется на противоположной стороне окна, а в режиме NOWRAP - становится невидимой и линия не вы- черчивается. 265
После назначения окна черепаха помещается в домик и ей уста- навливается направление вверх. Процедура WRAP. Заставляет черепаху появляться на противопо- ложной стороне окна всякий раз, когда она выходит за границы ок- на. Обращение WRAP Процедура NO WRAP. Отменяет режим WRAP: при выходе из границ экрана черепаха становится невидимой и линия не вычер- чивается. Обращение NOWRAP Режим NOWRAP устанавливается по умолчанию. В программе примера 79 вычерчиваются два прямоугольника, которые затем объявляются окнами, причем правое окно устанавли- вается в режим WRAP, а левое - NOWRAP. В каждом из окон вы- черчивается линия большой длины. Режим задержки и цикличе- ский вывод во втором случае позволяет наглядно увидеть, как чере- паха появляется с противоположной стороны окна (для, прекраще- ния штриховки окна и выхода из программы достаточно нажать любую клавишу). Пример 79 PROGRAM TurtleWindowDem; Uses Graph3, CRT; const w = 100; h = 100; var i : integer; BEGIN GraphColorMode; ShowTurtle; TurtleDelay (500); SetPosition (-100, 50); for i := 1 to 4 do begin TurnRight (90); Forwd (100) end; SetPosition (1, 50); for i := 1 to 4 do begin TurnRight (90); Forwd (100) end; TurtleWindow (110, 99, w, h); Home; NoWrap; Forwd (200); TurtleWindow (210, 99, w, h); 266
Home; TurnRight (10); Wrap; TurtleDelay (100); SetPenColor (1);, while not KeyPressed .do Forwd (15) END. 13.4. ДРУГИЕ ВОЗМОЖНОСТИ ГРАФИЧЕСКИХ СРЕДСТВ 13.4.1. Использование процедур WRITE и WRITELN \ « Как уже говорилось, использование процедур WRITE и WRITELN в графических программах имеет свою специфику. Весь излагаемый в этом разделе материал относится в основном к адап- теру CGA. Если ПЭВМ оснащена адаптерами EGA и VGA, то нет необходимости в дополнительной загрузке шрифтов специально для графического экрана, так как такая загрузка обычно осуществляется в ходе выполнения командного файла начальной установки систе- мы AUTOEXEC.BAT. Следует, однако, иметь в виду, что разработан- ные А.Чижовым (ВЦ АН СССР) популярные драйверы клавиатуры ALFA и ВЕТА могут "мешать” графическому выводу процедурами WRITE и WRITELN. Если Ваша ПЭВМ оснащена адаптерами EGA или VGA и Вы захотите исполнить описываемые ниже примеры, Вы должны позаботиться об инициации CGA-режима работы адап- тера: всюду вместо процедуры автоопределения типа драйвера вида d := Detect; InitGraph (d, г, необходимо поставить команды d := CGA; г := CGAHi; InitGraph (d, г, ’’); При работе с CGA-адаптером для вывода символов с кодами 128 и больше необходимо загрузить в память вторую половину таблицы знакогенератора и поместить адрес начала этой таблицы в вектор прерывания 31, т.е. начиная с адреса 124. Фактически вектор 31 (так же, кстати, как векторы 29 и 30) не является вектором прерывания в том смысле, что он не связан с процедурой обработки прерыва- ния, а лишь указывает адрес начала второй половины таблицы зна- когенератора (первая половина таблицы для кодов от 6 до 127 реа- лизуется в виде микросхемы ПЗУ). Правильно работающая про- грамма никогда не инициирует прерывание с номером 31, иначе управление будет передано в ту часть памяти, где хранится табли- ца, и коды этой таблицы будут интерпретироваться как команды программы обработки прерывания. Загружаемая часть таблицы представляет собой 128 (по количе- ству символов) восьмибайтных последовательностей. Поскольку каж- дый байт содержит 8 бит, то говорят о 128 матрицах из 8x8 бит. Матрицы хранят графические образы соответствующих символов: первая матрица - образ символа с кодом 128, вторая -129 и т.д. По- следняя, 128-я матрица хранит образ символа с кодом 255. Графи- ческий образ символа строится по точно таким же правилам, как и 267
пользовательский образец штриховки (см. с. 298): первые 8 бит мат- рицы - первый байт восьмибайтной последовательности определяет светимость восьми пикселей первой строки развертки знакоместа, второй байт - светимость восьми пикселей второй строки знакоместа и т.д. На рис. 23 в качестве примера показана светимость пикселей для знакоместа с изображением символа "а" и соответствующий фрагмент таблицы знакогенератора. Рис. 23. К пояснению структуры таблицы .знакогенератора 1-й байт: 00000000 $00 2-й байт: 00000000 |00 3-й байт: 01111000 $78 4-й байт: 00001100 $ос 5-й байт: 01111100 $7С 6-й байт: 11001100 $сс 7-й байт: 01110110 $76 8-й байт: 00000000 $00 В 16.4 приводится текст программы, с помощью которой Вы при желании сможете построить свой вариант таблицы знакогенера- тора, содержащий, например, национальный алфавит. В 16.3 приво- дится текст модуля GRAPHTXT.TPU, в котором содержится стандар- тный вариант символов русского алфавита (кириллицы). Поскольку адрес таблицы символов старших кодов загружается в оперативную память, его можно легко менять в процессе работы программы и таким образом переключаться с одного алфавита на другой. Более того, Вы можете подготовить свои пиктограммы, сим- волы псевдографики или любые другие элементы изображений, за- нимающие одно знакоместо, и строить из, них несложные рисунки простым обращением к процедурам WRITE или WRITELN. Одна из замечательных особенностей системного программного обеспечения ПЭВМ заключается в способности считывать с экрана и распознавать графические образы символов из таблицы пользова- теля. При работе с процедурами WRITE (WRITELN) в графическом режиме следует помнить о том, что "прокрутка” экрана вверх на од- ну строку приводит к заполнению освободившейся строки двухбайт- ным кодом (см. гл. 12), содержащим не только символ 0, который на экране воспроизводится как пробел, но и байт атрибутов тексто- вого режима. Чтобы байты атрибутов не мешали текстовым сообще- ниям, необходимо их сделать нулевыми, а для этого, как это ни 268
парадоксально выглядит, нужно установить режим вывода черных символов на черном же фоне! Проще всего такая установка дости- гается обращением к процедурам TEXTCOLOR и TEXTBACK- GROUND из стандартного модуля CRT. Программа примера 80 дважды выводит на графический экран по 30 строк, причем внача- ле с атрибутом по умолчанию (белый текст на черном фоне), а за- тем, после небольшой паузы, с нулевым атрибутом (черный текст на черном фоне). Обратите внимание: процедура CLRSCR из моду- ля CRT заполняет целиком весь экран двухбайтными кодами и, ес- ли байт атрибутов не нулевой, экран CGA-адаптера будет полоса- тым. Пример 80 PROGRAM WritelnDem; Uses Graph, CRT; var d, r, e, i : integer; BEGIN d := CGA; r:=CGAhi; InitGraph (d, r, ''); e := GraphResult; . if e <> grOk then writein (GraphErrorMSG(e)) else begin DirectVideo := false; TextColor (white); TextBackGround (black); ClrScr; for i := 1 to 30 do writein ('white & blak’); TextColor (black); delay (2000); for i := 1 to 30 do writein ('black & black’); delay (1000); TextColor (white); CloseGraph end END. Вообще, Вы не должны забывать, что все процедуры модуля CRT, в том числе позиционирование курсора и работа с окнами, доступны и графическим программам. Однако, если Вы собираетесь воспользоваться ими, Вы должны отключить прямой доступ к виде- опамяти с помощью оператора DirectVideo := fake;, как это сделано в рассмотренном выше примере, в противном случае собственная имитация средств прерывания $10 процедурами этого модуля сдела- ет невозможным вывод текстовых сообщений. Упоминавшийся вы- ше модуль GRAPHTXT.TPU в своей установочной части осуществ- ляет это несложное действие. Нестандартный модуль TEXTCRT, описанный в гл.16, не может использоваться в полном объеме, так как рассчитан на поддержку 269
многостраничной работы с видеопамятью. Однако те его процедуры и функции, которые работают только с основной (нулевой) страни- цей, в частности процедуры SETWINDOW и BORDER, будут нор- мально работать и в графическом режиме. Например, можно со- здать текстовое окно в графическом режиме и вывести в него про- извольный текст, как это делается в программе примера 81. Пример 81 PROGRAM’ GraphTxt-TextCRT-jDem; Uses Graph, CRT, TextCRT, GraphTxt; var d, r, e : integer; BEGIN d := CGA; r := CGAhi; InitGraph (d, r, e := GraphResult; if e <> grOk then writein (GraphErrorMSG(e)) else begin ClrScr; Colors (black, black) ; SetWindow (1, 1, 40, 12, DoubleBorder); GotoXY (14, 5); wr iteIn ('Это окно N 1 ’); SetWindow (41, 13, 80, 25, DoubleBorder); GotoXY (14, 5); writein ('Это окно N 2 ’); while not KeyPressed do; CloseGraph end END. Перед повторением примера 81 необходимо создать файлы с текстами модулей GRAPHTXT и TEXTCRT (см. 16.1 и 16.3). \ 13.4.2 Включение драйвера и шрифтов в тело программы В ТурбогПаскале имеется возможность включения графического драйвера и штриховых шрифтов непосредственно в тело програм- мы. Такое включение делает программу независимой от местополо- жения и наличия на диске драйверов и шрифтов, а также ускоряет подготовку графических программ к работе (шрифты и драйвер за- гружаются вместе с программой). Включение драйвера и шрифтов осуществляется по следующей общей схеме. Сначала с помощью вспомогательной программы BINOBJ.EXE, входящей в комплект поставки Турбо-Паскаля, драй- вер и шрифты преобразуются в OBJ-файл (файл с расширением .OBJ). Для этого вне среды Турбо-Паскаля необходимо дать команду BINOBJ с тремя параметрами: именем преобразуемого файла, име- 270
нем получаемого OBJ-файла и глобальным именем процедуры. Эти имена, в принципе, могут быть произвольными, правильными для ДОС именами, например: c:\pascal\binobj cga.bgi cga-cgadrv I В результате такого обращения из каталога PASCAL на диске С будет вызвана программа BINOBJ и ей будут переданы следующие параметры: CGA.BGI - имя файла с преобразуемым драйвером; CGA-имя файла с расширением .OBJ, т.е. CGA.OBJ, который будет получен в результате исполнения программы BINOBJ; CGADRV - глобальное имя, под которым этот драйвер будет из- вестен Турбо-Паскалевой программе. После этого можно написать следующий фрагмент программы: Uses Graph; Procedure CGADRV; external; {$L CGA.OBJ} var d, r, e : integer; BEGIN if RegisterBGIDriver (@CGADRV) < 0 then begin writein ('Ошибка при регистрации драйвера’); halt end; d := CGA; r := CGAHi; InitGraph (dr.r, e); Как видно из этого примера, в программе объявляется внешняя процедура с именем CGADRV (глобальное имя, указанное при об- ращении к BINOBJ), причем дается директива компилятору оты- скать в текущем каталоге и загрузить файл CGA.OBJ, в котором на- ходится эта процедура. Затем осуществляется регистрация драйвера путем обращения к функции REGISTERBGIDRIVER. Единственным параметром этой функции является адрес начала драйвера в памя- ти (@CGADRV). Функция возвращает значение типа INTEGER, ко- торое служит для контроля правильности завершения процедуры регистрации драйвера: если это значение равно нулю, регистрация прошла успешно, если меньше нуля - обнаружена ошибка. В при- мере контролируется правильность регистрации драйвера, и, если ошибка не обнаружена, инициируется графический режим работы экрана. t Аналогичным образом можно присоединить к программе стан- дартные штриховые шрифты (матричный шрифт 8x8 входит в со- став модуля GRAPH.TPU и его присоединять не надо). Присоедине- ние шрифта строится по той же схеме, за исключением того, что 271
для его регистрации вызывается функция REGISTERBGIFONT. На- пример, после преобразования c:\Pascal\binobj litt.chr litt litt можно использовать операторы Procedure Litt; external; {$L Litt.obj} if RegisterBGIFont (@litt) 0 then ... Обратите внимание: регистрация и драйвера, и шрифтов должна предшествовать инициации графического режима. В модуле GRAPH.TPU имеются две процедуры, обеспечивающие возможность работы с нестандартными (разработанными пользовате- лем) драйвером и шрифтами. Это процедуры INSTALLUSER- DRIVER и INSTALLUSERFONT. Однако никаких указаний на то, как должны строиться драйвер и штриховые шрифты, мне обнару- жить не удалось.
Г л а в a 14 УПРАВЛЕНИЕ КЛАВИАТУРОЙ, ПРИНТЕРОМ И ЗВУКОВЫМ УСТРОЙСТВОМ 14.1. ТЕХНИЧЕСКИЕ ОСОБЕННОСТИ КЛАВИАТУРЫ Несмотря на кажущуюся простоту, клавиатура ПЭВМ представ- ляет собой достаточно сложное устройство, содержащее разнообраз- ные электронные компоненты, в том числе встроенный микропро- цессор. Микропроцессор осуществляет в основном функции проти- водребезговой защиты: в процессе нажатия на клавишу и ее отпу- скания связанные с ней электронные или механические контакты могут замыкаться и размыкаться несколько раз подряд, пока, нако- нец, не установится их новое состояние; микропроцессор устраняет многочисленные ложные импульсы, посылаемые контактами в этот момент, и тем самым существенно разгружает центральный процес- сор. Кроме того, микропроцессор запоминает коды нажатых клави- шей в буферной памяти, если по каким-либо причинам централь- ный процессор не успевает обрабатывать их достаточно быстро, а затем по запросу центрального процессора передает их из памяти в том порядке, в каком они туда поступали. Наконец, микропроцес- сор следит за временем, в течение которого клавиша удерживается в нажатом состоянии, и осуществляет автоповтор кода клавиши, ес- ли это время превышает одну-две секунды. Наиболее существенным моментом х: точки зрения программи- рования является то обстоятельство, что коды, вырабатываемые клавиатурой, и коды, передаваемые в программу, - это разные коды. Выработанный клавиатурой код называется кодом сканирования. Кодов сканирования ровно столько, сколько клавишей в клавиатуре. Именно эти коды поступают от клавиатурного микропроцессора по линиям связи. Чтобы сообщить центральному процессору о готов- ности клавиатуры к передаче кода, микропроцессор вырабатывает прерывание с номером 9. Программа обработки этого прерывания (драйвер клавиатуры) входит в состав ДОС и размещается в ПЗУ. Драйвер клавиатуры осуществляет преобразование кода сканирова- ния с учетом того, что могут быть одновременно нажаты две или 273
более клавишей , например клавиша верхнего регистра и какая-ни- будь алфавитно-цифровая клавиша. В результате такого преобразова- ния на выходе драйвера клавиатуры формируется ТОТ КОД , который и воспринимается программой. В табл. 13 представлены коды ска- пирования клавиатуры ПЭВМ типа IBM PC/XT. Т а б л и ц а 13 | Коды сканирования клавиатуры ПЭВМ типа IBM PC/XT Клавиша Код Клавиша Код Клавиша Код Клавиша Код Л ESC 1 и 22 \ 43 F6 64 J 1 ! 2 I 23 Z 44 F7 65 ] 2 @ 3 о 24 X 45 F8 66 J 3 # 4 р 25 с 46 F9 67 4 $ 5 [ { 26 V 47 F10 68 5 % 6 ]} 27 в 48 NUMLK 69 < 6 л 7 ENTER 28 V 49 SCRLK 70 7 & 8 CTRL 29 м 50 [7] 71 8 * 9 А 30 51 [8] 72 9( 10 S 31 < 52 Р] 73 0 ) 11 D 32 7 ? 53 н 74 — — 12 F 33 SHFTR 54 • [4] 75 = + 13 G 34 PRTSC 55. [5] 76 BS 14 Н 35 ALT 56 И 77 TAB 15 J 36 BLANK 57 ( + ] 78 Q 16 К 37 CPSLK 58 [1] 79 W 17 L 38 Fl 59 Р] 80 Е 18 I ! 39 . F2 60 И 81 R 19 • и 40 F3 61 PJ 82 Т 20 • ~ 41 F4 62 [•] 83 Y 21 SHFTL 42 F5 63 Примечание. В квадратных скобках обозначена группа дополнительных цифровых и управляющих клавишей справа на клавиатуре. SHFTL и SHFTR - соответственно ле- вая и правая клавиши временной смены регистров. Микропроцессор клавиатуры дважды вырабатывает прерыва- ние 9 - в момент нажатия на клавишу и в момент ее отпускания. Для ПЭВМ класса IBM XT при отпускании передается код сканиро- вания, увеличенный на 128, а для IBM АТ - двухбайтовая последова- тельность: сначала код $F0 (240), а затем код сканирования клави- / ши. Именно поэтому программа обработки прерывания может еле- 1 дить за одновременным нажатием нескольких клавишей. На клавиатуре ПЭВМ имеются три особые клавиши, называе- мые клавишами смещения. Эти клавиши обычно не используются сами по себе, но предназначены для расширения возможностей клавиатуры. К клавиша^ смещения относятся клавиши SHIFT (вре- менная смена регистра), ALT (дополнительный регистр) и CTRL (управляющий регистр). Все они используются подобно клавише временного перехода в верхний регистр на клавиатуре пишушей 274
машинки: нужно нажать клавишу смещения и затем, не отпуская ее, какую-либо другую клавишу. Драйвер клавиатуры никогда не со- общает программе о нажатии на клавиши смещения, однако он со- ответствующим образом преобразует код сканирования, так что, на- пример, а и А имеют разные коды, хотя вызываются нажатием на одну и ту же клавишу. Точно так же свои коды будут выработаны драйвером для комбинаций ALT-A и CTRL-A. На стандартной клавиатуре IBM PC/XT имеется 83 клавиши, на клавиатуре IBM АТ - 101. С учетом возможного использования алфа- витно-цифровых клавишей совместно с тремя клавишами смеще- ния число возможных комбинаций составляет около 400, что много больше емкости одного байта. Таким образом, информация на вы- ходе драйвера клавиатуры должна кодироваться двумя байтами, причем далеко не каждая комбинация может быть отражена услов- ным знаком (символом) на экране. Специалисты фирмы Microsoft (эта фир1йа создала операцион- ную систему MS DOS, являющуюся прототипом и практически пол- ным аналогом системы PC DOS) реализовали следующий приццип. Все обычные алфавитно-цифровые клавиши как в нижнем, так и в верхнем регистрах вырабатывают на выходе драйвера однобайтный код символа. Этот код совпадает с указанным в таблице ASCII- кодом соответствующего символа. Некоторые специальные клавиши, такие как F1...F10, клавиши перевода курсора и другие, а также ком- бинации всех клавишей с двумя клавишами смещения ALT и CTRL - вырабатывают коды из так называемого расширенного набо- ра. Эти коды передаются двухбайтной последовательностью, причем первый байт в этой последовательности нулевой. Таким образом, нулевой код никогда не связывается с какой-либо клавишей или их комбинацией, но используется только в качестве признака перехода к расширенному набору кодов. В табл. 14 приведены коды из расширенного набора. Как видно из этой таблицы, используются далеко не все коды и возможны не все комбинации клавишей. При вводе непредусмотренных комбина- ций драйвер их просто игнорирует. Некоторые комбинации имеют специальное назначение: они не передаются на выход драйвера и, следовательно, не могут восприниматься программой, но обрабаты- ваются операционной системой. В частности, комбинация CTRL-S приводит к приостановке работы программы, CTRL-BREAK вообще прекращает ее исполнение, CTRL-ALT-DEL вызывает перезагрузку резидентной части ДОС, PRTSCR приводит к копированию содержи- мого экрана с помощью принтера. Кроме клавишей смещения на клавиатуре имеются четыре кла- виши-переключателя. При многократном нажатии на любую из них будет последовательно включаться и отключаться определенная функция. К клавишам-переключателям относятся клавиши INS (включение/отключение вставки символов), CAPS-LOCK (фиксация верхнего регистра, точнее, заглавных букв), NUM-LOCK (фиксация цифровой клавиатуры в зоне дополнительных и служебных клави- шей) и SCROLL-LOCK (фиксация прокрутки экрана). В ДОС выде- лены два байта с адресами 1047 и 1048, в которых хранится инфор- 275
мация о состоянии этих переключателей. В табл. 15 указан смысл отдельных разрядов этого двухбайтного слова. Таблица 14 Расширенный набор кодов Код Клавиша или комбинация клавишей 3 CTRL-2 15 SHIFT-TAB 16...25 ALT-Q...ALT-P (верхний ряд букв) 30...38 ALT-A..ALT-L (средний ряд букв) 44...50 ' ALT-Z...ALT-M (нижний ряд букв) 59...68 ’ F1...F10 71 НОМЕ 72- Курсор вверх 73 PGUP 75 Курсор влево 77 Курсор вправо 79 END 80 Курсор вниз 81 PGDN 82 INS 83 DEL 84...93 SHIFT-F1...SHIFT-F10 94...103 CTRL-F1...CTRL-F10 104...113 ALT-F1...ALT-F10 114 CTRL-PRTSCR 115 CTRL-Kypcop влево 116 CTRL-Kypcop вправо 117 CTRL-END 118 CTRL-PGDN 119 CTRL-HOME 120...131 ALT-l...ALT-= (верхний ряд клавишей) 132 CTRL-PGUP Комбинация CTRL-NUMLOCK. приводит к так называемому со- стоянию захвата: в этом состоянии драйвер непрерывно сканирует клавиатуру, ожидая нажатие на любую клавишу. В состоянии захва- та не может исполняться ни одна программа, кроме процедур обра- ботки прерывания, поэтому комбинация CTRL-NUMLOCK использу- ется для приостановки работающей программы. Поскольку в состоя- нии захвата не работает ни одна программа, установка соответствую- щего признака в байте 1048 кажется совершенно излишней. 14.2. УПРАВЛЕНИЕ КЛАВИАТУРОЙ Для управления клавиатурой у Турбо-Паскалевой программы имеется несколько возможностей. Прежде всего, это уже рассмотрен- ные ранее стандартные процедуры READ и READLN. Это процеду- ры самого верхнего логического уровня: они не только осуществля- ют взаимодействие с драйвером клавиатуры, но и преобразуют сим- 76
I / вольные данные во внутренний формат представления соответству- ющих переменных. Однако они обладают тремя существенными j недостатками. Во-первых, с их помощью невозможно опознать на- , жатие на клавиши управления курсором, на функциональные кла- виши, вообще на любые не алфавитно-цифровые клавиши. Во-вто- рых, ввод символов с помощью этих процедур сопровождется их воспроизведением (эхо-повтором) на экране, что не всегда удобно. И, наконец, обращение к этим процедурам приостанавливает про- грамму впредь до нажатия на клавишу "Ввод". Последняя особен- ность существенно обедняет ценность этих процедур, например, в игровых задачах. Таблица'15 Состояние указателей двоичных переключателей клавиатуры (для единичного значения бита) Байт Бит Функция/клавиша Состояние функции/клавиши 1047 1 Вставка Активное 2 Фиксация заглавных букв II 3 Фиксация цифр \ »| 4 Фиксация прокрутки VI 5 ALT Нажата 6 CTRL и 7 Левая клавиша SHIFT м 8 Правая клавиша SHIFT п 1048 1 INS It 2 CAPS-LOCK It 3 NUM-LOCK и 4 SCROLL-LOCK и 5 CTRL-NUMLOCK Активное 6 He используется - 7 He используется - 8 He используется — Почти все перечисленные недостатки можно устранить, если ис- пользовать две очень полезные функции стандартного модуля CRT. Функция KEYPRESSED. Возвращает значение типа BOOLEAN, указывающее, была ли нажата любая клавиша после последнего чтения из буфера клавиатуры или нет. Обращение KEYPRESSED Заметьте, что обращение к функции никак не влияет ни на подготовленный клавиатурой код ‘ нажатой клавиши, ни на саму функцию KEYPRESSED: сколько бы раз мы ни обращались к ней, она будет возвращать TRUE, если была нажата какая-либо клави- ша, - до тех пор, пока буфер клавиатуры не будет прочитан проце- дурами READ/READLN или функцией READKEY. Функция не за- держивает работу программы и возвращает FALSE, если ни одна клавиша не была нажата. 277
Функция READKEY. Возвращает значение типа CHAR - код оче- редной нажатой клавиши. Обращение READKEY В отличие от KEYPRESSED эта функция приостанавливает рабо- ту программы до тех пор, пока не будет нажата любая клавиша. Замечательной особенностью этой функции является то, что с ее помощью можно опознать нажатие почти на любую клавишу; иск- лючение составляют только клавиши смещения и клавиши-пере- ключатели. Кроме того, функция вводит символ ’’вслепую”, т.е. без эхо-повтора на экране. Нельзя не оценить тех преимуществ, которые дает программе использование обеих функций. Во-первых, появляется простая воз- можность создавать программы, которые будут продолжать свою ра- боту с одновременным сканированием клавиатуры. Для этого лишь нужно время от времени опрашивать состояние клавиатуры с по- мощью функции KEYPRESSED. Во-вторых, способность функции READKEY опознать нажатие на любую клавишу и прочитать ее код вслепую открывает широкие возможности для разработки удобных интерактивных программ, управление которыми сводится в основ- ном к нажатию на функциональные клавиши. С помощью READKEY можно опознать нажатие на клавиши, генерирующие расширенные коды. В этом случае при первом обра- щении функция возвращает нулевой байт, который служит призна- ком расширенного кода. Получив нулевой байт, программа должна прочитать второй символ и отнести его к расширенному набору ко- дов, например: Uses CRT; var ExtChar : Boolean; {флаг - признак расширенного кода} с : char; {вводимый символ} begin с := ReadKey; if с = 0 then ExtChar := true else ExtChar := false; if ExtChar then c := ReadKey; Программа в примере 82 поможет Вам составить собственную таблицу кодов, возвращаемых драйвером клавиатуры. Программа читает клавиатуру и выводит на экран коды клавишей до тех пор, пока не будет введена комбинация CTRL-2. Пример 82 PROGRAM TestOfKeyboard; Uses CRT; ' var cl, c2 : char; BEGIN repeat cl := ReadKey; if cl = #0 then c2 := ReadKey; 278
if cl = #0 then writein (ord(cl):5, ord(c2):5) else writein (ord(cl):5) until (cl = #0) and (c2 = #3) ENO. На основе функции READKEY можно создать ориентированный на национальный язык текстовый редактор для тех ПЭВМ, знакоге- нератор которых не имеет символов данного алфавита. Для этого нужно выбрать одну из клавишей-переключателей в качестве клави- ши перехода с латинского алфавита на национальный и обратно. Чаще всего для этого используется клавиша CAPS-LOCK, хотя лич- но мне кажется более удобным сохранить за ней ее основную фун- кцию - фиксацию заглавных букв, а в качестве переключателя алфа- вита использовать, например, SCROLL-LOCK. Состояние клавишей- переключателей легко прочитать с помощью прямого обращения к памяти (см. гл. 11). Например, следующая Турбо-Паскалевая функ- ция будет возвращать TRUE, если активна функция SCROLL-LOCK: Function ScrollLock : Boolean; begin if (Mem [0:1047] and 16) = 0 then Scr-ollLock := false - else ScrollLock : = true end; Далее необходимо организовать "слепой" ввод символов с по- мощью функции READKEY и при активном состоянии переключа- теля осуществлять их перекодировку в символы национального ал- фавита. Наконец, эхо-печать преобразованных символов нужно де- лать в графическом режиме с использованием загруженной таблицы знакогенератора (см. гл. 13). Останется лишь нанести на клавиши ПЭВМ символы национального алфавита да разработать шрифты для вывода сообщении на принтер - и Вы сможете писать тексты на родном языке. Функции KEYPRESSED и READKEY относятся к среднему логи- ческому уровню работы с клавиатурой. Хотя, на мой взгляд, они обеспечивают потребности подавляющего большинства прикладных программ, тем не менее в некоторых специальных случаях их воз- можности могут оказаться недостаточными: если Вы внимательно [изучите табл.14, то заметите, что многие комбинации клавишей смещения с другими клавишами в ней отсутствуют; кроме того, с помощью READKEY нельзя опознать нажатие на сами клавиши смещения, а также на клавиши-переключатели. Все эти действия становятся возможны при переходе на самый нижний логический уровень, связанный с непосредственным обращением к драйверу клавиатуры или к самой клавиатуре. Такие обращения реализуются | помощью двух прерываний: прерывание с номером 9-от микро- процессора клавиатуры и с номером 22-обращение к драйверу. I Наиболее универсальными возможностями обладает, разумеется, |рерывание с номером 9. Программная обработка этого прерывания вязана фактически с реализацией собственного драйвера клавиату- р. При обработке прерывания с номером 9 драйвер должен про- мтать код сканирования клавиши из порта 96. После чтения кода 279
сканирования драйвер посылает микропроцессору сигнал очистить буфер ввода. Так как все эти действия реализует стандартный драй- вер клавиатуры, обращаться непосредственно к порту 96 не имеет смысла: драйвер успевает прочитать символ и очистить буфер, поэ- тому программа пользователя будет читать из порта всегда нуль. Чтобы обрабатывать прерывание с номером 9, нужно отключить стандартный драйвер, для чего необходимо заменить вектор преры- вания с номером 9 (адреса 36...39) адресом точки входа в драйвер пользователя. К драйверам предъявляются повышенные требования в отношении скорости работы и компактности программ1. Прерывание с номером 22 инициируется самой работающей Турбо-Паскалевой программой и реализуется значительно проще. Однако по сравнению с функцией READKEY это прерывание не дает практически ничего нового. Вот его функции: АН=0 -читать символ с ожиданием нажатия на клавишу; АН = 1 -проверить готовность клавиатуры; АН = 2 -прочитать статус (байт с адресом 1047). При обращении к функции АН=0 в AL возвращается код нажа- той клавиши или 0; если эта клавиша генерирует код из расширен- ного набора, в АН возвращается код сканирования клавиши. При обращении к функции АН=2 в регистре АН возвращается содержи- мое байта с адресом 1047 (см. 14.1). Программа из примера 83 иллюстрирует технику обращения к драйверу клавиатуры. Программа читает и выводит коды сканирова- ния клавишей до тех пор, пока не будет нажата клавиша ESC. Пример 83 PROGRAM ScanCodesDemo; Uses DOS; var r : registers; BEGIN with r do begin repeat repeat Flags := 0; AH := 1; Intr (22, r) until Flags <>2022; AH := 0; Intr (22, r); writein (AL:5, AH:5) unti1 (AL = 27) and (AH = 1) end END. 1 Подробное описание русифицированного драйвера клавиатуры можно найти в статье Козлова А.В. Программы русификации клавиатуры и видеоадаптера ПК//В мир персональных компьютеров.-1988.-N2.-С.96...98. 280
В игровых программах часто возникает необходимость очистить буфер ввода клавиатуры. Для этого можно использовать несложный прием: var c:char while KeyPressed do c:=ReadKey; Однако более тонкие возможности дает непосредственное обра- щение к буферу ввода. Этот буфер размещается в оперативной па- мяти ПЭВМ по адресу $41Е (1054) и занимает 32 смежных байта, которые микропроцессор клавиатуры использует в качестве кольце- вого регистра: после заполнения последнего байта буфера по адресу >43D (1085) очередной код будет помещаться в начало буфера. Для управления работой кольцевого регистра микропроцессор использует два двухбайтных указателя с адресами $41А (1050) и $41С (1052):пер- вый содержит смещение для адреса байта, в котором начинается еще не прочитанная драйвером клавиатуры часть буфера, второй указывает на конец этой части (сегментная часть для обоих указате- лей равна $400). Если буфер пуст, то оба указателя содержат адрес одного и того же байта в кольцевом регистре. При нажатии на оче- редную клавишу микропроцессор поместит по этому адресу два байта. Если нажата клавиша, с которой связан однобайтный ASCII- код, то в первый байт будет помещен этот код, а в следующий за ним - код сканирования клавиши; если нажата клавиша, с которой связан код из расширенного набора, то в первом байте будет нуль, а во втором - номер расширенного кода (см. табл. 14). После этого микропроцессор увеличивает содержимое второго указателя. Когда драйвер прочитает символ, микропроцессор изменит содержимое первого указателя. Если программа не успевает обрабатывать нажи- маемые клавиши, их коды накапливаются в буфере и могут вы- звать его переполнение - в этот момент микропроцессор выдает ко- роткий звуковой сигнал на динамик ПЭВМ и "лишние" коды нажа- тых клавишей безвозвратно теряются. Таким образом, чтобы сбро- сить буфер ввода, достаточно установить одинаковое значение обоих указателей. Сделать это несложно, если обратиться непосредственно к указателям, например, так: MemW[0:$41A] := MemW [0: $41 С] ; Однако опытный программист должен предусмотреть возмож- ность очередного нажатия на клавишу в момент выполнения этого итератора и предварительно закрыть прерывания. Такую операцию легко реализовать с помощью встроенных машинных кодов по сле- тующей схеме: inline($FA/); {CLI - закрыть прерывания} MemW[0:$41A]:=MemW[0:$41C] ; {сбросить буфер} iniine($FB/); {ST! - открыть прерывания} Разумеется, можно обращаться к буферу ввода и в других ситуа- циях. Например, можно сначала просмотреть накопленные в буфе- >е коды с целью проверки, не была ли нажата какая-либо функци- •нальная клавиша или клавиша управления курсором. Если расши- >енных кодов не обнаружено, можно обращаться к стандартным 281
средствам оператора READ, в противном случае можно выполнить действия, связанные с нажатием на эту клавишу, например очи- стить экран или переместить на нем фрагмент изображения. Более того, можно помещать соответствующие коды в буфер и нужным образом изменять указатель $41С, имитируя тем самым нажатие тех или иных клавишей. Если подготовить таким образом буфер ввода непосредственно перед завершением программы, то сразу за. ее остановкой командный процессор COMMAND.COM операционной системы прочитает его содержимое и выполнит нужные действия, например запустит другую программу. Изменять можно не только содержимое буфера ввода, но и двухбайтного указателя состояния клавишей-переключателей (см. табл. 15). 14.3. ТЕХНИЧЕСКИЕ ОСОБЕННОСТИ ПРИНТЕРОВ Для ПЭВМ разработаны и выпускаются разнообразные печатаю- щие устройства (принтеры). В этом разделе описываются наиболее распространенные девятиигольчатые матричные принтеры, совме- стимые с IBM PC. Как и другие технические средства персонального компьютера, принтер представляет собой достаточно сложное устройство, в состав которого входит собственный микропроцессор и память. Печатаю- щий механизм принтера содержит матрицу из 9 тонких иголок, об- разующих вертикальный ряд. Иголки располагаются перпендикуляр- но к поверхности листа бумаги, между ними и листом проходит красящая лента. Каждая иголка может перемещаться независимо от других с помощью небольшого электромагнита. При включении электромагнита иголка через красящую ленту ударяет по листу бу- маги и оставляет на нем четко видимый след - точку. Печатающая' головка принтера, в которук! входят матрица иголок и управляю- щие электромагниты, перемещается поперек неподвижного листа подобно электронному лучу в телевизионной трубке. Остающиеся от) ударов иголок точки формируют на бумаге изображения символов! почти так же, как пиксели формируют символы на экране матрич ного дисплея. Поскольку иголок достаточно много - 9, строка симво- лов на- бумаге формируется за один проход печатающей головки. Программное управление движением каждой иголки в отдельности открывает практически неограниченные возможности смены шриф- тов, а также позволяет создавать с помофью принтера произволь- ные графические изображения на листе бумаги. Подобно дисплею принтер может работать в двух режимах: сим- вольном и графическом. , В символьном режиме он печатает текстовую информацию в со- ответствии с тем шрифтом (шрифтами), которые имеются в его ПЗУ (некоторые принтеры, например Amstrad LQ3500, позволяю^ загружать сменные шрифты в их память по каналам связи с ПЭВМ). Шрифт представляет собой набор прямоугольных матриц размером 11x9 бит (размер матриц зависит от типа шрифта и типа принтера), напоминающих матрицы знакогенератора дисплея. Мат; рицы содержат • образцы символов и используются микропроцессо- 282'
ром принтера для управления иголками: микропроцессор получает от ПЭВМ байты выводимых сообщений, отыскивает нужные матри- цы в шрифте и, согласуясь с ними, формирует соответствующее изображение на бумаге. Процесс печати в символьном режиме про- исходит достаточно быстро: обычно принтер успевает напечатать стандартную страницу текста за 2...3 минуты, причем печать идет как при движении головки слева направо, так и в обратном направ- лении. В графическом режиме микропроцессор / принимает от ПЭВМ сразу серию управляющих байт, каждый из которых своими битами кодирует состояние 8 иголок печатающей головки (в этом режиме 9-я иголка не задействована). Один байт обеспечивает управление состоянием иголок в текущем положении печатающей головки, по- сле удара иголок и смещения головки на небольшое (доли милли- метра) расстояние по горизонтали принимается следующий байт, вновь формируется состояние иголок и т.д. В результате прохода печатающей головки на лист будет выведена узкая горизонтальная полоска изображения, состоящая из 8 строк точек (по числу иго- лок). Расстояние между выводимыми точками по горизонтали и вертикали очень мало, поэтому точки сливаются в цельное изобра- жение. Графическую информацию принтер выводит гораздо медлен- нее, чем символьную: например, копия графического экрана форми- руется за 5...7 мин, причем печать идет только при проходе голо- вки слева направо. 14.4. УПРАВЛЕНИЕ ПРИНТЕРОМ Для управления принтером чаще всего используются стандарт- ные процедуры вывода WRITE и WRITELN, выход которых направ- ляется на печатающее устройство. Переадресовать вывод на при- нтер можно, например, таким способом: var 1ST : text; assign (LST, LPT1); rewrite (LST); writeIn (LST, ...); Эти несложные подготовительные действия реализуются в уста- новочной части стандартного библиотечного модуля PRINTER, поэ- тому после объявления Uses Printer; можно сразу использовать операторы вида ' writein (LST, ’Сообщение’); см. гл. 10). Доступ к принтеру с помощью процедур WRITE/WRITELN дает (остаточно гибкие возможности управления печатью, так как раз- личного рода команды управления принтером передаются по тем 283
же информационным линиям связи, что и собственно выводимая информация, а следовательно, они могут просто вставляться в вы- водимые данные. Примером простейших команд могут служить ко- манды прогона бумаги на очередную строку или до конца листа. Прогон на строку осуществляется всякий раз, когда среди выводи- мых символов микропроцессор обнаружит символы LF (ASCII-код 10) или VT (код 11), а прогон до конца страницы - символ FF (код 12). Таким образом, следующая программа выведет сообщение при- близительно на середине стандартного листа: Uses Printer; const MaxPage = 75; {длина листа в строках} var i : byte; begin for i := 1 to MaxPage div 2 do write (LST, #10); write (LST, 'Это середина листа’, #12), । end. КрбмЬ этих простейших команд, могут использоваться и другие. Ниже описываются управляющие команды принтера так, как они реализованы в широко распространенном в СССР принтере Robotron К6313 производства ГДР. Эти команды воспринимаются и в большинстве других принтеров, совместимых с IBM PC, однако для полной реализации возможностей принтера, с которым работает Ваша ПЭВМ, желательно изучить его описание. Команды реализуются одним или несколькими последовательны- ми байтами, причем в многобайтных командах первый байт - это обычно символ ESC (код 27). Команды разбиты на группы по фун- кциональному назначению. Рядом с символом (символами) коман- ды в скобках приводится его (их) байтовый эквивалент в коде ASCII. В многобайтных командах отдельные байты разделяются за- пятыми, т.е. представлены в том виде, как они будут выглядеть в списке параметров вызова процедур WRITE/WRITELN (в самой ко- манде, передаваемой принтеру, запятых не должно быть). 14.4Л. Выбор шрифтов Выбор шрифтов осуществляется с помощью следующих команд: SO (#14) - выбор широкого шрифта; DC4 (#20) - отказ от широкого шрифта; SI (#15) - выбор узкого шрифта; DC2 (#18) - отказ от узкого шрифта; ESC/E’ (#27,#71) - выбор утолщенного шрифта; ESC/F’ (#27,#72) - отказ от утолщенного шрифта; ESC/G’ (#27,#73) - двойная печать. В этом режиме печать одной строки повторяется дважды; ESC/H’ (#27,#74) - отказ от двойной печати; ESC/S’,CHR(0) (#27,#83,#0) - печать верхних индексов; 284
ESC,’S’,CHR(1) (#27,#83,#1) - печать нижних индексов; ESC,’T’ (#27,#84) - отказ от печати индексов; ESC,’-’,CHR(1) (#27,#35,#0) - печать с подчеркиванием; ESC,’-’,CHR(0) (#27,#35,# 1) - отказ от подчеркивания. В примере 84 иллюстрируются шрифтовые возможности принте- ров ROBOTRON. Пример 84 PROGRAM TypeStyleDemo; Uses Printer; const ESC = #27; SO = #14; DC4 = #20; SI = #15; DC2= #18; BEGIN writeln (LST, 'Демонстрация шрифтовых возможностей’, ' принтера типа ROBOTRON’); writein (LST, SO, ’Это широкий шрифт’, DC4, а это - стандартный’); writein (LST, SI, ’Это узкий шрифт’, DC2); . writein (LST, ESC, ’Е', ’Это утолщенный шрифт', ESC, 'F') writein. (LST, ESC, 'G', 'Двойная печать ’, ESC, ’Н'); writein (LST, ESC, #1, 'Печать с подчеркиванием’, ESC, #0); writein (LST, ’Печать индексов: (Н’, ESC, 'S’, #1, SI, '2', DC2, ESC, 'T', ’SO’, ESC, ’S’, #1, SI, ’4’, DC2, ESC, rT', ')’, ESC, ’S’, #0, SI, ’3’, DC2, ESC, ’T’) END. Еще раз напомню, что, если Ваш принтер не отвечает стандарту IBM PC (например, принтер D100 производства ПНР), описанные команды могут интерпретироваться иначе. Кроме того, если в его шрифтах не предусмотрены символы кириллицы, Вы не сможете на нем в текстовом режиме напечатать сообщения на русском язы- ке (в этом случае желательно заменить русские буквы в приведен- ном выше примере их латинским эквивалентом). 14.4.2. Перемещение бумаги Перемещение бумаги вверх на одну строку осуществляется вся- кий раз после выполнения процедуры WRITELN(LST), а также в том случае, когда среди символов выводимого текста обнаружатся коды LF (10) или VT (11). Описываемые ниже команды управляют перемещением бумаги и форматом текстовых страниц. Минималь- но возможное вертикальное перемещение листа составляет 1/216 часть дюйма (0,118 миллиметра) - это равно 1/3 расстояния между двумя иголками по вертикали. Один интервал принтера (высота стандартного шрифта) равен 1/8 дюйма (3,175 мм). В принтере Robotron предусмотрены следующие команды пере- мещения бумаги: 285
ESC,’0’ (#27,#48) - настройка на интервал 1/8 дюйма; ESC,’Г (#27,#49) - настройка на интервал 7/72 дюйма; ESC,’A’,CHR(n) (#27,#65,#п) - настройка на интервал п/72 дюйма, здесь п=1...85; эта команда используется совместно с командой ESC,’2’; ESC,’2’ (#27,#50) - начало работы с интервалом п/72 дюйма; этой команде должна предшествовать команда ESC,’А’; ESC,’3’,CHR(n) (#72,#51,#п) - настройка на интервал п/216 дюйма; если п=1 или п=2, то точность продвижения бумаги не гарантиру- ется; ESC,’C’,CHR(n) (#27,#67,#п) - установка числа строк в странице; здесь п=1...127~ число строк (в стандартной странице (66 или 72 строки). Описываемая команда не приводит к перемещению бумаги после заполнения страницы и используется совместно с командой ESC,’N’ для форматирования выводимого текста; ESC,’C’,CHR(0),CHR(n) (#27,#67,#0,#п) - установка длины страни- цы; здесь п=1...22 дюйма. Размеры стандартной машинописной страницы - 210x300 мм или приблизительно 8,26x11,8 дюймов; ESC,’N’,CHR(n) (#27,#78,#п) - пропустить и не печатать п строк в конце страницы, здесь п=1...127. Эта команда используется совмест- но с командой ESC,’С’ для форматирования выводимого текста. \ Например программа Uses Printer; var i, j : integer; BEGIN write (LST,#27,'C,#5); write (LST, #27,'N’,#1,#27,'9');' for i := 1 to 2 do for j := 1 to 5 do writein (LST,’Страница ',1, ' Строка ’,□) ENO. выведет на печать такие строки: Страница 1 Строка 1 Страница 1 Строка 2 Страница 1 Строка 3 Страница 1 Строка 4 Страница 2 Строка 1 Страница 2 Строка 2 Страница 2 Строка 3 Страница 2 Строка 4 ESC,’8’ (#27,#56) - включение контроля за концом бумаги; ESC,’9’ (#27,#57) - отключение контроля за концом бумаги. 286
14.4.3. Печать в графическом режиме Перед тем как описать команды графического режима печати, полезно привести некоторые количественные характеристики IBM- совместимого принтера.. Расстояние между двумя соседними иголками по вертикали со- ставляет 1/72 дюйма или 0,352 мм. В текстовом режиме печати ис- пользуются все 9 иголок печатающей головки, поэтому высота стан- дартного шрифта принтера составляет 1/8 дюйма (3,1 мм). В графи- ческом режиме задействованы только 8 иголок, что позволяет за один проход печатающей головки сформировать полоску изображе- ния высотой 1/9 дюйма (2,82 мм). Минимальное расстояние между двумя соседними точками в горизонтальном направлении зависит от выбранной плотности печати, которая может быть 480, 960 или 1920 точек на 8 дюймов (соответственно 0,423, 0,211 и 0,106 мм). Длина выводимого участка изображения в горизонтальном на- правлении задается количеством управляющих байтов, передавае- мых принтеру по линии связи. Каждый бит одного байта управляет одной из 8 иголок: если значение бита равно 1, иголка ударит по бумаге, если'0-нет. Старший бит в байте управляет самой верхней иголкой печатающей головки (рис. 24). Все команды графической печати однотипны: ESC,X,nl,n2, где Х-одна из букв К, L, Y или Z, задающих конкретный редким печати (см. ниже); байты nl и п2 Рис. 24. Связь управляющего байта с иголками печатающей головки 287
определяют двухбайтное слово типа WORD, причем nl - младший байт этого слова, а п2- старший байт. Двухбайтное слово указывает принтеру общее количество байтов, которые будут переданы ему при выводе очередной полоски изображения. Если N - количество этих байтов, то nl := chr(N mod 256); n2 := chr(N div 256); ИЛИ nl := chr(Lo(N)); n2 :• chr(Hi(N)); После завершения печати очередной полоски изображения при- нтер автоматически восстанавливает режим текстовой печати. Следующие команды задают плотность графической печати: ESC,’K’,CHR(nl),CHR(n2) (#27,#75,#nl,#п2) - плотность печати 480 точек на 8 дюймов, скорость печати 10 дюймов в секунду; ESC,’L’,CHR(nl),CHR(n2) (#27,#75,#nl,#п2) - плотность печати 960 точек на 8 дюймов, скорость печати 6 дюймов в секунду; ESC,’Y’,CHR(nl),CHR(n2) (#27,#75,#nl,#п2) - плотность печати 960 точек на 8 дюймов, скорость печати 10 дюймов в секунду; в этом режиме будет напечатана каждая вторая точка по горизонтали; ESC,’Z’,CHR(nl),CHR(n2) (#27,#75,#nl,#n2) - плотность печати 1920 точек на 8 дюймов, скорость печати 6 дюймов в секунду; в этом режиме будет напечатана каждая вторая точка по горизонтали. Программа примера 85 иллюстрирует графическую печать в каждом из четырех режимов. Пример 85 PROGRAM GraphPrintDemo; Uses Printer; const N = 256; m : array [1..4] of char = ('K', 'L', 'Y', 'Z'); var i, j’, k, 1 : integer; BEGIN for 1 := 1 to 4 do begin k := 128; write (LST, #27, m[l], chr(Lo(N)), chr(Hi(N))); for i := 1 to 8 do begin for j := 1 to N div 16 do write (LST, chr(k)); k := k shr 1 end; k := 1; for i := 1 to 8 do begin for j := 1 to N div 16 do write (LST, chr(k)); k := k + (1 shl i) end; 288
writein (LST) end END. Обратите внимание на плотность вывода во второй и третьей строках. В одной строке можно сочетать графическую и текстовую пе- чать, как, например, в программе из примера 86. Пример 86 PROGRAM Text_and_Graph_Print_Demo; Uses Printer; const _ m : array [1..12] of char = (#30, #30, #62, #127, #127, #127, #127, #126, #48, #48, #48, #48); var i : byte; BEGIN write (LST, #27, 'K', #12, #0); for i := 1 to 12 do write (LST, m[i]); writein (LST, ' Внимание!1) END. 14.5. УПРАВЛЕНИЕ ЗВУКОВЫМ УСТРОЙСТВОМ Звуковые возможности ПЭВМ основаны на одноканальном уп- равляемом звукогенераторе, вырабатывающем электромагнитные ко- лебания звуковой частоты. Колебания подаются на встроенный в ПЭВМ динамик и заставляют его звучать. В Турбо-Паскале предусмотрены весьма скромные средства уп- равления звуковым устройством в виде следующих двух процедур. Процедуре? SOUND. Заставляет выдать звук нужной частоты; формат обращения SOUND ( <F> ) Здесь < F> - выражение типа WORD, означающее частоту зву- ка, Гц. Процедура NOSOUND. Выключает звучание динамика. Обраще- ние NOSOUND Следующая процедура может использоваться в разных случаях, но чаще всего она применяется для выдачи звука нужной длитель- ности по схеме SOUND - DELAY - NOSOUND. Процедура DELAY. Обеспечивает задержку работы программы на заданный интервал времени; формат обращения DELAY ( <Т> ) Здесь <Т> - выражение типа WORD, задающее интервал време- ни, МС. При исполнении программы из примера 87 прозвучит простая музыкальная гамма. Массив' частот F содержит частоты всех полу- 10 - Фаронов 289
тонов в основной октаве. При переходе от одной октавы к соседней частоты изменяются в два раза. Пример 87 PROGRAM SoundDemo; . Uses crt; const f : array [1..13] of word = (330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 588, 622, 660); var i, j : byte; BEGIN for j := 1 to 2 do for i := 1 to 13 do begin sound(j * f[i]); delay(100); '; ?'pf nosound end; for j := 2 downto 1 do for i := 13 downto 1 do begin sound(j * f[i]); delay(100); nosound end END.
Ч а с т ь IY ПРОГРАММИРОВАНИЕ НА ТУРБО-ПАСКАЛЕ ю*
В этой части книги приводятся законченные программы, кото- рые иллюстрируют приемы программирования на Турбо-Паскале, но могут также представлять и самостоятельный интерес. При вы- боре программ для этой части я исходил из того, что многие чита- тели имеют практический опыт работы с Фортраном ОС ЕС, для которого разработана обширная библиотека подпрограмм разного на- значения. Отсутствие такого рода подпрограмм в среде Турбо-Паска- ля может затруднить переход к этой системе программирования. Разумеется, я не ставил себе целью полную замену библиотечных подпрограмм Фортрана аналогами на Турбо-Паскале: система про- граммирования Турбо-Паскаль еще слишком "молода", и разработка для нее мощных библиотек - дело будущего. Предлагаемые программы можно разбить на две группы: про- граммы поддержки математических расчетов и вспомогательные программы. Программы поддержки математических расчетов в основном за- имствованы мной из прекрасной книги [8]. При переносе Фортра- новских программ из этой книги на Турбо-Паскаль я стремился по возможности сохранить их стиль: идентификаторы и комментарии оставлены практически без изменений, однако везде, где это было; возможно без кардинальной переделки программы, я выбросил мет- ки и операторы безусловного перехода. После некоторых колебаний я решил все-таки сохранить элементы машинной независимости программ, хотя для Турбо-Паскаля это, по всей вероятности, не нужно: насколько мне известно, Турбо-Паскаль существует только на IBM-совместимых ПЭВМ. Учитывая, что программы из книги [8] достаточно громоздки, я добавил также несложные альтернатив- ные программы, которые, очевидно, не столь эффективны, но зна- чительно проще. Подпрограммы численных методов сгруппированы по функцио- нальному назначению и оформлены в виде отдельных модулей. С учетом описанной в гл. 5 особенности вычислений с типом REAL в каждом из этих модулей вводится базовый вещественный тип REALTYPE=EXTENDED, что обеспечивает максимально возмож- ную точность вычислений при сохранении высокой скорости на ПЭВМ, оснащенных арифметическим сопроцессором. Если Ваша ПЭВМ не имеет сопроцессора, Вы можете сохранить этот базовый тип, потребовав программную эмуляцию сопроцессора, однако ско- рость исполнения программ в этом случае будет самой медленной В тех случаях, когда требуется высокая скорость вычислений или необходимо экономить память, Вы можете пожертвовать точно стью, объявив REALTYPE = SINGLE для ПЭВМ с сопроцессором или REALTYPE = REAL для машин без сопроцессора. Вспомогательные программы носят оригинальный характер и рассчитаны на самые разнообразные применения. В них вошли уже упоминавшиеся в предыдущей части модули TEXTCRT и GRAPHTXT, а также две са- мостоятельные программы: установки текущих даты и времени в операционной системе и генерации шрифтов. 292
Глава 15 ПРОГРАММЫ ПОДДЕРЖКИ МАТЕМАТИЧЕСКИХ ВЫЧИСЛЕНИЙ 15.1. ЛИНЕЙНЫЕ СИСТЕМЫ УРАВНЕНИЙ В этот раздел включены две подпрограммы из книги [8], с по- мощью которых можно решить систему алгебраических линейных уравнений произвольного порядка. Описываемый ниже модуль DECOMPSV необходимо поместить в файл DECOMPSV.PAS. При сомпиляции нужно учесть, что в модуле используется десятибайт- гая арифметика с плавающей точкой, поэтому опция OPTIONS/ COMPILER/NUMERIC PROCESSING должна находиться в состоянии Ю87/80287, а если в Вашей ПЭВМ нет арифметического сопроцессо- >а, то в дополнение к этому опция OPTIONS/COMPILER/EMULA- TION должна иметь значение ON (подробнее об опциях среды :м. гл. 3). При решении системы линейных уравнений вида А*Х=В, где А - матрица коэффициентов размерностью NxN; N-порядок системы; В - вектор длиной N значений правых частей; X - искомый вектор решения, :ледует учесть, что коэффициенты матрицы А должны задаваться ю строкам. Эти коэффициенты, а также коэффициенты вектора В I переменная COND, в которой процедура DECOMP возвращает щенку обусловленности матрицы А, должны быть типа REALTYPE базовый вещественный тип, определенный в модуле DECOMPSV). Процесс решения системы состоит из двух шагов. Вначале с по- 1ощыо следующего вызова процедуры DECOMP: Decomp (N, A, Ipvt, Work, Cond); где N - выражение типа WORD, определяющее порядок системы; А - переменная в виде массива NxN и типа REALTYPE, содержа- щая матрицу коэффициентов левых частей уравнений; COND - переменная типа REALTYPE, возвращающая оценку бусловленности матрицы А; IPVT - рабочий вектор длиной N и типа INTEGER; 293
WORK - рабочий вектор длиной N и типа REALTYPE, осущес ляются некоторые подготовительные действия и оценивай обусловленность матрицы А: чем больше значение COND на в ходе процедуры, тем хуже обусловленность матрицы. Если прог дура обнаружила вырожденность матрицы А, она возвращг COND = 1.0E+32. Во всех случаях, если COND + 1 = COND, матрицу можно считать вырожденной в пределах машинной точности в числений. На втором шаге вызывается процедура SOLVE: Solve (N, А, В, Ipvt); Здесь В - вектор длиной N и типа REALTYPE, который при в; де в процедуру содержит значения правых частей уравнений, а п выходе возвращает искомый вектор решения; N, А и IPVT - описаны выше, причем между обращениями процедурам DECOMP и SOLVE их значения не должны изменя: ся. Процедуру SOLVE не следует вызывать, если DECOMP обна{ жила вырожденность или плохую обусловленность матрицы А. {=============} UNIT DecompSv; {============} {В этот модуль включены две процедуры DECOMP и SOLVE, которые мож) использовать для решения линейной системы уравнений методом гауссова и ключения с частичным выбором ведущего элемента [8, с. 61...70]. С п мощью DECOMP выполняется та часть алгоритма, которая зависит лишь свойств матрицы коэффициентов. Она сохраняет множители и информацию ведущих элеменУах, а процедура SOLVE использует эти результаты, что получить решение для произвольной правой части. Поскольку алгоритм чу ствителен к ошибкам округления, в качестве базового вещественного .типа Модуле принят тип REALTYPE = EXTENDED. Если Ваша ПЭВМ не оснащена сопр цессором и Вы хотите несколько выиграть в скорости ценой ухудшения то ности вычислений, Вы должны переопределить этот тип на REALTYPE = REAL {--------------------------------------------L--------------} INTERFACE {$N+,E+} {Директивы компилятора для эмуляции сопроцессора. Эти д рективы можно убрать, если установить перед компиляцией соответствуют опции среды Турбо-Паскаля. Их необходимо убрать и в том случае, ког Ваша ПЭВМ не имеет сопроцессор и Вы установили REALTYPE=REAL.} type RealType = extended; ArrayType = array [1..(2*MaxInt) div SizeOf(RealType)] of RealType; PROCEDURE Solve (N : word; var Al, BI, Ipvtl); PROCEDURE Decomp (N : word; var Al, Ipvtl, WorkI; var Cond : RealType); {-----------------------------------------------------------} IMPLEMENTATION * 1 FUNCTION Indx (N, i, j : integer) : integer; {Эта вспомогательная функция вычисляет положение элемента в- массив строке по значению индексов I и J квадратной матрицы размером NxN.} BEGIN {Indx} : 294
Indx := N * pred(i) + j END {Indx}; {---------------------} PROCEDURE Solve (N : word; var Al, BI, Ipvtl); var A : ArrayType absolute Al; В : ArrayType absolute BI; Ipvt : array [1. .Maxint] of integer absolute Ipvtl; T : RealType; i, k, m, kb : integer; {Решение линейной системы A * X = В. Процедуру не следует использовать, если DECOMP обнаружила вырожден- ность матрицы А. ВХОДНАЯ ИНФОРМАЦИЯ: N - порядок матрицы. А - факторизованная матрица, полученная из DECOMP. В - вектор правых частей. Ipvt - вектор ведущих элементов, полученный из DECOMP. ВЫХОДНАЯ ИНФОРМАЦИЯ: В - вектор решения X.} BEGIN {Solve} . {Прямой ход} if N <> 1 then begin for k := 1 to N-l do begin m := Ipvt [k]; T := PM; BM := В [k] ; B[k] := T; for i := k+1 to N do ♦ B[i] := B[i] + A[Indx(N, i ,k)] * T end; {Обратная подстановка} for kb := 1 to N-l do begin k := N - kb + 1; B[k] := B[k] / A[Indx(N,k,k)] ; T := -B[k] ; for i := 1 to N-kb do B[i] := B[i] + A[Indx(N, i,k)] * T end end; B[l] := 8[1] / A[l] END {Solve}; {---------1----------} PROCEDURE Decomp (N : word; var Al, Ipvtl, WorkI; var Cond : RealType); var A : ArrayType absolute Al; 295
Ipvt : array [1..Maxint] of integer absolute Ipvtl; Work : ArrayType absolute WorkI; EK, T, Anorm, Ynorm, Znorm : RealType; i, j, к, m, kb : integer; {Процедура вычисляет разложение вещественной матрицы посредством га уссова исключения и оценивает обусловленность матрицы. Она используете* для вычисления решений линейных систем. ВХОДНАЯ ИНФОРМАЦИЯ: N - порядок матрицы. AI - матрица, которую нужно разложить. WorkI - рабочий массив. ВЫХОДНАЯ ИНФОРМАЦИЯ: А содержит верхнюю треугольную матрицу U и учитывающую перестановк версию нижней треугольной матрицы 1-L, такие, что (матрица перестановок * А = L * U; Ipvtl - вектор ведущих элементов, причем Ipvtl [к] - индекс k-й веду щей строки’, Ipvtl [N] = (-1)**(число перестановок); Cond - оценка обусловленности А. Для линейной системы А * X = В из менения в А и В могут вызвать изменения в X, большие в Cond раз. Есл Coiqd + 1 = Cond, то в пределах машинной точности А является вырожденно матрицей. Cond полагается равным 1.0Е+32, если обнаружена точная вырох денность. Определитель матрицы А может быть получен на выходе по формуле Det (А) = Ipvt[N] * А[1,1] * А [2,2] * ... * A [N, N]} BEGIN {Decomp} •Cond := 1.0Е+32; Ipvt [N] := 1; if N = 1 then { случай матрицы lxl } begin if A[l] <> 0 then Cond := 1; ex i t end; {Вычислить 1-норму матрицы A.} Anorm := 0; for j := 1 to N do begin T := 0; for i := 1 to N do T := T + abs(A [Indx(N,i,j)]); if T > Anorm then Anorm := T end; {Гауссово исключение с частичным выбором ведущего элемента.} for k := 1 to N-l do begin | {Найти ведущий элемент.} m : = к; for i := к+1 to N do if abs(A СIndx(N,i,к)]) > abs(A[Indx(N,m,k)]) 1 ' then m: = i; Ipvt[k] := m; 296
if m <> к then Ipvt [N] := -Ipvt [N]; T := A[Indx(N,m,k)]; A [Indx(N,m,k)] := A [Indx(N,к,к)]; A [Indx(N,к,к)] := T; {Пропустить этот шаг, если ведущий элемент равен нулю.} if Т <> 0 then begin {Вычислить множители.} for i :='к+1 to N do A[Indx(N,i,k)] := -A[Indx(N,i,k)]/T; {Переставлять и исключать по столбцам.} for j := к+1 to N do beg i n T : = A [Indx(N,m,j)]; A[Indx(N,m, j)] := A[Indx(N,к,j)] ; A[Indx(N,k,j)] := T; if T <> 0 then for i := k+1 to N do A[Indx(N, i, j)] := A[Indx(N, i, j)] + A [Indx(N,i rк)] * T end {for j := k+1 to N} end {if T<> 0} end; {for к := 1 to N-l} (Cond = (1-норма матрицы А)*(оценка для 1-нормы матрицы, обратной к А) Оценка получается посредством одного шага метода обратных итераций ь.ля наименьшего сингулярного вектора. Это требует решения двух систем /равнений: (транспонированная для А) * Y = Е А * Z = Y, где Е - вектор из +1 и -1, выбранный так, чтобы максимизировать зна- чение Y. Оценка = (1-норма Z) / (1-норма Y)} {Решить систему (транспонированная для А) * Y = Е.} for k := 1 to N do begin Т := 0,л if к <> 1 then for i := 1 to k-1 do T := T + A [I ndx (N, i, к)] * Work[i] ; EK := 1; if T < 0 then EK. := -1; if A[Indx(N,k,k)] = 0 then exit; Work[k] := -(EK + T)/A[Indx(N,к,к)] end; к for kb := 1 to N-l do begin к := N - kb; T := 0; for i := k+1 to N do T := T + A [I ndx (N , i, к) ] * Work[k]; 297
Work [к] := T; m : = •Ipvt [к]; if m <> к then begin T := Work[m] ; Work [m] : = Work [k] ; Work[k] := T end end; Ynorm : = 0; for i := 1 to N do Ynorm := Ynorm + abs(Work[i]); { решить систему A * Z = Y } Solve (N, A, Work, Ipvt); Znorm := 0; for i := 1 to N do Znorm := Znorm + abs(Work [i]); { оценить обусловленность } .. t Cond := Anorm * Znorm / Ynorm; if Cond < 1 then Cond := 1 END {Decomp}; {==========} END. {Unit DecompSV} {===========} Следующая программа иллюстрирует работу с модулем, ее запуска на экране появятся такие результаты: Матрица А: 10 -7 0 -3 2 6 5-15 Оценка обусловленности = 12.607750778432 Вектор правых частей: 7 4 6 Вектор решения: -0.000000000 -1.000000000 1:000000000 Время счета даже для ПЭВМ без сопроцессора не npei секунды. PROGRAM Test_of_DecompSv; {$N+,E+} Uses DecompSv; const N = 3; A : array [1..N, 1..N] of RealType = ((10, -1, 0), (-3/2, 6), (5, -1,5)); 298
В : array [1..N] of RealType = (7, 4, 6); var Work : array [1..N] of RealType; Cond : RealType; Ipvt : array [1..N] of integer; i, j : integer; BEGIN writein (’Матрица A:’); for i := 1 to N do begin for j := 1 to N do write (A [i, j] : 6: 0); writein end; Decomp (N, A, Ipvt, Work, Cond); writein (’Оценка обусловленности = ’, Cond:15:12); if Cond+1 = Cond then writein (#7,’Матрица А в пределах машинной точности’, является вырожденной матрицей’) else . begin writein ('Вектор правых частей:'); for i := 1 to N do writein (b[i]:6:0); Solve (N, A, B, Ipvt); writein (’Вектор решения:’); for i := 1 to N do writein (b[i]:12:9) end END. {Program} ( 15.2. ИНТЕРПОЛЯЦИЯ В этом разделе описывается Турбр-Паскалевая реализация под- фограмм SPLINE и SEVAL из книги [8], с помощью которых по аданным интерполяционным узлам проводится кубическая сплайн- штерполяция функции. Подпрограммы (точнее, процедуру SPLINE и функцию SEVAL) гужно поместить в файл SPLINUNT.PAS. Необходимо учесть, что в модуле используется десятибайтная [рифметика с плавающей точкой, поэтому опция OPTIONS/ ^OMPILER/NUMERIC PROCESSING должна находиться в состоянии *087/80287, а если в Вашей ПЭВМ нет арифметического сопроцессо- ра, то в дополнение к этому опция OPTIONS/COMPILER/ -MULATION должна иметь значение ON (подробнее об опциях реды см. гл.З). Работа с модулем проходит в два приема. Сначала с помощью бращения к процедуре SPLINE готовятся вспомогательные массивы 299
коэффициентов, затем с помощью функции SEVAL можно высчс тать значение сплайна в нужной точке интерполяции. К функод SEVAL можно обращаться многократно без дополнительного вызо! процедуры SPLINE, если после последнего обращения к ней ц один из параметров ее вызова не изменялся. Обращение к процедуре SPLINE SPLINE (N, X, Y, В, C, D); Здесь N - выражение типа WORD, определяющее количество у лов интерполяции; необходимо, чтобы N>=2; X, Y - массивы длиной N и типа REALTYPE - соответственно а( сциссы и ординаты узлов; абсциссы должны быть строго возрастай щими; В, С, D - рабочие массивьь длиной по N элементов каждый типа REALTYPE. Обращение к функции SEVAL SEVAL (N, Ur X, Y, В, С, D); Здесь U - переменная типа REALTYPE, определяющая абсцисс интерполяционной точки; необходимо, чтобы X[1]<U<X[N]; N, X, Y, В, С, D - параметры обращения к процедуре SPLIN Перед обращением к SEVAL эти параметры не должны изменят 4ся. Функция SEVAL возвращает значение типа REALTYPE - ордин ту точки интерполяции. {============} UNIT SplinllNT; {============} {В этот модуль включены подпрограммы SPLINE и SEVEL из книги [8, ' 91...94]. При повторении текст этого модуля нужно поместить в фа SPLINUNT.PAS. Для повышения точности интерполяции используется десят байтная арифметика с плавающей точкой, определяемая типом REALTYPE EXTENDED. Если Ваша ПЭВМ не оснащена арифметическим сопроцессором и f хотите несколько увеличить скорость вычислений ценой ухудшения точност а также во всех других случаях, когда необходимо сменить базовый вещее венный тип .(например, в целях экономии памяти), Вы должны нужным образ изменить его определение, например: type RealType = real;} i {------------------------------------------------------} INTERFACE {$N+,E+} {Директивы компилятора для эмуляции сопроцессора. Эти д рективы можно убрать, если установить перед компиляцией соответствую!! опции среды Турбо-Паскаля.} type RealType = extended; {базовый вещественный тип} RealTypeArray = array [1..(2*MaxInt) div SizeOf(ReaIType)] of RealType; PROCEDURE Spline (N : word; var XI, Yl, BI, CI, DI); FUNCTION Seval (N : word; var U : RealType; var XI, Yl, BI, CI, DI) : RealType-; {------------------------------------------------------} IMPLEMENTATION 300
PROCEDURE Spline (N : word; var XI, Yl, BI, CI, DI); {Вычисляются коэффициенты B[i], C[i] и D[i], i = 1...N для кубиче- ского интерполяционного сплайна вида S(x) = Y [i]+B [i] *(Х-Х [i] )+С [i] *(Х-Х [i] )**2+С [i] *(Х-Х [i] )**3 для X[i] = X = X[i + 1] . ВХОДНАЯ ИНФОРМАЦИЯ: N - число заданных точек или узлов (N =2); X - абсциссы узлов в строго возрастающем порядке; Y - ординаты узлов. ВЫХОДНАЯ ИНФОРМАЦИЯ: В, С, D - массивы определенных выше коэффициентов сплайна. Если обозначить через Р символ дифференцирования, то Y[i] = S(X[i] ); B[i] = SP(X[i] ); C[i] = SPP(X[i] )/2; D[i] = SPPP(X[i])/6 (правосторонняя производная). С помощью сопровождающей функции SEVAL можно вычислять значения сплайна.} var X : RealTypeArray absolute XI; Y : RealTypeArray absolute Yl; В : RealTypeArray absolute BI; C : RealTypeArray absolute CI; D : RealTypeArray absolute DI; ib, i : integer; T : RealType; BEGIN {Spline} if N < 2 then exit; if N <> 2 then begin {Построить трехдиагональную систему: В = диагональ, С = наддиагональ, D = правые части. } D[l] := Х[2] - Х[1] ; С[2] := (Y [2] - Y [1] )/D[l] ; for i := 2 to N-l do begin D[i] := X[i + 1] - X[i] ; B[i] := 2 * (D[i-1] + D[i]); C[i + 1] (Y [i + 1] - Y [i] )/D[i] ; C[i] := C[i + 1] - C[i] end; {Граничные условия. Третьи производные в точках X[i] и X[N] вычисляются с помощью разделенных разностей.} В[1] := -D[l] ; В[N] := -D [N-1] ; С[1] := 0; С [N] := 0; if N = 2 then begin B[l] := (Y [2] - Y [2] ) / (X [2]-X [1] ) ; 301
C[l] 0; D[l] := 0; В[2] := B[l] ; С [2] := 0; 0[2] := 0; end else begin C[l] := С [3]/(X [4]-X [2])-С [2]/(X [3]-X [1]); C[N] := C[N-l]/(X[N]-X[N-2] )-C[N-2]/(X[N-l]- X[N-3] ); C[l] := C [1] *sqr (D [1] )/(X [4]-X [1] ) ; ' , C[N] := -C [N] *sqr (D [N-l] )/(X [N]-X [N-3] ) end; {прямой ход} . for i := 2 to N do begin T := D[i-1]/B[i-1] ; ' • B[i] := B[i]-T*D[i-l] ; C[i] := C[i]-T*C[i-l] end; {обратная подстановка} C [N] := C [N]/В [N] ; for ib := 1 to N-l do C[N-ib] := (C[N-ib]-D[N-ib]*C[N-ib+l] )/B[N-ib] ; {B C[i] теперь хранится значение SIGMA, определяемое в книге [8,4.4].' Вычислить коэффициенты полиномов.} B[N] := (Y[N]-Y[N-l])/D[N-l]+D[N-l]*(С[N-l]+2*С[N]); for 1 := 1 to N-l do B[i] := (Y [i+1]-Y [i] )/D[i]-D[.i]*(C [i+l]+2*C [i]); Q[i] := (C [i+1]-C [i] )/D [i] ; C[i] :» 3*C[i] end; • < . ; C[N] : = 3i*C[N] ; . ; '' . ; D[N] := 0 [N-l] ; end {if N <> 2}; END {Spline} FUNCTION Seval (N word; var U : RealType; . • , z. . var XI, Yl, BI, CI,. DI) : 'RealType; {Эта функция вычисляет значение кубического сплайна Seval = Y[i]+B[i]*(U-X[i])-bC[i]*(U-X[i])**2+D[i]*(U-X[i])**3, где X[i] < U < X[i+1]. Используется схема Горнера. Если U < Х[1], то берется значение i = 1, если U > X [N], то i = N. . ВХОДНАЯ ИНФОРМАЦИЯ: N - число заданных точек; U - абсцисса, для которой вычисляется значение сплайна; 302
X, Y - массивы заданных абсцисс и ординат; В, С, D - массивы коэффициентов сплайна, вычисленные программой SPLINE. Если по сравнению с предыдущим вызовом U не находится в том хе ин- тервале, то для отыскания нужного интервала используется двоичный по- иск.} var X : RealTypeArray absolute XI; Y : RealTypeArray absolute Yl; В : RealTypeArray absolute BI; C : RealTypeArray absolute CI; D : RealTypeArray absolute DI; DX : RealType; » j, k : integer; const 1 : integer = 1; BEGIN {Seval} if i >= N then i := 1; if U < X[i] then { Двоичный поиск } begin i := 1; □ :=₽ N + 1; repeat k := sqr(i+j); if U < X[k] then j := k-else i := k until j <- i+1 end; { Вычислить сплайн } DX := U - X[i] ; Seval := Y[i]+DX*(B[i]+DX*(C[i]+DX*D[i] )) END {Seval};-- {==========} END. {Unit SplinUNT} {===========} Следующая программа иллюстрирует технику работы с модулем SPLINUNT. В качестве примера интерполируется простая степенная функция у(х) = х*х*х, заданная в диапазоне 1<=х<=10. При интерполяции берутся сере- дины интервалов между узлами. В результате прогона программы на экран будет выведено: 1.50 3.37500 3.37500 2.50 15.62500 15.62500 3.50 42.87500 42.87500 4.50 91.12500 91.12500 5.50 166.37500 166.37500 6.50 274.62500 274.62500 7.50 421.87500 421.87500 8.50 614.12500 614.12500 9.50 857.37500 857.37500 303
PROGRAM Sp 1 ine_Interpolation; {$N+,E+} Uses SplinUNT; const N = 10; type SplineType = array [1..N] of RealType; var X, Y, В, C, D : SplineType; U : RealType; i : integer; BEGIN for i := 1 to N do begin X[i] := i; Y[i] := X[i]*X[i]*X[i] end; Spline (N, X, Y, В, C, 0); for i := 1 to N-l do begin U := i + 0.5; Writein (U:5:2, Seval (N,U,X,Y,В,C,D);12:5, U*U*U:12:5) , end END. {Program} 15.3. ВЫЧИСЛЕНИЕ ОПРЕДЕЛЕННЫХ ИНТЕГРАЛОВ Для вычисления определенных интегралов Вы можете использо вать процедуру QUANC8 или функцию SIMPSON из описываемой ниже модуля INTEGRAL. Процедура QUANCS-это Турбо-Паскалевый вариант одноимен ной Фортран-подпрограммы из книги [8, с.113.,.122], реализующе! алгоритм Ньютона-Котеса 8-го порядка с автоматическим определе нием количества подынтервалов, на которые будет разбиваться вео интервал интегрирования. Функция SIMPSON использует популяр ную формулу Симпсона 5-го порядка. Точность интегрирования п< этой формуле будет в общем случае зависеть от количества задавав мых пользователем подынтервалов. В процессе вычисления по алгоритму QUANC8 весь интервал определения интеграла [А, В] автоматически разбивается на подын тервалы [Xi, Xi+1], причем длина Hi каждого подынтервала не ос тается постоянной на всем интервале, но формируется с учете» свойств подынтегральной функции. Вот почему этот алгоритм назы вается адаптивным (QUANC8 - это аббревиатура английских сло1 Quadrature, Adaptive, Newton-Cotes’ 8-panel, т.е. квадратура, адаптш ная, Ньютона-Котеса, 8 элементарных отрезков). Подробное опис< ние алгоритма см. в [8]. Обращение к процедуре QUANC8 (F, А, В, AbsErr, RelErr, Result, ErrEst, NoFun, Flag) 304
Здесь F - имя функции типа FUNTYPE, в которой по заданному аргументу вычисляется подынтегральная функция; в модуле опреде- лен такой тип-функция: type FunType = Function (X : RealType) : RealType; Функции именно такого типа должны использоваться для интег- рирования с помощью процедуры QUANC8; А, В - выражения типа REALTYPE, определяющие соответствен- но нижний и верхний пределы интегрирования; ABSERR, RELERR - выражения типа REALTYPE, определяющие границы соответственно абсолютной и относительной погрешностей; любое из этих выражений не может быть отрицательным; RESULT - переменная типа REALTYPE, в которой процедура возвращает результат - приближение к интегралу; ERREST - переменная типа REALTYPE, оценивающая фактиче- ски полученную погрешность; NOFUN - переменная типа INTEGER, в которой процедура воз- вращает количество обращений к подынтегральной функции; FLAG - переменная типа REALTYPE, в которой процедура воз- вращает следующую диагностическую информацию: в целой части этой переменной возвращается количество подынтервалов, для кото- рых программа обнаружила отсутствие сходимости; в дробной части переменной указывается положение выявленной особенности подын- тегральной функции относительно границ интервала: часть интерва- ла справа от особенности.Если интегрируемая функция не имеет особенностей на интервале [А, В] и программа сумела достигнуть требуемой точности, значение переменной FLAG будет равно нулю. Однако если с помощью QUANC8 попытаться проинтегрировать функцию tg(x)/x на интервале [0, 2], то переменная FLAG будет иметь значение 242.214723. Это показывает, что для 242 подынтерва- лов не было сходимости (точнее, не была достигнута требуемая точность) и что трудности возникли при относительном значении X, равном 0.214723 от правой границы интервала. Нетрудно найти абсолютное значение X по формуле Х=В-(В-А) *0.214723=1.570554, что приблизительно соответствует pi/2 - именно в этой точке подынтег- ральная функция обращается в бесконечность. Функция SIMPSON реализует оценку интеграла по формуле: I=h*[f(A)+4*f(A+h)+2*f(A+2*h)+4*f(A+3*h) + + 2*f(A+4*h) +...+4*f(B-h)+f(B)] /3, где h=(B-A)/(2*N) - половина длины подынтервала; N - заданное пользователем количество подынтервалов. Обращение к функции Simpson (F, А, В, N) Здесь F, А и В аналогичны одноименным параметрам процеду- ры QUANC8; N - выражение типа INTEGER, означающее количество подын- тервалов. Функция возвращает значение типа REALTYPE, в котором содержится результат - оценка интеграла. Текст модуля необходимо поместить в файл с именем INTEGRAL.PAS. В качестве базового вещественного типа в модуле 305
используется тип EXTENDED, поэтому опция OPTIONS/COM PILER/ NUMERIC PROCESSING при компиляции модуля должн< иметь состояние 8087/80287, а для ПЭВМ без сопроцессора в до полнение к этому опция OPTIONS/COMPILER/EMULATION должн; быть установлена в состояние ON. Для обращения к подынтеграль ной функции в модуле используется механизм передачи имеш этой функции через процедурный тип, что требует дальней моделг вызова подпрограмм. Поэтому модуль и передаваемая ему подын тегральная функция должны компилироваться с опцией OPTIONS; COMPILER/FORCE FAR CALLS в состоянии ON. {============} UNIT Integral; {=============} {В этом модуле содержится процедура QUANC8 и подпрограмма-функци SIMPSON, с помощью которых можно вычислить значение определенного интег рала. Текст этого модуля следует поместить в файл INTEGRAL.PAS} {------------------------------------------------------------} INTERFACE {$N+,Е+,F+} {Директивы компилятора для эмуляции сопроцессора и даль ней модели вызова. Эти директивы можно убрать, если установить пере, компиляцией соответствующие опции среды Турбо-Паскаля.} . type RealType = extended; {базовый вещественный тип} FunType = Function (х : Rea.IType) : RealType; PROCEDURE Quanc8 (Fun : FunType; A, B, AbsErr, RelErr : RealType; var Result, ErnEst : RealType; var NoFun : integer; var Flag : RealType); FUNCTION Simpson(Fun : FunType; , A, В : RealType; N : integer) : RealType; {------------------------------------------------------------} IMPLEMENTATION FUNCTION Amaxl' (x, у : RealType) : RealType; '{Вспомогательная функция, имитирующая Фортран-функцию AMAX1} BEGIN if x > у then Amaxl := x else Amaxl := у END {Amaxl}; {-------------------} PROCEDURE QUANC8 (Fun : FunType; A, B, AbsErr, RelErr : RealType; var Result, ErrEst : RealType; var NoFun : integer; var Flag : RealType); {Оценить интеграл для FUN(X) от А до В с заданной пользователем точ- ностью. Автоматическая адаптивная программа, основанная на формуле Ньютона- Котеса 8-го порядка. ВХОДНАЯ ИНФОРМАЦИЯ: FUN - подынтегральная функция; А - нижний предел интегрирования; 306
В г верхней предел (может б^ть меньше А); RELERR - граница относительной погрешности (RELERR >а 0); ABSERR. - граница абсолютной погрешности (ABSERR >= 0). ВЫХОДНАЯ ИНФОРМАЦИЯ: RESULT - приближение к интегралу, удовлетворяющее, можно надеяться, менее жесткой из двух границ погрешности; ERREST - оценка действительной ошибки; NOFUN 4 - число значений функции, использованных при вычислении RESULT; FLAG - индикатор надежности. Если FLAG равен нулю, до RESULT, вероятно, удовлетворяет заданной границе погрешности. Если FLAG = XXX.YYY, то XXX - число интервалов, для которых не было сходимости, a O.YYY - часть основного интервала, остав- шаяся для обработки в тот момент, когда программа приблизилась к пре- дельному значению для NOFUN..} const ww = 14175; var w0, wl, w2, w3, w4, Area, xO, fO, stone, Step, Corll, Temp, qprev, qnow, qdiff, qleft, esterr, tolerr : RealType; qright : array [1..31] of Realtype; f, x : array [1..16] of RealType; fsave, xsave : array [l.\8, 1..30]sOf RealType; levmin, levmax, levout, nomax, nofin, lev, nim, i, j : integer; label 30, 50, 60, 62, 70; BEGIN {Quanc8} {Этап 1. Присвоение начальных значений переменным, не зависящим от интервала. Генерирование констант.} levmin := 1; levmax := 30; levout := 6; nomax ':= 5000; nofin := nomax-8*(levmax-levout+ round(exp((levout+1)* In(2)))); {Если NOFUN достигает значения NOFIN, то тревога.} w0 := 3956/ww; wl := 23552/ww; . w2 :=-3712/ww; w3 := 41984/ww; w4 :=-18160/ww; {Присвоить нулевые значения переменным суммам.} Flag := 0; Result := 0; corll := 0; ErrEst := 0; area : = 0; NoFun := 0; if A = В then exit; {Этап 2. Присвоение начальных значений переменным, зависящим от ин- тервала, в соответствии с первым интервалом.} lev := 0; nim := 1; х0 := А; Х[16] := В; 307
qprev := 0; fO := Fun(xO); stone := (B - A) / 16; X [8] := (xO + X[16]) / 2; X [4] := (xO + X [8]) / 2;- X [12] := (X [8] + X [16] ) / 2; X[2] := (xO + X [4]) I 2; X[6] := (X[4] + X[8]) / 2; X[10] := (X [8] + X[12]) / 2; X[14] := (X[12] + X[16]) / 2; for j := 2 to 16 do if not odd(j) then F[j] := Fun(X[j]); NoFun := 9; - {Этап 3. Основные вычисления. Требуются qprev, xO, х2, х4.;.х16, f0, f2...fl6. Вычисляются xl, х3..х15, fl, f3...fl5, qleft, qright, qnowK qdiff, area.} 30: X[1] := (xO + X[2]) / 2; F[1] := Fun(X [1]); for j := 3 to 15 do if oddjj) then begin X[j] := (X [j-1] + X [j+1] ) I 2; F[j] := Fun(X[j]) end; NoFun := NoFun + 8; Step := (X[16] - xO) / 16; qleft := (w0*(f0+F[8]) + wl*(F[1] +F[7]) + w2*(F[2]+F [6]) + w3*(F [3] +F[5]) + w4*F[4]) * Step; qright[lev+1] := (wO*(F[8]+F [16]) + wl*(F[9]+F [15]) + w2*(F [10] +F [14]) + w3*(F[ll]+F[13]) + w4*F[12]) * Step; qnow := qleft + qright [lev+1]; qdiff := qnow - qprev; area := area + qdiff; {Этап 4. Проверка сходимости для интервала.} esterr : = abs(qdiff) / 1023; tolerr := Amaxl(AbsErr, RelErr*abs(area)) * (Step/stone); if lev < levmin then goto 50; if lev >= levmax then goto 62; if NoFun > nofin then goto 60; if esterr <= tolerr then goto 70; . {Этап 5. Сходимости нет. Установить следующий интервал.} 50: Nim := 2 * nim; lev := lev + 1; {Запомнить элементы, относящиеся к правой половине интервала, дл будущего использования.} for i := 1 to 8 do begin fsave[i,lev] := F [i+8]; xsave [i,lev] := X [i+8] end; 308
{Собрать элементы, относящиеся к левой половине интервала, для немедленного использования } qprev : = qleft; for i := 1 to 8 do begin j := -i; F [2*j+18] := F[j+9]; , X [2*j+18] := X[j+9] end; goto 30; {Этап 6 "пожарный”. Число значений функции близко к тому,чтобы превысить установленный предел.} 60: nofin := 2 * nofin; levmax := levout; Flag := Flag + (В - xO) / (B - A); goto 70; {Текущее предельное значение глубины деления пополам равно LEVMAX} 62: Flag := Flag + 1; {Этап 7. Сходимость для интервала имеет место. Прибавить очередные слагаемые к переменным суммам.} 70: Result := Result + qnow; ErrEst := ErrEst + esterr;. corll := corll + qdiff / 1023; {Установить следующий интервал.} while nim <> 2*(nim div 2) do begin nim := nim div 2; lev := lev - 1 ' end; nim := |iim + 1; v if lev > 0 then begin {Собрать элементы, необходимые для следующего интервала.} qprev :« qright[lev]; х0 := X[16] ; f0 := F[16] ; for i := 1 to 8 do begin F[2*i] := fsave[i, lev] ; X[2*i] := Xsave[i,lev] end; goto 30 • end {if lev > 0}; {Этап 8. Заключительные операции и выход.} Result := Result + corll; {Обеспечить, чтобы значение переменной ERREST было не меньше уровня округления.} if ErrEst = 0 then exit; repeat 309
temp := abs(Result) + ErrEst; if temp <> abs(Result) then exit; ErrEst := 2 * ErrEst until false END {Quanc8}; {-------------------------------} FUNCTION Simpson(Fun : FunType; A, В : RealType; N : integer) : RealType; {Вычисление определенного интеграла по формуле Симпсона. ВХОДНАЯ ИНФОРМАЦИЯ: FUN - подынтегральная функция; А, В - границы интервала интегрирования; • ' N - количество подынтервалов, на которые разбивается интер- вал интегрирования. ВЫХОДНАЯ ИНФОРМАЦИЯ: SIMPSON - значение этой функции есть вычисленный по формуле Симпсона определенный интеграл.} var ' Н, I : RealType; k : integer; BEGIN {Simpson} Н := ((B-A)/N)/2; I := Fun(A);1 for к := 1 to 2*N-1 do : if odd(k) then I := I + 4*Fun(A+k*H) , else I := I + 2*Fun(A+k*H); Simpson := (I + Fun(B))*H/3 END {Simpson}; {=============} END. {Integral} {=============} Специфику использования процедуры QUANC8 и функции SIMPSON иллюстрирует следующий пример, в котором вычисляет ся интегральный синус на интервале [0, 2]. В результате прогона на экран будет выведено: QUANC8=1.6054129768 ErrEst?5.67330248232994Е-0017 NoFun=33 Simpson=l.6054129779 Время счета - около одной секунды. PROGRAM QUANC8_Simpson_Test; {$N+,Е+,F+} Uses Integral; FUNCTION Fun (x : RealType) : RealType; BEGIN if .x = 0 then Fun := 1 else Fun := sin(x)/x 'END; var A, B, AbsErr, RelErr, Result, ErrEst, Flag : RealType; 310
NoFun : integer; BEGIN A := 0; В := 2; RelErr := IE-10; AbsErr := 0; Quanc8(Fun,A,B,AbsErr,RelErr,Result,ErrEst,NoFun,Flag); writein ('QUANC8 =', Resu It: 12 :10, ’ ErrEst = Errest, ' NoFun = ’, NoFun); writein (’Simpson =’, Simpson(Fun, A, B, NoFun):12:10) END.{Program} 15.4. ЧИСЛЕННОЕ ИНТЕГРИРОВАНИЕ СИСТЕМ ОБЫКНОВЕННЫХ ДИФФЕРЕНЦИАЛЬНЫХ УРАВНЕНИЙ Для решения задачи Коши - интегрирования систем обыкновен- ных дифференциальных уравнений при заданных начальных усло- виях - в книге [8] приводится тщательно продуманная программа RKF45, которая, без преувеличения, стала едва ли не самой попу- лярной среди подобного рода программ в среде отечественных про- граммистов. Главные достоинства программы - эффективный конт- роль локальной погрешности и повышенная точность метода. Эту программу выгодно использовать для решения систем с умеренной жесткостью (до коэффициента жесткости 500...1000): на гладких уча- стках решения программа обычно сильно увеличивает шаг и ока- зывается в конце концов более быстрой, чем, например, широко используемые программы методов Рунге-Кутта без автоматического изменения шага. Однако в большинстве случаев интегрирование со- провождается накоплением промежуточных результатов для после- дующего построения графиков решения (так обычно бывает в зада- чах имитационного моделирования). Искусственное ограничение шага интегрирования шагом накопления промежуточных результа- тов сводит на нет главное преимущество программы и резко уменьшает ее эффективность. Вот почему в описываемый в этом разделе модуль SIMULA включены две процедуры - RKF45 и RUNGE. Первая является простым переводом на Турбо-Паскаль од- ноименной подпрограммы из книги [8, с. 146...164], вторая реализует классическую схему Рунге-Кутта с фиксированным шагом. Алгоритм Рунге-Кутта-Фельберга, положенный в основу програм- мы RKF45, относится к группе так ’ называемых вложенных явных методов интегрирования. В нем на каждом шаге интегрирования осуществляется шестикратное обращение к процедуре вычисления правых частей уравнений. Полученные 6 значений производных за- тем суммируются с учетом весовых коэффициентов таким образом, что получается явный метод 5-го порядка. При этом 4 производ- ные, взятые с другими весовыми коэффициентами, образуют вло- женный явный метод 4-го порядка, а разница в решениях, получае- мых обоими методами, используется для оценки локальной по- грешности. В процедуре RUNGE реализуется алгоритм метода Рунге-Кутта 4-го порядка по схеме 311
Y(t+h) = Y(t)+h*(kl + 2*k2+2*k3+k4)/6, где kl = f(t); k2=f(t + h/2, Y(t)+kl*h/2); k3=f(t+h/2. Y(t) + k2*h/2); k4=f(t + h, Y(t) + k3*h). • В модуле SIMULA используется базовый вещественный тип type RealType = extended обеспечивающий наивысшую возможную точность вычислений. При компиляции модуля и программы пользователя опция OPTIONS/ COMPILER/NUMERIC PROCESSING должна иметь значение 8087/ 80287, а если Ваша ПЭВМ не имеет арифметического сопро- цессора, то, кроме того, опция OPTIONS/COMPILER/EMULATION должна быть в состоянии ON. Для вычисления производных обе процедуры используют обра- щение к; составленной пользователем процедуре, имя которой пере- дается через формальный параметр процедурного типа PROCTYPE: type РгосТуре = Procedure (Т : RealType; var Y, YP) Здесь T - текущее значение независимой переменной; Y - массив из NEQN элементов типа REALTYPE, содержащий вектор состояния системы для данного значения Т; YP - массив из NEQN элементов типа REALTYPE, куда процеду- ра должна поместить вычисленный вектор производных. В модуле используется описанный в гл. 9 механизм передачи одномерных массивов произвольной длины с помощью нетипизиро- ванных параметров с последующим совмещением их в памяти с локальными псевдопеременными путем использования зарезервиро- ванного слова ABSOLUTE (см. с.164). Таким же способом передают- ся и два массива Y и DY в процедуру пользователя. Внутри проце- дуры пользователь должен определить локальные одномерные мас- сивы с элементами типа REALTYPE и длиной NEQN каждый. По- скольку количество NEQN уравнений пользователю известно, это сделать несложно, например: Uses Simula; const NE = 5; {количество уравнений} ' Procedure Right(T : RealType; var YY, YP); var Y : array [1..NEJ of RealType absolute YY; DY : array [1..NE] of RealType absolute YP; Теперь в процедуре RIGHT вычисления правых частей уравне- ний можно использовать, как обычно, массив Y начальных условий и массив DY производных. Еще раз напомню, что локальные пере- менные; совмещаемые с нетипизированными формальными пара- метрами, не требуют для своего размещения дополнительной памя- ти, так что длина массивов Y и DY в предыдущем примере может 312
быть какой угодно. Однако в целях контроля возможного наруше- ния границ диапазона индексными выражениями желательно объ- являть истинные размеры массивов. Поскольку процедурам RKF45 и RUNGE передается имя проце- дуры пользователя, модуль SIMULA и программа пользователя дол- жны компилироваться с установленным значением ON опции OPTIONS/COMPILER/FORCE FAR CALLS. {==============} UNIT Simula; {=============} {В модуль включены две процедуры для численного решения систем обык- новенных дифференциальных уравнений произвольного порядка: процедура RKF45, реализующая алгоритм Рунге-Кутта-Фельберга 4...5-го порядка с ав- томатическим выбором шага, и процедура RUNGE, работающая по классической схеме Рунге-Кутта 4-го порядка с фиксированным шагом. При повторении текст модуля нужно поместить в файл SIMULA.PAS.} {-------------------------------------------------------} INTERFACE {$N+,E+,F+} { Директивы компилятора, устанавливающие работу с ариф- метическим сопроцессором и дальнюю модель вызова подпрограмм.. Эти дирек- тивы можно удалить, если будут установлены соответствующие опции в среде Турбо-Паскаля.} type RealType = extended; {базовый вещественный тип} const ArraySize = (2*MaxInt) div SizeOf(ReaIType); type ArrayType = array [1..ArraySize] of RealType; ProcType = procedure (T : RealType; var Y, YP); PROCEDURE RKF45(F : ProcType; ; {процедура вычисления правых частей системы дифференциальных уравнений} NEQN : integer; {количест/во дифференциальных var Y; var T : RealType; уравнений} {при входе - начальные условия, при выходе - вектор решения} {при входе - начальная точка Tout : RealType; интегрирования, при выходе - достигнутое значение незави- симой переменной; обычно при выходе Т = TOUT} ; {конечная точка интегрирования} var RelErr : RealType; ; {верхняя граница относительной AbsErr : RealType; погрешности} : {верхняя граница абсолютной var Iflag : integer; погрешности} {указатель режима интегриро- var Work, Iworkl вания} {рабочие массивы}); 313
PROCEDURE Runge(F : ProcType; {процедура вычисления правых частей системы дифференци- 4 альных уравнений} NEQN : integer; {количество дифференциальных уравнений} 4 var YI; {при входе - начальные условия, при выходе - вектор решения} var Т : RealType; {при .входе - начальная точка интегри- рования, при выходе - достигнутое значение независимой перемен- ной;,обычно при выходе Т = TOUT} Tout : RealType; {конечная точка интегрирования} Н : ReplType; {шаг интегрирования} var Work {рабочий массив длиной 3*NEQN}); {----------------------------------------------.-------------} IMPLEMENTATION FUNCTION Amaxl(x, у : RealType) : RealType; {Имитация соответствующей Фортран-функции} BEGIN if х > у then Amaxl :• = x else Amaxl := у END {Amaxl}; {-----------------------------------------------------------} {Метод Рунге-Кутта-Фельберга 4...5-го порядка. Составители програм- мы: Н.A.WATTS, L.F.SHAMPINE. SANDIA LABORATORIES, ALBUQUERQUE, NEW MEXICO.} t PROCEDURE RKF45(F : ProcType; {процедура вычисления правых частей дифференциальных уравнений} Neqn : integer; {количество дифференциальных уравнений} var Y; {при входе - начальные условия, при выходе - вектор решения} var Т : RealType; {при входе - начальная точка интегри- рования, при выходе - достигнутое z значение независимой переменной; обычно при выходе Т = Tout} Tout : RealType; {конечная точка интегрирования} var RelErr : RealType; {верхняя граница относительнай погрешности} AbsErr : RealType; {верхняя граница относительной ' погрешности} var If lag : integer; {указательрежима интегрирования} var Work, Iworkl); рабочие массивы} var W : ArrayType absolute work; IWork : array [1..5] of integer absolute IWorkI; kl, k2, k3, k4, k5, k6, klm : integer; {Процедура RKF45 - это промежуточная программа, которая просто со- кращает для пользователя длинный список вызова путем расщепления двух рабочих массивов. В действительности RKF45 - это программа интерфейса, 314
которая вызывает процедуру RKFS, осуществляющую процесс решения. RKFS, в свою очередь, вызывает процедуру FEHL, которая вычисляет решение на один шаг. Тем не менее, везде дальше вся процедура Рунге-Кутта-Фельберга, ре- ализованная в этом модуле, называется RKF45. RKF45 предназначена главным образом для умеренно жестких дифференци- альных уравнений, когда вычисление производных не связано со значитель- ными затратами времени. RKF45, вообще говоря, не следует использовать, если требуется высокая точность. Процедура RKF45 применяется для интегрирования от Т до TOUT, однако ее можно использовать и как одношаговый интегратор, чтобы продолжить ре- шение на один шаг в направлении TOUT. На выходе параметрам, фигурирующим в списке вызова, присваиваются значения, необходимые для продолжения ин- тегрирования. Пользователю нужно лишь еще раз обратиться к RKF45 и, воз- можно, определить новое значение для TOUT. RKF45 использует метод Рунге-Кутта-Фельберга, описанный в следующей публикации: E.FEHLBERG. LOW-ORDER CLASSICAL RUNGE-KUTTA FORMULAS WITH STEPSIZE CONTROL, NASA TR R-315. Стиль работы RKF45 иллюстрируется в следующей публикации: L.E. SHAMPINE, Н.A.WATTS, S.DAVENPORT. SOLVING NON-STIFF ORDINARY DIFFERENTIAL EQUATIONS - THE STATE OF THE ART. SANDIA LABORATRIES REPORT SAND75-0182, SIAM REVIEW, 18 (1976), N3, 376-411. ПАРАМЕТРЫ ПРОГРАММЫ: F - процедура F(T, Y, YP) для вычисления производных YP[i] = dY[i]/dT; NEQN - число интегрируемых уравнений; Y[*J - решение в точке Т; Т - нез-ависимая переменная; TOUT - точка выхода, в которой нужно определить решение; RELERR, ABSERR - границы относительной и абсолютной погрешности для теста локальной ошибки. На любом шаге программа требует выполнения усло- вия АЬз(Локальная ошибка) = RelErr * Abs(Y[i]) + AbsErr для каждого компонента векторов локальной ошибки и решения; IFLAG - указатель режима интегрирования; WORK - массив, содержащий внутреннюю для RKF45 информацию, которая необходима при последующих вызовах. Его размерность должна быть не мень- ше 3 + 6*NEQN; IW0RK - целочисленный массив, содержащий внутреннюю для RKF45 инфор- мацию, которая необходима для последующих вызовов. Его размерность долж- на быть не меньше 5. ПЕРВОЕ ОБРАЩЕНИЕ К RKF45. Пользователь должен предусмотреть в своей вызывающей программе память для следующих фигурирующих в списке вызова массивов: Y[l..NEQN], WORK[1..3+6*NEQN], IWORK[1..5] (массивы Y и WORK типа REALTYPE, массив IWORK типа INTEGER). Кроме того, нужно подготовить процедуру F(T, Y, YP)’ и присвоить начальные значения параметрам: NEQN - число интегрируемых уравнений (NEQN = 1); выражение типа INTEGER; Y [*] - вектор начальных условий (тип REALTYPE); Т - начальная точка интегрирования (переменная типа REALTYPE); TOUT -выражение типа REALTYPE : точка выхода, в которой нужно найти 315
решение; Т = TOUT возможно лишь при первом обращении; в этом случае, ес- ли можно продолжать интегрирование, то выход из RKF45 происходит со зна- чением параметра IFLAG « 2; RELERR, ABSERR - границы относительной и абсолютной погрешностей. Эти границы должны быть неотрицательны. RELERR должна быть переменной типа REALTYPE, a ABSERR может быть и константой (выражением) того же ти- па. Не следует задавать границу для относительной погрешности меньше, чем 1Е-8, дабы избежать трудностей, связанных с очень высокими запросами к точности. Программа требует, чтобы RELERR была больше, чем некоторый параметр относительной ошибки, вычисляемый внутри программы и зависящий от особенностей машины1. В частности, не разрешается задавать только аб- солютную ошибку. Если же задано значение RELERR, меньшее допустимого, то RKF45 увеличивает RELERR надлежащим образом и возвращает управление пользователю, прежде чем продолжать интегрирование; IFLAG - при входе этот параметр служит указателем режима интегриро- вания, при выходе в нем помещается некоторая диагностическая информация. Нормальное входное значение параметра равно +1. Пользователь должен за- давать IFLAG а -1 лишь в том случае, когда применяется режим пошагового интегрирования. При этом RKF45 пытается продолжить решение на один шаг в направлении TOUT при каждом очередном вызове. Поскольку этот режим рабо- ты весьма неэкономичен, его следует использовать лишь в случае крайней необходимости. ИНФОРМАЦИЯ НА ВЫХОДЕ: Y[*J - решение в точке Т; 1 Т - последняя точка, достигнутая при интегрировании; IFLAG = 2 - при интегрировании достигнуто TOUT. Это значение указы- вает на успешный выход и является нормальным режимом дла продолжения ин- тегрирования. IFLAG =-2 - предпринят один успешный шаг в направлении TOUT. Это нормальный режим для продолжения пошагового интегрирования. IFLAG = 3 - интегрирование не закончено из-за того, что заданное значение RELERR оказалось слишком мало. Для продолжения интегрирования RELERR был надлежащим образом увеличен. IFLAG = 4 - интегрирование не закончено из-за того, что потребова- лось более 3000 вычислений производной. Это соответствует приблизительно 500 шагам. IFLAG = 5 - интегрирование не закончено из-за того, что решение об- ратилось в нуль, вследствие чего тест только относительной ошибки не ра- ботоспособен. Для продолжения необходимо ненулевое значение параметра ABSERR. Использование на один шаг режима пошагового интегрирования явля- ется в этой ситуации разумным выходом из положения. IFLAG = 6 - интегрирование не закончено из-за того, что требуемая точность не могла быть достигнута даже при наименьшем допустимом шаге. х3амечу, что этот параметр равен 1Е-12 плюс удвоенное значение машинного эп- силон. При работе с сопроцессором или при его эмуляции машинное эпсилон (мини- мальное значение вещественного типа, которое после прибавления к единице еще дает результат, отличный от единицы) составляет 5.42101Е-20, при работе без сопроцессора с REALTYPE=REAL эта величина будет 9.094947Е-13. Хотя эти значения, разумеется, постоянны для всех IBM-совместимых ПЭВМ, при переносе программы на Турбо-Пас- каль я решил сохранить имеющуюся в оригинале [8] процедуру вычисления машинного эпсилон именно из-за этого отличия. 316
Пользователь должен увеличить границу погрешности, прежде чем можно бу- дет попытаться продолжить интегрирование. IFLAG = 7 - по всей видимости, RKF45 неэффективна при решении этой задачи. Слишком большое число требуемых выходных точек препятствует вы- бору естественной длины шага. Следует использовать режим пошагового ин- тегрирования. IFLAG = 8 - неправильное задание входных параметров. Это значение появляется, если допущена одна из следующих ошибок: NEQN <= О (Т = TOUT) and ((IFLAG = 1) or (IFLAG < -1)) (RELERR < 0 ) or (ABSERR < 0) (IFLAG = 0) or (IFLAG < -2) or (IFLAG > 8) W0RK[*], IW0RK[*] - информация, которая обычно не представляет инте- реса для пользователя, но необходима при последующих вызовах. WORK[1]...WORK[NEQN] содержат первые производные вектора решения Y в точке Т. WORK[NEQN+1] хранит величину шага Н, с которой можно попытаться выполнить следующий шаг. IWORK[1] содержит счетчик числа вычислений про- изводных. ПОСЛЕДУЮЩИЕ ОБРАЩЕНИЯ К RKF45. На выходе процедуры RKF45 имеется вся информация, необходимая для продолжения интегрирования. Если при интег-. рировании было достигнуто TOUT, то пользователю достаточно определить новое значение TOUT и снова обратиться.к RKF45. В режиме пошагового ин- тегрирования (IFLAG = -2) пользователь должен иметь в виду, что каждый шаг выполняется в направлении TOUT. По достижении TOUT (сигнализируемом изменением IFLAG на +2) пользователь должен задать новое значение TOUT и переопределить IFLAG на -2, чтобы продолжить в режиме пошагового интег- рирования . Если интегрирование не было закончено, но пользователь хочет его продолжить (случай IFLAG = 3 или 4), он попросту снова обращается к RKF45. При IFLAG = 3 параметр RELERR к этому моменту уже изменен про- граммой надлежащим образом. В случае IFLAG = 4 счетчик числа значений функции будет переопределен на 0 и будут, разрешены еще 3000 вычислений функции. Однако при IFLAG = 5, прежде чем будет продолжено интегрирование, пользователь должен сначала изменить критерий ошибки, задав положитель- ное значение для ABSERR. Если он не сделает этого, выполнение программы будет прекращено. Точно так же при IFLAG = 6, прежде чем продолжить интегрирование, пользователю необходимо переопределить IFLAG на 2 (или -2, если исполь- зуется режим пошагового интегрирования) и увеличить значение ABSERR и/или RELERR. Если этого не будет сделано, выполнение программы прекра- щается. Появление IFLAG = 6 указывает на нерегулярность (решение быстро меняется или, возможно, имеется особенность интегрируемой системы урав- нений), и часто в подобных случаях не имеет смысла продолжать интегриро- вание. Если будет получено IFLAG = 7, то пользователь должен перейти к ре- жиму пошагового интегрирования с шагом, определенным программой, или рассмотреть возможность перехода на программы, использующие методы Адам- са. Если все же пользователь хочет продолжить интегрирование по програм- 317
ме RKF45, он должен до нового обращения к ней переопределить IFLAG на 2, в противном случае выполнение программы будет прекращено. Если получено значение IFLAG = 8, то интегрирование нельзя продол- жать, пока не будут исправлены ошибочные входные параметры. Нужно отметить, что массивы WORK и IW0RK содержат информацию, необ- ходимую для дальнейшего интегрирования, поэтому в данные массивы нельзя вносить изменения.} {------------------} PROCEDURE RKFS(F : РгосТуре; NEQN : integer; var Yl; var T : RealType; Tout, RelErr, AbsErr : RealType; var Iflag : integer; var YPI, HI, F1I, F2I, F3I. F4I, F5I; var SaveREI, SaveAEI, NFEI, КОР I; var Initl, JflagI, KflagI); var Y : ArrayType absolute Yl; YP : ArrayType absolute YPI; H : RealType absolute HI; Fl : ArrayType absolute F1I; F2 : ArrayType absolute F2I; F3 : ArrayType absolute F3I; F4 : ArrayType absolute F4I; F5 : ArrayType absolute F5I; SaveRE : RealType absolute SaveREI; SaveAE : RealType absolute S^aveAEI; NFE : integer absolute NFEI; KOP : integer absolute KOPI; Init : integer absolute Initl; Jflag : integer absolute JflagI; Kflag : integer absolute KflagI; {МЕТОД РУ'НГЕ-КУТТА-ФЕЛЬБЕРГА 4... 5-го ПОРЯДКА. RKFS интегрирует систему обыкновенных дифференциальных уравнений 1- го порядка (см. комментарий к процедуре RKF45). Массивы YP, Fl, F2, F3, F4 и F5 размерности NEQN и переменные Н, SaveRE, SaveA^, NFE, КОР, Init, Jflag и Kflag вынесены в список вызова, чтобы сохранить их определенность при повторном обращении. Поэтому их значения не должны изменяться пользователем. Определенный интерес представляют параметры: YP - производная вектора решения в точке Т; Н - предполагаемый размер шага для очередного этапа; NFE - счетчик количества вычислений функции;} const REMin = IE-12; {REMin - это минимально допустимое значение для RelErr. Попытки получить по этой программе более высокую точность обычно стоят очень дорого и зачастую безуспешны.} MaxNFE = 3000; {Стоимость счета контролируется требованием, чтобы количество вычислений функции было ограничено приблизительно значением константы MaxNFE. 318
Принятое значение этой константы соответствует примерно 500 шагам.,} var А, АЕ, DT, ЕЕ, ЕЕОЕТ, EstTol, ЕТ, Hmin, RER, S, Scale, Tol, TolN, U26, EPS, YPK : RealType; k, Mflag : integer; Hfaild, Output : Boolean; label 10, 40, 45, 50, 60, 65, 80, 100, 200; {--------------------} PROCEDURE FEHL (F : ProcType; NEQN : integer; var Yl; ' var T : RealType; H : RealType; var YPI, F1I, F2I, F3I, F4I, F5I, SI); var Y : ArrayType absolute Yl; YP : ArrayType absolute YPI; Fl : ArrayType absolute F1I; F2 : ArrayType absolute F2I; F3 : ArrayType absolute F3I; F4 : ArrayType absolute F4I; F5 : ArrayType absolute F5I; S : ArrayType absolute SI; {Процедура FEHL реализует алгоритм метода численного интегрирования Рунге-Кутта-Фельберга 4...5-го порядка и предназначена для интегрирова- ния системы из NEQN обыкновенных дифференциальных уравнений 1-го порядка следующего вида: dY[i]/dT = F(T, Т [1]................Y [NEQN]) ,- где начальные значения Y[i] и начальные производные YP[i] .заданы в точке Т; FEHL продолжает решение на фиксированный шаг Н и помещает в массив S [*] приближение к решению в точке Т+Н, имеющее 5-й порядок точности (локальный порядок равен шести); F1,...,F5 - массивы размерности NEQN, необходимые внутри программы. В формулах произведена группировка с целью уменьшить потерю верных зна- ков. Чтобы можно было различать разные независимые аргументы, при обра- щении к FEHL не следует задавать для Н значения, меньшие умноженной на 13 ошибки в округлении Т.} var CH : RealType; k : integer; BEGIN {FEHL} CH := H/4; for к := 1 to NEQN do F5[k] := Y[k] + CH*YP[k] ; F(T+CH, F5, Fl); CH := 3*H/32; for к := 1 to NEQN do F5[k] := Y[k] + CH*(YP[k] + 3*F1 [k] ); F(T+3*H/8, F5, F2); CH := H/2197; 319
for к := 1 to NEQN do F5[k] := Y [k]+CH*( 1932*YP [k] + ( 7296*F2 [k]-7200*Fl [k] )); F(T+12*H/13, F5, F3); CH := H/4104; for к := 1 to NEQN do F5[k] := Y[k] + CH*((8341*YP [k] - 845*F3 [k]) + (29440*F2[kj - 32832*F 1 [k] )) ; F(T+H, F5, F4); CH := H/20520; for к := 1 to NEQN do Fl[k] := Y[k]+CH*((-6080*YP[k] + (9295*F3[k]-5643*F4[k] )) + (41O4O*F1[k]-28352*F2[k])); F(T+H/2, Fl, F5); { Вычислить приближенное решение в точке Т+Н } CH := Н/7618050; for к := 1 to NEQN do S[k] := Y[k] + CH*( (902880*YP [к] + (3855735*F3 [к] - 1371249*F4 [k] )) + (3953664*F2 [k] + 277020*F5 [k] )) END {FEHL}; {-----r-----------..} BEGIN {RKFS} {Проверить входные параметры:} Mflag := abs(Iflag); if (NEQN < 1) or (RelErr < 0) or (AbsErr < 0) or (Mflag = 0) or (Mflag > 8) then {Ошибка во входной информации:} begin 10: Iflag := 8; exit end; if Mflag = 1 then {Первый вызов: вычислить машинное эпсилон.} begin EPS := 1; while ,1+EPS <> 1 do EPS := EPS/2; U26 := 26*EPS end else {He первый вызов: проверить возможность продолжения.} begin if (Т = Tout) and (Kflag <> 3) then goto 10; if Mflag = 2 then begin xif (Kflag = 3) or (Init = 0) then goto 45; if Kflag = 4 then goto 40; if (Kflag « 5) and (AbsErr = 0) or (Kflag = 6) and (RelErr <= SaveRE) and (AbsErr <= SaveAE) then halt; goto 50 end 320
else begin if (Iflag = 3) or (Iflag = 5) and (AbsErr > 0) then goto 45; if Iflag = 4 then goto 40; halt end; {Переопределить счетчик числа вычислений функции.} 40: NFE := 0; if Mflag = 2 then goto 50; {Переопределить значение IFLAG, установленное при предыдущем обращении.} 45: Iflag := Jflag; if Kflag = 3 then Mflag := abs(If lag) end; {не первый вызов} {Сохранить входное значение IFLAG и установить значение KFLAG для будущей входной проверки.} 50: Jflag := Iflag; Kflag := 0; {Сохранить значения RELERR и ABSERR для последующей проверки.} SaveRE := RelErr; SaveAE := AbsErr; {Установить значение границы для относительной погрешности, равное как минимум 2*EPS+REMIN, чтобы избежать трудностей, связанных с требованием недостижимой точности.} RER := 2*EPS + REMin; if RelErr < RER then {Заданная граница относительной погрешности слишком мала.} begin RelErr := RER; Iflag := 3; Kflag := 3; exit end; DT := Tout - T; if Mflag = 1 then goto 60; if Init = 0 then goto 65; goto 80; {Присвоение начальных значений (инициирование): - установить значение указателя окончания инициирования INIT; - установить значение указателя слишком большого затре- бованного числа выходных точек КОР; - установить значение счетчика числа вычислений функции NFE; - оценить начальную длину шага.} 60: Init := 0; И - Фаронов 321
КОР := О; А := Т; F (A, Y, YP); NFE := 1; if Т = Tout then begin If lag := 2; exit end; 65: Init := 1; H := abs(DT); TolN := 0; for к := 1 to NEQN do begin Tol := RelErr*abs(Y[k]) + AbsErr; if Tol <> 0 then begin TolN := Tol; YPK := abs(Y[k]); if YPK*H*H*H*H*H > Tol then H : = exp(0.2*ln(Tol/YPK)) end end; if TolN <= 0 then H := 0; H : = Amaxl(H, U26*Amaxl(abs(T), absfDT))); if Iflag < 0 then Jflag := -2 else Jflag := 2; {Присвоить шагу ^нак, соответствующий интегрирова- нию в направлении от Т к TOUT.} - 80: if ОТ > 0 then Н := abs(H) else Н := -abs(H); {Проверить, насколько серьезно влияние на RKF45 слишком большого затребованного числа выходных точек} if abs(H) > = 2*<abs(0T) then inc(KOP); if KOP >= 100 then {чрезмерная частота выходов} begin КОР := 0; Iflag := 7; exit end; if abs(DT) <='U26*abs(T) then {Если очень близко к точке выхода, проэкстраполировать ре- зультат и выйти из процедуры.} begin for k:=l to NEQN do Y [k] := Y[k] + DT*YP[k]; A := Tout; F (A, Y, YP); inc(NFE); T := Tout; Iflag := 2; ex i t end; {Присвоить начальное значение индикатору точки выхода.} Output := false; 322
{Чтобы избежать неоправданного машинного нуля при вычи- слении функции от границ погрешности, промасштабировать эти границы.} Scale := 2/RelErr; АЕ := Seale*AbsErr; {пошаговое интегрирование} 100: HfaiId := false; {Установить наименьшую допустимую длину шага} НМin := U26*abs(Т) ; {Исправить при необходимости длину шага, чтобы дости- гнуть точки выхода. Рассчитать на два шага вперед, чтобы избежать слишком резких изменений в длине шага и тем самым уменьшить влияние выходных точек на программу.} DT := Tout - Т; if abs(DT) < abs(H) then begin H := DT; Output : = true end else if abs(DT) < 2*abs(H) then H := BT/2; {ВНУТРЕННИЙ ОДНОШАГОВЫЙ ИНТЕГРАТОР. Границы погрешностей были про- масштабированы, чтобы избежать неоправданного машинного нуля при вычис- лении функции ЕТ от них. Чтобы избежать обращения в нуль знаменателя в тесте, относительная ошибка измеряется по среднему значению решений в начале и конце шага. В формуле, оценивающей ошибку, произведена группи- ровка слагаемых, уменьшающая потерю верных знаков. Чтобы различать между собой разные аргументы, для Н не допускаются значения, меньшие умноженной на 26 ошибки округления в Т. Введены прак- тические ограничения на скорость изменения шага, чтобы сгладить процесс выбора его длины и избежать чрезмерного ее разброса в задачах с наруше- нием непрерывности. Из предосторожности программа берет 9/10 от длины шага, которая нужна по ее оценке. Если на данном шаге была неудачная попытка, то при планировании сле- дующего увеличение шага не допускается. Это повышает эффективность про- граммы для задач с разрывами.} {Проверить число вычислений производных. Если оно не превышает установленного предела, попробовать продолжить интегрирование от Т до Т+Н.} 200: if NFE > MaxNFE then {слишком' большая работа} begin Iflag := 4; Kflag := 4; exit end; {Продолжить приближенное решение на один шаг длины Н.} FEHL (F, NEQN, Y, Т, Н, YP, Fl. F2, F3, F4, F5, F1); NFE := NFE + 5; {Вычислить и сравнить допустимые границы и оценки локальной ошибки, а затем снять масштабирование границ. Заметьте, что относительная ошибка измеряется среднему значению решений в начале и конце шага.} 11* > 323
ЕЕОЕТ := 0; for к : = 1 to NEQN do < begin ET := abs(Y[k]) + abs(Fl [k]) + AE; if ET <= 0 then {неправильна* граница погрешности} beg i n Iflag := 5; exit end; EE := abs((-2090*YP[k] + (21970*F3[k]-15048*F4 [k])) + (22528*F2[k]-27360*F5[k])); EEOET := Amaxl(ЕЕОЕТ, EE/ET) end; EstTol := abs(H)*EEOET*Scale/52440; if EstTol > 1 then {Неудачный шаг. Уменьшить шаг и снова попробовать. Уменьшение ограничивается снизу множителем 1/10.} begin Hfaild := true; Output := false; if EstTol < 52049 then S := 0.97exp(0.2*Ln(EstTbl)) else S := 0.1; H := S*H; if abs(H) > HMin then goto 200 else {Заданная граница ошибки недостижима даже при наименьшей допустимой длине шага.} begin Iflag *:= 6; Kflag := 6; exit end ’ end; {if EstTol > 1} {Успешный шаг. Поместить в массив Y[*] решение в точке . Т+Н и вычислить производное в этой точке.} Т := Т + Н; for k := 1 to NEQN do Y[к] := Fl[к]; A := T; F(A, Y, YP); inc(NFE); {Выбрать длину следующего шага. Увеличение ограничено множителем 5. Если на данном шаге была неудачная попыт- ка, то для следующего не допускается выбор большего шага.} if EstTol’ > 1.889568Е-4 then S := 0.9/exp(0.2*Ln(EstTo1)) else S := 5; if Hfaild then if S > 1 then S := 1; S := Amaxl(S*abs(H), HMin); if H < 0 then H := -S else H := S; {Конец одношагового интегратора. Проверить, нужно ли делать следующий шаг.} 324
if not Output then if Iflag > 0 then goto 100 else Iflag := -2 else Iflag := 2 END; {RKFS}> z Л {----------------------} BEGIN {RKF45} klm := NEQN +1; kl := klm + 1; k2 := kl + NEQN; k3 := k2 + NEQN; k4 := k3 + NEQN; k5 := k4 + NEQN; k6 := k5 + NEQN; RKFS (F, NEQN, Y, T, Tout, RelErr, AbsErr, Iflag. w[l] , w[klm] ,w[kl] ,w[k2] ,w[k3] ,w[k4] ,w[k5] ,w[k6] ,w[k6+l] , IWork[l], IWork[2], IWork[3], IWork[4], IWork[5]) END {RKF45}; ' ‘ {-----------------------} PROCEDURE Runge(F : ProcType; NEQN : integer; var Yl; var T’ : RealType; Tout : RealType; H : RealType; var Work); var W : array [1. . 3>r 1..ArraySize div 3] of RealType absolute Work; Y : ArrayType absolute Yl; к : integer; DT : RealType; ' .const EPS /RealType = 0; {машинное эпсилон; нулевое начальное значение используется как признак первого обращения} {КЛАССИЧЕСКИЙ МЕТОД РУНГЕ-КУТТА 4-го ПОРЯДКА С ПОСТОЯННЫМ ШАГОМ. Точность решения в основном зависит от правильного выбора шага инт^гри- эования. Допускается решение как в прямом (TOUT > Т), так и в обратном (TOUT < Т) направлении. В программе контролируется выполнение следующих условий: NEQN > 0 и Н <> 0. При невыполнении любого из них программа прекращает свою работу.} BEGIN {Runge} - {Проверить правильность параметров вызова.} if (NEQN <= 0) or (Н = 0) then halt; if EPS = 0 then {Первый вызов: вычислить машинное эпсилон. Его значение, умноженное на 20, будет служить мерой малости приращений Т.} 325
begin EPS := 1; while l+EPS/2 <> 1 do EPS := EPS/2; EPS := 20*EPS end; . . {Начало основного цикла. Определить текущее значение шага так, чтобы попасть точно в точку TOUT.} repeat F(T+O, Y, W); DT := Tout - T; if abs(DT) <= abs(T)*EPS then {Решение находится в EPS-окрестности точки TOUT; экстра- полировать результат и выйти из процедуры.} begin । for k := 1 to NEQN do Y [k] := Y [k] + DT*W[l,k]; T := Tout; exit end; if abs(DT) < abs(H) then H := DT; if DT < 0 then H := -abs(H) else H := abs(H); {Основные вычисления; W[l,*] уже содержит производную в начале шага.} for k := 1 to NEQN do W [2, к] := Y [к] + H*W[l,k]/2; F(T+H/2, W[2,l], W[3,l] ); for к := 1 to NEQN do begin W[1,k] := W[1,k] + 2*W[3,k] ; W[2,k] := Y[k] + H*W[3,k]/2 end; \ F(T+H/2, W[2,1] , W[3,1] ); for к := 1 to NEQN do begin W[1,k] := W[1 ,k] + 2*W[3,k]; W[2,k] := Y [k] + H*W[3,k] end; F(T+H, W[2,l] , W[3,l]); . for к := 1 to NEQN do Y[k] Y[k] + H*(W[l,k] + W[3,k] )/6; T := T + H until T = Tout END {Runge}; {==============} END.{Simula} {=============} i Иллюстрирующая программа с помощью RKF45 рассчитыва движение двух тел под действием взаимного гравитационного пр тяжения. В результате счета на экран будет выведено: RKF45: Runge: 2.0 -0.4902997920 0.9398749966 -0.4902997971 0.9398749862 4.0 -1.2500000001 -0.0000000000 -1.2499999911 -0.0000000296 6.0 -0.4902997921 -0.9398749966 -0.4902997515 -0.9398750092 326
8.0 0.7499999997 0.0000000005 0.7500000000 0.0000000382 10.0 -0.4902997929 0.9398749963 -0.4902998429 0.9398749624 12.0 -1.2500000002 -0.0000000007 -1.2499999894 -0.0000000917 Время счета на ПЭВМ класса IBM XT с сопроцессором около 18 с., без сопроцессора - около 2 мин. PROGRAM Test0fRKF45; {$N+,E+,F+} Uses Simula, CRT; var AlfaSQ : RealType; const NEQN = 4; {----------------------} PROCEDURE Orb(T : RealType; var Yl, YPI); var Y : array [1..NEQN] of RealType absolute Yl; YP : array [1..NEQN] of RealType absolute YPI; R : RealType; BEGIN {Orb} R := Y [1]*Y [1] + Y [2] *Y[2] ; R := R*sqrt(R)/AlfaSQ; Y P[1] := Y[3]; Y P[2] := Y[4]; Y P[3] : = -Y[l]/R; Y P[4] := -Y[2]/R END {Orb}; {---------------------------------------} var Y : array [1..NEQN] of RealType; Work : array [1. .6*NEQN+3] of RealType; T, Tout, RelErr, AbsErr, TFinal, • TPrint, ECC, Alfa : RealType; IWork : array [1..5] of integer; । Iflag : integer; BEGIN {Main} ClrScr; ECC := 0.25; Alfa : = Pi/4; AlfaSQ := sqr(Alfa); T := 0; TFinal := 12; TPrint := 2.0; RelErr := IE-9; AbsErr := 0; Iflag : = 1; Y [l] := 1-ECC; Y [2] := 0; Y [3] := 0; Y [4] := Alfa*sqrt((1+ECC)/(1-ECC)); Tout := TPrint; writein (’ RKF45: Runge: ’): repeat 327
RKF45(0rb, NEQN, Y, I, Tout, RelErr, AbsErr, Iflag, Work, IWork); writein (T:4:l, Y[l]:15:10, Y[2]:15:10); case Iflag of , 1 : begin'writeIn(’Неправильный вызов'); halt end; ’ 2 : Tout := Tout + TPrint; j 3 : writeln('Слишком высокая требуемая точность:’ ,#13, #10, .’RelErr=', RelErr, ' AbsErr=',• AbsErr); j 4 : writeln(’Слишком много шагов’); 5 : begin j •AbsErr := IE-9; writeln('Слишком высокая требуемая точность:’ , #13, #10, 'RelErr»', RelErr, ' AbsErr=’, AbsErr) end; .6 : begin RelErr :=, 10*RelErr; writeln('Слишком высокая требуемая точность:’ ,#13, #10, 'RelErr=RelErr, ’ AbsErr»’, AbsErr) end; end; until Tout Tfinal; Y [l] := 1-ECC; Y [2] :,= 0; Y [3] := 0; Y [4] := Alfa*sqrt( (1+ECC)/(1-ECC).); window (40,2,80,25); T := 0; repeat Runge(0rb, NEQN, Y, T, T+TPrint, 0.02, work); writein (Y[l] : 15:10, Y [2] : 15:10); until T = TFinal END. 15.5. РЕШЕНИЕ НЕЛИНЕЙНЫХ И ТРАНСЦЕНДЕНТНЫХ УРАВНЕНИЙ Решение нелинейных (в том числе трансцендентных) уравненш вида F(X)=0 заключается в отыскании одного или всех корней н; отрезке [А, В] изменения X. При отыскании только действительны: корней задача несколько упрощается. В этом случае используете следующий общий прием: исходный отрезок [А, В] постепенна уменьшается таким образом, чтобы значения функции F(X) на еп концах все время имели разные знаки. Процесс уменьшения отрез ка реализуется в виде итерационной процедуры и продолжаете до тех пор, пока длина отрезка не станет меньше некоторого напе ред заданного значения или пока не будет обеспечено равенстве F(X) = 0. Для отыскания действительного корня нелинейного уравнения < заданной точностью в книге [8] предлагается программа ZEROIN, 1 которой комбинируются разные алгоритмы уменьшения исходной отрезка (метод бисекций и метод секущих), что обеспечивает быст 328
)УЮ сходимость итерационного процесса при минимальном количе- стве вычислений функции F(X). В описываемый ниже модуль Z,EROFUN включена Турбо-Паскалевая реализация этого алгоритма з виде функции ZEROIN. Обращение к функции ZeroIN' (А, В, F, Err) Здесь А, В - выражения типа REALTYPE, значения нижней и верхней границ отрезка; F - имя составленной пользователем фунции типа FUNTYPE, в которой по заданной абсциссе вычисляется значение левой части уравнения F(X)=0; ERR - выражение типа REALTYPE, указывающее максимальную длину интервала неопределенности отыскийаемого действительного корня. Функция возвращает в качестве значения типа REALTYPE най- денный ею корень. Пользователь должен перед обращением к фун- кции определить концы отрезка [А, В] так, чтобы функция, вычис- ленная при этих значениях аргумента, была разного знака. В модуле в качестве базового вещественного типа REALTYPE определен тип EXTENDED, что требует настроить компилятор на работу с сопроцессором или на его программную эмуляцию. Кроме того, передача функции ZEROIN имени фунции пользователя тре- бует включения дальней модели памяти. В результате опция OPTIONS/COMPILER/NUMERIC PROCESSING должна иметь значе- ние 8087/80287, а опция OPTIONS/COMPILER/FORCE FAR CALLS - значение ON. Если на Вашей ПЭВМ нет арифметического сопро- цессора, то дополнительно к этому опция OPTIONS/COMPILER/ EMULATION должна быть в состоянии ON. {=============} UNIT ZeroFun; {=============} {При повторении текст этого модуля нужно поместить в файл ZEROFUN.PAS} {---------------------------------------------------------} INTERFACE {$N+, Е+,F+} type RealType = extended; {базовый вещественный тип} I FunType = Function(X : RealType) : RealType; {------------------------------------------------------- IMPLEMENTATION FUNCTION Zeroin (AX, BX : RealType; F : FunType; Tol : RealType) : RealType; {Процедура осуществляет поиск нуля функции F(x) в интервале [АХ, ВХ] . ВХОДНАЯ ИНФОРМАЦИЯ: АХ - левый конец исходного интервала? ВХ - правый конец исходного интервала; F - функция, которая вычисляет F(X) для любого X в интервале [АХ, ВХ] ; TOL - желаемая длина интервала неопределенности конечного результа- та . 329
ВЫХОДНАЯ ИНФОРМАЦИЯ: , ZEROIN возвращает значение, которое содержит абсциссу X в заданном интервале [АХ, ВХ]. Без проверки предполагается, что F(AX) и F(BX) имеют противоположные знаки. ZEROIN вычисляет нуль F(X) в заданном интервале [АХ, ВХ] в преде- лах допуска на ошибку 4 * MachEps * ABS(X) + TOL, \ где MACHEPS -^относительная машинная точность. Эта программа представляет собой слегка модифицированную трансляцию процедуры Z’ERO, написанной на языке Алгол-60 и приведенной в книге RICHARD BRENT. ALGORITHMS FOR MINIMIZATION WITHOUT DERIVATES. PRENTICEHALL, INC., 1973. > var а! в, C, D, E, Eps, FA, FB, FC, Toll, ХМ, P, Q, R, S : RealType; label 30, 70; BEGIN {ZeroIN} {Вычислить машинное эпсилон.} Eps := 1; while 1 + Eps/2 <> 1 do Eps := Eps/2; {Присвоение начальных значений.} A := AX; В := ВХ; FA := F(A); FB := F(B); {Начать шаг} repeat C := A; FC := FA; D := В - A; E := D; 30: if abs(FC) < abs(FB) then begin A := В; В := С; C := A; FA := FB; FB := FC; FC := FA end; {проверка сходимости} Toll := 2*Eps*abs(B) + Tol/2; XM := (C-B)/2; if (abs(XM) <= Toll) or (FB = 0) then {окончание работы и выход} begin ZeroIN := В; exit . end; ♦ ’ ; {Необходима ли бисекция?} if (abs(E) < Toll) or (abs(FA) <= abs(FB)) then goto 70 else {Возможна ли квадратичная интерполяция.?} begin if А = С then begin {линейная интерполяция} 330
S := FB/FA; P := 2*XM*S; Q := 1 - S end else {обратная квадратичная интерполяция} begin Q := FA/FC; R : = FB/FC; S := FB/FA; P := S*(2*XM*Q*(Q-R) - (В-A)*(R-l)); Q := (Q-1)*(R-1)*(S-1) end; {Выбрать знаки} if P > 0 then Q := -Q; P := abs(P) end {Возможна ли квадратичная интерполяция?};- {Приемлема ли интерполяция?} if (2*P<3*XM*Q-abs(Toll)*Q) and (P<abs(E*Q/2)) then beg i n E :'= D; D := P/Q end else {бисекция} begin 70: D := XM; E := D end; {завершить шаг} A := В; FA := FB; if abs(D) > Toll then В := В + D else if XM < 0 then В := В - abs(Toll) else В := В + abs(Toll); FB := F(B); if FB*(FC/abs(FC)) <= 0 then goto 30 until false END {ZeroIN}; {============} end. {ZeroFun} {============} Для иллюстрации методики работы с модулем ниже приводйтся эграмма, с помощью которой отыскивается действительный ко- 1ь уравнения Х*Х*Х-2*Х-5=0. В результате счета на экран будет выведено сообщение Z = 2.0945514815 Время счета - менее 1 с. PROGRAM ZeroINDemo; {$N+,E+,F+} Uses ZeroFun; 331
FUNCTION F(X : RealType) : RealType; BEGIN F := X*(X*X-2)-5 END; {----------------------------------------} var A, B, Tol : RealType; BEGIN A := 2; В := 3; Tol := IE-10; writein (’Z =’, ZeroIn(A, B, F, Tol):13:10) END. 15.6. ПОИСК ЭКСТРЕМУМА ФУНКЦИИ ОДНОЙ ПЕРЕМЕННОЙ В книге [8] приводится Фортран-программа FMIN, с помощ! которой можно отыскать экстремум некоторой унимодальной фуг ции F(X). Унимодальной на отрезке [А, В] называется такая фуг ция, которая имеет на этом отрезке единственный минимум в тс ке ХО, причем функция строго убывает на полуотрезке [А, ХО] строго возрастает на полуотрезке [ХО, В]. Унимодальная функция обязательно должна быть гладкой или даже непрерывной на отр< ке [А, В]. Ниже приводится текст модуля MINFUN, в который включ Турбо-Паскалевый вариант программы FMIN. Программа реализо! на в виде подпрограммы-функции типа REALTYPE, возвращающ своим значением абсциссу ХО на отрезке [А, В], в которой F(X) х стигает своего минимума. Алгоритм функции FMIN использу комбинацию метода золотого сечения и последовательной парабол ческой интерполяции. Обращение к функции: FMin (А, В, F, Err) Смысл параметров обращения полностью совпадает с одноимс ными параметрами функции ZEROIN (см. 15.5). {=============} UNIT MinFun; {=============} {При повторении текст этого модуля нужно поместить в файл MINFUN.РАЗ} INTERFACE {$N+,E+,F+} type RealType = extended; FunType = Function(X {базовый вещественный тип} : RealType) : RealType; FUNCTION FMin (АХ, ВХ : RealType; F : FunType; Tol : RealType) : RealType; {-------------------------------------------------------------------} IMPLEMENTATION FUNCTION Sign(a, b : RealType) : RealType; {Имитация соответствующей фортрановской функции} 332
BEGIN t if b 0 then Sign := -abs(a) else Sign := abs(a) END {Sign}; {-------------------------------------------------------------------} FUNCTION FMin (AX, BX : RealType; F : FunType; Tol : RealType) : RealType; {Функция вычисляет приближение X' к точке, где F достигает минимума на интервале [АХ, ВХ]. ВХОДНАЯ ИНФОРМАЦИЯ: АХ - левый конец исходного интервала; ВХ - правый конец исходного интервала; F - составленная пользователем подпрограмма-функция, которая вычис- ляет F(X) для любого X в интервале [АХ,ВХ]; TOL - желаемая длина интервала неопределенности конечного результата (должна быть больше или равна нулю). ВЫХОДНАЯ ИНФОРМАЦИЯ: FMIN - абсцисса, где F достигает минимума. Метод использует комбинацию поиска золотого сечения и последователь- ной параболической интерполяции. Сходимость никогда не бывает значитель- но хуже, чем для поиска Фибоначчи. Если F имеет непрерывную вторую про- изводную, положительную в точке минимума (не совпадающей с точками АХ, ВХ), то сходимость сверхлинейная и обычно имеет порядок примерно 1,324. .. Функция F никогда не вычисляется в двух точках, отстоящих друг от друга менее чем на EPS*ABS(X) + TOL/3, где EPS приблизительно равно квадратному корню из относительной машинной точности. Если F - унимо- дальная функция и вычисленные значения F сохраняют унимодальность при соблюдении указанного условия разделенности, то FNIM аппроксимирует абс- циссу глобального минимума F на интервале [ АХ, ВХ ] с ошибкой, меньшей 3*EPS*ABS(X)+T0L. Если F не является унимодальной, то FMIN может с той же точностью аппроксимировать локальный' минимум, возможно, не совпадаю- щий с глобальным. . Эта подпрограмма-функция является слегка модифицированной версией процедуры LOCALMIN, написанной на языке Алгол-60 и приведенной в книге RICHARD BRENT. ALGORITHMS FOR MINIMIZATION WITHOUT DERIVATIVES. PRENTICE-HALL, INC., 1973.} var А, В, C, D, E, Eps, XM, P, Q, R, Toll, TO12.U, V, W : RealType; > FU, FV, FW, FX, X : RealType; label 20, 40, 50; ’BEGIN {FMin} {Переменная С есть возведенная в квадрат величина, обратная золотому сечению.} С := 0.5*(3-sqrt(5)); {EPS приб лизительно равно квадратному корню из относительной машинной точности.} Fps := 1; while l+Eps/2 <> 1 do Eps := Eps/2; 333
Eps := sqrt(Eps); {присвоение начальных значений} A := AX; В := BX; V := A + C*(B-A); W := V; X := V; E := 0; FX := F(X); FV:= FX; FW := FX; {Здесь начинается основной цикл.} 20: ХМ := (A+B)/Z; Toll := Eps*abs(X) + Tol/3; То12 := 2*Toll; {проверить критерий окончания} if abs(X-XM) <= То12 - (В-А)/2 then {конец поиска и выход} begin FMin := X; exit end; {Необходимо ли золотое сечение?} if abs(E) <= Toll then goto 40; {построить параболу} R := (X-W)*(FX-FV); Q := (X-V)*(FX-FW); ; P := (X-V).*Q - (X-W)*Rr Q := 2*(Q-R); • • if Q > 0 then P := -P; Q := abs(Q); f / t * - R := E; E := D; {Приемлема ли парабола?} if (abs(P) >= abs(Q*R/2)) or (P <= Q*(A-X)) or (P >= Q*(B-X)) then goto 40; {шаг параболической интерполяции} D := P/Q; U := X + D; {F не следует вычислять слишком близко к АХ или ВХ.} if .(U-A < То12) or (B-U < То12) then 0 := sign(Toll, ХМ-Х); goto 50; {шаг золотого сечения} 40: if X = ХМ then Е := А-Х else Е := В7Х; D := С*Е; {F не следует вычислять слишком близко к X.} 50: if abs(D) >= Toll then U := X + D else U := X + sign(Tol 1, D); FU := F(U); ^{Присвоить новые значения параметрам А, В, V, W и X.} if FU <= FX then 334
begin if U >= X then A := X else В := X; , V := W; FV := FW; W := X; FW := FX; X := U; FX := FU; goto 20 end; if U < X then A := U else В := U; if (FU <= FW) or (W = X) then begin V := W; FV := FW; W := U; FW := FU end else if (FU <= FV) or (V = X) or (V = W) then begin V := U; FV := FU end; goto 20 END {FMin}; {=============} END. {MinFun} {=============} . Для иллюстрации работы функции FMIN найдем с ее помощью минймум функции Х*Х*Х-2*Х-5 на интервале [0, 1]. Следующая программа выведет на экран строку Z = 0.81650 менее чем через секунду после начала работы. PROGRAM FMinTest; {$N+,E+,F+} Uses MinFun; FUNCTION F(X : RealType) : RealType; BEGIN F := X*(X*X-2)-5 END; var A, B, Tol : RealType; BEGIN A : = 0; В := 1; Tol := IE-5; ‘ writein ('Z = FMin(A, B, F, Tol):7:5) END. {Program}
Г л а в a 16 ПРОГРАММЫ РАЗНОГО НАЗНАЧЕНИЯ В этой главе описываются модули TEXTCRT и GRAPHTXT обеспечивающие расширенные средства управления текстовым и графическим экраном, а также две самостоятельные программы: программа установки в операционной системе текущего времени и даты и программа генерации шрифтов. 16.1. ДОПОЛНИТЕЛЬНЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ УПРАВЛЕНИЯ ТЕКСТОВЫМ ЭКРАНОМ Описываемые в этом разделе процедуры и функции не являют ся стандартными для Турбо-Паскаля. Все они разработаны автором и могут представлять интерес для тех программистов, которые за- хотят иметь более гибкие средства управления текстовым экраном Ниже приводится полный текст библиотечного модуля TEXTCRT, 1 котором содержатся описываемые процедуры и функции. Чтобы получить доступ к ним, нужно предварительно создать файл TEXTCRT.PAS, содержащий текст модуля, и откомпилировать его i файл TEXTCRT.TPU. Одна из программ написана на языке ассемб- лера (текстовый файл VIDEOMEM.ASM). Ее, разумеется, нужнс транслировать с помощью соответствующего транслятора, например фирмы Microsoft, и создать файл VIDEOMEM.OBJ. Только после этой предварительной подготовки Вы получите доступ к описывае- мым ниже средствам. Основная особенность дополнительных средств заключается £ широком использовании в них функций прерывания $10 и прямогс обращения к видеопамяти. На этой основе организуется поддержка многостраничной работы с видеопамятью. Еще раз напомню, что многостраничный режим не распространяется на стандартные про- цедуры WRITE и WRITELN, которые будут выводить информации только в основную (нулевую) страницу независимо от того, какая именно страница сделана активной. Если Вы собираетесь совме- щать стандартные средства модуля CRT и дополнительные средства описываемого модуля TEXTCRT, Вы должны следить за тем, чтобы к моменту обращения к стандартным средствам активной была ну- левая страница. 336
Все программы модуля TEXTCRT настроены на работу с адапте- ром CGA в режиме СО80. Если Вы хотите работать в другом режи- ме или с адаптером другого типа, в исходный текст модуля необхо- димо внести небольшие изменения (см. ниже текст модуля). Предлагаемые программы тестировались на машинах ЕС 8531.М2 с CGA-адаптером, Mazovia с адаптером Hercules, Amstrad РС1640 с "усеченным" адаптером EGA {640x200 при 16 цветах), Tutor с полным адаптером EGA (640x348, 16 цветов) и Cordate с адаптером VGA (640x450, 16 цветов). Во всех случаях не понадоби- лось каких-либо изменений в исходном тексте модуля. Упоминавшаяся программа VIDEOMEMASM практически повто- ряет программу MCMVSMEM.ASM из демонстрационного пакета MicroCalc фирмы Borland. Эта программа необходима для синхрони- заций доступа к видеопамяти со строчной разверткой видеотермина- ла. Синхронизация устраняет "снег" на CGA-дисплее. При работе с EGA или VGA-дисплеем программа VIDEOMEMASM может не ис- пользоваться. Процедура COLORS. Устанавливает требуемые цвета символов и фона одновременно; формат обращения COLORS ( < текст >, <фон> ) Здесь < текст > и < фон >- выражения типа BYTE, устанавлива- ющие соответственно цвет символов и цвет фона. Эта процедура служит фактически интерфейсом обращения к стандартным процедурам TEXTCOLOR и TEXTBACKGROUND биб- лиотечного модуля CRT. Процедуры без параметров CURSOROFF и CURSORON. Соответ- ственно выключают и включают стандартное изображение мигающе- го курсора. Процедура PUTCURSOR. Устанавливает курсор на заданной стра- нице в заданную позицию; формат обращения PUTCURSOR ( <N с.>, <Х>, <Y> ) Здесь <N с. >-выражение типа BYTE, определяющее номер страницы, на которой устанавливается курсор; <Х>, <Y> - выражения типа BYTE, определяющие координа- ты курсора. Координаты исчисляются относительно левого верхнего угла эк- рана, который имеет координаты 1,1. Если координаты выходят из диапазона 1...MAXCHAR для X и 1...MAXLINE для Y или если но- мер страницы больше MAXPAGE, вызов процедуры игнорируется. Определения констант MAXCHAR, MAXLINE и MAXPAGE даны в тексте модуля. Процедура GETCURSOR. Возвращает координаты текущего поло- жения курсора на заданной странице; формат обращения GETCURSOR ( <N с.>, <Х>, <Y> ) Здесь < N с. > — выражение типа BYTE, означающее номер стра- ницы; < Y>-переменные типа BYTE, в которых возвращается положение курсора. Если номер страницы превышает значение MAXPAGE, вызов функции игнорируется. 337
Процедура SETTEXTPAGE. Делает видимой нужную текстовую страницу; формат обращения SETTEXTPAGE ( <N с.> ) Здесь <N с.> - выражение типа BYTE, указывающее^ номер тек- стовой страницы, которую нужно сделать видимой. Если указан номер, превышающий максимальное значение, со- держащееся в константе MAXPAGE, вызов процедуры игнорируется. Функция GETTEXTPAGE. Возвращает значение типа BYTE, со- держащее номер текстовой страницы в диапазоне 0...MAXPAGE, ко- торая была сделана видимой при последнем обращении к процеду- ре SETTEXTPAGE. Обращение GETTEXTPAGE Процедура MOVETOSCREEN. Переносит информацию из опера- тивной памяти в видеопамять; формат обращения MOVETOSCREEN( < источник >, < приемник >, < кол-во > ) Здесь < источник> - имя переменной, содержащей видеоинфор- мацию; < приемник > - адрес начала фрагмента видеопамяти, куда будеп перенесена информация; < кол-во> - количество переносимой информации, байты. Видеоинформация должна быть подготовлена в соответствии с требованиями, описанными в 12.1; для каждого символа отводите* по два байта, причем байт с меньшим адресом должен содержать собственно символ, а байт со старшим адресом - его атрибуты. Программа написана на языке ассемблера, так как процедура до- ступа к видеопамяти критична к Скорости обмена информацией Обмен синхронизируется с работой схем развертки изображения нг экране монитора, что исключает появление помех в виде "снега’ при работе с CGA-адаптером. При обращении к процедуре контро- лируется содержимое логической переменной' CHECKSNOW. Если переменная имеет значение FALSE (внутреннее представление - ну- левой байт), осуществляется обмен без синхронизации. Обычно зна- чение FALSE устанавливается при работе с адаптерами EGA или VGA, так как в них синхронизация достигается схемным путем. Процедура MOVEFROMSCREEN. Переносит информацию из ви- деопамяти в оперативную память; формат обращения MOVEFROMSCREEN ( <ист6чник>, < приемник >, < кол-во > ) Здёсь < источник >-адрес начала фрагмента видеопамяти, да будет перенесена информация; < приемник > - имя переменной, в которую будет записана ©информация; ! < кол-во >-количество переносимой информации, байты. Для этой процедуры справедливо все сказанное выше по шению к процедуре MOVETOSCREEN. Если Ваша ПЭВМ оснащена адаптером, не требующим синхро- низации, или если Вы по каким-либо причинам не смогли оттранс- лировать файл VIDEOMEM.ASM, Вы можете имитировать работ) этих программ на Турбо-Паскале. Для этого необходимр убрать и" отку виде- отно- 338
текста модуля директиву {$L .VIDEOMEM}, а вместо кодового слова EXTERNAL, заменяющего тела процедур, написать, например, сле- дующее: BEGIN Move (Source, Destin, count) END; Процедура MOVESCREEN. Копирует одну текстовую страницу в другую; формат обращения MOVESCREEN ( < источник >, < приемник > ) Здесь < источник >- выражение типа BYTE, содержащее номер страницы - источника видеоинформации; < приемник >-выражение типа BYTE, номер страницы - прием- ника видеоинформации. Процедура копирует текстовую страницу целиком. Если любой из параметров обращения превышает значение MAXPAGE, вызов процедуры игнорируется. Процедура GETSCREEN. Копирует указанную текстовую страницу в оперативную память; формат обращения GETSCREEN ( < приемник >, <N с.> ) Здесь < приемник > - имя переменной,имеющей длину внутрен- него представления не меньше 2*MAXCHAR*MAXLINE байт, куда будет скопирована текстовая страница; MAXCHAR - длина строки, а MAXLINE - количество строк экрана; <N с.> - выражение типа BYTE, содержащее номер копируемой страницы. В отличие от непосредственного обращения к процедуре MOVEFROMSCREEN, эта процедура копирует всю страницу цели- ком. Если при обращении к ней параметр <N стр.> имеет значе- ние, превышающее MAXPAGE, вызов процедуры игнорируется. Программа из примера 88 иллюстрирует работу с процедурой. Программа копирует содержимое текстового экрана в текстовый ди- сковый файл COPYSCR.TXT. Пример 88 PROGRAM CopyTextScreen; Uses TextScr; type CopyType = record c : char; b : byte end; var buf : array [l..MaxLine, 1..MaxChar] of CopyType; f : text; i,j : byte; st : string[MaxChar] ; BEGIN assign (f, ’CopyScr.txt’); rewrite(f); GetScreen(buf, 0); 339
for ; := л to MaxLine do beg i n. st := ' ' ; for j := 1 to MaxChar do st : = st + buf [ i , jJ . c ; j := MaxChar; while (st rjj = ' C and (j > 1) do dec(j); / w r i t e J. f , st) end, . ,jse(£) Процедура PUTSCREEN. Переносит копию текстовой страниць ил итеративной памяти в видеопамять; формат обращения PUTSCREEN ( < источник >, <N с.> ) Ьесь < источник >-имя переменной, содержащей копию стра я ины; <N с. >-выражение типа BYTE, означающее номер страни цы - приемника информации. Процедура копирует 2*MAXCHAR*MAXLINE байт из переменно! < источник > в видеопамяти. Видеоинформация должна бь^ть подго ювлена гак, как описано в процедуре MOVETOSCREEN, или полу ченй с помощью процедуры GETSCREEN. Процедура PUTCHAR. Выводит символ в заданную страницу Hi месщ указываемое курсором; формат обращения PUTCHAR ( <N с.>, < символ > ) здесь <N с. >-выражение типа BYTE, содержащее номер стра ницы, < символ >-выражение типа CHAR, определяющее выводимы! символ Символ выводится с текущими атрибутами цвета и фона (и байта TEXTATTR модуля CRT). После вывода курсор не меняе своего первоначального положения. Если номер страницы превыша ti 'яачение MAXPAGE. вызов процедуры игнорируется. Процедура GETCHAR. Читает символ и его атрибуты; форма обращения GETCHAR ( <N с.>, < символ >, < атрибуты > ) Здесь < х\ с > - выражение типа BYTE, означающее номер стра ницы: < символ > переменная типа CHAR, в которую будет записа! символ; < . атрибуты > переменная типа BYTE, в которую будут записа ны атрибуты символа. Символ считывается из заданной страницы с гой позиции, Hi которую указываез курсор. Если номер страницы превышает значе яие MAXPAGE вызов процедуры игнорируется. Процедура COPYCHAR. Выводит одну или несколько копий сим вола; формам обращения COPYCHAR ( <N с.>, < символ >, < атрибуты >, < кол-во > ' 540
Здесь < N с. > - выражение типа BYTE, указывающее номер страницы; < символ > - выражение типа CHAR, содержащее выводимый символ; < атрибуты >-выражение типа BYTE, определяющее атрибуты символа; < кол-во >-выражение типа WORD, указывающее количество копий символа. Копии выводятся начиная с позиции, на которую указывает кур- сор, и продолжаются вправо. Границы окна игнорируются. При до- стижении правой границы экрана вывод продолжается в следую- щую строку, однако "прокрутка" не осуществляется и выход за ниж- ний край экрана приводит к потере информации: Если номер стра- ницы превышает звачение MAXPAGE, вызов процедуры игнориру- ется. Процедура BORDER. Обводит прямоугольной рамкой заданную область в основной (нулевой) странице; формат обращения BORDER ( <Х1>, <Y1>, <Х2>, <Y2>, <тип> ) Здесь <Х1>, <Y1> - выражения типа BYTE, задающие коорди- наты левого верхнего угла прямоугольной области; < Х2>, < Y2> - выражения типа BYTE, задающие координаты правого нижнего угла; < тип > - выражение типа BORDERTYPE, определяющее тип выводимой рамки. В модуле TEXTCRT определен следующий тип: type BorderType,= (DoubleBorder, SingleBorder, EmptyBorder).; Константам этого типа придается следующий смысл: DoubleBorder - рамка из двойных линий; SingleBorder - рамка из одинарных линий; EmptyBorder - рамка из пробелов (стирание рамки). Координаты задаются относительно левого верхнего угла окна, объявленного стандартной процедурой WINDOW, или экрана, если окно не объявлено. Если при обращении к процедуре координаты вышли из границ 1...MAXCHAR для горизонтали и 1...MAXLINE для вертикали, вызов процедуры игнорируется. Символы рамки вы- водятся с текущими атрибутами из TEXTATTR. Процедура SETWINDOW. Создает окно в нулевой странице, очи- щает его и обводит рамкой; формат обращения SETWINDOW ( <Х1>, <Y1>, <Х2>, <Y2>, <тип> ) Здесь используется стандартная процедура WINDOW из модуля CRT для установки окна и дополнительная процедура BORDER, чтобы его обвести рамкой. Тип рамки задается параметром < тип >. Процедура PUTWINDOW. Создает окно в нулевой странице, очи- щает его и обводит рамкой; формат обращения PUTWINDOW ( <Х1>, <Y1>, <Х2>, <Y2>, <текст>, <фон>, <тип>, <N с.>) 341
Здесь < текст >, < фон >-выражения типа BYTE, задающие со- ответственно цвет символов и цвет фона; <N с.> - выражение типа BYTE, указывающее номер страницы. Особенностью процедуры является то обстоятельство, что перед созданием требуемого окна она сохраняет копию экрана в странице, заданной значением <N с.>. Совместно с процедурой GET- WINDOW (см. ниже) позволяет реализовать эффект наложения и снятия окна на существующее изображение. Если координаты выхо- дят за границы экрана или номер страницы выходит из диапазона 1...MAXPAGE, вызов процедуры игнорируется. Если задан тип рамки DOUBLEBORDER или SINGLEBORDER, координаты окна устанавливаются так, чтобы при выводе в окно не стереть созданную рамку. Процедура GETWINDOW. Снимает окно, ранее созданное проце- дурой PUTWINDOW; формат обращения GETWINDOW ( <N с.> ) Здесь <N с. >-выражение типа BYTE, указывающее номер страницы, на которой сохраняется первоначальное состояние экрана. Процедура полностью восстанавливает координаты активного ок- на, положение курсора и атрибуты цвета, существовавшие к момен- ту обращения к процедуре PUTWINDOW с тем же номером стра- ницы. Программа из примера 89 иллюстрирует эффект наложения и снятия текстовых окон. Пример 89 PROGRAM Put_GetWindows; Uses CRT,TextCrt; var i : byte; BEGIN repeat PutWindow(20, 1, 50, 15, white, blue, DoubleBorder, 1) ; for i:=l to 40 do write(’ Окно N 1’); PutWindow(30, 10, 60, 12, white, red, S i ngleBorder, 2); write(' Окно N 2 : Вывод текста'); PutWindow(40,* 1 1, 55, 25, white, green, DoubleBorder , 3); for i := 1 to 50 do writeln(' Окно N 3 ’,i:3); delay(lOOO); GetWindow(3); delay( 1000); GetWindow(2 ) ; de lay( 500); for i := 1 to 10 do wr11e1n(’Окно N 1 ’); delay(1000); getwindow(l) unt i1 KeyPressed END. Программа завершает работу при нажатии на любую клавишу. 342
{=============} Unit TextCRT; {=====«======} {МОДУЛЬ ПОДДЕРЖКИ ТЕКСТОВОГО РЕЖИМА. Текст этого модуля необходимо поместить в файл TEXTCRT.PAS.} {------------------------------------------------------------} INTERFACE Uses CRT, DOS; 1 {$L VIDEOMEM.OBJ} {Следующий тип используется для указания типа рамки при обращении к процедурам BORDER, SETWINDOW и PUTWINDOW. Назначение элементов типа: DoubleBorder - рамка из двойных линий; SingleBorder - рамка из одинарных линий; EmptyBorder - стереть рамку.} Туре BorderType = (DoubleBorder, SingleBorder, EmptyBorder); {Следующие константы используются при работе с CGA-адаптером в режи- ме 80x25 или 40x25. Выбор нужного режима осуществляется условием компи- ляции: если установлено условие С080, то компилируются константы для ре- жима 80x25; если указано условие С040, - для режима 40x25. При работе с другим адаптером константы необходимо заменить с учетом их назначения: МахРаде - максимальный номер текстовой страницы; MaxChar - максимальное количество символов в строке; MaxLine - максимальное количество строк на экране; VMemory - сегментная часть адреса начала видеопамяти.} {$Define С080} {установлен режим 80x25} {JIFNDEF С040} const МахРаде = 3; MaxChar =80; PageSize = 4096; ' {$ELSE} const MaxPage = 7; MaxChar = 40; PageSize = 2048; {$ENDIF} const MaxLine = 25; VMemory = $B800; ActivePage : byte = 0; {сохраняет номер активной текстовой страницы} PROCEDURE Colors (text, back : byte); {устанавливает цвет текста TEXT и фона BACK} PROCEDURE CursorOn; {включает курсор} PROCEDURE CursorOff; {отключает курсор} PROCEDURE PutCursor (page, x, у : byte); 343
{Устанавливает требуемое положение курсора. PAGE - номер страницы; X, Y - координаты курсора (отсчет от 1,1).} PROCEDURE GetCursor (page : byte; var x, у : byte); {Возвращает координаты курсора: PAGE - номер страницы; X, Y - возвращаемые координаты (отсчет от 1,1).} PROCEDURE SetTextPage (page : byte); {Устанавливает активной заданную текстовую страницу. PAGE - номер устанавливаемой страницы.} FUNCTION GetTextPage : byte; {Возвращает номер O...MaxPage активной текстовой страницы.} PROCEDURE MoveToScreen (var Source, Destin; count : word); {Записывает данные в видеопамять : SOURCE - имя переменной, содержащей записываемую информацию; DESTIN - адрес фрагмента видеопамяти; COUNT - объем записываемой информации, байты.} PROCEDURE MoveFromScreen (var Source, Destin; count : word); {Читает данные из видеопамяти : SOURCE - адрес фрагмента считываемой видеопамати; DESTIN - имя переменной, в которую будут прочитаны дан- ные; COUNT - объем считываемой информации, байты.} PROCEDURE MoveScreen (Source, destin : byte); {Копирует одну текстовую страницу в другую : SOURCE - номер О...МахРаде страницы - источника информации; DESTIN - номер О...МахРаде страницы - приемника информации.} PROCEDURE PutScreen (var Source; page : byte); {Пересылает копию текстовой страницы из оперативной памяти в видео память : SOURCE - имя переменной, хранящей копию страницы; PAGE - номер О...МахРаде страницы - приемника информации.} PROCEDURE GetScreen (var destin; page : byte); {Копирует текстовую страницу в оперативную память : DESTIN - имя переменной, в которую будет копироваться страница; PAGE - номер копируемой страницы.} PROCEDURE PutChar (page : byte; cr : char); {Записывает символ- на место, указываемое. курсором : PAGE - номер О...МахРаде страницы; CR - записываемый символ; Символ записывается с текущими атрибутами, курсор не меняет своего положения.} 344
PROCEDURE GetChar (page : byte; var cr : char; var attr : byte); {Читает символ, на который указывает курсор, и его атрибуты: PAGE - номер 0...MaxPage страницы, откуда нужно прочитать сим- вол ; CR - прочитанный символ; ATTR - его атрибуты.} PROCEDURE CopyChar (page : byte; cr : char; attr : byte; count : word); {Записывает несколько копий символа, начиная с позиции, на которую указывает курсор : PAGE - номер 0...MaxPage страницы; CR - копируемый символ; ATTR - его атрибуты; COUNT - количество копий символа. Символ записывается с указанными атрибутами, курсор не меняет сво- его положения.} PROCEDURE Border-(xl, yl, x2, y2 : integer; Bord : BorderType); {Обводит рамкой заданную прямоугольную область нулевой страницы: Xl, Yl, Х2, Y2 - горизонтальные, вертикальные координаты левого вер- хнего и правого нижнего углов; BORD -'константа, указывающая тип рамки. Символы рамки выводятся с текущими атрибутами.} PROCEDURE SetWindow (xl, yl, x2, y2 : integer; Bord : BorderType); {Создает окно в нулевой странице и обводит его рамкой. Символы рамки выводятся с текущими атрибутами, окно очищается.} PROCEDURE PutWindow(xl, yl, х2, у2, text, back : byte; Bord : BorderType; page : byte); {Сохраняет нулевую страницу в заданной текстовой странице, создает в нулевой странице окно с заданными атрибутами, очищает его и обводит рам- кой : TEXT - цвет символов; BACK - цвет фона; PAGE - номер 0...MaxPage страницы, на которую будет скопирована нулевая страница.} PROCEDURE GetWindow (page : byte); {Восстанавливает состояние экрана, бывшее перед обращением к проце- дуре PUTWINDOW: PAGE - номер той страницы, на которую была скопирована нулевая страница.} --------------------------------------------------------------} IMPLEMENTATION 345
{Внутренние глобальные типы, константы и переменные:} type ScreenAttrType = record case byte of 0 : (buf : array [1..7] of byte); 1 : (WindMinSave, WindMaxSave : word; x, y, AttrSave : byte); 2 : (yl, xl, y2, x2 : byte); end; const SizeVM =PageSize * (MaxPage + 1) - 1; var r : registers; ScreenAttr : array [0..MaxPage] of ScreenAttrType; VM : array [0..SizeVM] of byte absolute VMemory:$0000; {--------------------------------------------} PROCEDURE Colors (text, back : byte); {устанавливает цвет текста TEXT и цвет фона BACK} BEGIN {Colors} TextColor(text); TextBackGround(back) END {Colors}; .{--------------------} PROCEDURE CursorON; {включает курсор} BEGIN {CursorOn} with r do begin AH := 1; CH := 6; CL := 7 end; Intr($10, r) END {CursorOn}; {----------------------------} PROCEDURE CursorOff; {отключает курсор} BEGIN {CursorOff} with r do begin AH := 1; CH := $20 end; Intr($10, r) END {CursorOff}; {-------------------------} PROCEDURE PutCursor (page, x, у : byte); {Устанавливает требуемое положение курсора. PAGE - номер страницы; X, Y - координаты курсора (отсчет от 1,1).} 346
BEGIN {PutCursor if (page <= MaxPage) and (x in [1..MaxChar]) and (y in [1..MaxLine]) then with r do beg i n AH := 2; DH := y-1; DL := x-1; BH := page; Intr($10, r) end E-ND {PutCursor}; {-------------------------------------------} PROCEDURE GetCursor (page : byte; var x, у : byte); {Возвращает координаты курсора: PAGE - номер страницы; X, Y - возвращаемые координаты (отсчет от 1,1).} BEGIN {GetCursor} if page <= MaxPage then with r do begin AH := 3; BH := page; Intr($10, r); x := DL + 1; У := DH + 1 end END {GetCursor}; {-------------------------------------------} PROCEDURE SetTextPage (page : byte); {Устанавливает в активное состояние заданную текстовую страницу.} BEGIN {SetTextPage} if page > MaxPage then exit; with r do - begin AH := 5; AL := page;’ ActivePage page; Intr ($10,r) end END {SetTextPage}; {-----------------------------------------------} FUNCTION GetTextPage : byte; {Возвращает номер 0...3 активной текстовой страницы.} BEGIN {GetTextPage} GetTextPage := ActivePage END {GetTextPage}; {-------------------------} PROCEDURE MoveToScreen (var Source, Destin; count : word); {читает данные из видеопамяти} EXTERNAL; 347
{------.-------------------} PROCEDURE MoveFromScreen (var Source, Destih; count : word); {записывает данные в видеопамять} EXTERNAL; . {------------------------------------------} PROCEDURE MoveScreen (Source, Destin : byte); {копирует одну текстовую страницу в другую} var buf : array [1..2, 1..80] of byte; i : integer; BEGIN {MoveScreen} if (Source < = MaxPage) and (destin <= MaxPage) then for i := 0 to 24 do beg i n MoveFromScreen (VM [PageS i ze*Source+i*"2*MaxChar’’] , buf, "2*MaxChar”); MoveToScreen(buf, VM[PageSize*Destin+i*”2*MaxChar"], "2*MahCharH) end END {MoveScreen}; {-----------------------------------------} PROCEDURE PutScreen (var Source; page : byte); {Пересылает содержимое оперативной памяти в страницу видеопамяти.} BEGIN {PutScreen} if page < MaxPage then MoveToScreen(Source, VM[PageSize * page], MaxChar*MaxLine*2) END {PutScreen}; {--------------------------------------------} PROCEDURE GetScreen (var destin; page : byte); {Копирует текстовую Страницу в оперативную память.} BEGIN {GetScreen} if page < = MaxPage then MoveFromScreen(VM[PageSize * page], destin, MaxChar*MaxLint*2) END {GetScreen}; {---------------------------} PROCEDURE PutChar (page : byte; cr : char); {Записывает символ на место, указываемое курсором на заданной стра нице. Курсор не меняет положения.} BEGIN {PutChar} if page <= MaxPage then with r do begin AH := 10; AL := ord(cr); BH := Page; CX := 1; Intr($10, r,) end END {PutChar}; 348
{----------------------------} PROCEDURE GetChar (page : byte; var cr ; char; var attr ; byte}; {Читает символ, на который указывает курсор, и его атрибуты.} BEGIN {GetChar} if page <- MaxPage then with r do beg i n AH := 8; BH Dage; Intr($10, ”); cr := ch”(AL); attr := ДН end else beg i n cr := chr(0); attr := 0 end END {GetChar}; {----------------------------} PROCEDURE CopyChar (page : byte; cr : char; attr : byte; count : word); {выводит несколько копий символа} BEGIN {CopyChar} if (count <> 0) and (page <= MaxPage) then with r do begin AH := 9; AL := ord(cr); BL := attr; BH := page; CX := count; Intr($10, r) end END '{CopyChar}; {----------------------------} PROCEDURE Border (xl, yl, x2, y2 : integer; Bord : BorderType); {Обводит рамкой заданную прямоугольную область экрана.} const с : array [BorderType, 1..6} of char = ((#201, #205, #187, #186, #200, #188), (#218, #196, #191. #179, #192, #217), (#32, #32, #32, #32, #32, #32)); var x> У, i : integer; BEGIN {Border} x := WhereX; у := WhereY; if not ((xl < 1) or (x2 <= xl) .or (yl < 1) o” (y2 <= yl) or (x2 > MaxChar) or (y2 > MaxLine) ) then 349
begin GotoXY(xl, yl); write(c [Bond, 1]) ; for i := 1 to x2-xl-l do write(c [Bord,2]); write(c [Bord,3]); for i := Г to y2-yl-l do beg i n GotoXY(xl, yl + i); write(c [Bord, 4]); GotoXY(x2, yl + i); write(c [Bord, 4]) end; GotoXY(xl, y2); write(c [Bord, 5]); for i := 1 to x2-xl-l do write(c[Bord,2]); PutChar(0,c[Bord, 6]); GotoXY(x, y) end END {Border}; {------------------------------} PROCEDURE SetWindow (xl, yl, x2, y2 : integer; Bord : BorderType); {создает окно и обводит его рамкой} BEGIN {SetWindow} if (xl < 1) or (x2 > MaxChar) or (xl >= x2) or (y2 > MaxLine) or ( >= y2) then exit; Window(xl, yl, x2, y2); С1rScr; Border(l, 1, x2-xl+l, y2-yl+l, Bord); if Bord <> EmptyBorder then , begin if x2-xl > 2 then dec(x2);' if y2-yl > 2 then dec(y2); Window(xl+1, yl + 1, x2, y2) end; GotoXY(l, 1) END {SetWindow}; {---------------1------------} PROCEDURE PutWindow(xl, yl, x2, y2, text, back : byte; Bord : BorderType; ; page : byte); {Сохраняет текущий экран в текстовой странице PAGE и организует ок с заданными атрибутами и рамкой.} BEGIN {PutWindow} if (page > MaxPage) or (page = 0) or (xl < 1) or (x2 > MaxChar) or (xl >= x2) or (yl < 1) or (y2 > MaxLine) or (yl >= y2) then exit; MoveScreen(0, page); with ScreenAttr[page] do beg i n WindMinSave : = WindMin; WindMaxSave := WindMax; x := WhereX; у := WhereY; AttrSave := TextAttr 350
end; Colors(text, back); SetWindow(xl, yl, x2, y2, Bond) END {PutWindow}; {------------.------------} PROCEDURE GetWindow (page : byte); {Восстанавливает состояние экрана, бывшее перед обращением к проце- е PUTWINDOW.} BEGIN {GetWindow} if (page > MaxPage) or (Page = 0) then exit; MoveScreen(page, 0); with ScreenAttr[page] do begin WindMin := WindMinSave; WindMax := WindMaxSave; TextAttr := AttrSave; GotoXY(x, y) end END {GetWindow}; {============} END. {TextCRT} {============} Следующая программа написана на языке ассемблера и должна транслироваться соответствующим транслятором. Программа обеспе- 1ивает доступ к видеопамяти, синхронизируемый со строчной раз- верткой видеотерминала, что исключает появление помех в виде снега" на экране CGA-монитора. Если Ваша ПЭВМ оснащена7 адап- ерами EGA или VGA, эту программу можно заменить следующи- ми простыми Турбо-Паскалевыми процедурами, включаемыми не- юсредственно в текст модуля TEXTCRT вместо кодовых слов EXTERNAL (см. выше): PROCEDURE MoveToScreen (var Source, Destin; count : word); {Заголовок сохраняем полностью} { EXTERNAL; - так в тексте модуля; вместо этого нужно вставить:} BEGIN Move (Source, Destin, count) END; {-------------------------} PROCEDURE MoveFromScreen (var Source, Destin; count : word); {Заголовок сохраняем полностью} { EXTERNAL; - так в тексте модуля; вместо этого нужно вставить:} BEGIN Move (Source, Destin, count) END; После такой замены необходимо убрать директиву ‘ {$L VIDEOMEM.OBJ} самом начале модуля - теперь модуль не нуждается во внешних роцедурах'. Замечу, что модуль с описанной заменой можно ис- 351
пользовать и с CGA-адаптером, если Вам не очень мешает "снег" экране. Поддержка синхронизации доступа к видеопамяти с работой ; схем развертки монитора. Программа написана на ассембле- ; ре, так как критична к времени выполнения (Турбо-Паскале- ; вая программа не может обеспечить необходимую скорость ; обмена). ; Текст этой программы необходимо поместить в файл ; VIDEOMEM.ASM и транслировать любым транслятором с ; ассемблера (например, Турбо-Ассемблером) в файл ; VIDEOMEM.OBJ. Это необходимо сделать до компиляции ; модуля TEXTCRT. TITLE VideoMem DATA SEGMENT WORD PUBLIC ASSUME DS:DATA EXTRN CheckSnow : BYTE ; Байт CHECKSNOW определен как логическая переменная в ; библиотечном Турбо-Паскалевом модуле CRT. ; Байт CHECKSNOW сигнализирует о необходимости синхро- ; низации: если он нулевой (имеет значение FALSE в Турбо- ; Паскалевом модуле CRT), то синхронизация не нужна, так ; как она обеспечивается схемным способом (обычно это ; адаптеры EGA или VGA). DATA ENDS CODE SEGMENT BYTE PUBLIC ASSUME CS-.CODE PUBLIC MoveToScreen, MoveFromScreen ; PROCEDURE MoveToScreen(var Source, Destin; count : word); ; Обеспечивает перезапись информации из источника ; SOURCE в видеопамять по адресу DESTIN. Количество ; перезаписываемых байт указано в COUNT. MoveToScreen PROC FAR push bp mov bp,sp push bp ; сохранить Турбо-Паскалевые 352
push os регистры bh и иь mov bh,ds:CheckSnow загрузить в ВН признак CGA Ids si,dword ptr [bp+12] загрузить в DS:SI адрес источника les di,dword ptr [bp+8] загрузить в ES:DI адрес приемника mov ex, [bp+6] загрузить в СХ счетчик cmp ex, 0 проверить его на куль je xO если нуль - на выход cmp s 1, di согласовать последовательность jle xl переноса информации: eld' если адрес источника больше jmp short x2 адреса приемника, начать с младших xl: ; иначе - со старших адресов add si ,cx sub si ,2 add di,ex sub std di, 2 x2: shr ex, 1 ; перевести байты в слова .cmp bh,0 ; проверить признак CGA je x7 ' ; нет, не CGA - обойти ; синхронизацию ; Работа c CGA-адаптером : * x3: mov dx,3DAh ; получить в DX статус CGA- порта mov bl,9 ; подготовить в BL маску ; проверки готовности Отсюда начинается цикл записи в видеопамять, который продолжается во время обратного хода луча при горизон- тальной или вертикальной развертке. Запись проходит при закрытых прерываниях tx4: lodsw ; получить в BP mov bp,ax ; ; очередное видеослово х5: in a 1, dx получить статус видеопорта rcr a 1,1 проверить конец горизонта- льного хода луча jb x5 ждать начала обратного хода c 1 i есть обратный ход : закрыть прерывания x6: i n a 1,dx ; ; в начале очередного цикла 12 - Фаронов 353
and al,bl ; проверить наличие обратного ; хода ненулевое значение ну- ; левого бита - признак гори- ; зонтального, а третьего бита ; вертикального обратного хода ; луча jz mov stosw st i loop jmp s хб ; ждать, если нет обратного хо- ах,Ьр ; да перенести в АХ видеослово ; и записать его в видеопём. ; открыть прерывания х4 ; продолжить цикл hort хО ; конец работы ; Рабо та с адаптерами, которые не нуждаются в синхронизации х7: гер movsw 9 ; Заве ; регис ршение работы: восстановить Турбо-Паскалевые тры и вернуться в вызывающую программу хО: pop Pop mov ds bp sp, bp pop ret bp 10 MoveToS V creen ENDP ; PROCE ; Обес ; S 9 DURE MoveFromScreen(var Source, Destin; count :word); печивает чтение информации из видеопамяти с адреса OURCE в переменную по адресу DESTIN. Количество перезаписываемых байтов указано в COUNT. 9 MoveFro mScreen PROC FAR r push mov push push mov Ids bp ' bp, sp bp ; сохранить Турбо-Паскалевые ds ; регистры BP и DS bh,ds:CheckSnow ; загрузить в BP признак CGA si,dword ptr [bp+12]; загрузить в DS:SI адрес ; источника les di,dword ptr [bp+8] ; загрузить в ES:DI адрес ; приемника mov cmp cx,[bp+6] ; загрузить счетчик в СХ сх,0 ; и проверить его на 0 354
je yO ; выйти, если нуль стр s i , d i ; согласовать направление об- jle yl ; мена в зависимости от соот- с Id ; ношения адресов jmp short y2 ; перейти,если перенос вперед add s i, ex sub si ,2 add d i, ex sub std di ,2 shr ex, 1 ; заменить байты на слова cmp bh,0 ; проверить наличие CGA je y6 ; нет CGA ; Работа с CGA-адаптером УЗ: mov dx,3DAh ; загрузить в DX адрес порта ; Отсюда начинается цикл записи в видеопамять, который ; продолжается во время обратного хода лувча при горизон ; тальной или вертикальной развертке. Запись проходит при ; закрыты* : прерываниях у4: in a 1, dx ; в начале очередного цикла and al,bl ; проверить обратный ход: jb y4 ; ненулевое значение нулевого ; бита - признак горизонталь ; ного, а третьего бита - верти- ; кального обратного хода луча ; ждать обратного хода cli ; есть: закрыть прерывания У5: in a 1, dx ; получить статус видеопорта rcr a 1,1 ; проверить только горизонтальную jnb У5 ; развертку: команда LODSW ; выполняется на 1 цикл мед- леннее, чем STOSW; чтобы ; компенсировать эту потерю, ; приходится использовать ; команду проверки младшего ; бита RCR AL,1, которая на 1 ; цикл короче команды выде- ; ления признаков обеих раз- ; верток AND AL,АН ; ждать обратного хода 12* 355
; получить видеослово ; открыть прерывания ; записать видеослово в прием- ; ник ; продолжить цикл ; завершить работу lodsw st i stosw loop y4 jmp short yO ; Работа с адаптерами, не требующими синхронизации: уб: rep movsw ; Конец работы: восстановить Турбо-Паскалевые регистры ; и вернуться в вызывающую программу уО: pop ds pop bp mov sp,bp pop bp ret 10 MoveFromScreen ENDP CODE ENDS END 16.2. ПРОГРАММА УСТАНОВКИ ТЕКУЩЕЙ ДАТЫ И ВРЕМЕНИ Имеющиеся в ДОС средства ввода текущего времени и даты не- удобны и, как показывает практика, почти не используются про- граммистами. В то же время контроль за датой создания файлов может быть весьма полезен при систематической работе на ПЭВМ. С помощью описываемой программы Вы сможете установить дату и время, не пользуясь алфавитно-цифровыми клавишами. По- сле запуска программы (ее запуск удобно включить в файл автоза- пуска AUTOEXEC.BAT) экран приобретает вид, показанный на рис. 25. Как видно из подсказки, приведенной в нижней части экрана, все действия по установке времени и даты осуществляются четырь- мя клавишами управления курсором. Кроме того, задействованы клавиши "Ввод" - выход с установкой даты и времени и ESC - вы- ход без установки. Кдавишами перевода курсора влево-вправо мож- но смещать небольшое окно-рамку, выделяющую дату, месяц, год, час или минуты. Окно имитирует окошко кассового аппарата, через которое виден фрагмент бесконечной ленты с нанесенными на нее цифрами. Лента может перемещаться вверх или вниз относительно окошка с помощью клавишей перевода курсора. Если Вы вышли 356
i ; 03 программы, нажав на "Ввод", то дата и время, демонстрировав- I щиеся в этот момент на экране, будут установлены в операционной системе в качестве текущих и одновременно запомнятся в специ- альном дисковом файле DATESET.DAT. При следующем обращении к программе она попытается отыскать в текущем каталоге этот файл и, если он будет найден, восстановит вид экрана точно таким, каким он был к моменту последнего выхода из программы. (С) 1989 г. Фаронов В.В. Установка времени и даты: 20 - ИЮН - 89 10 : 45 Выбор : <— —> Установка : t I Выход : <J Отказ : ESC Рис. 25. Вид экрана для программы установки даты и времени Работа программы разбивается на 4 этапа, каждому этапу соот- ветствует своя процедура: - читать файл DATESET.DAT и установить начальное значение даты и времени (процедура READFILE); , - создать изображение на экране (INITSCREEN); - ввести новую дату и время (INPUTDATETIME); - записать файл DATESET.DAT (WRITEFILE). PROGRAM DateSet; {Программа установки текущей даты и времени} Uses CRT,DOS,TextCRT; type DatType = record case byte of 1 : (d : array [1..5] of word); 2 : (day : word; {день} mon : word; {месяц} yea : word; {год} 357
hou : word; min : word) {час} {минуты} end; const xl = 1; Dat : DatType = (d : (20, 6, 89, 10, 45)); var f : f ile of DatType; {---------------------} PROCEDURE ReadFile; {Осуществляет чтение данных из файла DATESET.DAT в переменную Dat. Если файл отсутствует, сохраняется начальное состояние полей записи, указанное в типизированной константе DAT.} const name = 'DATESET.DAT’; BEGIN {ReadFile} assign(f, name); {$!-} reset(f) {$!+}; if lOResult = 0 then begin read(f, Dat); close(f) end / END {ReadFile}; {----------------------} PROCEDURE InitScreen; {формирует изображение на экране} const f xl = 25; yl = 9; {координаты окна} x2 - 55; y2 = 16; {в центре экрана} {----------------------} PROCEDURE Line (ch : char; count : byte); {выводит COUNT символов CH текущим цветом, начиная с текущей пози- ции} var 1 : byte; BEGIN {Line} for i := 1 to count do write(ch) END {Line}; {-----------------} PROCEDURE UpString; {выводит верхнюю строку экрана и линию} const t = ’ (С) Фаронов В.В. 1989 г. ’; с = #196; BEGIN {UpString} TextBackGround(white); GotoXY(5, 1); writeln(t); Colors(ye 1 low,black); Line(c,80) END {UpString}; {---------------} 358
PROCEDURE DnString; {выводит нижнюю строку экрана и линию} const tl = ' Выбор : т#17#196#32#196#16; t2 = ' Установка : ’#24#32#25; t3 = ’ Выход : ’#17#217; t4 = ' Отказ : ESC ’; с = #196; BEGIN {DnString} GotoXY(1, 24); Line(c, 80); write(tl,t2,t3,t4) END {DnString}; {----------------------------------} BEGIN {InitScreen} Colors(white, black); ClrScr; UpString; DnString; Colors(yellow, blue); SetWindow (xl, yl, x2, y2, DoubleBorder) END {InitScreen}; {:--------------------------------} PROCEDURE InputDateTime; {основная процедура ввода даты и времени} type KeyReadType = (left, right, up, dn, enter, ESC, err); const 1 tl = 'Установка времени и даты:'; t2 = 'XX - XXX - XX XX : XX'; ExitF : Boolean = false; var k : byte; {положение активного окна} г : registers; {---------} PROCEDURE Out (text, back : byte); {выводит k-й элемент цветом TEXT и с фоном BACK} const х : array [1..5] of byte = (4, 9, 15, 22, 27); month : array [1..12] of string[3] =( 'ЯНВ','ФЕВ','MAP’,'АПР','МАЯ’,'ИЮН', ’ИЮЛ|', 'АВГ ’, 'СЕН ', ’ОКТ', 'НОЯ’, 'ДЕК '); var s : string[2] ; Bord : BorderType; j : byte; BEGIN {Out} if back = blue then Bord := EmptyBorder else Bord := SingleBorder; GotoXY(x[k], 5); Culors(Yellow, blue); 359
if k=2 then j := 3 else j := 2; Border(x[k]-1, 4, x[k]+j, 6, Bord); Colors(text, back); with Dat do Yf k=2 then write(month[mon] ) else , begin x str(d[k] , s); if d[k] 10 then s := 'O’+s; write(s) end; {погасить курсор:} CursorOFF END {Out}; {--------} FUNCTION KeyRead : KeyReadType; {Читает клавиатуру без эхо-повтора} const k : array [0..3] of char =(#75, #77, #72, #80); var c : char; f : Boolean; i : byte; BEGIN {KeyRead} KeyRead := err; f := false; c := ReadKey; if c = #0 then begin f := true; c := ReadKey end; if f then begin i := 0; while (i 4) and (c k[i]) do inc(i); if i 4 then KeyRead := KeyReadType(i) end else if c = #13 then KeyRead := enter else if c = #27 then KeyRead := ESC END {KeyRead}; {---------} PROCEDURE LeftKey; {сдвиг окна влево} 360
BEGIN {LeftKey} Out(white, blue); dec(k); if к < 1 then к := 5 END {LeftKey}; {--------} PROCEDURE RightKey; {сдвиг окна вправо} BEGIN {RightKey} Out(white, blue); inc(k); if к > 5 then к := 1 END {RightKey}; {--------} PROCEDURE DnKey; {наращивание элемента даты/времени} BEGIN {DnKey} Out(white, blue); with Dat do begin inc(d[k]); \ case к of 1 : if day > 31 then day •: = 1; 2 : if mon > 12 then mon := 1; 3 : if yea > 99 then yea := 0; 4 : if hou > 23 then hou := 0; 5 : if min > 59 then min := 0 end {case к of} end {with Dat} END {DnKey}; {--------} PROCEDURE UpKey; {уменьшение элемента даты/времени} BEGIN {UpKey} 0ut(white, blue); with Dat do begin case к of 1 : if day = 1 then day := 32; 2 : if mon = 1 then mon := 13; 3 : if yea = 0 then yea := 100; 4 : if hou = 0 then hou := 24; 5 : if min = 0 then min := 60 end {case к of}; dec(d [k]) end {with Dat} END {UpKey}; {---------} BEGIN {InputDateTime} GotoXY(4, 3); write(tl); GotoXY(4, 5); write(t2); for к :=1 to 5 do 0ut(white, blue); 361
к := 1; repeat Out(yellow, black); case KeyRead of left : LeftKey; right : RightKey; , up : UpKey; dn : DnKey; enter : ExitF := true; ESC : begin Colors(white, black); CursorON; halt end end {case KeyRead of} until ExitF; with Oat do begin SetTime(hou, min, 0, 0); SetDate(1900+yea, mon, day) end; Colors(white, black); CursorON END {InputDateTime}; {-------------------------} PROCEDURE WriteFile; {запись введенной даты и времени в файл DATESET.DAT} BEGIN {WriteFile} {$14 rewrite(f); _ write(f, Dat) {$! + } END {WriteFile}; •{---------------------------------------------} BEGIN {main} ReadF i le'; InitScreen; InputDateT ime; Wr i teF-i le END. {DateSet} 16.3. ДОПОЛНИТЕЛЬНЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ УПРАВЛЕНИЯ ГРАФИЧЕСКИМ ЭКРАНОМ Описываемые в этом разделе программные средства можно ис- пользовать только на ПЭВМ, поддерживающих графические режи- мы работы среднего и/или высокого разрешения CGA-адаптера (адаптеры CGA, MCGA, EGA, VGA), причем высококачественные адаптеры EGA и VGA не смогут использовать все свои цветовые и графические возможности. Это ограничение связано с принятым в модуле способом вывода текстовой информации процедурами 362
WRITE и WRITELN с использованием загружаемой в память вто- рой половины таблицы знакогенератора (см. 13.4.2). Предлагаемые процедуры и функции служат для расширения возможностей стандартных программных средств в отношении вы- вода текстовых сообщений (с использованием символов из второй половины ASCII-таблицы). Основные проблемы здесь связаны с от- сутствием на графическом экране мигающего курсора, что затрудня- ет работу с текстовыми редакторами, использующими националь- ный алфавит. Принятая в модуле имитация курсора делает невоз- можным ввод строк символов стандартными процедурами READ и READLN (точнее, ввод возможен, но без имитации курсора). Вот почему в модуль включены процедуры, обеспечивающие выделение из уже введенной текстовой строки подстроки с символьным пред- ставлением целого или вещественного числа с последующим преоб- разованием этой подстроки в число соответствующего типа. Процедура CURSOR. Обеспечивает вывод мигающего курсора в позицию, определяемую функциями WHEREX и WHEREY. Обра- щение CURSOR Выход из 'процедуры - по нажатйю на любую клавишу. Символ не считывается из буфера ввода, и пользовательская программа мо- жет его считывать с помощью любых стандартных процедур, в том числе READ, READLN и READKEY. Процедура GRAPHREAD. Вводит символьную строку с имита- цией мигающего курсора и расширенными (по сравнению с проце- дурами READ, READLN) средствами редактирования ввода; формат обращения GRAPHREAD ( < строка >, <ESC> ) Здесь < строка > - переменная типа STRING, содержащая введен- ную строку; < ESC >-переменная типа BOOLEAN, означающая признак на- жатия ESC. Расширение редакторских возможностей ввода заключается в том, что в процедуре задействованы клавиши перевода курсора вле- во-вправо, а также НОМЕ и END. Этими клавишами можно пере- местить курсор в нужную позицию уже введенной части строки и продолжить ввод в режиме вставки или в режиме замены. По умолчанию устанавливается режим вставки, переключение в режим замены и обратно осуществляется с помощью клавиши INS. Если ввод прерван нажатием на клавишу ESC, параметр <ESC> при выходе из процедуры имеет значение TRUE, если ввод завершен стандартно - нажатием на клавишу "Ввод”, значением параметра бу- дет FALSE. Сразу при входе в процедуру устанавливается нулевая длина переменной < строка >. Процедура SUBSTR. Выделяет из исходной строки подстроку; формат обращения SUBSTR ( < строка >, < подстрока >, < индекс > ) Здесь < строка >-выражение типа STRING, содержащее исход- ную строку; 363
< подстрока >-переменная типа STRING, в которой возвращает- ся выходная подстрока; < индекс >-переменная типа INTEGER, указывающая позицию в строке. Процедура начинает анализ исходной' строки с позиции, задавае- мой значением переменной < индекс >. Ведущие пробелы игнори- руются, ведущая запятая воспринимается как команда выхода из процедуры, при этом выделенная подстрока будет пустой. Призна- ком конца подстроки являются ведомые (т.е. появившиеся после любого значащего символа) пробел или запятая. Если при входе в процедуру значение переменной < индекс > превышает длину ис- ходной строки или меньше единицы, разбор не производится и при выходе из процедуры значение этой переменной будет равно нулю. Во всех остальных случаях в ней возвращается номер той позиции в строке, на которой закончился разбор: если встретилась запятая, то номер позиции, следующей за ней (сама запятая в вы- ходную строку не помещается), если пробел - номер занимаемой им позиции, если конец строки, то в переменной возвращается увели- ченная на единицу длина строки. Процедура INPUTINT. Выделяет из строки подстроку с помощью SUBSTR и преобразует ее в целое число; формат обращения INPUTINT ( <строка>, <число, <индекс> ) Здесь < строка > - выражение типа STRING, содержащее входную строку; < число >-переменная типа INTEGER, в которой возвращается выделенное число; < индекс > - переменная типа INTEGER, определяющая позицию в строке. Параметр < индекс > при входе в процедуру и после выхода из нее устанавливается так, как это описано в процедуре SUBSTR, од- нако если в выделенной строке обнаружилось нарушение формата символьного представления целого числа, то в этом параметре воз- вращается отрицательный номер той позиции в строке, где обнару- жена ошибка. Процедура INPUTREAL. Выделяет из строки подстроку с по- мощью SUBSTR и преобразует ее в вещественное число; формат обращения INPUTREAL ( <строка>,- <число>, <индекс> ) Здесь < число > - переменная типа REAL, в которой возвращает- ся выделенное число. {=============} UNIT GraphTxt; {============} {Модуль поддержки текстового вывода в графическом режиме, рассчитан- ный на работу с CGA-адаптером в режиме среднего или высокого разрешения. • В модуле собраны процедуры и функции, позволяющие выводить на графи- ческий экран текстовые сообщения.. Используется вторая половина таблицы ASCII-кодов с кириллицей по так называемому альтернативному варианту ко- дировки (см. прилож.1), однако пользователь может загрузить свой вариант этой таблицы в виде шрифта 8x8.} 364
INTERFACE Uses CRT, Graph, DOS; type FontType = array [1..64, 1..8] of word; PROCEDURE Cursor; {Обеспечивает вывод мигающего курсора в позицию графического экрана-, определяемую текущим положением курсора X,Y (задается процедурами COTOXY из модуля CRT и стандартными процедурами WRITE и WRITELN. Выход из про- цедуры - по нажатию на любую клавишу.} PROCEDURE GraphRead (var st:string; var ESC : Boolean); { Вводит символьную строки с имитацией курсора: ST - введенная строка; ESC - признак выхода по нажатой клавише ESC} PROCEDURE SubStr (st : string; var s : string; var index : integer); {Выделяет из строки ST подстроку начиная с позиции INDEX и помещает ее в подстроку S. Признак конца подстроки - пробел или запятая или конец строки ST. Ведущие пробелы'игнорируются. Ведущая запятая воспринимается как команда выхода, при этом возвращается пустая подстрока S. После ус- пешного ввода значение INDEX соответствует первому символу, следующему за выделенной подстрокой (включая ограничивающую его запятую). При ошиб- ке значение INDEX соответствует отрицательной позиции в строке ST, где. закончился разбор.} PROCEDURE Inputlnt (St : string; var k, Index ; integer); {Выделяет из символьной строки подстроку и преобразует ее в целое число. В случае пустой строки обнуляет INDEX. В случае ошибки возвращает в INDEX отрицательный номер ошибочной позиции.} PROCEDURE InputReal (St : string; var r : real; var index : integer); {Выделяет из символьной строки подстроку и преобразует ее в вещест- венное число. В случае пустой строки обнуляет INDEX. В случае ошибки возвращает в INDEX отрицательный номер ошибочной позиции.} {Константа FONT определяет конфигурацию символов кириллицы по ал- ьтернативному варианту кодировки (см. прилож. 1). Для создания собствен- ной версии шрифта Вы можете? использовать программу FONTGEN из 16.4. Соб- ственный шрифт можно загрузить в константу FONT из дискового файла, со- зданного этой программой. Например,, если файл со шрифтом имеет имя USER.FNT, то загрузка может быть осуществлена таким образом: Uses GraphTxt; var FileFpnt : file of FontType; begin assign(FileFont, 'User.fnt'); reset(FileFont) if lOResult = 0 then read(FileFont, GraphTxt.Font); . • •} const 365
{Таблица шрифта по ’’альтернативному’’ варианту кодировки:} Font : ($7830, FontType =( $CCCC, $CCFC, $00CC, $62FE, $7C60, $6666, $00FC), ($66FC, $7C66, $6666, $00FC, $62FE, $6060, $6060, $00F0), ($361E, $6666, $6666, $C3FF, $62FE, $7868, $6268, $OOFE), ($D6D6, $387C, $D67C, $00D6, $CC78, $300C, $CC0C, $0078), ($6763, $7B6F, $6373, $0063, $D6D6, $DECE, $E6F6, $00C6), ($66E6, $786C, $666C, $00E6, $331F, $6363, $6363, $0003), ($EEC6, $FEFE, $C6D6, $00C6, $CCCC, $FCCC, $CCCC, $0000), ($6C38, $C6C6, $6CC6, $0038, $C6FE, $C6C6, $C6C6, $0006), ($66FC, $7C66, $6060, $00F0, $663C, $0000, $6600, $0030), ($B4FC, $3030, $3030, $0078, $C6C6, $0606, $06FE, $0070), ($D67C, $D6D6, $7CD6, $0010, $C6C6, $3860, $6038, $0006), ($CCCC, $CCCC, $CCCC, $06FE, $CCCE, $CCCC, $0070, $001E), ($D6D6, $D6D6, $D6D6, $OOFE, $D6D6, $D6D6, $D6D6, $03FF), ($30F0, $3C30, $3636, $003C, $C6C6, $F6C6, $CECE, $00F6), ($C0C0, $F8C0, $CCCC, $00F8, $CC78, $3E06, $0006, $0078), ($D6CC, $F6F6, $D6D6, $00CC, $C67E, $7EC6, $6636, $0006), ($0000, $0078, $CC7C, $0076, $0000, $C0F8, $CCF8, $00F8), ($0000, $CCF8, $CCF8, $00F8, $0000, $64FC, $6060, $00F0), ($0000, $6030, $6C6C; $C6FE, $0000, $0078, $C0FC, $0078), ($0000, $0606, $D67C, $0006, $0000, $0CF8, $0078, $00F8), ($0000, $DECE, $E6F6, $0006, $3800, $DECE, $E6F6, $0006), ($0000, $D8CC, $D8F0, $00CC, $0000, $6030, $6060, $0000), ($0000, $EEC6, $D6FE, $00C6, $0000, $CCCC, $CCFC, $0000), ($0000, $0078, $CCCC, $0078, $0000, $CCFC, $0000, $0000), ($CC33, $CC33, $CC33, $CC33, $A'A55, $AA55, $AA55, $AA55), ($77EE, $77EE, $77EE, $77EE, $1818, $1818, $1818, $1818), ($1818, $1818, $18F8, $1818, $1818, $F818, $F818, $1818), ($2424, $2424, $24E4, $2424, $0000, $0000, $24FC, $2424), ($0000, $F800, $F818, $1818, $2424, $E424, $E404, $2424), ($2424, $2424, $2424, $2424, $0000, $FC00, $E404, $2424), ($2424, $E424, $FC04, $0000, $2424, $2424, $00FC, $0000), ($1818, $F818, $F818, $0000, $0000, $0000, $18F8, $1818), ($1818, $1818, $181F, $0000, $1818, $1818, $00FF, $0000), ($0000, $0000, $18FF, $1818, $1818, $1818, $181F, $1818), ($0000, $0000, $OOFF, $0000, $1818, $1818, $18FF, $1818), ($1818, $1F18, $1F18, $1818, $2424, $2424, $2427, $2424), ($2424, $2724, $3F20, $0000, $0000, $3F00, $2720, $2424), ($2424, $E724, $FFOO, $0000, $0000, $FFOO, $E700, $2424), ($2424, $2724, $2720,'$2424, $0000, $FF00, $FF00, $0000), ($2424, $E724, $E700, $2424, $1818, $FF18, $FF00, $0000), ($2424, $2424, $00FF, $0000, $0000, $FF00, $FF00, $1818), ($0000, $0000, $24FF, $2424, $2424, $2424, $003F, $0000), ($1818, $1F18, $1F1O, $0000, $0000, $lF00, $1F1O, $1818), ($0000, $0000, $243F, $2424, $2424, $2424, $24FF, $2424), ($1818, $FF18, $FF18, $1818, $1818, $1818, $00F8, $0000), ($0000, $0000, $181F, $1818, $FFFF, $FFFF, $FFFF, $FFFF), ($0000, $0000, $FFFF, $FFFF, $F0F0, $F0F0, $FOFO, $FOFO), ($0F0F, $0F0F, $0F0F, $0F0F, $FFFF, $FFFF, $0000, $0000), 366
($0000, $CCF8, $C0F8, $ooco, $0000, $CC78, $CCC0, $0078), ($0000, $B4FC, $3030, $0078, $0000, $cccc. $7CCC, $780C) , ($1000, $D67C, $D6D6, $107C, $0000, $6CC6, $6C38, $00C6), ($0000, $CCCC, $CCCC, $06FE, $0000, $CCCE, $0C7C, $001E), ($0000, $0606, $D6D6, $OOFE, $0000, $D6D6, $D6D6, $03FF), ($0000, $30F0, $363C, $003C, $0000, $F6C6, $CECE, $00F6), ($0000, $F8C0, $CCCC,- $00F8, $0000, $CC78, $CC1C, $0078), ($0000, $F6DC, $F6F6, $QODC, $0000, $CC7C, $6C7C, $00CC), ($FECC, $7860, $6260, $OOFE, $00CC, $CC78, $C0FC, $0078), ($0000, $0000, $0603, $180C, $0000, $0000, $60C0, $1830), ($3018, $C060, $0000, $0000, $0C18, $0306, $0000, $0000), ($0C00, $FF06, $0C06, $0000, $3000, $FF60, $3060, $0000), ($1818, $1818, $7E18, $183C, $3C18, $187E, $1818, $1818), ($1818, $7E00, $1800, $0018, $1818, $187E, $0018, $007E), ($C7C7, $F6E4,' $CEDE, $00C6, $C300, $663C, $3C66, $00C3), ($0000, $3C3C, $3C3C, {— $0000, $0000, $0000, $0000, } $0000)) IMPLEMENTATION PROCEDURE Cursor; {Обеспечивает вывод мигающего курсора в позицию графического экрана, определяемую текущим положением курсора X,Y (задается процедурами GOTOXY из модуля CRT и стандартными процедурами WRITE и WRITELN. Выход из про- цедуры - по нажатию на любую клавишу.} const time = 20; ImCursorl : array [1..14] of byte = (7, 0, 7, 0, 0, 0, 0, 0, 0, 255, 255, 0, 32, 32); ImCursor2 : array [1..22] of byte = (7, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 1, 0); { ImCursorl - графический образ курсора для 640x200; ImCursor2 - ” ” ” 320x200} var i : byte; x, у : byte; d, r : integer; BEGIN {Cursor} x := WhereX; у := WhereY; DetectGVaph (d, r); r := GetGraphMode; repeat if (d = CGA) and (r < CGAHi) then PutImage((x + Lo(WindMin) - 1) * 8, (y+Hi(WindMin)-l)*8+2, ImCursor2, XORPut) else PutImage((x + Lo(WindMin) - 1) * 8, (y+Hi(WindMin)-1)*8+2, ImCursorl, XORPut); i := 0; while (not KeyPressed) and (i < time) do begin delay(10); k 367
inc(i) end; if (d = CGA) and (r < CGAHi) then Putlmage((x + Lo(WindMin) - 1) * 8, (y+Hi(WindMin)-l)*8+2, ImCursor2, XORPut) else Putlmage((x + Lo(WindMin) - 1) * 8, (y+Hi(WindMin)-l)*8+2, ImCursorl, XORPut); i := 0; while (not KeyPressed) and (i < time) do begin de lay(10); inc(i) end until KeyPressed END {Cursor}; {-----------------------} PROCEDURE GraphRead (var stistring; var ESC : Boolean); {Вводит символьную строку с имитацией курсора: ST - введенная строка; ESC - признак выхода по нажатой клавише ESC.} const Ins : Boolean = true; var x, у , xO, yO, i : byte; cl, c2 : char; BEGIN {GraphRead} x ;= WhereX; xO := x; у := WhereY; yO := y; st := ' ’; ESC := false; repeat Cursor; cl := ReadKey; if cl * #0 then c2 := ReadKey; case ord(cl) of 13 : begin writein; exit end; 27 : begin ESC := true; exit end; 127, 8 : if x > xO then begin GotoXY(xO, yO); > for i := 1 to Length(st) do write(' ’); Delete(st, x - xO, 1); Dec(x); GotoXYfxO, yO); write(st) 368
end; О : case ord(c2) of 82 : Ins := not Ins; 71 : x := xO; 75 : if x > xO then dec(x); 77 : if x - xO < Length(st) then inc(x); 79 : x := xO + length(st); 83 : if (Length(st) <> 0) and (x - xO < Length(st)) then beg i n GotoXY(xO, yO); for i := 1 to Length(st) do write( ' ’); Delete(st, x - xO + 1, 1); - GotoXY(xO, yO); ’write(st); GotoXY(x, y) end; end {case ord(c2)}; else {case ord(cl)} begin if x - xO = Length(st) then begin st := st + cl; write(cl); inc(x) end else if Ins then begin Insert(cl, st, x - xO + 1); GotoXY(xO, yO); write(st); inc(x) ; GotoXY(x, y) end else begin st[x - xO + 1] := cl; GotoXY(x, y); write(cl); inc(x) end end {else case} ' end {case ord(cl)} until false END {GraphRead}; {--------------------} PROCEDURE SubStr (st : string; var s : string; var index : integer); {Выделяет из строки ST подстроку начиная с позиции INDEX и помещает !е в подстроку S. Признак конца подстроки - пробел или запятая, или ко- leU строки ST. Ведущие пробелы игнорируются. Ведущая запятая воспринима- лся как команда выхода, при этом возвращается пустая подстрока S. После 'спешного ввода значение INDEX соответствует первому символу, следующему а выделенной подстрокой (включая ограничивающую его запятую). При ошиб- 369
ке значение INDEX соответствует отрицательной позиции в строке ST, где закончился разбор.} var bg : longint; BEGIN {SubStr} s : = ' ' ; while (st [index] = ’ ') and (length(st) >= index) do inc(index); if index > Length(st) then index := 0 else if st[index] = then inc(index) else 'begin , bg := index; while (st[index] <> ' ') and (st[index] <> ’,’) and (index <= Length(st)) do , inc(index); • . , s :« Copy(st, bg, index - bg); if st[index] = then Inc(index) end END {SubStr}; {-----------------------} PROCEDURE Inputlnt (St : string; var k, Index : integer); {Выделя'ет из символьной строки подстроку и преобразует ее в целое число. В случае пустой строки обнуляет INDEX. В случае ошибки возвращает в INDEX отрицательный номер ошибочной позиции.} var s : string; i : integer; BEGIN {Inputl} / SubStr (st, s, index); if (index > 0) and (lengt-h(s) <> 0) then beg i n Va 1 (s , k , i); if i <> 0 then index := -index end END {Inputl}; {---------------------} PROCEDURE InputReal (St : string; var r : real; var index : integer); {Выделяет из символьной строки подстроку и преобразует ее в цел(? число. В случае пустой строки обнуляет INDEX. В случае ошибки возвращав в INDEX отрицательный номер ошибочной позиции.} var s i s : string; i : integer; BEGIN {InputReal} ‘ SubStr (st, s, index); 370
if (index > 0) and (length(s) <> 0) then begin' Val(s, r, i); if i <> 0 then index := -index end END {InputReal}; {---------------------} BEGIN {Установочная часть осуществляет регистрацию шрифта и отключает прямой доступ к видеопамяти для стандартных процедур WRITE и WRITELN.} DirectVideo:=false; SetIntVec(31, ©Font) {===========} END. {GraphTxt} {============} Следующая программа иллюстрирует применение описанных процедур. Программа вводит строку, целое число и вещественное число. Признаком разделения элементов ввода могут служить запя- тые и/или пробелы. Программа преобразует введенные символы во внутреннее представление соответствующих элементов, выводит их на экран и повторяет ввод до тех пор, пока не будет нажата клави- ша ESC. PROGRAM Unit_GraphTxt_Dem; Uses Graph, GraphTxt; var d, r, e, i, k': integer; a r rea 1; ESC : Boolean; «st, subst : string; BEGIN d <= CGA; r:=CGAHi; {Установка режима высокого разрешения CGA-адаптера} InitGraph (d, г, ’’); e := GraphResult; if e <> grOk then writein (GraphErrorMSG(e)) else begin repeat write('Строка, целое, вещественное:’); GraphRead (st, ESC); if not ESC then begin writein; i := 1; SubStr (st, subst, i); if i > 0 then Inputlnt (st, k, i); if i > 0 then InputReal(st, a, i); writeln(subst, k:10, a:10:4) end 371
until ESC; CloseGraph end ENO. 16.4. ПРОГРАММА ГЕНЕРАЦИИ ШРИФТОВ С помощью описываемой ниже программы Вы сможете создать любой шрифт, каждый символ которого будет представлен в виде матрицы из 8x8 пикселей. Созданный шрифт хранится на диске в виде файла типа FONTTYPE=ARRAY [1..128, 1..8] OF BYTE; и Вы сможете его использовать для вывода текстовых сообщений при ра- боте экрана с CGA-адаптером в графическом режиме. Программа может работать с адаптерами CGA, MCGA, EGA и VGA. После обращения к программе экран переводится в графически режим работы и приобретает вид, показанный на рис.26. В левой части экрана формируется рабочее поле из 8x8 клеток - это увели- ченное во много раз изображение знакоместа графического экрана. В этом поле Вы можете устанавливать светимость каждого пикселя в отдельности и таким образом формировать изображение символа. Выбор нужного пикселя осуществляется с помощью клавишей уп- равления курсором: текущий пиксель выделяется изображением ми- гающей клетки рабочего поля и этими клавишами можно сдвигать мигащее изображение к соседним клеткам. Чтобы зажечь пиксель, нужно нажать клавишу "Ввод". Повторное нажатие на эту клавишу погасит пиксель. В центре экрана вычерчивается небольшой прямоугольник, огра- ничивающий. поле экрана из 3x3 знакомест. При любом изменении рабочего поля в прямоугольнике выводится изображение девяти ко- пий создаваемого символа в натуральном масштабе. Наконец, правую часть экрана занимает полная таблица симво- лов с кодами от 0 до 255. В эту таблицу помещаются вновь сфор- мированные изображения символов, из нее же при необходимости можно брать символы и помещать их в рабочее поле - так осущест вляется редактирование изображений. Замечу, что взять из таблиць можно любой символ, а вот занести вновь созданный символ мож но только в правую ее часть, соответствующую кодам от 128 до 255 Чтобы указать, какой символ нужно взять из таблицы или в каку* ее клетку поместить новый символ, используются функциональны клавиши F1 и F2: если нажать на F1, программа переходит к выбо ру символов из таблицы с целью их переноса в рабочее поле; пр нажатии на F2 - к выбору того места, куда будет занесен новы символ. В обоих случаях начинает мигать одна из клеток таблиць Клавишами управления курсором мигание смещается к нужно клетке, после чего собственно операция переноса символа из табл! цы в рабочее поле или наоборот инициируется нажатием на клав! шу "Ввод". При перемещении мигающего изображения по клетка) таблицы в режимах F1 и F2 в правом верхнем углу экрана указь вается десятичный код того символа, который находится в выД< 372
денной клетке таблицы или будет помещен в нее. Отказаться от режимов F1 или F2 можно с помощью клавиши ESC. Клавиша F3 используется для выхода из программы. Самая верхняя строка экрана содержит подсказку с указанием назначения задействованных функциональных клавишей: Fl - Get char (взять символ); F2-Put char (поместить символ); F3 - Quit (выход). Основная программа состоит из трех частей. Вначале иницииру- ется графический режим работы экрана, причем вызывается CGA- драйвер. Предполагается, что этот драйвер находится в текущем ка- талоге (см. вызов процедуры INITGRAPH). Если инициация графи- ческого режима прошла успешно, программа Создает начальный об- раз экрана (процедура BUILD) и затем переходит собственно к ра- боте (процедура WORK). В процессе создания начального образа эк- рана программа расчерчивает рабочее поле (процедура BUILD- WORK) и создает таблицу символов (процедура BUILDTABLE). По- сле этого программа пытается отыскать в текущем каталоге файл 8x8.FNT. Если файл обнаружен, он рассматривается как файл типа FONTTYPE, содержащий ранее разработанный шрифт, и программа загружает его в память. В целях повышения защиты разрабатывае- мого шрифта от случайной порчи выходной файл, создаваемый в результате работы программы, называется 8x8FNT.NEW. Этот файл формируется программой после нажатия на клавишу F3 в том слу- чае, если были сделаны какие-либо изменения в таблице символов. PROGRAM Font_Generator; Uses Graph, CRT, DOS; type FontType = array [0..127, 0..7] of byte; ReadKType = (LeftK, RightK, UpK, DownK, F1K, F2K, F3K, EnterK, ESCK, Err); const wdw = 10; {ширина клетки рабочего поля} htw = 8; {высота клетки рабочего поля} wdt = 15; {ширина клетки таблицы} htt = 11; {высота клетки таблицы} xw = wdw; {левая граница рабочего поля} Flf : Boolean = false; {флаг режима Fl} F2f : Boolean = false; {флаг режима F2} iw : byte = 0; {горизонтальная координата пикселя в рабочем поле} jw : byte = 0; {вертикальная координата пикселя} 1F1 : byte = 4; {горизонтальная координата режима F1} JF1 : byte = 1; {вертикальная координата} 1F2 : byte = 8; {горизонтальная координата режима F2} jF: byte = 0; {вертикальная координата} Cng : Boolean = false; {флаг изменения шрифта} FlagFont : Boolean = false; {флаг считанного шрифта} InpName = '8x8.FNT’; {имя входного файла} 373
Fl - Get char; F2 - Put char; F3 - Quit 129 Рис. 26. Вид экрана при работе с программой генерации шрифтов
OutName - ’8x8FNT.NEW'; {имя выходного файла} var f : Iile of FontType; Font : FontType; {таблица знакогенератора} FntC : array [-4..10] of byte; {образ очередного символа: первые 4 байта - служебные, собственно образ начинается с байта с номером 0} ywx : integer; {верхняя граница рабочего поля} xt : integer; {левая граница таблицы} yt : integer; {верхняя граница таблицы} brdr : pointer; {указатель на рамку для рабочего поля} pw : pointer; {указатель на клетку для рабочего поля} pwO : pointer; pc : pointer; {указатель на символ} pt : pointer; {указатель на клетку таблицы} i, j : integer; {рабочие переменные} х( у : integer; FontB : array [0..7, 0..7] of Boolean; {массив признаков светящихся пикселей} PROCEDURE Build; {Обеспечивает поиск в каталоге и чтение шрифта из файла с именем x8.fnt” (процедура ReadFont), формирование рабочего поля (процедура ildWork) и подготовку и формирование таблицы (процедура BuildTable). водит верхнюю строку-подсказку.} {------------------------} PROCEDURE ReadFont; ~ {Переносит данные из файла ”8x8.fnt” в переменную Font. Если файл не наружен, обнуляет эту переменную. Подготавливает вектор-укдзатель 31.} BEGIN {начало процедуры READFONT} for i := 0 to 127 do for j := 0 to 7 do Font[i, j] := 0; assign (f., InpName); {$!-} reset (f); {$!+} if lOResult = 0 then begin read (f, Font); Close (f); r FlagFont := true end; SetlntVec (31, @Font) END {конец процедуры READFONT}; PROCEDURE BuildWork; {Подготавливает рабочее поле в левой части экрана: расчерчивает поле 8x8 и готовит образ пустой клетки (указатель pwO) и память для мигания (pw). } BEGIN {начало процедуры BUILDWORK} yw := (GetMaxY - htw*8 - 9) div 2; for i := 0 to 8 do 375
begin Line (xw, yw+i*htw, xw+wdw*8, yw+i*htw); Line (xw+i*wdw, yw, xw+i*wdw, yw+htw*8) end; GetMem (pw, ImageSize (xw+1, yw+1, xw+wdw, yw+htw)); GetMem (pwO, ImageSize (xw+1, yw+1, xw+wdw, yw+htw)); Getlmage (xw+1, yw+1, xw+wdw, yw+htw, pwO") t END {конец процедуры BUILDWORK}; {-------------------------} PROCEDURE BuildTable; {Определяет координаты и расчерчивает поле 16x16 для таблицы знакогенератора. Читает стандартный шрифт из первой половины ASCII-кодов и помещает его в левую часть таблицы. Если был считан файл "8x8.fnt" (признак в переменной FlagFont), то формируется и правая половина таблицы. В указателе PC сохраняется память для переноса символов из таблицы в рабочее поле и обратно. В,указателе РТ резервируется память для выделения, клетки таблицы.} var is, k : integer; BEGIN {начало процедуры BUILDTABLE} xt := GetMaxX - 16*wdt - xw - 1; yt := (GetMaxY - 16*htt - 8) div 2 + 8; . for i := 0 to 16 do begin Line (xt, yt+i*htt, xt+16*wdt, yt+i*htt); Line (xt+i*wdt, yt, xt+i*wdt, yt+16*htt) end; Rectangle (xt-1, yt-1, xt+8*wdt-l, yt+16*htt+l); Rectangle (xt+8*wdt+l, yt-1, xt+16*wdt+l,yt+16*htt+l); is := ImageSize (0, 0, 7, 7); Getlmage (0, 0, 7, 7, FntC); {готовит служебные байты в массиве FntC} . GetMem (pc, is); {Цикл подготовки символов: сначала каждый символ выводится в лев* верхний угол экрана, затем переносится на нужное место в таблице. Симвс лы 7, 8, 10 и 13 выводятся процедурой OUTTEXT, так как процедура WRI1 обрабатывает их особым образом.} for i := 0 to 8*succ(ord(FlagFont))-1 do for j := 0 to 15 do begin GotoXY (1, 1); • k := i*16 + j; if (k=7) or (k=8) or (k=10) or (k=13) then OutTextXY (0, 0, chr(k)) else write (chr(k)); Getlmage (0, 0, 7, 7, pc"); Putlmage (0, 0, pc", XORPut); Putlmage (xt+i*wdt+(wdt-8) div 2 +1, yt+j*htt+(htt-8) div 2 +1, pc", NormalPut); 376
end; GetMem (pt, ImageSize (0, 0, wdt, htt)) END {коьГец процедуры BUILDTABLE}; {-------------------} const tx = 'Fl - Get char; F2 - Put char; F3 - Quit’; BEGIN {начало процедуры BUILD} ReadFont; BuildWork; BuiIdTable; GotoXY ((80 - TextWidth(tx) div 8) div 2+1, 1); write (tx); Rectangle(0, 10, GetMaxX, GetMaxY) END {конец процедуры BUILD}; {-------------------} PROCEDURE Work; {Готовит вспомогательный массив - признак пикселей FontB, затем в бесконечном цикле читает клавиатуру и передает управление соответствующей процедуре.} {-------------------} FUNCTION RedK : ReadKType; {Обеспечивает мигание клетки рабочего поля или выделенного символа тех пор, пока не будет нажата очередная клавиша. Преобразует код кла- 1И в тип ReadKType.} const ReadKTable : array [0..6] of byte = (75, 77, 72, 80, 59, 60, 61); Temp = 5; {частота мигания} var p : pointer; {образ мигания} cl, c2 : char; RK : ReadKType; BEGIN {начало функции REDK} {подготовить в x, у - координаты, а в р~ - образ для мигания:} if not (Flf or F2f) then begin x := xw + iw*wdw + 3; у := yw + jw*htw + 2; p := pw; Getlmage (x, y, x+wdw-7, y+htw-4, p~) end else begin if Flf then begin x := xt + iFl*wdt; у := yt + jFl*htt* end 377
else begin x := xt + iF2*wdt; у := yt + jF2*htt end; p pt; Getlmage (x, y, x+wdt, y+htt, p~) end; {цикл сканирования клавиатуры} repeat while not KeyPressed do begin Putlmage (x, y, p", NotPut); for i := 1 to 50 do if not KeyPressed then delay (Temp); Putlmage (x, y, p\ NormalPut); if not KeyPressed then for i := 1 to 50 do if not KeyPressed then delay (Temp) end; {опознать клавишу в соответствии с типом ReadKType} RK := Err; cl := ReadKey; if ord(cl) = 0 then c2 := ReadKey else c2 := chr(0); if brd(cl) = 13 then RK := EnterK else if ord(cl) = 27 then RK := ESCK else if ord(cl) = 0 then for i := 0 to 6 do if ord(c2) = ReadKTable[i] then RK := ReadKType(i); {Продолжить сканирование,'если клавиша не соответствует типу ReadKType.} until RK <> Err; RedK := RK END {конец функции REDK}; • {----------------------} PROCEDURE MoveXY (x : integer); {Переводит указатель в рабочем поле или таблице: 1 - налево; 2 - направо; 3 - вверх; 4 - вниз.} {---------------------} PROCEDURE Left (var i : byte; a, b : byte); begin' if i = a then i := b else dec (i) end; {---------------------} PROCEDURE Right (var i : byte; a, b : byte); begin if i = b then i a else inc .(i) end; {---------------------} PROCEDURE Up (var i : byte; a : byte); 378
begin if i = 0 then i a else dec ri) end; {--------•------------} PROCEDURE Down (var i : byte; a : byte); begin if i = a then i := 0 else inc (i) end; {-------------------------------------} BEGIN {начало процедуры MOVEXY} if Flf then beg i n case x of 1 : Left (iFl, 0, 15); 2 : Right (iFl, 0, 15); 3 : Up (jFl, 15) ; 4 : Down (jFl, 15) end; GotoXY (76, 1); write (iFl*16 + jFl : 3) end else if F2f then begin case x of 1 : Left (iF2, 8, 15); 2 : Right (iF2, 8, 15); 3 : Up (jF2, 15); 4 : Down (jF2, 15) end ; GotoXY (76, 1); write (iF2*16 + jF2 : 3) end else begin case x of 1 : Left (iw, 0, 7) ; 2 : Right (iw, 0, 7) ; 3 : Up (jw, 7); 4 : Down (jw, 7) end end END {конец процедуры MOVEXY}; {----------------------} PROCEDURE CopyChar; {Копирует символ в виде матрицы 3x3 знака в центре экрана.} BEGIN {начало процедуры COPYCHAR1} х := xw + 8*wdw + 1; х :=x+(xt-x-24)div2; У .= (GetMaxY - 32) div 2; for i := 0 to 2 do for j := 0 to 2 do Putlmage (x + 8*j, у + 8*i, FntC, NormalPut); 379
Rectangle (x-1,. y-1, x+25, y+25) END {конец процедуры COPYCHAR}; {---------------------------------------} PROCEDURE Enter; {В режиме редактирования инвертирует пиксель, в режиме F1 переносит выбранный символ в рабочее поле, в режиме F2 переносит символ в таблицу.} {--------------------------------------} PROCEDURE GetChar; {Переносит выбранный в таблице символ в рабочее поле.} BEGIN {начало процедуры GETCHAR} х := xt + iFl*wdt + (wdt - 8) div 2+1; у := yt + jFl*htt + (htt - 8) div 2+1; Getlmage (x, y, x+7, y+7, FntC); {Проверить образ побитно и сформировать изображение в рабочем поле.} for i := 0 to 7 do for j := 0 to 7 do if (FntCfi] and (Д28 shr j)) <> 0 then beg i n Putlmage(xw+j*wdw+l, yw+i*htw+l, pwO", NOTPut); FontB [i, j] := true end else begin Putlmage (xw+j*wdw+l, yw+i*htw+l, pwO\ NormalPut); FontB[i. j] := false end; CopyChar; ‘ ' GotoXY (76, 1); write (' ’); Flf := false END {конец процедуры GETCHAR}; {-----------------‘ —‘ } PROCEDURE PutChar; {Помещает отредактированный символ в таблицу.} BEGIN {начало процедуры PUTCHAR} х := xt’+ iF2*wdt + (wdt - 8) div 2+1; у := yt + jF2*htt + (htt - 8) div 2+1; Putljaage (x, y, FntC, NormalPut); {скопировать символ в основную таблицу Font} i := iF2*16 + jF2 - 128; for j := 0 to 7 do Font [i, j] := FntC [j]; {очистить рабочее поле} for i := 0 to 7 do begin FntC[i] := 0; for j := 0 to 7 do begin 380
Putlmage (xw+j*wdw+l, yw+i*htw+l, pwO", NormalPut); FontB [i, j] := false end; end; Cng := true; CopyChar; GotoXY (76, 1); write(’ ’); F2f := false END {конец процедуры PUTCHAR}; {-------------------------} BEGIN {начало процедуры ENTER} if Flf then GetChar else if F2f then PutChar else begin if FontB[jw, iw] then begin Putlmage (xw+iw*wdw+l, yw+jw*htw+l, pwOA, NormalPut); FontB[jw, iw] := false end else begin Putlmage(xw-+iw*wdw+l, yw+ jw*htw+l, pwO~, NOTPut); FontB[jw, iw] := true end; FntC[jw] := FntC[jw] xor (128 shr iw); CopyChar end END {конец процедуры ENTER}; , {--------------------------} PROCEDURE Quit; {Завершает работу программы.} BEGIN if Cng then begin assign (f, OutName); rewrite' (f); write (f, Font); Close (f) end; CloseGraph; Halt END; {------------------------} BEGIN {начало процедуры WORK} for' i := 0 to 7 do for j := 0 to 7 do 381
FontВ [ i, j] := false; while true do case RedK of LeftK : : MoveXY (1); RightK : : MoveXY (2); UpK : MoveXY (3); DownK : MoveXY (4); F1K : begin Flf : := true; F2f := false end; F2K : begin Flf : := false; F2f := true end; F3K : Quit; EnterK : Enter; ESCK : begin Flf := false; F2f false end end {case} ' ENO {конец процедуры WORK}; {-----------------------------------------} var d, r, e : integer; BEGIN {начало основной программы} d := CGA; r := CGAHi; {Установка графики высокого разрешения CGA-адаптера} InitGraph (d, г, ''); e := GraphResult; if e <> grOk then writein (GraphErrorMSG (e)) else begin DirectVideo := false; Build; Work end END {конец основной программы}. {===========}
ПРИЛОЖЕНИЯ
1. ВАРИАНТЫ КОДИРОВКИ ЗНАКОГЕНЕРАТОРОВ ПЭВМ В этом приложении приводятся таблицы стандартного знакогенератора фирх^ IBM, а также варианты кодировки второй половины таблицы знакогенератора для рас. пространенных у нас в стране ПЭВМ. Вторая половина символов с кодами от 128 до 255 не регламентируется стандар. том ASCII и на ПЭВМ разных фирм может быть различной. По замыслу разработчц. ков эта часть кодов используется для размещения символов псевдографики и нацио. нального алфавита. В стандартном знакогенераторе фирмы IBM под символы нацио. нального алфавита отводятся первые три колонки по 16 символов в каждой (см.рис 276), в то время как в стандартном отечественном знакогенераторе, построенном по рекомендациям Международного комитета по стандартизации в области телефона и те. леграфа (МКТТ). в этих колонках располагаются символы псевдографики (рис. 28а) Такое несоответствие практически исключает эксплуатацию на отечественных ПЭВ^ разнообразного программного обеспечения зарубежного производства, использующего псевдографику. Вот почему этот так называемый ГОСТовский вариант кодировки обыч- но не используется. Наибольшее распространение на отечественных ПЭВМ получил вариант кодирс®. ки. который называется альтернативным1 (рис. 286). Этот вариант сохраняет располо- жение символов псевдографики на том же месте, что и в знакогенераторе IBM, однакс при этом символы кириллицы не образуют непрерывный массив. В ПЭВМ производства НРБ и ПНР, поставляемых в СССР, иногда используются другие варианты кодировки (рис. 29). 1 См. Брябрин В.М. Программное обеспечение персональных ЭВМ. - М.: НаукА 1988. - с.НО...122. 384
a) 809QAOBOCOOOEOFO 0 Ё 3 £ L К ОС 1 ii эе f i f" ₽ + £ ё ft б й “Р IT г > 3 3 л о <5 1 ь IL п < 4 а о лг п 1 — L E г 5 к % о /V N =1 + F Q J 6 а гъ и а 11 1= ГГ r У Q и О и II- 4 T 8 ₽ У U: + $ о 9 ё 0 г— я fr j e я А ё и —" II JL Г ft В 1 Ф £ =П тг I 5 С f £ м 1^ 0$ п D 1 ¥ i Л = 1 ff 2 к А Pt J JL тг 1 e fi F й J’ » т X n b) Рис. 27. Стандартная кодировка фирмы IBM: а)-для кодов 0...127; Ь)-для кодов 128...255. 13 - Фаронов 385
8090AOBOCODOEOFO 8090AOBOCODOEOFO b) Рис. 28. Варианты кодировки для кодов 128...255: а) - по рекомендацям МКТТ; Ь) - альтернативный вариант. 386
8D90AOBOCODOEOFO 0 Б ы н L 11 а •= 1 Г ь п $ 1 тг ₽ + 2 Д э т 3 т пг Г > 3 Ж ю У 1 1- И л < 4 3 я ф 4 — Ь Е г 5 и Б ц 4 + F а J 6 и В ч. к гг г ж ж 7 л Г ш п IF т л» 8 п А щ ч L 4= £ О 9 У ж "1^ 41 (г j е ж А ф в ы II JL г ft В ц И ь ч1 тг 1 б С ч А 3 1г n D IU К ю и —— 1 0 2 Е m Л я J JL ТГ 1 Е F ъ м N ч JL Л a) 8090AOBOCODOEOFO 0 А р a р L ЙЁ ОС 1 Б с Б с 1 $ ₽ + 2 В т В т т □ Г > 3 Г У Г У 1- D п < 4 Д ф А ф —— Н Е г 5 Е X е X 4- 13 а J 6 Ж ц ж ц 41 г ж ТГ: 7 3 ч в ч II я т .V лг 8 И ш и ш 1Ь г О 9 И 111 А 14 Гг ц в ж А к ъ К ъ JL в ft * В л ы Л ы ТГ □ Г "JT С м ь м ь к в 04 п D н э н 3 а X 2 Е 0 ю О ю JL тг D Е 1 F п я п я ч 1 Q П b) Рис. 29. Варианты кодировки для кодов 128...255, используемые на некоторых ПЭВМ производства ВНР и ПНР: а) - на ПЭВМ ЕСТЕЛ. "Mazovia" ("старый" вариант ВЦ АН СССР): Ь) - на ПЭВМ "Правец” (кодировка MIK) 13* 387
2. СТАНДАРТНЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ Здесь приводится список всех стандартных для Турбо-Паскаля процедур и функ- ций в алфавитном порядке. В список не включены процедуры и функции из модулей GRAPH3 и TURBO3. с помощью которых обеспечивается совместимость с ранней вер- сией Турбо-Паскаля 3.0. Подавляющее большинство процедур и функций рассматрива- лось в книге - в списке в этом случае приводится ссылка на соответствующую страни- цу. Некоторые упоминаются впервые, для них даются исчерпывающие пояснения и примеры использования. Процедуры и функции, вошедшие в библиотечный модуль SYSTEM, доступны для любой программы. Остальные становятся доступными только после объявления имени соответствующего модуля в предложении USES ..., открываю- щем текст программы (см. 10.7), и для них в списке указывается имя этого модуля. ABS. Функция, возвращающая абсолютное значение аргумента, Обращение ABS(X) где X - выражение любого целого или любого вещественного типа. Результат имеет тип аргумента (см. с. 83 и 89.) ADDR. Функция, возвращающая адрес аргумента X. Обращение и тип результата ADDR(X) : pointer где X - переменная любого типа или идентификатор любой нестандартной проце- дуры (функции). Результат имеет тип POINTER, совместимый с любым указателем; функция осу- ществляет действия, аналогичные операции например: var р : "integer; х : integer; begin р := addr(х); wrjteln(seg(p"), ofs(p")); p := @x; writeln(seg(p"), ofs(p")); writeln(seg(x), ofs(x)) end. APPEND. Процедура, открывающая существующий текстовый файл для расшире- ния(см. с. 113). Обращение Append(var F : text) ARC. Процедура из модуля GRAPH. Обращение Arc(X, Y : integer: StAngle, EndAngle. Radius : word) Вычерчивает дугу окружности радиуса RADIUS с центром в точке с координата- ми X,Y начиная с угла STANGLE до угла ENDANGLE(cm. с.234). ARCTAN, Функция, возвращающая главное значение арктангенса от аргумента X в радианах (см. с. 89). Обращение и тип результата ArcTan(X : real) : real где X - выражение любого вещественного типа. ASSIGN. Процедура, связывающая имя NAME дискового файла или логического устройства с'файловой переменной F (см. с. 109). Обращение Assign(var F; NAME : string) ASSIGNCRT. Процедура из модуля CRT. Обращение AssignCRT(var F : text) Связывает с файловой переменной F клавиатуру как источник информации иди экран как приемник (см. с.208). BAR. Процедура из модуля GRAPH. Обращение 388
Bar(Xl, Yl, X2, Y2 : integer) Штрихует, (но не обводит) прямоугольник текущим цветом и текущим образ- цОм(см. с. 247). BAR3D. Процедура из модуля GRAPH. Обращение Bar3D(Xl, Yl, Х2, Y2: integer: DEPTH : word: TOP: Boolean) Обводит и закрашивает трехмерный параллелепипед (см. с. 231). BLOCKREAD. Процедура, считывающая данные из нетипизированного файла(см. с. 129). Обращение BlockRead(var F : file; var Buf: Count : word [; var Result : word] ) BLOCKWRITE. Процедура, записывающая данные в нетипизированный файл (см. с. 129). Обращение BlockWrite(var F : file; var Buf; Count : word [: var Result : word] ) 4 CHDIR. Процедура, изменяющая текущий каталог (см. с. 115). Обращение . ChDir(S : string) CHR. Функция, возвращающая символ с заданным порядковым номером (см. с.86). Обращение и тип результата Chr(X : byte) : char CIRCLE. Процедура из модуля GRAPH. Обращение Circle(X, Y : integer; Radius : word) Вычерчивает окружность радиуса RADIUS с координатами центра X и Y (см. с. 233). CLEARDEVICE. Процедура из модуля GRAPH. Обращение ClearDevice Очищает графический экран (см. с. 222). CLEARVIEWPORT. Процедура из модуля GRAPH. Обращение ClearViewPort Очищает графическое окно (см. с. 222). CLOSE. Процедура, закрывающая файл, который связан с файловой переменной F (см. с. ИЗ). Обращение Close(var F) CLOSEGRAPH. Процедура из модуля GRAPH. Обращение CloseGraph Закрывает графический режим работы экрана и переводит его в текстовый режим (СМ. С. 217). CLREOL. Процедура из модуля CRT. Обращение ClrEOL Очищает текущую строку от курсора до конца строки (см. с. 207). CLRSCR. Процедура из модуля CRT. Обращение ClrScr Очищает окно или текстовый экран (см. с. 206). CONCAT. Функция, объединяющая несколько строк в одну строку (см. с. 103). Обращение и тип результата 389
Concat(Sl [,S2, ...••-SN] : string) : string * - . И :. . COPY. Функция, которая возвращает подстроку, выделенную из строки S начиная с позиции INDEX, длиной COUNT символов (см. с. 103). Обращение и тип результата Copy(S : string; Index, Count : integer) : string DELLINE. Процедура из блока CRT. Обращение DelLine Уничтожает строку, на которой расположен курсор (см. с. 207). DETECTGRAPH. Процедура из модуля GRAPH. Обращение DetectGraph(var GraphDriver. GraphMode : integer) Возвращает текущий графический драйвер и его режим , работы (см. с. 218). DISKFREE. Функция из модуля DOS. Обращение и тип результата DiskFree(Drive : word) : longint Возвращает значение типа LONGINT, содержащее объем свободного пространства на диске DRIVE (см. с. 116). DISKSIZE. Функция из модуля DOS. Обращение и тип результата DiskSize(Drive : word) : longint Возвращает значение типа LONGINT, содержащее полный объем диска DRIVE (см. с. 116). DISPOSE. Процедура, удаляющая динамическую переменную из кучи (см. с. 136 и 142). Обращение Dispose(P : pointer) DOSEXITCODE. Функция из модуля DOS. Обращение и тип результата DOSExitCode : word Возвращает значение типа WORD, содержащее код завершения запущенной про- граммы (см. с. 185). DOSVERSION. Функция из модуля DOS. Обращение и тип результата DOSVersion : word Возвращает значение типа WORD, содержащее номер версии операционной систе- мы. Младший байт содержит ведущий номер версии, старший - номер реализации, на- ример: Uses DOS; begin writе 1 n(Lo(DOSVersion), Hi(DOSVersion)) end. DRAWPOLY. Процедура из модуля GRAPH. Обращение DrawPoly(NumPoints : word: var PolyPoints) Вычерчивает ломаную по заданным координатам (см. с. 232). DSEG. Функция, которая возвращает значение типа WORD, содержащее текущее состояние регистра DS центрального процессора. Обращение и тип результата 4 DSeg : word ELLIPSE. Процедура из модуля GRAPH. Обращение: Ellipse(X, Y : integer: StAngle, EndAngle : word; XRadius. YRadius : word) Вычерчивает дугу эллипса (см. с. 2369). ENVCOUNT. Функция из модуля DOS. Обращение и тип результата EnvCount : integer Возвращает значение типа INTEGER, содержащее общее количество установлен- ных в ДОС параметров (см. с. 188). 390
ENVSTR. Функция из модуля DOS. Обращение и тип результата , EnvStr(Index : integer) : string Возвращает значение типа STRING, содержащее установленный в ДОС параметр с номером INDEX (см. с. 188). EOF. Функция, которая возвращает значение типа BOOLEAN, сигнализирующее с конце файла, связанного с переменной F (см. с. 115). Обращение и тип результата EOF(var F) : Boolean EOLN. Функция, которая возвращает значение типа BOOLEAN, сигнализирующее о конце строки текстового файла, связанного с переменной F (см. с. 124). Обращение и тип результата EOLN(var F : text) : Boolean ERASE. Процедура. Обращение: Erase(var F) Уничтожает файл, связанный с переменной F (см. с. 114). ЕХЕС. Процедура из модуля DOS. Обращение: Exec(Path. CmdLine : string) Запускает готовую к работе программу (см. с. 186). EXIT. Процедура, осуществляющая немедленный выход из исполняемой процеду* ры или функции в вызывающую программу. Обращение Exit Вызов процедуры EXIT в основной программе ведет к останову программы, нари- мер: Uses CRT; Procedure Wnd; begin Window(20, 10, 60, 15); . repeat 1 if KeyPressed then exit; write(' Нажмите любую клавишу ... ') until False end; begin ClrScr; GotoXY(l,2); writeln(’Вход в процедуру :'); Wnd; z Window(1,1,80,25); x GotoXY(1,24); writeIn(’Выход из процедуры.’) end. EXP. Функция. Обращение и тип результата Ехр(Х : real) : real Возвращает экспоненту от вещественного аргумента (см. с. 89). FEXPAND. Функция из модуля DOS. Обращение и тип результата FExpand(Path : PathStr) : PathStr Дополняет имя файла, заданное в строке PATH, полным путем к нему. Возвра- щает результат типа PATHSTR' (тип PATHSTR определен в модуле D6S как STRING[79]) (см. с. 120). FILEPOS. Функция. Обращение и тип результата FilePos(var F) : longint Возвращает значение типа LONGINT, в котором дается текущее положение фай- лового указателя (см. с. 128). 391
FILESIZE. Функция. Обращение и тип результата FileSize(var F) : longint Возвращает значение типа LONGINT, в котором дается размер файла, связанного с переменной F (см. с. 128). FILLCHAR. Процедура, заполняющая произвольную переменную X побайтно начи- ная с первого байта переменной. Обращение FillChar(var X; count : word; CH : value) где X - заполняемая переменная; может иметь любой тип; COUNT - количество заполняемых байт в переменной X: СН - выражение любого перечисляемого типа, задающее образец заполнения. При заполнении переменной X процедура не контролирует длину этой перемен- ной - контроль за правильным значением счетчика COUNT возлагается на программи- ста. Если X - строка, то при заполнении будет изменен ее нулевой байт, содержащий текущую длину строки, например: var s : string; begin FillChar(s, 20, ’Г); writeln(ord(s [0]), ’ ',s) end. FILLELLIPSE. Процедура из модуля GRAPH. Обращение FillEllipse(X, Y : integr: XRadius. YRadius : word) Обводит линией и штрихует эллипс (см. с. 248). FILLPOLY. Процедура из модуля GRAPH. Обращение FillPoly(NumPoints ; word; var PolyPoints) Обводит линией и штрихует замкнутый многоугольник (см. с.247). FINDFIRST. Процедура из модуля DOS. Обращение FindFirst(Path : string; Attr ; word; var S : SearchRec) Возвращает атрибуты первого из файлов, зарегистрированных в текущем каталоге (см. с. 116). FINDNEXT. Процедура из модуля DOS. Обращение FindNext(var S ; SearchRec) Возвращает атрибуты следующего файла в каталоге (см. с. 118). FLOODFILL. Процедура из модуля GRAPH. Обращение FloodFill(X. Y : integer: Border : word) Штрихует произвольную замкнутую фигуру (см. с. 246). FLUSH. Процедура, записывающая данные из внутреннего буфера в файл (см. с. 115). Обращение Flush(var F) FRAC. Функция, возвращающая дробную часть вещественного аргумента. Обраще- ние и тип результата Frac(X : real) : real где X - выражение любого вещественного типа. Результат имеет тип и знак аргумента, например: begin writeln(Frac(Pi):10:8, Frac(-Pi):20:8) end. FREEMEM. Процедура, освобождающая участок кучи длиной SIZE байт, связан- ный с указателем Р (см. с. 138 и 143). Обращение 392
FreeMem(var P : pointer; Size : word) FSEARCH. Функция из модуля DOS. Обращение и тип результата FSearch(Path : PathStr; DirList : string) : PathStr Ищет заданный файл в каталоге (см. с. 119). FSPLIT. Процедура из модуля DOS. Обращение FSplit(Path : PathStr: var Dir : DirStr: var Name : NameStr: var Ext : ExtStr) Возвращает в качестве отдельных параметров путь к файлу, его имя и расшире- ние (см. с. 120). GETARCCOORDS. Процедура из модуля GRAPH. Обращение GetArcCoords(var ArcCoords : ArcCoordsType) Возвращает координаты центра, начала и конца дуги (см. с. 235). GETASPECTRATIO. Процедура из модуля GRAPH. Обращение Get Aspect Ratio( var Xasp. Yasp : word) Возвращает два числа, позволяющих оценить отношение сторон графического эк- рана (см. с. 222). GETBKCOLOR. Функция из модуля GRAPH. Обращение и тип результата GetBkColor : word Возвращает значение типа WORD, содержащее текущий цвет фона (см. с. 239). GETCBREAK. Процедура из модуля DOS. Обращение GetCBreak(var Break : Boolean) Возвращает текущее/ состояние функции CTRL-BREAK. устанавливаемое процеду- рой SETCBREAK. Если переменная BREAK имеет значение FALSE, нажатие на клави- ши CTRL-BREAK проверяется только при обращении программы к вводу с клавиату- ры, выводу на экран, принтер или при работе с коммуникационным каналом; если зна- чение TRUE - при любом обращении к операционной системе. Нажатие на CTRL-BREAK позволяет прервать работу программы. GETCOLOR. Функция из модуля GRAPH. Обращение и тип результата GetColor : word Возвращает текущий цвет, установленный процедурой SETCOLOR (см. с. 238). GETDATE. Процедура из модуля DOS. Обращение GetDate(var Year, Month, Day, DayOfWeek . word) Возвращает текущую дату: YEAR - год в диапазоне от 1980 до 2099: MONTH-месяц в диапазоне 1...12; DAY - число в диапазоне 1...31: DAYOFWEEK-день недели в диапазоне 0...6 (0 - воскресенье). Пример: Uses DOS; var Y, M, D, W : word; const Wk : array [0..6] of string[ll] - ('воскресенье', 'понедельник', 'вторник', 'среда', 'четверг ', 'пятница ’, 'суббота'); begin GetDate(Y, М, D, W); writeln(D, М, Y, ’, ’, Wk[W]) end. 393
GETDEFAULTPALETTE. Процедура из модуля GRAPH. Обращение GetDefaultPalette(var Pal : PaletteType) Возвращает структуру палитры, устанавливаемой по умолчанию (см. с. 242). GETDIR. Процедура, возвращающая путь S к текущему каталогу на диске D (см. с. 115). Обращение GetDir(D : word: var S : string) GETDRTVENAME. Функция из модуля GRAPH. Обращение и тип результата GetDriveName : string Возвращает имя загруженного графического драйвера (см. с. 219). GETENV. Функция из модуля DOS. Обращение и тип результата GetEnv(EnvVar : string) : string Возвращает значение заданного параметра настройки ДОС (см. с. 188). GETFATTR. Процедура из модуля DOS. Обращение GetFAttr(var F; var Attr : word) Возвращает атрибуты файла (см. с. 119). GETFILLPATTERN. Процедура из модуля GRAPH. Обращение GetFillPattern(var FillPattem : FillPattemType) Возвращает образец закраски, установленный ранее процедурой SETFILLPATTERN (см. с. 245). GETFILLSETTINGS. Процедура из модуля GRAPH. Обращение GetFillSettings(var Fillinfo : FillSettingsType) Возвращает образец и цвет штриховки (см. с. 245). GETFTIME. Процедура из модуля DOS. Обращение ' GetFTime(var F; var Time : longint) Возвращает время создания или последнего обновления файла (см. с. 119). GETGRAPHMODE. Функция из модуля GRAPH. Обращение и тип результата GetGraphMode : integer Возвращает код установленного режима работы графического адаптера (см. с. 217). GETIMAGE. Процедура из модуля GRAPH. Обращение Getlmage(xl, yl. х2, у2 : integer; var BitMap) Копирует в память часть графического экрана (см. с. 250). GETINTVEC. Процедура из модуля DOS. Обращение GetIntVec(IntNo : byte; var Vector : pointer) Возвращает вектор прерывания с указанным номером (см. с. 185). GETLINESETTINGS. Процедура из модуля GRAPH. Обращение GetLineSettings(var Lineinfo : LineSettingsType) Возвращает текущий стиль линий (см. с. 229). GETMAXCOLOR. Функция из модуля GRAPH. Обращение и тип результата GetMaxColor : word Возвращает максимальный доступный код цвета (см. с. 238). GETMAXMODE. Функция из модуля GRAPH. Обращение и тип результата GetMaxMode : integer Возвращает количество возможных режимов работы графического адаптера (см. с 219). GETMAXX. Функция из модуля GRAPH. Обращение и тип результата GetMaxX : integer 394
Возвращает максимальную горизонтальную координату экрана (см. с. 220). GETMAXY. Функция из модуля GRAPH. Обращение и тип результата GetMaxY : integer Возвращает максимальную вертикальную координату экрана (см. с. 220). GETMEM. Процедура, резервирующая участок кучи (см. с. 138 и 143). Обращение GetMem(var Р : pointer: Size : word) GETMODENAME. Функция из модуля GRAPH. Обращение и тип результата GetModeName(ModeNumber : word) : string Возвращает имя текущего режима работы графического адаптера (см. с. 219). * GETMODERANGE. Процедура из модуля GRAPH. Обращение GetModeRange(GraphDriver : integer: var LoMode. HiMode : integer) Возвращает диапазон возможных режимов работы заданного графического драйве- ра (см. с. 219). GETPALETTE. Процедура из модуля GRAPH. Обращение GetPalette(var Palette : PaletteType) Возвращает размер и цвета текущей палитры (см. с. 240). GETPALETTESIZE. Функция из модуля GRAPH. Обращение и тип результата GetPaletteSize : integer Возвращает размер текущей палитры (см. с. 242). GETPIXEL. Функция из модуля GRAPH. Обращение и тип результата GetPixel(X, Y : integer) : word Возвращает цвет указанного пикселя (см. с. 226). GETTEXTSETTINGS. Процедура из модуля GRAPH. Обращение GetTextSettings(var Textinfo : TexrSettingsType) Возвращает текущий стиль и выравнивание текста (см. с. 257). GETTIME. Процедура из модуля DOS. Обращение GetTime(var Hour, Minute. Second. SeclOO : word) Возвращает текущее время: HOUR - час в диапазоне 0...23: MINUTE - минуты в диапазоне 0...59: SECOND - секунды в диапазоне 0...59: SEC100 - сотые доли секунды в диапазоне 0...99. Например: Us.es Dos; var Н, М, S, S100 : word; begin GetTime(H,M,S,S100); Writeln(H, ’:’, M, ’:’, S, ’ . ' , S100) end. GETVERIFY. Процедура из модуля DOS. Обращение GetVerify(var Verify : Boolean) Возвращает признак записи на диск с контролем. Этот признак устанавливается процедурой SETVERIFY. Если параметр VERIFY имеет значение TRUE, запись на сопровождается контрольным чтением с диска, если FALSE, контроль записи не производится. GETVIEWSETTINGS. Процедура из модуля GRAPH. Обращение GetViewSettings(var Viewport : ViewPortType) 395
Возвращает координаты и признак отсечки текущего графического окна (см. 5 221). GETX. Функция из модуля GRAPH. Обращение и тип результата GetX : integer Возвращает горизонтальную координату графического указателя (см. с. 220). GETY. Функция из модуля GRAPH. Обращение и тип результата GetY : integer Возвращает вертикальную координату графического указателя (см. с. 220). GOTOXY. Процедура из модуля CRT. Обращение GotoXY(X. Y : byte) Позиционирует курсор в текстовом режиме (см. с.205). GRAPHDEFAULTS. Процедура из модуля GRAPH. Обращение GraphDefaults Восстанавливает стандартные значения следующих параметров графического режи- ма: - графическое окно; - палитра; - цвет линий и цвет фона: -стиль линий: - стиль и цвет штриховки; - активный шрифт, стиль и выравнивание текста. Перед обращением к процедуре необходимо установить графический режим рабо ты экрана. GRAPHERRORMSG. Функция из модуля GRAPH. Обращение и тип результата GraphErrorMsg(ErrorCode : integer) : string Возвращает текстовое сообщение об ошибке (см. с. 216). GRAPHRESULT. Функция из модуля GRAPH. Обращение и тип результата Graph Result : integer Возвращает код ошибки для графического режима работы экрана (см. с. 216). HALT. Процедура, прекращающая исполнение программы. Обращение: Halt [ (ExitCode : word) ] Необязательный параметр EXITCODE возвращает код завершения программы, ко- торый может быть проанализирован запускающей программой с помощью функции DOSEXITCODE или через параметр ERRORLEVEL в командном ВАТ-файле. Если па- раметр EXITCODE опущен, считается, что EXITCODE = 0. HI. Функция, обращение и тип результата Hi(X) : byte Возвращает старший байт аргумента X (см. с. 83). HIGH VIDEO. Процедура из модуля CRT. Обращение HighVideo ' Устанавливает максимальную яркость текста (см. с. 208). INSERT. Процедура, обращение Insert(Source : string: var S : string: Index : integer) Вставляет подстроку SOURCE в строку S. начиная с позиции INDEX (см. с. 103). IXSLIXE. Процедура из модуля CRT. Обращение InsLine 396
Вставляет пустую строку на место той, на которой стоит курсор (см. с. 207). INT. Функция, возвращающая целую часть вещественного аргумента X. Обраще- ние и тип результата Int(X : real) : real где X-выражение любого вещественного типа. Значение функции имеет тип'аргумента (см. с. 89). INTR. Процедура, вызывающая программное прерывание с нужным номером (см. с. 182). Обращение Intr(IntNo : byte; var Regs : Registers) IO RESULT. Функция, возвращающая код ошибки операций ввода-вывода (см. с. 112 и 116). Обращение и тип результата lOResult : integer KEEP. Процедура из модуля DOS. Обращение Keep(ExitCode : word) Завершает программу и оставляет ее резидентной в памяти (см. с. 185). KEYPRESSED. Функция из модуля CRT. Обращение и тип результата KeyPressed : Boolean Возвращает признак нажатой клавиши (см. с. 277). LENGTH. Функция, возвращающая текущую длину строки (см. с. 103). Обращение и тип результата Length(S : string) : integer LINE. Процедура из модуля GRAPH. Обращение Line(xl, yl, х2, у2 : integer) Вычерчивает линию между двумя произвольными точками (см. с. 226). LINEREL. Процедура из модуля GRAPH. Обращение LineRel(DX, DY : integer) Вычерчивает линию от текущего положения указателя к точке, заданной прира- щением координат (см. с. 227). LINETO. Процедура из модуля GRAPH. Обращение LineTo(X. Y : integer) Вычерчивает .линию от текущего положения указателя к нужной точке (см. с. LN. Функция, возвращающая логарифм натуральный от вещественного аргумента (см. с. 89). Обращение и тип результата Ln(X : real) : real LO. Функция, возвращающая младший байт аргумента (см. с. 83). Обращение и тип результата Lo(X) : byte LOWVIDEO. Процедура из модуля CRT. Обращение LowVideo Устанавливает пониженную яркость текста (см. с. 208). MARK. Процедура, запотйинающая текущее состояние указателя кучи (см. с. 137 и 143). Обращение < Mark(var Р : pointer) MAXAVAIL. Функция, возвращающая размер в байтах наибольшего непрерывного Участка кучи (см. с. 143). Обращение и тип результата 397
MaxAvail : longint MEMAVAIL. Функция, возвращающая общую длину в байтах свободного простран- ства кучи (см. с. 143). Обращение и тип результата MemAvail : longint MKDIR. Процедура, создающая ’ новый каталог (см. с.115). Обращение MkDir(S : string) MOVE. Процедура, которая переносит COUNT байт из источника SOURCE в при- емник DEST, начиная с первого байта. Обращение Move(var Source. Dest: Count : word) где SOURCE. DEST - переменные любого типа. Процедура не контролирует фактическую длину переменных, поэтому пользовать- ся ею нужно с осторожностью. Если переменная DEST - строка, процедура изменит ее фактическую длину, хранящуюся в первом байте, например: var s : string; х : real; begin x : = p i ; Move(x, s, SizeOf(x)); writeln(SizeOf(x),length(s):6) end. MOVEREL. Процедура из модуля GRAPH. Обращение MoveRel(DX. DY : integer) Переводит графический указатель в точку, заданную приращением координат (см. с. 222). MOVETO. Процедура из модуля GRAPH. Обращение MoveTo(X, Y : integer) Переводит графический указатель в заданную точку (см. с. 222). MSDOS. Процедура из модуля GRAPH. Обращение MsDos(var Regs : Registers) Вызывает программное прерывание с номером 33 ($21) (см. с. 183). NEW. Процедура, размещающая в памяти динамическую переменную (см. с. 136 и 143). Обращение New(var Р : pointer) NORMVIDEO. Процедура из модуля CRT. Обращение Norm Video Устанавливает нормальную яркость текста (см. с. 208). NOSOUND. Процедура из модуля CRT. Обращение NoSound Выключает звук (см. с. 289). ODD. Функция, возвращающая TRUE, если аргумент X - нечетное число (см. с. 83). Обращение и тип результата Odd(X : longint) : Boolean OFS. Функция, возвращающая смещение адреса аргумента X (см. с. 140 и 144). Обращение и тип результата Ofs(X) : word 398
ORD. Функция, возвращающая порядковый номер аргумента порядкового типа (см. с. 80). Обращение и тип результата Ord(X) : longint OUTTEXT. Процедура из модуля GRAPH. Обращение OutText(TextString : string) Выводит текстовую строку на графический экран (см. с. 253). OUTTEXTXY. Процедура из модуля GRAPH. Обращение OutTextXY(X, Y : integer, TextString : string) Выводит текстовую строку в заданное место графического экрана (см. с. 253). OVRCLEARBUF. Процедура из модуля OVERLAY. Обращение OvrClearBuf Очищает оверлейный буфер. Эта процедура обычно не используется, но в некото- рых специальных случаях она может пригодиться. Вызов процедуры приводит к немед- ленной очистке оверлейного буфера и последующей загрузке в него очередного требу- емого оверлейного модуля. Если процедура вызвана из оверлейного модуля, этот мо- дуль будет перезагружен тотчас же после очистки буфера. OVRGETBUF. Функция изх модуля OVERLAY. Обращение и тип результата OvrGetBuf : longint Возвращает размер оверлейного буфера в байтах (см. с. 192). OVRINIT. Процедура из модуля OVERLAY. Обращение 4 OvrInit(FileName : string) Инициализирует оверлейный файл (см. с. 190). OVRINITEMS. Процедура из модуля OVERLAY. Обращение OvrlnitEMS Обеспечивает использование расширенной памяти (см. с. 192). . OVRSETBUF. Процедура из модуля OVERLAY. Обращение OvrSetBuf(BufSize : longint) Увеличивает размер оверлейного буфера (см. с. 192). PACKTIME. Процедура из модуля DOS. Обращение PackTime(var DT : DateTime: var Time : longint) Упаковывает дату и время в четырехбайтное слово. В модуле DOS определен следующий тип: DATETIME = record Year, Month, Day, Hour, Min, Sec : word end Здесь элементами записи являются соответственно год, месяц, день, час, минуты и секунды. Для распаковки используетсй процедура UNPACKTIME. Пример: Uses DOS; var Time : longint; DT : DateTime; begin with DT do begin Year := 1989; Month := 7; Day := 30; 399
Hour := 8; Min := 26; Sec := 32; PackTime(DT, Time); UnpackTime(Time, DT); writeln(Day, Month, Year, ’ Hour, ’:’, Min, ’:’, Sec), end end. PARAMCOUNT. Функция, возвращающая количество параметров, переданных про- грамме при ее вызове (см. с. 186). Обращение и тип результата ParamCount : word PARAMSTR. Функция, возвращающая указанный в INDEX параметр вызова про- граммы (см. с. 186). Обращение и тип результата ParamStr(Index : word) : string PI. Функция, возвращающая значение числа "пи" = 3.1415926535897932385. Обраще- ние и тип результата Pi : real PIVESLICE. Процедура из модуля GRAPH. Обращение PieSlice(X, Y : integer; StAngle, EndAngle, Radius : word) Вычерчивает и штрихует сектор окружности (см. с. 249). POS. Функция, выделяющая в строке S подстроку SUBSTR (см. с. 103). Обраще- ние и тип результата Pos(Substr, S : string) : byte PRED. Функция, возвращающая предыдущее значение порядкового типа (см. с. 98). Обращение Pred(X) PTR. Функция, преобразующая сегмент и смещение адреса в указатель (см. с. 140 и 144). Обращение и тип результата Ptr(Seg, Ofs : word) : pointer PUTIMAGE. Процедура из модуля GRAPH. Обращение PutImage(X, Y : integer; var BitMap; BitBlt : word) Выводит на графический экран копию фрагмента изображения (см. с. 250). PUTPIXEL. Процедура из модуля GRAPH. Обращение PutPixel(X. Y : integer; Color : word) Устанавливает новый цвет пикселя (см. с. 225). RANDOM. Функция, возвращающая псевдослучайное число (см. с. 83 и 89). Обра- щение Random [(Range : word)] RANDOMIZE. Процедура, инициализирующая встроенный генератор псевдослучай- ных чисел (см. с. 89). Обращение Randomize READ. Процедура, считывающая данные из файла или логического устройства (см. с. 121). Обращение Read( [var F,] vl [, v2.vn]) READKEY. Функция из модуля CRT. Обращение и тип результата ReadKey : char Считывает символ с клавиатуры (см. с. 278). 400
READLN. Процедура, считывающая данные из файла или логического устройства (см. с. 122). Обращение Readln( [var F : text,] vl [, v2,...,vn]) RECTANGLE. Процедура из модуля GRAPH. Обращение Rectangle(xl, yl, x2, y2 : integer) Вычерчивает прямоугольник (см. с. 230). RELEASE. Процедура, возвращающая кучу в состояние, помеченное процедурой MARK (см. с. 137 и 144). Обращение Release(var Р : pointer) RENAME. Процедура, переименовывающая файл (см. с. 113). Обращение Rename(var F; NewName : string) RESET. Процедура, открывающая файл для чтения (см. с.112). Обращение Reset(var F) RESTORECRTMODE. Процедура из модуля GRAPH. Обращение RestoreCrtMode Осуществляет перевод экрана в текстовый режим (см. с. 217). REWRITE. Процедура, открывающая файл для записи (см. с. 113). Обращение Rewrite(var F) RMDIR. Процедура, удаляющая пустой каталог (см. с. 115). Обращение RmDir(DirName : string) ROUND. Функция, округляющая вещественный аргумент до ближайшего целого. Обращение и тип результата Round(X : real) : longint Пример: begin write In(Round(2.49), Round(2.5):5) end. RUNERROR. Процедура, останавливающая исполнение программы; генерирует код ошибки периода исполнения. Обращение RunError [(ErrorCode : word)] В результате исполнения Процедуры на экран будет выведено сообщение Runtime error XXX at SSSS:OQOO Здесь XXX - код ошибки, устанавливаемый параметром ERRORCODE; SSSS:OOOO - адрес размещения процедуры в памяти. Если параметр ERRORCODE опущен, считается, что ERRORCODE=0. Пример: begin RunError(l) end. SECTOR. Процедура из модуля GRAPH. Обращение Sector(X, Y : integer; St Angle, EndAngle, XRadius, YRadius : word) Вычерчивает и штрихует сектор эллипса (см. с.248). SEEK. Процедура, перемещающая указатель файла к требуемой, записи (см. с. *28). Обращение Seek(var F; N : longint) SEEKEOF. Функция, пропускающая все пробелы и разделители строк до первого 401
значащего символа и возвращающая, TRUE, если обнаружен конец файла (см. с. 125). Обращение и тип результата SeekEOF [(var F : text)] : Boolean SEEKEOLN. Функция, пропускающая все пробелы до первого значащего символа и возвращающая TRUE, если обнаружен конец строки (см, с. 125). Обращение и тип результата SeekEOLn [(var F ; text)] : Boolean SEG. Функция, возвращающая сегментную часть адреса аргумента X. Обращение и тип результата Seg(X) : word где X - идентификатор переменной, процедуры или функции (см. с. 140 и 144). SETACTIVEPAGE. Процедура из модуля GRAPH. Обращение SetActivePage(Page : word) Устанавливает активную страницу графического экрана (см. с. 224). SETALLPALETTE. Процедура из модуля GRAPH. Обращение SetAllPalette(var Palette) Изменяет все цвета палитры (см. с. 241). SETASPECTRATIO. Процедура из модуля GRAPH. Обращение SetAspectRatio(Xasp. Yasp : word) Изменяет масштабный коэффициент отношения сторон графического экрана (см. с. 223). SETBKCOLOR. Процедура из модуля GRAPH. Обращение SetBkColor(Color : word) Изменяет цвет фона графического экрана (см. с. 238). SETCBREAK. Процедура из модуля DOS. Обращение SetCBreak(Break : Boolean) Устанавливает или отменяет состояние проверки CTRL-BREAK при обращении к любой функции ДОС. Если BREAK - FALSE, нажатие CTRL-BREAK проверяется толь- ко при обращении к клавиатуре, экрану и принтеру: если BREAK=TRUE, проверка осуществляется при любом обращении к ДОС. SETCOLOR. Процедура из модуля GRAPH. Обращение SetColor(Color i word) Устанавливает текущий цвет (см. с. 237). SETDATE. Процедура из модуля DOS. Обращение SetDate(Year. Month. Day : word) Устанавливает текущую дату в операционной системе: YEAR - год в диапазоне 1980...2099; MONTH - месяц в диапазоне 1...12; DAY - день в диапазоне 1...31. Пример: Uses DOS; var Y>f M, D, W : word; const Wk : array [0..6] of string[ll] = ('воскресенье', 'понедельник', 'вторник', 'среда', 'четверг ’, 'пятница ’, 'суббота'); begin 7 SetDate(1989, 7, 31); 402
GetDate(Y, M, D, W); writeln(D, M, Y, Wk[W]) end. SETFATTR. Процедура из модуля DOS. Обращение SetFAttr(var F; Attr : word) Устанавливает новые атрибуты файла (см. с. 119). SETFILLPATTERN. Процедура из модуля GRAPH. Обращение SetFillPattem(Pattem : FillPatternType; Color : word) Устанавливает произвольный образец штриховки (см. с. 244). SETFILLSTYLE. Процедура из модуля GRAPH. Обращение SetFillStyle(Pattem. Color : word) Устанавливает образец и цвет штриховки (см. с. 242). SETFTIME. Процедура из модуля DOS. Обращение SetFTime(var F: Time : longint) Устанавливает время последнего изменения файла (см. с. 119). SETGRAPHBUFSIZE. Процедура из модуля GRAPH. Обращение SetGraphBufSize(Size : word) Изменяет размер внутреннего буфера, используемого в процедуре штриховки (за- полнения) замкнутой фигуры. По умолчанию. размер этого буфера составляет 4 Кбай- та. что достаточно для штриховки многоугольника с 650 вершинами. Процедуру необ- ходимо вызывать до инициации графического режима. SETGRAPHMODE. Процедура из модуля GRAPH. Обращение SetGraphMode(Mode : integer) Устанавливает новый режим работы графического адаптера (см. с.2175). SETINTVEC. Процедура из модуля DOS. Обращение SetIntVec(IntNo : byte: Vector : pointer) Устанавливает вектор прерывания (см. с. 185). Setlinestyle, процедура из модуля graph, обращение SetLineStyle(LineStyle. Pattern, Thickness : word) Устанавливает стиль линий (см. с. 227). SETPALETTE. Процедура из модуля GRAPH. Обращение SetPalette(ColorNum : word: Color : shortint) Устанавливает один из цветов палитры (см. с. 239). SETRGBPALETTE. Процедура из модуля GRAPH. Обращение SetRGBPalette(ColorNum, Red, Green, Blue : integer) Устанавливает цветовую гамму для дисплея IBM-8514 (см. с. 246). SETTEXTBUF. Процедура, устанавливающая имя переменной и ее длину. Обраще- ние SetTextBuf(var F : text; var buf (: size : word]) Данная переменная будет использоваться в качестве внутреннего буфера при об- Мене данными с текстовым файлом, связанным с F. По умолчанию любой текстовый | Файл использует внутренний Турбо-Паскалевый буфер длиной 128 байт. Этого вполне Достаточно для большинства применений. Однако в некоторых случаях может потребо- ваться большая длина буфера, например для ускорения копирования файла. Эту проце- дуру не следует применять по отношению к уже открытому файлу. Процедура осу- ществляет действия, подобные возможному объявлению текстового файла вида: var F : text [2048]; (эта форма введена для совместимости с версией 3.0). 403
SETTEXTJUSTIFY. Процедура из модуля GRAPH. Обращение SetTextJustify(Horiz. Wert : word) Осуществляет горизонтальное и вертикальное выравнивание текста (см. с. 255). SETTEXTSTYLE. Процедура из модуля GRAPH. Обращение SetTextStyle(Font. Direction, CharSize : word) Устанавливает стиль текста (см. с. 253). SETTIME. Процедура из модуля DOS. Обращение SetTime(Hour, Min. (Sec, SeclOO : word) Устанавливает текущее время в операционной системе: HOUR - час в диапазоне 0...23; MIN- минуты, 0...59; SEC-секунды, 0...59; SEC100 - сотые доли секунды, 0...99. Пример: ( Uses DOS; var- Н, М, S, S100 : word; begin SetTime(5, 22, 0, 0); GetTime(H, M, S, S100); writeln(H, M, S, S100) end. SETUSERCHARSIZE. Процедура из модуля GRAPH. Обращение SetUserCharSize(MultX, DivX, MuItY, DivY) Устанавливает пропорции выводимых символов (см. с. 256). SETVERIFY. Процедура из модуля DOS. Обращение SetVerify(Verify :. Boolean) Устанавливает или отменяет контроль записи на диск. Если параметр VERIFY имеет значение TRUE, каждая запись на диск будет сопровождаться контрольным счи- тыванием записанного; если FALSE - контроль отменяется. SETVIEWPORT. Процедура из модуля GRAPH. Обращение SetViewPort(xl, yl, х2, у2 : integer: ClipOn : Boolean) Устанавливает графическое окно (см. с. 220). SETVISUALPAGE. Процедура из модуля GRAPH. Обращение SetVisualPage(PageNo : word) Делает видимой нужную страницу графического экрана (см. с. 224). SETWRITEMODE. Процедура из модуля GRAPH. Обращение SetWriteMode(WriteMode : integer) Устанавливает способ взаимодействия вычерчиваемых линий с уже существующим на экране изображением (см. с. 229). SIN. Функция, возвращающая синус от аргумента (см. С. 89). Обращение и тип результата Sin(X : real) : real SIZEOF. Функция, возвращающая длину внутреннего представления аргумента в байтах. Обращение и тип результата SizeOf(X) : word В качестве X может использоваться переменная или идентификатор типа. SOUND. Процедура из модуля CRT. Обращение Sound(Hz : word) Выдает звуковой сигнал нужной частоты (см. с. 289). 404
SPTR. Функция, возвращающая содержимое регистра SP центрального процессора. Обращение и тип результата SPtr : word SQR. Функция/вычисляющая квадрат аргумента (см. с. 83 и 89). Обращение Sqr(X) SQRT. Функция, вычисляющая корень квадратный из аргумента (см. с. 89). Обр' щение и тип результата Sqrt(X : real) : real SSEG. Функция, возвращающая содержимое регистра SS центрального процессора. Обращение и тип результата SSeg : word STR. Процедура, преобразующая число X в строковое представление (см. с .103). Обращение Str(x [: width [: decimals ] ]: var s : string) SUCC. Функция, возвращающая следующее значение порядкового типа (см. с. 81). Обращение Succ(X) SWAP. Функция, которая меняет местами старший и младший байты аргумента и полученное возвращает в качестве своего значения. Обращение Swap(X) где X - выражение типа INTEGER или WORD. Тип функции совпадает с типом аргумента. SWAPVECTORS. Процедура из модуля DOS. Обращение Swap Vectors Обменивает содержимое указателей SAVEINTXX модуля DOS с векторами преры- ваний (см. с. 187). TEXTBACKGROUND. Процедура из модуля CRT. Обращение х TextBackGround(Color : byte) Устанавливает цвет фона для текстового экрана (см. с. 204). TEXTCOLOR. Процедура из модуля CRT. Обращение TextColor(Color : byte) Устанавливает цвет символов текстового экрана (см. с. 204). TEXTHEIGHT. Функция из модуля GRAPH. Обращение и тип результата TextHeight(Text : string) : word Возвращает максимальную высоту символов в пикселях (см. с. 257). TEXTMODE. Процедура из модуля CRT. Обращение TextMode(Mode : word) Устанавливает режим работы текстового экрана (см. с. 207). TEXTWIDTH. Функция из модуля GRAPH. Обращение и тип результата TextWidth(Text : string) : word Возвращает длину символьной строки в пикселях (см. с. 257). TRUNC. Функция, преобразующая вещественное число в целое путем отбрасыва- ния дробной части. Обращение й тип результата Trunc(X : real) : longint 405
Пример: begin writeln(Trunc(pi)) {результат 3} end. TRUNCATE. Процедура, отсекающая часть файла от текущего положения файло- вого указателя до конца. Обращение Truncate(var F) Применяется к любым файлам, кроме текстовых. Файл должен быть открыт для записи или чтения. UNPACKTIME. Процедура, которая переводит четырехбайтное внутреннее для ДОС представление текущей даты и времени в формат, соответствующий такому ти- пу: DATETIME = record Year, Month, Day, Hour, Min, Sec : word end Обращение UnpackTime(Time : longint: var DT : DateTime) UPCASE. Функция, которая возвращает символ, соответствующий символу верхне- го регистра для СН; если он для него имеется, либо сам символ СН. если для него не определен символ верхнего регистра (см. с. 86 и 104). Обращение и тип результата UpCase(ch : char) : char VAL. Процедура, преобразующая строковое выражение в числовой эквивалент (см. с. 103). Обращение Val(S : string; var V; var Code : integer) WHEREX. Функция из модуля CRT. Обращение и тип результата WhereX : byte Возвращает горизонтальную координату текстового курсора (см. с. 207). WHEREY. Функция из модуля CRT. Обращение и тип результата WhereY : byte Возвращает вертикальную координату текстового курсора (см. с. 207). WINDOW. Процедура из модуля CRT. Обращение Window(xl, yl, х2, у2 : byte) Устанавливает текстовое окно (см. с. 206). WRITE. Процедура, выводящая записи в файл (см. с. 122). Обращение Write( [var F] vl [, v2,...,vn] ) WRITELN. Процедура, выводящая записи в текстовый файл (см. с. 124). Обраще- ние Writeln( [var F: text;] vl [, v2,...,vn] ) 406
З.СООБЩЕНИЯ И КОДЫ ОШИБОК 3.1. Сообщения об ошибках периода компиляции Среда Турбо-Паскаля позволяет получить исчерпывающую информацию о характе- ре и месте обнаруженной компилятором ошибки. При обнаружении ошибки среда Тур- бо-Паскаля автоматически загружает в текстовый редактор исходный файл и, помещает курсор около того места, где в исходном тексте обнаружена ошибка. При этом в ко- мандной строке редактора появляется диагностическое сообщение. После нажатия на любую клавишу (кроме F1) командная строка восстановит свой первоначальный вид и среда перейдет к режиму редактирования. Если после появления сообщения об ошибке нажать F1, на экране появится небольшое окно с детальной справкой об ошибке и ре- комендациями по ее устранению. Некоторые ошибки в исходном тексте обнаруживают- ся не сразу, а в ходе продолжающегося контекстного разбора. Например, несоответст- вие типов в операторе присваивания не может быть обнаружено до тех пор. пока не будет вычислено целиком выражение в правой части этого оператора. В таких случаях ищите ошибку слева от курсора или в предыдущей строке текста. . Ниже приводятся сообщения об ошибках в том виде, в каком они появляются в командной строке1, а также перевод сообщений справочной службы. * 1 Out of memory (выход за границы памяти). Компилятору не хватает памяти. Имеется ряд возможных решений этой пробле- мы: - если в опции COMPILE/DESTINATION установлено значение MEMORY, замени- те эту опцию на DISK: - если в опции OPTIONS/COMPILER/LINK установлено значение MEMORY, заме- ните эту опцию на DISK: - если Вы используете постоянно помещенные в память (резидентные) обслужи- вающие программы, такие как WINDOWS, SIDEKICK, NORTON, удалите их из памяти; - если Вы используете интегрированную среду TURBO.EXE, то попробуйте вос- пользоваться компилятором ТРС.ЕХЕ: он занимает меньше памяти. Если ни одна из рекомендаций не помогает, то, возможно. Ваша программа про- сто слишком велика, чтобы компилировать ее в таком объеме памяти. В этом случае Вы должны разбить ее на два или более модулей. 2 Identifier expected (не указан идентификатор). В этом месте должен находить- ся идентификатор. Возможно, Вы пытаетесь использовать в качестве идентификатора зарезервированное слово. 3 Unknown identifier (неизвестный идентификатор). Этот идентификатор не был описан. 4 Duplicate identifier (двойной идентификатор). Попытка дважды описать один и тот же идентификатор. 5 Syntax error (синтаксическая ошибка). В исходном тексте найден неверный сим- вол. Возможно. Вы забыли заключить в кавычки строковую константу. 6 Error in real constant (ошибка в вещественной константе). Синтаксис констант вещественного типа описан на с. 74. 7 Error in integer constant (ошибка в целой константе). Синтаксис констант це- лого типа описан на с. 73. Учтите, что после целых действительных чисел, превышаю- щих диапазон представления целых чисел (-2147483648...+ 2147483647), должны ставиться т°чка и нуль, например 12345678912.0. 8 String constant exceeds line (строковая константа превышает допустимые раз- оры). Вероятно, Вы забыли поставить апостроф в конце строковой константы. 9 Too many nested files (слишком много вложенных файлов). Компилятор попу- гает не более пяти вложенных исходных файлов. 1 На экране после кода ошибки (цифры) нет точки, поэтому ее нет и в соответ- Ствующем месте текста. 407
10 Unexpected end of file (не найден конец файла). Вы могли получить это сообщение об ошибке по одной из следующих причин: - Ваш исходный файл закончился перед последним END основного раздела опе- раторов. Вероятно, в Вашей программе йеодинаковое количество операторов BEGIN и END; - включаемый файл заканчивается в, середине раздела операторов. Каждый раздел операторов должен целиком помещаться в одном файле; - Вы не закончили комментарий. 11 Line too long (слитком длинная строка). Максимальная длина строки, обрабатываемой компилятором, равна 126 символам (обратите внимание: редактор среды может обрабатывать до 249 символов в строке). 12 Type identifier expected (здесь нужен идентификатор типа). Не указан тип идентификатора. 13 Too many open files (слитком много открытых файлов). Если появляется эта ошибка, то это означает, что конфигурационный файл CONFIG.SYS операционной системы не включает параметр FILES=хх или этот пара- метр указывает слишком мало файлов. Увеличьте число файлов до нужного значения, например, до 20. 14 Invalid file name (неверное имя файла). Имя файла неверно или указан несуществующий путь. 15 File not found (файл не найден). Файл не был найден в просмотренных каталогах. 16 Disk full (диск заполнен). Удалите некоторые файлы или воспользуйтесь новым диском. 17 Invalid compiler directive (неправильная директива компилятора). Неверная буква в директиве компилятора, один из параметров директивы компиля- тора неверный, или Вы пользуетесь глобальной директивой компилятора, когда компи- ляция тела программы уже началась. 18 Too many files (слитком много файлов). В компиляции программы или программного модуля участвуют слишком много файлов. Попытайтесь не использовать так много файлов, например, объединяя включае- мые файлы. 19 Undefined type in pointer definition (неопределенный тип в объявлении указа- теля). Попытка объявить типизированный указатель, связанный с необъявленным типом данных. 20 Variable identifier expected (отсутствует идентификатор переменной). На этом месте должен быть идентификатор переменной. 21 Error in type (ошибка в объявлении типа). Объявление типа не может начинаться с этого символа. 22 Structure too large (слитком большая структура). Максимально допустимый размер любого структурированного типа - 65520 байт. 23 Set base type of range (базовый тип множества нарушает границы). Базовый тип множества должен представлять собой тип-диапазон с границами в пределах от 0 до 255 или перечисляемый тип с не более чем 256 значениями. 24 File components may not be files (компонентами файла не могут быть фай- лы). Конструкции типа файл файлов не допускаются. 25 Invalid string length (неверная длина строки). Длина строки должна находиться в диапазоне от 1 до 255. 26 Type mismatch (несоответствие типов). Это сообщение может -быть вызвано следующими причинами: - несовместимые типы переменной и выражения в операторе присваивания; - несовместимые типы фактического и формального параметров в обращении к процедуре или функции: - тип выражения несовместим с типом индекса при индексировании массива; - несовместимые типы операндов в выражении. 408
27 Inyalid subrange base type (неправильный базовый тип для типа-диапазона). Допустимыми базовыми типами являются все порядковые типы. 28 Lower bound greater than upper bound (нижняя граница больше верхней). Описание типа-диапазона содержит .неправильные границы. 29 Ordinal type expected (нужен порядковый тип). Вещественные, строковые, структурные, процедурные типы и указатели в данном месте программы не допускаются. 30 Integer constant expected (нужна целая константа). 31 Constant expected (нужна константа). 32 Integer or real constant expected (нужна целая или вещественная константа). 33 Type identifier expected (нужен идентификатор типа). 34 Invalid function result type (неправильный тип результата функции). Правильными типами результата функции являются все простые типы, строковые типы и указатели. 35 Label identifier expected (нужен идентификатор метки). Метка не обозначена с помощью идентификатора, как это требуется из контекста программы. 36 BEGIN expected (нужен BEGIN). 37 END expected (нужен END). 38 Integer expression expected (нужно выражение типа INTEGER). 39 Ordinal expression expected (нужно выражение перечисляемого типа) Предшествующее выражение должно иметь перечисляемый тип. 40 Boolean expression expected (нужно выражение типа BOOLEAN). Предшествующее выражение должно иметь тип BOOLEAN. 41 Operand types do not match operator (типы операндов не соответствуют опе- рации). Данная операция не может быть применена к указанным операндам, например, ’A1 div ’2\ 42 Error in expression (ошибка в выражении). Данный символ не может участвовать в выражении указанным образом. Возмож- но. Вы забыли указать операцию между двумя операндами. 43 Illegal assignment (неверное присваивание). Файлам и нетипизированным переменным нельзя присваивать значения. Идентифи- катору функции можно присвоить значение только внутри раздела операторов данной функции. 44 Field identifier expected (нужен идентификатор поля). Попытка использовать запись целиком в том месте, где требуется ссылка на ка- кое-либо поле записи. 45 Object file too large (объектный файл слишком большой). Турбо-Паскаль не может компоновать файлы .OBJ больше 64 Кбайт. 46 Undefined external (неопределенная внешняя процедура). Внешняя процедура или функция не имеет соответствующего определения PUBLIC в объектном файле. Убедитесь, что Вы указали все объектные файлы в дирек- тивах {$L filename} и проверьте написание идентификаторов процедура или функции в файле .ASM. 47 Invalid object file record (неправильная запись объектного файла). Файл .OBJ содержит неверную объектную запись. Убедитесь, что данный файл яв- ляется действительно файлом .OBJ. ‘ 48 Code segment too large (сегмент кода слишком большой). Максимальный размер кода программы или программного модуля равняется 65520 байт. Если Вы компилируете программный модуль, разбейте его на два или более про- П^аммных модуля. 49 Data segment too large (сегмент данных слишком велик). Максимальный размер сегмента данных программы равен 65520 байт, включая Данные, используемые программными модулями. Если Вам нужно большее количество гяобальных данных, опишите большие структуры с помощью указателей и выделяйте них память с помощью процедуры NEW. 50 DO expected (нужен оператор DO). 409
51 Invalid PUBLIC definition (неверное определение PUBLIC}. Возможные причины сообщения: - данный идентификатор получил тип PUBLIC с помощью соответствующей рективы языка ассемблера, но не соответствует описанию EXTERNAL в программе или программном модуле Паскаля; - две или более директивы PUBLIC языка ассемблера определяют один и тот идентификатор; - файлы .OBJ определяют символы PUBLIC, не находящиеся в сегменте CODE. 52 Invalid EXTRN definition (неправильное определение EXTRN). Возможные причины сообщения: - программа на ассемблере ссылается с помощью директивы EXTRN на идентифц. катор, который не описан в программе на Паскале и не был описан в интерфейсных секциях используемых программных модулей; - ассемблерная программа ссылается на идентификатор, обозначающий абсолют- ную переменную (т.е. определенную словом ABSOLUTE); - ассемблерная программа ссылается на идентификатор процедуры или функции типа INLINE. 53 Too many EXTRN definition (слишком много определений типа EXTRN). Турбо-Паскаль не может обрабатывать файлы .OBJ при более чем 256 определе- ниях EXTRN. 54 OF expected (требуется OF). 55 INTERFACE expected (требуется интерфейсная секция). 56 Invalid relocatable reference (недействительная перемещаемая ссылка). Возможные причины сообщения: - Файл .OBJ содержит данные и перемещаемые ссылки в сегментах, отличных от CODE. Например, Вы пытаетесь описать инициализированные переменные в сегменте DATA. ‘ - Файл .СОМ содержит ссылки с размерами в байтах на перемещаемые символы. Такая ошибка происходит в случае, если Вы используете операторы HIGH и DOWN с перемещаемыми символами или если Вы ссылаетесь в директивах DB на перемещае- мые символы. - Операнд ссылается на перемещаемый символ, который не был определен в сег- менте CODE или в сегменте DATA. - Операнд ссылается на процедуру EXTRN или функцию EXTRN со сдвигом, на- пример, CALL SortProc+8. 57 THEN expected (требуется THEN). 58 TO or DOWNTO expected (требуется TO или DOWNTO). 59 Undefined forward (неопределенное опережающее описание). Возможные причины сообщения: - были описаны процедура или функция в интерфейсной секции программного мо- дуля, но их определение отсутствует в секции реализации; - процедуры или функции были описаны с помощью опережающего описания, но их определение не найдено. 60 Too many procedures (слишком много процедур). Турбо-Паскаль не допускает более 512 процедур или функций в одном модуле. Если Вы компилируете программу, то поместите некоторые процедуры или функции в модули. Если Вы компилируете модуль, то разбейте его на два или несколько моду* лей. , 61 Invalid typecast (неверное преобразование типа). Возможные причины сообщения: - попытка разместить в памяти, занимаемой некоторой переменной, значение вь1' ражения другого типа в случае, когда размер размещаемого значения не равен разМ^’ ру переменной; - Вы пытаетесь осуществить преобразование типа выражения, когда разрешает^ только ссылка на переменную, процедуру или функцию. 62 Division by zero (деление на ноль). Предшествующая операция пытается выполнить деление на ноль. 410
63 Invalid file type (неверный файловый тип). Данный файловый тип не обслуживается процедурой обработки файлов. Напри- мер. процедура READLN используется для типизированного файла, или процедура SEEK-для текстового файла. 64 Cannot Read or Write variables of this type (нет возможности считать или записать переменные данного типа). Нарушены следующие ограничения: - процедуры READ и READLN могут считывать переменные символьного, целого, действительного и строкового типа; - процедуры WRITE и WRITELN могут выводить переменные символьного, цело- го, действительного, логического и строкового типа. 65 Pointer variable expected (нужно использовать переменную-указатель). Предыдущая переменная должна быть указателем. 66 String variable expected (нужна строковая переменная). Предшествующая переменная должна иметь строковый тип. 67 String expression expected (нужно выражение строкового типа). Предшествущее выражение должно иметь строковый тип. 68 Unit not found (программный модуль не найден). Один или несколько программных модулей, используемых данным модулем, не указаны в предложении USES. 69 Unit name mismatch (несоответствие имен программных модулей). Имя программного модуля, найденное в файле .TPU, не соответствует имени, указанному в предложении USES. 70 Unit version mismatch (несоответствие версий программных модулей). Один или несколько программных модулей, используемых данной программой, бы- ли изменены после их компиляции. Воспользуйтесь опцией COMPILE/МАКЕ или COMPILE/BUILD в интегрированной интерактивной среде или опциями /М или /В в компиляторе ТРС, что позволит автоматически скомпилировать программные модули, нуждающиеся в перекомпиляции. 71 Duplicate unit name (повторное имя программного модуля). Вы уже указали этот программный модуль в операторе USES. 72 Unit file format error (ошибка формата файла модуля). Файл .TPU является недействительным. Убедитесь, что это действительно файл .TPU. 73 Implementation expected (отсутствует исполняемая часть модуля). 74 Constant and case types do not match (типы констант и тип выражения опе- ратора CASE не соответствуют друг другу). Тип константы оператора CASE не совместим с выражением в операторе вариан- та. 75 Record variable expected (нужна переменная типа запись). Предшествующая переменная должна иметь тип запись. 76 Constant out of range (константа нарушает границы). Возможные причины сообщения: - Вы пытаетесь указать индекс массива, выходящий за его границы; - Вы пытаетесь присвоить переменной значение, выходящее за границы, допусти- мые для типа этой переменной; - Вы пытаетесь передать в качестве фактического параметра процедуре или фун- кции константу, выходящую за границы, допустимые для типа соответствующего фор- мального параметра. 77 File variable expected (нужна файловая переменная). Предшествующая переменная должна иметь файловый тип. 78 Pointer expression expected (нужно выражение типа указатель). Предшествующее выражение должно иметь тип указателя. or real expression expected (нужно выражение типа REAL или Предшествующее выражение должно иметь тип REAL или INTEGER. 411
80 Label not within current block (метка не находится внутри текущего блока}. Оператор GOTO не может ссылаться на метку, находящуюся вне текущего бло- ка. 81 Label already defined (метка уже определена). Данная метка уже помечает оператор. 82 Undefined label in processing statement part (неопределенная метка в предше- ствующем разделе операторов). Данная метка была описана, и на нее осуществлялась ссылка в предшествующем разделе операторов, но она не указана в тексте программы. 83 Invalid @ argument (недействительный аргумент операции @). Действительными аргументами являются идентификаторы переменных, процедур и функций. 84 Unit expected (нужно кодовое слово UNIT). 85 expected (нужно указать 86 expected (нужно указать 87 expected (нужно указать 88 *(" expected (нужно указать "('')- 89 *)* expected (нужно указать ")"). 90 " = " expected (нужно указать "'=**). 91 *:»" expected (нужно указать 92 *[” or ”(•" expected (нужно "[" или "(.")• 93 "]* or expected (нужно ,']и или 94 *.* expected (нужно 95 expected (нужно 96 Too many variables (слишком много переменных). Возможные причины сообщения: - общий размер глобальных переменных, описанных в программе или программ- ном модуле, не может превышать 64 Кбайт: • размер локальных переменных, описанных в программе или функции, не может превышать 64 Кбайт. 97 Invalid FOR control variable (неправильная управляющая переменная оператора FOR). Управляющая переменная оператора FOR должна быть переменной перечисляемо- го типа, определенной в разделе описаний текущей подпрограммы. 98 Integer variable expected (нужна переменная целого типа). Предшествующая переменная должна иметь целый тип. 99 Files are not allowed here (здесь не допускаются файлы). Типизированная константа не может иметь файловый тап. 100 String length mismatch (несоответствие длины). Длина строковой константы не соответствует количеству элементов символьного массива. 101 Invalid ordering of fields (неверный порядок полей). Поля в константе типа запись должны записываться в порядке их описания. 102 String constant expected (нужна константа строкового типа). 103 Integer or real variable expected (нужна переменная типа INTEGER или REAL), Предшествующая переменная должна иметь целый или вещественный тип. 104 Ordinal variable expected (нужна переменная порядкового типа). Предшествующая переменная должна иметь порядковый тип. 105 INLINE error (ошибка в операторе INLINE). Оператор "<" не допускается в сочетании с перемещаемыми ссылками на пере- менные. Такие ссылки всегда, имеют размер в слово. 106 Character expression expected (предшествующее выражение должно иметь символьный тип). 107 Too many relocation items (слишком много перемещаемых элементов). Размер таблицы перемещения файла .EXE превышает 64К, что является верхним 412
пределом в Турбо-Паскале. Если Вы обнаружили эту ошибку, то это значит, что про- Ррамма просто слишком велика для обработки компоновщиком Турбо-Паскаля. Возмож- й0 также, что она слишком велика для выполнения в PC DOS. В таком случае нужно выделить в программе основной раздел, который выполнял бы обращение к двум' или более вспомогательным разделам с помощью процедуры ЕХЕС из модуля DOS (см. 115). 108 Not enough memory to run program (недостаточно памяти для выполнения программы). Недостаточно памяти для выполнения программы из среды ТурбоПаскаля. Попы- тайтесь воспользоваться рекомендациями, связанными с ошибкой 1. Если это не помо- жет, то скомпилируйте свою программу на диск, выйдите из среды и запустите про- ррамму средствами ДОС. 109 Cannot find EXE file (невозможно найти файл .EXE). По какой-то причине файл .EXE, сгенерированный ранее компилятором, исчез. 110 Cannot run a unit (модуль выполнить нельзя). Вы не можете выполнить программный модуль. Чтобы проверить его, напишите программу, использующую этот программный модуль. Ill Compilation aborted (компиляция прервана). Компиляция была прервана с помощью CTRL-BREAK. 112 CASE constant out of range (константа CASE нарушает допустимые грани- цы). Целочисленные константы оператора CASE должны находиться в диапазоне от - 32768 до 32767. / 113 Error in statement (ошибка в операторе). ' Данный символ не может быть первым символом в операторе. 114 Cannot call an interrupt procedure (невозможно вызвать процедуру прерыва- ния). Вы не можете непосредственно вызвать процедуру прерывания. 115 Must have an 8087 to compile this (для компиляции необходимо наличие со- процессора 8087). Для компиляции программ и программных модулей в режиме {$N + } необходим сопроцессор 8087. Используйте программную эмуляцию сопроцессора с помощью ди- рективы {$N + .E+}. 116 Must be in 8087 mode to compile this (для компиляции необходим режим 8087). Данная программа может быть скомпилирована только в режиме {$N + }. В со- стоянии {$N-} операции с типами SINGLE, DOUBLE, EXTENDED и COMP не допу- скаются. 117 Target address not found (указанный адрес не найден). Команда COMPILE/FIND ERROR в среде Турбо-Паскаля или опция /F в команд- ной строке компилятора ТРС.ЕХЕ не обнаружила оператор, соответствующий заданно- му адресу< 118 Include files are not allowed here (здесь не допускаются включаемые файлы). Раздел операторов должен целиком размещаться в одном файле. 119 ТМР file format error (ошибка формата файла .ТМР). v Файл .ТМР является недействительным. Убедитесь, что этот файл является в дей- ствительности файлом .ТМР. 120 NIL expected (нужен NIL). 121 Invalid qualifier (неверный идентификатор). Возможные причины сообщения: • Вы пытаетесь индексировать переменную, которая не является массивом. - Вы пытаетесь указать поля в переменной, которая не является записью. - Вы пытаетесь использовать в качестве указателя переменную, которая не яв.ля- стся указателем. 122 Invalid variable reference (недействительная ссылка на переменную). Предыдущая конструкция удовлетворяет синтаксису ссылки на переменную, но 413
она не указывает адрес памяти. Наиболее вероятно, что Вы вызываете функцию^указа- тель, но забываете поставить знак А после нее. , 123 Too many symbols (слишком много символов). Программа или программный модуль содержат более 64 Кбайт символов. Если Вы компилируете программу с ди- рективой {$D + }, то попробуйте отключить эту директиву или разбейте программу на несколько модулей. 124 Statement part too large (слишком большой раздел операторов). Турбо-Паскаль ограничивает размер раздела операторов примерно до 24 Кбайт. Если Вы обнаружили эту ошибку, поместите части раздела операторов в одну или не- сколько процедур и вообще сделайте Вашу программу более структурированной. 125 Module has no debug information (в модуле нет отладочной информации). Ошибка периода выполнения обнаружена в модуле (программе), который не име- ет отладочной информации, и по этой причине Турбо-Паскаль не может указать соот- ветствующий оператор. Перекомпилируйте модуль с включенной опцией {SD+} или воспользуйтесь опцией COMPILER/FIND ERROR, чтобы найти эту ошибку. 126 Files must be var parameters (файлы должны передаваться как параметры-пе- ременные). Вы пытаетесь передать процедуре или функции параметр-значение файлового ти- па. Параметры файлового типа должны быть параметрами-переменными. 127 Too many conditional symbols (слишком много условных символов). Не хватает памяти для определения условных символов (слов, управляющих ко- мандами условной компиляции). Попытайтесь удалить некоторые символы или умень- шить их длину. 128 Misplaced conditional directive (пропущена условная директива). Компилятор обнаружил директиву {SELSE} или {SENDIF} без соответствующих директив {SIFDEF}, {SIFNDEF} или {SIFOPT}. 129 ENDIF directive missing (пропущена директива ENDIF). Исходный файл закончился внутри конструкции условной компиляции. В исходном файле должно быть равное количество директив {SIFxxx} и {SENDIF}. 130 Error in initial conditional defines (ошибка в условных определениях). Исходные условия компиляции, указанные в опции OPTIONS/COMPILER /CONDITIONAL DEFINES являются недействительными. В Турбо-Паскале разрешается не указывать условия компиляции; если же приведено несколько условий, они должны разделяться точкой с запятой. 131 Header does not match previous definition (заголовок не соответствует предыдущему определению). Возможные причины сообщения: - заголовок процедуры или функции, указанный в интерфейсной секции, не соот- ветствует заголовку в исполняемой части: - заголовок процедуры или функции, указанный с помощью опережающего описа- ния (FORWARD), не соответствует заголовку найденной далее одноименной процедуры или функции. 132 Critical disk error (критическая ошибка диска). Во время компиляции произошла критическая ошибка диска (например, дисковод находится в состоянии "не готов"). 133 Cannot evaluate this expression (нельзя вычислить данное выражение). В выражении-константе или в отладочном выражении Вы пытаетесь использовать неподдерживаемые средства, например в описании константы пытаетесь использовать функцию SIN или вызвать в отладочном выражении определенную пользователем функ- цию. 134 Expression incorrectly terminated (некорректное завершение выражения). Контекстуально в данном месте программы должен быть конец выражения или оператора. 135 Invalid format specifier (неверный спецификатор формата). Используется неверный спецификатор формата или числовой аргумент специфика- тора формата выходит за допустимые границы. 136 Invalid indirect reference (недопустимая косвенная ссылка). 414
Ьйе^атор пытается' осуществить недопустимую косвенную ссылку. Например, Вы используете абсолютную переменную, базовая переменная которой в текущем модуле неизвестна, или используете программу типа INLINE, в которой делается ссылка на переменную, неопределенную в текущем модуле. 137 Structured variable are not allowed here (здесь нельзя использовать переменную структурного типа). Делается попытка! выполнить над переменной структурного типа неподдерживае- мую операцию. Например, Вы пытаетесь перемножить две записи. 138 Cannot evaluate without System unit (нельзя вычислить выражение без модуля SYSTEM). Чтобы отладчик смог вычислить выражение, в файле TURBO.TPL должен содер- жаться модуль SYSTEM. 139 Cannot access this symbol (нет доступа к данному символу). Как только Вы скомпилируете программу, все множество ее символов станет до- ступным. Однако к отдельным символам (например, к переменным) нельзя получить доступ, пока Вы не запустите программу. 140 Invalid floating-point operation (недопустимая операция с плавающей запятой). При операции с плавающей запятой произошло переполнение или деление на нуль. 141 Cannot compile overlay to memory (нельзя выполнить компиляцию оверлеев в память). Программа, использующая оверлеи, должна компилироваться на диск. 142 Procedure or function variable expected (должна использоваться переменная 'процедурного типа). В этом контексте оператор получения адреса @ может использоваться только с переменной процедурного типа. 143 Invalid procedure or function reference (недопустимая ссылка на процедуру или функцию). Возможные причины сообщения: - попытка вызова процедуры в выражении; - процедура или функция, использующая в качестве параметра вызов другой про- цедуры или функции, должна компилироваться в состоянии {$F+} и не может описы- ваться с помощь ключевых слов INLINE или INTERRUPT. 144 Cannot overlay this unit (этот модуль не может использоваться в качестве оверлейного). Попытка использовать в качестве оверлейного модуль^ который не был скомпили- рован с директивой {$О + }. 3.2. Ошибки, возникающие во время выполнения программы Некоторые ошибки, обнаруженные во время выполнения программы, приводят к появлению на экране сообщения вида Runtime error nnn at xxxx:yyyy (ошибка времени выполнения nnn по адресу хххх:уууу). после чего программа за- вершает свою работу. Ошибки времени выполнения делятся на две категории: ошибки ввода-вывода (ко- ды ошибок с 1 до 199) и грубые ошибки (коды ошибок с 200 до 255). Ошибки ввода-вывода вызывают завершение выполнения программы в случае, ес- ли оператор ввода-вывода был скомпилирован в режиме {$! + }. В режиме {$!-} про- ' Должается выполнение программы, а ошибка возвращается функцией IORESULT. Коды ошибок 1-99 соответствуют кодам ошибок ДОС. 100-149 - ошибкам ввода-вывода, ! 150-199 - критическим ошибкам, а 200-255 - фатальным ошибкам. 1 File not found (не найден файл). Ошибка генерируется процедурами RESET, APPEND, RENAME или ERASE, если иМя, присвоенное файловой переменной, указывает несуществующий файл. 3 Path not found (путь не найден). Ошибка генерируется процедурами: 415
- RESET, REWRITE, APPEND или ERASE, если имя, присвоенное файловой пере- менной, является недействительным или указывает на несуществующий подкаталог; - CHDIR, MKDIR или RMDIR, если путь является недействительным или указыва- ет на несуществующий подкаталог. 4 Too many open files (слишком много открытых файлов). Ошибка генерируется процедурами RESET, REWRITE или APPEND, если про- грамма имеет слишком много открытых файлов. Операционная система ДОС не позво- ляет использовать более 15 открытых файлов для каждого процесса. Если Вы получи- ли данное сообщение при наличии менее 15 открытых файлов, это может означать, что файл CONFIG.SYS не содержит параметр FILES = ххх или этот параметр задает слишком мало файлов. Увеличьте параметр FILES=ххх до какого-либо подходящего значения, например, до 20. 5 File access defined (отказано в доступе к файлу). Данная ошибка генерируется процедурой: - RESET или APPEND, когда имя, присвоенное файловой переменной, указывает каталог или файл, доступный только для чтения, в то время как параметр FILEMODE файловой переменной содержит указание на запись данных; - REWRITE, если каталог заполнен или если имя, присвоенное файловой перемен- ной. задает каталог или сущестующий файл, доступный только для чтения; - RENAME, если имя, присвоенное файловой переменной, указывает каталог или если новое имя указывает существующий файл; , - ERASE, если имя, присвоенное файловой переменной, указывает каталог или файл, доступный только для чтения; - MKDIR, если файл с тем же именем уже существует в каталоге, в котором со- здается подкаталог, и в этом каталоге нет места для подкаталога или путь к каталогу содержит имя логического устройства; - RMDIR, если каталог не является пустым, если путь не определяет каталог или если путь задает корневой каталог; - READ или BLOCKREAD в случае типизированного или нетипизированного фай- ла, если файл не открыт для чтения. - WRITE или BLOCKWRITE для типизированного или нетипизированного файла, если этот файл не открыт для записи. 6 Invalid file handle (недопустимый файловый канал). Данная ошибка генерируется, когда системному вызову ДОС передается недопу- стимый файловый канал. Эта ошибка не должна возникать в правильно работающей программе. Ее появление является свидетельством того, что файловая переменная ка- ким-либо образом испорчена. 12 Invalid file access code (недействительный код доступа к файлам). Ошибка генерируется процедурами RESET или APPEND, если значение параметра FILEMODE в файловой переменной не является допустимым. 15 Invalid drive number (недопустимый номер дисковода). Ошибка генерируется процедурой GETDIR, если номер дисковода не является до- пустимым. 16 Cannot remove current directory (нельзя удалить текущий каталог). Ошибка генерируется процедурой RMDIR, если путь указывает текущий каталог. 17 Cannot rename across drives (нельзя при переименовании указывать разные ди- сководы). Генерируется процедурой RENAME, если оба файла не находятся на одном и том же диске. Если один из операторов компилировался с директивой {$! + }. то ошибка ввода- вывода приводит к прекращению выполнения программы. В состоянии {$!-} программа продолжает выполняться, а ошибка возвращается функцией IORESULT. 100 Disk read error (ошибка чтения с диска). Генерируется процедурой READ в типизированном файле, если Вы пытаетесь осуществить считывание после конца файла. 101 Disk write error (ошибка записи на диск). 416
Ошибка генерируется процедурами CLOSE, WRITE, WRITELN, FLUSH, если диск заполнен. 102 File not assigned (файлу не присвоено имя). Ошибка генерируется процедурами RESET REWRITE. APPEND, RENAME и ERASE в случае, если файловой переменной не было присвоено имя файла с по- мощью обращения к процедуре ASSIGN. 103 File not open (файл не открыт). Ошибка генерируется процедурами CLOSE, READ, WRITE, SEEK, EOF, FILEPOS, FILESIZE, FLUSH, BLOCKREAD, BLOCKWRITE, если файл не открыт. 104 File not open for input (файл не открыт для ввода). Ошибка генерируется процедурами READ, READLN, EOF, EOLN, SEEKEOF или SEEKEOLN в текстовом файле, если файл не открыт для ввода. 105 File not open for output (файл не открыт для вывода). Ошибка генерируется процедурами WRITE или WRITELN в текстовом файле, ес- ли файл не открыт для вывода. 106 Invalid numeric format (неверный числовой формат). Ошибка генерируется процедурами READ или READLN, если числовое значение, считанное из текстового файла, не соответствует правильному числовому формату. 150 Disk is write protected (диск защищен от записи). 151 Unknown unit (неизвестный модуль). 152 Drive not ready (дисковод находится в состоянии "не готов"). 153 Unknown command (неопознанная команда). 154 CRC error in data (ошибка в исходных данных). 155 Bad drive requiest structure length (при обращении к диску указана неверная длина структуры). 156 Disk seek error (ошибка при операции установки головок на диске). 157 Unknown media type (неизвестный тип носителя). 158 Sector not found (сектор не найден). 159 Printer out of paper (кончилась бумага на принтере). 160 Device write fault (ошибка при записи на устройство). 161 Device read fault (ошибка при чтении с устройства). 162 Hardware failure (сбой аппаратуры). Грубые ошибки всегда приводят к немедленной остановке программы. 200 Division by zero (деление на нуль). 201 Range check error (ошибка при проверке границ). Ошибка генерируется операторами, скомпилированными в состоянии {$R+}, при возникновении одной из следующих ситуаций: - индексное выражение массива находилось вне допустимого диапазона; - была осуществлена попытка присвоить переменной значение, находящееся вне диапазона переменной; - была осуществлена попытка передать значение, находящееся вне допустимого диапазрна, в качестве параметра процедуре или функции. 202 Stack overflow error (переполнение стека). Эта ошибка генерируется при входе в процедуру или функцию, скомпилирован- ную в режиме {$S+}, если нет достаточной области для размещения локальных пере- менных подпрограммы. Увеличьте размер стека, используя директиву компилятора {$М}. 203 Heap overflow error (переполнение кучи). Эта ошибка генерируется процедурами NEW или GETMEM в случае, если в куче не хватает памяти требуемого размера. 204 Invalid pointer operation (недействительная операция с указателем). Эта ошибка генерируется процедурами DISPOSE или FREEMEM, когда указатель Имеет значение NIL или содержит адрес, лежащий за пределами динамически распреде- ляемой области памяти. 205 Floating point overflow (переполнение при операции с плавающей запятой). 14 - Фаронов 417
206 Floating point underflow (исчезновение порядка при операции с плавающей за- пятой). Эта ошибка генерируется только в том случае, если используется сопроцессор 8087/80287/80387 с управляющим словом, которое демаскирует ошибку исчезновения по- рядка. По умолчанию исчезновение порядка приводит к возвращению результата, рав- ного нулю. 207 Invalid floating point operation (недопустимая операция с плавающей запя- той). Возможные причины сообщения: - аргумент функций TRUNC или ROUND не может быть преобразован в целое число, находящееся внутри диапазона типа LONGINT (от -2147483648 до ,+ 2147483647); - отрицательный аргумент функции SQRT (извлечение квадратного корня); - аргумент функции LN (логарифм) равен нулю или имеет отрицательное значе- ние; - произошло переполнение стека сопроцессора. 208 Overlay manager not installed (не установлена подсистема управления оверле- ем). Ваша программа вызывает оверлейную процедуру или функцию, а подсистема уп- равления оверлеем не инициализирована. Вероятнее всего в программе отсутствует об- ращение к процедуре OVRINIT или обращение к этой процедуре завершилось с ошиб- кой. Нужно иметь в виду, что если в каком-либо из оверлейных модулей содержится раздел инициализации, то в программе необходимо создать дополнительный или исполь- зовать имеющийся неоверлейный модуль, вызывающий процедуру OVRINIT в своем разделе инициализации, и указать этот модуль в предложении USES перед любым из оверлейных модулей. 209 Overlay file read error (ошибка чтения оверлейного файла). Когда подсистема управления оверлем пыталась считать оверлей из оверлейного файла, произошла ошибка чтения. *
4. ОСНОВНЫЕ ОСОБЕННОСТИ ВЕРСИИ 5.5 В мае 1989 г. фирма Borland выпустила на рынок свою новую версию Турбо-Пас- каля, которая получила обозначение 5.5. Мне удалось познакомиться с этой версией лишь в январе 1990 г., когда рукопись книги уже была сдана в издательство и близи- лось к концу ее редактирование. ‘Поэтому в данном приложении приводятся лишь са- мые необходимые сведения об основных особенностях версии 55. Прежде всего следует констатировать тот факт, что каждая очередная версия Турбо-Паскаля включает в себя какие-то новые, доселе не использовавшиеся расшире- ния языка или новые свойства среды, что позволяет говорить о непрерывном совер- шенствовании и развитии системы программирования Турбо-Паскаль. Это развитие идет по пути строгой преемственности основных языковых конструкций и* найденных ранее удачных решений, так что с помощью более поздних версий можно компилировать и исполнять ранее созданные программы. Не явилась исключением и версия 5.5: все на- писанное в данной книге по поводу Турбо-Паскаля 5.0 применимо и к Турбо-Паскалю 5.5. Главным и, на мой взгляд, весьма существенным отличием новой версии является введение в нее средств объективно-ориентированного программирования (ООП). Многие специалисты связывают с ООП дальнейшее развитие программирования как способа реализации алгоритмов для ЭВМ. Идеи ООП на первый взгляд могут по- казаться простыми и не сулящими особых выгод. Но не спешите выносить свой приго- вор и категорически их отвергать. Внимательный анализ, уверен, покажет Вам, что за внешней простотой скрывается по существу совершенно новая технология программи- рования. В небольшой главе невозможно обстоятельно изложить суть и преимущества ООП. Ниже приводятся сведения, которые, возможно, заинтересуют Вас и побудят к самостоятельному изучению нового пЪдхода к программированию, каким, безусловно, является ООП. Начнем с чисто внешних отличий. Их немного: в языке версии 5.5 появились 4 новых зарезервированныхе слова: constructor (конструктор) destructor (деструктор) object (объект) ( virtual (виртуальный) Эти зарезервированные слова используются только в контексте определения ново- го типа Турбо-Паскаля - так называемого объекта. В отличие от всех остальных типов (см. гл. 5), объект не является типом данных. Тем не менее, он объявляется вместе с другими типами и внешне напоминает запись. Объект есть объединение (инкапсуляция) данных и правил их преобразования. В качестве данных в объекте могут фигурировать переменные любого типа Турбо-Паска- ля, в качестве правил - процедуры, функции, конструкторы и деструкторы. Как Вы увидите дальше, такое объединение ни в коем случае не является чисто механическим соединением переменных и связанных с ними процедур. Объекты обладают двумя но- выми свойствами, которых не было ни у типов, ни у процедур Турбо-Паскаля 5.0: - наследование - любой объект может быть объявлен как производный (потомок) от объявленного ранее объекта (родителя), причем вновь, созданный объект наследует все данные и правила своего родителя и может дополнять их новыми данными и пра- вилами; у одного объекта может быть сколько угодно потомков, но только один роди- тель; свойство наследования позволяет создавать ветвящуюся иерархию родственных объектов; - полиморфизм - похожие по характеру правила преобразования данных (процеду- ры) у всех родственных объектов могут носить одно и то же имя, причем каждый объект иерархии реализует соответствующее действие своим способом; свойство поли- морфизма позволяет использовать в программе родственные объекты единообразно. Чтобы продемонстрировать основные идеи ООП, рассмотрим следующий пример. Пусть необхойимо показать светящуюся точку на графическом экране.Точка характе- ризуется своими координатами, цветом и состоянием (видимая или невидимая). Для описания точки в традиционном Паскале можно было бы ввести следующий тип: type Point = record 14* 419
X, Y : integer; {координаты точки} Color ' : word; {цвет} Visible: Boolean; {признак светимости} end; Для работы с полями этого типа можно использовать прямое обращение к ним с помощью составных имен или оператора WITH, либо написать специальные процедуры изменения положения, цвета и признака светимости точки, а также функции проверки соответствующих полей переменных типа POINT. Если, например, имеется описание var Point 1: Point; PPOCEDURE Pointlnt (Target: Point; NewX, NewY: integer); {устанавливает новые координаты точки} BEGIN with Target do begin X: = NewX; Y: = NewY end END; {Pointlnit} FUNCTION-PointVisible(Target: Point): Boolean; {возвращает признак светимости точки} BEGIN PointVisible: = Target.Visible END; {PointVisible} то для того, чтобы задать точку в начале средней строки графического экрана, можно было бы написать: if not PointVisible(Pointl) then Pointinit(Pointl, 0, GetMaxY div 2); Аналогичным образом можно написать и использовать другие процедуры и функ- ции. Если бы мы захотели создать библиотеку графических образов: круги, дуги, пря- моугольники и т.п. - нам пришлось бы вводить новые типы записей и новые процедуры для их обработки. Так, для описания окружности можно использовать тип type Circle = record X, Y : integer; {координаты центра} Radius : word; {размер радиуса в пикселях} Color : word; {цвет окружности} , Visible: Boolean; {признак видимости} end; Как видим, тип CIRCLE отличается от типа POINT только новым полем RADIUS (радиус), во всем остальном они схожи. Для этого типа необходимо написать свои процедуры и функции, которые выполняют приблизительно такие же действия, что и с точкой: вычерчивают окружность (делают видимой) или стирают ее, изменяют положе- ние, цвет, размер и т.д. Например, для указания новых координат и размера окружно- сти моэ^сно использовать такую процедуру: PROCEDURE Circlelnit(Target: Circle; NewX, NewY: integer; NewRadius: word); BEGIN with Target do begin X: = NewX; Y: = NewY; Radius: = NewRadius end END: {Circlelnit} Так обстоит дело в традиционном программировании. Объектно - ориентированное 420
программирование дает другой подход. Заметим, что все простейшие фигуры будущей | графической библиотеки характеризуются некоторыми общими свойствами: координата- ми характерной (центральной или угловой) точки, цветом, признаком светимости. Эти свойства логично закрепить за некоторыми общими для всех объектов полями данных, и определить некоторые также общие для всех правила преобразования и анализа этих полей. Назовем получившийся в результате такого объединения объект именем LOCATION (положение): type Location = object {объект LOCATION будет родителем} {для иерархии графических объектов.} {Первыми в объекте всегда перечисляются данные:} X, Y : integer; {координаты характерной точки} Color : word: {цвет графической фигуры} Visible: Boolean {признак светимости} {За данными описываются применяемые к ним правила:} Procedure SetLocation(NewX, NewY: integer); {задает новое положение} {характерной точки} Procedure SetColor(NewColor : word); {устанавливает цвет фигуры} Procedure SetVisib 1 е(Vis: Boolean); {устанавливает признак светимости} Procedure GetLocation(var XLoc, YLoc: integer); {возвращ’ает координаты} {характерной точки} Function GetColor: word; {возвращает цвет фигуры} Function GetVisible: Boolean {возвращает признак светимости} end; {конец описания объекта LOCATION} Тип LOCATION задает образец объекта, который, вероятно, не будет использо- ваться в программе самостоятельно, но послужит родоначальником целой иерархии объектов. Как видим, в объекте LOCATION описаны не только уже использовавшиеся в типе POINT поля X, Y, COLOR и VISIBLE, но и некоторые правила работы с этими полями: процедурами SETLOCATION, SETCOLOR и SETVISIBLE можно установить нужные значения полей, а функциями GETCOLOR. GETVISIBLE и процедурой GETLOCATION - прочитать эти значения. Заметим, что сами правила в объекте не раскрываются - они будут конкретизированы где-то дальше по тексту программы. Та- ким образом, появление процедур и функций в качестве правил в некотором объекте эквивалентно их опережающему описания (см. 9.6.). В объектно-ориентированном программировании предполагается, что доступ ко всем без исключения полям объекта реализуется в основном с помощью соответствую- щих правил. В некоторых более ранних объектно-ориентированных языках, таких как Smalltalk, доступ к данным возможен только с помощью встроенных правил. В этом случае говорят, что данные и правила строго инкапсулированы. Турбо-Паскаль более гибок в этом отношении, однакб и для него предпочтительнее полная инкапсуляция Данных, т.е. доступ к ним только с помощью правил. Инкапсуляция накладывает огра- ничения на имена формальных параметров, которые не могут совпадать с именами по- лей в том же объекте. Однако именно вследствие инкапсуляции операторы в теле лю- бого правила могут беспрепятственно использовать любое поле объекта (не забывайте, нто поля данных в объекте всегда описываются до объявления правил). Теперь введем новый объект POINT для описания точки: type Point = object(Location) {объект POINT служит для описания точки. Он является потомком Указанного в скобках объекта LOCATION. От своего родителя он отличается следующими дополнительными правилами:} 421
Procedure Show; {делает точку видимой} Procedure Hide; {делает точку невидимой} Procedure SetPoint(NewX, NewY:integer; NewColor: word); {задает точку} end; {конец описания объекта POINT} При описании объекта POINT за зарезервированным словом OBJECT в круглых скобках указано имя объекта-родителя LOCATION. Потомок POINT наследует все дан. ные и правила своего родителя и, кроме того, дополняет его новыми правилам^ SHOW, HIDE и SETPOINT. Такое описание эквивалентно следующему: type AlternativePoint = object X, Y : integer; Color : word; Visible: Boolean: Procedure SetLocation(NewX, NewY: integer); Procedure SetColor(NewColor: word); Procedure SetVisib1e(Vis: Boolean); Procedure GetLocation(var XLoc, YLoc: integer) Function GetColor: word; • Function GetVisible: Boolean; Procedure Show; Procedure Hide; Procedure SetPoint(NewX, NewY: integer; NewColor: word) end: Если, однако, использовать описание ALTERNATTVEPOINT вместо POINT, то нам пришлось бы описывать шесть пар совершенно идентичных правил: SETLOCATION) SETCOLOR. GETVISIBLE для объектов LOCATION и POINT. Таким образом, создание иерархии родственных объектов позволяет более экономно описывать каждый объект- потомок за счет автоматического наследования им всех свойств объекта-родителя. Полезно учесть, что при обработке родственных объектов компилятор Турбо-Пас- каля формирует поля объекта-потомка непосредственно за полями объекта-родителя, так что чем ниже в иерархии объектов располагается объект-потомок, тем больше па- мяти он занимает. Свойство наследования накладывает ограничения на имена полей: ни одно поле объекта-потомка не может иметь имя. совпадающее с именем какого-либо поля в объекте-родителе. Это ограничение не распространяется на имена правил, кото- рые могут совпадать с именами правит в объекте-родителе и, следовательно, “закры- вать" их подобно тому, как локальные имена данных и блоков закрывают собой гло- бальные имена в процедурах и функциях. В силу того же свойства наследования пото- мок автоматически получает доступ к любому полю родителя. Для описания окружности введем объект CIRCLE: type Circle = object(Location) Radius: word; I {радиус окружности} Procedure Init(InitXr InitY: integer: InitRadius, InitColor: word) {определяет параметры окружности} Proocedure Show; {делает окружность видимой} । Procedure Hide; {делает окружность невидимой} Procedure M6veTo(NewX, NewY: word); {перемещает окружность} Prodedure Expand(ExpandBy: integer) {изменяет размер (радиус) окружности} end; Как видим, объект CIRCLE отличается от своего объекта-родителя LOCATION & ременной RADIUS и правилами INIT, SHOW, HIDE, MOVETO и EXPAND. НетрудН0 422
заметить, что правила SHOW и HIDE имеются и в родственном объекте POINT, од- нако способы реализации одинаковых правил у этих объектов совершенно различны: PROCEDURE Point.Show; ' {реализация правила SHOW для объекта POINT.} BEGIN V isible: = true; PutPixel(X, Y, Color) END; {PointVisible} PROCEDURE Point.Hide; {реализация правила HIDE для объекта POINT.} BEGIN V isible: = fa Ise; PutPixel(X, Y, GetBkColor) END; {--------------------------} PROCEDURE Circle.Show; {реализация правила SHOW для объекта CIRCLE.} var ColorLoc: word; BEGIN if not GetVisible then begin SetV i s ible(true); ColorLoc: = Graph.GetColor; Graph.SetColor(Color); Graph.Circle(X, Y, Radius); Graph.SetCo lor(ColorLoc) end END {Circle.Show}; {--------------------------} PROCEDURE Circle.Hide; {реализация правила HIDE для объекта CIRCLE.} var ColorLoc: word; BEGIN if GetVisible then begin SetVisible(false); ColorLoc: = Graph.GetColor; Graph. SetColor(GetBKColor) Graph.Circle(X, Y, Radius); Graph. SetColor(ColorLoc) end END; {Circ le.Hide} Применение одноименных правил к различным родственным объектам вполне до- пустимо: компилятор всегда связывает правила и данные объекта в единый логический блс^ и применяет именно эти правила и данные к любым экземплярам данного объек- та/ Как уже говорилось, объекты задают некоторый образец, по которому в программе можно создать и использовать сколько угодно экземпляров данного типа, например: var Loci : location; Pointl: point; Circle!, Circle2: circle; beg in 423
with Circle! do begin {создать и показать новую окружность:} Init(100, 100r 20, white); Show end; with Point! do begin {создать и показать новую точку:} SetLocation(25, 25); SetColor(red) end Circlel.MoveTo(25, 25); {переместить окружность} Pointl.Hide; {погасить точку} Circle2/Hide; {погасить окружность} Как видно из этого примера, использование экземпляров объекта во многом напо- минает использование записей: для доступа к определенным в них данным и правилам применяются составные имена типа POINT.SETCOLOR или CIRCLE1.MOVETO, а также оператор WITH. Именно поэтому операторы POINT1.HIDE и CIRCLE2. HIDE приведут к вызову разных правил HIDE. Вызов правила определяется тем, экземпляр какого именно объекта (POINT или CIRCLE) предшествует вызываемому правилу в со- ставном имени или какой экземпляр объекта связан с оператором вызова предложени- ем WITH ... DO. Любопытным свойством объектов является возможность присваивания значений экземпляра одного объекта экземпляру другого объекта, причем второй экземпляр мо- жет быть объектом-родителем первого. Для нашего примера допустимо, в частности, такое присваивание: Loci: = Circle2; Почему возможно такое присваивание? Чтобы разобраться в этом, вспомним, что объект-потомок CIRCLE унаследовал от своего родителя LOCATION все свойства, поэ- тому экземпляр CIRCLE1 имеет все поля данных экземпляра LOCI и в ходе присваи- вания эти поля будут полностью определены. Именно по этой причине недопустимо обратное присваивание, т.е. ошибочным будет оператор Circlel: = Loci; так как "лишние" поля экземпляра CIRCLE1 останутся неопределенными. Рассмотрен- ное выше расширенное правило совместимости объектов распространяется на любые родственные объекты: если ТО1 и ТО2 - экземпляры двух родственных объектов, то присваивание Т01: = Т02 возможно только в том случае, если оба экземпляра есть объекты одного и того же типа или если объект ТО2 есть прямой или косвенный потомок объекта ТО1 ("косвен- ный потомок" означает потомок в k-м поколении). Во многих случаях может понадобиться изменить то или иное правило объекта непосредственно в ходе работы программы. Для этого используется описание соответ- ствующего правила как виртуального (изменяемого) с помощью зарезервированного слова VIRTUAL. Вернемся вновь к проблеме создания библиотеки графических фигур- 11 равила SHOW и HIDE позволяют показать на экране или погасить фигуру. Последо- вательное применение этих правил к графическим объектам с одновременным измене- нием координат их характерной точки позволит создать иллюзию перемещения соответ- ствующей фигуры по экрану, для чего могут понадобиться правила DRAG (перемеще- ния). Сделаем упомянутые правила / виртуальными и включим их в родительский тип LOCATION: type Location = object X, Y : integer; Color ; word; Visible: Boolean; 424
Procedure SetLocation(NewX, NewY: integer); Procedure SetColor(NewColor: word); Procedure SetVisible(Vis: Boolean); Procedure GetLocation(var XLoc, YLoc: integer); Function GetColor: word; Function GetVisible: Boolean; Procedure Show; virtual; Procedure Hide; virtual; Procedure Drag(DragBy: integer); virtual end; Алгоритм нового правила DRAG может быть, например, таким: сначала показыва- ется фигура, затем осуществляется анализ нажатых клавишей на клавиатуре ПЭВМ и в случае, если нажата клавиша управления курсором, фигура гасится, изменяются нуж- ным образом координаты ее характерной точки, после чего алгоритм повторяется- фигура показывается вновь и т.д.; если нажата некоторая заранее обусловленная клави- ша (например, "Ввод"), правило DRAG прекращает работу и возвращает управление основной программе. Вот возможный вариант реализации описанного алгоритма: FUNCTION GetDelta(var DeitaX, DeltaY: integer): Boolean; {Эта.функция считывает состояние клавиатуры. Если нажата клавиша уп- равления курсором, функция возвращает TRUE, при этом в параметрах вызова передаются признаки приращения координат характерной точки. Если нажата клавиша "Ввод”, функция возвращает FALSE. Нажатие на любую другую клави- шу игнорируется.} var Quit: Boolean; {признак необходимости завершения работы} BEGIN {GetDelta} GetDelta: = true; * repeat DeltaX: =0: DeltaY: =0; Quit: = true; case ReadKey of #0 : begin {'код из расширенного набора} case ReadKey of # 72 : DeltaY: = -1; {стрелка вверх} # 80 : DeltaY: = +1; {стрелка вниз} # 75 : DeltaX: = -1; {стрелка влево} , #77 : DeltaX: = +1; {стрелка вправо} else Quit: = false {другие клавиши игнорировать} end {case} end; {#0} #13: GetDelta: - false; {клавиша ’’Ввод”} else Quit: = false; {другие клавиши игнорировать} end {case} until Quit END; {GetDelta} PROCEDURE Location.Drag(DragBy: integer); {перемещает любую графическую фигуру по экрану.} var DeltaX, DeltaY: integer; PosX, PosY: integer; BEGIN {Location.Drag} Show; {показать исходное положение фигуры} while GetDelta(DeltaX, DeltaY) do {цикл перемещения} begin Hide; {погасить изображение фигуры} GetLocation(PosX, PosY); 425
SetLocation(PosX+DeltaX*DragBy, PosY+DeltaY*DragBy); {изменить координаты} Show {показать новое положение фигуры} end END; {Locat ion ..Drag} Теперь любой производный от' LOCATION объект унаследует от него не только прежние свойства (светимость, положение и цвет), но также и новые виртуальные правила. Перед обращением к этим правилам объект-потомок должен каким-либо обра- зом указать, какие именно данные (и правила) следует в них использовать. Для этого в Турбо-Паскале предусмотрен специальный тип процедуры - конструктор. Конструкто- ры описываются с помощью зарезервированного слова CONSTRUCTOR, которое заме- няет собой слово PROCEDURE в описании соответствующего правила, например: type Circle = object(Location); Radius: word; Constructor Init(InitX, InitY, InitRadius, InitColor: word); Procedure Show; virtual; Procedure Hide; virtual; Procedure MoveTo(NewX, NewY: word); Procedure Expand(ExpandBy: integer) end; - Как видим, процедуры SHOW и HIDE объявляются виртуальными точно так же, как и в родительском объекте LOCATION, - это обязательное требование. Кроме того, вызов конструктора (в данном случае INIT) при исполнении программы должен пред- шествовать обращению к любому виртуальному правилу - это второе непременное пра- вило. Совершенно недостаточно инициировать некоторый экземпляр объекта и затем присвоить его значение другому экземпляру: перед обращением к любому виртуально- му правилу этого второго экземпляра следует вновь инициировать его вызовом конст- руктора. Таким образом, если в описании объекта-потомка используется одно или не- сколько виртуальных правил, в этом объекте должен быть хотя бы один конструктор. Все объекты-потомки, использующие виртуальные правила объекта-родителя, должны описывать соответствующие правила как виртуальные. Объявив объект CIRCLE, мы получили доступ к правйлу DRAG родительского объекта LOCATION, которое в свою очередь использует виртуальные правила SHOW и HIDE из объекта-потомка! Например: var Acircle: Circle; begin Acircle.Init(100, 100, 20, white); ,{Обращение к конструктору для указания начального положения ок- ружности одновременно активизирует связанные с типом CIRCLE виртуальные правила SHOW и HIDE.} Location.Drag(5); , {Это и есть полиморфизм•в действии: процедура LOCATIN. DRAG спо- собна перемещать любые производные от типа LOCATION объекты за счет ис- пользования определенных в них правил SHOW и HIDE.} Замечательным свойством виртуальных правил является то обстоятельство, что связывание этих правил с инкапсулированными данными происходит не на этапе ком- пиляции, а непосредственно в ходе исполнения программы. Такое связывание называет- ся поздним (в отличие от него связывание данных с невиртуальными правилами осуще- ствляется компилятором и называется ранним). Позднее связывание придает Турбо-Па- скалевым программам совершенно новое свойство расширяемости. Вы можете разрабо- тать некоторый библиотечный модуль и поставлять его пользователям в виде TPU-фай- ла. Пользователи смогут не только дополнять этот модуль новыми объектами, но и применять к ним уже существующие правила, если, разумеется, эти правила объявле- 426
0Ы Вами как виртуальные - позднее' связывание позволит легко осуществить это, не прибегая к изменению и перекомпиляции исходного текста. Как и любые другие данные, экземпляры объектов могут размещаться в динами- ческой памяти (куче) с помощью процедуры NEW и удаляться из нее с помощью pISPOSE (см. гл. 7), например: var PCircle: "Circle; begin New(PCirele); PCircle".Init(100, 100, 20, white); PCircle".Drag(2); Dispose(PCircle) * end Синтаксис процедур NEW и DISPOSE в версии 5.5 расширен по сравнению с вер- сией 5.0 таким образом, чтобы дать более удобные средства работы с объектами. В частности, при вызове процедуры NEW можно в качестве второго ее параметра ука- зать имя конструктора, чтобы не только разместить новый экземпляр объекта в куче, но и одновременно инициировать его, например: New(PCirc/le, Init(100, 100, 20, white)); Обратите внимание: конкретный экземпляр объекта PCIRCLEA к моменту вызова процедуры NEW еще не существует, поэтому было бы ошибкой указать его префикс в составном имени, т.е. нельзя написать New(PCircle, PCirele".In it(...)); Процедуру NEW можно вызывать теперь и как функцию, которая в этом случае будет возвращать ссылку на динамический объект: type CirclePtr ="Circle; var PCircle: CirclePtr; begin PCircle: = New(CirelePtr, Init(100, 100, 20, white)); Кстати, это свойство можно использовать и для других типов Турбо-Паскаля, на- пример: type CharPtr ="Char; var PChar: CharPtr; PChar: = New(CharPtr); Обратите внимание: функция NEW возвращает ссылку на вновь размещенный в куче экземпляр объекта по указанному типу объекта, поэтому при обращении к функ- ции NEW первым параметром должен указываться тип объекта, в то время как при обращении к процедуре NEW первым должен стоять указатель на него. Правила совместимости типов в Турбо-Паскале 5.5 расширены таким образом, что если PTR1 - указатель на объект ТО1 и PTR2 - на объект ТО2, то возможны присваи- вания PTR1: = PTR2 или PTR2: = PTR1, если только ТО1 и ТО2 - родственные объек- ты. В Турбо-Паскале 5.5 предусмотрен особый вид правил, который называется де- структор. Деструктор сочетает операцию освобождения динамической памяти с выпол- нением некоторых других действий, которые могут понадобиться при. ликвидации раз- мещенного в куче объекта. Деструкторы, объявляюся с помощью зарезервированного слова DESTRUCTOR при описании объекта. Для иллюстрации техники работы с размещаемыми в куче объектами допустим. Вы имеете файл FIGURES.TPU с уже разработанной кем-то библиотекой графических фигур и Вам понадобилось создать связанный список графических объектов. Как уже 427
отмечалося в 7.2, каждый элемент связанного списка должен иметь ссылку на следую- щий элемент или признак NIL, если это последний элемент в списке. Однако роди- тельский тип LOCATION, от которого берут начало все другие объекты графической библиотеки, не имеет поля-указателя и добавить это поле нельзя, так как считается, что исходный текст модуля * Вам недоступен. Выходом из положения может служить создание нового объекта LIST (список), который не является ничьим потомком и в ко- тором предусмотрены необходимые для организации списка поле NODES (узлы) и пра- вила работы с этим полем. В частности, с помощью правила ADD можно добавлять новые элементы в список. В силу расширенного правила совместимости типов поле NODES может содержать ссылку на любой производный от LOCATION объект, что позволяет создавать списки с указателями на экземпляры любых графических фигур. Ниже приводится полный текст модуля FIGURES и иллюстрирующей программы LISTDEMO. {======== } unit Figures; {======== } INTERFACE Uses Graph, crt; type Location = object X, Y : integer Color : word: ~ Visible: Boolean Procedure SetLocation(NewX, NewY: integer); Procedure SetColor(NewColor : word); Procedure SetVisib1e(Vis: Boolean); Procedure GetLocation(var XLoc, YLoc: integer) Function GetColor: word; Function GetVisible: Boolean; Procedure Show; virtual; Procedure Hide; virtual; Procedure Drag(DragBy: integer); virtual end; {конец описания объекта LOCATION} Point = object(Location) Procedure Show; virtual; Procedure Hide; virtual; Procedure SetPoint(NewX, NewY: integer; NewColor: word); end: {конец описания объекта POINT} Circle = object(Location) Radius: word; Constructor Init(InitX, InitY: integer; InitRadius, InitColor: word); Procedure Show; virtual; Procedure Hide; virtual; Procedure MoveTo(NewX, NewY: word); Procedure Expand ExpandBy: (integer) end; {конец объекта CIRCLE} {------------------------------------------------} IMPLEMENTATION PROCEDURE Location.SetLocation(NewX, NewY: integer); BEGIN X: =NewX; Y: =NewY; SetVisible(fa 1se) END; {------------------------------} PROCEDURE Location.SetColor(NewColor: word); BEGIN Color: =NewColor END; 428
PROCEDURE Locat ion . SetV i s i Ые (V i s: Boolean); BEGIN Visible: =Vis END; PROCEDURE Location.GetLocation(var XLoc, YLoc: integer); BEGIN XLoc: =X; YLoc: =Y END; {------------------------------} FUNCTION Location.GetColor: word; . BEGIN GetColor: =Color END; {-----------------------------} FUNCTION Location.GetVisible: Boolean; BEGIN GetV i s i ble END; {--------------— --------------} PROCEDURE Location. Show; BEGIN if not GetVisible then begin PutPixel(X, Y, Color); SetVisible(True) end END; {--------------------------- - } PROCEDURE Location Hide; BEGIN if GetVisible then begin PutPixel(X, Y, GetBKColor); SetV i s i ble(faIse) end END; {------------------------------} FUNCTION GetDelta(var DeltaX, DeltaY: integer): Boolean; var Quit: Boolean; BEGIN {GetDelta} GetDelta: =true; repeat DeltaX: =0; DeltaY: =0;. Quit: =true; qase ReadKey of #0 : begin {код из расширенного набора} case RedKey of # 72 : DeltaY: =-l; {стрелка вверх} # 80 : DeltaY: =+l; {стрелка вниз} # 75 : DeltaX: =-l; {стрелка влево} # 77 : DeltaX: =+l; {стрелка вправо} else Quit: =false {другие клавиши игнорировать} end {case} end; {#0} 429
#13: GetDelta: =false; else Quit: =false; end {case} until Quit END; {GetDelta} PROCEDURE Location.Drag(DragBy: integer); var DeltaX, DeltaY: integer; PosX, PosY: integer; BEGIN {Location. Drag} Show; while GetDelta(DeltaX, DeltaY) do begin Hide; GetLocation(PosX, PosY); SetLocation(PosX+DeltaX*DragBy, PosY+DeltaY*DragBy); Show end END; {Location.Drag} PROCEDURE Point.Show; {делает точку видимой} BEGIN ' Location.Show END; PROCEDURE Point.Hide; {делает точку невидимой} BEGIN Location.Hide END; {------------------------------} PROCEDURE Point.SetPoint(NewX, NewY: integer; NewColor: word); BEGIN SetLocation(NewX, NewY); SetColor(NewColor) END; {------------------------------} CONSTRUCTOR Circle.Init(InitX, InitY: integer; InitRadius, InitColor: word); BEGIN SetLocation(.InitX, InitY); SetColor(InitColor); Radius: =InitRadius END; {------------------------------} PROCEDURE Circle.Show; var LocColor: word; BEGIN if not GetVisible then begin LocColor: =Graph.GetColor; Graph.SetColor(Color); Graph.Circle(X, Y, Radius); SetVisible(true); Graph.SetColor(LocColor) end 430
5 END; PROCEDURECircle.Hide; var LocColor: word; BEGIN if GetVisible then begin LocColor: =Graph. GetCo lor;. Graph.SetColor(GetBKColor); Graph.Circle(X, Y, Radius); SetVisible(false); Graph.SetColor(LocColor) end END; PROCEDURE Circle. Mcvefo(NewX, NewY: word); BEGIN Hide; Init(NewX, NewY, Radius, GetColor); Show END; {-'---------------------------} , PROCEDURE Circle. Expand(ExpandBy: integer); BEGIN Hide; Init(X, Y, Radius*ExpandBy, GetColor); Show END; { = = = = = = = = ,} END. {Figures} {======= } PROGRAM ListDemo; Uses Graph, Figures; type NodePtr=ANode; ' LocPtr ^Location; CirclePtr ="CiroTe; Node=record {тип для элемента списка} Item: LocPtr; {указатель на графическую фигуру} Next: NodePtr {указатель на следующий элемент} end; L istPtr=4 i st; List=object {тип для списка} Nodes:NodePtr; {указатель на элемент списка} Constructor Init; {инициация списка} Destructor Done; virtual; {уничтожение списка} Procedure Add(Item: LocPtr); {добавление элемента} end; var GD, GR: integer; {переменные для процедуры INITGRAPH} Alist: List; {экземпляр списка} Pcircle: CirclePtr; CONSTRUCTOR List.Init; {инициация списка} BEGIN Nodes:=n i 1 END;. DESTRUCTOR List.Done; {уничтожение списка} 431
var N: NodePtr; BEGIN while Nodes nil do {уничтожать все элементы} begin N:=Nodes; {N' очередной уничтожаемый элемент} vDispose(N~.Item); {уничтожить фигуру} Nodes:=N~.Next; {ссылка на следующий элемент} Dispose(N) {уничтожить элемент списка} end END; PROCEDURE List. Add(Item: LocPtr); {добавить элемент к списку} var N: NodePtr; BEGIN New(N); {разместить элемент списка} Nitem:=Item; {ссылка на фигуру} NNextr=Nodes; {ссылка на следующий элемент} Nodes:=N {ссылка на текущий элемент} END; {------------------------------} BEGIN GD:=detect; ' InitGraph(GD, GR, '’); with Alist do begin Init; {инициализировать список} PCircle:=New(CirclePtr, Init(200, 100, 20, white)); {создать и разместить окружность} ' Add(Pcirc1е); {добавить элемент в список} PCirc le". Drag(10); {перемещать окружность} Done {освободить всю кучу} end; closegraph END. В версии 5.5 расширены возможности справочной службы. Теперь большинство справок по процедурам и функциям дополнены короткими примерами их использования. Вы можете "вырезать" эти примеры из справочного окна и перенести их в текст программы. Для этого после вызова нужной справки нажмите клавишу с символом С - в окне появится курсор (ESC вернет. окну первначальный вид). Подведите курсор к интересующему Вас фрагменту и нажмите В, чтобы зафиксировать начало выделяемого участка - на месте курсора появится темный прямоугольник. Теперь при переводе курсора вниз и вправо выделенный участок будет соответствующим образом расширяться и Вы сможете выделить все необходимое. После такой подготовки достаточно нажать "Ввод” - и выделенный фрагмент окна будет скопирован в текст программы на то место, которое занимает курсор в окне редактора. 432
ЛИТЕРАТУРА 1. Вирт Н. Программирование на языке Модула-2: Пер. с англ.- М.: Мир, 1987.- 222 с. 2. Дао Л. Программирование микропроцессора 8088: Пер. с англ.- М.: Мир, 1988.- 357 с. 3. Йенсен К.. Вирт Н. Паскаль. Руководство для пользователя и описание язы-» ка: Пер. с англ. - М.: Финансы и статистика, 1982.- 151 с. 4. Йодан К. Стуктурное программирование и конструирование программ: Пер. с англ.- М.: Мир, 1979.- 415 с. 5. Перминов О.Н. Программирование на языке Паскаль.- М.: Радио и связь, 1988.- 224 с. 6. Фаронов В.В. Организация работы на ПЭВМ: Методические указания по ис- пользованию вычислительной техники в курсовом и дипломном проектировании.- М.: Изд-во МГТУ, 1989.- 26 с. 7. Фаронов В.В. Система автоматизированного моделирования СИАМ: Методиче- ские указания к лабораторным работам по курсу “Основы автоматизации проектирова- ния САУ".- М.: Изд-во МГТУ, 1989.- 30 с. 8. Форсайт Дж., Малькольм М., Моулер К. Машинные методы математических вычислений: Пер. с англ.- М.: Мир, 1980.- 279 с. 9. Язык программирования Ада (предварительное описание): Пер. с англ.- М.: Фи- нансы и статистика, 1981.- 190 с. L
ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ При чтении многоуровневых ссылок предметного указателя необходимо руководст- воваться следующими правилами: -если термины в рубриках согласуются по падежам, они читаются от старшей рубрики к младшей, например вычерчивание - дуги окружности читается: вычерчивание дуги окружности; - если рубрики не согласуются по падежам, они читаются в обратном порядке с изменением соответствующих окончаний: видеотерминал / - режим работы - - графический ----инициализация читается: инициализация графического режима работы видеотерминала; -оба правила распространяются на ссылки, в которых чередуются согласованные и несогласованные рубрики: принтер, команды его - - графической печати читается: команды графической печати принтера. А адаптер 198, 209 - CGA 198, 209, 211 - EGA 198, 210, 211 - Hercules 198 - VGA. 198,210 адрес 133 - сегмент 133 - смещение 133 алфавит 72 апостроф 19 арифметика с плавающей точкой 89 Б байт атрибутов 198 блок 149 - вызов 150 - - рекурсивный 152, 166 - - описание 154 - опережающее 166 - параметры 153 - - значения 155 - - массивы 157 - - строки 157 - - переменные 155 ----нетипизированные 163 - - фактические 155 - - формальные 155 буквы 72 буферизация ввода 111 В ввод, клавиша его 16 - данных 24 - буферизация 111 вектор прерывания 184 видеопамять 196 - страница 197, 224 - байт атрибутов 198 видеотерминал 195 434
- контррллер 198 - режим работы 196, 214 - - графический 209, 358 вывод текста 211, 259 инициация 214 определение 218 - - текстовый 196 выражение 75 - значение 75 вывод текста 211, 253 вычерчивание - дуги окружности 234, 259 - дуги эллипса 236 - ломаной линии 232 - линии 226, 259 - многоугольника 239 - окружности 233, 260 - параллелепипеда 231 - прямоугольника 230 - сектора окружности 249 - сектора эллипса 248 - эллипса 248 - вариантная часть 96 - объявление 94 - список полей 94 - раздел 94 заполнение см. штриховка зарезервированное слово - ABSOLUTE 106 - AND 28 - ARRAY 92 - BEGIN 19,29 - CASE 35, 96 - CONST 19 - DIV 26 - DO 32, 95 - DOWNTO 33 - ELSE 30, 35 - END 21,29 - EXTERNAL 178 - FILE 108 - FOR 32 - FORWARD 168 - FUNCTION 153 - GOTO 37. Д дисплей см. видеотерминал директива компилятора - $А 65 - $В 65 - $D 66 - SDEFINE 66 - $Е 66 - SELSE 55 - $ENDIF 55 - $F 66 - $1 66 - SIFDEF 55 - $L 66 - $M 66 - $N 66 - $O 66 - $R 66 - $S 66 - $V 66 драйвер 12 - графический 12, 214 - - включение в программу 270 - клавиатуры 279 - IF 30 - IMPLEMENTATION 170 - IN 76, 101 - INLINE 180 - INTERFACE 170 - INTERRUPT 184 - LABEL 37 - MOD 22 - NIL 140, 148 - NOT 28 - OF 35, 92, 100 - OR 28 - PACKED 92 - PROCEDURE 153 - PROGRAM 19 - RECORD 94 - REPEAT 34 - SET 100 - SHL 78 - SHR 78 - STRING 102 - THEN 30 - TO 32 - TYPE 83 - UNIT 170 3 запись 94 - UNTIL 34 - USES 171 435
- VAR 23 - WHILE 33 - WITH 95 - XOR 28 звукогенератор .289 знаки специальные 72 знакогенератор - таблица 276, 365 - - загружаемая часть 267 знакоместо 197 И идентификатор 21, 73 - описание 22 - ограничения на него 21 изображение, выдача его 250, 260 - константы 251 - координаты 220, 250 - окна 220 - отношение сторон 222 - сохранение 250 - фрагмент 250 имя диска 110 - каталога ПО - локализация 150 - файла 109 индекс 93 индексный тип 93 ч интеграла вычисление 304 интерполяция 299 К каталог системный 12 - смена 46 - текущий 12 клавиша “Ввод” 16 - "Забой" 15 - перевода курсора 15 - переключатель 275 - смещения 274 кластер 129 клавиатура 273 ключ выбора 35 код ASCII 85 - встроенный машинный 180 - расширенного набора 275 - сканирования 273 кодовое слово см. зарезервированное слово компоновщик 56 константа 73 - вещественная 74 - выбора 35 - выравнивания текста 255 - значение 22 - имя 19 - логическая 74 - описание 22, 75 - символьная 22,74 - типизированная 145 - - для записи 147 - - для массива 146 - - для множества 148 - - объявление 145 - - для простого типа 145 - - строковая 145 - - для указателя 148 - цвета 203, 238 - целая 22, 75 - черепаховой графики 262 - шрифта 254, 255 курсор - размер 201 - положение 201 куча см. также память динамичес- кая 132 - размер 56 М мантисса 88 машинное эпсилон 33 массив 92 - МЕМ 192 - MEML 192 - MEMW 192 - PORT 192 - PORTW 192 меню 39 - главное 40 - система 40 - структура 40, 41 - правила перехода 40 метка 37 - описание 37 - использование 37 множество 100 - конструктор 100 - объявление 100 - операции над ним 101 модель памяти 53 - ближняя 53 436
- дальняя 53 модуль 169 - DECOMPSV 294 - GRAPHTXT 364 - MINFUN 3321 - SIMULA 311 - SPLINUNT 293 - TEXTCRT 365 - заголовок 170 - инициирующая часть 172 - интерфейсная часть 171 - исполняемая часть 172 - компиляция 173 - объекты глобальные 171 - - доступ 174 - связь 170 - стандартный 176 - - CRT 176 - - GRAPH 176 - - GRAPH3 177 - - OVERLAY 177 - - PRINTER 176 - - SYSTEM 176 - - TURBO3 177 монитор см. видеотерминал - MS DOS и PC DOS 181 операция 76 - @ 77 - AND 78 - DIV 27 - IN 77 - MOD 27 - NOT 78 - OR 78 - SHL 78 - SHR 78 - XOR 78 - аддитивная 76 - арифметическая 26 - логическая 28, 76 - мультипликативная 76 - отношения 28, 76 - унарная 76 опция 40 - переключатель 40 - структура 40, 41 - указатель 40 - BREAK/WATCH 63 - - ADD WATCH 63 - - CLEAR ALL BREAKPOINTS 65 - - DELETE WATCH 63 О образец рисунка 244 - штриховки 244 оверлей 188 - буфер 188 - - изменение размера 192 - инициация 190 - создание 189 окно графическое 220, 259 - отладчика 13 - программы 13 - редактора 12 - текстовое 201, 206 оператор выбора 35 - перехода 37 - повторения - - FOR 32 - - REPEAT 34 - - WHILE 33 - присвоения 24 - пустой 29 - составной 29 операционная система 181 ' - функции 181 - - EDIT WATCH 64 - - REMOVE ALL WATCHES 64 - - TOGGLE BREAKPOINT 64 - - VIEW NEXT BREAKPOINT 65 - COMPILE 49 - - BUILD 50 - - COMPILE 49 - - DESTINATION 50 - - FIND ERROR 50 - - GET INFO 51 - - MAKE 49 - - PRIMARY FILE 50 - DEBUG 60 - - CALL STACK 62 - - DISPLAY SWAPPING 63 ALWAYS 63 - - - NEVER 63 - - - SMART 63 - - EVALUATION 62 - - FIND PROCEDURE 62 - - INTEGRATED DEBUGGING 62 - - REFRESH DISPLAY 63 - - STANDALONE DEBUGGING 62 - FILE 47 437
- - CHANGE DIRECTORIES 46 - - DIRECTORY 46 - - LOAD 47 - - NEW 46 - - OS SHELL 46 - - PICK 45 - - QUIT 47 - - SAVE 46 - - WRITE TO 46 - OPTIONS 52 - - COMPILER 52 ALIGN DATA 54 CONDITIONAL DEFINES 55 DEBUG INFORMATION 55 EMULATION 54 FORCE FAR CALLS 53 I/O CHECKING 53 LOCAL SYMBOLS 55 MEMORY SIZE 56 --HIGH HEAP LIMIT 56 --LOW HEAP LIMIT 56 --STACK SIZE 56 NUMERIC PROCESSING 54 OVERLAYS ALLOWED 53 RANGE CHECKING 53 STACK CHECKING 53 VAR-STRlNG CHECKING 54 - - DIRECTORIES 59 CURRENT PICK FILE 60 EXE & TPU DIRECTORIES 59 INCLUDE DIRECTORIES 59 OBJECT DIRECTORIES 60 PICK FILE NAME 60 TURBO DIRECTORIES 59 - - - UNIT DIRECTORIES 59 - - ENVIRONMENT 57 BACKUP FILES 58 CONFIG AUTO SAVE 57 EDIT AUTO SAVE 57 4 SCREEN SIZE 59 TAB SIZE 58 ZOOM WINDOWS 59 - - LINKER 56 LINK BUFFER 56 - - - MAP FILE 56 - - PARAMETERS 52 - - RETRIEVE OPTIONS 52 - - SAVE OPTIONS 52 - RUN 47 - - GOTO CURSOR 48 - - PROGRAM RESET 47 - - RUN 47 - - STEP OVER 48 - - TRACE INTO 48 - - USER SCREEN 48 отношение сторон экрана 222 П палитра 210, 239 память динамическая 1а. 0ад6ё куча - нехватка 57 - объем 56 - прямое обращение к ней 192 - экономия 57 параграф 133 Паскаль 11 переменная внутреннее представле- ние ее 22 - описание 23 - процедурного типа 162 - фиктивная 165 пиксель 195 порт ввода-вывода 192 прерывание 183 - вектор 183 принтер 282 - иголки 282 - команды 284 - - выбора шрифта 284 - - графической печати 288 - - прогона бумаги 285 - печатающий механизм 282 - режимы работы 282 - шрифт 282 программа внешняя 186 - генерации шрифтов 372 - компиляция 49 - отладка 47, 60 - прогон 47 - структурирование 150 - текст 15 - Остановки даты/времени 356 процедура 149 - APPEND 113 - ARC 234., 259 - ASSIGN 109 - ASSIGNCRT 208 - BACK 262 - BAR 247 438
- BAR3D 231 - BLOCKREAD 129 - BLOCKWRITE 129 - BORDER 341 - CHDIR 115 - CIRCLE 233, 260 - CLEARDEVICE 222 - CLEARVIEWPORT 222 - CLOSE 113 - CLOSEGRAPH 217 - CLREOL 207 - CLRSCR 207 - COLORS 337 - COLORTABLE 259 - COPYCHAR 340 - CURSOR 363 - CURSOROFF 346 - CURSORON 346 - DECOMP 283 - DELLINE 207 - DETECTGRAPH 218 - DISPOSE 136, 142 - DRAW 259 - DRAWPOLY 232 - ELLIPSE 236 - ERASE 114 - EXEC 186 - EXIT 391 - FILLCHAR 392 - FILLELLIPSE 248 - FILLPATTERN 261 - FILLPOLY 247 - FILLSCREEN 261 - FILLSHARE 261 - FINDFIRST 116 - FINDNEXT 118 - FLOODFILL 246 - FLUSH 115 - FORWD 262 - FREEMEM 138, 143 - FSPLIT 120 - GETARCCOORDS 235 - GETASPECTRATIO 222 - GETCBREAK 393 - GETCHAR 340 - GETCURSOR 337 - GETDATE 393 - GETDEFAULTPALETTE 242 - GETDIR 115 - GETFATTR 119 - GETFILLPATTERN 245 - GLTFILLSETTINGS 245 - GETFTIME 119 - GETIMAGE 250 - GETINTVEC 185 - GETLINESETTINGS 229 -GETMEM 138, 143 - GETMODERANGE 219 - GETPALETTE 240 - GETPIC 260 - GETSCREEN 339 - GETTEXTSETTINGS 257 - GETTIME 395 - GETVERIFY 395 - GETVIEWSETTINGS 221 - GETWINDOW 342 - GOTOXY 205 - GRAPHBACKGROUND 259 - GRAPHCOLORMODE 258 - GRAPHDEFAULTS 396 - GRAPHMODE 258 - GRAPHREAD 363 - GRAPHWINDOW 259 - HALT 396 - HIGHVIDEO 208 - HIDETURTLE 262 - HIRES 259 - HIRESCOLOR 259 - HOME 264 - INPUTINT 364 - INPUTREAL 364 - INSERT 103 - INSLINE 207 - INTR 182 - KEEP 185 - LINE 226 - LINEREL 227 - LINETO 227 - LOWVIDEO 208 - MARK 137, 143 - MKDIR 115 - MOVE 298 - MOVEFROMSCREEN 338 - MOVEREL 222 - MOVESCREEN 339 - MOVETO 222 . - MOVETOSCREEN 339 - MSDOS 183 - NEW 136. 143 - NORMVIDEO 208 439
NOSOUND 289 NOWRAP 266 OUTTEXT 253 OUTTEXTXY 258 OVRCLEARBUF 399 OVRINIT 290 OVRINTTEMS 192 OVRSETBUF 192 PACKTIME 399 PALETTE 259 PATTERN 261 PENDOWN 264 PENUP 265 PESLICE 249 PLOT 259 PUTCHAR 340 PUTCURSOR 337 PUTIMAGE 250 PUTPIC 260 PUTPIXEL 225 PUTSCREEN 340 PUTWINDOW 341 QUANC8 304 RANDOMIZE 89 READ 121 READLN 122 RECTANGLE 230 RELEASE 137, 144 RENAME 113 RESET 112 RESTORECRTMODE 217 REWRITE 113 RKF45 311 RMDIR 115 RUNERROR 401 RUNGE 311 SECTOR 248 •SEEK 128 SETASPECTRATIO 223 SETBKCOLOR 238 SETCBREAK 402 SETCOLOR 237 SETDATE 4<)2 SETFATTR 119 SETFILLPATTERN 244 SETFILLSTYLE 243 SETFTIME 119 SETGRAPHBUFSIZE 403 SETGRAPHMODE 213. SETHEADING 262 - SETINTVEC 185 - SETLINESTYLE 227 - SETPALETTE 239 - SETPENCOLOR 265 - SETPOSITION 263 - SETRGBPALETTE 246 - SETTEXTBUF 403 - SETTEXT JUSTIFY 255 - SETTEXTPAGE 338 - SETTEXTSTYLE 253 - SETTIME 407 - SETUSERCHARSIZE 256 - SETVERIFY 404 - SETVIEWPORT 220 - SETVISUALPAGE 224 - SETWINDOW 341 - SETWRITEMODE 229 - SHOWTURTLE 261 - SOLVE 294 - SOUND 289 - SPLINE 199 - STR 103 - SUBSTR 363 - SWAPVECTORS 187 - TEXTBACKGROUND 204 - TEXTCOLOR 204 - TEXTMODE 207 - TRUNCATE 406 - TURNLEFT 264 - TURNRIGHT 264 - TURTLEDELAY 262 - TURTLEWINDOW 265 - UNPACKTIME 406 - VAL 103 - WINDOW 206 - WRAP 266 - WRITE 122 - WRITELN 124 - внешняя 178 - заголовок 153 - обработки прерываний 183 путь к файлу 110 Р раздел операторов 19 - описаний 19 редактор, команды его 68 - - восстановления строки 71 - - поиска 70 - - поиска и замены 70 440
- - поиска маркера 71 - - поиска парной скобки 71 - - установки маркера 70 - режим 16 - служебная строка 67 рекурсия 166 - - - символьный 85 целый 81 BYTE 82 INTEGER 82 LONGINT 82 SHORTINT 83 С сегмент данных 132 сектор окружности 248 - эллипса 248 сопроцессор 89 среда Турбо-Паскаля 39 стек, значения его 62 - размер 56 стиль линии 227 - текста 253 - штриховки 242 страница активная 224 - видеопамяти 197 - видимая 242 WORD 82 - процедурный 161 - строковый 102 - структурированный 92 - - запись 94 - - массив 92 - - множество 100 - - файл 108 У указатель 133 - графический 214 - нетипизированный 134 - объявление 133 - типизированный 133 Т текст, вывод его 253 - выравнивание 255 - - константы 255 - высота 257 - длина 257 - направление 254 - стиль 253 тип 22, 80 - вложение 82, 93 - индексный 93 - объявление 83 - преобразование 104 - - автоматическое 105 - - неявное 105 - - функции 105 - - явное 105 - простой 105 - - вещественный 88 СОМР 88 - - - DOUBLE 88 EXTENDED 88 REAL 88 SIGNLE 88 - - порядковый 80 диапазон 86 логический 82 перечисляемый 83 уравнение дифференциальное 311 - линейное 293 - нелинейное 328 г устройство логическое 110 - AUX 111 - CON 1104 - COMI 111 - COM2 111 - LPT1 111 - LPT2 111 - NUL 111 - PRN 111 Ф файл 108 - GRAPH.TPU 12 - CGA.BGI 12 - TURBO.EXE 12 - TURBO.HLP 12 - TURBO.PCK 12, 46 - TURBO.TP 12, 57 - TURBO.TPL 12, 176 - атрибуты 116 - включаемый 59 - доступ 109 - имя 109 - - расширение 110 - - .ВАК 58 - - .BG1 213 441
- - .EXE 50, 59 - GETPIXEL 246 - - .MAP 56 - GETTEXTPAGE 338 - - .OBJ 60, 270 - GETX 220 - - .OVR 190 - GETY 220 - - .PAS 173 - GRAPHERRORMSG 216 - - .TPU 59, 173 - GRAPHRESULT 216 - инициация 112 - HEADING 263 - конфигурационный 12, 46, 57 - HI 83 - настроечный 12, 46, 57 - INT 89 - начальный 50 - IORESULT 112, 116 - нетипизированный 128 - KEYPRESSED 277 - путь к нему 110 - LENGTH 103 - список 46 - LN 89 - текстовый 120 - LO 83 - типизированный 127 - MAXAVAIL 143 функция 149 - MEMAVAIL 143 - ABS 83, 89 - ODD 83 - ADDR 388 - OFS 140, 144 - ARCTAN 89 - ORD 80 - CHR 86 - OVRGETBUF 192 - CONCAT 103 - PARAMCOUNT 186 - COPY 103 - PARAMSTR 186 - DISKFREE 116 - PI 400 - DISKSIZE 116 - POS 103 - DOSEXITCODE 185 - PRED 81 - DOSVERSION 390 - PTR 140, 149 - DSEG 390 - RANDOM 83, 89 - ENVCOUNT 188 - READKEY 278 ENVSTR 188 - ROUND 401 - EOF 115 - SEEKEOF 125 - EOLN 124 - SEEKEOLN 125 - EXP 89 - SEG 140, 144 - FEXPAND 120 - SEVAL 299 - FILEPOS 128 - SIMPSON 304 - FILESIZE 128 - SIN 89 - FMIN 332 - SIZEOF 404 - FRAC 392 - SPTR 405 - FSEARCH 119 - SQR 83, 89 - GETBKCOLOR 239 - SORT 89 - GETCOLOR 238 - SSEG 405 - GETDOTCOLOR 261 - SUCC 81 - GETDRIVENAME 219 - SWAP 405 - GETENV 181 - TEXTHEIGHT 257 - GETGRAPHMODE 217 - TEXTWIDTH 257 - GETMAXCOLOR 238 - TRUNC 405 - GETMAXMODE 219 - TURTLETHERE 262 - GETMAXX 220 - UPCASE 86, 104 - GETMAXY 220 - WHEREX 107 - GETMODENAME 219 - WHEREY 107 - GETPALETTESIZE 242 - XCOR 264 442
- YCOR 264 - ZEROIN 329 - преобразования типа 105 Ц цвет константы 203, 238 - пикселя 266 - преобразование 259 - символов 204 - текущий 204, 237 - фона 204, 238 цифра 72 - шестнадцатиричная 72 Ч черепаха 258 черепаховая графика 258 - константы 262 Ш шрифт 254 - включение в тело программы 270 - загрузка 267 - константы 254 - принтерный 282 - размер 254 - создание 267, 372 - стандартный 254 - штриховый 254 штриховка 242 Э экран см. видеотерминал экстремума поиск 332 эллипс 248 эхо-повтор 277 Я язык Паскаль И - Турбо-Паскаль 11
Внимание руководителей организаций! Головной отдел научно-технической информации Московского государственного технического университета им. Н.Э.Баумана (ГОНТИ МГТУ) предлагает научно-техническую информацию по библиографическим базам данных магнитно-ленточных служб Всесо- юзных центров НТИ (ВНТИЦ, ВИНИТИ) для ПЭВМ, совместимых с IBM PC. ГОНТИ МГТУ по требованию абонента переносит информацию с магнитных лент ЕС ЭВМ баз данных центров НТИ на ПЭВМ в форматах МЕКОФ, ISO 2709, Электронного журнала, Print-формата (возможны варианты) либо представляет копию базы данных на магнитной ленте. Базь) данных могут быть адаптированы к ППП CDS ISIS микро- версия для ПЭВМ или других ППП, используемых абонентом. ГОНТИ МГТУ готов взять на себя обязательство предоставлять тематические подборки по логическим запросам как от коллектив- ных, так и индивидуальных абонентов. Условия договора, оплаты, тематику можно узнать в ГОНТИ МГТУ по адресу: 107005, Москва, 2-я Бауманская ул., 5, комн. 394-а или по тел. 263-69-35
Издательство МГТУ ВЫЙДЕТ В СВЕТ В 1991 ГОДУ: Пешти Ю.В. Газовая смазка: Учебник. -М.: Изд-во МГТУ, 1991. - 25 л.: ил. - ISBN 5-7038-0327-6 (в пер.): 2 р. 50 к., 10000 экз. Рассматриваются актуальные вопросы, возникающие при созда- нии узлов с газовой смазкой для принципиально новых машин криогенных установок» систем кондиционирования, станков, турбо- молекулярных вакуум-насосов, газовых турбин, лазеров и других ус- тройств. Читатели знакомятся с новым принципом подхода к выбору и расчету виброустойчивых газовых подшипников: динамическая часть машин с газовыми подшипниками рассматривается как еди- ная динамическая система, состоящая из таких, например, элемен- тов как колеса турбомашин, уплотнения, подшипники, электриче- ские машины, демпферы. Для студентов и аспирантов соответствующих специальностей, а также научных работников и инженеров, работающих в области ма- шин с подшипниками на газовой смазке.
Издательство МГТУ ВЫЙДЕТ В СВЕТ В 1991 ГОДУ: Попов Б.Г. Расчет многослойных конструкций вариа- ционно-матричными методами: Учлюсобие. - М.: Изд-во МГТУ, 1991. - 20 л.: ил. - ISBN 5-7038-0330-6 (в обл.): 4 р„ 5000 экз. Изложены теоретические основы, численные методы и алгорит- мы расчета силовых многослойных конструкций из композитных материалов. Особое внимание уделено вариационно-матричным формулировкам задач и построению конечно-элементных моделей деформирования многослойных стержней, пластин и оболочек. Тео- ретический материал ' иллюстрируется конкретными примерами. Приводятся подпрограммы, написанные на языке Фортран-4, кото- рые могут использоваться для решения широкого круга задач стро- ительной механики конструкций из композитных материалов. Для студентов, аспирантов и преподавателей вузов, может быть полезной инженерам-расчетчикам в проектно-конструкторских и на- учно-исследовательских организациях.
Издательство МГТУ принимает заказы на рекламные объявления по машиностроительной и приборостроительной тематике Рекламная информация по согласованию с заказчиком будет помещаться на свободных полосах книг и журнала "Вестник Ml ГУ". С предложениями обращаться по адресу: 107005, Москва, 2-я Бауманская ул., д. 5, тел.: 2614597, 2654298 '
Валерий Васильевич Фаронов ПРОГРАММИРОВАНИЕ НА ПЕРСОНАЛЬНЫХ ЭВМ В СРЕДЕ ТУРБО-ПАСКАЛЬ Заведующая редакцией Н. Г.Ковалевская Редактор Ю.Н.Хлебинский Художественный редактор Л.М.Толмачева Технический редактор О.В.Рыбина Корректор В.Т.Карасева ИБ 006 Подписано в печать 28.09.90. Формат 60x90/16. Бумага офс.№2. Печать офсетная Усл.печ.л. 28,0. Уч.изд.л. 30,98. Тираж 25000 экз. Изд.№240. Заказ № 1033 Цена 5 р. Московская типография №6 Госкомпечати СССР 109088, Москва, Ж-88, Южнопортовая ул., 24. Издательство МГТУ. 107005, Москва, Б-5, 2-я Бауманская, 5.
Цена 5р.