Модуль CRT Модуль CRT содержит подпрограммы управления текстовым выводом на экран дисплея, звуковым генератором и чтения с клавиатуры. В режиме текстового вывода координаты экрана следующие: верхний левый угол <1,1>, нижний правый <WindMax>, причем горизонтальная координата возрастает слева направо, а вертикальная - сверху вниз. Если на экране активизировано окно, то все координаты определяются относительно границ окна, за исключением процедуры Window (здесь координаты всегда задаются относительно границ экрана). Для чтения с клавиатуры используются две функции: KeyPressed и ReadKey. Функция KeyPressed определяет факт нажатия на любую клавишу и не приостанавливает дальнейшее исполнение программы. Функция ReadKey читает расширенный код нажатой клавиши и ожидает действий пользователя. Управление звуковым генератором строится по схеме Sound - Delay - NoSound. Первая процедура включает генератор и генерирует звук нужного тона. Вторая - приостанавливает работу программы на заданное число миллисекунд реального времени. Третья - отключает звуковой генератор. КОНСТАНТЫ МОДУЛЯ CRT Константы цвета фона и символов: Black = 0; {черный} Blue = 1; {голубой} Green = 2; {зеленый} Cyan = 3; {бирюзовый} Red = 4; {красный} Magenta = 5; {малиновый} Brown = 6; {коричневый} LightGray = 7; {светло-серый} Константы цвета символов: DarkGray = 8; {темно-серый} LightBlue = 9; {светло-голубой} LightGreen = 10; {светло-зеленый} LightCyan = 11; {светло-бирюзовый} LightRed = 12; {светло-красный} LightMagenta = 13; {светло-малиновый} Yellow = 14; {желтый} White = 15; {белый} Blink = 128; {бит мерцания} ПЕРЕМЕННЫЕ CheckBreak: Boolean; { Реакция на Ctrl-Break } CheckEOF: Boolean; { Реакция на Ctrl-Z - end of file} DirectVideo: Boolean; { Разрешение/запрещение прямой работы с видеопамятью } CheckSnow: Boolean; { Реакция на "снег" монитора } LastMode: Word; { Хранение последнего текстового режима} TextAttr: Byte; { Текущий текстовый атрибут} WindMin: Word; { Координаты <X,Y> верхнего левого угла текущего окна } WindMax: Word; { Координаты <X,Y> нижнего правого угла текущего окна } ОСНОВНЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ МОДУЛЯ CRT procedure AssignCrt(var F: Text); связывает с файловой переменной устройство ввода/вывода CRT. function KeyPressed: Boolean; возвращает значение True, если на клавиатуре была нажата любая клавиша. function ReadKey: Char; читает символ с клавиатуры без эхо повтора и приостанавливает исполнение программы до нажатия на любую клавишу, кроме Shift, Ctrl, Alt, CapsLock, NumLock, ScrollLock. procedure TextMode(Mode: Integer); устанавливает нужный текстовый режим. procedure Window(X1,Y1,X2,Y2: Byte); открывает текстовое окно на экране с абсолютными координатами <X1,Y2>, <X2,Y2>. procedure GotoXY(X,Y: Byte); перемещает курсор в нужное место <X,Y> активного окна. function WhereX: Byte; возвращает горизонтальную координату X текущего положения курсора в активном окне. function WhereY: Byte; то же для вертикальной координаты Y. procedure ClrScr; очищает окно и помещает курсор в левый верхний угол <1,1>. procedure ClrEol; удаляет все символы от текущей позиции курсора до конца строки без перемещения курсора. procedure InsLine; вставляет пустую строку в позицию курсора. procedure DelLine; удаляет строку, на которой находится курсор, и перемещает все строки, расположенные ниже нее, на строку вверх. procedure TextColor(Color: Byte); устанавливает цвет символов. procedure TextBackground(Color: Byte); устанавливает цвет фона. procedure LowVideo; устанавливает низкую яркость символов. procedure HighVideo; устанавливает высокую яркость символов. procedure NormVideo; устанавливает нормальную яркость символов. procedure Delay(MS: Word); приостанавливает работу программы на указанное число миллисекунд MS. procedure Sound(Hz: Word); включает звуковой генератор с указанной звуковой частотой Hz. procedure NoSound; выключает звуковой генератор. 87 Введение В кратком изложении история языков программирования такова: изначально вычислительные машины программировались в машинном коде. Тоесть в их оперативную память напрямую вводили последовательность чисел, являющиеся кодами команд, которые процессор может выполнить. При этом программа составлялась с периодическим заглядыванием в таблицу кодов команд процессора и была отнюдь не наглядной. Затем появилась идея обозначить коды какими-то короткими, но осмысленными, и потому легко запоминаемыми словами – мнемониками, и создать программу, которая бы, руководствуясь таблицей команд, переводила последовательность мнемоник – мнемокод в последовательность машинных кодов. Такую программу называют ассемблером (assembler - сборочное устройство, транслятор, ассемблер). Программы стали гораздо нагляднее, но решение практических задач требовало написания очень длинных программ (например, файловый менеджер Volkov Commander имеет размер около 64000 байт). Тогда появились языки программирования высокого уровня. При их создании использовали то обстоятельство, что в программе часто встречаются участки одинакового кода, выполняющие какое либо одно действие: вывод строки, запись в файл, вычисление математической функции и т.д. В языках высокого уровня таким последовательностям кода присвоены имена, и программа составляется на условном языке, каждое, из слов которого заменяет десятки, ато и сотни команд процессора. Таким образом, программа становится еще нагляднее и короче. Существует множество условных языков высокого уровня, для каждого из них написано немало вариантов пограммы, переводящей условный код в последовательность машинных команд. Один из таких языков – Паскаль. Язык программирования Pascal был разработан в 1968-1971 гг. Никлаусом Виртом в Цюрихском Институте информатики (Швейцария), и назван вчесть Блеза Паскаля – выдающегося математика, философа и физика 17-го века. Первоначальная цель разработки языка диктовалась необходимостью создания инструмента "для обучения программированию как систематической дисциплине". Однако очень скоро обнаружилась чрезвычайная эффективность языка Pascal в самых разнообразных приложениях, от решения небольших задач численного характера до разработки сложных программных систем - компиляторов, баз данных, операционных систем и т.п. К настоящему времени Pascal принадлежит к группе наиболее распространенных и популярных в мире языков программирования: существуют многочисленные реализации языка практически для всех машинных архитектур; разработаны десятки диалектов и проблемно-ориентированных расширений языка Pascal; обучение программированию и научно-технические публикации в значительной степени базируются на этом языке. Характеристика и особенности языка Существует ряд объективных причин, обусловивших выдающийся успех языка Pascal. Среди них в первую очередь необходимо указать следующие: Язык в естественной и элегантной форме отразил важнейшие современные концепции технологии разработки программ: развитая система типов, ориентация на принципы структурного программирования, поддержка процесса пошаговой разработки. Благодаря своей компактности, концептуальной целостности и ортогональности понятий, а также удачному первоначальному описанию, предложенному автором языка, Pascal оказался весьма легок для изучения и освоения. В противоположность громоздким многотомным описаниям таких языков, как PL/l, Cobol, FORTRAN, достаточно полное описание языка Pascal занимает около 30 страниц текста, а его синтаксические правила можно разместить на одной странице. Несмотря на относительную простоту языка, он оказался пригоден для весьма широкого спектра приложений, в том числе для разработки очень больших и сложных программ, например, операционных систем. Pascal весьма технологичен для реализации практически для всех, в том числе и нетрадиционных, машинных архитектур. Утверждается, что разработка Pascal-транслятора "почти не превышает по трудоемкости хорошую дипломную работу выпускника вуза". Благодаря этому для многих ЭВМ существует несколько различных реализаций языка, отражающих те или иные практические потребности программистов. Язык Pascal стандартизован во многих странах. В 1983 году был принят международный стандарт (ISO 7185:1983) Основные особенности языка Pascal. Pascal является традиционным алгоритмическим языком программирования, продолжающим линию Algol-60. Это означает, что программа на языке Pascal представляет собой специально организованную последовательность шагов по преобразованию данных, приводящую к решению некоторой задачи. Это отличает Pascal от так называемых непроцедурных языков типа Prolog, по существу, представляющих собой формализмы для записи начальных условий некоторой задачи и синтезирующих решение посредством встроенных механизмов логического вывода. Язык Pascal содержит удобные средства для представления данных. Развитая система типов позволяет адекватно описывать данные, подлежащие обработке, и конструировать структуры данных произвольной сложности. Pascal является типизированным языком, что означает фиксацию типов переменных при их описании, а также строгий контроль преобразований типов и контроль доступа к данным в соответствии с их типом (как на этапе компиляции, так и при исполнении программ). Набор операторов языка Pascal отражает принципы структурного программирования и позволяет записывать достаточно сложные алгоритмы в компактной и элегантной форме. Pascal является процедурным языком с традиционной блочной структурой и статически определенными областями действия имен. Процедурный механизм сочетает в себе простоту реализации и использования и гибкие средства параметризации. Синтаксис языка достаточно несложен. Программы записываются в свободном формате, что позволяет сделать их наглядными и удобными для изучения. Паскаль – компилятор, тоесть, прежде чем начать испоолнение программы, Паскаль полностью прочитывает исходный текст, написанный программистом, и составляет последовательность машинных кодов, выполняющую те действия, которые описал программист в hqundmnl тексте. Эта последовательность сохраняется в файл с расширением “.EXE” и является самостоятельным исполняемым файлом, который может быть запущен сам по себе, уже без участия Паскаля и, даже, на другом компъютере, на котором Паскаль может быть не установлен. TURBO PASCAL Прошло много времени с момента появления Паскаля на рынке программных продуктов, прежде чем он получил всеобщее признание. Признание программистов и простых пользователей пришло вследствие появления языка программирования Турбо Паскаль (ТП) -диалекта языка, созданного американской фирмой Борланд. Эта фирма объединила очень быстрый компилятор с редактором текста и добавила к стандартному Паскалю мощное расширение, что способствовало успеху первой версии этого языка. В 1985 году на рынке ПЭВМ появился язык программирования Турбо Паскаль (версия 3.0) с компилятором стандартного Паскаля. С тех пор Паскаль стал применяться в общеобразовательных, профессионально-технических школах и в сфере высшего образования в качестве "первого" языка программирования. Благодаря простоте использования язык Турбо Паскаль получил широкое распространение и в любительских кругах. Повышению популярности Турбо Паскаля способствовал набор небольших сопутствующих программ (т.н. Tools), позволяющих получать чрезвычайно компактную, быструю и легко читаемую программу. Эти качества Турбо Паскаля были высоко оценены и в среде профессиональных программистов. Встроенный редактор текста использует достаточно широко распространенную систему команд, берущую начало от пакета WordStar и хорошо знакомую каждому, кто интенсивно использует ПЭВМ. В появившемся со временем пакете Турбо Паскаль 4.0 было устранено большинство подвергавшихся критике ограничений компилятора и была повышена производительность системы. Кроме того, новый компилятор версии 4.0 имел существенные отличия от предыдущей версии. Наиболее важным нововведением была UNIT- концепция, заимствованная из языка Модула-2. Это дало возможность реализовать в рамках ТП разработку крупных программных продуктов. С выходом в свет версии 5.0 ТП получил еще большие шансы на благосклонную реакцию со стороны профессиональных пользователей благодаря встроенному в среду программирования интегрированному отладчику, который позволил повысить производительность труда. Существенно улучшила технические характеристики ТП реализация аппарата перекрытий (overlays), позволяющего строить мощные программные комплексы, рассчитанные на эксплуатацию в малых по объему областях памяти. Суть механизма перекрытий сводится к делению программы на части, поочередно загружаемые по мере необходимости с дискеты или магнитного диска (винчестера) в одну и ту же область памяти, заменяя при этом находившуюся там часть программы. Кроме того, в ТП 5.0 были расширены возможности отладки (debugging) программ и обеспечена возможность поддержки расширенной помята в стандарте Lotus-Intel-Microsoft (LIMS/EMS 4.0). Сокращение EMS обозначает Expanded Memory Specification (спецификация расширенной памяти). Нельзя путать этот вид дополнительной памяти с другим - Extended Memory (сокращенно - XMS). EMS имеется на обычных ПЭВМ класса ХТ, в то время как Extended Memory -только на машинах АТ-класса (с процессором 286, 386 и выше) при объеме памяти свыше 1 Мбайта. В этой версии были также исправлены и улучшены библиотеки графических процедур, поставляемые вместе с пакетом ТП. При этом обеспечивалась полная совместимость с графическими адаптерами класса VGA (Video Graphics Array). В рамках версии ТП 5.5 были осуществлены дальнейшие преобразования в направлении улучшения технических характеристик пакета. Наряду с внутренними улучшениями и новыми возможностями встроенной справочной системы Help и большим набором учебных примеров, важным нововведением явилась реализация в языке концепции объектно-ориентированного программирования (ООП). Через некоторое время на рынке появилась версия 6.0 ТП, в которой чисто теоретическая концепция объектно-ориентированного программирования была реализована практически с полным набором объектов, которые могли использоваться для решения прикладных задач пользователя. Кроме того, реализация системы меню приведена b соответствие со стандартом SAA (Turbo Vision). В качестве практического примера использования новых возможностей был реализован текстовый редактор, встроенный в IDE - Integrated Development Environment - интегрированную инструментальную оболочку. При этом сторонники программирования на ТП 6.0 получили возможность не только работать со встроенным многооконным текстовым редактором, но и использовать мышь, которая значительно облегчает работу пользователя. В 1992 году фирма Borland International представила пользователям очередную версию языка программирования Паскаль - Турбо Паскаль 7.0. Наряду со всеми преимуществами, которые ТП 7.0 унаследовал от предыдущей версии ТП (многооконный режим работы, возможность использования мыши, возможность использования при написании программ языка программирования низкого уровня Ассемблер или прямого ввода машинного кода, возможность создавать объектно- ориентированные программы), в нем были произведены изменения и улучшения: Появилась возможность выделять определенным цветом различные элементы исходного текста (зарезервированные слова, идентификаторы, числа и т.д.), позволяющая даже неопытным пользователям устранять ошибки на этапе ввода исходного текста. Язык программирования ТП 7.0 был расширен (появилась возможность использовать типизированный адресный оператор, открытые массивы и строки и т.д.), что предоставило пользователю дополнительные возможности при решении повседневных задач. Был улучшен компилятор, вследствие чего "коды программ" стали более эффективными. Был улучшен интерфейс пользователя. Кроме того, в ТП 7.0 расширены возможности объектно-ориентированного программирования (в частности, расширены и улучшены возможности Turbo Vision). Основы языка Pascal. Лингвистическая концепция языка Паскаль Целью работы И. Вирта было создание языка, который строился бы на небольшом количестве базовых понятий, имел бы простой синтаксис, допускал бы перевод программ в машинный код простым компилятором. По природе своей компьютер может выполнять только простейшие операции, которые можно вводить одну за другой в его память прямо в машинных кодах. Изнурительная монотонность такой работы привела когда-то первых программистов к естественному решению - созданию Ассемблеров, т.е. средств, упрощающих подготовку машинных кодов программ пользователя за счет написания их в некоторых мнемонических обозначениях с последующим автоматическим переводом. Дальнейшее развитие этих идей привело к созданию языков программирования высокого уровня, в которых длинные и сложные, но часто применяемые последовательности машинных операций были заменены каждая одним-единственным обозначающими ее словом - оператором. В области малых ЭВМ среди языков программирования высокого уровня следует в первую очередь назвать БЕЙСИК. Программы, написанные на этом языке, к сожалению, часто содержат запутанные последовательности операторов, называемые иногда на жаргоне программистов блюдо спагетти. Лингвистическая концепция Паскаля отрицает методы программирования, которые могут привести к подобным эффектам. В mei, напротив, пропагандируется системный подход, выражающийся, в частности, в расчленении крупных проблем на меньшие по сложности и размеру задачи, легче поддающиеся решению. Основные принципы Паскаля таковы: Структурное программирование. Суть его заключается в оформлении последовательностей команд как замкнутых функций или процедур и в объединении данных, связанных по смыслу, в сложные структуры данных. Благодаря этому повышается наглядность текста и упрощается его отладка. Проектирование сверху вниз. Программист разбивает свою задачу на несколько более простых, после чего каждая из задач решается по отдельности. Затем компонуются результаты проектирования простых задач и решается задача проектирования сверху вниз в целом. Объектно-ориентированное программирование делает следующий шаг от ремесла к науке программирования. Данные объединяются со свойственными им операциями обработки в некоторые объекты (Инкапсулирование). Например, данным "Человек" присуща операция "Идти". При этом свойства одних объектов могут передаваться другим, переноситься на другие классы объектов (Наследование). С другой стороны, в объектно-ориентированном программировании существует явление полиморфизма: часы тоже могут "Идти", но не ногами. Влияние Паскаля ощущается в настоящее время в разных языках программирования. Так, среди новых диалектов Бейсика есть Паскаль с символикой Бейсика. Даже в язык С встраивается все больше элементов, порожденных Паскаль-концепцией. Необходимо отметить, что все эти явления находятся в русле характерной для современных языков программирования тенденции к конвергенции. Набор операторов стандартного Паскаля относительно мал и легко изучаем. Но это порождает проблему расширения языка в приложениях. В ТП эта проблема решается за счет поставок большого количества библиотек разнообразных процедур, готовых к употреблению в прикладных программах. Широкое распространение Паскаля привело к появлению на рынке программного обеспечения большого числа инструментальных и прикладных программ. Подобные программы разработаны для многих проблемных областей, однако задача их настройки в соответствии с требованиями пользователей продолжает оставаться достаточно важной. Алфавит языка и специфика использования символов Язык программирования ТП 7.0, как и любой другой, имеет свой алфавит. Как правило, алфавитом языка программирования называют набор символов, разрешенный к использованию и воспринимаемый компилятором, с помощью которого могут быть образованы величины, выражения и операторы данного языка. Алфавит языка ТП 7.0 включает в себя все символы, представленные в кодировочной таблице, которая в настоящий момент загружена в оперативную память или хранится в ПЗУ Вашего компьютера. Каждому символу алфавита соответствует индивидуальный числовой код от 0 до 255. Примечание: Символы с кодами от 0 до 127 представляют собой так называемую основную таблицу кодов ASCII. Их состав и порядок определены американским стандартом на коды обмена информацией. Часть кодировочной таблицы с кодами от 0 до 127, т.е. "Основная таблица кодов ASCII, идентична на всех IBM-совместимых компьютерах. Алфавит языка ТП 7.0 составляют: Символы, используемые для составления идентификаторов: латинские строчные и прописные буквы, арабские цифры от 0 до 9 (в идентификаторах цифры могут использоваться наряду с буквами, начиная со второй позиции), символ подчеркивания (ASCII, код 95). Символы-разделители: Символ пробела (ASCII, код 32). Как уже отмечалось, символ пробела является разделителем в языке ТП 7.0. Основное назначение этого символа - разделение ключевых слов и имен. Управляющие символы (имеют ASCII-коды от 0 до 31). Эти символы могут применяться при описании строчных и символьных констант. Управляющие символы с ASCII-кодом 9 (табуляция), а также 10 и 13 (замыкающее строку) используются в качестве разделителей при написании программ на ТП 7.0. В любом месте программы, где можно расположить один символ- разделитель, их можно разместить сколько угодно, т.е. для компилятора следующие записи будут эквивалентны: A:=B+C-D;Write(A); А := В + С - D; Write (A); A:= В+С - D ; Write (А); Специальные символы - символы, выполняющие определенные функции при построении различных конструкций языка: + - * / { } [ ] ( ) < > . , ' : ; ^ @ # $ Составные символы - группа символов, которые воспринимаются компилятором как единое целое: <= => := (* *) (. .) .. "Неиспользуемые" символы. Символы так называемой расширенной таблицы ASCII, т.е. символы, имеющие коды от 128 до 255 (в этой области находятся символы алфавита русского языка и символы псевдографики на IBM-совместимых компьютерах), а также некоторые символы из основной таблицы ASCII (например: (&), (!), (%), (-), (") и некоторые другие) не входят в алфавит языка, т.е. эти символы не используются в идентификаторах. Тем не менее, их можно использовать в тексте комментариев, а также в виде значений констант строк или констант символов. Зарезервированные слова. ТП 7.0, как и его предшественники, включает в себя так называемые ключевые или зарезервированные слова (BEGIN, END, PROGRAM). В качестве имен идентификаторов в программе зарезервированные слова использоваться не могут. Если Вы будете использовать зарезервированные слова не по назначению, это вызовет ошибку при попытке откомпилировать программу. Понятие лексемы и разделителя Программа на Паскале состоит из последовательности лексических единиц - лексем. Лексема - минимальная единица языка, имеющая самостоятельный смысл. В Паскале лексемы условно делятся на несколько классов: идентификаторы; числа без знака; специальные знаки (слова-символы и специальные знаки); символьные константы (строки), директивы; метки. Блок лексического анализа Паскаль-компилятора, рассматривая символы входного языка, должен определить, какому классу принадлежит лексема. Между лексемами разрешено вставлять один и более разделителей. В качестве разделителей в стандарте Паскаля используются пробелы, комментарии, символы «конец строки». В Турбо-Паскале кроме этих в качестве разделителей разрешено использование и других символов. Комментарии заключают в фигурные скобки. Вместо них также могут использоваться пары символов «b>(**) Примеры записи лексем:
12–А 12 - A+5 В первом примере записаны три лексемы “12”, “-“, “А”, между которыми нет разделителей. Во втором примере между первой и второй лексемами, а также между второй и третьей использован разделитель пробел.
А{присвоить}:=1;
Между лексемами “А” и “:=” в качестве разделителя использован комментарий.
А:{присвоить}=1 Последний пример ошибочен, так как разделитель в виде комментария разрывает лексему ":=”. Идентификаторы Идентификатор является именем, которое использует программист при обращении к какому-то значению. В качестве имен не могут быть использованы зарезервированные слова (слова-символы, например слово “PROGRAM”). В стандартном Паскале идентификаторы используются для обозначения переменных, констант, типов, процедур и функций. Имена могут быть длинными, но при трансляции рассматривается ограниченное число символов (по стандарту Паскаля - первые восемь символов, тоесть идентификаторы “dlinniy_identifikator1” и “dlinniy_identifikator2” будут восприняты компилятором как одно и тоже слово). В Турбо-Паскале идентификатор кроме символов букв и цифр может содержать символ “_” (подчеркивание). Подчеркивание полезно, когда имя состоит из нескольких осмысленных слов. В общем случае следом за зарезервированным словом PROGRAM в программе стоит пробел, разделяющий в Паскале слова, и далее - имя, данное программе. Это имя представляет собой пример идентификатора. Идентификатор -имя, свободно избираемое программистом для элементов программы (процедур, функций, констант, переменных и типов данных). При обозначении какого-либо элемента программы с помощью идентификатора Вы должны руководствоваться следующими постулатами: Идентификатор должен начинаться буквой или символом подчеркивания “_”. ТП 7.0 не различает прописные и строчные буквы. Поэтому можно записать WriteLN, Writeln или даже wRITeLn, не опасаясь быть непонятым компилятором. Для него все три записи эквивалентны. Начиная со второй позиции, в идентификаторе можно применять наряду с буквами цифры Пробел в ТП 7.0 является разделителем и не может стоять внутри идентификатора. Для создания идентификаторов, состоящих из двух слов, можно воспользоваться большими буквами (например, ReadText) или символом подчеркивания (Read_Text), но не пробелом (bRead Text – два отдельных слова). Применение других символов (букв неанглийского алфавита, знаков препинания, псевдографических символов и т.п.) в идентификаторах не допускается. Зарезервированные слова (такие как BEGIN, END или PROGRAM) в качестве идентификаторов не используются. Идентификаторы могут быть любой длины, но сравнение их между собой производится по первым 63 символам. Кроме того, при написании программы рекомендуется следовать ряду правил хорошего тона, которые упрощают редактирование программы и повышают ее наглядность: Изобретая идентификаторы, старайтесь делать их "осмысленными", не экономьте на именах - имя ReadTest всегда лучше, чем RT. Не бойтесь потратить время на написание длинных идентификаторов. Встроенный в ИПО (Интегрированная Пользовательская Оболочка) редактор предоставляет возможность копировать и переносить фрагменты текста. Все структуры языка имеют англоязычные идентификаторы. Для своих элементов Вы можете изобрести русскоязычные идентификаторы (записанные латинскими литерами), например PROGRAM Privetstvie. Но для удобства, создавая идентификаторы, выполняйте не транслитерацию русских слов в английские, а перевод на английский язык (приветствие - welcome). В нашей учебной программе слово PROGRAM и последующий идентификатор представляют собой незаконченный оператор программы. Для правильного оформления его необходимо завершить символом точка с запятой (;). Таким образов завершается каждый оператор программы на языке Паскаль, за исключением последнего оператора END, после которого всегда ставится точка, тем самым информируя компилятор об окончании текста программы. Структура программы Общая структура программ в ТП 7.0 Программы, написанные на языке программирования ТП 7.0, строятс в соответствии с правилами, представляющими собой несколько расширенные и "ослабленные" правила синтаксиса стандартного Паскаля. Но эти "ослабленные" правила (т.е. порядок размещения в тексте программы различных смысловых блоков) должны неукоснительно соблюдаться при написании программы. Любую программу, написанную на ТП 7.0, можно условно разделить на три основные части: раздел объявлений и соглашений (декларационная часть), раздел текстов процедур и функций, раздел основного блока (сама программа). Раздел объявлений и соглашений.
PROGRAM Заголовок программы; {$ ... } Глобальные директивы компилятора; USES Подключаемые библиотеки; LABEL Подраздел объявления глобальных меток; CONST Подраздел объявления глобальных констант; TYPE Подраздел объявления глобальных типов; VAR Подраздел объявления глобальных переменных; В первой части программы программист сообщает компилятору, какими идентификаторами он обозначает данные (константы и переменные), а также определяет собственные типы данных, которые он в дальнейшем намеревается использовать в данной программе. Например, можно объявить переменные как локальные, допустив тем самым создание объектов с одинаковыми идентификаторами внутри функций и процедур. При этом необходимо следить за тем, чтобы не возникали конфликты между локальными и глобальными объявлениями различных объектов. Раздел текстов процедур и функций В этом разделе записываются подпрограммы, осуществляющие сложные действия, которые необходимо произвести неоднократно на разных этапах выполнения программы. Подпограммы бывают двух типов: прjцедуры (PROCEDURE) и функции (FUNCTION). И те и другие пребставляют собой программы в миниатюре:
PROCEDURE (FUNCTION) Заголовок процедуры (функции); LABEL Подраздел объявления локальных меток; CONST Подраздел объявления локальных констант; TYPE Подраздел объявления локальных типов; VAR Подраздел объявления локальных переменных; Раздел текстов подпрограмм. BEGIN Основной блок процедуры или функции; END; Они могут иметь всетеже разделы, что и основная программа, в частности, раздел локальных процедур и функций, вызываемых только в педелах данной подпрограммы. Раздел основного блока программы
BEGIN {Основной блок программы} {текст программы} END. В этом разделе содержится смысловая часть программы. Заголовок программы Со строкой заголовка Вы уже знакомы. Она состоит из зарезервированного слова PROGRAM и имени программы. В Турбо Паскале эта строка не обязательна, и ее можно без ущерба исключить. Но правила хорошего тона в программировании требуют задания некоторого имени программы, чтобы уже при первом знакомстве можно было получить хоть какую-нибудь информацию об ее назначении. Однако не стремитесь привести здесь всю известную Вам информацию о программе - для этих целей можно воспользоваться дополнительными комментариями. Обычно в заголовке достаточно указать имя и версию программы. Следующее за оператором PROGRAM имя является идентификатором и обладает всеми его свойствами. В частности, внутри тела программы не могут быть объявлены объекты, имя которых совпадает с именем программы. Раздел объявлений и соглашений Глобальные директивы компилятора В этом разделе программы компилятору можно дать указания, определяющие режимы его работы при трансляции последующей программы. Эти указания оформляются в тексте программы как комментарии, начинающиеся парой символов “{$” и заканчивающиеся символом “}”. Такие указания могут содержать указания на включение в текст программы фрагментов других программ (из соответствующих файлов), информацию для отладчика или сведения о необходимости использования арифметического сопроцессора. Оператор USES Оператор USES играет важную роль в подключении к тексту программы системных модулей из библиотек. В этом операторе Вы указываете компилятору, из какой библиотеки использует модули данная программа, чтобы компилятор выбрал соответствующие модули из этой библиотеки и включил их в текст программы. Понятия "библиотека", "модуль", "блок" составляют основу терминологии программирования на Паскале. Библиотека включает набор модулей, каждый из которых замкнут, имеет свое имя, компилируется отдельно и к нашей программе подключается уже как "черный ящик" с известным интерфейсом. Каждый модуль (блок (UNIT), как его называют на Паскале) представляет собой программу, включающую декларации типов и переменных, процедуры и функции. Оператор USES может быть использован в программе только один раз, при этом у него есть свое четко определенное место, (он предваряет все прочие операторы и разделы). Названия библиотек, подключаемых к программе с помощью оператора USES, должны разделяться запятой. Например: USES Crt, Graph, String, Overlay; Объявления глобальных меток, констант, типов и переменных Следом за строкой, содержащей оператор USES, идут строки объявляющие: метки (LABEL) (хотя их использование противоречит классической технике программирования на Паскале, дающей превосходную стройность и однозначность понимания кода программы); константы (CONST); определенные пользователем типы данных (TYPE); переменные (VAR). В Турбо Паскале жесткое соблюдение именно такого порядка объявлений не требуется. В этом отношении данный диалект весьма "либерален". На практике в большинстве программ часть, заключающая в себе объявления глобальных объектов, непосредственно предшествует основному блоку программы. В разделе описания меток LABEL содержатся перечисленные через запятую имена меток переходов. Следует помнить, что имена меток переходов не должны дублировать друг друга. Имя метки перехода может представлять собой целое число (от 0 до 9999), строку символов или символьно-цифровую конструкцию. Например: LABEL, 1, 5, 9999, h2, 4t32e, metka_l; В разделе CONST содержатся определения констант, используемых в программе. Например:
CONST Year=1995; Month='Июль'; Day='Понедельник'; Примечание: Заметьте, что при присвоении значений константам вместо оператора присвоения “:=” используется просто знак равенства “=”. Тип константы определяется автоматически по виду значения, присваемового константе и не может быть сложным. Раздел описания типов TYPE позволяет программисту определить новый тип в программе. В данном разделе могут быть использованы ранее определенные в разделе CONST константы. В разделе описания глобальных переменных VAR содержится список переменных, используемых в программе, и определяется их тип. Например:
VAR А, В, С: INTEGER; {Переменным А, В и С присваивается тип INTEGER} DDT: REAL; {Переменной DDT присваивается тип REAL } Примечание: Разделы LABEL, CONST, TYPE и VAR могут располагаться в произвольном месте программы. При этом каждый из этих разделов может встречаться в программе несколько раз или вообще не встречаться в ней. Основной блок программы Непосредственно за заголовком программы следует основной блок программы, ограниченный операторами BEGIN и END.. Как уже говорилось, оператор END. указывает компилятору, что программа закончена, в отличие от операторов END;, которые завершают блоки, процедуры, модули и т.п. Текст, следующий за оператором END., игнорируется транслятором. Объем основного блока программы, написанной нами, невелик:
writeLn (' привет. Ваня !' ); writeLn; Wlitelin(' Я надеюсь, что мы отлично'); WrlteLn (' сработаемся !'); Оператор WriteLn (Write Line - записать строку) - представляет собой стандартную процедуру, с помощью которой можно вывести на экран или на другие носители и средства отображения информации текст и числа. Подробное истолкование применения этой процедуры в первом операторе программы может быть, например, таким: "Вывести (Write) строку символов (String) "Привет, Ваня!" на экран и перевести курсор на начало следующей строки (Line) экрана". Перевод курсора на начало следующей строки вызван окончанием Ln в имени процедуры. Существует также стандартная процедура Write для вывода информации на экран без перевода курсора. После выполнения оператора WriteLn последующий вывод приведет к выдаче информации в следующую строку экрана, а после оператора Write - в ту же строку, следом за уже выведенным текстом, пока хватит места, а затем вывод продолжится уже на следующей строке экрана. Оператор WriteLn; без параметров просто переведет курсор на начало следующей строки. Операторы WriteLn и Write присутствуют практически в каждой Паскаль-программе. Скобки, следующие за оператором, необходимы для задания параметров процедуры. Причем, компилятор не рассматривает слова "Привет" и "Ваня" как идентификаторы для каких-либо переменных, т.к. они стоят внутри фрагмента текста, ограниченного апострофами. Заканчивается этот оператор, как и любой другой, точкой с запятой. Еще одна характерная особенность Паскаль-программ - ступенчатая форма записи. Строки, относящиеся к одной конструкции или связанные по смыслу, записываются с одной и той же позиции. Строки, относящиеся к подчиненной конструкции, записываются правее ("глубже"), например, на две позиции, благодаря чему наглядно представляется структура программы. В длинных программах этот подход позволяет фиксировать соответствие пар операторов BEGIN - END. Редактор текста в ИПО поддерживает такую технологию оформления текстов программ: после нажатия клавиши [Enter] курсор переходит на следующую строку в ту позицию, с которой начинается текст в предыдущей, а клавиша [Backsрасе] из этого положения переводит курсор на конец предыдущей строки. Примечание: Ступенчатое оформление программы преследует только "эстетические" цели и не влияет на эффективность работы компилятора или программы. Компилятор обрабатывает Паскаль- программы с любым расположением операторов: как разделенные построчно нажатием клавиши [Enter] при подготовке текста программы, так и записанные подряд в одну строку. Например:
program hello; begin writeln('Привет, Ваня!'); writeln; writeln('Я надеюсь, что мы отлично'); writeln (' сработаемся!'); end. Однако "воспитанные" программисты не могут себе позволить оформлять программы подобным образом, ведь таже программа, записанная с табуляцией, читается гараздо легче:
program hello; {заголовок программы} begin {начало сегмента команд программы} writeln('Привет, Ваня!'); {вывод строки} writeln; {перевод позиции курсора на строку ниже} writeln('Я надеюсь, что мы отлично'); {вывод строки} writeln('сработаемся!'); {вывод строки} end. {конец программы} Вернемся еще раз к символу точка с запятой, завершающему каждый оператор. Наличие точки с запятой обязательно, т.к. этот символ показывает Паскаль-компилятору, где заканчивается один оператор и начинается следующий. Благодаря тому, что Паскаль - язык со "свободной формой записи", можно без опасений "растянуть" оператор на несколько строк. С помощью символа точка с запятой Вы сообщаете Паскаль-компилятору, какую часть текста программы следует рассматривать как цельный, неделимый фрагмент. Зарезервированное слово BEGIN, с которого начинаются блоки программы, не требует после себя символа точка с запятой. Типы переменных. Тип переменной задает вид того значения, которое ей присваивается и правила, по которым операторы языка действуют с переменной, например: Если переменные A и B целочисленного типа, то программа:
A:=3.14; B:=2.71; WRITELN(A,’ ‘,B,’ ‘,A+B); Выведет на экран строку: “3.14 2.71 5.85” Если же они строкового типа, то программа:
A:=’3.14’; B:=’2.71’; WRITELN(A,’ ‘,B,’ ‘,A+B); Выведет: “3.14 2.71 3.142.71”, так как оператор сложения просто добавит строку B в конец строки A. В Паскале предопределены следующие простейшие типы переменных: Тип константы определяется способом записи ее значения:
Const C1=17; C2=3.14; C3='A'; C4=False; C5=C2+C1; Можно использовать выражения. Выражения должны в качестве операторов содержать только константы, в том числе ранее объявленные, а так же знаки математических операций, скобки и стандартные функции. BYTE – целое число от 0 до 255, занимает одну ячейку памяти (байт). BOOLEAN – логическое значение (байт, заполненный единицами, или нулями), true, или false. WORD – целое число от 0 до 65535, занимает два байта. INTEGER – целое число от –32768 до 32767, занимает два байта. LONGINT – целое число от –2147483648 до 2147483647, занимает четыре байта. REAL – число с дробной частью от 2.9*10-39.до 1.7*1038, может принимать и отрицательные значения, на экран выводится с точностью до 12-го знака после запятой, если результат какой либо операции с REAL меньше, чем 2.9*10-39, он трактуется как ноль. Переменная типа REAL занимает шесть байт. DOUBLE - число с дробной частью от 5.0*10-324.до.1.7*10308, может принимать и отрицательные значения, на экран выводится с точностью до 16-го знака после запятой ,если результат какой либо операции с DOUBLE меньше, чем 5.0*10-324, он трактуется как ноль. Переменная типа DOUBLE занимает восемь байт. CHAR – символ, буква, при отображении на экран выводится тот символ, код которого хранится в выводимой переменной типа CHAR, переменная занимает один байт. STRING – строка символов, на экран выводится как строка символов, коды которых хранятся в последовательности байт, занимаемой выводимой переменной типа STRING; в памяти занимает от 1 до 256 байт – по количеству символов в строке, плюс один байт, в котором хранится длина самой строки. При обьявлении переменной строкового типа можно заранее указать ее длину в байтах – X: MyString:STRING[X]; При присвоении этой переменной строки длиннее X, присваиваемая строка будет обрезана с конца после X-того символа. Размер переменной типа STRING в памяти можно узнать следующим способом: Size:=SizeOf(MyString); Функция SizeOf() возвращает размер, занимаемый переменной, служащей параметром. Параметром может служить и тип переменной; строка: Writeln(SizeOf(STRING)); Выведет на экран число 256, так как по умолчанию под все строки отводится по 256 байт. Кроме того, можно узнать, сколько символов в строке (индекс последнего непустого символа в строке): Size:=Ord(MyString[0]); Используется ибращение к нулевому элементу (символу) строки, в котором хранится ее длина, но MyString[0] – значение типа CHAR, тоесть символ, код которого равен длине строки, нужный нам код – число возвращает функция Ord()Таким же образом можно обратиться к любому N – тому элементу строки:
MyChar:=MyString[N]; {MyChar:CHAR} ARRAY[a..b,c..d,….] OF “тип элемента”; - массив некоторой размерности, содержащий элементы указанного типа. Диапазоны индексов для каждого измерения указываются парами чисел или констант, разделенных двумя точками, через запятую (a..b,c..d). После OF записывается тип элементов массива. В памяти массив занимает место, равное: (b-a)*(d-c)*..* SizeOf(“тип элемента”). Размер массива не может превосходить 65536 байт. Обращение к элементам массива происходит следующим образом: X:=MyArray[a,b,c,..]; При этом переменная X должна быть того же типа, что и элементы массива или приводимого типа. Число индексов (a,b,c,..) должно быть равно числу обьявленных при описании измерений массива. О приводимости типов. В паскале существуют ограничения на присвоение значений одних переменных другим. Если переменные которую и которой присваивают одного типа, то никаких проблем не возникнет. Но если они разных типов, присвоение не всегда может быть произведено. Это связано стем, что при таком присвоении необходимо отсечь часть информации, а какую – компьютер “не знает”. Проблема возникает при следующих присвоениях:
I:=J; {I:INTEGER; J:REAL} A:=B; {A:CHAR; B:STRING} В то же время, такие присвоения будут выполнены вполне корректно:
J:=I; B:=A; При этом переменная J примет значение с нулевой дробной частью, а B – станет строкой, содержащей один символ – из A. В первом же случае, можно поизвести следующие операции:
I:=Trunc(J); {функция trunc() возвращает целую часть аргумента} I:=Round(J); {round() – округляет аргумент стандартным способом} Кроме рассмотренного случая может существовать множество других, все не описать, но наиболее общее правило таково: следить за однозначностью присвоения с потерями информации и не удивляться, а экспериментировать переделывать программу, если компилятор выдает сообщение о невозможности присвоения. Операторы языка Pascal Составной и пустой операторы. Составной оператор - это последовательность произвольных операторов программы, заключенная в операторные скобки. Турбо-Паскаль допускает произвольную глубину вложенности:
Begin ... Begin ... Begin ... End; End; End; Наличие ; перед End - пустой оператор. Операторы ветвлений. Условный оператор. IF <условие> THEN <оператор1> [ELSE <оператор2>] Условие – значение типа BOOLEAN или логическая операция. Если условие верно, выполняется оператор, или блок операторов, следующий за THEN, в противном случае выполняется блок операторов после ELSE, если он есть. Условия могут быть вложенными и в таком случае, любая встретившаяся часть ELSE соответствует ближайшей к ней "сверху" части THEN. Пример:
Var A, B, C, D: Integer; begin A:=1; B:=2; C:=3; D:=4; If A > B Then If C < D Then If C < 0 Then C:=0 {обратите внимание, что перед Else пустой оператор ";"не ставится} Else A:=B; end. а могло быть и так:
If A > B Then If C < D Then If C < 0 Then C:=0 Else Else Else A:=B Рассмотрим программу, которая вводит произвольное целое число от 0 до 15 и выводит его в шестнадцатеричной системе:
Program Hex; 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); End Else Writeln('Ошибка'); End. Операторы повторений. Цикл с предопределенным числом повторений. For <переменная цикла>:=<начальное значение> To(DownTo) <конечное значение> Do <блок операторов> Переменная должна быть целого или перечислимого типа. При исполнении цикла переменная цикла изменяется от начального до конечного значения с шагом 1. Если стоит to, то переменная увеличивается, если downto – уменьшается. Условия выполнения цикла проверяются перед выполнением блока операторов. Если условие не выполнено, цикл For не выполняется. Следующая программа подсчитывает сумму чисел от 1 до введенного:
Program Summa; Var I, N, S: Integer; Begin Write('N = '); Readln(N); S:=0; For I:=1 To N Do S:=S + I; Writeln ('Cумма = ', S) End. Условный цикл с проверкой условия перед исполнением блока операторов. While <условие> Do <блок операторов> Блок операторов будет исполняться, пока условие имеет значение true. Необходимо, чтобы значение условия имело возможность изменения при исполнении блока операторов, иначе исполнение цикла не закончится никогда (в DOS это приведет к зависанию компыютера). Если условие зарание ложно, блок операторов не исполнится ни разу. Найдем машинное "эпсилон" для переменной типа Real:
Program Epsilondetect; Var Epsilon: Real; Begin Epsilon:=1; While Epsilon + 1 > 1 Do Epsilon: = Epsilon/2; Writeln ('Эпсилон = ', Epsilon); End. Условный цикл с проверкой после выполнения блока операторов. Repeat <тело цикла> Until <условие> Блок операторов независимо от значения условия будет выполнен хотябы один раз. Цикл заканчивается, если после очередного исполнения блока операторов условие имеет значение true. Пример: программа запрашивает ввод одного символа и выводит его ASCII - код, пока не будет введен пустой символ:
Program Code; Const Cr = 13; Var Ch:Char; Begin Repeat Readln (Ch); Writeln (Ch,' = ', Ord (Ch)); Until Ord (Ch) = Cr End. Оператор выбора одного из вариантов. Case <ключ выбора> Of <список выбора> Else <оператор> End; <ключ выбора> - выражение любого перечислимого типа, <список выбора> - одна или более конструкций вида <значение ключа>:<блок операторов>. Составим программу, имитирующую калькулятор. Программа вводит две строки: первая содержит два числа, разделенные пробелом или запятой, вторая - символ арифметического действия.
2 2 * Признаком конца работы служит ввод любого символа, отличного от +, -, /, *. Программа:
Program Calc; Var Operation: Char; {Знак Операции} X, Y, Z: Real; Stop: Boolean; Begin Stop:= False; repeat Writeln; {Пустая Строка - Разделитель} Write ('X, Y = '); Readln (X,Y); Write ('Операция: '); Readln (Operation); Case Operation Of '+': Z: = X+Y; '-': Z: = X-Y; '*': Z: = X*Y; '/': Z: = X/Y; Else Stop:= True; End; If Not Stop Then Writeln('Z = ',Z); Until Stop; End. Любому из блоков операторов списка может предшествовать не одно, а несколько значений выбора, разделенных запятыми. Оператор безуслов ного перехода на строку с меткой. Goto <метка> Метка, должна быть описана в разделе описаний. Метка, описанная в процедуре (функции) локализуется в ней, поэтому передача управления извне процедуры (функции) на метку внутри нее невозможна. Простые и структурные типы данных. Перечисляемый и ограниченный типы. Ранее рассматривались простые стандартные типы данных (Integer, Boolean,Char ...) - порядковые типы то есть к переменным этих типов применимы Succ и Pred. Real - не порядковый тип. В Паскале разрешено введение новых типов. Секция Type располагается между секцией констант и секцией переменных. Введение своих типов повышает "читабельность" программ. Например: Месяцы.
Type Month = (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec); Var M: Month; Максимальная мощность перечисляемого типа - 256 значений, поэтому перечисляемый тип фактически задаёт подмножество типа Byte. В Паскале отсутствуют средства, которые бы позволяли осуществить непосредственный ввод - вывод переменных перечисляемого типа, однако можно вывести код: Write(Ord(M)). Кроме того применимы операции сравнения:
If (M > FEB) And (M < JUN) Then Write ('Весенний месяц'); Следует иметь ввиду, что упорядоченность элементов перечисляемого типа определяется порядком их следования. Самый левый элемент имеет минимальное значение, правый - максимальное. Пример: процедура, в которой переменной перечисляемого типа присваивается значение, в зависимости от введенного дня недели; если введённые символы ошибочны, возвращается соответствующее сообщение.
Program DAY (Input, Output); Type DayOfWeek = (Sun, Mon, Tue, Wen, Thu, Fri, Sat); Var D: DayOfWeek; Well: Boolean;
Procedure Read_WD (Var I: DayOfWeek;Well: Boolean;); Var C: Char; Begin Read(C); Well:= False; Case C Of "S": Begin Readln(C); Case C Of "U": Begin I:= Sun; Well:= True; End; "A": Begin I:= Sat; Well:= True; End; End;{Of Case} "M": Begin Well:= True; I:= Mon; End; "T": Begin Readln(C); Case C Of . . . . . . . . . End; "W": Begin Well:= True; I:= Wen; End; "F": Begin Well:= True; I:= Fri; End; End; End; Begin Read_WD(D, Well); If Not Well Then Writeln ('Ошибка'); End. Кроме перечисленного типа можно вводить ограниченные или интервальные типы. Пример:
Type Year = 1900..2000; Letter = "A".."Z"; Левая и правая константы задают диапазон значений и их называют соответственно верхней и нижней границей ограниченного типа. Константы в определении ограниченного типа должны относиться к одному и тому же базовому типу (целому, символьному, порядковому). Базовый тип констант определяет допустимость операций над данными ограниченного типа. Пример:
Type Days = (Mo, Tu, We, Th, Fr, Sa, Su); WeekEnd = Sa..Su; Var W: WeekEnd; . . . . . . . . . . . . Begin . . . . . . . . . . . . W:= Sa; . . . . . . . . . . . , Так как тип-диапазон наследует все свойства базового типа, но с ограничениями, связанными с его меньшей мощностью, то Ord(W) вернёт значения S , в то время как Pred(W) приведёт к ошибке. И перечисляемый и ограниченный типы относят к простому типу. Простой тип - это такой тип, который в некоторый момент времени хранит только одно значение. Структурированные типы данных - совокупность связанных данных и множество правил, определяющих, как их организацию так и способ доступа к элементам данных. Массив - это упорядоченный набор переменных одного типа. Массив содержит фиксированное число компонент, которое задаётся при определении переменных типа массива. Тип компоненты массива может быть любым. Каждый элемент масива имеет свой индекс. Тип индекса - простой, поэтому все элементы массива могут быть перебраны при помощи операторов цикла. Описание массивов:
Var Mas: Array [1..15] Of Real. Work: Array [(Mon, Tue, Wed)] Of Integer. B: Array ['A'..'Z'] Of Boolean. C: Array [1..3, 1..5] Of Real. D: Array [(Black, White)] Of 11..20. В Паскале многомерный массив можно описать как одномерный:
Type Mas = Array [1..3] Of Array [1..5] Of Integer; Var A, B: Mas; C: Array [1..3, 1..5] Of Integer; {такая же структура но определена как двумерный массив} Ссылка на элемент матрицы А, лежащий на пересечении I-той строки и J-ого столбца выглядит следующим образом A [I] [J]; законно и такое обращение A[2]:= B[1], для массива такая запись не верна. Все элементы структурированных типов допускают A:= B (исключение - переменные файлового типа). Можно использовать комформант - массивы. Массивы с переменными границами в Турбо-Паскале использовать нельзя. Нельзя, также и сравнивать массивы: If A = B Then ... В Турбо-Паскале предварительно определены два массива Mem и Port. Символьные строки. Переменную типа Packed Array [0..N] of Char принято называть символьной строкой. Строка - это упакованный массив, компоненты которого имеют тип Char и тип индекса имеет нижнюю границу равную одному. К строкам применимы все 6 операций отношений, но строки при этом должны иметь равную длину. В Турбо-Паскале введён тип данных String вместо описанного Packed Array of Char. Операции с Char:
'5' < '25' {ошибка, так как разные длины строк} 'Var' = 'Var' {верно}
Var Age: String [3]; Begin Age:="тринадцать"; {Лишние символы после "и" усекаются} Множества. Паскаль позволяет оперировать с множествами как с типами данных. Пример:
Type Symbolset = Set Of ' ' .. '_'; Color = (White, Blue, Red); Colorset = Set Of Color; T1= Set Of 0..9; Var C: Color; Colset: Colorset; T: Integer; Tset: T1; Множества - наборы однотипных объектов, каким-либо образом связанных между собой. Характер связей лишь подразумевается программистом и никак не контролируется Турбо-Паскалем. Максимальное количество элементов множества - 256. Два множества считаются эквивалентными тогда и только тогда, когда все элементы их одинаковы, причём порядок следования элементов безразличен. Описание: <имя типа> = Set Of <базовый тип>, где <базовый тип> есть любой порядковый тип кроме Word, Integer, Longint. Для задания множества используется конструктор множества (((: : =))). Список спецификаций элементов множества, отделяемых друг от друга запятыми; список обрамляется [ ]. Спецификациями элементов могут быть константы или выражения базового типа, а так же тип - диапазон того же базового типа:
Type Digitchar = Set Of '0'..'9'; Digit =Set Of 0..9;
Var S1, S2, S3: Digitchar; S4, S5, S6: Digit; begin . . . . . . . . . . . . . . . . S1 : = ['1', '2', '3']; S2 : = ['3', '2', '1']; S3 : = ['2', '3']; S4 : = [0..3, 6]; S5 : = [4, 5]; S6 : = [3..9]; Операции над множествами: * - пересечение (S4*S6 = [3,6]; S4*S5 = [4]) + - объединение (S4+S5 = [0, 1, 2, 3, 4, 5, 6]) - -разность, содержит элементы из 1-го, которые не принадлежат второму (S6-S5 = [3, 6, 7, 8, 9]) = - проверка эквивалентности (S4=S4 = true; S5=S4 = false) <> - проверка неэквивалентности (S4<>S4 = false; S5<>S4 = true) <= - проверка вхождения (True если 1-е содержится во втором). >= - (наоборот). in - проверка принадлежности. Пример: Алгоритм получение простых чисел.
Const N = 100; Type Set_Of_Num = Set Of 1..N; Var N1, Next, I: Word; Begset, Primerset: Set_Of_Num; Begin Begset:= [2..N]; Primerset:= [1]; Next:=2; While Begset <> [ ] Do Begin N1:= Next; While N1 <= N Do Begin Begset:= Begset - [N1]; N1:= N1 + Next; End; Primerset:= Primerset + [Next]; Repeat Inc(Next); Until (Next In Begset) Or (Next > N); End; For I:=1 To N Do If I In Primerset Then Write(I: 8); Writeln; End. Итак, над множествами допустимы четыре операции: объединение (+) пересечение (*) разность (-) (содержит элементы из 1-го, которых нет во 2-ом). операция in - позволяет определить, принадлежит элемент множеству или нет. В программе множество задаётся в виде списка элементов, заключённого в квадратные скобки:
Colset:= [White, Red]; Colset:= [ ]; Tset:= [1, 7, 5]; Tset:= [0 ... 3, 6, 9]; Tset:= [8 Mod4, 15 Div 5]; Можно использовать операции сравнения. Пример: из файла вводится текст, содержащий символы от "+" до "[". Требуется рассчитать символы текста в порядке кода ASCII (из повторно встречающихся выводить только один):
Program Sort Var S: Char; Sets: Set Of '+'..'['; I: '+'..'['; Begin Sets:= [ ]; Read(S); While Not Eof Do Begin While Not Eof Do Begin Sets: = Seets + [S]; Read(S); End; End; Writeln; For I: ='+' To '[' Do If I In Sets Then Write (I); Writeln; End. Записи. Запись - наиболее общий и гибкий структурированный тип в Паскале. Запись состоит из фиксированного числа компонентов, называемых полями, которые могут быть различных типов. Этим запись существенно отличается от массива, все компоненты которого должны быть одного и того же типа и доступ к компонентам осуществляется не по индексам (номерам), а по именам. Пример:
Type Date = Record Year: Integer; Month: 1..12; Day: 1..31; End; Book = Record Title: String [40]; Author: String [50]; Entry: Date; End; Var D1: Date; B: Book; В Паскале разрешено использовать массивы записей. Записи также можно использовать в качестве компонент файлов. Однако поля записи не могут быть файлового типа. Чтобы обратиться к отдельной компоненте записи необходимо задать имя записи, за ним точку и сразу за точкой написать название нужного поля. Пример1: D1.Day:=25; B.Author:= "Levy"; B.Entry.Year:= 1980; а можно и B. Entry: = 1980; так как Year первое поле Date. Пример2: В памяти находится массив из 1000 элементов, каждый из которых имеет тип записи Book. Определить количество книг, год издания которых меньше или равен 1600 году. Для каждого из таких элементов массива распечатать название книги, имя автора, год издания:
Program Rarity; Type Date = Record . . . . . . . . . . . End; Book = Record . . . . . . . . . . . End; Var S, I: Integer; Mas: Array[1..1000] Of Book; Begin S:= 0; For I:= 1 To 1000 Do If Mas[I].Entry.Year <= 1600 Then Begin Write (Mas[I].Title,' , ',Mas[I].Author,',' , ',Mas[I].Entry.Year: 6); S:= S+1; End; Writeln ('Число Книг' , S: 4); End. Оператор присоединения With. Можно сократить длинные обозначения элементов записи: With имя записи Do оператор. Оператор With открывает область действия, содержащую имена полей указанной переменной типа записи так, что эти имена фигурируют в качестве имён переменных. Кроме экономии места при написании программ, оператор With экономит так же время при выполнении программы, так как ссылка на запись подготавливается только один раз. Пример:
With B Do Begin Title: = ' ... '; Author:= ' ... '; End;
эквивалентно
B.Title: = ' ... '; B.Author:= ' ... '; Запись с вариантами. Вариантная часть начинается со слова Case. Это означает, что в записях можно задавать тип, содержащий определения нескольких вариантов структуры. Различие может касаться как числа компонент, так и их типов. Запись может содержать только одну вариантную часть (экономия памяти). Вариантная часть сама может содержать варианты (вложения). Пример:
Type N = String [20]; Status = (Женат, Вдов, Разведён, Холост); Date = Record Mo: 1..12; Day: 1..31; Year: Integer; End; Person = Record Name: N; Sex: (Муж, Жена); Birth: Date; Case Ms: Status Of Женат, Вдов: (MDate: Date); Разведён: (Date: Date; First: Boolean); Холост: (Indept: Boolean); End. Замечательная особенность - наложение в памяти вариантных полей, то есть дополнительная возможность преобразования типов:
Var M = Record Case Byte Of 0: (By: Array[0..3] Of Byte); 1: (Wo: Array[0..3] Of Word); 2: (Lo: Longint); End; . . . . . . . . . . . . . . With M Do Begin Lo: . . . . . .; If By [3] = 2 Then ... ; Case Of, открывающее вариантную часть не имеет ничего общего с ветвлением Case Of; в данном случае это директива компилятору, сигнализирующая о том, что последующие поля нужно разместить начиная с одной и той же ячейки памяти, поэтому, если изменяется одно из полей - вариантов, изменяются и все остальные. Поле выбора должно быть порядкового типа. В Турбо-Паскале, в отличие от классики, переменной, описанной в поле ключа выбора, можно присваивать значения, но это ни как не влияет на выбор поля. Значения констант выбора могут быть произвольными и даже повторяющимися. Вместо поля выбора можно иставить лишь идентификатор какого - либо типа, но в таком случае в программе невозможно будет узнать, каким из альтернативных полей используется память, а значит и то, значение какого типа там лежит, но это не всегда и нужно. Пример:
Type Rec1 = Record A: Byte; B: Word; End; Rec2 = Record C: Longint; Case X: Byte Of 1: (D: Word); 2: (E: Record); Case Boolean Of 3: (F: Rec1); 3: (G: Single); '3': (C: Word); End; End;
Var 2: Rec2 . . . . . . . . . . R.X: = 255; If R.E.G = 0 Then Writeln (0K) Else Writeln (R.E.G); Имена полей должны быть уникальными в пределах той записи, где они объявлены, однако если записи содержат поля-записи то есть вложены одна в другую, имена могут повторяться на разных условиях вложенности. Совместимость и преобразования типов. Турбо-Паскаль - типизированный язык, следовательно, все применяемые операции определены только над операндами совместимых типов. Два типа считаются совместимыми, если оба они есть один и тотже тип. один тип есть тип-диапазон второго типа. оба они являются типами-диапазонами одного и того же базового типа. один тип есть строка, а другой - строка или символ. оба они есть процедурные типы с одинаковым типом результата (для типа-функции), одинаковым количеством параметров и одинаковым типом взаимно соответствующих параметров. В Турбо-Паскалевской программе данные одного типа могут преобразовываться в данные другого, явным или неявным образом. При явном преобразовании используются специальные функции Ord, Trunc, Round, Chr, Ptr (преобразует четырёхбайтный целочисленный аргумент к типу-указателю). Преобразование может достигаться применением идентификатора (имени) стандартного типа, или определённого пользователем типа, в качестве идентификатора функции преобразования к выражению преобразуемого типа (так называемое автоопределённое преобразование типов). Например, допустимы следующие вызовы функций:
Type Mytype = (A, B, C, D); . . . . . . . . . . . . . . . . . Mytype (2); Integer (D); Pointer (Longint (A) + $FF); Char (127 Mod C); Byte (K); При автоопределённом преобразовании типа выражения может произойти изменение длины его внутреннего представления (уменьшение или увеличение). В Турбо-Паскале есть ещё один явный способ: в ту область памяти, которую занимает переменная некоторого типа, можно поместить значение выражения другого типа, если только длина внутреннего представления вновь размещаемого значения в точности равна длине внутреннего представления переменной. С этой целью вновь используется автоопределённая функция преобразования типов, но уже в левой части оператора присваивания:
Type Byt = Array [1..2] Of Byte; Int = Array [1..2] Of Integer; Rec = Record X: Integer; Y: Integer; End; Var VByt: Byt; VInt: Int; VRec: Rec; Begin Byt (VInt[1])[2]:= 0; Int (VRec)[1]:= 256; End. Неявное преобразование типов возможно только в двух случаях: выражение из целых и вещественных приводится к вещественным одна и та же область памяти трактуется попеременно как содержащая данные то одного, то другого типа. Совмещение данных может произойти при использовании записей с вариантами, типизированных указателей, содержащих одинаковый адрес, а также при явном размещении данных разного типа в одной области памяти (используется Absolute - за ним помещается либо абсолютный адрес, либо идентификатор ранее определённой переменной). Абсолютный адрес - пара чисел, разделённых двоеточием - первое - сегмент, второе - смещение. Пример: B: Byte Absolute $0000:$0055; W: Longint Absolute 128:0; Если за Absolute указан идентификатор переменной, то происходит совмещение в памяти данных разного типа, причём первые байты внутреннего представления данных будут располагаться по одному и тому же абсолютному адресу:
Var X: Real; Y: Array [1..3] Of Integer Absolute X; Типизированные константы Описываются в разделе констант: <идентификатор> : <тип> = < значение > В ходе выполнения программы можно присваивать другие значения. При повторном входе в блок (процедуру или функцию), в котором объявлена типизированная константа, переинициализации не происходит и она сохраняет то значение, которое имела при выходе из блока. Могут быть любого типа, кроме файлов. Нельзя так же объявить типизированную константу-запись, если хотя бы одно её поле файлового типа. Нельзя использовать в качестве значения при объявлении других констант или границ типа-диапазона. Примеры: Типизированные константы простых типов и типа String:
Type Colors = (White, Red, Black);
Const {Правильные объявления} Cyrrcol: Colrs = Red; Name: String = 'Ку-Ку'; Year: Word = 1989; X: Real = 0.1; Min: Integer = 0; Max: Integer = 10; Days: 1..31 = 1; Answer: Char = 'Y';
{Неправильные объявления} Mars: Arrray [Min..Max] Of Real; A,B,C: Byte = 0; X: Real = Pi;
Var Namef: String [22] = 'Prog.Pas'; Типизированные константы-массивы В качестве начального значения используется список констант, отделённых друг от друга запятыми, список заключается в круглые скобки:
Type Colors = (White, Red, Black); Const Colstr: Arrray [Colors] Of String [5] = ('White', 'Red', 'Black'); Vector: Array [1..5] Of Byte = (0, 0, 0, 0, 0); При объявлении массива символов можно использовать то обстоятельство, что все символьные массивы и строки в Турбо- Паскале хранятся в упакованном формате,.поэтому в качестве значения массива-константы типа Char допускается указывать символьную строку:
Const Digit: Array [0..9] Of Char = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'); Digchr: Aray [0..9] Of Char = ('0123456789'); При объявлении многомерных - множество констант, соответствующее каждому измерению заключается в дополнительные круглые скобки и отделяется от соседей множества запятыми. Множество констант с максимальной глубиной вложения связывается с изменением самого правого индекса массива. Пример - вывести на экран три строки с монотонно увеличивающимися целыми числами:
Var I, J, K, L: Integer; Const Matr: Array [1..3, 1..5] Of Byte = ((0, 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 {Циклы и Writeln} End. Количество переменных в списке констант должно строго соответствовать объявленной длине массива по каждому измерению. Типизированные константы-записи. <идентификатор>:<тип> = (<список значений полей>) <тип> - предварительно объявленный тип записи. <список значений полей > - список из последовательностей следующего вида: имя поля, двоеточие и константа; элементы списка отделятся друг от друга точкой с запятой. Пример:
Type Point = Record X,Y: Real; End; Vect = Array [0..1] Of Point; Month = (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec); Date = Record D: 1..31; M: Month; Y: 1900..1999; End; Const Orign: Point = (X: 0; Y: -1); Line: Vector = ((X: -3.1; Y: 1.5), (X: 5.9; Y: 3.0)); Someday: Date = (D: 16; M: Mar; Y: 1989); Для записей с вариантными полями указывается только один из возможных вариантов констант:
Type Forma = Record Case Boolean Of True: (Place: String [40]); False: (Country: String [20]; Port: String [20]; Date: Array[1..3] Of Word; Count: Word) End; Const Con1: Forma = (Country: 'Россия'; Port: 'Москва'; Date: (16,3,89); Count: 10); Con2: Forma = (Place: 'Петрозаводск'); Типизированные константы множества. Значение типизированной константы множества задаётся в виде правильного конструктора множества:
Type Days = Set Of 1..31; Dige = Set Of '0'..'9'; Error = Set Of 1..24; Const Workdays: Days = [1..5, 8..12, 15..19, 22..26, 29, 30]; Evendigits: Dige = ['0', '2', '4', '6', '8']; Errorflag: Error = [ ]; Типизированные константы указатели. Единственным значением типизированной константы указателя может быть только Nil:
Const Pr: ^Real = Nil; P: POINTER = Nil; Процедуры и функции. Блочная структура программ. Подпрограмма - обособленная именованная часть программы со своим собственным локальным контекстом имён. Или иначе: часть программы, оформленная в виде отдельной синтаксической конструкции и снабжённая именем. Вызов подпрограммы осуществляется по имени. Локальные объекты (константы, переменные, типы, подпрограммы) доступны только внутри подпрограммы. В Паскале подпрограммы бывают двух типов: процедуры и функции. Общая структура подпрограммы. Структура подпрограммы в Турбо-Паскале почти буквально повторяет структуру всей программы, и в общем случае состоит из 3-х основных компонент: Интерфейс подпрограммы (информация, необходимая для её вызова). Сосредоточен в заголовке. Локальный контекст подпрограммы (совокупность описаний объектов, с которыми осуществляются действия) Собственно действия, составляющие смысл подпрограммы (последовательность операторов и вызовов подпрограмм, тоесть программа на Паскале). Процедуры и функции различаются назначением и способом использования. Процедуры служат для задания совокупности действий, направленных на изменение внешней по отношению к ним программной обстановки. Или по-человечески, процедура, это подпрограмма, которая обрабатывает данные, принимает или выводит информацию, меняет или нет каким-либо образом глобальные и локальные переменные, вообще что-то делает. Смысл функций - определить алгоритм вычисления нового значения некоторого простого или ссылочного типа. Функция, это подпрограмма, которая, как и процедура, что-то делает, но, помимо этого, она обязательно возвращает значение, тип которого задается при описании заголовка процедуры. Вызов функции является одним из допустимых операндов выражения, обозначая в нём то значение, которое вычисляет функция, тоесть, если подпрограмма с идентификатором “Function1” – функция, то можно произвести следующие действия: X:=Function1(“параметры – аргументы функции”); X:=2*Function1(a,e,…,m,…)-1/Function1(c,f,…,k,…); и т.д. Области видимости объектов. Имена объектов, описанных в некотором блоке, считаются известными в пределах данного блока, включая и все вложенные блоки. Имена объектов, описанные в блоке, должны быть уникальными в пределах данного блока и могут совпадать с именами объектов из других блоков. Если в некотором блоке описан объект, имя которого совпадает с именем объекта, описанного в объемлющем блоке, то последнее становится недоступным в данном блоке (экранировка). Пример:
Program X; Var I,j:integer; z:real;
Procedure x1(i:integer;); Var j:integer; begin End; Begin End;
Procedure x2; Var z:integer;
Procedure x3(z:string); Begin End;
Function y4(z:string):integer; Begin End;
Begin End;
Begin End. Приведенная ниже диаграмма показывает видимость идентификаторов в примере. Каждый идентификатор виден в пределах того прямоуголльника, в котором он описан и в прямоугольниках, содержащихся в данном. За пределами этого прямоугольника идентификатор либо неизвестен, либо имеет другое значение, тип. Так, например, функция y4 может быть вызвана лишь внутри процедур x2 и x3, аналогично и x3. Переменные z в теле пограммы и в каждой из x2, x3, y4 имеют абсолытно разные и независимые значения, а в x1 видима таже z, что и в теле программы.В тоже время переменные i и j перекрыты в x1 новыми определениями, а в x2, x3, y4, они те же, что и в основной программе. Механизм передачи параметров. В заголовке подпрограммы может быть задан список формальных параметров. Каждый параметр, заданный в заголовке, считается локальным в данной подпрограмме. Идентификаторы формальных параметров можно считать условными обозначениями реальных (фактических) параметров, которые будут переданы в подпрограмму при её вызове. Внимание: Типы формальных параметров должны обязательно обозначаться идентификаторами.
Недопустимо: Procedure InCorrect (Var A: Array [1..10] of Byte);
Нужно: Type MyArray = Array [1..10] of Byte; Procedure Correct (Var A: MyArray); Допустимы по крайней мере три способа задания формальных параметров: параметры, перед которыми отсутствует служебное слово Var и за которыми следует идентификатор типа; параметр, перед которым Var и далее тип; параметр со словом Var и не имеющие типа. Эти три способа задания формальных параметров отражают три различных способа передачи параметров a - по значению; b - по ссылке; c - передача нетипизированных параметров по ссылке (b, c - параметры-переменные). Параметры - значения. Наиболее распространенный и простой способ. Параметр - обычная локальная переменная. Может использовать выражение. Любые действия внутри подпрограммы никак не отражаются на значениях переменной вне подпрограммы. Параметры - переменные. Передаются по ссылке. Способ используется, когда необходимо передать некоторое значение в точку вызова подпрограммы. В случае Var - формальные параметры считаются синонимами соответствующих фактических параметров. При этом фактические параметры должны быть переменными (не выражениями) того же типа, что и формальные параметры:
Procedure Swap (Var X,Y: Real); Var T: Real; Begin T:= X; X:= Y; Y:= T; End; Переменные файловых типов могут передаваться в подпрограмму только как параметры - переменные. Безтиповые параметры. Var Ident, где Ident - идентификатор формального параметра. Фактический параметр, соответствующий формальному нетипизированному, должен представлять собой переменную любого типа (но не выражение). Единственным способом использования таких параметров является “наделение” их определённым типом. Существуют два способа: Применить операцию приведения типа. Описать в подпрограмме локальную переменную определённого типа с совмещением её в памяти с нетипизированным параметром. Пример1: функция для сравнения значений двух переменных любых типов и, соответственно, любых размеров:
Function Equal (Var Source, Dest; Size: Word): Boolean; Type Bytes = Array [0..MaxInt] of Byte; Var N: Integer; Begin N:= 0; While (N < Size) and (Bytes(Dest)[N] - Bytes(Source)[N]) do N:=N+1; Equal:= (N = Size); End;
{Имеются описания:}
Type Vector = Array [1..10] of Integer; Point = Record X,Y: Integer; End;
Var Vec1, Vec2: Vector; N: Integer; P: Pointer; Begin . . . . . . . . . . . . . . . . . . . . . . . . .
Equal(Vec1, Vec2, SizeOf(Vector)); {сравниваются все элементы Vec1 c Vec2} Equal(Vec1, Vec2, SizeOf(Integer)*N); {сравниваются N первых элементов Vec1 и Vec2} Equal(Vec1[1], Vec2[6], SizeOf(Integer)*5); {сравниваются 5 первых элементов Vec1 c пятью последними Vec2} Equal(Vec1[1], P, 4); {сравнивает Vec1[1] c P.x и Vec2 с P.y} Пример2: задание различных действий в зависимости от размера переданного нетипизированногопараметра:
Procedure Size (Var X); Var V1: Byte absolute X; V2: Word absolute X; V3: LongInt absolute X;
Begin Case SizeOf(X) of SizeOf(Byte): Begin . . V1 . . End; SizeOf(Word): Begin . . V2 . . End; SizeOf(LongInt): Begin . . V3 . . End; End; End; Вычисление значения функции, завершение подпрограммы. Смысл функции заключается в задании алгоритма вычисления некоторого значения и организации возврата этого значения в точку вызова. В теле функции должен присутствовать оператор присваивания специального вида, в левой части которого должен быть указан идентификатор, совпадающий с именем функции, а в правой части - выражение, вычисляющее возвращаемое значение. Таких операторов может быть несколько, важно чтобы хотя бы один всегда срабатывал в процессе выполнения тела функции. Если в процессе выполнения функции не было выполнено ни одного присваивания, то результат функции считается неопределённым. Функция может возвращать в качестве результата значения только простого, строкового или ссылочного типа. Работа процедуры или функции по определению завершится после выполнения последнего оператора её тела. Однако Турбо-Паскаль имеет оператор Exit, возвращающий программу в точку вызова. Перед выполнением Exit в теле функции должен выполнится оператор присваивания имени функции значения. Предварительные и внешние описания подпрограмм. Как правило, телом процедуры является блок, но есть несколько исключений из данного правила. Две подпрограммы, описанные на одном уровне, содержат взаимные вызовы друг друга.
Procedure A(X,Y: Real); Begin . . . . . . B (2, 2); . . . . . . End;
Procedure B (A,B: Integer); Begin . . . . . . A (1.5, 2.8); . . . . . . End; Решением этой проблемы является механизм предварительных описаний. Предварительное описание содержит заголовок подпрограммы, а вместо тела записывается служебное слово Forward. В этом случае заголовок полного описания может быть записан без списка параметров и (для функций) без типа результата.
Procedure A(X,Y: Real): Forward; Procedure B(A,B: Integer): Forward; . . . . . . . . . Procedure A; Begin . . . . . . . . End;
Procedure B; Begin . . . . . . . . End; В случае предварительного описания подпрограммы далее в тексте должно обязательно содержаться её определяющее описание, даже если нигде в программе не встречается вызов этой подпрограммы. Подпрограмма или группа подпрограмм разработана вне системы Турбо-Паскаля, на другом языке, и необходимо использовать её в данной Pascal-программе. Внешнее описание. Объектный код подпрограммы содержится в OBJ- файле. В Pascal-программе необходимо описать заголовок подключаемой подпрограммы, после которого должно идти слово external, и указать имя файла, содержащего подпрограмму: {$L <имя .OBJ-файла>}
{$L ABC.OBJ} Procedure A(C,D,E: Real); External; Procedure B(I,F,J: Integer); Extenal Для обеспечения корректности такого подключения необходимо соблюдать определённые межъязыковые соглашения о связях принятые в Турбо-Паскале. Специальные случаи. Описание тела подпрограммы в машинных кодах - используется специальная конструкция со словом inline или серия ассемблерных инструкций с использованием слова asm. Следующие коды эквивалентны:
asm mov cx,100 metka mov i,cx . . . . . . . . loop metka end;
И:
for i:=100 downto 0 do . . . . . . . . . . Однако, код, написанный между asm и end выполнится быстрее, нежели всего одна строка, заменяющая его с использованием оператора цикла паскаля. В описании процедуры перед блоком может указываться специальное слово interrupt, которое служит для определения процедур прерываний. Допускается указание “типа вызова” процедуры (Far, Near). Для функций не допускается interrupt, тоесть она не может быть обработчиком прерывания. Рекурсия и побочный эффект. В теле подпрограммы доступны все объекты, описанные в объемлющем блоке, в том числе и имя самой подпрограммы. Подпрограммы, вызывающие сами себя, называются рекурсивными. Пример: алгоритм вычисления факториала: N! = (N-1)!*N
Fuction Fact(N: Word): LongInt; Begin If N = 1 then Fact:= 1 else Fact:= N * Fact(N-1); End; В Турбо-Паскале нет никаких ограничений на рекурсивные вызовы подпрограмм, необходимо только помнить, что каждый очередной рекурсивный вызов приводит к образованию новой копии локальных объектов подпрограммы и все эти копии, соответствующие цепочке активизированных и незавершенных рекурсивных вызовов существуют независимо друг от друга. Может возникнуть ситуация, при которой значение выражения, использующего вызов такой функции, зависит от порядка следования операндов, что является источником трудноуловимых программных ошибок - побочный эффект. Например:
Program SideEffect; Var A,Z: Integer; F: Text;
Function Change(X: Integer): Integer; Begin Z:= Z - X; { изменяем значение локальной переменной } Change:= Sqr (X); End;
Begin Assign(F, ‘Prn’); Rewrite (F);
Z:= 10; A:= Change (Z); WriteLn (F, A,’ ’,Z);
Z:= 10; A:= Change (10) * Change (Z); WriteLn (F,A,’ ‘,Z);
Z:= 10; A:= Change (Z) * Change (10); WriteLn (F,A,’ ‘,Z); End;
Результаты:
100 0 10000 -10 0 0 Распределение памяти под данные. Память под глобальные и типизированные константы выделяется при запуске программы в одном сегменте (65520 байт - сегмент данных программы). Локальные переменные динамически размещаются в том же сегменте при активизации подпрограммы. При рекурсии или просто вызове подпрограммы используется стек. Через стек передаются параметры подпрограмм, в стеке сохраняются переменные, вышедшие за область видимости при передаче управления в подпрограмму. Причем дописывание информации в стек происходит при каждом рекуррентном вызове , поэтому нужно следить за глубиной рекурсии, иначе слишком длинная, или бесконечная рекурсия забьет все стековое пространство и программа не сможет выполняться дальше, произойдет ошибка времени исполнения. Максимальный объём стековой памяти в Турбо-Паскале - 1 сегмент (65520 байт). Можно устанавливать размер стека и контроль за переполнением стека - $M и $S (тоесть, компилятор может включить в программу код, следящий за переполнением стека и выдающий вовремя сообщение о переполнении стека, вместо зависания программы, обычно этот ключ установлен по умолчанию). Но не следует всегда выставлять непременно максимальный обьем стека, так как ето приводит к увеличению размера памяти, занимаемой программой при исполнении. Процедурные типы. Турбо-Паскаль позволяет вводить переменные специального типа, значениями которых могут служить подпрограммы. Можно интерпретировать подпрограмму как значения, которые можно присваивать переменным и передавать их в качестве параметров (речь идёт о подпрограммах как о целостных объектах, а не о значениях, возникающих в процессе их выполнения).
{$F+} Var P: Procedure; Значениями Р могут быть любые процедуры без параметров. В более общем случае:
Type Func = Function(X,Y: Integer): Integer; Var F1,F2: Func; Например, если есть функция:
Function Add(A,B: Integer): Integer; Begin Add:= A + B; End; то допустимо F1:= Add, в этом случае переменной F1 присваивается функция Add как таковая, но её исполнения не происходит.Теперь можно: Write (Add (1,2)) или Write (F1 (1,2));. Следует обратить внимание на строку {$F+} – она существенна, это ключ компилятора, и если он выключен, при присвоении переменным процедурного типа значений конкретных подпрограмм, возникнет ошибка присвоения типа. Имена формальных параметров, указываемые в процедурных типах, играют часто иллюстративную роль и на смысл определений никакого влияния не оказывают. Необходимыми являются только идентификаторы типов параметров и результатов (для функций). Процедурные типы допускают также присвоение вида F1:= F2; Такие переменные можно использовать для вызова подпрограмм, которые присвоены этим переменным. Пример:
Var Oper: Function(X, Y: Real): Real;
Function Add(A,B: Real): Real; Begin Add:= A + B; End;
Function Sub (A,B: Real): Real; Begin Sub:= A - B; End; тогда в основной программе можно использовать:
If <условие> then Oper:= Add else Oper:= Sub; Write (Oper(2.5, X+Y)); Процедурные типы, чтоб они были совместимыми по присваиванию, должны иметь одинаковое количество формальных параметров, а параметры на соответствующих позициях должны быть одного типа. Также, должны совпадать типы возвращаемых значений у функций. Использование процедурных типов не ограничиваются простыми процедурными переменными. Как и любой другой тип, процедурные типы могут участвовать в построении структурированных типов:
Type Proc = Procedure(T: Real); Notice = Record Next: Integer; Time: Real; Action: Proc; End; Var New_Notices: Array[1..10] of Proc; Notices: Notice; Правила корректной работы с процедурными типами. Подпрограмма, присваиваемая процедурной переменной должна быть странслирована в режиме “дальнего вызова”. Подпрограмма, присваиваемая процедурной переменной, не должна быть стандартной процедурой или функцией. Это ограничение легко обойти:
Var Func: Function(R: Real): Real; . . . . . . . . . . . . . . . . Function MyExp(R: Real): Real Begin MyExp:= Exp (R); End; . . . . . . . . . . . . . . . . Begin Func:= MyExp; Подпрограмма, присваиваемая процедурной переменной, не может быть вложенной в другие подпрограммы. Подпрограмма, присваиваемая процедурной переменной, не может быть подпрограммой специального вида (interrupt или inline). Процедурная переменная занимает в памяти 4 байта (2 слова). В первом хранится смещение, во втором - сегмент (т.е. указатель на код подпрограммы). Можно описывать процедуры и функции, параметрами которых являются процедурные переменные. Таким образом, можно организовать подпрограмму, которая будет выполнять некоторое общее действие для различных подпрограмм - периметров. В общем случае использование процедурных переменных в операторе или выражении означает вызов присвоенной данной переменной процедуры или функции. Необходимо помнить, что если в левой части оператора стоит процедурная переменная, правая часть его должна представлять идентификатор другой процедурной переменной или идентификатор подпрограммы. К сожалению есть неопределённость для компилятора, например в таком случае:
Type Func = Function: Real; Var F: Func;
Function FFF: Real; Begin FFF:= 1.25; End;
Function FF: Real; Begin FF:= 2.10; End; . . . . . . . . . . F:= FF; If F = FFF then . . . . В подобных случаях неочевидно, должен ли компилятор сравнивать значение процедуры F с FFF или нужно вызвать процедуру F и FFF и сравнить их значения. Принято, что такое вхождение идентификатора подпрограммы означает вызов функции. Чтобы сравнить значение переменной F со значением (адресом) подпрограммы FFF нужно использовать следующую конструкцию: If @F = @FFF then . . . . Чтобы получить адрес самой процедурной переменной нужно написать: @@F Приведение типов переменных для процедурных типов. Определены следующие типы и переменные:
Type Func:= Function(X: Integer): Integer;
Function MyFunc(X: Integer): Integer; Begin MyFunc:= X; End;
Var F: Func; P: Pointer; N: Integer; Можно построить следующие присваивания:
F:= MyFunc {переменной F присваивается функция MyFunc} N:= F(N) {функция MyFunc вызывается через переменную F} P:= @F {P получает указатель на функцию MyFunc} N:= Func (P)(N) {функция MyFunc вызывается через указатель P} F:= Func (P) {присвоить значение подпрограммы в P переменной F} Func(P):= F {присвоить значение подпрограммы в F указателю P} @F:= P {присвоить значение указателя в P переменной F} Динамическая память. Все глобальные переменные и типизированные константы размещаются в одной непрерывной области оперативной памяти, которая называется сегментом данных. Длина сегмента определяется архитектурой процессора 8086 и составляет 64 Килобайта (65536 байт), что может вызвать определённые трудности при описании и обработке больших массивов данных. С другой стороны объём стандартной памяти - 640 Килобайт. Выход - использовать динамическую память. Динамическая память - это оперативная память ЭВМ, предоставляемая Турбо-Паскалевой программе при её работе, за вычетом сегмента данных (64 К), стека (обычно 16 К) и собственно тела программы. По умолчанию размер динамической памяти определяется всей доступной памятью ЭВМ и, как правило, составляет не менее 200 - 300 Кбайт. Динамическую память обычно используют при: обработке больших массивов данных; разработке САПР; временном запоминании данных при работе с графическими и звуковыми средствами ЭВМ. Размещение статических переменных в памяти осуществляется компилятором в процессе компиляции. Динамические переменные - размещаются в памяти непосредственно в процессе работы программы. При динамическом размещении заранее неизвестны ни тип, ни количество размещаемых данных, к ним нельзя обращаться по именам, как к статическим переменным. Турбо-Паскаль представляет средство управления динамической памятью: указатели. Указатель - это переменная, которая в хранит качестве своего значения адрес байта памяти. В 8086 адреса задаются совокупностью двух шестнадцатиразрядных слов - сегмента и смещения. Сегмент - участок памяти, имеющий максимальную длину 64 К и начинающийся к физического адреса, кратного 16 (то есть 0, 16, 32, 48 и т.д.). Смещение - указывает, сколько байт от начала сегмента нужно пропустить, чтобы обратиться по нужному адресу. Фрагмент памяти в 16 байт называется параграфом. Сегмент адресует память с точностью до параграфа, а смещение - с точностью до байта. Каждому сегменту соответствует непрерывная и отдельно адресуемая область памяти. Сегменты могут следовать в памяти один за другим, или с некоторыми интервалами, или, наконец, перекрывать друг друга. Таким образом любой указатель по своей внутренней структуре представляет собой совокупность двух слов (типа Word), трактуемых как сегмент и смещение. Указатель адресует лишь первый байт типа данных. Справка: процессор 8086. 8086 имеет 16 - битовые регистры, всего 14 штук, из них: 12 - регистры данных и адресов; 1 - указатель команд (регистр адреса команды); 1 - регистр состояния (регистр флагов). Регистры данных и адресов делятся на три группы: регистр данных; регистр указателей и индексов; регистр сегментов. Процессор 8086 всегда генерирует 20-ти бытовые адреса за счёт добавления 16-ти битового смещения к содержимому регистра, умноженному на 16: физический адрес = смещение + 16 * регистр сегмента. Вместо умножения на 16 содержимое регистра сегмента используется так, как если бы оно имело четыре дополнительных нулевых бита:
пусть: смещение = 10H регистр сегмента = 2000H тогда: 0000 0000 0001 0000 (смещение) 0010 0000 0000 0000 (0000) (номер блока) 0010 0000 0000 0001 0000 (физический адрес)
физический адрес = 20010H У 8086 адрес ячейки задаётся номером блока и смещением, что обусловлено тем, что команды 8086 и её данные должны располагаться в разных частях памяти (в разных сегментах). Если требуется адресоваться к данным, то потребуется адресация блока памяти, с которого начинается сегмент данных (из регистра сегмента данных) и позиция желаемой ячейки в этом сегменте (смещение). В дополнение к области памяти 1 Мбайт, 8086 может адресоваться к внешним устройствам через 65536 (64 К) портов ввода-вывода. Имеются специальные команды ввода-вывода, позволяющие иметь непосредственный доступ к первым 256 (от 0 до 255) портам. Другие команды позволяют получить косвенный доступ к порту с помощью занесения идентифицирующего его номера (0 - 65535) в определенный регистр данных. Подобно ячейке памяти любой порт может быть 8 или 16- битовым. Распределение памяти IBM PC. 16 - старших байт - команды начальной загрузки системы. Первые 1024 ячейки - вектора прерываний. Типы прерываний. Существуют 2 вида прерываний - одни можно игнорировать, другие обязательно обслужить как можно скорее. 8086 может распознать 256 различных прерываний, каждому из них однозначно соответствует код типа (целое число от 0 до 255). Код используется в качестве указателя ячейки в области памяти с младшими адресами (область векторов прерываний). Объявление указателей. Как правило, в Турбо-Паскале указатель связывается с некоторым типом данных (типизированные указатели).
Type PerPnt = ^PerRec; {указатель на переменную типа PerRec} PerRec = Record Name: String; Job: String; Next: PerPnt; End; Var P1: ^Integer; P2: ^Real; Внимание! При объявлении типа PerPnt мы сослались на PerRec, который ещё не был объявлен, это связано с тем, что существует исключение из правил для указателей, которые содержат ссылку на ещё неописанный тип данных. Это исключение сделано не случайно, так как динамическая память позволяет использовать организацию данных в виде списков (каждый элемент имеет в своём составе ссылку на соседний элемент - обратите внимание на поле Next). В Турбо-Паскале можно объявлять указатель и не связывать его с конкретным типом данных:
Var PP: Pointer; Указатели такого рода называют нетипизированными. В них удобно размещать данные, структура которых меняется по ходу работы. В Турбо-Паскале можно передавать значения только между указателями, связанными с одним и тем же типом данных.
Var P1,P2: ^Integer; P3: ^Real; PP:Pointer; Begin P1:= P2; {- верно} P1:= P3; {- неверно} но можно сделать так: PP:= P3; P1:= PP; так как ограничение не распространяются на нетипизированные указатели. Но подобные операции часто путают программиста и чреваты смысловыми ошибками. Выделение и освобождение динамической памяти. Вся динамическая память – пространство ячеек, называемое кучей. Физически куча располагается в старших адресах, сразу за программой. Указатель на начало кучи храниться в предопределенной переменной HeapOrg, конец - FreePtr, текущую границу незанятой динамической памяти указывает указатель HeapPtr. Для выделения памяти под любую переменную используется процедура New. Единственным параметром является типизированный указатель:
Var I,J: ^Integer; R: ^Real; Begin New(I); {под I выделяется область памяти,} {адрес первого байта этой области помещается в I} End. После выполнения этого фрагмента указатель I приобретёт значение, которое перед этим имел указатель кучи HeapPtr, а HeapPtr увеличится на два (т.к. он типа Integer); New(R) - вызовет смещение указателя на 6 байт. После того как указатель приобрёл некоторое значение, то есть стал указывать на конкретный байт памяти, по этому адресу можно разместить значения соответствующего типа: I^:= 2; R^:= 2*Pi; Допустима запись: R^:= Sqr (R^) + I^ - 17; Но недопустима запись: R:= Sqr (R^) + I^ - 17; так как указателю R нельзя присвоить значение вещественного выражения. Возврат динамической памяти обратно в кучу осуществляется оператором Dispose: Dispose(R); Dispose(I); - вернут в кучу, ранее забранные 8 байт. Dispose(Ptr) не изменяет значения указателя Ptr, а лишь возвращает в кучу память, связанную с этим указателем. Однако повторное применение процедуры к “свободному” указателю приведет к возникновению ошибки времени исполнения. Чтобы указать, что указатель свободен, нужно использовать зарезервированное слово Nil. К указателям можно применять операции отношения, в том числе и сравнения с Nil:
Const P:^Real = Nil; . . . . . . . . Begin If P = Nil then New (P); . . . . . . . . Dispose(P); P:= Nil; End. Использование указателей, которым не присвоено значение процедурой New или каким-либо другим способом, никак не контролируется системой и может привести к непредсказуемым результатам. Многократное чередование New и Dispose приводит к “ячеистой” структуре кучи. Дело в том, что все операции с кучей выполняется под управлением особой программы, которая ведёт учёт всех свободных фрагментов в куче. При выполнении оператора New( ) эта программа отыскивает минимальный свободный фрагмент, в котором может разместиться требуемая переменная. Адрес начала найденного фрагмента возвращается в указателе, а сам фрагмент или его часть нужной длины, помечаются как занятая часть кучи. Можно освободить целый фрагмент кучи следующим образом: Перед началом выделения динамической памяти значения указателя HeapPtr запоминается в переменной-указателе с помощью процедуры Mark. Выполнение программы. Освобождение фрагмента кучи от заполненного адреса до конца динамической памяти с использованием процедуры Release.
Var P, P1, P2, P3, P4, P5: ^Integer; Begin New(P1); New(P2); New(P3); New(P4); New(P5); Mark(P); . . . . . . . Release (P); End. В этом примере процедурой Mark(P) в указатель P было помещено текущее значение HeapPtr, однако память под переменную не резервировалась. Вызов Release уничтожает список свободных фрагментов в куче, созданных Dispose, поэтому совместное применение этих процедур не рекомендуется. Для работы с нетипизированными указателями используются также процедуры GetMem(P, Size) и FreeMem(P, Size) - резервирование и освобождение памяти. P - нетипизированный указатель, Size - размер. За одно обращение к куче процедурой GetMem можно зарезервировать до 65521 байт. Освобождать нужно ровно столько памяти, сколько было зарезервировано, и именно с того адреса, с которого память была зарезервирована, иначе программа не будет работать и завершаться корректно !!! Использование нетипизированных указателей даёт широкие возможности неявного преобразования типов:
Var i,j: ^Integer; r: ^Real; Begin New (i); { I:= HeapOrg; HeapPtr:= HeapOrg+2 } j:= i; { J:= HeapOrg } j^:=2; Dispose (i); { HeapPtr:= HeapOrg } New (r); { R:= HeapOrg; HeapPtr:= HeapOrg+6 } r^:= Pi; WriteLn (j^); End.
Будет выведено: "8578" {здесь преобразование не имеет никакого смысла} Примеры использования указателей. Динамическая память - 200..300 Кбайт. Нужно разместить массив 100 * 200 типа Extended. Требуется 100 * 200 * 10 = 200000 байт. Пробуем:
Var i,j: Integer; PtrArr: Array[1..100, 1..200] of ^Extended. Begin . . . . . . For i:= 1 to 100 do For j:= 1 to 200 do New (PtrArr [i,j]); . . . . . . End. Теперь к любому элементу можно обратиться: PtrArr[i,j]^:=...; Но длина внутреннего представления указателей 4 байта, поэтому потребуется ещё 100*200*4 = 80000 байт, что превышает размер сегмента (65536 байт), доступный для статического размещения данных. Можно было бы работать с адресной арифметикой (арифметикой над указателями), то есть не создавать массив указателей, а вычислять адрес элемента непосредственно перед обращением к нему. Однако в Турбо-Паскале над указателями не определены никакие операции, кроме присваивания и отношения. Но задачу решить можно: Seg(x) - возвращает сегментную часть адреса. Ofs(x) - возвращает смещение. x - любая переменная, в том числе и та, на которую указывает указатель. Далее с помощью Ptr(Seg, Ofs: Word): Pointer можно создать значение указателя, совместимое с указателем любого типа. Таким образом, сначала с помощью GetMem забираем из кучи несколько фрагментов подходящей длины (не более 65521). Удобно по строкам 200 * 100 = 20000 байт. Начало каждого фрагмента запоминается в массиве PtrStr из 100 указателей. Теперь для доступа к любому элементу строки нужно вычислить смещение этого элемента от начала строки и сформировать указатель.
Var i,j: Integer; PtrStr: Array [1..100] of Pointer; Pr: ^Extended; Const SizeOfExt = 10; Begin For i:= 1 to 100 do GetMem (PtrStr[i], SizeOfExt*200); . . . . . . . . . . . . . . . . Pr:= Ptr(Seg(PtrStr[i]^), Ofs(PtrStr[i]^) + (j - 1) * SizeOfExt); {Обращение к элементу матрицы [i,j]} End; далее работаем с Pr^:=. . . и т.д. Полезно ввести две вспомогательных функции GetExt и PutExt. Каждая из них будет обращаться к функции вычисления адреса AddrE.
Program Demo; Const SizeOfExt = 10; N = 100; M = 200; Type ExtPoint = ^Extended; Var i,j: Integer; PtrStr: Array[1..N] of Pointer; S: Extended;
Function AddrE(i,j: Word): ExtPoint; Begin AddrE:= Ptr(Seg(PtrStr[i]^), Ofs(PtrStr[i]^) + (j - 1) * SizeOfExt); End;
Function GetExt(i,j: Integer): Extended; Begin GetExt:= AddrE(i,j)^; End;
Procedure PutExt(i,j: Integer; X: Extended); Begin AddrE(i,j)^:= X; End;
Begin {main} Randomize; For i:=1 to N do Begin GetMem (PtrStr[i], M*SizeOfExt); For i:= 1 to m do PutExt(i, j, Random(255)); End; S:= 0; For i:= 1 to N do For j:= 1 to M do S:= S + GetExt(i,j); WriteLn(S/(N*M)); End. Мы предполагали, что каждая строка размещается в куче с начала границы параграфа и смещение каждого указателя PtrStr ровно 0. В действительности при последовательных обращениях к GetMem начало очередного фрагмента следует за концом предыдущего и может не попасть на границу сегмента. В результате при размещении фрагментов максимальной длины (65521 байт) может возникнуть переполнение при вычислении смещения последнего байта. Процедуры и функции для работы с динамической памятью. Функция Addr - возвращает результат типа Pointer, в котором содержится адрес аргумента. Addr(X), x - любой объект программы. Возвращаемый адрес совместим с указателем любого типа. Аналогично операции @. Функция CSeg - возвращает значения хранящееся в регистре CS (в начале работы программы в CS содержится сегмент начала кода программы), результат CSeg - слово типа Word. Процедура Dispose(x) - возвращает в кучу фрагмент динамической памяти, зарезервированный за типизированным указателем x. Функция DSeg - возвращает значение хранящиеся в регистре DS (в начале работы в DS - сегмент начала данных программы), результат - типа Word. Процедура FreeMem - возвращает в кучу фрагмент динамической памяти, который ранее был зарезервирован за нетипизированным указателем. FreeMem(P, Size), P - нетипизированный указатель. Size - длина фрагмента, подлежащего освобождению. Процедура GetMem(P, Size) - резервирует память (за одно обращение не более 65521 байт), если нет свободной памяти - ошибка времени исполнения. Процедура Mark(Ptr) запоминает текущее значение указателя кучи HeapPtr. Ptr - указатель любого типа, в нём будет возвращено HeapPtr. Используется совместно с Release для освобождения части кучи. Функция MaxAvail - возвращает размер (в байтах) наибольшего непрерывного свободного участка кучи. Результат типа LongInt. За один вызов New или GetMem нельзя зарезервировать значение большее, чем возвращаемое этой функцией. Процедура New(TP) - резервирует фрагмент кучи для размещения переменной. TP - типизированный указатель (за одно обращение не более 65521байт). Функция MemAvail - возвращает размер (в байтах) общего свободного пространства кучи. Результат типа Longint. Функция Ofs(X) - возвращает значение типа Word, содержащее смещение адреса указанного объекта. X - выражение любого типа или имя процедуры. Функция Ptr(Seg, Ofs) - возвращает значение типа Pointer по заданному сегменту и смещению. Значение, возвращаемое функцией, совместимо с указателем любого типа. Процедура Release(Ptr) - освобождает участок кучи. Рtr - указатель любого типа, в котором сохранено процедурой Mark значение указателя кучи. Освобожденный участок кучи - от адреса в Ptr до конца. Одновременно уничтожается список свободных фрагментов, созданных по Dispose и FreeMem. Функция Seg(X) - возвращает значение типа Word, содержащее сегмент адреса указанного объекта. Функция SizeOf(X) - возвращает длину (в байтах) внутреннего представления указанного объекта. X - имя переменной, функции или типа. Проблема потерянных ссылок Работа с динамическими переменными через указатели требует большой тщательности и аккуратности при проектировании программ. В частности, следует стремиться освобождать выделенные области сразу же после того, как необходимость в них отпадает, иначе “засорение” памяти ненужными динамическими переменными может привести к быстрому ее исчерпанию. Кроме того, необходимо учитывать еще одну проблему, связанную с противоречием между стековым принципом размещения статических переменных и произвольным характером создания и уничтожения динамических переменных. Рассмотрим следующий схематический пример программы:
Program LostReference; Type PPerson = ^Person; Person = Record . . . . End;
Procedure GetPerson; Var Р: РРerson; Begin P:= New(PPerson); End;
Begin WriteLn(MemAvail); GetPerson; Writeln(MemAvail); End. Вызов New в процедуре GetPerson приводит к отведению памяти для динамической переменной типа Person. Указатель на эту переменную присваивается переменной Р. Рассмотрим ситуацию, возникающую после выхода из процедуры GetPerson. По правилам блочности все локальные переменные подпрограммы перестают существовать после ее завершения. В нашем случае исчезает локальная переменная Р. Но, с другой стороны, область памяти, отведенная в процессе работы GetPerson, продолжает существовать, так как освободить ее можно только явно, посредством процедуры Dispose. Таким образом, после выхода из GetPerson отсутствует какой бы то ни было доступ к динамической переменной, так как единственная "ниточка", связывавшая ее с программой - указатель Р - оказался потерянным при завершении GetPerson. Вывод на печать общего объема свободной памяти до и после работы GetPerson подтверждает потерю определенной области. При проектировании программ, интенсивно использующих динамическую память, следует с особой внимательностью относиться к данной проблеме, так как Turbo Pascal, как, впрочем, и многие другие языки программирования, не имеет встроенных средств борьбы с засорением памяти неиспользуемыми динамическими переменными. Во всяком случае нужно придерживаться правила, согласно которому при выходе из блока необходимо или освободить все созданные в нем динамические переменные, или сохранить каким-то образом ссылки на них (например, присвоив эти ссылки глобальным переменным). К описанной проблеме примыкает коллизия другого рода, заключающаяся в ситуации, когда некоторая область памяти освобождена, а в программе остался указатель на эту область. Рассмотрим следующий пример:
Var P: Integer;
Procedure X1; Var i: Integer; Begin i:= 12345; P:= @i; WriteLn(P^); { напечатает 12345 } End;
Procedure X2; Var j: Integer; Begin j:= 7777; WriteLn(P^); { напечатает 7777, а не 12345 } End;
Begin X1; X2; End; В этом примере глобальная ссылочная переменная Р первоначально (в процедуре X1) устанавливается на локальную переменную i. После завершения процедуры X2 переменная i исчезает, указатель Р “повисает”. Вызов процедуры Х2 приводит к тому, что на место, локальной переменной i, будет помещена локальная переменная j, и указатель Р теперь ссылается на нее, что подтверждает результат второго вызова WriteLn. Таким образом, смысл указателя Р зависит в данном случае не от семантики решаемой задачи, a от системных особенностей ее функционирования, что может привести к неожиданным эффектам. Аналогичные ситуации возможны при повторном использовании областей динамической памяти: если на освобожденную область остался указатель, то он может быть (по ошибке) использован после повторного выделения этой памяти для другой переменной, что опять- таки не будет "замечено" системой, но может сделать поведение программы непредсказуемым. Модули Стандартный Паскаль не предусматривает никаких механизмов раздельной компиляции частей программы с последующей их сборкой перед выполнением. Более того, последовательное проведение в жизнь принципа обязательного описания любого объекта перед его ис пользованием делает фактически невозможным разработку разнообразных библиотек прикладных программ. Точнее, такие библиотеки в рамках стандартного Паскаля могут существовать только в виде исходных текстов и программист должен сам включать в программу подчас весьма обширные тексты различных поддерживающих процедур, таких, как, например, процедуры матричной алгебры, численного интегрирования, математической статистики и т.п. Вполне понятно, поэтому стремление разработчиков коммерческих компиляторов Паскаля включать в язык средства, повышающие его модульность. Чаще всего таким средством является разрешение использовать внешние процедуры и функции, тела которых заменяются зарезервированным словом External. Разработчики Турбо-Паскаля пошли в этом направлении еще дальше, включив в язык механизм так называемых модулей. Модуль - это автономно компилируемая программная единица, включающая в себя различные компоненты раздела описаний (типы, константы, пе ременные, процедуры и функции) и, возможно, некоторые исполняемые операторы инициирующей части. По своей организации и характеру использования в программе модули Турбо-Паскаля близки к модулям- пакетам (Package) языка программирования Ада: в них также явным образом выделяется некоторая "видимая" интерфейсная часть, в которой сконцентрированы описания глобальных типов, констант и переменных, а кроме того, приводятся заголовки глобальных процедур и функций. Появление объектов в интерфейсной части делает их доступными для других модулей и основной программы. Тела процедур и функций располагаются в исполняемой части модуля, которая может быть скрыта от пользователя. Насколько сильно изменяются свойства языка Паскаль при введении механизма модулей, свидетельствует следующее замечание его автора Н.Вирта, сделанное им по поводу более позднего языка Модула-2: "Модули - самая важная черта, отличающая язык Модула-2 от его предшественника Паскаля". Кстати, из пяти основных отличий Модулы- 2 от Паскаля, сформулированных Н. Виртом, три: модули, средства программирования низшего уровня и процедурные типы - реализованы в Турбо-Паскале. Модули представляют собой прекрасный инструмент для разработки библиотек прикладных программ и мощное средство модульного программирования. Важной особенностью модулей является то обстоятельство, что компилятор Турбо-Паскаля размещает их про граммный код в отдельном сегменте памяти. Максимальная длина сегмента не может превышать 64 Кбайт, однако количество одновременно используемых модулей ограничивается лишь доступной памятью, что дает возможность создавать весьма крупные программы. Структура модулей. Модуль имеет следующую структуру:
Unit < имя >; Interface < интерфейсная часть > Implementation < исполняемая часть > [ Begin < инициирующая часть > ] End. Здесь: Unit - кодовое слово (англ. модуль); начинающее заголовок модуля; <имя> - имя модуля (правильный идентификатор); Interface - кодовое слово, начинающее интерфейсную часть модуля; Implementation - кодовое слово (англ. реализация); начинает исполняемую часть; Begin - кодовое слово, начинающее инициирующую часть; (часть модуля Begin < инициирующая часть > необязательна); End. - признак конца модуля. Таким образом, модуль состоит из заголовка и трех составных частей, любая из которых может быть пустой. Заголовок модуля и связь модулей друг с другом Заголовок модуля состоит из кодового слова Unit и следующего за ним имени модуля. Для правильной работы среды Турбо-Паскаля и возможности подключения средств, облегчающих разработку крупных программ, это имя должно совпадать, с именем дискового файла, в который помещается исходный текст модуля. Если, например, имеем заголовок Unit Global, то исходный текст соответствующего модуля должен размещаться в дисковом файле GLOBAL.PAS. Имя модуля служит для его связи с другими модулями и основной программой. Эта связь устанавливается специальным предложением Uses <список модулей> Здесь: Uses - кодовое слово (англ. использует); <список модулей> - список модулей, с которыми устанавливается связь; элементами списка являются имена модулей, отделяемые друг от друга запятыми, например: Uses Crt, Graph, Global; Если предложение Uses... используется, оно должно открывать раздел описаний основной программы или следовать сразу за кодовым словом Interface в модуле. Интерфейсная часть. Интерфейсная часть открывается кодовым словом Interface. В этой части содержатся объявления всех глобальных объектов модуля (типов, констант, переменных и подпрограмм), которые должны стать доступными основной программе и/или другим модулям. При объявлении глобальных блоков в интерфейсной части пишется только их заголовок, например:
Unit Cmplx; Interface Type Complex = Record Re: Real; Im: Real; End;
Procedure AddC (X, У: Complex; Var R: Complex);
Procedure MulC (X, У: Complex; Var Z : Complex); Если теперь в основной программе написать предложение Uses Cmplx; то в программе станут доступными тип Complex и две процедуры - Addc и Mulc из модуля Cmplx. Следует учесть, что все константы и переменные, объявленные в интерфейсной части модуля, равно как и глобальные объекты основной программы, помещаются компилятором Турбо-Паскаля в обилии сегмент данных (максимальная длина сегмента 65521 байт). Порядок появления различных разделов объявлений и их количество могут быть произвольными. Если в интерфейсной части объявляются внешние блоки или блоки в машинных кодах, их тела (т.е. зарезервированное слово External в первом случае и машинные коды вместе со словом Inline во втором) должны следовать сразу за их заголовками. В интерфейсной части модулей нельзя использовать опережающее описание. Исполняемая часть Исполняемая часть начинается кодовым словом Implementation и содержит тела процедур и функций, объявленных в интерфейсной части. В этой части могут также объявляться локальные для модуля объекты: вспомогательные типы, константы, переменные и блоки, а также метки, если они используются в инициирующей части. Ранее объявленные в интерфейсной части глобальные процедуры и функции должны описываться в той же последовательности, в какой появляются их заголовки в интерфейсной части. Описанию глобального блока в исполняемой части должен предшествовать заголовок, в котором разрешается опускать список формальных переменных (и тип результата для функции), так как они уже описаны в интерфейсной части. Но если заголовок блока приводится в полном виде, т.е. со списком формальных параметров и объявлением результата, он должен совпадать с заголовком, объявленным в интерфейсной части, например:
Unit Cmplx; Interface Type Complex = Record Re: Real; Im: Real; End; Procedure AddC (X, У: Complex; Var Z : Complex);
Implementation
Procedure AddC; Begin Z.Re:= Х.Rе + Y.Re; Z.Im:= X.Im + Y.Im; End;
End. Локальные переменные и константы, а также все программные коды, порожденные при компиляции модуля, помещаются в общий сегмент памяти. Инициирующая часть Инициирующая часть завершает модуль. Она может отсутствовать вместе с начинающим ее словом Begin или быть пустой - тогда за Begin сразу следует признак конца модуля (кодовое слово End и следующая за ним точка). В инициирующей части помещаются ис полняемые операторы, содержащие некоторый фрагмент программы. Эти операторы исполняются до передачи управления основной программе и обычно используются для подготовки ее работы. Например, в них могут инициироваться переменные, открываться нужные файлы, устанавливаться связь с другими ПЭВМ по коммуникационным каналам и т.п.:
Unit FileText; Interface
Procedure Print(S: String);
Implementation Var F: Text; Const Name = 'output.txt’;
Procedure Print; Begin WriteLn(F, S); End; { Начало инициирующей части } Begin Assign(F, Name); Rewrite(F); { Конец инициирующей части } End. Компиляция модулей В среде Турбо-Паскаля имеются средства, управляющие способами компиляции модулей и облегчающие разработку крупных программных проектов. В частности, определены три режима компиляции: Compile, Make и Build, Режимы отличаются только способом связи компилируемого модуля или компилируемой основной программы с другими модулями, объявленными в предложении Uses. При компиляции модуля или основной программы в режиме Compile все упоминающиеся в предложении Uses модули должны быть предварительно откомпилированы и результаты их компиляции должны быть помещены в одноименные файлы с расширением .TPU. Например, если в программе (в модуле) имеется предложение Uses Global, то на диске в каталоге, объявленном опцией Unit directories, уже должен находиться файл GLOBAL.TPU. Файл с расширением .TPU (от англ. Turbo-Pascal Unit) создается в результате компиляции модуля. В режиме MAKE компилятор проверяет наличие TPU-файлов для каждого объявленного модуля. Если какой-либо из файлов не обнаружен, система пытается отыскать одноименный файл с расширением .PAS, т.е. файл с исходным текстом модуля, и, если PAS- файл найден, приступает к его компиляции. Кроме того, в этом случае система следит за возможными изменениями исходного текста любого используемого модуля. Если внесены какие-либо изменения в PAS-файл (исходный текст модуля), то независимо от того, есть ли уже в каталоге соответствующий TPU-файл или нет, система осуществляет его компиляцию перед компиляцией основной программы. Более того, если изменения внесены в интерфейсную часгь модуля, то будут перекомпилированы также и все другие модули, обращающиеся к нему. Режим Make, таким образом, существенно облегчает процесс разработки крупных программ с множеством модулей: программист избавляется от необходимости следить за соответствием существующих TPU-файлов и их исходного текста, так как система делает это автоматически. В режиме Build существующие TPU-файлы игнорируются, и система пытается отыскать (и компилировать) соответствующий PAS-файл для каждого объявленного в Uses модуля. После компиляции в режиме Build программист может быть уверен в том, что учтены все сделанные им изменения в любом из модулей. Подключение модулей к основной программе и их возможная компиляция осуществляется в порядке их объявления в предложении Uses. При переходе к очередному модулю система предварительно отыскивает все модули, на которые он ссылается. Ссылки модулей друг на друга могут образовывать древовидную структуру любой сложности, однако запрещается явное или косвенное обращение модуля к самому себе. Доступ к объявленным в модуле объектам Пусть, например, создан модуль, реализующий арифметику комплексных чисел (напомню, что такая арифметика ни в стандартном Паскале, ни в Турбо-Паскале не предусмотрена). К сожалению, в Турбо-Паскале нельзя использовать функции, значения которых имели бы структурированный тип (запись, например), поэтому арифметика комплексных чисел реализуется четырьмя приведенными ниже процедурами.
Unit Cmplx; Interface Type Complex = Record Re: Real; Im: Real; End; Procedure AddC (X, У: Complex; Var Z ; Complex); Procedure SubC (X, У: Complex; Var Z: Complex); Procedure MulC (X, Y.: Complex; Var Z: Complex); Procedure DivC (X, У: Complex; Var Z: Complex);
Const С: Complex = (Re: 0.1; 1m: -1);
Implementation Procedure Addc; Begin Z.Re:= X.Re + Y.Re; Z.Im:= X.Im + Y.Im; End {Addc};
Procedure Subc; Begin Z.Re:= X.Re - Y.Re; Z.Im:= X.Im - Y.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 DivC; 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) / ZZ; End; End. Текст этого модуля нужно поместить в файл CMPLX.PAS. Теперь можно написать программу, приведенную в примере:
Program TestOfComplex; Uses Cmplx; Var A, B, С: Complex; Begin A.Re:= 1; A.Im:= 1; B.Re:= 1; B.Im:= 2; AddC (A, B, C); WriteLn (‘Сложение: ‘, C.Re:5:1, C.Im:5:1,’i’); SubC (A, B, C); WriteLn (‘Вычитание: ‘, C.Re:5:1, C.Im:5:1,’i’); MulC (A, B, C); WriteLn ('Умножение: ‘, C.Re:5:1, C.Im:5:1,’i’); DivC (A, B, C); WriteLn ('Деление: ', C.Re:5:1, C.Im:5:1,’i’); End. Как видим, программе стали доступны все объекты, объявленные в интерфейсной части. При необходимости мы можем переопределить любой из этих объектов, как это произошло, например, с объявленной в модуле типизированной константой С. Переопределение объекта означает, что вновь объявленный объект "закрывает" ранее определенный в блоке одноименный объект. При необходимости мы можем получить доступ к "закрытому" объекту. Для этого нужно перед именем объекта поставить имя модуля и точку. Так, оператор WriteLn(Cmplx.C.Re:5:1, Cmplx.C.Im:5:1,’i’); выведет на экран содержимое "закрытой" типизированной константы из примера. Стандартные модули В Турбо-Паскале имеется восемь стандартных модулей, в которых содержится большое число разнообразных типов, констант, процедур и функций. Этими модулями являются: System, Dos, Crt, Printer, Graph, Overlay, Turbo3 и Graph3. Модули Graph, Turbo3 и Graph3 содержатся в одноименных TPU-файлах, остальные входят в состав библиотечного файла TURBO.TPL. Лишь один модуль System подключается к любой Турбо-Паскалевой программе автоматически, все остальные становятся доступны только после указания их имен в списке, следующем за кодовым словом Uses. Ниже приводится краткая характеристика стандартных модулей. В модуль System входят все процедуры и функции стандартного Паскаля, а также встроенные процедуры и функции Турбо-Паскаля, которые не вошли в другие стандартные модули (например, Inc, Dec, Getdir и т.п.). Как уже говорилось, модуль System подключается к любой Турбо-Паскалевой программе независимо от, того, объявлен ли он в предложении Uses или нет. Модуль Printer упрощает вывод текстов на матричный принтер. В нем определяется файловая переменная Lst типа Text, которая связывается с логическим устройством Prn. После подключения модуля может быть выполнена, например, такая программа:
USES Printer; Begin WriteLn (Lst, ‘TEST’); {Выводит строку на принтер} End. В модуле Crt сосредоточены процедуры и функции, обеспечивающие управление текстовым режимом работы экрана. С помощью входящих в модуль блоков можно перемещать курсор в произвольную позицию экрана, менять цвет выводимых символов и окружающего их фона, создавать окна. Кроме того, в модуль включены также процедуры "сле пого" чтения клавиатуры и управления звуком. Модуль Graph содержит обширный набор типов, констант, процедур и функций для управления графическим режимом работы экрана. С помощью блоков, входящих в модуль Graph, можно создавать самые разнообразные графические изображения и выводить на экран текстовые надписи стандартными или разработанными программистом шрифтами. Программы модуля Graph после соответствующей настройки могут поддержать любой тип аппаратных графических средств (CGA, EGA, VGA). Настройка на имеющиеся в распоряжении программиста технические средства графики осуществляется специальными программами-драйверами, которые не входят в библиотечный файл TURBO.TPL, но поставляются вместе с ним. В модуле Dos собраны процедуры и функции, открывающие доступ Турбо-Паскалевым программам ко всем средствам дисковой операционной системы PC DOS (MS DOS). Блоки модуля Overlay понадобятся при разработке громоздких программ с перекрытиями (позволяет хранить в памяти только нужные части программы, а не требующиеся на данный момент удалять из памяти, сохраняя на диск, либо возвращать в память при необходимости). Как уже говорилось, Турбо-Паскаль обеспечивает создание программ, длина которых ограничивается лишь доступной памятью. Для большинства отечественных IBM-совместимых ПЭВМ доступная программе память составляет около 550 Кбайт (без инстру ментальных оболочек типа Norton Commander и без самой системы Турбо-Паскаль). Память такого размера достаточна для большинства прикладных задач, тем не менее, использование программ с перекрытиями снимает и это ограничение. Два библиотечных модуля Turbo3 и Graph3 введены для совместимости с ранней версией 3.0 системы Турбо-Паскаль. Ключи и директивы компилятора. Позволяют управлять процессами компиляции программы. Директива представляет собой комментарий со специальным синтаксисом: начинается с {$, далее пишется имя директивы (одна или несколько букв) и параметры. Турбо-Паскаль 7.0 допускает применение директив в любом месте программы, где допускается применение комментариев. В Турбо-Паскале 7.0 существует три основных вида директив: Директивы - переключатели. С их помощью, указывая символ (+) или (-), можно разрешать или запрещать тот или иной режим. Директивы с параметрами. Позволяют задавать компилятору параметры, такие как имена подключаемых файлов, размеры памяти, выделяемые под определённую задачу, и так далее. Условные директивы. Позволяют организовывать так называемую "условную компиляцию" частей исходного текста, благодаря чему скомпоновать программу определённым образом. Все директивы, кроме директив-переключателей, должны иметь по крайней мере один пробел между именем директивы и параметром. Чтобы не прописывать директивы компилятора в тексте программы можно воспользоваться пунктом "Options" меню ИПО и далее подпунктом "Compiler". Но при этом все изменения будут иметь силу при всех последующих компиляциях других программ. Однако, если указать в исходном тексте какие-либо директивы, противоречащие установленным в ИПО, то последние будут проигнорированы. Это справедливо как для командно- строчного компилятора, так и для встроенного в ИПО. Директивы-переключатели. Различаются по диапазону своего действия на глобальные и локальные: Глобальные - влияют на ход компиляции и выполнение всей программы, локальные - только на определённую часть программы. Глобальные директивы могут быть объявлены в разделе объявлений и соглашений прежде, чем будет использовано хотя бы одно из следующих зарезервированных слов: Uses, Label, Const, Type, Procedure, Function, Begin, а Локальные директивы могут быть объявлены в любом месте программы или программного модуля. При необходимости можно сгруппировать комментарии из нескольких директив, разделённых запятой: {$A+, B-}. Примечание: двойное нажатие комбинации Ctrl+O заставит редактор поместить в самое начало файла строки, содержащие текущую настройку среды в виде директив компилятора. Директивы: Выравнивание данных - (Word Align Data) {$A+} Тип глобальный. Делает возможным переключение между выравниванием на границу слова или байта. (Для 8086 выравнивание на границу слова игнорируется, однако для 80X86 выравнивание на границу слова означает более быстрое выполнение, поскольку адресация по всем элементам, имеющим размер в слово (чётная адресация) происходит за один цикл вместо двух. {$A+} не влияет: на переменные размером в байт, на поля структур или элементы массивов. Булевы вычисления (Complete Boolean Evaluation) {$B+} Тип локальный Может быть осуществлено два различных вида генерации кода для And и Or .При {$B-} генерируется код вычисления выражения, по короткой схеме (вычисления прекращаются сразу, как станет очевидным результат вычисления всего выражения). Пример:
Function Fun(X: Integer): Boolean; Begin X:= X + 1; Fun:= X > 10; End; . . . . . . . . . . . Begin X:= 0; If False And Fun(X) Then X:= 10; Информация для отладки (Debug Information) {$D+} Тип глобальный. Составляется таблица для отладчика, состоящая из N-строк для каждой процедуры, устанавливающая соответствующие адреса объектных кодов номерам строк исходного текста. Отладочная информация приводит к увеличению размера файла .TPU и требует дополнительного пространства при компиляции использующей модуль программы, но на размер и скорость не влияет. $D не будет работать в Турбо-Паскале 7.0 до тех пор пока не установить в меню или не задать параметр /V при запуске TPC.EXE . Эмуляция сопроцессора. (Emulation). {$E+} Тип глобальный. В процессе генерации кода нет библиотеки, реализующей функции сопроцессора программы. Дальний тип вызова (Force Far Calls). {$F-} Тип локальный, (Far) или (Near). Генерация кода для 80286 (286 Instruction). {$G-} Тип локальный. При {$G-} генерируются инструкции 8086, такие программы могут работать на любой машине. Если {$G+}, то будут использоваться дополнительные инструкции 80286, которые позволяют генерировать более эффективный код, но не работают на 8086 и 8087. (Например команды Shr и Shl - расширенные). Проверка ошибок ввода-вывода (I/O checking). {$I+} Тип локальный. Если {$I-}, то результат ввода-вывода может быть проанализирован с помощью функции IOResult. Информация о локальных идентификаторах. (Local Symbols) {$L+} Тип глобальный. Используется в основном для модулей, дает возможность проверить и модифицировать локальные переменные модуля. Увеличивает размер .TPU, на размер и скорость работы выполняемой программы не влияет. В Турбо-Паскале 7.0 директива не будет работать до тех пор, пока в меню не будет Debuginfo или для TPC.EXE ключ /V. Использование математического сопроцессора (8087/80287). {$N-} Тип глобальный. Позволяет осуществлять выбор генерируемых кодов для вычислений с плавающей запятой при “-“ реализуется программно, при ”+” - код для сопроцессора. Примечание: если отсутствует сопроцессор и возникла необходимость в использовании специальных вещественных типов, нужно использовать {$E+}. Использование оверлейных структур. (Overlays allowed). {$O-} Тип глобальный. Разрешает или запрещает генерацию оверлейного кода, то есть Турбо-Паскаль 7.0 может использовать модуль в качестве оверлейного, если он был скомпилирован с {$O+}. Задание данного режима компиляции не обязывает использовать данный модуль в качестве оверлейного. Директиву {$O+} почти всегда используют с {$F+}. Использование в качестве параметров массивов открытого типа (Open parameters). {$P-} Тип глобальный. Проверка переполнения при математических операциях (Overflow checking). {$Q-} Тип глобальный. Проверка границ. (Range-Checking). {$R-} Тип локальный. Рекомендуется использовать R+ при отладке, а затем отключать так как “+” увеличивает размер и замедляет работу программ. R+ осуществляет так же проверку виртуальных методов на состояние инициализации для экземпляра объекта, выполняющего вызов. Проверка переполнения стека (Stack Checking) {$S+} Тип локальный. При “+” компилятор генерирует вначале каждой процедуры или функции код, который проверяет, достаточно ли в стеке выделено места для локальных переменных. Использование типизированного адресного оператора @ (Typed @ operator). {$T-} Тип глобальный. Позволяет или запрещает использовать в тексте оператор @. Проверка параметров переменных строкового типа (Strict Var String). {$V+} Тип локальный. Управляет проверкой типа при передачи строк в качестве параметров-переменных. В состоянии “+” выполняется строгая проверка типа. Требуется, чтобы формальный и фактический параметр имели идентичные строковые типы; при “-” длины могут не совпадать. Расширенный синтаксис. (Extended Syntax). {$X-} Тип глобальный. При X+ вызов функции можно использовать как оператор, то есть результат функции может быть отброшен (функция может быть интерпретирована как процедура). Директивы с параметрами. Включение файла для компиляции (Include Directories). {$I < имя файла >} Тип локальный. Сообщает компилятору о необходимости включить в компиляцию названный файл. Текст файла вставляется сразу за директивой. Расширение по умолчанию .PAS. Турбо-Паскаль допускает одновременно не более 15 входных файлов. Значит допускается вложенность до 15-ти уровней. Так же включаемый файл не может указываться в середине раздела операторов. Это означает, что все операторы между Begin и End раздела операторов должны быть в одном файле. Компоновка объектного файла. (Object Directories). {$L < Имя-файла >} Тип локальный. Предписывает компилятору использовать указанный файл (.Obj) при компоновке компилируемой программы или модуля. $nbsp Директива {$L} используется для компоновки кода Ассемблера для подпрограмм, описанных External. Размеры выделяемой памяти (Memory Sizes). {$M < размер стека >, < размер динамической области >}. Тип глобальный. Размер-стека - целое число от 1024 до 65520. Для модуля размер- стека игнорируется (модуль использует стек основной программы). Размер динамической области - целое число от 0 до 65360. Директива {$M} при использовании в Unit не оказывает влияния на компиляцию. Компоновка оверлейного модуля. {$O < Имя модуля >} Тип локальный. Можно преобразовать обычный модуль в оверлейный. Игнорируется при попытке объявить её в теле какого-либо модуля. Директива должна указываться в тексте программы после слова Uses. Любой модуль, который указывается в качестве параметра, должен быть откомпилирован с директивами {$O+} и {$F+}. Условные директивы. Установить условие (Conditional Defines). {$ Define < условие >} Определяет условия, которые можно использовать в операторах условной компиляции. Установить условие - значит с помощью данной директивы ввести некоторое слово, которое затем будет управлять компиляцией какого- либо фрагмента программы. За условием не должно следовать ничего кроме }. Пример:
{$Define Debug} . . . . . . . . . . . . . . {$Ifdef Debug} Writeln(‘Отладка’); {$Endif} Можно ввести несколько условий одновременно, причём условия должны отделяться друг от друга точкой с запятой (в меню). Допускается использование развёрнутой формы операторов условной компиляции. Пример:
{$Ifdef Demo} Code:= 1; {$Else} Code:= 2; A:= ”Yes”; {$EndIf}. Операторы условной компиляции могут быть вложенными, что позволяет контролировать несколько условий одновременно. Пример:
{$Ifdef Var1} {$Ifdef Var2} A: = N; {$Else} A: = 0; {$Endif} {$Endif}. ФАЙЛЫ Основные определения. Файл - именованная область внешней памяти ПЭВМ (жесткого диска, гибкой дискеты, электронного "виртуального" диска), либо логическое устройство - потенциальный источник или приемник информации. Любой файл имеет три характерных особенности: Во-первых, у него есть имя, что дает возможность программе работать одновременно с несколькими файлами. Во-вторых, он содержит компоненты одного типа. Типом компонентов может быть любой тип Турбо-Паскаля, кроме файлов. Иными словами, нельзя создать «файл файлов». В-третьих, длина вновь создаваемого файла никак не оговаривается при его объявлении и ограничивается только емкостью устройств внешней памяти. Файловый тип или переменную файлового типа можно задать одним из трех способов:
< имя > = FILE OF <тип>; < имя > = ТЕХТ; < имя > = FILE; . Здесь < имя > - имя файлового типа или файловой переменной, FILE, OF, TЕХТ - кодовые слова (англ.: файл, из, текст), < тип > - любой тип Турбо-Паскаля, кроме файлов. Пример:
Type Man=record Name: string; LastName: string; End; Men=file of Man; Var Staff: Men; Numbers: file of real; Book: Text; A_File: File; В зависимости от способа объявления можно выделить три вида файлов: типизированные (задаются предложением FILE OF ...), текстовые (задаются предложением ТЕХТ), нетипизированные (задаются предложением FILE). Вид файла, вообще говоря, определяет способ хранения информации в файле. Однако в Паскале нет средств контроля вида ранее созданных файлов. При объявлении уже существующих файлов программист должен сам следить за соответствием вида объявления характеру файла. Доступ к файлам. Любой Турбо-Паскалевой программе доступны два предварительно объявленных файла со стандартными файловыми переменными: INPUT - для чтения данных с клавиатуры и OUTPUT - для вывода на экран. Стандартный Паскаль требует обязательного упоминания этих файлов в заголовке программы. В Турбо-Паскале это необязательно, вот почему заголовок программы можно опускать. Любые другие файлы, а также логические устройства становятся доступны программе только после выполнения особой процедуры открытия файла (логического устройства). Эта процедура заключается в связывании ранее объявленной файловой переменной с именем существующего или вновь создаваемого файла, а также в указании направления обмена информации: чтение из файла или запись в него. Связывание файловой переменной с именем файла осуществляется обращением к встроенной процедуре ASSIGN: ASSIGN(< ф.п. >, < имя файла или л.у. >); Здесь < ф.п. > - файловая переменная (правильный идентификатор, объявленный в программе как переменная файлового типа); < имя файла или л.у. > - текстовое выражение, содержащее имя файла или логическое устройство. Пример: Assign(Book,’PascalLecture.txt’); Если имя файла задается в виде пустой строки, например, ASSIGN(f, ‘’), то файловая переменная связывается со стандартным файлом INPUT или ОUТРUТ. Имена файлов Имя файла - это любое выражение строкового типа, которое строится по правилам определения имен в дисковой операционной ДОС) ПЭВМ: имя содержит до восьми разрещенных символов (разрешенные символы - это произвольные символы с кодами от 33 до 255, кроме символов пробел, точка, запятая, двоеточие, звездочка, знак вопроса, обратная косая черта, а также символ Забой - код 127 по стандарту АSCII); имя начинается с любого разрешенного символа; за именем может следовать расширение - последовательность до трех разрешенных символов; расширение, если оно есть, отделяется от имени точкой. Перед именем может ставиться так называемый путь к файлу - имя диска и/или имя текущего каталога и имена каталогов вышестоящих уровней. Имя диска содержит одну из латинских букв A..Z, после которой ставится двоеточие. Имена А: и В: относятся к дисковым накопителям на гибких дискетах, имена С:, D: и т.д.-к жестким дискам. Эти имена могут относиться также к одному или нескольким виртуальным дискам, созданным в операционной памяти ПЭВМ специальной командой RAMDRIVE в ходе выполнения файла автоустановки параметров ДОС СОNFIG.SYS. Если имя диска не указано, подразумевается устройство по умолчанию - то, которое было установлено в операционной системе перед началом работы программы. 3а именем диска может указываться имя каталога, содержащего файл. Если имени каталога предшествует обратная косая черта, то путь к файлу начинается из корневого каталога, если черты нет - из текущего каталога, установленного в системе по умолчанию. За именем каталога может следовать одно или несколько имен каталогов нижнего уровня. Каждому из них должна предшествовать обратная косая черта. Весь путь к файлу отделяется от имени файла обратной косой чертой. Максимальная длина имени вместе с путем - 79 символов. Пример: программа создает на диске C файл, содержащий возрастающие числа, закрывает файл, открывает его и полностью выводит его содержимое (в виде чисел).
Var Num: file of Byte; i:byte; Begin Assign(Num,'C:\Numbers.num'); Rewrite(Num); For i:=0 to 255 do Write(Num,i); Close(Num); Assign(Num,'C:\Numbers.num'); Reset(Num); Repeat Read(Num,i); Writeln(i); Until Eof(Num); End. Логические устройства Стандартные аппаратные средства ПЭВМ, такие как клавиатура, экран терминала, печатающее устройство (принтер) и коммуникационные каналы ввода-вывода, определяются в Турбо-Паскале специальными именами, которые называются логическими устройствами. Все они в Турбо-Паскале рассматриваются как потенциальные источники или приемники текстовой информации. СОN означает консоль - клавиатуру или экран терминала. Турбо- Паскаль устанавливает различие между этими физическими устройствами по направлению передачи данных: чтение данных возможно только с клавиатуры, а запись данных - только на экран. Таким образом, с помощью логического устройства СОN нельзя, например, прочитать данные с экрана ПЭВМ, хотя такая аппаратная возможность существует. Ввод с клавиатуры буферируется: символы по мере нажатия на клавиши помещаются в специальный строковый буфер, который передается программе только после нажатия на клавишу "Ввод". Сам символ "Ввод" (код 13) в буфер не помещается и программе не передается. Буферизация ввода обеспечивает возможность редактирования вводимой строки стандартными средствами ДОС. При вводе символов осуществляется их эхо-повтор на экране ПЭВМ. В Турбо-Паскале можно прочитать любой символ клавиатуры, в том числе и "Ввод", сразу после нажатия на соответствующую клавишу без эхо- повтора. PRN - логическое имя принтера. Если к ПЭВМ подключено несколько принтеров, доступ к ним осуществляется по логическим именам LРТ1, LРТ2 и LРТЗ. Имена РRN и LРТ1 первоначально синонимы. Средствами ДОС можно переназначить имя РRN на любое другое логическое устройство, способное принимать информацию. Стандартный библиотечный модуль PRINTER, входящий в библиотеку ТURВО.ТРL, объявляет имя файловой переменной LST и связывает его с логическим устройством LРТ1. Это дает возможность использовать простое обращение к принтеру. АUХ - логическое имя коммуникационного канала, который обычно используется для связи ПЭВМ с другими машинами. Коммуникационный канал может осуществлять и прием, и передачу данных, однако в Турбо-Паскалевой программе в каждый момент времени ему можно назначить только одну из этих функций. Как правило, в составе ПЭВМ имеются два коммуникационных канала, которым даются имена логических устройств СОМ1 и СОМ2. Первоначально имена АUХ и СОМ1 синонимы. NUL - логическое имя "пустого" устройства. Это устройство чаще всего используется в отладочном режиме и трактуется как устройство неограниченной емкости - приемник информации. При обращении к NUL как к источнику информации выдается признак конца файла ЕОF. Связывание логического устройства с файловой переменной осуществляется процедурой ASSIGN(< файловая переменая >, '< имя файла >'). Турбо-Паскаль никогда не связывает имена логических устройств с дисковыми файлами, и в этом смысле можно считать эти имена зарезервированными. Иными словами, нельзя, например, обратиться к дисковому файлу с именем РRN - Турбо-Паскаль всегда интерпретирует такой запрос как обращение к принтеру. Инициализация файла Инициировать файл означает указать для этого файла направление передачи данных. В Турбо-Паскале можно открыть файл только для чтения, только для записи, а также для чтения и записи информации одновременно. Для чтения файл инициируется с помощью процедуры: RESET (< ф.п. >); Здесь < ф.п. > - файловая переменная, связанная ранее процедурой ASSIGN() с уже существующим файлом или логическим устройством - источником информации. При выполнении этой процедуры дисковый файл или логическое устройство подготавливается к чтению информации. Внутренняя Турбо- Паскалевая переменная-указатель, связанная с этим файлом, указывает на начало файла, т.е. на компонент с порядковым номером 0. Если делается попытка инициировать чтение из несуществующего файла или логического устройства РRN, возникает ошибка периода исполнения, которая может быть сообщена программе с помощью встроенной функции IORESULT типа WORD, которая в этом случае имеет ненулевое значение. (Можно выяснить, существует ли требуемый файл на диске). Турбо-Паскаль допускает к типизированным файлам, открытым с помощью процедуры RESET (т.е. для чтения информации), обращаться с помощью процедуры WRITE (т.е. для записи информации). Такая возможность позволяет легко обновлять ранее созданные типизированные файлы и при необходимости расширять их. Встроенная процедура REWRITE(< ф.п. >) инициирует запись информации в файл или логическое устройство, связанное ранее с файловой переменной < ф.п. >. Процедурой REWRITE нельзя инициировать запись информации в ранее существовавший дисковый файл: при выполнении этой процедуры старый файл уничтожается и никаких сообщений об этом в программу не передается. Новый файл подготавливается к приему информации и его указатель устанавливается в нуль. Встроенная процедура АРРЕND(< ф.п. >) инициирует запись в ранее существовавший текстовый файл для его расширения - указатель файла устанавливается в его конец. Не следует забывать, что процедура АРРЕND применима только к текстовым файлам. Если текстовый файл ранее уже был открыт с помощью RESET или REWRITE, использование процедуры APPEND приведет к закрытию этого файла и открытию ею вновь, но уже для добавления записей. Процедуры и функции для работы с файлами. Ниже описываются процедуры и функции, которые можно использовать с файлами любого вида. Специфику работы с типизированными, текстовыми и нетипизированными файлами рассмотрим позднее. CLOSE(< ф.п. >) Закрывает файл, однако связь файловой переменной с именем файла, установленная ранее процедурой АSSIGN(), сохраняется. Поскольку связь файла с файловой переменной сохраняется, файл можно повторно открыть без дополнительного использования процедуры ASSIGN. RENAME (< ф.п. >, < новое имя >) Переименовывает файл < ф.п. >. Перед выполнением процедуры необходимо закрыть файл, если он ранее был открыт. ERASE(< ф.п. >). Уничтожает файл < ф.п. > FLUSH(< ф.п. >). Очищает внутренний буфер файла и, таким образом, гарантирует сохранение всех последних изменений файла на диске. Любое обращение к файлу в Турбо-Паскале осуществляется через некоторый внутренний буфер, что необходимо для согласования bmsrpemmecn представления файлового компонента - записи с принятым в ДОС форматом хранения данных на диске. В ходе выполнения процедуры FLUSH все новые записи будут действительно записаны на диск. Процедура игнорируется, если файл был инициирован для чтения процедурой RESET. Функция ЕОF(< ф.п. >): BOOLEAN. Логическая функция, тестирующая конец файла. Возвращает ТRUE, если файловый указатель стоит в конце файа. При записи это означает, что очередной компонент будет добав лен в конец файла, при чтении - что файл исчерпан. MKDIR(< имя каталога >). Создает новый каталог на указанном диске. Имя уже существующего каталога не может быть последним именем в пути, т.е. именем вновь создаваемого каталога. ДРУГИЕ ВОЗМОЖНОСТИ ТУРБО-ПАСКАЛЯ Внешние процедуры (функции) С помощью внешних процедур (функций) можно вызывать из Турбо- Паскалевой программы процедуры или функции, написанные на языке ассемблера и других языках. Машинно-ориентированный язык ассемблера предоставляет квалифицированному программисту богатейшие возможности использования всех особенностей архитектуры ПЭВМ. Ассемблерные программы выполняются значительно быстрее и занимают меньший объем памяти, чем Турбо-Паскалевые программы, однако низкий уровень языка существенно снижает производительность труда программиста и резко усложняет отладку программ, Как правило, на языке ассемблера пишутся сравнительно небольшие фрагменты программ, учитывающие особенности архитектуры ПЭВМ, которые невозможно использовать в Турбо- Паскалевой программе. Внешняя процедура (функция) в Турбо-Паскалевой программе объявляется заголовком, за которым следует зарезервированное слово ЕХТЕRNАL, например:
Function LoCase(ch: char): char; external; Procedure Swapping(Var a, b; N: word); externa1; Как видим, тело внешней процедуры (функции) отсутствует его заменяет кодовое слово ЕХТЕRNАL. Для подключения ассемблерной программы необходимо предварительно ее откомпилировать и получить OBJ - файл с перемещаемым кодом программы. Непосредственно перед описанием внешней процедуры (функции) в основной программе вставляется директива компилятора {$L < имя файла >}, где < имя файла > - имя OBJ - файла. Диск и каталог, в котором следует искать этот файл, если он не обнаружен в текущем каталоге, указывается опцией "OPTIONS/DIRECTORIES/OBJECT DIRECTORIES" ИПО. Разумеется, в рамках этой книги совершенно невозможно рассмотреть методику разработки программ на языке ассемблера - это предмет самостоятельной книги. Тем не менее, следует дать один совет. Прежде чем браться за разработку ассемблерной процедуры, тщательно взвесьте специфические потребности разрабатываемой программы и возможности Турбо-Паскаля: может оказаться, что, оставаясь только в рамках Турбо-Паскаля, Вы решите задачу намного проще и быстрее, хотя, разумеется, Ваша программа и не будет оптимальной по скорости вычислений и расходуемой памяти. Замечено, что производительность труда программиста почти не зависит от языка и составляет 10...20 операторов отлаженной программы в день. Один оператор Турбо- Паскаля реализуется десятками машинных команд, вот почему использование языков высокого уровня, к которым относится Турбо- Паскаль, значительно ускоряет разработку программ. Перед передачей управления внешней процедуре (функции) Турбо- Паскалевая программа заталкивает параметры обращения в программный стек в том порядке, как они перечислены в заголовке процедуры (функции). Ассемблерная процедура должна сохранить регистры BP, SP, SS и DS центрального процессора в самом начале своей работы и восстановить содержимое этих регистров перед возвратом управления в Турбо-Паскалевую программу. Остальные регистры можно не сохранять и соответственно не восстанавливать. Параметры могут передаваться по ссылке или по значению. Если параметр передается по ссылке, в стек заталкивается указатель, содержащий абсолютный адрес параметра; если по значению - в стек заталкивается сам параметр, точнее - его значение. Параметры, объявленные в заголовке с предшествующим словом VAR, всегда передаются по ссылке. Параметры- значения могут передаваться по ссылке или по значению, в зависимости от длины внутреннего представления соответствующею параметра. В общем случае используется следующее правило: если длина внутреннего представления параметра-значения составляет 1, 2 или 4 байта, само значение параметра заталкивается в стек. Точно так же через стек передаются и все вещественные данные длиной в 4, 6, 8 и 10 байт (в версии 4.0 эти данные передаются через стек сопроцессора 8087/80287, в версии 5.0 - через стек центрального процессора 8086/80286). Во всех остальных случаях, если длина внутреннего представления больше 4 байт, соответствующий параметр передается по ссылке. Ассемблерные функции должны возвращать результат с помощью регистров центрального процессора или сопроцессора в зависимости от длины внутреннего представления результата по следующим правилам: длиной в 1 байт - в регистре АL; длиной в 2 байта - в регистре АХ; длиной в 4 байта - в регистрах DХ:АХ (старшее слово в DХ); тип REAL (б байт) - в регистрах DХ:ВХ:АХ; типы SINGLE, DOUBLE, EXTENDED и COMP - через стек сопроцессора 8087/80287; указатели - в регистрах DХ:АХ (сегмент в DХ); строки возвращаются по ссылке: адрес начала строки помещается в DХ:АХ (сегмент в DХ). Все ассемблерные процедуры должны размещаться в сегменте с именем СОDЕ, а имена процедур и функций должны быть объявлены директивой PUBLIC. Локальные переменные необходимо размещать в сегменте с именем DАТА. Все имена, объявленные в интерфейсной части модулей Турбо-Паскалевой программы, становятся доступны ассемблерной процедуре (функции) после их объявления директивой ЕХТRN. Использование встроенных машинных кодов. В Турбо-Паскале имеется возможность включения в программу фрагментов, написанных непосредственно в машинных кодах. Для этого используется зарезервированное слово INLINE, за которым в круглых скобках следует один или несколько элементов машинного кода, разделяемых косыми чертами. Элемент кода, в свою очередь, строится из одного или более элементов данных, разделенных знаками "+" или "-". В качестве элемента данных может использоваться целая константа, идентификатор (переменной, константы или функции) или ссылка на счетчик адреса "*". Каждый элемент данных вызывает генерацию 1 или 2 байт кода программы. Значение этого кода получается сложением или вычитанием элементов данных в соответствии с разделяющим их знаком. Значением идентификатора переменной, константы, функции является адрес соответствующего объекта, значением ссылки на счетчик адреса является тот адрес, по которому будет размещаться следующий байт кода. Элемент кода будет генерировать 1 байт кода, если этот элемент состоит только из целых констант и значение результата не превышает 1 байта, т.е. находится в диапазоне от 0 до 255. Если значение превышает 255 или элемент кода содержит ссылку на счетчик адреса, генерируются 2 байта. Знаки ">" и "<" могут использоваться для отмены автоматического выбора размера генерируемого кода. Если элемент кода начинается со знака "<", в код заносится только один младший байт, даже если само значение занимает 2 байта. Наоборот, если элемент начинается со знака ">", в код заносится 2 байта (старший байт может оказаться нулевым). Значением идентификатора, как уже говорилось, является смещение соответствующего объекта. Если переменная. глобальная, смещение задается относительно сегмента данных, хранящегося в регистре DS, если переменная локальная, - относительно сегмента стека (регистр SР). Базовым сегментом типизированной константы является сегмент кода (регистр СS). В следующем примере приводятся две короткие процедуры, с помощью которых можно вывести данные через любой порт ПЭВМ:
Function InPort(Port: Word): Word; Var pp: Word; cc: Char; Begin pp:= port; in1ine( {assembler code: } $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); in1ine( $8a/$86/cc/ { MOV AX,cc[bp] } $8b/$96/pp/ { MOV DX,pp[bp] } $EE { OUT DX,AX } ); END; Операторы INLINE могут произвольным образом смешиваться с другими операторами Турбо-Паскаля, однако при выходе из процедуры (функции) содержимое регистров BP, SP, DS и SS должно быть таким же, как и при входе в нее. Обращение к функциям операционной системы Турбо - Паскаль предоставляет программисту практически неограниченные возможности использования любых функций стандартной операционной системы PC DOS (MS DOS). При внимательном анализе материала этой книги Вы, очевидно, заметите, что большую часть составляет описание многочисленных библиотечных процедур и функций. Собственно язык Паскаль весьма прост и лаконичен, что, по мнению многих специалистов, и послужило одной из причин его широкого распространения. Значительная же часть библиотечных процедур и функций является по существу своеобразным интерфейсом между языковыми средствами Турбо-Паскаля и функциями операционной системы. Разумеется, можно только приветствовать усилия разработчиков Турбо-Паскаля по созданию мощных библиотек TURBO.TPL и GRAPH.TPU, однако ясно что таким способом невозможно запрограммировать все допустимые обращения к средствам ДОС. Вот почему в Турбо-Паскаль включены две процедуры, с помощью которых программист может сам сформировать вызов той или иной функции ДОС. Следует учесть, что единственным механизмом обращения к функциям операционной системы является инициация программного прерывания. Прерывание - это особое состояние вычислительного процесса. В момент прерывания нарушается нормальный порядок выполнения команд программы, и управление передается специальной процедуре, которая входит в состав ДОС и называется процедурой обработки прерывания. Каждое прерывание характеризуется в рамках ДОС порядковым номером и связано со своей процедурой обработки. В архитектуре центрального процессора ПЭВМ предусмотрены прерывания двух типов: аппаратные и программные. Аппаратные прерывания создаются схемами контроля и управления ПЭВМ и сигнализируют операционной системе о переходе какого-либо устройства в новое состояние или о возникновении неисправности. Программные прерывания инициируются при выполнении одной из двух специальных команд (INТ или INТО) и служат для обращения к средствам ДОС. Всего в ДОС имеется около 40 программных прерываний, каждое из которых может активизировать одну или несколько функций ДОС. Одно из прерываний - с номером 33 ($21 - шестнадцатиричное число) обеспечивает доступ к 85 функциям, а всего в ДОС имеется более 200 разнообразных функций. Описываемые ниже процедуры входят в состав библиотечного модуля DOS и становятся доступными после объявления USES DOS. При возникновении программного прерывания в большинстве случаев необходимо передать процедуре обработки прерывания некоторые параметры, в которых конкретизируется запрос нужной функции. Эти параметры, а также выходная информация передаются от программы к процедуре и обратно через регистры центрального процессора. В модуле DOS для этих целей определен специальный тип:
Type Registers = record Case integer of 0:(AX, BX, CX, 0X, BP, SI, DI, DS, ES, Flags: word); 1:(AL, AH, BL, BH, CL, CH, DL, DH: byte) end; Как видим, этот тип имитирует регистры центрального процессора и дает возможность обращаться к ним как к 16-битным или 8-битным регистрам. С помощью процедуры INTR инициируется программное прерывание с требуемым номером; формат обращения: INTR(< N >, < регистры >) Здесь < N > - выражение типа ВYТЕ, означающее номер прерывания; < регистры > - переменная типа REGISTERS, в которой процедуре обработки прерывания передается содержимое регистров и возвращается выходная информация. Так, прерывание с номером 18 ($12) возвращает в регистре AX объем оперативной памяти ПЭВМ. Пример программы выводящей на экран сообщение об этом объеме:
Program IntrDem; Uses DOS; Var r: registers; Begin Intr($12, r); writeln('Объем памяти = ', r.АХ, ' Кбайт') END. Процедура MSDOS. Инициирует прерывание с номером 33 ($21); формат обращения: MSDOS(< регистры >); Здесь < регистры > - переменная типа REGISTERS, содержащая значения регистров на входе и выходе процедуры обработки прерывания. Программное прерывание с номером 33 ($21) стоит особняком: как уже говорилось, оно дает доступ к большому количеству функций ДОС (этим прерыванием вызывается 85 функций). Рассматриваемая процедура полностью эквивалентна вызову процедуры INTR c номером прерывания 33. Программа примера выведет на экран версию операционной системы:
Program MsDosDemo; Uses DOS; Var R: registers; Begin r.AH: = $30; MsDos(r); write1n ('Версия операционной системы: г.АL, '.', г.АН) END. Поддержка процедур обработки прерываний. При написании процедур обработки прерываний существенными являются два обстоятельства. Во-первых, процедура обработки прерывания не должна искажать работу прерванной программы. Для этого необходимо сначала сохранить регистры центрального процессора, а перед выходом из процедуры - восстановить их. Во- вторых, процедура должна строиться по принципу реентерабельности (повторной входимости): ее работа может быть прервана в любой момент другими прерываниями и ДОС может обратиться к соответствующей функции до завершения обработки предыдущего прерывания. Турбо - Паскаль предоставляет программисту возможность написать процедуру обработки прерывания на языке высокого уровня, хотя обычно такие процедуры пишутся на языке ассемблера. Турбо-Паскалевая процедура обработки прерывания должна начинаться зарезервированным словом INTERRUPT (англ. прерывание), например:
Procedure IntProc(Flags, CS, IP, AX, BX, CX, DX, SI, DF, 0S, ES, BP: word); inerrupt; begin ....... end; Список формальных параметров должен быть именно таким, как в примере: через эти параметры вызова все регистры прерванной программы становятся доступны процедуре обработки прерывания. Кодовое слово INTERRUPT приводит к генерации специальных машинных кодов, обеспечивающих заталкивание регистров в стек при входе в процедуру и извлечение их из стека перед выходом из нее.
При входе в процедуру: push ax push bx push cx push dx push si push di push ds push es push bp mov bp,si sub sp,LocalSize mov ax,SEG DATA mov ds,ax
При выходе из процедуры: mov sp,bp pop bp pop es pop ds pop di pop si pop dx pop cx pop bx pop ax irep В самой процедуре обработки прерывания не рекомендуется обращение к другим функциям ДОС, так как некоторые из них, в том числе все функции ввода-вывода, нереентерабельны. Для связи с любыми процедурами прерываний, а следовательно и с процедурами, написанными программистом, используются векторы прерываний - четырехбайтные абсолютные адреса точек входа в эти процедуры. Векторы прерываний располагаются в младших адресах оперативной памяти, начиная с нулевого адреса: прерывание номер 0 - по адресу 0, номер 1 - по адресу 1 * 4 = 4, номер N - по адресу N*4. С помощью следующих двух процедур программист может прочитать содержимое любого вектора или установить его новое значение. Процедура GETINTVECT. Возвращает вектор прерывания с указанным номером; формат обращения GETINTVECT(< N >, < вектор >> ) Здесь < N > - выражение типа ВYТЕ, содержащее номер прерываиия; < вектор > -переменная типа РOINTER, в которой возвращается адрес точки входа в процедуру обработки прерывания. Пример: программа выводит на экран содержимое всех ненулевых векторов прерываний.
Uses DOS; var i: byte; p: pointer; Begin for i:= 0 to 255 do Begin GetIntVec(i, p); if (Seg(p^) <> 0) or (Ofs(p^) <> 0) then write1n('N = ', i:3, 'Seg = ', Seg(p^):5, 'Ofs =', Ofs(p^):5); End; End. Процедура SETINTVEC. Устанавливает новое значение вектора прерывания; формат обращения SETINTVEC(< N >>, < адрес >); Здесь < адрес > - выражение типа РOINTER , в котором задается адрес точки входа в процедуру обработки прерывания. При нормальном завершении Турбо-Паскалевой программы она выгружается из памяти, что делает невозможным разработку резидентных в памяти процедур обработки прерываний. Вы можете прекратить работу программы и оставить ее в памяти, если воспользуетесь процедурой КЕЕР. Процедура КЕЕР. Завершает работу программы и оставляет ее в памяти; формат обращения КЕЕР(< код >) < код > - выражение типа WORD, в котором содержится код завершения программы. Код завершения представляет собой фактически единственный механизм передачи сообщений от запущенной программы к программе, которая ее запустила. Он может быть проанализирован в вызывающей программе с помощью функции DOSEXITCODE. Функция DOSEXITCODЕ. Возвращает значение типа WORD - код завершения подчиненной программы. Запуск внешних программ. Из Турбо-Паскалевой программы можно запустить любую другую готовую к работе программу. Для этого используется процедура ЕХЕС из библиотечного модуля DOS; формат обращения ЕХЕС(< имя >, < параметры >); Здесь < имя > - выражение типа STRING, в котором задается имя файла с вызываемой программой; < параметры > - выражение типа STRING, в котором передаются параметры вызова. Имени запускаемой программы должен предшествовать путь к файлу. Параметры передаются запускаемой программе в виде текстовой строки и могут быть проанализированы ею с помощью двух следующих функций. Функция PARAMCOUNT. Возвращает общее количество параметров вызова программы (значение типа WORD). Обращение PARAMCOUNT Параметры вызова обычно следуют в командной строке ДОС сразу за именем вызываемой программы и отделяются от этого имени и друг от друга пробелами, например: С:\ТURBO MyProg.Рas C:\SIAM A:\Sistem.Sia Здесь MyProg.Pas и A:\Sistem.Sia - параметры, передаваемые программам TURBO и SIAM. При вызове программы непосредственно из среды Турбо-Паскаля ей можно передать параметры с помощью опции OPTIONS/PARAMETERS Функция PARAMSTR Возвращает значение типа STRING, соответствующее нужному параметру вызова, формат обращения PARAMSTR(< N >) Здесь < N > - выражение типа WORD, задающее порядковый номер параметра. Использование процедуры ЕХЕС имеет ряд специфических особенностей. Прежде всего необходимо отметить, что сама Турбо- Паскалевая вызывающая программа остается резидентной в памяти, поэтому она не должна занимать всю оперативную память. Объем выделяемой программе памяти регулируется опцией OPTIONS/COMPILER/MEMORY SIZE или директивой компилятора {$M . . }. По умолчанию параметры этой опции таковы (0 и 655360 байт соответственно), что Турбо-Паскалевая программа занимает весь доступный объем памяти и вызываемая программа не будет загружена. Полезно включить в текст вызывающей программы директиву компилятора, в которой изменяются принятые по умолчанию размеры памяти. Это можно сделать, например, так: {$М 2048, 0, 0} Такая директива ограничивает используемую программой область стека объемом 2 Кбайт и исключает возможность использования в ней динамической памяти. Разумеется, Вы можете установить и другие значения параметров в этой директиве. Далее, специфические особенности исполнения Турбо-Паскалевых программ требуют изменения стандартных значений некоторых векторов прерываний. К ним относятся векторы со следующими шестнадцатеричными номерами: $00, $02, $18, $23, $24, $34, $35, $36, $37, $38, $39, $3А, $3В, $3С, $3D, $3Е, $3F, $75. Начальные значения этих векторов сохраняются в восемнадцати переменных с именами SAVEINTXX из библиотечного модуля SYSTEM, где ХХ -шестнадцатеричный номер прерывания. Поэтому непосредственно перед запуском внешней программы и сразу после возврата из нее рекомендуется вызвать библиотечную процедуру без параметров SWAPVECTORS, которая обменивает содержимое векторов прерывания и перечисленных переменных. Пример: программа воспринимает с клавиатуры любую команду ДОС, затем вызывает командный процессор СОММАND.СОМ операционной системы и передает ему эту команду.
Program ExecDemo; {$M 1024, 0, 0} Uses DOS; Var st: string [79]; Begin Write(‘Введите команду DOS:’); Readln(st); if st <> '' then Begin st:= 'C:\'+st,; SwapVectors; Exec(GetEnv('COMSPEC'), st); SwapVectors; End; End. Обратите внимание: для указания файла СОММАND.СОМ и пути к нему использовано обращение к библиотечной функции GETENV, с помощью которой можно получить параметры настройки операционной системы. В частности, параметр СОМSPЕС определяет спецификацию файла, содержащего командный процессор. С помощью следующей несложной программы можно вывести на экран ПЭВМ список всех параметров настройки ДОС.
Program EnvParDemo; Uses DOS; Var i: integer; Begin For i:= 0 To EnvCount Do Writeln(EnvStr(i)); End. Функция ENVCOUNT. Возвращает значение типа INTEGER, в котором содержится общее количество установленных в ДОС параметров. Обращение ENVCOUNT. Функция ENVSTR. Возвращает значение типа STRING, содержащее имя и значение нужного параметра настройки операционной системы; формат обращения ENVSTR(< N >) Здесь < N > - выражение типа INTEGER, в котором указывается номер параметра. Эта функция возвращает строку вида NАМЕ = VАLUЕ, где NАМЕ - имя, а VALUE - значение соответствующего параметра настройки. Функция GETENV возвращает значение типа STRING, в котором содержится параметр настройки ДОС; формат обращения GETENV (< имя >) Здесь < имя > - выражение типа STRING, определяющее имя параметра. Эта функция имеет параметр обращения NАМЕ и возвращает значение VALUE (см. функцию ENVSTR). Оверлей Как уже говорилось в, максимальный размер модуля не может превышать 64 Кбайт, однако количество модулей не ограничено, что дает возможность разрабатывать весьма крупные программы, занимающие, например, всю доступную оперативную память ПЭВМ (приблизительно 580 Кбайт). Однако в некоторых случаях и этот объем может оказаться недостаточным. Турбо-Паскаль предоставляет в распоряжение программиста простой и достаточно эффективный механизм оверлея, с помощью которого Вы сможете создавать программы практически неограниченной длины (следует оговориться, что речь идет только о длине кода программ; два важных размера - длина стека данных и программного стека - в Турбо-Паскале не может превышать 64 Кбайт независимо от структуры программы). Оверлей - это такой способ использования оперативной памяти, при котором в один и тот же участок памяти, называемый оверлейным буфером, попеременно по мере надобности загружаются различные оверлейные (перекрывающиеся) модули. При этом все оверлейные модули в готовом к работе виде хранятся на диске, а в оперативной памяти в каждый момент находится лишь один активный модуль и, возможно, небольшое число других неактивных модулей. Пусть, например, программа состоит из главной части МАIN и двух модулей А и В. Пусть также LМ, LА и LВ - длина соответственно главной части и обоих модулей, причем LA > LВ. Тогда неоверлейная программа займет в памяти LМ + LA + LВ байт, в то время как оверлейная программа лишь LМ + LА. При исполнении оверлейной программы в память первоначально загружается главная часть и один из модулей, например А. Если в процессе исполнения программы встретится обращение к модулю В, программа приостановит свою работу, с диска в оверлейный буфер будет загружен модуль В (модуль А при этом будет частично уничтожен), после чего программа продолжит свою работу. Если в дальнейшем встретится обращение к А, точно таким же образом будет загружен модуль А, причем загрузка нужных модулей в оверлейный буфер осуществляется автоматически и программисту не нужно об этом заботиться. Описанный механизм выявляет главное преимущество оверлейной структуры: объем памяти, занимаемой оверлейной программой, определяется длиной главной части н наибольшего из перекрывающихся модулей, в то время как при неоверлейной структуре в этот объем входит суммарная длина всех модулей. Чем больше в программе оверлейных модулей и чем меньше длина наибольшего из них, тем больший выигрыш в памяти дает оверлейная структура. Однако совершенно очевиден и главный недостаток таких структур: на каждую загрузку оверлейного модуля с диска в оверлейный буфер требуется дополнительное время, поэтому оверлейная программа будет в общем случае исполняться с меньшей скоростью. Работа оверлейных программ обеспечивается с помощью процедур, и функций библиотечного модуля OVERLAY, входящего в библиотечный файл TURBO.TPL. Оверлейные программы нужно создавать в такой последовательности: Вначале необходимо выделить главную часть программы и разбить оставшуюся часть на несколько модулей. Отметим, что никаких дополнительных ограничений на модули по сравнению с описанными в, за одним исключением: в оверлейных модулях нельзя использовать процедуры обработки прерываний. Желательно продумать состав модулей таким образом, чтобы минимизировать количество их перезагрузок в буфер в процессе исполнения программы. В главной части программы необходимо указать с помощью директив компилятора вида {$0 < имя >} те модули, которые будут оверлейными, например:
Program Main; Uses CRT, DOS, Graph, Overlay, UnitA, UnitB; {$O DOS} {$O UNITA} {$O UNITB} Учтите, что из всех стандартных библиотечных модулей только модуль DOS может быть оверлейным, остальные же модули (CRT, Graph, Printer и т.д.) не могут объявляться оверлейными. Необходимо предусмотреть перед первым по логике работы программы обращением к какому-либо оверлейному модулю вызов процедуры инициализации оверлея OVERINIT. Здесь же, если это необходимо, следует установить размер оверлейного буфера и указать возможность использования расширенной памяти (см. ниже). Наконец, в начале главной программы и каждого оверлейного модуля необходимо поместить директивы компилятора {$O+} и {$F+} или установить опции OPTIONS/COMPILE/FORCE FAR CALLS и OPTIONS/COMPILE/OVERLAYS ALLOWED в состояние ОN, после чего следует откомпилировать программу на диск. Программа готова к работе. Необходимо использовать дальнюю модель вызова - это обязательное условие. Директива {$O+}, строго говоря, не является обязательной. Если она указана, то при вызове любой процедуры или функции программа будет помещать все фактические параметры обращения в резидентную (неоверлейную) часть памяти, что позволяет из одного оверлейного модуля вызывать процедуры и функции любого другого оверлейного же модуля. Процедура OVRINIT. Инициализирует оверлейный файл; формат обращения OVRINIT(< имя >) Здесь < имя > - выражение типа STRING, означающее имя файла с оверлейной частью программы. При компиляции оверлейной программы создается специальный файл с именем, совпадающим с именем главной программы, и расширением .OVR. В этот файл компилятор помещает все оверлейные модули, из него же эти модули будут загружаться в оверлейный буфер в процессе исполнения программы. Файл с оверлейной частью программы должен размещаться в том же каталоге, что и файл с главной частью (с расширением .ЕХЕ). Пример: пусть файл с главной частью программы называется MAIN.РАS, в программе используются два оверлейных модуля, помещаемые в файлы UNITA.PAS и UNITB.PAS.
Program OverlayDemo; {Текст главной программы нужно поместить в файл МAIN.РАS.} {$F+,0+} Uses Over1ay, UnitA, UnitB; {$0 UnitA} {$0 UnitB} Begin OvrInit('MAIN.OVR'); SubA End. {---------------------------------------------------------------}
UNIT UnitA; {Текст модуля нужно поместить в файл UNITA.PAS} {$F+,0+} Interface Uses UnitB; Procedure SubA; Implementation Procedure SubA; const st = 'Работает модуль’; Begin Writeln(st, 'A'); SubB(st); End; End. {---------------------------------------------------------------} Unit UnitB; {Текст модуля нумно поместить в файл UNITB.PAS.} {$F+,0+} Interface Procedure SubB(s: string); Implementation Procedure SubB; Begin Writeln(s, 'B'); End; End. 0бычно размер оверлейного буфера автоматически определяется таким, что в нем может разместиться самый крупный из всех оверлейных модулей. Программист может увеличить размер буфера. Тогда при загрузке в буфер очередного модуля программа проверит, достаточно ли в буфере свободного места, и, если места достаточно, загрузит новый модуль сразу за старым, который таким образом не будет уничтожен. Такой механизм способствует минимизации потерь времени на перезагрузку модулей. Если установлен очень большой размер буфера, то в нем, возможно, смогут разместиться все оверлейные модули и потери времени будут сведены к нулю, однако в этом случае оверлейная структура становится просто ненужной. Процедура OVRSETBUF. Устанавливает больший, чем по умолчанию, размер оверлейного буфера; формат обращения OVRSETBUF(< длина >) Здесь < длина > - выражение типа LONGINT, определяющее новую длину буфера. Новая длина задается в байтах и не может быть меньше той, что устанавливает сама система автоматически. Расширение буфера идет за счет соответствующего уменьшения доступной динамической памяти, поэтому к моменту вызова этой процедуры куча должна быть пустой. Функция OVRGETBUF. Возвращает значение типа LONGINT, содержащее текущий размер кучи. Обращение OVRGETBUF. Если Ваша ЭВМ имеет расширенную память (общий объем памяти свыше 1024 Кбайт), Вы можете использовать эту память для размещения в ней оверлейного файла .ОVR. Поскольку время доступа к расширенной памяти значительно меньше времени чтения с диска, такое размещение увеличивает скорость исполнения оверлейной программы. Процедура OVRINITEMS обеспечивает использование расширенной памяти. Обращение OVRINITEMS При обращении к этой процедуре программа прежде всего проверит, подключена ли к Вашей ЭВМ ЕМS-память (от англ. Expanded Memory Specification - расширенная память,) нужного для размещения оверлейной части объема. Если это так, то оверлейный файл будет считан в расширенную память, сам файл будет закрыт и программа будет считывать оверлейные модули из этой памяти. Если же расширенная память отсутствует или ее объем недостаточен для размещения оверлейного файла, обращение к процедуре игнорируется и программа будет считывать оверлейные модули с диска. Прямое обращение к памяти и портам ввода-вывода В Турбо-Паскале имеется пять предварительно объявленных массивов: МЕМ, МЕМW, МЕМL, РОRТ и РОRТW. Первые три обеспечивают доступ к любому участку оперативной памяти по абсолютному адресу, два другие - доступ к портам ввода-вывода. Компонентами массива МЕМ являются данные типа ВYТЕ, массива МЕМW - типа WORD, массива МЕМL - типа LONGINT. Обращение к элементам этих массивов, т.е. их индексация, имеет специальный вид: каждый индекс представляет собой абсолютный адрес и состоит из двух выражений типа WORD; первое дает сегментную часть адреса, второе - смещение; выражения разделяются двоеточием:
Mem[$0000:$1000]:= 0; DataMem:= MemW[Seg(p):Ofs(p)]; MemLong:= MemL[64:i*SizeOf(rea1)]; Как следует из технического описания операционной системы МS DOS, в ПЗУ BIOS по адресу $F000:$FFFE зашит байт-идентификатор типа компьютера. Таким образом можно определить тип компьютера, на котором запускается программа:
Program DMA_Demo; Begin Write('Тип компьютера: '); Case Mem[$FOOO:$FFFE] of $FF: writeln('PC'); $FE: writeln('XT'); $FD: writeln('PCjr'); $FC: write1n('AT'); $F9: writeIn('совместимый c PC'); End End. Компонентами массива РОRТ являются байты, а массива РОRТW - слова. Индексами этих массивов должно быть выражение типа ВYТЕ, указывающее номер нужного порта. Присвоение значения элементу массива РОRТ или РОRТW приведет к записи в порт, упоминание элемента в выражении - к чтению из порта. Компоненты массивов РОRТ и РОRТW нельзя передавать в качестве параметров процедурам или функциям. Эти идентификаторы нельзя употреблять без индексных выражений. ТурбоПаскаль 6.0 и структурное программирование. Мы уже знаем, что на этапе проектирования архитектуры Турбо- Паскаль поддерживает модульное проектирование. Но наиболее эффективен Турбо-Паскаль на этапе детального проектирования. Процесс проектирования программ - это процесс решения проблем человеком, подобно другим процессам решения проблем в науке и технике. Из-за ограниченных возможностей человеческом мозга, не способного одновременно охватить все мельчайшие детали, необходимо определить некоторый способ представления проекта. Это представление затем будет использоваться как средство общения. Результирующий код должен выводиться из этого представления просто и однозначно. Приемы такого представления могут быть классифицированы как графические и языковые. К графическим приемам относят блок-схемы и диаграммы Нэсси- Шнейдерман. Блок-схема (схема) - наиболее распространенная и наиболее понимаемая форма графического представления. Она была предложена фон Нейманом как средство документации программ. Для каждой программной структуры существует соответствующая графическая схема. Основное преимущество этого метода - простота и наглядность. Однако, у схем есть и недостатки: их нотации несовместимы с нотациями, используемыми в программных спецификациях и в реализациях; невозможен прямой ввод в ЭВМ и вывод из нее, практически мало средств автоматизированной поддержки; нет эффективном способа управления уровнем детализации в рамках каждой схемы; нотации недостаточны для проектирования крупномасштабных систем ПО. Диаграммы Нэсси-Шнейдерман призваны поддерживать структурное программирование. Предложены специальные графические прямоугольные изображения для базовых структур. Программа описывается с использованием этих изображений. Основные характеристики: функциональная область хорошо определена; не разрешены произвольные передачи управления; легко определяются границы локальных и глобальных данных; легко представляются рекурсивные свойства. Среди приемов языкового представления наиболее известным является использование псевдокодов или языков проектирования программ. Язык проектирования (РDL) - это неграфическая форма представления проекта программы, характерной особенностью которой является возможность оформления шагов обработки на естественном языке с использованием специальных предопределенных слов (кодовых вставок) для описания операторов ветвлений, циклов и описания данных. Внесение изменений в алгоритм программы может быть осуществлено при помощи редактора текстов, имеющемся в составе ПО всех современных ЭВМ. Разработка исполнительной программы заключается в преобразовании описания шагов обработки и описания данных, выполненных на естественном языке, в операторы языка программирования. Наиболее приспособленным для поддержки методологии структурного программирования, является РАSCАL. На его базе элементарно строится язык проектирования PDL-PASCAL. Действия разработчика на каждом шаге Детализации заключаются в подстановке одной из структур, изображенных на рисунке вместо прямоугольников на схеме, полученной на предыдущем шаге. Доказано, что такая процедура детализации позволяет построить логику любом алгоритма. Выбор необходимой структуры для подстановки и составляет суть проектирования на этом этапе. ТурбоПаскаль и объектно-ориентированное программирование. Основные определения типа <объект>. Начиная с версии 5.5, Турбо-Паскаль охватывает еще один современный метод проектирования программ, описанный выше как обьектно-ориентированное проектирование. Объект - это структура данных, содержащая поля данных (подобно записи) различных типов и заголовки методов. Синтаксис объявления объекта:
< ИмяПотомка > = Object(< ИмяПредка >) поле; ............ поле; метод; ............. метод; End; Метод - это процедура или функция, объявленные внутри объявления элемента типа объект и предназначенная в обычно для работы с полями этого объекта. Формат обьявления Procedure <ИмяМетода>(<Параметры, как у процедуры>); Метод имеет доступ к полям данных объекта, не требуя передачи их ему в виде параметров. Объявление метода внутри объявления объектного типа содержит только заголовок. Тело метода определяется вне объявления объекта. Его заголовок должен содержать имя объекта, которому принадлежит метод. Например:
Procedure Объект.Метод; (< параметры >); Begin ....... ....... End; Методы подразделяются на статические и виртуальные. Виртуальный метод отличается от статического тем, что реализующий его код подсоединяется к вызову не в процессе компиляции, а в процессе выполнения, что достигается так называемым поздним связыванием. Это дает возможность строить иерархию объектов с одинаковыми названиями методов, реализуемыми, однако, различными кодами. Синаксис объявления виртуального метода: Procedure Метод(< параметры >); virtual; Кроме обычных процедур и функций Турбо-Паскаль 6.0 реализует два специальных типа методов: конструктор и деструктор. Конструктор - это специальный метод, инициализирующий объект, содержащий виртуальные методы, он объявляется специально зарезервированным словом constructor. Синтаксис: constructor Init(< параметры >); Конструктор инициализирует объект установлением связи между объектом и специальной таблицей виртуальных методов, содержащей адреса кодов, реализующих виртуальные методы. Конструктор может также использоваться для инициализации полей данных объекта. Деструктор - это специальный метод, освобождающий память кучи от динамических объектов. Он объявляется с использованием специально зарезервированного слова destructor. Синтаксис: destructor Done; Основные свойства объектов. Основными отличительными свойствами объекта являются: инкапсуляция - объединение записей с процедурами и функциями, работающими с этими записями; наследование - задание объекта, затем использование его для построения иерархии порожденных объектов с наследованием доступа каждом из порожденных объектов к коду и данным предка; полиморфизм - задание одного имени действию, которое передается вверх и вниз по иерархии объектов, с реализацией этом действия способом, соответствующим каждому объекту в иерархии. Рассмотрим смысл каждого из приведенных свойств. Инкапсуляция Допустим, наши практические интересы лежат в области построения изображений тел звездном неба в двумерной проекции. Очевидно, что основой всякого изображения является положение (позиция) отдельного элемента на экране, описываемая координатами Х и У. Для задания двумерной позиции подходит тип запись, имеющйся в Турбо- Паскале.
Position = Record Х: Integer; Y: Integer; End; Что можно делать с парой координат (Х,У) ? Во-первых, может потребоваться задать значения координат (в программировании такая процедура носит название инициализации). Создадим соответствующую процедуру:
Procedure Init(СоordХ, СооrdУ: Integer); Begin Х:= СооrdX; Y:= СооrdY., End; Во-вторых, нам может потребоваться знание фактических значений координат, для этом вводим две функции:
Function GetX: integer; Begin GetX:= X; End; {---------------------------} Function GetY: integer; Begin GetY:= Y; End; По нашему замыслу процедура Init и функции GetХ и GetY должны работать только с полями записи Pozition. Введение объектов в Паскаль позволяет зафиксировать зто положение, объявив и поля и действия в одном месте:
Pozition = Object X: Integer; Y: Integer,
Procedure Init(CoordX, CoordY: Integer); Function GetX: Integer; Function GetY: Integer; End; Теперь для инициализации экземпляра типа Pozition достаточно вызвать его метод, как если бы он был полем записи:
Var FirstPozition: Pozition; ................... Begin FirstPozition.Init(10,15); ................... Метод задается так же, как и процедура в модуле: внутри объекта записывается заголовок (как в секции Interface модуля), при этом все поля, используемые методом, должны предшествовать ем объявлению. Определение метода (расшифровка действий) происходит вне объявления объекта. Имя метода должно предваряться названием типа объекта, которому метод принадлежит, сопровождаемым точкой.
Procedure Pozition.Init(CoordX, CoordY: Integer); Begin X:= CoordX; Y:= CoordY; End; Заметим, что имена формальных параметров метода не могут совпадать с именами полей данных объекта. Также как модуль защищает детали реализации процедур от пользователя, объект может защищать свои поля и методы. Для этом используется ключевое слово private (личный), Личные поля и методы доступны только внутри метода. Объявление выглядит следующим образом:
Type ObjectNam = Object поле; .......... поле; метод; .......... метод; private ЧастноеПоле; .......... ЧастноеПоле; ЧастныйМетод; .......... ЧастныйМеетод; End; Наследование. Рассмотрим звезду с координатами Х и У. Ее можно сделать видимой или невидимой, ей можно задать цвет, ее можно переместить. Создадим объект с такими возможностями:
Star = Object X: Integer; Y: Integer; Procedure Init(CoordX, CoordY: Integer); Function GetX: Integer; Function GetY: Integer; Visible: Boolean; Color: Word; Procedure Init(CoordX, CoordY: Integer; InitColor: Word); Function IsVisible: Boolean; Procedure Show; { зажигает звезду } Procedure Blind; { гасит звезду } Procedure Jump(NextX, NextY: Integer); { перемещает звезду } End; Заметим, однако, что поля Х,У и методы GetХ, GetУ практически совпадают с соответствующими полями и методами обьекта Pozition. &nnsp Турбо-Паскаль предоставляет возможность учесть эту ситуацию. Следует считать тип объекта Star порожденным типом Pozition, записав это следующим образом (наследование):
Star = Object(Pozition) Visible: Boolean; Color: Word; Procedure Init(CoordX, CoordY: Integer; InitColor: Word); Function IsVisible: Boolean; Procedure Show; Procedure Blind; Procedure Jump(NextX, NextY: Integer); End; Объект Star теперь наследует свойства объекта Pozition. Поля Х,У явно не заданы в Star, но Star ими обладает благодаря наследованию, т.е. можно написать: Star.X:=17; Смысл обьектно-ориентированного программирования заключается именно в работе с полями объекта через его методы. Полиморфизм. Давайте создадим объект "планета". Очевидно, что новый объект должен иметь предком объект Star, обладая всеми его свойствами, кроме того, быть "больше" по размеру (точнее - видимому размеру). Однако, даже начинающему программисту ясно, что нарисоавть на экране точку и закрашенную окружность не удастся одними и теми же командами. Турбо-Паскаль разрешает сохранить потомку имя родительского метода, "перекрывая" его. Чтобы перекрыть родительский метод, нужно просто задать его с тем же именем, но с другим телом (кодом) и, если необходимо, с другим набором параметров. Такой метод делается виртуальным и к его объявлению добавляется слово virtual. Применение виртуальных методов налагает ограничения на процедуры инициализации, которые должны записываться с зарезервированным словом constructor и иметь общее имя Init. Каждый отдельный экземпляр объекта должен инициализироваться с помощью отдельного вызова конструктора. Для очистки и убирания динамически распределенных объектов существует специальная процедура - destructor Done. Деструктор комбинирует шаг освобождения памяти в "куче" с некоторыми другими задачами. Метод деструктора может быть пустым, поскольку работу выполняет не только код тела, но и код, генерируемый Турбо-Паскалем в ответ на зарезервированное слово destructor. Объектно-ориентированная библиотека Turbo Vision Основные определения Turbo Vision - это оболочка оконной программы, управляемой событиями, включающая: многократные перекрывающиеся окна с изменяемыми размерами, выпадающие меню, поддержку мышки, диалоговые окна, встроенную установку цвета, кнопки, полосы скроллинга, окна ввода, зависимые и независимые кнопки, стандартную обработку клавиш и нажатий мышки. Turbo Vision использует понятия видимых элементов, событий и невидимых объектов. Видимый элемент - это любой элемент программы прямоугольной формы, который виден на экране: поля, рамки окон, полосы скроллинга, полосы меню и диалоговые окна. Только видимые элементы могут взаимодействовать с дисплеем. Видимые элементы могут объединяться для формирования более сложных элементов таких, как окна и диалоговые окна. Эти наборы видимых элементов называются группами и они работают вместе так, как если бы это был один элемент. Невидимые объекты - это любые другие объекты программы, которые ничего не выводят на экран. Событие - это внешний фактор, заставляющий реагировать Вашу программу: нажатие клавиши, нажатие кнопки мышки, какие либо другие события в системе компьютера. Встроенная справочная система Если Вы забыли, как точно пишется имя процедуры, какие аргументы ей требуются, то ИПО может помочь, предоставив в Ваше распоряжение свою справочную систему. Ее можно вызвать, нажав клавишу [F1]. Справка, которую выдает система, является контекстно-зависимой. Это означает, что Вы получите сообщение об объекте, указываемом курсором в тексте программы или в меню, либо сообщение о текущей ситуации в системе. Передвигая курсор по тексту справки, выбирая те или иные отмеченные элементы и нажимая затем клавишу [Enter], можно получить более подробную информацию. Целесообразно пользоваться справочной системой и при появлении сообщений об ошибках. Это избавляет от необходимости выяснять их причины в руководствах и справочниках. Во время работы с редактором текста в ТП 7.0 Вы можете с помощью комбинации [Ctrl+Fl] или правой кнопки мыши (для получения справки необходимо активизировать правой кнопкой мыши локальное меню и вызвать команду Topic\search) получить справочную информацию об операторе программы, на который указывает курсор. Установите курсор на строку, содержащую оператор WriteLn (непосредственно на само имя), и, используя комбинацию [Ctrl+Fl], активизируйте справочную систему ТП 7.0. Вы получите справку об операторе WriteLn. Если курсор не находится на зарезервированном слове и транслятор ошибок не обнаружил, то нажатие комбинации клавиш [Ctrl+Fl] приводит к выдаче индексной страницы (предметного указателя справок), из которой можно вызвать справочную информацию о любой процедуре или функции, даже если ее имя неточно указано в тексте программы (или вообще там отсутствует). Если Вы уже вызвали справку, а затем еще раз нажали клавишу [F1], Вы попадете в меню доступных справок. Это меню построено по иерархическому принципу и организовано как дерево, передвижение по которому в направлении корня осуществляется с помощью клавиш [Alt+Fl], а в противоположную сторону - выбором в справках конкретного термина, подлежащего более подробному истолкованию. 9 Лекция 2. 236 уч. группа Тема 1. Основные этапы разработки программ. Вопросы: Этапы подготовки и решения задач на ЭВМ. Алгоритмирование вычислительного процесса. Структурные схемы алгоритмов. Редактирование, компиляция, и выполнение программы в инструментальной среде программирования. 1. Этапы подготовки и решения задач на ЭВМ На ЭВМ могут решаться задачи различного характера, например: научно-инженерные; разработки системного программного обеспечения; обучения; управления производственными процессами и т.д. В процессе подготовки и решения на ЭВМ научно-инженерных задач можно выделить следующие этапы: постановка задачи; математическое описание задачи; выбор и обоснование метода решения; алгоритмизация вычислительного процесса; составление программы; отладка программы; решение задачи на ЭВМ и анализ результатов.
В задачах другого класса некоторые этапы могут отсутствовать, например, в задачах разработки системного программного обеспечения отсутствует математическое описание. Перечисленные этапы связаны друг с другом. Например, анализ результатов может показать необходимость внесения изменений в программу, алгоритм или даже в постановку задачи. Для уменьшения числа подобных изменений необходимо на каждом этапе по возможности учитывать требования, предъявляемые последующими этапами. В некоторых случаях связь между различными этапами, например, между постановкой задачи и выбором метода решения, между составлением алгоритма и программированием, может быть настолько тесной, что разделение их становится затруднительным. Постановка задачи. На данном этапе формулируется цель решения задачи и подробно описывается ее содержание. Анализируются характер и сущность всех величин, используемых в задаче, и определяются условия, при которых она решается. Корректность постановки задачи является важным моментом, так как от нее в значительной степени зависят другие этапы. Математическое описание задачи. Настоящий этап характеризуется математической формализацией задачи, при которой существующие соотношения между величинами, определяющими результат, выражаются посредством математических формул. Так формируется математическая модель явления с определенной точностью, допущениями и ограничениями. При этом в зависимости от специфики решаемой задачи могут быть использованы различные разделы математики и других дисциплин.
Выбор и обоснование метода решения. Модель решения задачи с учетом ее особенностей должна быть доведена до решения при помощи конкретных методов решения. Само по себе математической описание задачи в большинстве случаев трудно перевести на язык машины. Выбор и использование метода решения задачи позволяет привести решение задачи к конкретным машинным операциям. При обосновании выбора метода необходимо учитывать различные факторы и условия, в том числе точность вычислений, время решения задачи на ЭВМ, требуемый объем памяти и другие. Одну и ту же задачу можно решить различными методами, при этом в рамках каждого метода можно составить различные алгоритмы. Алгоритмизация вычислительного процесса. На данном этапе составляется алгоритм решения задачи согласно действиям, задаваемым выбранным методом решения. Процесс обработки данных разбивается на отдельные относительно самостоятельные блоки и устанавливается последовательность выполнения блоков. Разрабатывается блок-схема алгоритма.(Смотри алгоритмизацию вычислительного процесса) Составление программы. При составлении программы алгоритм решения задачи переводится на конкретный язык программирования. Для программирования обычно используются языки высокого уровня, поэтому составленная программа требует перевода ее на машинный язык ЭВМ. После такого перевода выполняется уже соответствующая машинная программа. Отладка программы. Отладка заключается в поиске и устранении синтаксических и логических ошибок в программе. В ходе (1) синтаксического контроля программы транслятором выявляются конструкции и сочетания символов, недопустимые с точки зрения правил их построения или написания, принятых в данном языке. Сообщения об ошибках ЭВМ выдает программисту, при этом вид и форма выдачи подобных сообщений зависят от вида языка и версии используемого транслятора. После устранения синтаксических ошибок (2) проверяется логика работы программы в процессе ее выполнения с конкретными исходными данными. Для этого используются специальные методы, например, в программе выбираются контрольные точки, для которых вручную рассчитываются промежуточные результаты. Эти результаты сверяются со значениями, получаемыми ЭВМ в данных точках при выполнении отлаживаемой программы. Кроме того, для поиска ошибок (3)могут быть использованы отладчики, выполняющие специальные действия на этапе отладки. Например, удаление, замена или вставка отдельных операторов или целых фрагментов программы, вывод или изменение значений заданных переменных. Решение задачи на ЭВМ и анализ результатов. После отладки программу ее можно использовать для решения прикладной задачи. При этом обычно выполняется многократное решение задачи на ЭВМ для различных наборов исходных данных. Получаемые результаты интерпретируются и анализируются специалистом или пользователем, поставившим задачу. Разработанная программа длительного использования устанавливается на ЭВМ, как правило, в виде готовой к выполнению машинной программы. К программе прилагается документация, включая инструкцию для пользователя. Чаще всего при установке программы на диск для ее последующего использования помимо файлов с исполняемым кодом устанавливаются различные вспомогательные программы (утилиты, справочники, настройщики и т.д.), а также необходимые для работы программ разного рода файлы с текстовой, графической, звуковой и другой информацией. Рассмотрим подробнее алгоритмизацию вычислительного процесса. 2. Алгоритмирование вычислительного процесса. 2.1 Способы описания алгоритмов К основным способам описания алгоритмов можно отнести следующие: словесно-формульный; структурный или блок-схемный; с помощью граф-схем; с помощью сетей Петри.
Перед составлением программ чаще всего используются словесно-формульный и блок-схемный способы. Иногда перед составлением программ на низкоуровневых языках программирования типа Ассемблера алгоритм программы записывают, пользуясь конструкциями некоторого высокоуровнего языка программирования. Удобно использовать программное описание алгоритмов функционирования сложных программных систем. Так, для описания принципов функционирования ОС использовался алголо-подобный высокоуровневый язык программирования. При словесно-формульном способе алгоритм записывается в виде текста с формулами по пунктам, определяющим последовательность действий. Пусть, например, необходимо найти значение следующего выражения:
y = 2a - (x+6).
Словесно-формульным способом алгоритм решения этой задачи может быть записан в следующем виде: Ввести значения а и х. Сложить х и 6. Умножить а на 2. Вычесть из 2а сумму (х+6). Вывести у как результат вычисления выражения.
При блок-схемном описании алгоритм изображается геометрическими фигурами (блоками), связанными по управлению линиями (направлениями потока) со стрелками. В блоках записывается последовательность действий. Данный способ по сравнению с другими способами записи алгоритма имеет ряд преимуществ. Он наиболее нагляден: каждая операция вычислительного процесса изображается отдельной геометрической фигурой. Кроме того, графическое изображение алгоритма наглядно показывает разветвления путей решения задачи в зависимости от различных условий, повторение отдельных этапов вычислительного процесса и другие детали. Оформление программ должно соответствовать определенным требованиям. В настоящее время действует единая система программной документации (ЕСПД), которая устанавливает правила разработки, оформления программ и программной документации. В ЕСПД определены и правила оформления блок-схем алгоритмов (ГОСТ 10.002-80 ЕСПД, ГОСТ 10.003-80 ЕСПД). Операции обработки данных и носители информации изображаются на схеме соответствующими блоками. Большая часть блоков по построению условно вписана в прямоугольник со сторонами a и b. Минимальное значение а равно 10 мм, увеличение а производится на число, кратное 5 мм. Размер b=1,5а. Для отдельных блоков допускается соотношение между a и b, равное 1:2. В пределах одной схемы рекомендуется изображать блоки одинаковых размеров. Все блоки нумеруются. Виды и назначение основных блоков приведены в табл.1.
Блок-схема должна содержать все разветвления, циклы и обращения к подпрограммам, содержащиеся в программе. 2.2 Структурные схемы алгоритмов Вычислительные процессы, выполняемые на ЭВМ по заданной программе, можно разделить на три основных вида: линейные; ветвящиеся; циклические. На Рис.1 показан пример линейного алгоритма, определяющего процесс вычисления арифметического выражения y=(b2-ac):(a+c). Рис. 2 .1. Пример линейного алгоритма Вычислительный процесс называется ветвящимся, если для его реализации предусмотрено несколько направлений (ветвей). Ветвящийся процесс, включающий в себя две ветви, называется простым, более двух ветвей - сложным. Сложный ветвящийся процесс можно представить с помощью простых ветвящихся процессов. Направление ветвления выбирается логической проверкой, в результате которой возможны два ответа: “да” - условие выполнено и “нет” - условие не выполнено. На Рис. 2.2. показан пример алгоритма с разветвлением для вычисления следующего выражения: Рис. 2 .2. Пример разветвляющегося алгоритма Циклическими называются программы, содержащие циклы. Цикл - есть многократно повторяемый участок программы.
Рис. 2 .3. Алгоритм нахождения суммы 10-и чисел
Задание на самоподготовку: найти в литературе для самостоятельной подготовки и записать в конспект условные обозначения блоков схем алгоритмов с расшифровкой назначения. Редактирование, компиляция, и выполнение программы в инструментальной среде программирования.
3.1 Среда программирования TurboPascal 7.0
Среда программировании Turbo Pascal 7.0 представляет собой интегрированную среду разработки компьютерных программ с использованием языка программирования Pascal. Следует отметить, что язык программирования, используемый этой системой, значительно шире и мощнее так называемого стандартного языка Pascal.
Turbo Pascal - это эффективный компилятор Паскаль с интегрированной усовершенствованной средой, легкой для изучения и использования.
В среду программирования входят: текстовый редактор, компилятор, редактор связей, отладчик, контекстно-ориентированный справочник .
Все эти свойства встроены в Turbo Pascal, и. все они доступны из среды программирования.
Справочную информацию можно получить посредством нажатия F1 (Ctrl-F1) и из меню Help.
3.2 Компоненты среды программирования T.P 7.0.
Существуют три видимых компоненты в интегрированной усовершенствованной среде: полоса меню в верхней части, область окна в центре, строка статуса внизу.
Многие элементы меню также предлагают диалоговые окна.
Среда программирования TP7.0 запускается программой TURBO.EXE, которая находится в каталоге на жестком диске (обычно: c:\tp)
3.3 Создание программы.
При загрузке Turbo Pascal (набрав TURBO и нажав Enter в ответ на подсказку DOS), Вы увидите полосу меню, строку статуса, пустой экран и окно с информацией о версии продукта (выбор команды About (о) из меню Ё, или System (системного), в любой момент времени приведет к появлению этой информации). При нажатии любой клавиши информация с версией исчезает, но среда с окнами остается.
Перед началом работы необходимо настроить компилятор и директории в которых вы хотите хранить исходный текст программы и директории куда вы хотите поместить откомпилированную программу.
Выход в меню производится нажатием клавиши F10. Установите в строке меню File – Change dir рабочий каталог для хранения своих файлов. Меню Compile – Destination Memory поменять на Destination Disk В меню Options укажите реальные пути к каталогам TPU, Unit, Object Нажатие клавиши F3 (сокращение для File/Open) открывает диалоговое окно Open a File в котором задается или вызовается текстовый файл с программой. Все действия в среде программирования подтверждаются нажатием клавиши Enter или кн. “ОК” (кроме работы в текстовом редакторе).
Для удаления используется Backspace, а для передвижения внутри окна редактора используйте клавиши со стрелками.
3.4 Сохранение программы.
Выберается команду Save из меню File. Или клавишей F2.
3.5 Компиляция программы.
Производится из опции Complile в основном меню или Alt-F9.
Turbo Pascal компилирует Вашу программу, изменяя ее с Паскаля (который можно читать) на машинный код 8086 для микропроцессора (который может выполнить Ваша РС). Вы не увидите машинный код 8086; он хранится в памяти (или на диске). Когда Вы начинаете компиляцию, в центре экрана появляется окно, содержащее информацию о данной компиляции. Если во время компиляции не произошло никаких ошибок, то в этом окне появится сообщение "Compilation successful: press any key" (компиляция успешна: нажмите любую клавишу). Окно остается на экране до тех пор, пока Вы не нажмете клавишу.
Если во время компиляции произошла ошибка, Turbo Pascal останавливается, устанавливает курсор на ошибку в редакторе и показывает сообщение об ошибке вверху редактора. (Первое нажатие клавиши очистит это сообщение, а Ctrl-Q W будет показывать его снова до тех пор, пока Вы не измените файл или не перекомпилируете его). Сделайте исправления, сохраните обновленный файл и компилируйте снова.
3.6 Выполнение программы.
После фиксации ошибок в основнм меню и выберите Run/Run (или нажмите Ctrl-F9). Если во время выполнения программы произошла ошибка, то на экране появится сообщение, которое выглядит следующим образом: Run-time error <errnum> at <segment>:<offset> где <errnum> - это соответствующий номер ошибки (см. в приложении А "Сообщения об ошибках" в руководстве программиста информацию по ошибкам компиляции и ошибкам времени выполнения), а <segment>:<offset> - это адрес в памяти, где произошла ошибка. (Если Вы хотите вернуться к этой ошибке позднее, ищите ее в окне Output). Вы окажетесь в точке расположения ошибки в своей программе с описательным сообщением об ошибке, показанным в строке статуса редактора. Пока сообщение находится в строке статуса редактора, можно нажать F1 для получения справочной информации по конкретной ошибке. Нажатие любой другой клавиши приводит к исчезновению сообщения об ошибке. Если Вам нужно будет найти местоположение ошибки снова, выберите Search/Find Error. Когда Ваша программа закончит выполнение, Вы вернетесь в то место программы, с которого начинали. Теперь Вы можете модифицировать программу, если хотите. Если Вы выберете команду Run/Run перед внесением изменений в свою программу, Turbo Pascal немедленно выполнит ее снова без перекомпиляции. Вы можете просмотреть вывод своей программы посредством выбора команды Run/User Screen (или нажатия Alt-F5). Выберите ее снова для возврата в среду Turbo Pascal.
Более широко функции среды программирования Turbo Pascal будут рассмотрены на практических занятиях.
Проверка файлов, которые Вы создали. Если Вы вышли из Turbo Pascal (выбрав Exit из меню File), Вы можете просмотреть справочный листинг исходного файла (Паскаль), который Вы создали в любом текстовом редакторе файловых оболочек.
Вывод: На сегодняшней лекции рассмотрены: основные этапы разработки программ, способы создания алгоритма будущей программы, минимальный набор управляющих команд среды программирования для создания программ на языке Turbo Pascal. Т.е. минимальный, но необходимый набор средств для создания программных продуктов. 6 Практическое занятие по работе с модулем Graph. Инициализация графики Язык Turbo Pascal предоставляет целый ряд процедур и других средств, позволяющих рисовать на экране разноцветные точки, отрезки прямых, дуги, закрашенные и незакрашенные окружности, прмоугольники, а также выполнять ряд других действий. Все средства для работы с графикой находятся в модуле GRAPH, поэтому он должен быть подключен в программе перед тем, как начать работу:
USES Graph; Инициализация графики производится с помощью процедуры InitGraph:
InitGraph(var GraphDriver:Integer; var GraphMode:Integer; PathToDriver:String)
Переменная GraphDriver определяет тип видеоадаптера, GraphMode - тип графического режима. Если переменной GraphDriver присвоить значение Detect, то Паскаль сам определит установленный видеоадаптер и установит самый мощный из имеющихся графический режим Переменная PathToDriver определяет путь к каталогу, в котором находятся графические драйвера Паскаля. Для проверки успешности инициализации графики можно использовать процедуру GraphResult. Если графика была успешно инициализировнана, то процедура GraphResult вернет значение grOk.
Процедуры модуля Graph.
Здесь приведены наиболее интересные и часто используемые процедуры для рисования. Все остальные, быть может, появятся позже, хотя в их использовании и нет особой необходимости. К неописанным ниже относятся такие процедуры, как DrawPoly и FillPoly, позволяющие рисовать многоугольники и закрашивать их, процедуры для работы с экранными координатами и другие. Но того, что есть, обычно хватает.
Очень важно! В языке Паскаль используется нестандартная система координат. Нуль ее расположен в левом верхнем углу, ось Ox направлена вправо, а Oy - вниз.
Процедуры для рисования фигур.
PutPixel(X, Y:Integer; Pixel:Word); Рисует на экране точку с координатами (X, Y) цветом Pixel. Line(X1, Y1, X2, Y2:Integer); Рисует на экране отрезок прямой от точки (X1, Y1) до точки (X2, Y2). Rectangle(X1, Y1, X2, Y2:Integer); Рисует на экране прямоугольник с верхним левым углом в точке (X1, Y1) и нижним правым углом в точке (X2, Y2). Bar(X1, Y1, X2, Y2:Integer); Рисует на экране залитый прямоугольник с верхним левым углом в точке (X1, Y1) и нижним правым углом в точке (X2, Y2). Стиль и цвет заливки задаются процедурой SetFillStyle. Bar3D(X1, Y1, X2, Y2:Integer; Depth:Word; Top:Boolean); Рисует на экране параллелепипед с залитой передней гранью. Глубина фигуры - Depth. Если Top равно TopOn , то параллелепипед рисуется с верхней гранью, если TopOff, то без верхней грани. Стиль и цвет заливки передней грани задаются процедурой SetFillStyle. Circle(X, Y:Integer; Radius:Word); Рисует на экране окружность с центром в точке (X, Y) радиусом Radius. Ellipse(X, Y:Integer; StAngle, EndAngle:Word; XRadius, YRadius:Word); Рисует на экране эллиптическую дугу с центром в точке (X, Y), радиусами XRadius, YRadius. StAngle, EndAngle - начальный и конечный углы. Arc(X, Y:Integer; StAngle, EndAngle:Word; Radius:Word); Рисует на экране дугу окружности с центром в точке (X,Y), радиусом Radius. StAngle, EndAngle - начальный и конечный углы. FillEllipse(X, Y:Integer; XRadius,YRadius:Word); Рисует на экране залитый эллипс с центром в точке (X, Y), радиусами XRadius, YRadius. Стиль и цвет заливки задаются процедурой SetFillStyle. Sector(X, Y:Integer; StAngle, EndAngle:Word; XRadius, YRadius:Word); Рисует на экране закрашенный сектор эллипса с центром в точке (X, Y), радиусами XRadius, YRadius. StAngle, EndAngle - начальный и конечный углы. Стиль и цвет заливки задаются процедурой SetFillStyle. PieSlice(X, Y:Integer; StAngle, EndAngle:Word; Radius:Word); Рисует на экране закрашенный сектор круга с центром в точке (X, Y), радиусом Radius. StAngle, EndAngle - начальный и конечный углы. Стиль и цвет заливки задаются процедурой SetFillStyle.
Другие процедуры. ClearDevice; Очищает экран в графическом режиме. CloseGraph;
Закрывает графический режим. SetColor(Color:Word); Устанавливает новый цвет для рисования. SetLineStyle(LineStyle:Word; Pattern:Word; Thickness:Word); Устанавливает стиль и толщину линий. Если Thickness равно ThickWidth, то линии будут толстыми, если NormWidth, то обычными. SetFillStyle(Pattern:Word; Color:Word); Устанавливает стиль и цвет заливки. Если Pattern равно UserFill, то используется определенный пользователем стиль, описанный процедурой SetFillPattern. SetFillPattern(Pattern:FillPatternType; Color:Word); Устанавливает определяемый пользователем стиль и цвет заливки. FillPatternType=Array [1..8] of Byte; FloodFill(X, Y:Integer; Border:Word); Заливает область вокруг точки (X, Y) до линии цвета Border, используя текущий стиль и цвет заливки. SetTextStyle(Font, Direction:Word; CharSize:Word); Устанавливает используемый шрифт, его направление и размер. Направление соответствует значению переменной Direction и может быть горизонтальным (Direction=0) или вертикальным (Direction=1). Шрифт может быть как одним из стандартных, так и определенный пользователем (с помощью функции InstallUserFont). SetUserCharSize(MultX, DivX, MultY, DivY:Word); Устанавливает ширину букв используемого шрифта в MultX/DivX раз больше, а высоту в MultY/DivY раз больше. OutTextXY(X, Y:Integer; TextString:String); Выводит текст TextString на экран от точки (X, Y).
Образец:
{ Внимание! Для работы этой программы необходимо, чтобы:
1) Turbo Pascal был установлен в каталог C:\TP; 2) каталог C:\TP\BGI содержал файл egavga.bgi ; 3) в меню Options/Directories был указан путь к файлу graph.tpu, н пример, С:\TP\UNITS.
Если Turbo Pascal установлен в другом каталоге, нужно изменить путь к нему в процедуре InitGraph (10-я строк программы). }
Program Lines; Uses Graph, Crt; {подключение к программе библиотек Crt и Graph} Var Key : Char; LineStyle : Word; { номер стиля рисования линии } Style : String; { название стиля } GrDriver, GrMode : Integer; { тип и режим работы графического драйвера } GrError : Integer; { код ошибки графики } BEGIN GrDriver := Detect; { втоопределение тип графического драйвера } InitGraph(GrDriver, GrMode, 'C:\TP\BGI'); { установка графического режим } GrError := GraphResult; If GrError<>GrOk then begin Writeln('Обнаружена ошибка !'); Halt end; SetBkColor(LightGray); SetColor(Red); { цвет фона и цвет рисования } {------------------------------------------------------------} OutTextXY(120, 100, 'Рисуем линию от точки (200,200) к точке (400,280)'); Line(200, 200, 400, 280); Key:=ReadKey; { приостановление исполнения программы } ClearViewPort; { очистка окна } {-----------------------------------------------------------} OutTextXY(240, 80, 'Рисуем ломанную'); Rectangle(110, 120, 520, 400); { рисование рамки } MoveTo(Round(GetMaxX/2), Round(GetMaxY/2)); { указатель в центре окна }
Repeat {цикл прерывается нажатием любой клавиши} LineTo(Random(GetMaxX-250)+120, Random(GetMaxY-210)+120); Delay(100); until KeyPressed;
Key := ReadKey; ClearViewPort; {-----------------------------------------------------------} OutTextXY(190, 80, 'Mеняем стили рисования линий');
For LineStyle := 0 to 3 do begin SetLineStyle(LineStyle, 0, 1); Case LineStyle of 0: Style:='Сплошная'; 1: Style:='Точечная'; 2: Style:='Штрихпунктирная'; 3: Style:='Пунктирная' end; Line(120, 150+LineStyle*50, 430, 150+LineStyle*50); OutTextXY(450, 145+LineStyle*50, Style); end;
Key:=ReadKey; ClearViewPort; {очистка окна } {-----------------------------------------------------------} OutTextXY(180, 80, 'Меняем толщину рисования линий'); SetLineStyle(0, 0, 1); {толщина 1 пиксела } Line(140, 200, 430, 200); OutTextXY(450, 195, 'Нормальная'); SetLineStyle(0, 0, 3); {толщина 3 пиксела} Line(140, 250, 430, 250); OutTextXY(450, 245, 'Тройная'); ReadLn; CloseGraph; {закрытие графического режима } END.
Образец 2: { Движущееся сложное изображение } program titanik; uses Graph, Crt; var grDriver:integer; { драйвер } grMode:integer; { графический режим } grPath:string; { место расположения драйвера } ErrCode:integer; { результат инициализации граф. режим }
x,y:integer; { координаты кораблика } color:word; { цвет кораблика } bkcolor:word; { цвет фона экрана }
{ Кораблик } Procedure Titan(x,y:integer; { координаты базовой точки } color:word); { цвет корабля } const dx=5; dy=5; var OldColor:word; begin OldColor:=GetColor; { сохранить текущий цвет } SetColor(color); { установить новый цвет }
{ корпус } MoveTo(x,y); LineTo(x,y-2*dy); LineTo(x+10*dx,y-2*dy); LineTo(x+11*dx,y-3*dy); LineTo(x+17*dx,y-3*dy); LineTo(x+14*dx,y); LineTo(x,y); { надстройка } MoveTo(x+3*dx,y-2*dy); LineTo(x+4*dx,y-3*dy); LineTo(x+4*dx,y-4*dy); LineTo(x+13*dx,y-4*dy); LineTo(x+13*dx,y-3*dy); Line(x+5*dx,y-3*dy,x+9*dx,y-3*dy); { капитанский мостик } Rectangle(x+8*dx,y-4*dy,x+11*dx,y-5*dy); { труб } Rectangle(x+7*dx,y-4*dy,x+8*dx,y-7*dy); { иллюминаторы } Circle(x+12*dx,y-2*dy,Trunc(dx/2)); Circle(x+14*dx,y-2*dy,Trunc(dx/2)); { мачта } Line(x+10*dx,y-5*dy,x+10*dx,y-10*dy); { оснастка } MoveTo(x+17*dx,y-3*dy); LineTo(x+10*dx,y-10*dy); LineTo(x,y-2*dy); SetColor(OldColor); { восстановить текущий цвет } end; begin grDriver := VGA; { режим VGA} grMode:=VGAHi; { разрешение 640х480} grPath:='d:\bp\bgi'; { драйвер, файл EGAVGA.BGI, находится в каталоге d:\bp\bgi } InitGraph(grDriver, grMode,grPath); ErrCode := GraphResult; if ErrCode <> grOk then Halt(1);
x:=10; y:=200; color:=LightGray; SetBkColor(Blue); bkcolor:=GetBkColor; repeat Titan(x,y,color); { нарисовать корабль } Delay(100); Titan(x,y,bkcolor); { стереть корабль } PutPixel(x,y,color); { след от корабля } x:=x+2; until (x>500); OutTextXY(10,10,'Рейс завершен!'); readln; CloseGraph; End.
{ Программ изображает планету, вращающуюся вокруг Солнца н фоне мерцающих звезд и расходящейся галактики
Внимание! Для работы этой программы необходимо, чтобы: 1) Turbo Pascal был установлен в к т лог C:\TP; 2) каталог C:\TP\BGI содержал файл egavga.bgi ; 3) в меню Options/Directories был указан путь к файлу graph.tpu, н пример, С:\TP\UNITS.
Если Turbo Pascal установлен в другом к т логе, то нужно изменить путь к нему в процедуре InitGraph. }
Program Space; Uses Graph, Crt; Const RadOrb = 250 { радиус орбиты Земли }; RadSun = 70 { радиус Солнц }; RadGal = 100 { радиус галактики }; RadZem = 18 { радиус Земли }; Naklon = 0.2 { коэффициент наклона плоскости орбиты Земли }; PressZem = 0.65 { коэффициент сплющенности полюсов Земли }; Compress = 0.8 { коэффициент сжатия при переходе из }; { расширения режим VGA в режим CGA } Var ZemX, ZemY, UgMer, PixelY, DUgZem , UpDown, XRad, Grad, UgZem, PixelX, StAngle, Ua, Ub, ParallelY, Color, ZemPix, EndAngle, VisualPage, GrMode, GrError, GrDriver, i : Integer; Ugol, CompressZem, Expansion, DUgol, Projection, PolUgol : Real; BEGIN { установк графического режим и проверк возможных ошибок } GrDriver := EGA; GrMode := EGAHi; InitGraph(GrDriver, GrMode, 'C:\TP\BGI'); GrError := GraphResult; If GrError<>GrOk then Halt;
SetBkColor(Black); SetFillStyle(1, Yellow); { установка стиля заполнения и цвет Cолнцa } Ugol := 0; DUgol := 2*Pi/180; { орбитальное угловое смещение Земли } UgZem := 0; DUgZem := 14; { осевое угловое смещение Земли } {--------------------------------------------------} VisualPage := 1; Repeat { цикл прерывается нажатием любой клавиши } SetVisualPage(1- (VisualPage mod 2)); { установка номер видимой видеостраницы } VisualPage := VisualPage+1; { листание видеостраниц } SetActivePage(1 - (VisualPage mod 2)); { установк номера невидимой ( активной) видеостраницы, } { используемой для построения смещенного изображения } ClearDevice; { очистка графического экрана } {-----------------------------------} { Рисование "расходящейся" галактики } RandSeed:=1; { исходное значение датчика случайных чисел } Expansion:=VisualPage/100; { cкорость расширения галактики } For i:= 1 to VisualPage do begin XRad := Trunc(Expansion*RadGal*Random); { текущее расстояние от звезды до центра галактики } PolUgol:= 2*Pi*Random-VisualPage/30; { текущий центральный угол положения звезды галактики } PixelX := 370+Trunc(XRad*cos(PolUgol+1.8)); { координаты } PixelY := 250+Trunc(XRad*0.5*sin(PolUgol)); { звезды } PutPixel(PixelX, PixelY, White) { рисование звезды } end; {-----------------------------------} {Рисование мерцающих звезд} Randomize; { инициализация датчика случайных чисел }
For i:=1 to 70 do PutPixel(Random(640),Random (350),White); { вспыхивающие звезды } {-----------------------------------} For i := 1 to 100 do { Рисование орбиты } PutPixel(320+Round(RadOrb * cos((i+VisualPage/5)*Pi/50+0.3)), 160+Round(RadOrb*Naklon*sin((i+VisualPage/5)*Pi/50-Pi/2)),15); {-----------------------------------} PieSlice(310, 160, 0, 360, RadSun); { Рисование Солнц } {-----------------------------------} { Рисов ние Земли (ее параллелей и меридианов) } Ugol := Ugol+DUgol ; { угол поворот Земли относительно Солнц } Grad := Round(180*Ugol/Pi) mod 360; { в рад.(Ugol) и в град.(Grad) } ZemX := 320+Round(RadOrb*cos((Ugol+Pi/2+0.3))); { координаты } ZemY:=160+Round(RadOrb*Naklon*sin(Ugol)); { центр Земли } CompressZem := 2.5-cos(Ugol+0.3); { коэффициент учета удаленности Земли от наблюдателя } ZemPix := Round(RadZem*CompressZem); { текущий радиус Земли } UgZem := UgZem+DUgZem; { угол поворот Земли относительно своей оси }
For i := 0 to 11 do { рисование меридианов } begin UgMer := (UgZem+i*30) mod 360; If (90<UgMer) and (UgMer<270) { установк начального и конечного } then begin StAngle := 90; EndAngle := 270 end { углов дуги } else begin StAngle := 270; EndAngle := 90 end; {эллипс меридиан } Ua := (Grad+220) mod 360; Ub := (Grad+400) mod 360; { установка цветов рисования затененной и освещенной } { частей меридиана } Color := LightBlue; If Ua<=Ub then if (Ua<UgMer) and (UgMer<Ub) then Color := White; If Ua >Ub then if (Ua<UgMer) or (UgMer<Ub) then Color := White; SetColor(Color); XRad := round((ZemPix*cos(UgMer*Pi/180))); Ellipse(ZemX,ZemY,StAngle,EndAngle,abs(XRad),round(PressZem*ZemPix)); end;
For i := 2 to 7 do { рисование параллелей } begin XRad := abs(Round(ZemPix*sin(i*Pi/9))); { большая полуось эллипс параллели } UpDown := Round(ZemPix*PressZem*cos(i*Pi/9)); { высота параллели над плоскостью экватора } ParallelY := ZemY+UpDown; { координата Y центр эллипса параллели } SetColor(LightBlue); Ellipse(ZemX, ParallelY, 0, 360, XRad, Round(Naklon*XRad)); { затененная часть параллели } SetColor(White); Ellipse(ZemX,ParallelY,Grad+220,Grad+400,XRad,Round(Naklon*XRad)); { освещенная часть параллели } end; {-----------------------------------} { Повторное рисование Cолнца , если оно ближе к наблюдателю, чем Земля } If CompressZem<2 then PieSlice(310, 160, 0, 360, RadSun); {-----------------------------------} RandSeed := VisualPage mod 12; For i := 1 to 250 do { Рисование протуберанцев } begin Projection := (1-sqr(Random))*Pi/2; XRad := RadSun+Round((20)*sin(Projection))-15; PolUgol := 2 * Pi * Random+VisualPage/20; { PolUgol, XRad - полярные координаты протуберанца } PixelX := 310 + Round( XRad * cos(PolUgol)); PixelY := 160 + Round( Compress * XRad * sin(PolUgol)); PutPixel(PixelX, PixelY, LightRed) end; until KeyPressed END. 4 Структура модуля.
Модуль обеспечивает набор средств, благодаря возможности испольозвания процедур и функций, поддерживающих константы, типы данных и переменных, но их действительная реализация скрыта из-за того, что модуль разделен на два раздела: интерфейс и реализация. Все объявления и описания модуля становятся доступными программе, использующей его. Структура модуля похожа на структуру программы, но имеет и отличия:
unit <идентификатор>; interface uses <список модулей>; {общие объявления} implementation uses <список модулей>; {личные объявления} {реализация процедур и функций} begin {код инициализации} end.
Заголовок модуля - слово unit, за которым следует имя модуля - идентификатор. Следующий элемент - ключевое слово interface. Это слово обозначает начало раздела интерфейса модуля, доступного для всех других модулей и программ, использующих этот модуль. В предложении uses указываются модули, которые может использовать этот модуль.
Слово uses может появляться в двух местах: - сразу же после слова interface; в этом случае, константы или типы данных, объявленные в интерфейсах этих модулей, могут быть использованы в любых объявлениях. - сразу же после слова implementation; в этом случае, любые объявления этого модуля могут использоваться только внутри раздела реализации. Это так же допускает циклические ссылки модулей; мы покажем как их использовать ниже.
Раздел интерфейса.
Это "открытая" часть модуля, она начинается ключевым словом interface, следующим сразу за заголовком, и ограничена ключевым словом imрlеmentation. Интерфейс определяет, что является видимым (доступным) для некоторой программы (или других модулей), использующих этот модуль. Любая программа, использующая этот модуль, имеет доступ к этим видимым элементам. В интерфейсе модуля можно объявить константы, типы данных, переменные, процедуры и функции. Как и в программе, они могут быть расположены в любом порядке, т.е разделы могут встречаться повторно
(type...var...<proc>...type...const...var)
Процедуры и функции, доступные для программы, использующей этот модуль описываются в разделе интерфейса. А их действительные тела - операторы, реализующие их, - в разделе реализации. Объявление forward не разрешается. Тела всех обычных процедур и функций находятся в разделе реализации после раздела интерфейса, в котором перечислены их имена и заголовки. uses может появиться и в разделе implementation. Если в разделе реализации имеет место uses, то это слово следует сразу же за словом implementation.
Секция реализации.
Раздел реализации - закрытая, недоступная часть - начинается со слова implementation. Все, что объявлено в части интерфейса видимо для раздела реализации: константы, типы, переменные, процедуры и функции. Кроме того, в разделе реализации могут быть свои собственные дополнительные объявления, недоступные программам, использующим этот модуль. Программы не могут обращаться и ссылаться на них. Однако эти недоступные элементы могут использоваться (и, как правило, это делается) видимыми процедурами и функциями, заголовки которых появляются в разделе интерфейса. Предложение uses может появляться в разделе implementation. В этом случае uses следует непосредственно за ключевым словом implementation. Если процедуры были объявлены как внешние, то в исходном файле должна быть директива {$L имя файла} в любом месте до конца модуля end. Обычные процедуры и функции, объявленные в разделе интерфейса - которые не являются встроенными - должны появляться в разделе реализации. Заголовок procedure (function) в разделе реализации должен быть такой же, как и в разделе интерфейса, или же иметь короткую форму. В краткой форме за ключевым словом (procedure или function) следует идентификатор (имя). Подпрограмма содержит свои собственные локальные объявления (метки, константы, типы, переменые, процедуры и фукции). За ними следует тело главной программы.
Например, в разделе интерфейса объявлены:
procedure ISwap (var v1,v2: integer); function IMax (v1,V2:integer);
Раздел реализации может быть:
procedure ISwap; var Temp : integer; begin Temp := V1;V1:= V2;V2 := Temp; end; {процедуры ISwap}
function IMax (v1,v2:integer):integer; begin if V1 > V2 then IMax := V1 else IMax := V2 end; {функции IMax}
Подпрограммы раздела реализации (неописанные в секции интерфейса), должны иметь полный заголовок procedure/funсtion.
Раздел инициализации.
Раздел реализации модуля заключен между словами implementation и end. Но если присутствует слово begin перед end, и операторы между этими словами, то получившийся составной оператор, похожий на тело главной программы, становится разделом инициализации модуля. В разделе инициализации инициализируются структуры данных (переменных), используемые модулем или доступные программам, использующим этот модуль. Вы можете использовать этот раздел для открытия файлов. Например, стандартный модуль Printer использует этот раздел для открытия на вывод текстового файла Lst. Файл Lst впоследствии можно использовать в программах, в операторах Write или Writeln. При выполнении программы, использующей некоторый модуль, раздел инициализации вызывается перед выполнением тела главной программы. Если в программе используется несколько модулей, раздел инициализации каждого модуля вызывается (в порядке, указанном в операторе uses программы) до выполнения тела главной программы.
Как используются модули? Модули, которые использует Ваша программа, уже откомпилированы и хранятся в специальном машинном коде; это не файлы типа Include. Даже раздел интерфейса хранится в специальном двоичном формате, который использует Turbo Pascal. Более того, стандартные модули хранятся в специальном файле TURBO.TPL и автоматически загружаются в память с Turbo Pascal. В результате подключения модулей к программе увеличивается время и компиляции программы (незначительно, приблизительно на 1 секунду). Если модули загружаются из отдельных дисковых файлов может потребоваться дополнительное время из-за чтения с диска. Для использования модулей необходимо, чтобы в начале присутствовало предложение uses, за которым следует список имен всех модулей, разделенных запятыми.
program MyProg; uses thisUnit,thatUnit,theotherUnit;
При компиляции этой информации к таблице символов прибавляется информации из раздела интерфейса, а из раздела реализации к самой программе машинный код. Порядок описания модулей в предложении uses не имеет большого значения. Если thisUnit использует thatUnit, то можно объявить их в любом порядке. Компилятор сам определит, который из них должен следовать первым. Иначе говоря, если thisUnit использует thatUnit, а программа MyProg не вызывает какие-либо программы в подпрограмме thatUnit, то можно "спрятать"подпрограммы в программу thatUnit, опуская их в операторе uses:
unit thisUnit uses thatUnit ... program MyProg; uses thisUnit,theotherUnit; ...
В этом примере thisUnit может вызвать подпрограмму thatUnit, а MyProg - подпрограммы thisUnit и thеоtherUnit. MyProg не может вызвать thatUnit, т.к эта подпрограмма не описана в его предложении uses. Если предложение uses отсутствует, Turbo Pascal подсоединяет стандартный модуль System. Этот модуль обеспечивает выполнение некоторых стандартных подпрограмм Turbo Pascal и программ, специфичных для Turbo Pascal. 15 Целочисленная арифметика TURBO PASCAL (примеры задач)
1. 0.pas-Подсчитать кол-во цифр в заданном натуральном числе
program N1{подсчит ть кол-во цифр в з д нном н тур льном числе}; var N:integer; function KOL(N:integer):integer; begin if N>9 then KOL:=KOL(N mod 10)+KOL(N div 10) else KOL:=1; end; BEGIN Write('Введите N');ReadLn(N); WriteLn(KOL(N)); ReadLn; END.
2. Найдите целые числа-палиндромы, которые при возведении в квадрат тоже дают палиндромы
program borlpasc; {н йдите целые числа -палиндромы, которые при возведении в квадрат тоже д ют п линдромы} var i,i1,i2:longint; function Palindrom(n:longint):boolean; var n1,n2,o:longint; begin n1:=n;n2:=0; {n1 - данное число, n2 - число которое получится} while n1>0 do begin o:=n1 mod 10;{о - ост ток от деления н 10} n1:=n1 div 10; n2:=n2*10+o; end; Palindrom:=(n=n2); end; begin writeln('Введите интервал поиск :'); write('Начало интервала:');readln(i1); write('Конец интерв л :');readln(i2); for i:=i1 to i2 do if Palindrom(i) and Palindrom(sqr(i)) then writeln(i,'-п линдром ',sqr(i),'-п линдром'); end.
3. Поменять местами первую и последнюю цифры числа program borlpasc; var b,a,n,k:integer; begin write('введите число n='); readln(n); k:=n; a:=k mod 10; repeat b:=k mod 10; k:=k div 10 until k div 10 =0; b:=k mod 10;
writeln(a,n div 10 div 10 mod 10,n div 10 mod 10,b) end.
4. Дано нат. K. напечатать К-ю цифру последовательности 12345678910111213..., в которой записаны подряд все натуральныe числa
{Andrey Sharov} { e-mail : ansharov@one.lv } { website: borlpasc.narod.ru } {д но н т. K. н печ т ть К-ю цифру последов тельности 12345678910111213..., в которой з пис ны подряд все н тур льныe числa} program borlpasc; var i,j,k,n,o:integer; a,t:longint; begin write('Введите k=');readln(k); t:=0;i:=0; repeat t:=t+1; j:=t; while j>0 do{счит ем количество цифр в числе и доб вляем к общему количеству} begin j:=j div 10; i:=i+1; {if i=k then o:=j mod 10;} end; until i>=k; while i>=k do{возвр щ емся по цифр м последнего числ до нужной} begin o:=t mod 10; t:=t div 10; i:=i-1; end; writeln('k-я цифр :',o) end.
5. Дано число N<=99. Дописать в начало и в конец числа цифру k program borlpasc; var n,k:integer; begin write('введите k='); readln(k); write('введите n='); readln(n); writeln('Получилось число:'); write(k,n,k) end.
6. Проверить, есть ли в записи числа N в степени k цифра m Program borlpasc; var i,n,m,s,k,l,a:integer; begin write('введите n='); readln(n); write('введите k='); readln(k); write('введите цифру m='); readln(m); i:=0;l:=0;s:=1; repeat s:=s*n; i:=i+1 until i=k; repeat a:=s mod 10; if a=m then l:=l+1; s:=s div 10 until s=0; if l=0 then writeln('В з писи числ нет цифры',' ',m) else writeln('В з писи числ цифр ',m,' есть') end.
7. Найти наименьшее нат. число, представимое двумя различными способами в виде суммы кубов двух натуральных чисел program borlpasc; var i,k:integer; x,y:integer; begin i:=1; k:=0; repeat i:=i+1; for y:=1 to i-1 do for x:=1 to i do if x*x*x+y*y*y=i then k:=k+1 until k>=2; writeln(x,y); writeln('н им н т число=',i) end. 8. Доказать, что любую целочисленную денежную сумму большую 7 рублей можно выплатить без сдачи трешками и пятерками. program borlpasc; var n,n3,n5,k:integer; begin write('Введите сумму(>7) n='); readln(n); k:=0; for n3:=0 to (n div 3) do for n5:=0 to (n div 5) do begin if n3*3+n5*5=n then writeln(n3,' трешки и ',n5,' пятерок'); k:=k+1 end; if k=0 then writeln('Из трешек и пятерок эту сумму не сложить.') end.
9. Дано нат. K. напечатать К-ю цифру последовательности 149162536..., в которой записаны подряд квадраты всех натуральных чисел program borlpasc; var i,j,k,n,o:integer; a,t:longint; begin write('Введите k=');readln(k); j:=0;a:=1;i:=0; repeat j:=j+1;a:=sqr(j); t:=a; while t>0 do{счит ем количество цифр в числе и доб вляем к общему количеству} begin t:=t div 10; i:=i+1; end; until i>=k; t:=a; while i>=k do{возвр щ емся по цифр м последнего числ до нужной} begin o:=t mod 10; t:=t div 10; i:=i-1; end; writeln('k-я цифр :',o) end.
10. Есть ли в записи числа 3 одинак цифры PROGRAM borlpasc;{Есть ли в з писи числ 3 один к цифры} VAR N:INTEGER;a,b,c,d:integer; BEGIN WRITE('ВВЕДИТЕ ЧИСЛО N='); READLN(N); a:=n div 1000; b:=n div 100 mod 10; c:=n div 10 mod 10; d:=n mod 10; IF (a=b) and (a=c) or (a=b) and (a=d) or (a=c) and (a=d) or (b=c) and (b=d) THEN WRITELN('В ЗАПИСИ ЧИСЛА ЕСТЬ 3 ОДИНАКОВЫЕ ЦИФРЫ') ELSE WRITELN('В ЗАПИСИ ЧИСЛА НЕТ ТРЕХ ОДИНАКОВЫХ ЦИФР') END.
11. Дано натур число n<=9999. Поменять порядок следования цифр. program borlpasc; var s,x:integer; begin write('введите число='); readln(x); s:=0; repeat s:=s*10+x mod 10; x:=x div 10 until x=0; writeln(s) end.
12. Перевод из 10 в двоичную ситему счисления PROGRAM borlpasc; {ПЕРЕВОД ЧИСЛА ИЗ 10-ОЙ СИСТЕМЫ СЧИСЛЕНИЯ В 2-УЮ} TYPE massiv=array [1..50] of integer; var a:massiv; n,i:integer; begin write('введите число:'); readln(n); i:=1; while n>=2 do begin a[i]:= n mod 2; i:=i+1; n:= n div 2; end; i:=i-1; write(n); while i<>0 do begin write(' ',a[i]); i:=i-1; end; writeln end.
13. Перевод из 10 в двоичную ситему счисления (II способ); program two_system; var sec:array[1..16] of byte; d,ten,i:integer; procedre two( ten_1:integer;var two_s:byte); begin two_s:=ten_1 mod 2; ten_1:=ten_1 div 2; end; begin write('введите н т.число: '); readln(ten); if ten<=0 then write('некорректные д нные') else d:=ten; a[i]:=0; fori:=16 downto 1 do two(ten,sec[i]); write('н тур льное число: ',d); write('в двоичной системе счисления '); for i:=1 to 16 do write(' ',sec[i]); end.
14. Найти все делители натурального числа N program borlpasc; const kol=100; type cyfra=0..9; chislo=array[1..kol] of cyfra; var i,r,d,s,k,code:integer; j,c0,c1,x,y,z,o,z1,n,lastd:chislo; p:boolean; function sravnenie(x,y:chislo):integer; var i,r:integer; begin r:=0;i:=1; repeat if (x[i])>(y[i]) then r:=1; if x[i]<y[i] then r:=-1; i:=i+1; until (r<>0)or(i>kol); sravnenie:=r; end; procedure add(x,y:chislo;var z:chislo); var p,a,b,c:integer; begin p:=0; for i:=kol downto 1 do begin a:=x[i]; b:=y[i]; c:=a+b+p; z[i]:=c mod 10; p:=c div 10; end; if p>0 then begin write('переполнение'); readln end end; procedure sub (x,y:chislo;var z:chislo); var i,j,p,l,a,b,r,c:integer; begin p:=0; for i:=kol downto 1 do begin a:=(x[i]); b:=(y[i]); c:=a-b+p; if c<0 then begin c:=c+10; p:=-1; end else p:=0; z[i]:=(c); end; if p<0 then begin write('отриц.число'); readln end; end;
procedure Division(x,y:chislo;var z,O:chislo); var a,b,r,c,i,j,xt,yt,yt1,s:integer; y1:chislo; begin z:=C0; o:=x; if sravnenie(x,y)=-1 then exit; y1:=y; yt:=1;while y[yt]=0 do inc(yt); xt:=1;while x[xt]=0 do inc(xt); s:=yt-xt;yt1:=xt; for i:=1 to kol do if i+s<=kol then y1[i]:=y1[i+s] else y1[i]:=0; while yt1<=yt do begin r:=0; while not(sravnenie(x,y1)=-1) do begin Sub(x,y1,x); r:=r+1 end; for i:=1 to kol-1 do z[i]:=z[i+1]; z[kol]:=r;r:=0; for i:=kol downto 2 do y1[i]:=y1[i-1]; y1[1]:=0;yt1:=yt1+1; end; o:=x end;
procedure print(x:chislo); var i:integer; p:boolean; begin p:=false; for i:=1 to kol do begin if x[i]<>0 then p:=true; if p then write(x[i]) end; if not(p) then write(0) end; procedure input(var x:chislo); var i,j:integer; s:string; begin readln(s); x:=c0;j:=kol; for i:=length(s) downto 1 do begin val(s[i],x[j],code); j:=j-1; end; end; begin for i:=1 to kol do c0[i]:=0; c1:=c0;c1[kol]:=1; write('Введите n=');input(n);x:=n; j:=c1;add(j,c1,j);k:=0; writeln('Делители:'); writeln(1);p:=true;lastd:=c1; while not(sravnenie(x,j)=-1) do begin division(x,j,z,o); if sravnenie(o,c0)=0 then begin x:=z; if not(sravnenie(lastd,j)=0) then begin k:=k+1; lastd:=j; print(j); writeln end end else add(j,c1,j); end; writeln('Всего ',k+1,' делителей'); readln end.
15. Переставить цифры числа так, чтобы образовалось максимальное число, записанное теми же цифрами program borlpasc; var n:string; c:char;i,j:integer; begin write('введите n'); readln(n); for j:=1 to length(n) do for i:=1 to length(n)-1 do if n[i]<n[i+1] then begin c:=n[i]; n[i]:=n[i+1]; n[i+1]:=c; end; writeln('n=',n); end. 16. Переставить цифры числа так, чтобы образовалось наименьшее число, записанное теми же цифрами. program borlpasc; var n:string; c:char;i,j:integer; begin write('введите n='); readln(n); for j:=1 to length(n) do for i:=1 to length(n)-1 do if n[i]>n[i+1] then begin c:=n[i]; n[i]:=n[i+1]; n[i+1]:=c; end; writeln('n=',n); end. 17. составить программу перевода римских чисел в арабски program borlpasc;{сост вить прогр мму перевод римских чисел в р бские} var s:string; {ВЕРНА!!!!} n,c,c1,i,a:integer; begin writeln('введите число:'); readln(s); c:=0;n:=0; for i:=1 to length(s) do begin c1:=c; if s[i]='I' then c:=1; if s[i]='V' then c:=5; if s[i]='X' then c:=10; if s[i]='L' then c:=50; if s[i]='C' then c:=100; if s[i]='D' then c:=500; if s[i]='M' then c:=1000; if c>c1 then a:=-2*c1 else a:=0; n:=n+a+c end; writeln('в ше число=',n) end.
18. Дано натур. число N. Если это не палиндром, реверсируйте его цифры и сложите исходное число с числом, полученным в результате реверсирования. Если сумма не палиндром, то повторите те же действия и выполняйте их до тех пор, пока не получится палиндром { Пример: } { 78+87=165 } { 165+561=726 } { 726+627=1353 } { 1353+3531=4884 } uses CRT; var N, N2, nn:Longint; BEGIN ClrScr; Write('N:= '); ReadLn(N); nn:= 0; repeat N:= N+ nn; nn:= 0; N2:= N; while N>0 do begin nn:= nn*10+(N mod 10); N:= N div 10; end; N:= N2; Write(#13#10,N,'+ ', nn, '='); until N=nn; WriteLn(' Ответ'); Write('< Ok >'); ReadKey; END. 19. Дано натур. число N. Поменять порядок следования цифр в этом числе на обратный uses CRT; var N, nn:Longint; BEGIN ClrScr; Write('N:= '); ReadLn(N); nn:= 0; while N>0 do begin nn:= nn*10+(N mod 10); N:= N div 10; end; WriteLn('N''= ',nn); Write(#10#13'< Ok >'); ReadKey; END.
20. Дано натур. число N. Найти и вывести все числа в интервале от 1 до N-1, у которых произведение всех цифр совпадает с суммой цифр данного uses CRT; var N, nn, i, A, B:integer; BEGIN ClrScr; Write('N:= '); ReadLn(N); A:= 1; nn:= N; Write('Произведение '); while nn>0 do begin A:= (nn mod 10)* A; if (nn mod 10)>1 then Write(nn mod 10,'x'); nn:= nn div 10; end; WriteLn(#8'=',A); WriteLn('Числ :'); for i:=1 to N-1 do begin nn:= i; B:=1; while nn>0 do begin B:= (nn mod 10)* B; nn:= nn div 10; end; if A=B then Write(i:8); end; Write(#10#13'< Ok >'); ReadKey; END.
20. Найти произведение цифр заданного целого четырехзначного числа. Систем тестов +------------------------------------------------------------------------------+ ¦ Номер ¦ Проверяемый ¦ Число ¦ Результ ты ¦ ¦ тест ¦ случ й ¦ ¦ ¦ ¦-------+---------------------+---------------+----------------¦ ¦ 1 ¦ Число положительное ¦ Number = 2314 ¦ P = 24 ¦ ¦-------+---------------------+---------------+----------------¦ ¦ 2 ¦ Число отриц тельное ¦ Number =-1245 ¦ P = 40 ¦ +-------------------------------------------------------------------------------+ Program DigitsProduct; Uses Crt; Var Number, {з д нное число} i, j, k, l, {цифры числ } P : Integer; {произведение цифр} BEGIN ClrScr; Write( 'Введите четырехзн чное число : ' ); ReadLn(Number); Write( 'Цифры числ ' , Number , ' : ' ); Number:=Abs(Number);
i := Number div 1000; Write(i:3); {перв я цифр } j := Number div 100 mod 10; Write(j:3); {втор я цифр } k := Number div 10 mod 10; Write(k:3); {третья цифр } l := Number mod 10; WriteLn(l:3); {четверт я цифр }
P := i * j * k * l ; WriteLn( 'О т в е т : произведение цифр равно ' , P ); ReadLn END.
21. Определить является ли заданная последовательность чисел 1, 2,..., N монотонно убывающей. Систем тестов +------------------------------------------------+ ¦ Номер ¦ Проверяемый ¦ Д нные ¦ ¦ ¦ тест ¦ случ й +--------------¦ Результ т ¦ ¦ ¦ ¦ N ¦ Вектор А ¦ ¦ ¦-------+-------------+---+----------+-----------¦ ¦ 1 ¦ Является ¦ 3 ¦(3, 2, 1) ¦ "Да " ¦ ¦-------+-------------+---+----------+-----------¦ ¦ 2 ¦ Не является ¦ 3 ¦(2, 3, 1) ¦ "Нет" ¦ +------------------------------------------------+} Program Decrease; Uses Crt; Var A : Array [1..10] of Real; N, i : Integer; Otvet: Boolean; {--------------------------------------------} Procedure InputOutput; {опис ние процедуры ввод -вывод д нных} Begin ClrScr; Write('Количество элементов - '); ReadLn(N); For i := 1 to N do begin Write('A[' , i , '] = '); ReadLn(A[i]) end; WriteLn;
WriteLn('З д нн я последов тельность чисел'); For i := 1 to N do Write(A[i] : 5 : 1); WriteLn End; { of InputOutput } {--------------------------------------------} Procedure Processing( Var Otvet: Boolean); Begin {опис ние процедуры проверки н убыв ние элементов} Otvet := TRUE; i:=1; While (i<=N-1) and Otvet do If (A[i]<A[i+1]) then Otvet := FALSE else i := i+1; End; { of Processing } {--------------------------------------------} Procedure Result(Otvet: Boolean); {опис ние процедуры вывод результ т } Begin If Otvet then Write('обр зует ') else Write('не обр зует '); WriteLn('монотонно убыв ющую последов тельность.'); ReadLn End; {--------------------------------------------} BEGIN InputOutput; {вызов процедуры ввод -вывод } Processing(Otvet); {вызов процедуры проверки н убыв ние} Result(Otvet); {вызов процедуры вывод результ т } END. Практическое занятие по работе с модулем Graph. Инициализация графики Язык Turbo Pascal предоставляет целый ряд процедур и других средств, позволяющих рисовать на экране разноцветные точки, отрезки прямых, дуги, закрашенные и незакрашенные окружности, прмоугольники, а также выполнять ряд других действий. Все средства для работы с графикой находятся в модуле GRAPH, поэтому он должен быть подключен в программе перед тем, как начать работу:
USES Graph; Инициализация графики производится с помощью процедуры InitGraph:
InitGraph(var GraphDriver:Integer; var GraphMode:Integer; PathToDriver:String)
Переменная GraphDriver определяет тип видеоадаптера, GraphMode - тип графического режима. Если переменной GraphDriver присвоить значение Detect, то Паскаль сам определит установленный видеоадаптер и установит самый мощный из имеющихся графический режим Переменная PathToDriver определяет путь к каталогу, в котором находятся графические драйвера Паскаля. Для проверки успешности инициализации графики можно использовать процедуру GraphResult. Если графика была успешно инициализировнана, то процедура GraphResult вернет значение grOk.
Процедуры модуля Graph.
Здесь приведены наиболее интересные и часто используемые процедуры для рисования. Все остальные, быть может, появятся позже, хотя в их использовании и нет особой необходимости. К неописанным ниже относятся такие процедуры, как DrawPoly и FillPoly, позволяющие рисовать многоугольники и закрашивать их, процедуры для работы с экранными координатами и другие. Но того, что есть, обычно хватает.
Очень важно! В языке Паскаль используется нестандартная система координат. Нуль ее расположен в левом верхнем углу, ось Ox направлена вправо, а Oy - вниз.
Процедуры для рисования фигур.
PutPixel(X, Y:Integer; Pixel:Word); Рисует на экране точку с координатами (X, Y) цветом Pixel. Line(X1, Y1, X2, Y2:Integer); Рисует на экране отрезок прямой от точки (X1, Y1) до точки (X2, Y2). Rectangle(X1, Y1, X2, Y2:Integer); Рисует на экране прямоугольник с верхним левым углом в точке (X1, Y1) и нижним правым углом в точке (X2, Y2). Bar(X1, Y1, X2, Y2:Integer); Рисует на экране залитый прямоугольник с верхним левым углом в точке (X1, Y1) и нижним правым углом в точке (X2, Y2). Стиль и цвет заливки задаются процедурой SetFillStyle. Bar3D(X1, Y1, X2, Y2:Integer; Depth:Word; Top:Boolean); Рисует на экране параллелепипед с залитой передней гранью. Глубина фигуры - Depth. Если Top равно TopOn , то параллелепипед рисуется с верхней гранью, если TopOff, то без верхней грани. Стиль и цвет заливки передней грани задаются процедурой SetFillStyle. Circle(X, Y:Integer; Radius:Word); Рисует на экране окружность с центром в точке (X, Y) радиусом Radius. Ellipse(X, Y:Integer; StAngle, EndAngle:Word; XRadius, YRadius:Word); Рисует на экране эллиптическую дугу с центром в точке (X, Y), радиусами XRadius, YRadius. StAngle, EndAngle - начальный и конечный углы. Arc(X, Y:Integer; StAngle, EndAngle:Word; Radius:Word); Рисует на экране дугу окружности с центром в точке (X,Y), радиусом Radius. StAngle, EndAngle - начальный и конечный углы. FillEllipse(X, Y:Integer; XRadius,YRadius:Word); Рисует на экране залитый эллипс с центром в точке (X, Y), радиусами XRadius, YRadius. Стиль и цвет заливки задаются процедурой SetFillStyle. Sector(X, Y:Integer; StAngle, EndAngle:Word; XRadius, YRadius:Word); Рисует на экране закрашенный сектор эллипса с центром в точке (X, Y), радиусами XRadius, YRadius. StAngle, EndAngle - начальный и конечный углы. Стиль и цвет заливки задаются процедурой SetFillStyle. PieSlice(X, Y:Integer; StAngle, EndAngle:Word; Radius:Word); Рисует на экране закрашенный сектор круга с центром в точке (X, Y), радиусом Radius. StAngle, EndAngle - начальный и конечный углы. Стиль и цвет заливки задаются процедурой SetFillStyle.
Другие процедуры. ClearDevice; Очищает экран в графическом режиме. CloseGraph;
Закрывает графический режим. SetColor(Color:Word); Устанавливает новый цвет для рисования. SetLineStyle(LineStyle:Word; Pattern:Word; Thickness:Word); Устанавливает стиль и толщину линий. Если Thickness равно ThickWidth, то линии будут толстыми, если NormWidth, то обычными. SetFillStyle(Pattern:Word; Color:Word); Устанавливает стиль и цвет заливки. Если Pattern равно UserFill, то используется определенный пользователем стиль, описанный процедурой SetFillPattern. SetFillPattern(Pattern:FillPatternType; Color:Word); Устанавливает определяемый пользователем стиль и цвет заливки. FillPatternType=Array [1..8] of Byte; FloodFill(X, Y:Integer; Border:Word); Заливает область вокруг точки (X, Y) до линии цвета Border, используя текущий стиль и цвет заливки. SetTextStyle(Font, Direction:Word; CharSize:Word); Устанавливает используемый шрифт, его направление и размер. Направление соответствует значению переменной Direction и может быть горизонтальным (Direction=0) или вертикальным (Direction=1). Шрифт может быть как одним из стандартных, так и определенный пользователем (с помощью функции InstallUserFont). SetUserCharSize(MultX, DivX, MultY, DivY:Word); Устанавливает ширину букв используемого шрифта в MultX/DivX раз больше, а высоту в MultY/DivY раз больше. OutTextXY(X, Y:Integer; TextString:String); Выводит текст TextString на экран от точки (X, Y).
Образец:
{ Внимание! Для работы этой программы необходимо, чтобы:
1) Turbo Pascal был установлен в к т лог C:\TP; 2) к талог C:\TP\BGI содержал файл egavga.bgi ; 3) в меню Options/Directories был указан путь к файлу graph.tpu, н пример, С:\TP\UNITS.
Если Turbo Pascal установлен в другом к т логе, нужно изменить путь к нему в процедуре InitGraph (10-я строк программы). }
Program Lines; Uses Graph, Crt; {подключение к программе библиотек Crt и Graph} Var Key : Char; LineStyle : Word; { номер стиля рисования линии } Style : String; { название стиля } GrDriver, GrMode : Integer; { тип и режим р боты гр фического др йвер } GrError : Integer; { код ошибки гр фики } BEGIN GrDriver := Detect; { втоопределение тип гр фического др йвер } InitGraph(GrDriver, GrMode, 'C:\TP\BGI'); { уст новк гр фического режим } GrError := GraphResult; If GrError<>GrOk then begin Writeln('Обн ружен ошибк !'); Halt end; SetBkColor(LightGray); SetColor(Red); { цвет фон и цвет рисов ния } {------------------------------------------------------------} OutTextXY(120, 100, 'Рисуем линию от точки (200,200) к точке (400,280)'); Line(200, 200, 400, 280); Key:=ReadKey; { приост новление исполнения прогр ммы } ClearViewPort; { очистк окн } {-----------------------------------------------------------} OutTextXY(240, 80, 'Рисуем лом нную'); Rectangle(110, 120, 520, 400); { рисов ние р мки } MoveTo(Round(GetMaxX/2), Round(GetMaxY/2)); { ук з тель в центре окн }
Repeat {цикл прерыв ется н ж тием любой кл виши} LineTo(Random(GetMaxX-250)+120, Random(GetMaxY-210)+120); Delay(100); until KeyPressed;
Key := ReadKey; ClearViewPort; {-----------------------------------------------------------} OutTextXY(190, 80, 'Mеняем стили рисов ния линий');
For LineStyle := 0 to 3 do begin SetLineStyle(LineStyle, 0, 1); Case LineStyle of 0: Style:='Сплошн я'; 1: Style:='Точечн я'; 2: Style:='Штрихпунктирн я'; 3: Style:='Пунктирн я' end; Line(120, 150+LineStyle*50, 430, 150+LineStyle*50); OutTextXY(450, 145+LineStyle*50, Style); end;
Key:=ReadKey; ClearViewPort; {очистк окн } {-----------------------------------------------------------} OutTextXY(180, 80, 'Меняем толщину рисов ния линий'); SetLineStyle(0, 0, 1); {толщин 1 пиксел } Line(140, 200, 430, 200); OutTextXY(450, 195, 'Норм льн я'); SetLineStyle(0, 0, 3); {толщин 3 пиксел } Line(140, 250, 430, 250); OutTextXY(450, 245, 'Тройн я'); ReadLn; CloseGraph; {з крытие гр фического режим } END.
Образец 2: { Движущееся сложное изобр жение } program titanik; uses Graph, Crt; var grDriver:integer; { др йвер } grMode:integer; { гр фический режим } grPath:string; { место р сположения др йвер } ErrCode:integer; { результ т иници лиз ции гр ф. режим }
x,y:integer; { координ ты кор блик } color:word; { цвет кор блик } bkcolor:word; { цвет фон экр н }
{ Кор блик } Procedure Titan(x,y:integer; { координ ты б зовой точки } color:word); { цвет кор бля } const dx=5; dy=5; var OldColor:word; begin OldColor:=GetColor; { сохр нить текущий цвет } SetColor(color); { уст новить новый цвет }
{ корпус } MoveTo(x,y); LineTo(x,y-2*dy); LineTo(x+10*dx,y-2*dy); LineTo(x+11*dx,y-3*dy); LineTo(x+17*dx,y-3*dy); LineTo(x+14*dx,y); LineTo(x,y); { н дстройк } MoveTo(x+3*dx,y-2*dy); LineTo(x+4*dx,y-3*dy); LineTo(x+4*dx,y-4*dy); LineTo(x+13*dx,y-4*dy); LineTo(x+13*dx,y-3*dy); Line(x+5*dx,y-3*dy,x+9*dx,y-3*dy); { к пит нский мостик } Rectangle(x+8*dx,y-4*dy,x+11*dx,y-5*dy); { труб } Rectangle(x+7*dx,y-4*dy,x+8*dx,y-7*dy); { иллюмин торы } Circle(x+12*dx,y-2*dy,Trunc(dx/2)); Circle(x+14*dx,y-2*dy,Trunc(dx/2)); { м чт } Line(x+10*dx,y-5*dy,x+10*dx,y-10*dy); { осн стк } MoveTo(x+17*dx,y-3*dy); LineTo(x+10*dx,y-10*dy); LineTo(x,y-2*dy); SetColor(OldColor); { восст новить текущий цвет } end; begin grDriver := VGA; { режим VGA} grMode:=VGAHi; { р зрешение 640х480} grPath:='d:\bp\bgi'; { др йвер, ф йл EGAVGA.BGI, н ходится в к т логе d:\bp\bgi } InitGraph(grDriver, grMode,grPath); ErrCode := GraphResult; if ErrCode <> grOk then Halt(1);
x:=10; y:=200; color:=LightGray; SetBkColor(Blue); bkcolor:=GetBkColor; repeat Titan(x,y,color); { н рисов ть кор бль } Delay(100); Titan(x,y,bkcolor); { стереть кор бль } PutPixel(x,y,color); { след от кор бля } x:=x+2; until (x>500); OutTextXY(10,10,'Рейс з вершен!'); readln; CloseGraph; End. 11 ОСНОВНЫЕ ПРИНЦИПЫ ООП
Руководящая идея объектно-ориентированного программирования (ООП) заключается в стремлении связать данные с обрабатывающими эти данные процедурами в единое целое - объект. Характерной чертой объектов является инкапсуляция (объединение) данных и алгоритмов их обработки, в результате чего и данные, и процедуры во многом теряют самостоятельное значение. Фактически объектно-ориентированное программирование можно рассматривать как модульное программирование нового уровня, когда вместо во многом случайного, механического, объединения процедур и данных акцент делается на их смысловую связь. Какими мощными средствами располагает объектно-ориентированное программирование, наглядно демонстрирует библиотека Turbo Vision, входящая в комплект поставки Турбо Паскаля Объектно-ориентированное программирование основано на «трех китах» - трех важнейших принципах, придающих объектам новые свойства. Этими принципами являются инкапсуляция, наследование и полиморфизм. Инкапсуляция Инкапсуляция - есть объединение в единое целое данных и алгоритмов обработки этих данных. В рамках ООП данные называются полями объекта, а алгоритмы - объектными методами. Инкапсуляция позволяет в максимальной степени изолировать объект от внешнего окружения. Она существенно повышает надежность разрабатываемых программ, т.к. локализованные в объекте алгоритмы обмениваются с программой сравнительно небольшими объемами данных, причем количество и тип этих данных обычно тщательно контролируются. В результате замена или модификация алгоритмов и данных, инкапсулированных в объект, как правило, не влечет за собой плохо прослеживаемых последствий для программы в целом (в целях повышения защищенности программ в ООП почти не используются глобальные переменные). Другим немаловажным следствием инкапсуляции является легкость обмена объектами, переноса их из одной программы в другую. Можно сказать, что ООП «провоцирует» разработку библиотек объектов, таких как Turbo Vision.
Наследование Наследование есть свойство объектов порождать своих потомков. Объект-потомок автоматически наследует от родителя все поля и методы, может дополнять объекты новыми полями и заменять (перекрывать) методы родителя или дополнять их. Принцип наследования решает проблему модификации свойств объекта и придает ООП в целом исключительную гибкость. При работе с объектами программист обычно подбирает объект, наиболее близкий по своим свойствам для решения конкретной задачи, и создает одного или нескольких потомков от него, которые «умеют» делать то, что не реализовано в родителе. Последовательное проведение в жизнь принципа «наследуй и изменяй» хорошо согласуется с поэтапным подходом к разработке крупных программных проектов и во многом стимулирует такой подход. Полиморфизм Полиморфизм - это свойство родственных объектов (т.е. объектов, имеющих одного общего родителя) решать схожие по смыслу проблемы разными способами. В рамках ООП поведенческие свойства объекта определяются набором входящих в него методов. Изменяя алгоритм того или иного метода в потомках объекта, программист может придавать этим потомкам отсутствующие у родителя специфические свойства. Для изменения метода необходимо перекрыть его в потомке, т.е. объявить в потомке одноименный метод и реализовать в нем нужные действия. В результате в объекте-родителе и объекте-потомке будут действовать два одноименных метода, имеющие разную алгоритмическую основу и, следовательно, придающие объектам разные свойства. Это и называется полиморфизмом объектов. В Турбо Паскале полиморфизм достигается не только описанным выше механизмом наследования и перекрытия методов родителя, но и их виртуализацией (см. ниже), позволяющей родительским методам обращаться к методам потомков Пример-лекция иллюстрирующий основные принципы объектно-ориентированного программирования (на примере двух работающих модулей и тексте программы) Обработанный материал из учебного пособия Фаронов В.В. “Turbo Pascal 7.0” Учебное пособие. Начальный курс. Нолидж. Москва. 2001 г. (с.179-194)
Программа создает на экране ряд графических изображений (точки, окружность, линия, квадрат) и может перемещать эти изображения по экрану.
Вид создаваемого программой экрана.
Для перемещения изображений в программе будут использоваться: клавиши управления курсором, клавиши Ноте, End, PgUp, PgDn (для перемещения по диагональным направлениям), клавиша Tab для выбора перемещаемого объекта. Выход из программы - клавиша Esc.
Техническая реализация программы требует использования средств двух стандартных библиотек - CRT и GRAPH. Для решения задачи создадим два модуля GraphObj и GraphApp. В Модуле GraphObj описываются все используемые объекты. Модуль компилируется в библиотечный файл GraphObj.tpu. Основная программа будет использовать модуль GraphObj с описанием объектов.(Далее текст рабочего модуля. Файл: GraphObj.pas. )
Unit GraphObj; Interface type TGraphObj=object {объект – родитель в рамках которого инкапсулированы поля и методы, общие для всех остальных объектов<=Свойство объектов-инкапсуляция } Private { Поля объекта будут скрыты от пользователя. Объект не может менять координаты реперной точки и цвет фигуры. Описания полей ничем не отличаются от описания обычных переменных.} x,y:integer; {Координаты реперной точки} color:word; {Цвет фигуры} Public {Методы объекта будут доступны пользователю. Директива public отменяет действие директивы private. Для описания методов используются процедуры и функции, конструкторы и деструкторы.} Constructor Init(aX,aY:integer; aColor:word); { Конструкторы предназначены для создания конкретного экземпляра объекта обращение к конструктору должно предшествовать обращению к любому виртуальному методу. Типичное действие, реализуемое конструктором, состоит в наполнении объектных полей конкретными значениями.} Procedure Draw (aColor:word);virtual; {Вычерчивает объект заданным цветом aColor В объекте TGraphObj процедура Draw определена как виртуальная («воображаемая»). Абстрактный объект TGraphObj не предназначен для вывода на экран, однако наличие процедуры Draw в этом объекте говорит о том, что любой потомок TGraphObj должен иметь собственный метод Draw, с помощью которого он может показать себя на экране. (<=Свойство объектов – полиморфизм).При трансляции объекта, содержащего виртуальные методы, создается так называемая таблица виртуальных методов (ТВМ), количество элементов которой равно количеству виртуальных методов объекта. В этой таблице будут храниться адреса точек входа в каждый виртуальный метод. В нашем примере ТВМ объекта TGraphObj хранит единственный элемент - адрес метода Draw.}
Procedure Show; {Показывает объект - вычерчивает его цветом Color. Для реализации своих действий и Hide, и Show обращаются к виртуальному методу Draw.}
Procedure Hide; {Прячет объект - вычерчивает его цветом фона}
Procedure MoveTo (dX,dY:integer); {Перемещает объект в точку с координатами X+dX и Y+dY. Если потомок TGraphObj (например, TLine) хочет переместить себя на экране, он обращается к родительскому методу MoveTo. В этом методе сначала с помощью Hide объект стирается с экрана, а затем с помощью Show показывается в другом месте.}
end; {Конец описания объекта TGraphObj}
{Далее мы создадим несколько потомков объекта TGraphObj. Каждый из них будет иметь собственный конструктор, с помощью которого ТВМ каждого потомка настраивается так, чтобы ее единственный элемент содержал адрес нужного метода Draw. Такая процедура называется поздним связыванием объекта. Позднее связывание позволяет методам родителя обращаться к виртуальным методам своих потомков и использовать их для реализации специфичных для потомков действий. (Это опять - полиморфизм)(В нашем примере она совпадает с координатами точки в описываемом ниже объекте TPoint, с центром окружности в объекте TCircle, первым концом прямой в объекте Liпе и с левым верхним углом прямоугольника в объекте TRect.)}
{Сначала описываются все объекты (совокупность полей и методов (данные и процедуры) присущих только данному объекту), а далее обычно в разделе Implementation объектного модуля описываются соответствующие объектам процедуры и функции (т.е. соответствующие методы (действия над объектами)}
TPoint = object(TGraphObj) {Описание объекта TPoint. Потомки объекта TGraphObj: TPoint, TLine, TCircle наследуют поля и методы родительского объекта. TRect – потомок объекта TLine <=Свойство объектов – наследование. }
Procedure Draw (aColor:word);virtual; end;
TLine= object(TGraphObj) dx,dy:integer; {Приращения координат второго конца}
Constructor Init(X1,Y1,X2,Y2:integer; aColor:word); {Инициализируется новый объект TLine который является наследником объекта TGraphObj,но скоро будет «отцом» объекта TRect,а новый обьект будет по прежнему использовать «дедовскую» виртуальную процедуру Draw. По-этому вместо слова Procedure пишем Constructor } Procedure Draw (aColor:word);virtual; end; {Поскольку вызов MoveTo ,будет происходить в рамках объекта TLine, используется ТВМ этого объекта и вызывается его метод Draw, вычерчивающий прямую. Если бы перемещалась окружность, ТВМ содержала бы адрес метода Draw объекта TCircle и визуализация-стирание объекта осуществлялась бы с помощью этого метода. (Следует выполняется третье свойство объектов - полиморфизм)}
TCircle = object(TGraphObj) r:integer; {вводится новое поле объекта, которое отсутствует у предка - радиус} Constructor Init(aX,aY,aR:integer; aColor:word);{Перед использованием «виртуальной» процедуры Draw при инициализации нового объекта всегда заменяем слово Procedure на слово Constructor. Так как выводиться на экран будет новый объект. То есть меняем ссылку с указанием параметров вывода предыдущего объекта на новые. В данном случае на следующем шаге Draw выведет на экран – окружность. } Procedure Draw (aColor:word);virtual; end;
TRect= object(TLine) {TRect – потомок объекта TLine} Procedure Draw (aColor:word);virtual; end;
{Описание методов (- «телодвижений» объектов) производится обычным для Паскаля способом в любом месте раздела описаний (обычно в разделе Implementation объектного модуля), но после описания объекта.}
Implementation {Исполняемая часть содержит описания всех объектных методов}
Uses Graph; Constructor TGraphObj.Init; {Инициализируется родительский объект TGraphObj. Далее используется виртуальный метод Draw, по-этому вместо слова Procedure используется слово Constructor } Begin X:=aX; Y:=aY; color:=aColor; end;
Procedure TGraphObj.Draw; {Эта процедура в родительском объекте ничего не делает, поэтому экземпляры TGraphObj не способны отображать себя на экране. Чтобы потомки объекта TGraphObj были способны отображать себя, они должны перекрывать этот метод (стоять раньше «телодвижений» потомков)} Begin end;
Procedure TGraphObj.Show; {Процедура вывода на экран «виртуального» объекта (нарисованного Draw-ом с соответствующими указаниями на вид обекта (окружность это или линия или точка и т.п.))соответствующим объекту цветом color }
Begin Draw(color); end;
Procedure TGraphObj.Hide; {Процедура вывода на экран виртуального объекта цветом фона }
Begin Draw(GetBkcolor); end;
Procedure TGraphObj.MoveTo; {Процедура перемещения виртуального объекта на dX и dY }
Begin Hide; X:=X+dX; Y:=Y+dY; Show; end;
{ Отмечу два обстоятельства. Во-первых, при описании методов имя метода дополняется спереди именем объекта, т.е. используется составное имя метода. Это необходимо по той простой причине, что в иерархии родственных объектов любой из методов может быть перекрыт в потомках. Составные имена четко указывают принадлежность конкретной процедуры. Во-вторых, в любом объектном методе можно использовать инкапсулированные поля объекта почти так, как если бы они были определены в качестве глобальных переменных. Например, в-конструкторе TGraph.Init переменные в левых частях операторов присваивания представляют собой объектные поля и не должны заново описываться в процедуре. Более того, описание
Constructor TGraphObj.Init; var X,Y: Integer; {Ошибка!} Color: Word; {Ошибка!} begin … … end;
вызовет сообщение о двойном определении переменных X, Y и Color (в этом и состоит отличие в использовании полей от глобальных переменных: глобальные переменные можно переопределять в процедурах, в то время как объектные поля переопределять нельзя). }
Procedure TPoint.Draw; {В новом объекте TPoint можно использовать любые методы объекта-родителя TGraphObj. Например, вызвать метод MoveTo, чтобы переместить изображение точки на новое место. В этом случае родительский метод TGraphObj.MoveTo будет обращаться к методу TPoint.Draw, чтобы спрятать и затем показать изображение точки. Такой вызов станет доступен после обращения к конструктору Init объекта TPoint, который нужным образом настроит ТВМ объекта. Если вызвать TPoint.Draw до вызова Init, его ТВМ не будет содержать правильного адреса и программа «зависнет».}
Begin PutPixel (X,Y,Color); end;
Constructor Tline.Init; {В конструкторе TLine.Init для инициации полей X, Y и Color, унаследованных oт родительского объекта, вызывается унаследованный конструктор TGraphObj.Init, для чего используется зарезервированное слово inherited (англ.- унаследованный). Здесь слово Procedure заменено на Constructor так как далее при использовании Draw начальная точка линии будет выводиться как точка (или из точки) уже проинициализированной ранее при инициализации родительского объекта TGraphObj (т.е. используются «виртуальные» параметров вывода на экран «точки», на которую указывает ТВМ начальной установки родителя, при рисовании линии) }
Begin Inherited Init (X1,Y1,aColor); {Инициируем поля dX и dY} dX:=X2-X1; dY:=Y2-Y1; end;
Procedure TLine.Draw; Begin SetColor(Color); {Устанавливаем цвет Color} Line(X,Y,X+dX,Y+dY); {Вычерчиваем линию}
end;
Constructor TCircle.Init; {Вызывает унаследованный конструктор TGraphObj для инициации полей X, Y и Color. Затем инициирует поля dX и dY} {Далее используется виртуальный метод Draw, с начальными параметрами родителя и унаследованными полями при инициализации родителя, по - этому вместо слова Procedure используется слово Constructor }
Begin Inherited Init (aX,aY,aColor); {Вызываем унаследованный конструктор} R:=aR; {новое поле- радиус} end; Procedure TCircle.Draw;{Процедура – выводит окружность. Виртуальные параметры для центра окружности унаследованы от родителя, а при инициализации окружности добавлен еще и радиус aR} Begin SetColor(aColor); Circle(X,Y,R); end;
{ В объекте TRect, с помощью которого создается и перемещается прямоугольник, учтем то обстоятельство, что для задания прямоугольника требуется указать четыре целочисленных параметра, т.е. столько же, сколько для задания линии. Поэтому объект TRect удобнее породить не от TGraphObj, а от TLine, чтобы использовать его конструктор Init}
Procedure TRect.Draw; {Процедура рисования прямоугольника} Begin SetColor(aColor); Rectangle(X,Y,X+dX,Y+dY); {Вычерчиваем прямоугольник} end; end. {окончание самостоятельного модуля GraphObj } { ИСПОЛЬЗОВАНИЕ ОБЪЕКТОВ} {Самостоятельный модуль GraphApp. Располагается в файле GraphApp.pas. После компиляции расположить в каталоге Units под именем GraphApp.tpu.} { Идею инкапсуляции полей и алгоритмов можно применить не только к графическим объектам, но и ко всей программе в целом. Ничто не мешает нам создать объект-программу и «научить» его трем основным действиям: инициации (Init), выполнению основной работы (Run) и завершению (Done). На этапе инициации экран переводится в графический режим работы и создаются и отображаются графические объекты (100 экземпляров TPoint и по одному экземпляру TLine, TCircle, TRect). На этапе Run осуществляется сканирование клавиатуры и перемещение графических объектов. Наконец, на этапе Done экран переводится в текстовый режим и завершается работа всей программы. Назовем объект-программу именем TGraphApp и разместим его в модуле GraphApp (пока не обращайте внимание на точки, скрывающие содержательную часть модуля - позднее будет представлен его полный текст)}
Unit GraphApp; Interface Uses GraphObj; const Npoints=100; {Количество точек}
Type {Объект-программа} TGraphApp=Object Points:array [1..NPoints] of TPoint; {Массив точек} Line:Tline; {Линия} Rect:TRect; {Прямоугольник} Circ:TCircle; {Окружность} ActiveObj:Integer; {Активный объект}
Procedure Init; Procedure Run; Procedure Done; Procedure ShowAll; Procedure MoveActiveObj(dX,dY:integer); end;
Implementation Uses Graph,CRT; Procedure TGraphApp.Init; {Инициирует графический режим работы экрана. Создает и отображает NPoints экземпляров объекта TPoint, а также экземпляры объектов TLine, TCircle и Trect}
Var D,R,Err,k:integer; Begin {Инициируем графику} D:=detect; {Режим автоматического определениятипа графического адаптера} InitGraph(D,R,'d:\tp7\bgi'); {Инициируем графический режим. Текстовая строка должна задавать путь к каталогу с графическими драйверами} Err:=GraphResult; {Проверяем успех инициации графики}
If Err<>0 then begin GraphErrorMsg(Err); Halt; end; {Создаем точки} for k:=1 to Npoints do Points[k].Init(Random(GetMaxX),Random(GetMaxY),Random(15)+1); Line.Init(GetMaxX div 3, GetMaxY div 3,2*GetMaxX div 3, 2* GetMaxY div 3, LightRed); Circ.Init (GetMaxX div 2, GetMaxY div 2, GetMaxY div 5,White); Rect.Init(2*GetMaxX div 5, 2*GetMaxY div 5,3*GetMaxX div 5, 3* GetMaxY div 5, Yellow); ShowAll; {Показываем все графические объекты}
ActiveObj:=1; {Первым перемещаем прямоугольник} end;
Procedure TGraphApp.Run; {Выбирает объект с помощью Tab и перемещает его по экрану} var stop:Boolean; {Признак нажатия Esc} const D=5; {Шаг смещения фигур} Begin Stop:=False; {Цикл опроса клавиатуры} repeat case ReadKey of {Читаем код нажатой клавиши}
#27: Stop:=True; {Нажата Esc} #9:begin {Нажата Tab} inc(ActiveObj); if ActiveObj>3 then ActiveObj:=1 end; #0: case ReadKey of #71: MoveActiveObj(-D,-D); {Влево и вверх} #72: MoveActiveObj(0, -D); {Вверх} #73: MoveActiveObj(D, -D); {Вправо и вверх} #75: MoveActiveObj(-D, 0); {Влево} #77: MoveActiveObj(D, 0); {Вправо} #79: MoveActiveObj(-D, D); {Влево и вниз} #80: MoveActiveObj(0, D); {Вниз} #81: MoveActiveObj(D, D); {Вправо и вниз}
end end; ShowAll; Until Stop; end; {TGraphApp.Run} {————————} Procedure TGraphApp.Done; {Закрывает графический режим} begin CloseGraph; end; {TGraphApp.Done} Procedure TGraphApp.ShowAll; {Показывает все графические объекты} Var k:integer; begin for k:=1 to Npoints do Points[k].Show; Line.Show; Rect.Show; Circ.Show; end;
Procedure TgraphApp.MoveActiveObj; {Перемещает активный графический объект} begin case ActiveObj of 1: Rect.MoveTo(dX,dY); 2: Circ.MoveTo(dX,dY); 3: Line.MoveTo(dX,dY); end end; end.
{Основная программа будет предельно простой:}
Program Graph_Objects; Uses GraphApp; var App: TGraphApp; { Получив это указание, компилятор зарезервирует нужный объем памяти для размещения всех полей объекта TGraphApp. } Begin App.Init; App.Run; App.Done end. {Согласитесь, что введение лишь шести зарезервированных слов, из которых действительно необходимыми являются три (object, constructor и virtual}, весьма небольшая плата за мощный инструмент создания современного программного обеспечения.} 7 Основные понятия программирования Вычислительная машина - комплекс устройств и программных средств для автоматической обработки данных при решении математических и в основном информационных задач. Управление машиной, обработка данных осуществляется по алгоритмам, которые вводится в машину в виде программ. Программы и данные хранятся в памяти машины. Основное устройство, которое "понимает" программы – центральный процессор. Во многих источниках синонимом термина "вычислительная машина" является термин компьютер. Программа - упорядоченная совокупность указаний некоторой вычислительной системе, в результате выполнения которой получается требуемый результат. В то же время, программа - это формализованная запись алгоритма на конкретном языке программирования. В конечном счете, программу должен понять исполнитель - процессор. Можно сразу написать программу на языке процессора, это будет программа в машинных командах. На заре программирования так и делали, в настоящее время программу обычно пишут на языках высокого уровня, а затем переводят (транслируют) ее в машинные команды. Программа на языке высокого уровня - это обычный текст, оформленный в соответствии с требованиями языка. орпропорп орлор оро пр Язык программирования Формальный язык, обеспечивающий удобное описание конкретных проблем, которые формулируются человеком. На языке программирования пишется программа, которая позволяет, при ее выполнении компьютером (вычислительной системой), получить конкретные результаты. Язык программирования состоит из двух составляющих: синтаксиса и семантики. Синтаксис - набор правил построения слов, конструкций и структур текста в языке или системе. Некоторые авторы включают в синтаксис и алфавит. Семантика - смысл каждой синтаксической конструкции в языке или системе. В языках программирования транслятор превращает синтаксические построения в команды, понятные операционной системе и процессору. Смысловые ошибки транслятор не выявляет, их поиск осуществляет человек в процессе отладки, тестирования и даже эксплуатации. От греч. semantikos - обозначающий. Транслятор Переход от языковых конструкций к машинным командам осуществляет транслятор языка.
Программа, производящая трансляцию программы с одного языка программирования в другой. В языках программирования транслятор превращает синтаксические построения в команды, понятные операционной системе и процессору. Смысловые ошибки транслятор не выявляет, их поиск осуществляет человек в процессе отладки, тестирования и даже эксплуатации. Трансляция Процесс преобразования программы, написанной на одном языке программирования, в программу на другом языке. Как правило, трансляция - это создание программы в машинных кодах, которую можно выполнять.
Различают два вида трансляции: 1) компиляцию, при которой результат получается в виде готовой программы, выполняемой независимо от исходного текста программы; 2) интерпретацию, при которой трансляция и выполнение программы происходит покомандно. От лат. translatio - передача. Программирование Теоретическая и практическая деятельность по созданию программного обеспечения. Само программирование - итерационный процесс, который состоит из: понимания задачи, разработки алгоритма, модулей, отладки модулей, решения тестовой задачи, сравнения результатов, уточнения постановки задачи и последующей итерации. Программный продукт - программный комплекс в совокупности с сопровождающими документами, готовый к непосредственному использованию. Продуктом такой комплекс называется по аналогии с любым производственным товаром, полностью готовым для потребления.
Классификации языков программирования
Языки программирования имеют разные классификации. Приведем только некоторые из них.
Классификация по уровням: Языки программирования разделяются на языки высокого и низкого уровня в соответствии с тем, в каких терминах следует описывать задачу. Если язык близок к естественному, он называется языком высокого уровня, если ближе к машинным командам, - языком низкого уровня. Например, языки: Си, Бейсик, Паскаль, Пролог относятся к языкам высокого уровня, а язык ассемблера - язык низкого уровня.
Классификация по стилю программирования: Стиль - совокупность правил, лежащих в основе синтаксиса и семантики языка программирования. Различают следующие стили: неструктурный; структурный; логический; объектно-ориентированный; функциональный.
Рассмотрим перечисленные стили подробнее.
Неструктурное программирование - допускает использование в явном виде команды безусловного перехода (в большинстве языков GOTO). Типичные представители неструктурных языков - ранние версии Бейсика и Фортрана. Данный стиль вызван особенностями выполнения машиной программы в кодах и унаследован от программ на языке ассемблера, поскольку там команда перехода является обязательной. Однако в языках высокого уровня наличие команды перехода влечет за собой массу серьезных недостатков: программа превращается в "спагетти" с бесконечными переходами вверх-вниз, ее очень трудно сопровождать и модифицировать. Фактически неструктурный стиль программирования не позволяет разрабатывать большие проекты. Ранее широко практиковавшееся первоначальное обучение программированию на базе неструктурного языка (обычно Бейсика) приводило к огромным трудностям при переходе на более современные стили. Как отмечал известный голландский ученый Э. Дейкстра, "программисты, изначально ориентированные на Бейсик, умственно оболванены без надежды на исцеление".
Структурный стиль (структурное программирование) был разработан в середине 60-х - начале 70-х гг.
В его основе лежат две идеи: Задача разбивается на большое число мелких подзадач, каждая из которых решается своей процедурой или функцией (декомпозиция задачи). 2) проектирование программы идет по принципу сверху вниз: сначала определяются необходимые для решения программы модули, их входы и выходы, а затем уже эти модули разрабатываются. Такой подход вместе с локальными именами переменных позволяет разрабатывать проект силами большого числа программистов. Как доказал Э. Дейкстра, любой алгоритм можно реализовать, используя лишь три управляющие конструкции: последовательное выполнение, ветвление, цикл. Данное обстоятельство позволяет при наличии соответствующих операторов исключить из языка команду перехода GOTO.
Будем применять в основном структурный стиль.
Логическое программирование - представляет собой попытку возложить на программиста только постановку задачи, а поиски путей ее решения предоставить транслятору. Логические языки (Пролог, Симула) имеют специальные конструкции для описания объектов и связей между ними. Например, если дано: БРАТЬЯ ИМЕЮТ ОДНОГО ОТЦА ДЖОН - ОТЕЦ ДЖЕКА МАЙК - БРАТ ДЖЕКА то система логического программирования должна сделать вывод: ДЖОН - ОТЕЦ МАЙКА. Хотя работы по логическому программированию ведутся с 50-х гг., в настоящее время данное направление несколько потеряло свою актуальность в связи с отсутствием реальных результатов, поскольку большинство реализованных на принципах логического программирования систем оказались практически непригодными. Объектно-ориентированное (ОО) программирование, разработанное в середине 70-х гг. Керниганом и Риччи и реализованное в ОО-версиях языков Си и Паскаль, представляет собой отображение объектов реального мира, их свойств (атрибутов) и связей между ними при помощи специальных структур данных. Структурное программирование подразумевает наличие ряда встроенных структур данных: целых, вещественных и строковых переменных, массивов, записей - при помощи которых и производится отображение свойств объектов реального мира. При объектно-ориентированном подходе для объекта создается своя структура данных (класс), содержащая как свойства объекта (поля), так и процедуры для управления объектом (методы). Функциональный стиль В основе функционального стиля лежит понятие функции как "черного ящика", имеющего вектор параметров (аргументов) на входе и результат r (скаляр) на выходе: Допустим случай , т.е. вектор параметров отсутствует. В функциональных языках программирования отсутствуют операторы: все действия, в том числе и управляющие конструкции, выполняются при помощи вызовов функций. Поскольку каждая функция возвращает значение, то: Каждую функцию можно подставить в качестве аргумента другой функции, что позволяет записывать сложные выражения в функциональной форме. Одним из первых функциональных языков стал язык Лисп, созданный в конце 50-х гг. как язык искусственного интеллекта. К языкам искусственного интеллекта (сокращенно обозначается AI - artificial intelligence) относят такие языки, которые способны в зависимости от набора исходных данных модифицировать алгоритм работы, т.е. "на ходу" менять программу (поговорка гласит, что на языках искусственного интеллекта программируют те, кому не хватает интеллекта естественного).
Языки высокого уровня могут быть декларативными (например, Пролог, ЛИСП) и процедурно-ориетированными (например, Си, Бейсик, Паскаль, Ада). Процедурные языки развиваются в объектно-ориентированные.
Программное обеспечение Совокупность программ для компьютера образует программное обеспечение (ПО).
По функциональному признаку различают следующие виды ПО: системное; прикладное.
Под системным (базовым) понимается программное обеспечение, включающее в себя операционные системы, сетевое ПО, сервисные программы, а также средства разработки программ (трансляторы, редакторы связей, отладчики и пр.).
Основные функции операционных систем (ОС) заключаются в управлении ресурсами (физическими и логическими) и процессами вычислительных систем. Сетевое ПО - программное обеспечение для поддержки работы объедененных в сеть компьютеров. К сервисным программам относятся: утилиты, драйверы, файловые оболочки и т.п.
К средствам разработки программ относится среда программирования - Turbo Pascal 7.0 (подробнее рассмотрим на следующих лекциях и практических занятиях) и другие продукты создания ПО.
Прикладным называется ПО, предназначенное для решения определенной целевой задачи из проблемной области. Часто такие программы называют приложениями. Спектр проблемных областей в настоящее время весьма широк и включает в себя по крайней мере следующие: промышленное производство, инженерную практику, научные исследования, медицину, управление (менеджмент), делопроизводство, издательскую деятельность, образование и т.д.
Приложения и системные программы могут создаваться и в среде программирования TP7.0.
Среда программирования Turbo Pascal 7.0
Среда программировании TurboPascal 7.0 представляет собой интегрированную среду разработки компьютерных программ с использованием языка программирования Pascal. Следует отметить, что язык программирования, используемый этой системой, значительно шире и мощнее так называемого стандартного языка Pascal.
Turbo Pascal - это больше, чем просто быстрый компилятор Паскаль. Turbo Pascal -это эффективный компилятор Паскаль с интегрированной усовершенствованной средой, легкой для изучения и использования.
В среду программирования Turbo Pascal входят: редактор, компилятор, редактор связей и отладчик встроенная контекстно-ориентированная справочная система (Подробнее среда программирования будет изучена в ходе лекционных и практических занятий в процессе создания программных продуктов)
Язык программирования TurboPascal 7.0 является процедурным языком высококо уровня. Позволяет использовать: - структурный стиль программирования, средства ООП (Библиотека Turbo-Vision); средства неструктурного программирования (подключаемые модули на языке Ассемблер).
Т.Е. среда программирования TP7.0. позволяет создавать как приложения, так и системные программы.
9
Программирование графики в среде Thrbo Pascal 7.0.
Базовые процедуры и функции. В библиотеке Graph вывод точки осуществляется процедурой PutPixel(X, Y: integer; Color:word), где X, Y - экранные координаты расположения точки, Color - ее цвет (от 0 до 15). Для определения цвета точки в конкретной позиции экрана служит функция GetPixel(X, Y: integer): word. Из точек возможно построение линий. Это выполняет процедура Line(X1, Y1, X2, Y2), где X1, Y1 - координаты начала, X2, Y2 - координаты конца линии.
Например: Line(1, 1, 200, 1). Для черчения линий применяются также еще две процедуры: LineTo(X, Y) - строит линию из точки текущего положения указателя в точку с координатами X, Y; LineRel(dX, dY) - проводит линию от точки текущего расположения указателя в точку СРx+dX, CPy+dY, где CPx и CPy - текущие координаты СР.
Установка стиля (тонкие, широкие, штриховые пунктирные линии и т.д.) производится процедурой: SetLineStyle (LineStyle: word; Pattern: word; Thickness: word). Параметр LineStyle устанавливает тип строки, который может быть задан поименованной константой или соответствующим ей цифровым значением из Pattern - образец, Thickness - толщину линии, определяемую константами из Табл.2.
Таблица 1 Типы линий КОНСТАНТА | ЗНАЧЕНИЕ | ОПИСАНИЕ | SolidLn | 0 | Непрерывная линия | DottedLn | 1 | Линия из точек | CentedLn | 2 | Линия из точек и тире | DashedLn | 3 | Штриховая линия | UserLn | 4 | Тип пользователя |
Таблица 2 Толщина линии КОНСТАНТА | ЗНАЧЕНИЕ | ОПИСАНИЕ | NormWidth | 1 | Нормальная толщина | ThickWidth | 3 | Жирная линия |
Процедура GetLineSettings (Var lineInfo: LineSettingsType) возвращает текущий стиль, образ и толщину линии, установленные SetLineStyle. Работа с цветом
В видеобуфере (видеопамяти) для хранения информации о цвете пиксела отводится фиксированное количество битов памяти. Размер палитры и ее организация зависят от типа используемого видеоадаптера. При использовании видеокарты EGA/VGA для установки цвета пиксела используется 6 битов. Для формирования цвета используется система RrGgBb, где RGB - красный, зеленный и голубой цвета нормальной яркости, а rgb - те же цвета, но яркость их в два раза меньше. Для EGA/VGA-карт драйвер EGAVGA..BGI устанавливает 54 цвета. В Таблице 4 приведены 16 основных цветов. Таблица 3 Цвета видеокарты EGA/VGA КОНСТАНТА | ЗНАЧЕНИЕ | КОД | ЦВЕТ | EGABlack | 0 | 000000 | Черный | EGABlue | 1 | 000001 | Синий | EGAGreen | 2 | 000010 | Зеленый | EGACyan | 3 | 000011 | Сине-зеленый | EGARed | 4 | 000100 | Красный | EGAMagenta | 5 | 000101 | Красно-синий | EGABrown | 6 | 000110 | Коричневый | EGALightGray | 7 | 000111 | Светло-серый | EGADarkGray | 56 | 111000 | Темно-серый | EGALightBlue | 57 | 111001 | Ярко-синий | EGALightGreen | 58 | 111010 | Ярко-зеленый | EGALightCyan | 59 | 111011 | Яркий сине-зеленый | EGALightRed | 60 | 111100 | Ярко-красный | EGALightMagenta | 61 | 111101 | Яркий красно-синий | EGAYellow | 62 | 111110 | Желтый | EGAWhite | 63 | 111111 | Белый |
До того момента, пока цвет не определен, для вывода используется цвет, имеющий максимальный номер палитры и фон, устанавливаемый по минимальному номеру. Для EGA и VGA адаптеров в качестве фона может быть задано любое целое число в диапазоне 0-63.
Любой цвет для создания фигур и вывода текста может быть установлен с помощью процедуры SetColor(Color: word).
Фон задается процедурой SetBkColor(Color: word).
Для проверки максимально допустимого количества используемых программой цветов служит процедура GetMaxColor: word, возвращающая максимальное значение кода цвета в палитре минус 1. Процедуры GetColor: word - возвращают номера текущего цвета для выводимых элементов GetBKColor: word возвращают номера текущего цвета для фона. Работа с текстом Для вывода в графическом режиме на экран текста используются процедуры: OutText(TextString: string), выводящая строку текста с текущего положения СР, и OutText(X, Y, Text), где X, Y - координаты точки начала вывода текста, Text - константа или переменная типа string. Определенные проблемы создает вывод численных данных, так как в модуле Graph нет специально предназначенных для этого процедур.
Установить нужный шрифт вывода можно процедурой SetTextStyle(Font: word; Direction: word; CharSize: word), где Font - выбранный шрифт, задаваемый константами из Табл. 5, Direction - направление вывода (горизонтальное или вертикальное), задаваемое константами из Табл.6, CharSize - размер выводимых символов. Таблица 4 Типы шрифтов КОНСТАНТА | ЗНАЧЕНИЕ | ОПИСАНИЕ | DefaultFont | 0 | 8x8-битовый шрифт | TriplexFont | 1 | Штриховой шрифт | SmallFont | 2 | Малый шрифт | SansSerifFont | 3 | Шрифт SansSerif | GothicFont | 4 | Готический шрифт |
Таблица 5 Ориентация текста КОНСТАНТА | ЗНАЧЕНИЕ | ОПИСАНИЕ | HorizDir | 0 | Слева направо | VertDir | 1 | Снизу вверх |
Для выравнивания текста необходимо использовать процедуру SetTextJastify (Horiz, Vert: word). Выравнивание относительно СР выполняется по вертикали и по горизонтали с помощью параметров Horiz и Vert (Табл.7). Таблица 6 Типы линий КОНСТАНТА | ЗНАЧЕНИЕ | ОПИСАНИЕ | LeftText | 0 | Выровнять влево | CenterText | 1 | Центрировать по горизонтали | RightText | 2 | Выровнять вправо | BottomText | 0 | Выровнять вниз | CenterText | 1 | Центрировать по вертикали | TopText | 2 | Выровнять вверх |
Пример: В следующем примере приведена программа, выводящая заставку. Заставка представляет собой фразу "Демонстрация заставки", напечатанную коричневыми буквами обычного размера и синими буквами увеличенного размера; по периметру экрана нарисованы три разноцветных прямоугольника линиями различной толщины и стиля. Program Zast; Uses Graph, Crt; Var DriverVar, ModeVar :integer; xm,ym :integer; c :char; Begin {Инициализация графического режима } DriverVar:=Detect; InitGraph(DriverVar, Modevar, ‘ ’); xm:=GetMaxX; ym:=GetMaxY; { Вывод прямоугольника, нарисованного зеленой сплошной жирной линией } SetColor(2); SetLineStyle(0,0,2); Rectangle(0,0,xm,ym); { Вывод прямоугольника, нарисованного голубой сплошной тонкой линией } SetColor(3); SetLineStyle(0,0,1); Rectangle(20,20,xm-20,ym-20); { Вывод прямоугольника, нарисованного красной пунктирной тонкой линией } SetColor(4); SetLineStyle(3,0,1); Rectangle(40,40,xm-40,ym-40); { Вывод синего текста с увеличенным размером букв } SetColor(1); SetTextStyle(0,0,2); OutTextXY(100,60,'Демонстрация заставки'); { Вывод коричневого текста с обычным размером букв } SetColor(6); SetTextStyle(0,0,1); OutTextXY(150,100,'Демонстрация заставки'); { Ожидание нажатия клавиши } c:=readkey; { Закрытие графического режима } CloseGraph; End. Построение геометрических фигур Библиотека Graph содержит ряд процедур, которые на основе задаваемых параметров формируют различные геометрические фигуры. Рассмотрим некоторые из них: Для построения прямоугольных фигур существуют несколько процедур. Rectangle (X1, Y1, X2, Y2: integer) - процедура вычерчивания одномерного прямоугольника, где X1, Y1 - координаты левого верхнего угла, X2, Y2 - координаты правого нижнего угла прямоугольника.
Более удобные для восприятия закрашенные прямоугольники можно строить с использованием процедуры: Bar(x1, y1, x2, y2: integer) - процедура рисует закрашенный столбец. Цвет закраски устанавливается с помощью SetFillStyle.
Процедура Bar3D(x1, y1, x2, y2: integer; Depth: word; Top: boolean) вычерчивает трехмерный закрашенный прямоугольник. При этом используются тип и цвет закраски, установленные с помощью процедуры SetFillStyle. Параметр Depth представляет собой число пикселов, задающих глубину трехмерного контура. Чаще всего его значение принимают равным четверти ширины прямоугольника: Depth:=(X2 - X1) DIV 4. Параметр Top определяет нужно ли строить над прямоугольником вершину (Top:=True) или нет (Top:=False).
Процедура вычерчивания окружности текущим цветом имеет следующий вид: Circle(X,Y,Radius: word), где X, Y - координаты центра окружности, а Radius - радиус.
Что бы создать закрашенный эллипс, используется процедура FillEllipse(X,Y:integer;xR,yR:word), где X, Y - центр эллипса, xR и yR - горизонтальная и вертикальная оси. Заполнитель устанавливается процедурой SetFillStyle.
В следующем примере приведена программа, рисующая на желтом фоне синего снеговика.
Program SnowMan; Uses Graph, Crt; Var DriverVar, ModeVar :integer; c :char; Begin DriverVar:=Detect; InitGraph(DriverVar, Modevar, ‘ ’); SetBkColor(14); SetColor(1); Circle(300,300,50); Circle(300,215,35); Circle(300,160,20); Circle(300,180,5); Circle(290,140,5); Circle(310,140,5); Line(335,215,370,200); Line(265,215,230,200); c:=readkey; SetBkColor(0); SetColor(GetMaxColor); CloseGraph; End. Манипулирование фрагментами изображений (Анимация) Программы, которые строят, перемещают и изменяют форму различных изображений на экране, называются анимационными. Такие программы, как правило, требуют применения достаточно сложных алгоритмов и используют большой объем памяти для хранения данных. В основе перемещения какого-либо объекта на экране лежит следующий алгоритм: вывести объект на экран; стереть объект с экрана; вывести с некоторым смещением другой вариант объекта и т. д. При частом выводе объекта с небольшими смещениями создается иллюзия движения. Существует большое количество анимационных методов, различающихся способами вывода или построения движущихся объектов, источниками поступления данных об изображении и т. п. Простейший анимационный метод заключается в следующем: - определенным цветом выводится рисунок; - рисунок формируется на том же месте цветом, совпадающим с цветом фона. Это вызывает исчезновение рисунка;
- рисунок выводится на другом месте своим первоначальным цветом и т. д. В качестве примера приведем программу, выводящую мяч (окружность красного цвета), катящийся по горизонтальной линии и отражающийся от вертикальных стенок (левая и правая границы экрана). Program Ball; Uses Graph, Crt; Label 10,100; Const bxi=300; byi=200; bri=10; bci=4; Var DriverVar, ModeVar :integer; xm,ym :integer; bx,by,br,bc :integer; dx :integer; Begin DriverVar:=Detect; InitGraph(DriverVar, Modevar, ‘ ’); xm:=GetMaxX; ym:=GetMaxY; bx:=bxi; by:=byi; br:=bri; bc:=bci; dx:=+1; SetColor(bc); Circle(bx,by,br); 10: SetColor(GetBKColor); Circle(bx,by,br); bx:=bx+dx; by:=by; if bx>=xm-br then dx:=-1; if bx<=0+br then dx:=+1; SetColor(bc); Circle(bx,by,br); if KeyPressed then goto 100; goto 10; 100:SetColor(GetMaxColor); CloseGraph; End.
Процедуры для рисования фигур.
PutPixel(X, Y:Integer; Pixel:Word); Рисует на экране точку с координатами (X, Y) цветом Pixel. Line(X1, Y1, X2, Y2:Integer); Рисует на экране отрезок прямой от точки (X1, Y1) до точки (X2, Y2). Rectangle(X1, Y1, X2, Y2:Integer); Рисует на экране прямоугольник с верхним левым углом в точке (X1, Y1) и нижним правым углом в точке (X2, Y2). Bar(X1, Y1, X2, Y2:Integer); Рисует на экране залитый прямоугольник с верхним левым углом в точке (X1, Y1) и нижним правым углом в точке (X2, Y2). Стиль и цвет заливки задаются процедурой SetFillStyle. Bar3D(X1, Y1, X2, Y2:Integer; Depth:Word; Top:Boolean); Рисует на экране параллелепипед с залитой передней гранью. Глубина фигуры - Depth. Если Top равно TopOn , то параллелепипед рисуется с верхней гранью, если TopOff, то без верхней грани. Стиль и цвет заливки передней грани задаются процедурой SetFillStyle. Circle(X, Y:Integer; Radius:Word); Рисует на экране окружность с центром в точке (X, Y) радиусом Radius. Ellipse(X, Y:Integer; StAngle, EndAngle:Word; XRadius, YRadius:Word); Рисует на экране эллиптическую дугу с центром в точке (X, Y), радиусами XRadius, YRadius. StAngle, EndAngle - начальный и конечный углы. Arc(X, Y:Integer; StAngle, EndAngle:Word; Radius:Word); Рисует на экране дугу окружности с центром в точке (X,Y), радиусом Radius. StAngle, EndAngle - начальный и конечный углы. FillEllipse(X, Y:Integer; XRadius,YRadius:Word); Рисует на экране залитый эллипс с центром в точке (X, Y), радиусами XRadius, YRadius. Стиль и цвет заливки задаются процедурой SetFillStyle. Sector(X, Y:Integer; StAngle, EndAngle:Word; XRadius, YRadius:Word); Рисует на экране закрашенный сектор эллипса с центром в точке (X, Y), радиусами XRadius, YRadius. StAngle, EndAngle - начальный и конечный углы. Стиль и цвет заливки задаются процедурой SetFillStyle. PieSlice(X, Y:Integer; StAngle, EndAngle:Word; Radius:Word); Рисует на экране закрашенный сектор круга с центром в точке (X, Y), радиусом Radius. StAngle, EndAngle - начальный и конечный углы. Стиль и цвет заливки задаются процедурой SetFillStyle.
Другие процедуры. ClearDevice; Очищает экран в графическом режиме. CloseGraph;
Закрывает графический режим. SetColor(Color:Word); Устанавливает новый цвет для рисования. SetLineStyle(LineStyle:Word; Pattern:Word; Thickness:Word); Устанавливает стиль и толщину линий. Если Thickness равно ThickWidth, то линии будут толстыми, если NormWidth, то обычными. SetFillStyle(Pattern:Word; Color:Word); Устанавливает стиль и цвет заливки. Если Pattern равно UserFill, то используется определенный пользователем стиль, описанный процедурой SetFillPattern. SetFillPattern(Pattern:FillPatternType; Color:Word); Устанавливает определяемый пользователем стиль и цвет заливки. FillPatternType=Array [1..8] of Byte; FloodFill(X, Y:Integer; Border:Word); Заливает область вокруг точки (X, Y) до линии цвета Border, используя текущий стиль и цвет заливки. SetTextStyle(Font, Direction:Word; CharSize:Word); Устанавливает используемый шрифт, его направление и размер. Направление соответствует значению переменной Direction и может быть горизонтальным (Direction=0) или вертикальным (Direction=1). Шрифт может быть как одним из стандартных, так и определенный пользователем (с помощью функции InstallUserFont). SetUserCharSize(MultX, DivX, MultY, DivY:Word); Устанавливает ширину букв используемого шрифта в MultX/DivX раз больше, а высоту в MultY/DivY раз больше. OutTextXY(X, Y:Integer; TextString:String); Выводит текст TextString на экран от точки (X, Y). 14 12.05.03 226 уч. группа. Лекция 10.1 1. Использование динамического выделения памяти при программировании на языке Turbo Pascal 7.0. 1.1 Динамическая память. Все глобальные переменные и типизированные константы размещаются в одной непрерывной области оперативной памяти, которая называется сегментом данных. Длина сегмента определяется архитектурой процессора 8086 и составляет 64 Килобайта (65536 байт), что может вызвать определённые трудности при описании и обработке больших массивов данных. С другой стороны объём стандартной памяти - 640 Килобайт. Выход - использовать динамическую память. Динамическая память - это оперативная память ЭВМ, предоставляемая Турбо-Паскалевой программе при её работе, за вычетом сегмента данных (64 К), стека (обычно 16 К) и собственно тела программы. По умолчанию размер динамической памяти определяется всей доступной памятью ЭВМ и, как правило, составляет не менее 200 - 300 Кбайт. Динамическую память обычно используют при: обработке больших массивов данных; разработке САПР; временном запоминании данных при работе с графическими и звуковыми средствами ЭВМ.
Размещение статических переменных в памяти осуществляется компилятором в процессе компиляции. Динамические переменные - размещаются в памяти непосредственно в процессе работы программы. При динамическом размещении заранее неизвестны ни тип, ни количество размещаемых данных, к ним нельзя обращаться по именам, как к статическим переменным. Турбо-Паскаль представляет средство управления динамической памятью: указатели. Указатель - это переменная, которая в хранит качестве своего значения адрес байта памяти. В 8086 адреса задаются совокупностью двух шестнадцатиразрядных слов - сегмента и смещения. Сегмент - участок памяти, имеющий максимальную длину 64 К и начинающийся к физического адреса, кратного 16 (то есть 0, 16, 32, 48 и т.д.). Смещение - указывает, сколько байт от начала сегмента нужно пропустить, чтобы обратиться по нужному адресу. Фрагмент памяти в 16 байт называется параграфом. Сегмент адресует память с точностью до параграфа, а смещение - с точностью до байта. Каждому сегменту соответствует непрерывная и отдельно адресуемая область памяти. Сегменты могут следовать в памяти один за другим, или с некоторыми интервалами, или, наконец, перекрывать друг друга. Таким образом любой указатель по своей внутренней структуре представляет собой совокупность двух слов (типа Word), трактуемых как сегмент и смещение. Указатель адресует лишь первый байт типа данных.
Справка: процессор 8086. 8086 имеет 16 - битовые регистры, всего 14 штук, из них: 12 - регистры данных и адресов; 1 - указатель команд (регистр адреса команды); 1 - регистр состояния (регистр флагов).
Регистры данных и адресов делятся на три группы: регистр данных; регистр указателей и индексов; регистр сегментов. Процессор 8086 всегда генерирует 20-ти бытовые адреса за счёт добавления 16-ти битового смещения к содержимому регистра, умноженному на 16: физический адрес = смещение + 16 * регистр сегмента. Вместо умножения на 16 содержимое регистра сегмента используется так, как если бы оно имело четыре дополнительных нулевых бита:
пусть: смещение = 10H регистр сегмента = 2000H тогда: 0000 0000 0001 0000 (смещение) 0010 0000 0000 0000 (0000) (номер блока) 0010 0000 0000 0001 0000 (физический адрес)
физический адрес = 20010H
У 8086 адрес ячейки задаётся номером блока и смещением, что обусловлено тем, что команды 8086 и её данные должны располагаться в разных частях памяти (в разных сегментах). Если требуется адресоваться к данным, то потребуется адресация блока памяти, с которого начинается сегмент данных (из регистра сегмента данных) и позиция желаемой ячейки в этом сегменте (смещение). В дополнение к области памяти 1 Мбайт, 8086 может адресоваться к внешним устройствам через 65536 (64 К) портов ввода-вывода. Имеются специальные команды ввода-вывода, позволяющие иметь непосредственный доступ к первым 256 (от 0 до 255) портам. Другие команды позволяют получить косвенный доступ к порту с помощью занесения идентифицирующего его номера (0 - 65535) в определенный регистр данных. Подобно ячейке памяти любой порт может быть 8 или 16- битовым. Распределение памяти IBM PC. 16 - старших байт - команды начальной загрузки системы. Первые 1024 ячейки - вектора прерываний. Типы прерываний. Существуют 2 вида прерываний - одни можно игнорировать, другие обязательно обслужить как можно скорее. 8086 может распознать 256 различных прерываний, каждому из них однозначно соответствует код типа (целое число от 0 до 255). Код используется в качестве указателя ячейки в области памяти с младшими адресами (область векторов прерываний). 1.2 УКАЗАТЕЛИ (Операционная система MS - DOS все адресуемое пространство делит на сегменты. Сегмент - это участок памяти размером 64 К байт. Для задания адреса необходимо определить адрес начала сегмента и смещение относительно начала сегмента.)
В TURBO PASCAL определен адресный тип Pointer - указатель. Переменные типа Pointer
var p: Pointer;
содержат адрес какого - либо элемента программы и занимают 4 байта, при этом адрес хранится как два слова, одно из них определяет сегмент, второе - смещение.
Переменную типа указатель можно описать другим способом. type NameType= ^T; var p: NameType;
Здесь p - переменная типа указатель, связанная с типом Т с помощью имени типа NameType. Описать переменную типа указатель можно непосредственно в разделе описания переменных: var p: ^T;
Необходимо различать переменную типа указатель и переменную, на которую этот указатель ссылается. Например если p - ссылка на переменную типа Т, то p^ - обозначение этой самой переменной. Для переменных типа указатель введено стандартное значение NIL, которое означает, что указатель не ссылается ни к какому объекту. Константа NIL используется для любых указателей. Над указателями не определено никаких операций, кроме проверки на равенство и неравенство. Переменные типа указатель могут быть записаны в левой части оператора присваивания, при этом в правой части может находиться либо функция определения адреса Addr(X), либо выражение @ X, где @ - унарная операция взятия адреса, X - имя переменной любого типа, в том числе процедурного. Переменные типа указатель не могут быть элементами списка ввода - вывода. 1.3 ДИНАМИЧЕСКИЕ ПЕРЕМЕННЫЕ Статической переменной (статически размещенной) называется описанная явным образом в программе переменная, обращение к ней осуществляется по имени. Место в памяти для размещения статических переменных определяется при компиляции программы. В отличие от таких статических переменных в программах, написанных на языке ПАСКАЛЬ, могут быть созданы динамические переменные. Основное свойство динамических переменных заключается в том, что они создаются и память для них выделяется во время выполнения программы. Размещаются динамические переменные в динамической области памяти (heap - области). Динамическая переменная не указывается явно в описаниях переменных и к ней нельзя обратиться по имени. Доступ к таким переменным осуществляется с помощью указателей и ссылок.
См. разд мат-л 1. // Работа с динамической областью памяти в TURBO PASCAL реализуется с помощью процедур и функций New, Dispose, GetMem, FreeMem, Mark, Release, MaxAvail, MemAvail, SizeOf. Процедура New( var p: Pointer ) выделяет место в динамической области памяти для размещения динамической переменной p^ и ее адрес присваивает указателю p. Процедура Dispose( var p: Pointer ) освобождает участок памяти, выделенный для размещения динамической переменной процедурой New, и значение указателя p становится неопределенным. Проуедура GetMem( var p: Pointer; size: Word ) выделяет участок памяти в heap - области, присваивает адрес его начала указателю p, размер участка в байтах задается параметром size. Процедура FreeMem( var p: Pointer; size: Word ) освобождает участок памяти, адрес начала которого определен указателем p, а размер - параметром size. Значение указателя p становится неопределенным. Процедура Mark( var p: Pointer ) записывает в указатель p адрес начала участка свободной динамической памяти на момент ее вызова. Процедура Release( var p: Pointer ) освобождает участок динамической памяти, начиная с адреса, записанного в указатель p процедурой Mark, то-есть, очищает ту динамическую память, которая была занята после вызова процедуры Mark. Функция MaxAvail: Longint возвращает длину в байтах самого длинного свободного участка динамической памяти. Функция MemAvail: Longint полный объем свободной динамической памяти в байтах. Вспомогательная функция SizeOf( X ): Word возвращает объем в байтах, занимаемый X, причем X может быть либо именем переменной любого типа, либо именем типа. // Рассмотрим некоторые примеры работы с указателями. var p1, p2: ^Integer;
Здесь p1 и p2 - указатели или пременные ссылочного типа. p1:=NIL; p2:=NIL;
После выполнения этих операторов присваивания указатели p1 и p2 не будут ссылаться ни на какой конкретный объект.
New(p1); New(p2); Процедура New(p1) выполняет следующие действия: в памяти ЭВМ выделяется участок для размещения величины целого типа; адрес этого участка присваивается переменной p1:
Аналогично, процедура New(p2) обеспечит выделение участка памяти, адрес которого будет записан в p2:
После выполнения операторов присваивания p1^:=2; p2^:=4; в выделенные участки памяти будут записаны значения 2 и 4 соответственно: В результате выполнения оператора присваивания p1^:=p2^; в участок памяти, на который ссылается указатель p1, будет записано значение 4:
После выполнения оператора присваивания p2:=p1; оба указателя будут содержать адрес первого участка памяти:
Переменные p1^, p2^ являются динамическими, так как память для них выделяется в процессе выполнения программы с помощью процедуры New.
Динамические переменные могут входить в состав выражений, например:
p1^:=p1^+8; Write('p1^=',p1^:3);
Пример. В результате выполнения программы:
Program DemoPointer; var p1,p2,p3:^Integer; begin p1:=NIL; p2:=NIL; p3:=NIL; New(p1); New(p2); New(p3); p1^:=2; p2^:=4; p3^:=p1^+Sqr(p2^); writeln('p1^=',p1^:3,' p2^=',p2^:3,' p3^=',p3^:3); p1:=p2; writeln('p1^=',p1^:3,' p2^=',p2^:3) end. на экран дисплея будут выведены результаты: p1^= 2 p2^= 4 p3^= 18 p1^= 4 p2^= 4 Выделение и освобождение динамической памяти. Процедуры и функции в раздаточном материале см.1 //**//
Вся динамическая память – пространство ячеек, называемое кучей. Физически куча располагается в старших адресах, сразу за программой. Указатель на начало кучи храниться в предопределенной переменной HeapOrg, конец - FreePtr, текущую границу незанятой динамической памяти указывает указатель HeapPtr. Для выделения памяти под любую переменную используется процедура New. Единственным параметром является типизированный указатель:
(на доске) // Var I,J: ^Integer; R: ^Real; Begin New(I); {под I выделяется область памяти,} {адрес первого байта этой области помещается в I} End. // После выполнения этого фрагмента указатель I приобретёт значение, которое перед этим имел указатель кучи HeapPtr, а HeapPtr увеличится на два (т.к. он типа Integer); New(R) - вызовет смещение указателя на 6 байт. После того как указатель приобрёл некоторое значение, то есть стал указывать на конкретный байт памяти, по этому адресу можно разместить значения соответствующего типа: (на доске) I^:= 2; R^:= 2*Pi;
Допустима запись: R^:= Sqr (R^) + I^ - 17; Но недопустима запись: R:= Sqr (R^) + I^ - 17; так как указателю R нельзя присвоить значение вещественного выражения.
Возврат динамической памяти обратно в кучу осуществляется оператором Dispose: Dispose(R); Dispose(I); - вернут в кучу, ранее забранные 8 байт. Dispose(Ptr) не изменяет значения указателя Ptr, а лишь возвращает в кучу память, связанную с этим указателем. Однако повторное применение процедуры к “свободному” указателю приведет к возникновению ошибки времени исполнения. Чтобы указать, что указатель свободен, нужно использовать зарезервированное слово Nil. К указателям можно применять операции отношения, в том числе и сравнения с Nil: (на доске) // Const P:^Real = Nil; . . . . . . . . Begin If P = Nil then New (P); . . . . . . . . Dispose(P); P:= Nil; End. // Использование указателей, которым не присвоено значение процедурой New или каким-либо другим способом, никак не контролируется системой и может привести к непредсказуемым результатам. Многократное чередование New и Dispose приводит к “ячеистой” структуре кучи. Дело в том, что все операции с кучей выполняется под управлением особой программы, которая ведёт учёт всех свободных фрагментов в куче. При выполнении оператора New( ) эта программа отыскивает минимальный свободный фрагмент, в котором может разместиться требуемая переменная. Адрес начала найденного фрагмента возвращается в указателе, а сам фрагмент или его часть нужной длины, помечаются как занятая часть кучи.
Можно освободить целый фрагмент кучи следующим образом: (на доске) // Перед началом выделения динамической памяти значения указателя HeapPtr запоминается в переменной-указателе с помощью процедуры Mark. Выполнение программы. Освобождение фрагмента кучи от заполненного адреса до конца динамической памяти с использованием процедуры Release.
Var P, P1, P2, P3, P4, P5: ^Integer; Begin New(P1); New(P2); New(P3); New(P4); New(P5); Mark(P); . . . . . . . Release (P); End. // В этом примере процедурой Mark(P) в указатель P было помещено текущее значение HeapPtr, однако память под переменную не резервировалась. Вызов Release уничтожает список свободных фрагментов в куче, созданных Dispose, поэтому совместное применение этих процедур не рекомендуется. Для работы с нетипизированными указателями используются также процедуры GetMem(P, Size) и FreeMem(P, Size) - резервирование и освобождение памяти. P - нетипизированный указатель, Size - размер. За одно обращение к куче процедурой GetMem можно зарезервировать до 65521 байт. Освобождать нужно ровно столько памяти, сколько было зарезервировано, и именно с того адреса, с которого память была зарезервирована, иначе программа не будет работать и завершаться корректно !!! (см. раздат материал. 2) //Примеры использования указателей. Динамическая память - 200..300 Кбайт. Нужно разместить массив 100 * 200 типа Extended. Требуется 100 * 200 * 10 = 200000 байт. Пробуем:
Var i,j: Integer; PtrArr: Array[1..100, 1..200] of ^Extended. Begin . . . . . . For i:= 1 to 100 do For j:= 1 to 200 do New (PtrArr [i,j]); . . . . . . End.
// есть в рм.// Теперь к любому элементу можно обратиться: PtrArr[i,j]^:=...; Но длина внутреннего представления указателей 4 байта, поэтому потребуется ещё 100*200*4 = 80000 байт, что превышает размер сегмента (65536 байт), доступный для статического размещения данных. Можно было бы работать с адресной арифметикой (арифметикой над указателями), то есть не создавать массив указателей, а вычислять адрес элемента непосредственно перед обращением к нему. Однако в Турбо-Паскале над указателями не определены никакие операции, кроме присваивания и отношения. Но задачу решить можно: Seg(x) - возвращает сегментную часть адреса. Ofs(x) - возвращает смещение. x - любая переменная, в том числе и та, на которую указывает указатель. Далее с помощью Ptr(Seg, Ofs: Word): Pointer можно создать значение указателя, совместимое с указателем любого типа. Таким образом, сначала с помощью GetMem забираем из кучи несколько фрагментов подходящей длины (не более 65521). Удобно по строкам 200 * 100 = 20000 байт. Начало каждого фрагмента запоминается в массиве PtrStr из 100 указателей. Теперь для доступа к любому элементу строки нужно вычислить смещение этого элемента от начала строки и сформировать указатель.
Var i,j: Integer; PtrStr: Array [1..100] of Pointer; Pr: ^Extended; Const SizeOfExt = 10; Begin For i:= 1 to 100 do GetMem (PtrStr[i], SizeOfExt*200); . . . . . . . . . . . . . . . . Pr:= Ptr(Seg(PtrStr[i]^), Ofs(PtrStr[i]^) + (j - 1) * SizeOfExt); {Обращениекэлементуматрицы [i,j]} End; далее работаем с Pr^:=. . . и т.д. Полезно ввести две вспомогательных функции GetExt и PutExt. Каждая из них будет обращаться к функции вычисления адреса AddrE.
Program Demo; Const SizeOfExt = 10; N = 100; M = 200; Type ExtPoint = ^Extended; Var i,j: Integer; PtrStr: Array[1..N] of Pointer; S: Extended;
Function AddrE(i,j: Word): ExtPoint; Begin AddrE:= Ptr(Seg(PtrStr[i]^), Ofs(PtrStr[i]^) + (j - 1) * SizeOfExt); End;
Function GetExt(i,j: Integer): Extended; Begin GetExt:= AddrE(i,j)^; End;
Procedure PutExt(i,j: Integer; X: Extended); Begin AddrE(i,j)^:= X; End;
Begin {main} Randomize; For i:=1 to N do Begin GetMem (PtrStr[i], M*SizeOfExt); For i:= 1 to m do PutExt(i, j, Random(255)); End; S:= 0; For i:= 1 to N do For j:= 1 to M do S:= S + GetExt(i,j); WriteLn(S/(N*M)); End. / /
Мы предполагали, что каждая строка размещается в куче с начала границы параграфа и смещение каждого указателя PtrStr ровно 0. В действительности при последовательных обращениях к GetMem начало очередного фрагмента следует за концом предыдущего и может не попасть на границу сегмента. В результате при размещении фрагментов максимальной длины (65521 байт) может возникнуть переполнение при вычислении смещения последнего байта. 1.4 ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ Структурированные типы данных, такие, как массивы, множества, записи, представляют собой статические структуры, так как их размеры неизменны в течение всего времени выполнения программы. Часто требуется, чтобы структуры данных меняли свои размеры в ходе решения задачи. Такие структуры данных называются динамическими, к ним относятся стеки, очереди, списки, деревья и другие. Описание динамических структур с помощью массивов, записей и файлов приводит к неэкономному использованию памяти ЭВМ и увеличивает время решения задач. Каждая компонента любой динамической структуры представляет собой запись, содержащую по крайней мере два поля: одно поле типа указатель, а второе - для размещения данных. В общем случае запись может содержать не один, а несколько укзателей и несколько полей данных. Поле данных может быть переменной, массивом, множеством или записью. Для дальнейшего рассмотрения представим отдельную компоненту в виде: где поле p - указатель; поле D - данные.
Описание этой компоненты дадим следующим образом: (на доске) // type Pointer = ^Comp; Comp = record D:T; pNext:Pointer end; здесь T - тип данных. Рассмотрим основные правила работы с динамическими структурами данных типа стек, очередь и список, базируясь на приведенное описание компоненты. // 1.5 СТЕКИ Стеком называется динамическая структура данных, добавление компоненты в которую и исключение компоненты из которой производится из одного конца, называемого вершиной стека. Стек работает по принципу LIFO (Last-In, First-Out) - поступивший последним, обслуживается первым.
Обычно над стеками выполняется три операции: начальное формирование стека (запись первой компоненты); добавление компоненты в стек; выборка компоненты (удаление).
Для формирования стека и работы с ним необходимо иметь две переменные типа указатель, первая из которых определяет вершину стека, а вторая - вспомогательная.
Пусть описание этих переменных имеет вид: var pTop, pAux: Pointer; где pTop - указатель вершины стека; pAux - вспомогательный указатель.
Начальное формирование стека выполняется следующими операторами:
Последний оператор или группа операторов записывает содержимое поля данных первой компоненты.
Добавление компоненты в стек призводится с использованием вспомогательного указателя: (на доске)
(на доске) Добавление последующих компонент производится аналогично. Рассмотрим процесс выборки компонент из стека. Пусть к моменту начала выборки стек содержит три компоненты: Первый оператор или группа операторов осуществляет чтение данных из компоненты - вершины стека. Второй оператор изменяет значение указателя вершины стека: Как видно из рисунка, при чтении компонента удаляется из стека.
Пример. Составить программу, которая формирует стек, добавляет в него произвольное количество компонент, а затем читает все компоненты и выводит их на экран дисплея, В качестве данных взять строку символов. Ввод данных - с клавиатуры дисплея, признак конца ввода - строка символов END. Program STACK; uses Crt; type Alfa= String[10]; PComp= ^Comp; Comp= Record sD: Alfa; pNext: PComp end; var pTop: PComp; sC: Alfa; Procedure CreateStack(var pTop: PComp; var sC: Alfa); begin New(pTop); pTop^.pNext:=NIL; pTop^.sD:=sC end; Procedure AddComp(var pTop: PComp; var sC: Alfa); var pAux: PComp; begin NEW(pAux); pAux^.pNext:=pTop; pTop:=pAux; pTop^.sD:=sC end; Procedure DelComp(var pTop: PComp; var sC:ALFA); begin sC:=pTop^.sD; pTop:=pTop^.pNext end; begin Clrscr; writeln(' ВВЕДИ СТРОКУ '); readln(sC); CreateStack(pTop,sC); repeat writeln(' ВВЕДИ СТРОКУ '); readln(sC); AddComp(pTop,sC) until sC='END'; writeln('****** ВЫВОД РЕЗУЛЬТАТОВ ******'); repeat DelComp(pTop,sC); writeln(sC); until pTop = NIL end. Unit GraphApp; Interface Uses GraphObj; const Npoints=100; tape TGraphApp=Object Points:array [1..NPoints] of Point; Line:Tline; Rect:TRect; Circ:TCircle; ActiveObj:Integer; Procedure Init; Procedure Run; Procedure Done; Procedure ShowAll; Procedure MoveActiveObj(dX,dY:integer); end; Implementation Uses Graph,CRT; Procedure TgraphApp.Init; Var D,R,Err,k:integer; Begin D:=detect; InitGraph(D,R,’c:\tp7\bgi’); Err:=GraphResult; If Err<>0 then begin GraphErrorMsg(Err); Halt; end; for k:=1 to Npoints do Points[k].Init(Random(GetMaxX),Random(GetMaxY),Random(15)+1); Line.Init(GetMaxX div 3, GetMaxY div 3,2*GetMaxX div 3, 2* GetMaxY div 3, LightRed); Circl.Init (GetMaxX div 2, GetMaxY div 2, GetMax, White); Rect.Init(2*GetMaxX div 5, 2*GetMaxY div 5,3*GetMaxX div 5, 3* GetMaxY div 5, Yellow); ShowAll; ActiveObj:=1; end;
Procedure TGraphApp.Run; var stop:Boolean; const D=5; Begin Stop:=False; repeat case ReadKey of #27: Stop:=True; #9: begin inc(ActiveObj); if ActiveObj>3 then ActiveObj:=1 end; #0:case ReadKey of #71:MoveActiveObj(-D,-D); #72:MoveActiveObj(0,-D); #73:MoveActiveObj(D,-D); #75:MoveActiveObj(-D,0); #79:MoveActiveObj(-D,D); #80:MoveActiveObj(0,D); #81:MoveActiveObj(D,D); end end; ShowAll;
Until Stop; end; Destructor TgraphApp.Done; begin CloseGraph; end; Procedure TgraphApp.ShowAll; Var k:integer; begin for k:=1 to Npoints do Points[k].Show; Line.Show; Rect.Show; Circ.Show; end; Procedure TgraphApp.MoveActiveObj; begin case ActiveObj of 1: Rect.MoveTo(dX,dY); 2: Circ.MoveTo(dX,dY); 3: Line.MoveTo(dX,dY); end end; end. 30 Краткий курс лекций "Основы программирования на языке Паскаль" | Введение. | Прежде всего, следует напомнить, что изучение языка программирования представляет собой знакомство с формальными правилами записи алгоритмов для их последующего выполнения компьютером. Формальность сия проистекает из самих принципов, заложенных в архитектуру вычислительных устройств, и жесткости математической логики. Поэтому, постарайтесь воспринять все довольно строгие правила как неизбежность, настроить себя на серьезную, скрупулезную, порой сложную работу. Однако не стоит бояться, расстраиваться и сетовать на судьбу: немного аккуратности, внимания, знания предыдущего материала - и вы уже программист. | Основные понятия. | Как и любой алгоритм, являющийся, как вы помните, последовательностью инструкций, программа на языке Паскаль состоит из команд (операторов), записанных в определенном порядке и формате. Команды позволяют получать, сохранять и обрабатывать данные различных типов (например, целые числа, символы, строки символов, т.д.). Однако кроме команд в записи программы участвуют еще так называемые "служебные слова". Это и есть элементы формальности, организующие структуру программы. Их не так много, но их значение трудно переоценить. Служебные слова можно использовать только по своему прямому назначению. Переопределять их не разрешается. Вам уже известно, что основное назначение компьютера - облегчить человеку работу с большими объемами информации, поэтому подавляющее большинство программ построено по одному, довольно простому принципу: получение данных из внешнего мира (ввод), обработка их по соответствующему алгоритму, хранение необходимой информации и вывод во внешний (по отношению к компьютеру) мир полученных результатов. Все эти действия реализуются через имеющиеся в языках программирования команды, алгоритмические структуры и структуры данных. | Основная структура программы. | Правила языка Паскаль предусматривают единую для всех программ форму основной структуры: Program <Имя программы>; <Раздел описаний> Begin <Тело программы> End. Здесь слова Program, Begin и End являются служебными. Правильное и уместное употребление этих слов является обязательным. Угловые скобки в формате указывают на то, что вместо них при реальном программировании должно быть подставлено конкретное значение. Сама запись программы в принципе может производиться вообще в одну стоку. При этом ее части должны отделяться друг от друга хотя бы одним пробелом. Однако, такая запись неудобна для чтения, недостаточно наглядна, поэтому я рекомендую придерживаться приведенной структуры, а в теле программы по возможности записывать по одному оператору в строке. Имя программы выбирается программистом самостоятельно в соответствии с правилами построения идентификаторов. Все объекты, не являющиеся зарезервированными в Паскале, наличие которых обусловлено инициативой программиста, перед первым использованием в программе должны быть описаны. Это производится для того, чтобы компьютер перед выполнением программы зарезервировал память под соответствующие объекты и поставил в соответствие этим участкам памяти идентификаторы. Раздел описаний может состоять из пяти подразделов: 1. Описание меток (Label). 2. Описание типов (Type). 3. Описание констант (Const). 4. Описание переменных (Var). 5. Описание процедур и функций (Procedure, Function). При отсутствии необходимости в каком-либо виде объектов, соответствующий подраздел может быть опущен. | Алфавит языка. | Основу любого языка составляет алфавит, то есть конечный, фиксированный набор символов, используемых для составления текстов на данном языке (в нашем случае - программ). Конечно, стройность картины немного портит наличие диалектов, создающихся стихийно и очень часто включающих в себя апокрифические (неканонические) буквы и знаки. В программировании эта проблема решается введением понятия "стандарт языка". Оно практически неприменимо к языкам человеческим, вечно развивающимся и изменяющимся. Мы с вами в основном будем говорить о той самодостаточной части языка Паскаль, которая входит в различные его компьютерные реализации в неизменном виде. В плане изучения, я не вижу большого смысла излагать вам строгие правила стандарта, хотя такие существуют. Ограничимся некоторыми замечаниями, раскрывающими все же формальности употребления символов в языке Паскаль. Итак, алфавит языка Паскаль составляют: 1) буквы латинского алфавита; 2) арабские цифры; 3) специальные знаки. Использование символов первой группы чаще всего вопросов не вызывает, но свои тонкости здесь имеются. Во-первых, это употребление заглавных и строчных букв. Большинство существующих трансляторов не различают буквы разных регистров. Таким образом, записи "progRaM" и "PROGram" будем считать идентичными. Во-вторых, некоторые символы латиницы и кириллицы совпадают по начертанию. Нельзя ли вместо буквы "К" латинской написать "K" русскую? Ответ: в тетради (если вы их сможете различить) - пожалуйста, в программе на ЭВМ - ни в коем случае. На вид они может быть и похожи, но уж коды-то у них совершенно разные, а компьютер, как вам известно, оперирует внутри себя не буквами, а их числовыми кодами. По поводу привычных арабских цифр сказать можно только то, что с их помощью записываются не только числа. Цифры в качестве обыкновенных символов могут использоваться в различных других конструкциях языка. Сложнее всего обстоит дело со специальными знаками, поэтому их придется разобрать подробно, иногда забегая вперед, но вы пока можете пропускать мимо ушей непонятные термины, не забывая, однако, записывать все в тетрадь. Потом, при изучении соответствующих структур, вы будете иметь возможность заглянуть в этот раздел для того, чтобы уточнить какой знак в данном месте необходимо использовать. Наиболее часто употребляемым специальным символом является пробел (в значимых местах мы будем обозначать его в записях знаком "V"). Его использование связано с форматами основной структуры программы, разделов описаний, операторов. Не следует путать наличие пробела с отсутствием символа. . конец программы, разделение целой и дробной частей вещественного числа (десятичная точка), разделение полей в переменной типа Record; , разделение элементов списков; .. указание диапазона; : используется в составе оператора присваивания, а также для указания формата вывода в операторе Writeln; ; отделяет один раздел программы от другого, разделяет операторы; ' используется для ограничения строковых констант; - + * / ( ) арифметические знаки (используются по своему назначению); < > знаки отношений; = используется в составе оператора присваивания, в разделах описаний констант и типов, используется как знак отношения (равно); @ имя специального оператора определения адреса переменной, подпрограммы; ^ используется для именования динамических переменных; {} ограничение комментариев в программе; [ ] заключают в себе индексы элементов массивов; _ символ подчеркивания используется также как любая буква, например, в идентификаторах - вместо пробела; # обозначение символа по его коду; $ обозначение директивы компилятора, обозначение шестнадцатеричного числа. Возникает вопрос, а как же быть с русскими буквами и другими знаками, имеющимися на клавиатуре? Некоторые версии Паскаля допускают их использование в программе, но стандарт языка этого не подразумевает. Поэтому включать эти символы в программу можно только в качестве строковых констант или внутри комментария, то есть там, где транслятор при компиляции их игнорирует. При использовании этих знаков в качестве данных, они равноправны со всеми символами, которые может хранить в памяти и обрабатывать компьютер. | Идентификаторы. | Имена операторов, переменных, констант, типов величин, имя самой программы назначаются программистом и называются в Паскале идентификаторами. Существуют правила, которым должны отвечать все идентификаторы: идентификатор должен быть уникальным, то есть одним и тем же именем разные объекты не могут быть названы; идентификатор имеет ограничение по длине (зависит от конкретной реализации языка на компьютере); идентификатор может состоять только из символов латинского алфавита, цифр и знака подчеркивания ("_"); идентификатор не может начинаться с цифры. | Константы. | Из всех подразделов описаний сейчас мы рассмотрим только описание констант и переменных, остальные - позже. Вообще говоря, в Паскале константами являются любые явно заданные в программе данные (например, 7493, 'привет', 54.899). Следует обратить ваше внимание на то, что при записи числовых констант с дробной частью эта часть отделяется от целой не запятой, как, возможно, вы привыкли, а точкой. Для записи очень больших по модулю или очень малых (близких к нулю) чисел существует возможность записи их в так называемой экспоненциальной форме. С такой записью вы встречались в математике и физике, но называли ее стандартным видом числа. Пример: 2 . 4 5 6 7 Е - 0 6 ^мантисса ^порядок Здесь буква "Е" отделяет мантиссу (совокупность значащих цифр числа с десятичной точкой после первой) от порядка (показателя степени десятки в стандартном виде числа). Вам предстоит научиться как читать числа в таком виде, так и записывать. Константы, представляющие собой строковые величины, заключаются в апострофы. Если одна и та же величина используется в программе несколько раз, то удобнее было бы обозначить ее каким-нибудь именем и использовать это имя везде, где требуется записать соответствующую константу. Кроме сокращения размера исходного текста программы, это позволит избежать случайных ошибок, а также упростит отладку программы. Описание именованных констант начинается служебным словом Const. Далее следуют записи вида: <Идентификатор>=<значение>; Пример: Const Pi=3.14; Name1='Татьяна'; Name2='Виктор'; R21=6.33187E+03; W_W_W=934122; | Понятие переменной. Типы. | Данные, как вы знаете, хранятся в памяти компьютера, но для указания на конкретную информацию очень неудобно все время записывать физические адреса ячеек. Эта проблема в языках программирования высокого уровня, в частности в Паскале, решена введением понятия переменной. Переменная в Паскале - именованный участок памяти для хранения данных определенного типа. Значение переменной (информация в соответствующих ячейках памяти) в ходе выполнения программы может быть изменено. Константами же, как вы уже знаете, называются величины, значение которых в ходе выполнения программы изменено быть не может. Конкретные переменные и константы представляют собой объекты уникальные и отличаются друг от друга именем. В качестве данных в программах на языке Паскаль могут выступать числа, символы, целые строки символов. Заметьте, что с этими различными видами информации выполняются совершенно разные действия. Например, с числовыми величинами производятся арифметические операции, чего невозможно сделать с символьными. Кроме того, разные виды данных требуют различного объема памяти для хранения. В соответствии с этими соображениями в языке Паскаль введено понятие "Тип" (TYPE). Тип переменной указывает на то, какие данные могут быть сохранены в этом участке памяти, и в каких действиях эта переменная может участвовать. Существуют зарезервированные (базовые) типы в языке Паскаль, но, как далее вы убедитесь, есть также возможность создавать свои собственные, определяемые программистом типы переменных. К базовым типам относятся: тип целых чисел - Integer тип "длинных" целых чисел - Longint тип действительных (вещественных) чисел (то есть - с дробной частью) - Real тип неотрицательных целых чисел от 0 до 255 - Byte тип неотрицательных целых чисел от 0 до 65535 - Word символьный тип - Char строковый тип - String логический тип - Boolean Физически типы данных отличаются друг от друга количеством ячеек памяти (байтов), отводимых для хранения соответствующей переменной. Логическое же отличие проявляется в интерпретации хранящейся информации. Например, переменные типа Char и типа Byte занимают в памяти по одному байту. Однако в первом случае содержимое ячейки памяти интерпретируется как целое беззнаковое число, а во втором - как код (ASC) символа. В отличие от констант, неименованных переменных не существует. Все используемые в программе переменные должны быть описаны в соответствующем разделе описания. Раздел описания переменных начинается служебным словом Var, после которого следуют записи следующего вида: <Список имен переменных>:<Название типа>; Список может состоять из нескольких имен (при этом они разделяются запятыми), а также и из одного имени. Тип, указываемый после двоеточия, определяет, какие данные теперь могут храниться в описанных таким образом переменных. Для каждого используемого в программе типа переменных в разделе их описания должна быть, как минимум, одна собственная строка. Пример: Var A,B,H_22,Angle : Real; Name3 : String; Flag : Boolean; I,J,K,Count : Word; | Оператор присваивания. Арифметические выражения. | Самым простым действием над переменной является занесение в нее величины соответствующего типа. Иногда говорят об этом, как о присвоении переменной конкретного значения. Такая команда (оператор) в общем виде выглядит на языке Паскаль следующим образом: <Имя переменной>:=<Выражение>; Выражение, указанное справа от знака ":=", должно приводить к значению того же типа, какого и сама переменная, или типа, совместимого с переменной относительно команды присваивания. Например, переменной типа Real можно присвоить значение типа Integer или Word (впрочем, наоборот делать нельзя). Выражение будет сначала вычислено, затем, его результат будет положен в ячейки памяти, отведенные для переменной. Что же представляет собой выражение на языке Паскаль? Многое зависит от типа выражения. Рассмотрим сначала выражения арифметические, то есть те, результатом которых является число. В состав арифметического выражения на языке Паскаль могут входить: числовые константы; имена переменных; знаки математических операций; математические функции и функции, возвращающие число; открывающиеся и закрывающиеся круглые скобки. Правила построения выражений напоминают математические с некоторыми уточнениями. Выражение записывается в одну строку (никакой многоэтажности), между операндами обязательно должен стоять знак операции (Запись "2x" - не допускается), знаки некоторых операций и названия некоторых функций отличны от привычных вам. Операции: + сложение; - вычитание; / деление; * умножение; MOD остаток от деления (записывается так: A MOD B; читается: остаток от деления A на B); эта операция применима только к целым числам; DIV целочисленное деление (записывается так A DIV B; читается: результат деления A на B без дробной части); эта операция тоже применяется только для целых операндов. Аргументы функций всегда записываются в круглых скобках: SIN(X) sin x; COS(X) cos x; ARCTAN(X) arctg x; ABS(X) абсолютное значение x (в математике - |x|); SQR(X) возведение x в квадрат; SQRT(X) извлечение квадратного корня; TRUNC(X) отбрасывание дробной части х; ROUND(X) округление х до ближайшего целого числа; После выполнения второго оператора присваивания в участке памяти, отведенном под переменную R, окажется результат указанного выражения, однако, к сожалению, узнать его мы не сможем, поскольку пока не имеем возможности "заглянуть" в память машины, вывести значение переменной хотя бы на экран. | Составной оператор | Этот оператор, строго говоря, оператором не является. Дело в том, что также как арифметические действия иногда бывает необходимо заключать в скобки, последовательности команд (операторов) тоже иногда требуют объединения. Это позволяют сделать так называемые операторные скобки. Формат (общий вид) составного оператора таков: Begin <Оператор 1>; <Оператор 2>; ...... <Оператор N> End; Возможно, такая структура напоминает вам основную структуру программы. Действительно, отличие только в том, что после End в конце составного оператора ставится точка с запятой, а в конце программы - точка. По своей сути вся программа представляет собой большой составной оператор. Обратите внимание на то, что точка с запятой перед End может не ставиться. Составной оператор предоставляет возможность выполнить произвольное количество команд там, где подразумевается использование только одного оператора. Как вы узнаете потом, такая необходимость встречается довольно часто. | Операторы ввода и вывода информации | Если вы помните, при рассмотрении примера работы оператора присваивания мы столкнулись с необходимостью узнать результат выполнения программы. Мы разобрались с тем, как информацию сохранять (в переменных), как обрабатывать (с использованием выражений), но два фундаментальнейших информационных процесса остались вне нашего внимания: получение информации и передача ее во внешний по отношению к компьютеру мир. Пока наши программы могут использовать лишь информацию, которая находится непосредственно в тексте программы. Узнать, какие значения в данный момент имеют переменные, также не представлялось возможным. Программирование в таких условиях теряет смысл. Взаимодействие устройств обработки и хранения информации с внешней средой (хотя бы с пользователем) является совершенно необходимым. За такой интерфейс в языке Паскаль отвечают операторы ввода-вывода информации. Эти инструкции позволяют ввести аргументы, параметры расчетов во время выполнения программы (а не на этапе ее написания), осуществить вывод рассчитанных данных в понятном человеку виде. Сначала операторы ввода (форматы операторов): Read(<Список ввода>); Readln(<Список ввода>); В таком формате эти команды позволяют вводить данные в переменные во время выполнения программы с клавиатуры. Элементами списка ввода могут быть имена переменных, которые должны быть заполнены значениями, введенными с клавиатуры. Выполнение операторов ввода происходит так: ход программы приостанавливается, на экран выводится курсор, компьютер ожидает от пользователя набора данных для переменных, имена которых указаны в списке ввода. Пользователь с клавиатуры вводит необходимые значения в том порядке, в котором они требуются списком ввода, нажимает Enter. После этого набранные данные попадают в соответствующие им переменные и выполнение программы продолжается. Примечание: данные при вводе разделяются пробелами. Разница между работой процедур Read и Readln (от Read line) состоит в следующем: после выполнения Read значение следующего данного считывается с этой же строчки, а после выполнения Readln - с новой строки. Для вывода информации в Паскале также есть две команды: Write(<Список вывода>); Writeln(<Список вывода>); Такой формат использования Write и Writeln позволяет выводить на экран монитора данные из списка вывода. Элементами списка вывода могут являться имена переменных, выражения, константы. Прежде чем вывести на экран компьютер значения выражений сначала вычислит. Элементы списка, также как и в операторах ввода, разделяются запятыми. Различие между двумя операторами вывода таково: после выполнения оператора Writeln (от Write line) происходит переход на новую строчку, а после выполнения инструкции Write, переход на новую строчку не происходит и печать по последующим командам вывода Write или Writeln будет происходить на той же строчке. При вызове оператора Writeln без параметров просто происходит переход на новую строчку. Приведем пример использования операторов ввода и вывода: Program Inteface; Var R,S : Real; Begin Write('Введите радиус круга '); {Печать на экране просьбы о вводе} Readln(R); {Ввод значения в переменную R с клавиатуры} S:=4*ARCTAN(1)*SQR(R); {Вычисление площади круга (pR2)} Writeln('Площадь круга радиусом ',R,' равна ',S) End. Эта программа запрашивает у пользователя значение радиуса круга, обеспечивает возможность ввести его значение, рассчитывает и выводит на экран величину площади круга с таким радиусом. Таким образом, появляется возможность, не внося изменений в текст программы, вводить различные значения радиуса и получать, соответствующие им значения площади круга. Для этого достаточно несколько раз запустить программу. Также эта программа демонстрирует следующее правило: выдача результатов должна быть прокомментирована так, чтобы был ясен смысл напечатанных чисел. Действительно, ведь можно было бы ограничиться Writeln(S), но значение выведенного программой числа в этом случае было бы ясно только тому, кто эту программу написал. | Метки. Оператор безусловного перехода. | Каждый дом на улице имеет свой номер, все люди имеют собственные имена, даже ячейки памяти компьютера имеют каждая свой адрес. Все это принято для того, чтобы иметь возможность однозначно указать на определяемый объект. Точно также, для указания на операторы в программах применяются метки. Метка в стандарте языка Паскаль представляет собой целое неотрицательное число. Все используемые в программе метки должны быть перечислены в разделе описания меток, начинающемся служебным словом Label, например: Label 1, 2, 8; Одной меткой можно пометить только один оператор. Метка от помеченного оператора отделяется двоеточием. Пример: 6: Writeln(14/2); Во всех приведенных ранее программах операторы выполнялись один за другим в том порядке, в котором они были записаны в тексте. Такая алгоритмическая структура называется прямым следованием. Однако, в языке Паскаль изначально существует оператор, нарушающий прямолинейное выполнение программы, передающий управление в произвольную ее точку. Такая инструкция называется безусловным переходом и имеет такой формат: Goto <метка>; Оператор, к которому происходит переход должен быть помечен данной меткой. Использовать оператор безусловного перехода следует крайне осторожно во избежание получения ошибочных результатов или полного "зацикливания" программы. Вообще, употребление данной команды среди программистов считается дурным тоном. Как вы убедитесь, всегда существует возможность обойтись без него. | Условный оператор | Одной из основных алгоритмических структур является ветвление (альтернатива). Если условие выполняется, то будет выполнена инструкция "1", если нет, то - инструкция "2". Несмотря на то, что в схеме присутствуют два действия, выполнено будет только одно, так как условие либо ложно, либо истинно. Третьего не дано. Такая схема позволяет решать задачи, в которых в зависимости от сложившихся обстоятельств требуется совершить то или иное действие. Нет никакого сомнения, что число задач такого рода огромно. Более того, очень сложно придумать реально значимое задание, алгоритм выполнения которого содержал бы в себе простое прямое следование команд. Даже примитивный пример, взятый из курса математики, как вы увидите, не может быть решен без использования ветвления. Итак, необходимо вычислить значение выражения y=1/x. Вам известно, что данная функция не всегда имеет значение, то есть не для всех значений аргумента существует значение результата. Наша задача так составить алгоритм, чтобы исполнитель ни в коем случае не встал в тупик, даже при получении нуля в качестве аргумента. Сформулировать это на естественном языке не трудно: 1. Получить значение x. 2. Если x=0, то сообщить, что выражение значения не имеет, иначе - вычислить y как 1/x. Таким образом используется приведенная выше алгоритмическая структура. Она может быть выражена простыми словами: Если <усл.> {Если выполняется условие} то <действие 1> {то выполнить действие № 1 } иначе <действие 2> {иначе - выполнить действие № 2 } все Как это записать на Паскале? Да точно так же, только по-английски. Формат условного оператора на языке Паскаль: If <условие> Then <оператор 1> Else <оператор 2>; Обратите внимание на то, что в Then- и Else- части стоит только один оператор. Но что делать, чтобы решить задачу, в которой по выполнению или невыполнению условия нужно совершить не одно, а несколько действий? Здесь приходит на помощь уже известный вам составной оператор. В операторные скобки можно заключить любое количество операторов. Вариант условного оператора в этом случае: If <условие> Then Begin <группа операторов 1> end Else Begin < группа операторов 2> end; Знак "точка с запятой" не ставится перед служебным словом Else, но операторы в группах, естественно, отделяются друг от друга этим знаком. Теперь поговорим об условиях. В программах на языке Паскаль условия представляют собой выражения, значением которых является величина логического (Boolean) типа. Это может быть как просто переменная указанного типа, так и сложная последовательность высказываний, связанных логическими операциями. В простых условиях могут применяться знаки операций сравнения: >(больше), <(меньше), =(равно), <>(не равно), >=(больше или равно), <=(меньше или равно). Примеры простых условий: A=5 {Значение переменной А равно 5} (C+D3)>=(D1*(45-2)) {Значение выражения в левой части больше либо равно значению выражения из правой части} S<>'ABC' {Значение переменной S не равно строковой константе 'ABC'} Приведем пример решения еще одной задачи: "Из двух чисел выбрать наибольшее". На первый взгляд решение очевидно, но оно не столь тривиально, как кажется. Program Example; Var A,B,C : Real; {A,B - для хранения аргументов, C - результат} Begin Writeln('Введите два числа'); Readln(A,B); {Вводим аргументы с клавиатуры} If A>B Then C:=A Else C:=B; {Если A>B, то результат - A, иначе результат - B} Writeln(C); {Выводим результат на экран} End. Еще один классический пример: "По заданным коэффициентам решить квадратное уравнение". Эта задача сложнее, поэтому перед тем как писать программу составим алгоритм, записав его в виде блок-схемы. Сначала вводим коэффициенты, затем вычисляем дискриминант. Теперь возникает две возможности: либо отсутствие действительных корней в случае отрицательного дискриминанта, либо эти корни можно все-таки вычислить и вывести на экран в случае неотрицательного дискриминанта (случай равенства дискриминанта нулю входит сюда же, корней - два, только они одинаковые J). При записи алгоритма на языке программирования следует учесть, что в ветви "нет" не одно действие, а три, поэтому следует применить составной оператор. Арифметические выражения не забывайте записывать в соответствии с правилами языка Паскаль. В остальном, эта программа не сложнее предыдущей. Program Sq1; Var A, B, C, D, X1, X2 : Real; Begin Writeln ('Введите коэффициенты квадратного уравнения'); Readln (A,B,C); D:=B*B-4*A*C; If D<0 Then Writeln ('Корней нет! ') Else Begin X1:=(-B+SQRT(D))/2/A; X2:=(-B-SQRT(D))/2/A; Writeln ('X1=', X1:8:3, ' X2=',X2:8:3) End End. Интересно, что в качестве оператора, который выполняется по выполнению или невыполнению условия, может выступать условный же оператор. В этом случае говорят о вложенности условных операторов. Я настоятельно рекомендую при решении такого рода задач составлять блок-схему алгоритма в тетради. Только потом, при составлении программы, вам остается лишь аккуратно прописывать сначала всю Then- часть, а затем переходить к Else- части. Обычно при записи условных операторов на языке Паскаль (особенно при множественных ветвлениях) команды записывают уступом вправо и вниз. Это повышает наглядность, и, поверьте, снижает потери времени на отладку. Для иллюстрации решим еще одну задачу: "решить уравнение вида A*x^2 + B*x + C = 0". Прошу не путать с квадратным уравнением, для которого нам было известно, что коэффициент А не равен нулю. Здесь же коэффициенты могут быть любыми числами. Исходя из элементарных математических рассуждений, получаем следующий алгоритм: Program Sq2; Var A, B, C, D, X, X1, X2 : Real; Begin Writeln ('Введите коэффициенты уравнения (A, B, C) '); If A=0 Then If B=0 Then If C=0 Then Writeln('X - любое число') Else Writeln('Корней нет! ') Else Begin X:=-C/B; Writeln('X=',X:8:3) End Else Begin D:=B*B-4*A*C; If D<0 Then Writeln ('Корней нет! ') Else Begin X1:=(-B+SQRT(D))/2/A; X2:=(-B-SQRT(D))/2/A; Writeln ('X1=', X1:8:3, ' X2=',X2:8:3) End End End. | Цикл. Виды Циклов. | Циклом называется многократное повторение однотипных действий. Телом же цикла будем называть те самые действия, которые нужно многократно повторять. Как вы понимаете, повторять одни и те же действия можно и при помощи оператора безусловного перехода. Если записать эти действия в программе одно за другим, а в конце поставить оператор перехода к началу этого блока. Однако таким образом можно получить только программу, которая работает вечно (зацикливается). Этого можно избежать, используя совместно с оператором перехода условный оператор, поставив выполнение перехода в зависимость от выполнения некого условия. Таким образом, мы получим структуру условного перехода и возможность организации конечного цикла. Вообще говоря, так мы можем решить практически любую задачу, требующую реализации циклического алгоритма. Конечно же, при помощи одного только топора можно построить дом. Поставим перед собой вопросы: "А будет ли этот дом красив? Сколько времени и сил можно сэкономить, используя всевозможные специальные инструменты?". Создатель языка Паскаль Никлаус Вирт также задался этими вопросами и решил их в пользу расширения языка тремя специальными возможностями организации циклов. Для чего? - Для удобства, краткости, простоты чтения программы и, не побоюсь этого слова, красоты. Итак, существует три вида цикла, имеющих собственные операторы на языке Паскаль для их записи. Эти виды имеют собственные условные названия: "Пока", "До", "С параметром". Друг от друга они несколько отличаются и используются каждый для своего класса задач. Цикл "ПОКА" Группа операторов, называемая "телом цикла", судя по этой схеме, будет выполняться пока истинно условие цикла. Выход из цикла произойдет, когда условие перестанет выполняться. Если условие ложно изначально, то тело цикла не будет выполнено ни разу. Если условие изначально истинно и в теле цикла нет действий, влияющих на истинность этого условия, то тело цикла будет выполняться бесконечное количество раз. Такая ситуация называется "зацикливанием". Прервать зациклившуюся программу может либо оператор (нажав Ctrl+C), либо аварийный останов самой программы, в случае переполнения переменной, деления на ноль и т.п., поэтому использовать структуру цикла следует с осторожностью, хорошо понимая, что многократное выполнение должно когда-нибудь заканчиваться. На языке Pascal структура цикла "Пока" записывается следующим образом: While <условие> Do <оператор>; Правда, лаконично? По-русски можно прочитать так: "Пока истинно условие, выполнять оператор". Здесь, так же как в формате условного оператора, подразумевается выполнение только одного оператора. Если необходимо выполнить несколько действий, то может быть использован составной оператор. Тогда формат оператора принимает такой вид: While <условие> Do Begin <оператор #1>; <оператор #2>; <оператор #3>; . . . End; Цикл "ДО" Этот вид цикла отличается от предыдущего в основном тем, что проверка условия повторения тела цикла находится не перед ним, а после. Поэтому цикл "До" называют циклом "с постусловием", а "Пока" - "с предусловием". Обратите также внимание на то, что новая итерация (повторное выполнение тела цикла) происходит не тогда, когда условие справедливо, а как раз тогда, когда оно ложно. Поэтому цикл и получил свое название (выполнять тело цикла до выполнения соответствующего условия). Интересно, что в случае, когда условие цикла изначально истинно, тело цикла все равно будет выполнено хотя бы один раз. Именно это отличие "до" от "пока" привело к тому, что в программировании они не подменяют друг друга, а используются для решения задач, к которым они более подходят. Формат цикла на языке Pascal: Repeat <оператор #1>; <оператор #2>; <оператор #3>; . . . Until <условие>; Читается так: "Выполнять оператор #1, оператор #2. : до выполнения условия". Здесь не требуется использование составного оператора, потому, что сами слова Repeat и Until являются операторными скобками. Цикл "С параметром". В данном случае параметром будет являться целочисленная переменная, которая будет изменяться на единицу при каждой итерации цикла. Таким образом, задав начальное и конечное значения для такой переменной, можно точно установить количество выполнений тела цикла. Нарисовать блок-схему такой структуры вы сможете сами после некоторых пояснений. Форматов у этого вида цикла предусмотрено два: For <И.П.>:=<Н.З.> To <К.З.> Do <оператор>; For <И.П.>:=<Н.З.> Downto <К.З.> Do <оператор>; Здесь И.П. - имя переменной-параметра, Н.З. - его начальное значение, К.З. - соответственно конечное значение параметра. В качестве начального и конечного значений Читается данная структура так: "Для переменной (далее следует ее имя) от начального значения до конечного выполнять оператор (являющийся телом цикла)". Иногда цикл с параметром даже называют "Для" или "For". В первом случае параметр с каждой итерацией увеличивается на единицу, во втором - уменьшается. Выполняется этот цикл по следующему алгоритму: 1. переменной-параметру присваивается начальное значение; 2. выполняется тело цикла; 3. переменная-параметр автоматически увеличивается на 1 (в первом случае формата); 4. если параметр превышает конечное значение, то происходит выход из цикла, иначе - переход к пункту 2. Примечание: при использовании Downto параметр автоматически уменьшается на 1, а выход из цикла происходит тогда, когда параметр становится меньше конечного значения. Таким образом, в отличие от первых двух видов цикла, этот цикл используется тогда, когда известно необходимое количество выполнений тела цикла. Вообще говоря, цикл "Пока" является универсальным, то есть любая задача, требующая использования цикла, может быть решена с применением этой структуры. Циклы "До" и "С параметром" созданы для удобства программирования. Пример. Найти сумму квадратов всех натуральных чисел от 1 до 100. Решим эту задачу с использованием всех трех видов циклов. I. С использованием цикла "Пока". Program Ex1; Var A : Integer; S : Longint; Begin A:=1; S:=0; While A<=100 Do Begin S:=S+A*A; A:=A+1 End; Writeln(S) End. II. С использованием цикла "До". Program Ex2; Var A : Integer; S : Longint; Begin A:=1; S:=0; Repeat S:=S+A*A; A:=A+1 Until A>100; Writeln(S) End. III. С использованием цикла "С параметром". Program Ex3; Var A : Integer; S : Longint; Begin S:=0; For A:=1 To 100 Do S:=S+A*A; Writeln(S) End. Теперь вам известны все основные алгоритмические структуры языка Паскаль. Комбинируя их, возможно запрограммировать решение любой задачи, конечно, если таковое существует. Тем не менее, изучение языка на этом не закачивается, так как для написания хороших программ по утверждению уважаемого Никлауса Вирта (за время моей работы у меня не появилось оснований в этом сомневаться) нужны кроме алгоритмических, еще удобные структуры данных. В рассматриваемом языке таких структур множество, для каждого вида определены свои команды и операции. К их рассмотрению мы и переходим. | Строковые операции | До сих пор мы с вами рассматривали программы, реализующие алгоритмы обработки числовых данных. Однако хоть ЭВМ изначально и были созданы только для этой цели, по мере развития аппаратной части появилась возможность оцифровывать данные других типов, хранить их в памяти машины, перерабатывать, выводить во внешний по отношению к компьютеру мир. Проще всего можно было так поступить с текстовой информацией. Если не ставить перед машиной задачу "понимания" смысла текста, то задача оцифровки сводится к установлению правил замены символов (литер) при вводе в компьютер на их коды и обратной замены при выводе информации на экран или принтер. Такие правила, конечно же, были составлены. Как водится, сначала их было множество (вспомните разнообразие таблиц кодировки), затем весь мир остановился на ASCII. Все языки программирования высокого уровня имеют средства работы с литерными величинами. Паскаль - не исключение. Как вам уже известно, в стандарте языка описаны два типа переменных для литерных величин. Это - String и Char. Напомню - переменная типа Char может содержать в себе только один единственный символ, тип String предназначен для хранения строковых величин до 255 символов длиною. Кстати, вы знаете не все о типе String. При описании переменной этого типа вы можете сами указать максимальное число символов, которое можно занести в нее. Конечно же, это число не должно превышать 255. Делается это так: Var S : String[30]; Для чего это нужно? Дело в том, что при компиляции для каждой переменной отводится свой участок памяти. Если мы будем выделять для всех переменных типа String по 256 байт, то это приведет к тому, что при использовании достаточно большого их количества, памяти может и не хватить? Но если в переменной мы собираемся хранить, например, фамилию пользователя, то тридцати символов (тридцати байт) для этого вполне достаточно. Таким образом, экономится память и увеличивается быстродействие программ. Переменным строкового типа можно присваивать строковые величины (внутри программы они заключаются в апострофы), значения выражений, которые приводят к строковым величинам. Значения можно также вводить с клавиатуры. При этом апострофы не используются. Как вам известно, в числовую переменную нельзя ввести строковую величину. Сделать наоборот - возможно, однако число, находящееся в строковой переменной представляет собой просто последовательность символов (цифр), поэтому в арифметических выражениях участвовать не может. Также, новым для вас явится то, что при использовании строковой переменной, к каждому ее символу можно обратиться отдельно. Необходимо только знать номер нужного символа от начала строки. Его достаточно поставить после имени переменной типа String в квадратных скобках. Пример: S[5] - пятый символ строки S. С отдельным символом строки можно производить все действия, которые можно производить с любой символьной переменной (ввод, присвоение, вывод на экран, участие в выражениях и т.д.). Обратите внимание на то, что нумерация символов в строке начинается с единицы. Внутри квадратных скобок вместо числа может находиться выражение, результатом которого является целое число. Главное чтобы символ с таким номером в строке существовал. Но как же узнать, сколько символов в данный момент находится в строковой переменной? Для этого существует специальная функция, которая возвращает длину строковой переменной в символах. Это функция Length. Ее формат: Length(S) Здесь S - либо строковая величина, либо строковая переменная. Приведенная далее программа выводит на экран длину введенной пользователем строковой величины. Program Str1; Var S : String; Begin Writeln('Введите последовательность символов'); Readln(S); Writeln('Вы ввели строку из ',Length(S), ' символов') End. Другой пример: Решим задачу: "Введенную строку вывести на экран по одному символу в строке экрана". Program Str2; Var S : String; I : Byte; Begin Writeln('Введите строку'); Readln(S); For I:=1 to Length(S) do {организуем цикл, начиная с первого символа} Writeln(S[I]) {строки, до последнего (номер последнего} {совпадает с количеством символов строки S) } End. Какие же еще действия можно выполнять с переменными строкового типа? Две строковые величины можно состыковывать. Эта операция называется конкатенацией и обозначается знаком "+". Например, результатом выполнения следующих команд: R:= 'kadabra'; H:= 'abra'; S:=H+R; в переменной S будет значение 'abrakadabra'. Для конкатенации результат зависит от порядка операндов (в отличие от операции сложения). Следует помнить о том, какой максимальной длины может быть результирующая переменная, так как в случае превышения значением выражения числа, указанного после String в описании переменной, "лишние" символы в переменную не попадут. Строковые величины можно сравнивать между собой. Это относится также и к строковым переменным. Но как же компьютер определяет, какая строка больше: та, которая длиннее? та, которая содержит больше заглавных букв? На самом деле такая проверка проходит довольно сложно: компьютер сравнивает сначала первые символы строк. Большим из двух считается тот, код которого больше (вспомните, что такое код символа). Если равны первые символы, то так же анализируется следующая пара до тех пор, пока не будет найдено различие. Если начало строк совпадает, а одна из них кончается раньше, то вторая автоматически называется большей. Код символа в Паскале можно определить при помощи функции Ord. Ее формат: Ord(C), где С - либо непосредственно указанный символ, либо переменная символьного типа, либо один символ строковой переменной. Вообще, функция Ord имеет более глубокий смысл, но об этом - позже. Есть и обратная функция, которая возвращает символ по известному коду. Это функция Chr(N), где N - выражение, приводящее к целому числу в интервале от 0 до 255 (возможные значения кода символа). Очевидно, что Chr(Ord(C))=C, Ord(Chr(N))=N. Следующая маленькая программа выводит на экран кодовую таблицу: Program Str3; Var I : Byte; Begin For I:=32 to 255 do Write('VV',I:4, '-',Chr(I)) End. Цикл в программе начинается с 32 потому, что символы с кодами от 0 до 31 являются управляющими и не имеют соответствующего графического представления. Задача: "Определить, является ли введенная строка "перевертышем". Перевертышем называется такая строка, которая одинаково читается с начала и с конца. Например, "казак" и "потоп" - перевертыши, "канат" - не перевертыш". Поступим следующим образом: из введенной строки сформируем другую строку из символов первой, записанных в обратном порядке, затем сравним первую строку со второй; если они окажутся равны, то ответ положительный, иначе - отрицательный. Естественно, предложенный способ решения не является единственно возможным. Program Str4; Var S,B : String; I : Byte; Begin Writeln('Введите строку'); Readln(S); B:=''; {Переменной B присваиваем значение "пустая строка"} For I:=1 to Length(S) do B:=S[I]+B; {Конкатенация. Символы строки S пристыковываются к} {переменной B слева. Самым левым окажется последний.} If B=S Then Writeln('Перевертыш') Else Writeln('Не перевертыш') End. Число, записанное в строковую переменную, естественно числом не является, но очень часто требуется его все же использовать в качестве числа. Для этого нужно произвести преобразование типа. Перевод строкового представления числа в числовое выполняет в Паскале оператор Val. Его формат: Val(S,X,C); Здесь S - строка, содержащая число, X - числовая переменная, в которую будет помещен результат, С - переменная целочисленного типа, в которую помещается первого встреченного в S отличного от цифры символа. Если после выполнения оператора Val переменная С имеет значение 0, то это означает, что преобразование типа прошло совершенно успешно и в строке нецифровых символов не встретилось. Противоположное действие осуществляет оператор Str. Формат оператора: Str(X,S); X - число (либо арифметическое выражение), S - строковая переменная. В переменную S попадает строковое представление числа X. Это нужно, например, при необходимости выводить на экран числа в графическом режиме (будет изучено позже), так как стандартные процедуры вывода на экран там работают только со строковыми величинами. Для иллюстрации рассмотрим такую задачу: "Найти сумму цифр введенного натурального числа". Используя только числовые переменные, решить ее можно, но предлагаемое здесь решение, по-моему, проще. Program Str5; Var S : String; I,X,A,C : Integer; Begin Writeln('Введите натуральное число'); Readln(S); {Число вводится в строковую переменную} A:=0; For I:=1 To Length(S) Do Begin Val(S[I],X,C); {Цифровой символ превращается в число} A:=A+X {Цифры суммируются} End; Writeln('Сумма цифр равна ',A) End. Теперь рассмотрим еще несколько действий над строками: оператор DELETE(S,I,C) из строковой переменной S удаляет C символов, начиная с I-того; оператор INSERT(SN,S,I) вставляет подстроку SN в строковую переменную S перед символом с номером I; функция COPY(S,I,C) возвращает подстроку строки S из C символов, начиная с символа с номером I; функция Pos(SN,S) возвращает номер символа, с которого в строке S начинается подстрока SN (позицию первого вхождения подстроки в строку). Если такой подстроки нет, то возвращается ноль. Пример их использования: "Во введенной строке заменить все вхождения подстроки 'ABC' на подстроки 'KLMNO'". Program Str6; Var S : String; A : Byte; Begin Writeln('Введите строку'); Readln(S); While Pos('ABC',S)<>0 Do Begin A:= Pos('ABC',S); Delete(S,A,3); Insert('KLMNO',S,A) End; Writeln(S) End. | Определение типов | Как было упомянуто ранее, в изучаемом языке возможно определять новые типы переменных. После определения этот тип становится доступным для описания переменных, также как и стандартные типы. Новый тип перед первым его использованием должен быть описан в соответствующем разделе описаний. Его заголовок - служебное слово Type. Type <Имя типа> = <Описание типа>; Есть несколько способов описания. Иногда говорят даже о видах типов (как бы это странно ни звучало). Итак, первым рассмотрим так называемый перечисляемый тип. Перечисляемый тип используется для повышения наглядности программ, позволяя записывать в переменные этого типа названия разнообразных объектов, исследуемых программой. Этот тип представляет собой набор идентификаторов, с которыми могут совпадать значения параметров. Формат описания следующий: <Имя типа> = (<Ид.1>, <Ид.2>,? <Ид.n>); Далее можно определить любое число переменных уже описанного типа. Обратите внимание на то, что каждый идентификатор может участвовать в описании только одного перечисляемого типа. Этим переменным можно присваивать только значения из списка, определенного при описании типа. Эти значения не являются ни числами, ни строковыми величинами, ни даже величинами логического типа, поэтому они не могут участвовать в арифметических, строковых, логических выражениях, а также не могут быть выведены на экран или принтер. Величины перечисляемого типа можно сравнивать между собой, над их множеством в языке Паскаль определены несколько функций: Ord(X) - порядковый номер значения переменной X в списке идентификаторов. Succ(X) - следующее значение для величины Х. Pred(X) - предыдущее значение данного типа. Обратите внимание на то, что для функции Ord нумерация среди значений идет, начиная от нуля. Для последнего значения нельзя применять функцию Succ, для первого - Pred. Переменные различных перечисляемых типов несовместимы друг с другом. Множество стандартных порядковых типов в языке Паскаль на самом деле определены как перечисляемые. Это типы Char, Integer, другие. Достоинства стандартных порядковых типов лишь в том, что над каждым из них уже определены специфические действия. Например, тип Boolean описан так: Type Boolean = (False, True); Единственное его отличие от перечисляемых типов, определяемых программистом, состоит в том, что значения типа Boolean можно выводить на экран. Можете проверить, Ord(False)=0. Интересно, что переменная перечисляемого типа может быть счетчиком в цикле "с параметром". Пример: Program T1; Type Colors = (Black, Blue, Green, Cyan, Red, Magenta, Brown, Yellow, White); Var C1,C2 : Colors; Begin C1:=Green; C2:=Red; Writeln(Ord(C1), Ord(Succ(C2))) End. Во время выполнения на экране появятся числа "2" и "5", что соответствует номерам значений Green и Magenta. Следующий тип, который можно определить в программе - тип-диапазон. Здесь не нужно перечислять все значения этого типа, потому, что возможными для него являются значения поддиапазона уже определенного до него любого порядкового типа (стандартного или описанного ранее перечисляемого типа). Достаточно лишь указать начальную и конечную величину отрезка порядкового типа. Единственное условие: начальное значение не должно превышать конечное. Формат описания отрезочного типа: Type <Имя типа>=<Нач.>..<Кон.>; Примеры: Type Age=0..150; {Целое число в интервале от 0 до 150} Lat='A'.. 'Z'; {Заглавные буквы латинского алфавита} Month=(January, February, March, April, May, June, July, August, September, October, November, December); Spring=March..May; {Весенние месяцы} Есть еще одна возможность определить новый тип, о существовании которой можно было бы и догадаться. Type <Имя типа>=<Имя ранее определенного или стандартного типа>; Пример: Type Number=Byte; | Массивы | До сих пор мы рассматривали переменные, которые имели только одно значение, могли содержать в себе только одну величину определенного типа. Исключением являлись лишь строковые переменные, которые представляют собой совокупность данных символьного типа, но и при этом мы говорили о строке, как об отдельной величине. Вы знаете, что компьютер предназначен в основном для облегчения работы человека с большими информационными объемами. Как же, используя только переменные известных вам типов, сохранить в памяти и обработать данные, содержащие десяток, сотню, тысячу чисел или, к примеру, строк? А ведь такие задачи встречаются в любой области знания. Конечно, можно завести столько переменных, сколько данных, можно даже занести в них значения, но только представьте, какой величины будет текст такой программы, сколько времени потребуется для его составления, как много места для возможных ошибок? Естественно, об этом задумывались и авторы языков программирования. Поэтому во всех существующих языках имеются типы переменных, отвечающие за хранение больших массивов данных. В языке Паскаль они так и называются: "массивы". Массивом будем называть упорядоченную последовательность данных одного типа, объединенных под одним именем. Кстати, под это определение подходит множество объектов из реального мира: словарь (последовательность слов), мультфильм (последовательность картинок) и т. д. Проще всего представить себе массив в виде таблицы, где каждая величина находится в собственной ячейке. Положение ячейки в таблице должно однозначно определяться набором координат (индексов). Самой простой является линейная таблица, в которой для точного указания на элемент данных достаточно знания только одного числа (индекса). Мы с вами пока будем заниматься только линейными массивами, так как более сложные структуры строятся на их основе. Описание типа линейного массива выглядит так: Type <Имя типа>=Array [<Диапазон индексов>] Of <Тип элементов>; В качестве индексов могут выступать переменные любых порядковых типов. При указании диапазона начальный индекс не должен превышать конечный. Тип элементов массива может быть любым (стандартным или описанным ранее). Описать переменную-массив можно и сразу (без предварительного описания типа) в разделе описания переменных: Var <Переменная-массив> : Array [<Диапазон индексов>] Of <Тип элементов>; Примеры описания массивов: Var S, BB : Array [1..40] Of Real; N : Array ['A'..'Z'] Of Integer; R : Array [-20..20] Of Word; T : Array [1..40] Of Real; Теперь переменные S, BB и T представляют собой массивы из сорока вещественных чисел; массив N имеет индексы символьного типа и целочисленные элементы; массив R может хранить в себе 41 число типа Word. Единственным действием, которое возможно произвести с массивом целиком - присваивание. Для данного примера описания впоследствии допустима следующая запись: S:=BB; Однако, присваивать можно только массивы одинаковых типов. Даже массиву T присвоить массив S нельзя, хотя, казалось бы, их описания совпадают, произведены они в различных записях раздела описания. Никаких других операций с массивами целиком произвести невозможно, но с элементами массивов можно работать точно также, как с простыми переменными соответствующего типа. Обращение к отдельному элементу массива производится при помощи указания имени всего массива и в квадратных скобках - индекса конкретного элемента. Например: R[10] - элемент массива R с индексом 10. Фундаментальное отличие компонента массива от простой переменной состоит в том, что для элемента массива в квадратных скобках может стоять не только непосредственное значение индекса, но и выражение, приводящее к значению индексного типа. Таким образом реализуется косвенная адресация: BB[15] - прямая адресация; BB[K] - косвенная адресация через переменную K, значение которой будет использовано в качестве индекса элемента массива BB. Такая организация работы с такой структурой данных, как массив, позволяет использовать цикл для заполнения, обработки и распечатки его содержимого. Если вы помните, с такой формой организации данных мы встречались, когда изучали строковые переменные. Действительно, переменные типа String очень близки по своим свойствам массивам типа Char. Отличия в следующем: строковые переменные можно было вводить с клавиатуры и распечатывать на экране (с обычным массивом это не проходит); длина строковой переменной была ограничена 255 символами (255 B), а для размера массива критическим объемом информации является 64 KB. Теперь рассмотрим несколько способов заполнения массивов и вывода их содержимого на экран. В основном мы будем пользоваться числовыми типами компонент, но приведенные примеры будут справедливы и для других типов (если они допускают указанные действия). Program M1; Var A : Array [1..20] Of Integer; Begin A[1]:=7; {Заполняем массив значениями (отдельно каждый компонент)} A[2]:=32; A[3]:=-70; .............. {Трудоемкая задача?} A[20]:=56; Writeln(A[1],A[2],A[3], ?,A[20]) End. Как бы ни был примитивен приведенный пример, он все же иллюстрирует возможность непосредственного обращения к каждому элементу массива отдельно. Правда, никакого преимущества массива перед несколькими простыми переменными здесь не видно. Поэтому - другой способ: Program M2; Var A : Array [1..20] Of Integer; I : Integer; Begin For I:=1 To 20 Do {Организуем цикл с параметром I по всем возможным} Readln(A[I]); {значениям индексов и вводим A[I] с клавиатуры } For I:=20 Downto 1 Do {Распечатываем массив в обратном порядке} Write(A[I],'VVV') End. Эта программа вводит с клавиатуры 20 целых чисел, а затем распечатывает их в обратном порядке. Теперь попробуйте написать такую же программу, но без использования структуры массива. Во сколько раз она станет длиннее? Кстати, введение язык Паскаль цикла с параметром было обусловлено во многом необходимостью обработки информационных последовательностей, т. е. массивов. Следующая программа заполняет массив значениям квадратов индексов элементов: Program M3; Const N=50; {Константа N будет содержать количество элементов массива} Var A : Array [1..N] Of Integer; I : Integer; Begin For I:=1 To N Do A[I]:=I*I For I:=1 To N Do Write(A[I],'VVV') End. В дальнейшем для учебных целей мы будем использовать массивы, заданные с помощью генератора случайных чисел. В языке Паскаль случайные числа формирует функция Random. Числа получаются дробными, равномерно расположенными в интервале от 0 до 1. Выражение, дающее целое случайное число в интервале [-50,50] будет выглядеть так: Trunc(Random*101)-50 Зададим и распечатаем случайный массив из сорока целых чисел: Program M4; Const N=40; {Константа N будет содержать количество элементов массива} Var A : Array [1..N] Of Integer; I : Integer; Begin For I:=1 To N Do Begin A[I]:= Trunc(Random*101)-50 Write(A[I],'VVV') End End. С обработкой линейных массивов связано множество задач. Их мы рассмотрим на практических занятиях. |
Двумерные и многомерные массивы | Представьте себе таблицу, состоящую из нескольких строк. Каждая строка состоит из нескольких ячеек. Тогда для точного определения положения ячейки нам потребуется знать не одно число (как в случае таблицы линейной), а два: номер строки и номер столбца. Структура данных в языке Паскаль для хранения такой таблицы называется двумерным массивом. Описать такой массив можно двумя способами: I. Var A : Array [1..20] Of Array [1..30] Of Integer; II. Var A : Array [1..20,1..30] Of Integer; В обоих случаях описан двумерный массив, соответствующий таблице, состоящей из 20 строк и 30 столбцов. Приведенные описания совершенно равноправны. Отдельный элемент двумерного массива адресуется, естественно, двумя индексами. Например, ячейка, находящаяся в 5-й строке и 6-м столбце будет называться A[5][6] или A[5,6]. Для иллюстрации способов работы с двумерными массивами решим задачу: "Задать и распечатать массив 10X10, состоящий из целых случайных чисел в интервале [1,100]. Найти сумму элементов, лежащих выше главной диагонали." При отсчете, начиная с левого верхнего угла таблицы, главной будем считать диагональ из левого верхнего угла таблицы в правый нижний. При этом получается, что элементы, лежащие на главной диагонали будут иметь одинаковые индексы, а для элементов выше главной диагонали номер столбца будет всегда превышать номер строки. Договоримся также сначала указывать номер строки, а затем - номер столбца. Program M5; Var A : Array[1..10,1..10] Of Integer; I, K : Byte; S : Integer; Begin S:=0; For I:=1 To 10 Do Begin For K:=1 To 10 Do Begin A[I,K]:=Trunc(Random*100)+1; Write(A[I,K]:6); If K>I Then S:=S+A[I,K] End; Writeln End; Writeln('Сумма элементов выше гл. диагонали равнаV',S) End. Если модель данных в какой-либо задаче не может свестись к линейной или плоской таблице, то могут использоваться массивы произвольной размерности. N-мерный массив характеризуется N индексами. Формат описания такого типа данных: Type <Имя типа>=Array[<диапазон индекса1>,<диапазон индекса2>,... <диапазон индекса N>] Of <тип компонент>; Отдельный элемент именуется так: <Имя массива>[<Индекс 1>,<Индекс 2>,...,<Индекс N>] | Процедуры и функции | При решении сложных объемных задач часто целесообразно разбивать их на более простые. Метод последовательной детализации позволяет составить алгоритм из действий, которые, не являясь простыми, сами представляют собой достаточно самостоятельные алгоритмы. В этом случае говорят о вспомогательных алгоритмах или подпрограммах. Использование подпрограмм позволяет сделать основную программу более наглядной, понятной, а в случае, когда одна и та же последовательность команд встречается в программе несколько раз, даже более короткой и эффективной. В языке Паскаль существует два вида подпрограмм: процедуры и функции, определяемые программистом. Процедурой в Паскале называется именованная последовательность инструкций, реализующая некоторое действие. Функция отличается от процедуры тем, что она должна обязательно выработать значение определенного типа. Процедуры и функции, используемые в программе, должны быть соответствующим образом описаны до первого их упоминания. Вызов процедуры или функции производится по их имени. Подпрограммы в языке Паскаль могут иметь параметры (значения, передаваемые в процедуру или функцию в качестве аргументов). При описании указываются так называемые формальные параметры (имена, под которыми будут фигурировать передаваемые данные внутри подпрограммы) и их типы. При вызове подпрограммы вместе с ее именем должны быть заданы все необходимые параметры в том порядке, в котором они находятся в описании. Значения, указываемые при вызове подпрограммы, называются фактическими параметрами. Формат описания процедуры: Procedure <Имя процедуры> (<Имя форм. параметра 1>:<Тип>; < Имя форм. параметра 2>:<Тип>?); <Раздел описаний> Begin <Тело процедуры> End; Раздел описаний может иметь такие же подразделы, как и раздел описаний основной программы (описание процедур и функций - в том числе). Однако все описанные здесь объекты "видимы" лишь в этой процедуре. Они здесь локальны также, как и имена формальных параметров. Объекты, описанные ранее в разделе описаний основной программы и не переопределенные в процедуре, называются глобальными для этой подпрограммы и доступны для использования. Легко заметить схожесть структуры программы целиком и любой из ее процедур. Действительно, ведь и процедура и основная программа реализуют некий алгоритм, просто процедура не дает решения всей задачи. Отличие в заголовке и в знаке после End. Формат описания функции: Function <Имя функции> (<Имя форм. параметра 1>:<Тип>; < Имя форм. параметра 2>:<Тип>?) : <Тип результата>; <Раздел описаний> Begin <Тело функции> End; В теле функции обязательно должна быть хотя бы команда присвоения такого вида: <Имя функции>:=<Выражение>; Указанное выражение должно приводить к значению того же типа, что и тип результата функции, описанный выше. Вызов процедуры представляет в программе самостоятельную инструкцию: <Имя процедуры>(<Фактический параметр 1>, < Фактический параметр 2>?); Типы фактических параметров должны быть такими же, что и у соответсвующих им формальных. Вызов функции должен входить в выражение. При вычислении значения такого выражения функция будет вызвана, действия, находящиеся в ее теле, будут выполнены, в выражение будет подставлено значение результата функции. Приведем простейший пример использования подпрограммы. Задача: "Найти максимальное из трех введенных чисел". Для решения воспользуемся описанием функции, принимающей значение максимального из двух чисел, которые передаются в нее в виде параметров. Program Fn; Var A,B,C :Real; Function Max(A,B:Real):Real; {Описываем функцию Max с формальными} Begin {параметрами A и B, которая принимает } If A>B Then Max:=A {значение максимального из них } Else Max:=B {Здесь A и B - локальные переменные } End; Begin Writeln('Введите три числа'); Readln(A,B,C); Writeln('Максимальным из всех является ', Max(Max(A,B),C)) End. Обратите внимание на краткость тела основной программы и на прозрачность действий внутри функции. Формальные параметры A и B, используемые в подпрограмме, не имеют никакого отношения переменным A и B, описанным в основной программе. Существует два способа передачи фактических параметров в подпрограмму: по значению и по ссылке. В первом случае значение переменной-фактического параметра при вызове подпрограммы присваивается локальной переменной, являющейся формальным параметром подпрограммы. Что бы потом ни происходило с локальной переменной, это никак не отразится на соответствующей глобальной. Для одних задач это благо, но иногда требуется произвести в подпрограмме действия над самими переменными, указанными в качестве фактических параметров. На помощь приходит второй способ. Происходит следующее: при обращении к подпрограмме не происходит формирования локальной переменной-формального параметра. Просто на время выполнения подпрограммы имя этой локальной переменной будет указывать на ту же область памяти, что и имя соответствующей глобальной переменной. Если в этом случае изменить локальную переменную, изменятся данные и в глобальной. Передача параметров по ссылке отличается тем, что при описании подпрограммы перед именем переменной-формального параметра ставится служебное слово Var. Теперь использование в качестве фактических параметров выражений или непосредственных значений уже не допускается - они должны быть именами переменных. Еще один классический пример. Задача: "Расположить в порядке неубывания три целых числа". Program Pr; Var S1,S2,S3 :Integer; Procedure Swap(Var A,B: Integer);{Процедура Swap с параметрами-переменными} Var C : Integer; {C - независимая локальная переменная} Begin C:=A; A:=B; B:=C {Меняем местами содержимое A и B} End; Begin Writeln('Введите три числа'); Readln(S1,S2,S3); If S1>S2 Then Swap(S1,S2); If S2>S3 Then Swap(S2,S3); If S1>S2 Then Swap(S1,S2); Writeln('Числа в порядке неубывания:V',S1,S2,S3) End. | Работа с файлами | Тип-файл представляет собой последовательность компонент одного типа, расположенных на внешнем устройстве (например, на диске). Элементы могут быть любого типа, за исключением самого типа-файла. Число элементов в файле при описании не объявляется. Работа с физическими файлами происходит через так называемые файловые переменные. Для задания типа-файла следует использовать зарезервированные слова File и Of, после чего указать тип компонент файла. Пример: Type N = File Of Integer; {Тип-файл целых чисел} C = File Of Char; {Тип-файл символов} Есть заранее определенный в Паскале тип файла с именем Text. Файлы этого типа называют текстовыми. Введя файловый тип, можно определить и переменные файлового типа: Var F1 : N; F2 : C; F3 : Text; Тип-файл можно описать и непосредственно при введении файловых переменных: Var Z : File Of Word; Файловые переменные имеют специфическое применение. Над ними нельзя выполнять никаких операций (присваивать значение, сравнивать и т.д.). Их можно использовать лишь для выполнения операций с файлами (чтение, запись и т.д.). Элементы файла считаются расположенными последовательно, то есть так же, как элементы линейного массива. Отличие же состоит в том, что, во-первых, размеры файла могут меняться, во-вторых, способ обращения к элементам совсем другой: невозможно обратиться к произвольному элементу файла; элементы его просматриваются только подряд от начала к концу, при этом в каждый момент времени доступен только один элемент. Можно представить себе, что для каждого файла существует указатель, показывающий в данный момент на определенный компонент файла. После проведения операции чтения или записи указатель автоматически передвигается на следующий компонент. Перед тем, как осуществлять ввод-вывод, файловая переменная должна быть связана с конкретным внешним файлом при помощи процедуры Assign. Формат: Assign(<Имя файловой переменной>,<Имя файла>); Имя файла задается либо строковой константой, либо через переменную типа Sting. Имя файла должно соответствовать правилам работающей в данный момент операционной системы. Если строка имени пустая, то связь файловой переменной осуществляется со стандартным устройством ввода-вывода (как правило - с консолью). После этого файл должен быть открыт одной из процедур: Reset(<Имя файловой переменной>); Открывается существующий файл для чтения, указатель текущей компоненты файла настраивается на начало файла. Если физического файла, соответствующего файловой переменной не существует, то возникает ситуация ошибки ввода-вывода. Rewrite(<Имя файловой переменной>); Открывается новый пустой файл для записи, ему присваивается имя, заданное процедурой Assign. Если файл с таким именем уже существует, то он уничтожается. После работы с файлом он, как правило, должен быть закрыт процедурой Close. Close(<Имя файловой переменной>); Это требование обязательно должно соблюдаться для файла, в который производилась запись. Теперь рассмотрим непосредственную организацию чтения и записи. Для ввода информации из файла, открытого для чтения, используется уже знакомый вам оператор Read. Правда, в его формате и использовании вы заметите некоторые изменения: Read(<Имя файловой переменной>, <Список ввода>); Происходит считывание данных из файла в переменные, имена которых указаны в списке ввода. Переменные должны быть того же типа, что и компоненты файла. Вывод информации производит, как можно догадаться оператор Write(<Имя файловой переменной>, <Список вывода>); Данные из списка вывода заносятся в файл, открытый для записи. Для текстовых файлов используются также операторы Readln и Writeln с соответствующими дополнениями, относящимися к файловому вводу-выводу. Любопытно, что вывод данных на монитор и ввод с клавиатуры в языке Паскаль тоже являются действиями с файлами. Они даже имеют свои предопределенные файловые переменные текстового типа: Output и Input соответственно. Переменная Output всегда открыта для записи, Input - для чтения. Если не указывать файловые переменные в операторах ввода-вывода (придем к формату, рассмотренному в теме "Операторы ввода-вывода"), то в случае записи по умолчанию выбирается файл Output, в случае чтения - Input. Как вы знаете, любой файл конечен и продолжать чтение из него информации можно лишь до определенного предела. Как этот предел установить? Проверить, окончен ли файл, можно вызовом стандартной логической функции Eof(<Имя файловой переменной>) Она вырабатывает значение True, если файл окончен, и False - в противном случае. Решим следующую задачу: "Написать программу, которая вводит с клавиатуры список фамилий учащихся, а затем распечатывает его, кроме тех учащихся, у которых фамилия начинается с буквы 'Ш'". Так как заранее количество данных не известно, то для их хранения используем файл. Тип элементов - строковый. Program L; Var I,N : Integer; F : File Of String; S : String; Begin Assign(F,'Spis.lst'); {Связываем переменную F с файлом Spis.lst} Writeln('Введите количество учащихся'); Readln(N); {Вводим количество учащихся} Rewrite(F); {Создаем файл для записи в него данных} For I:=1 To N Do {Для всех учащихся} Begin Writeln('Введите фамилию'); Readln(S); Write(F,S) End; Close(F); Reset(F); Writeln; Writeln('Список учащихся:'); While Not(Eof(F)) Do Begin Read(F,S); If S[1]<>'Ш' Then Writeln(S) End; Close(F) End. | {описание объектов GraphObj} Unit GraphObj Interface tape TgraphObj=object Private x,y:integer; color:word; Public Consttuctor Init(aX,aY:integer; aColor:word); Procedure Draw (aColor:word);virtual: Procedure Show; Procedure Hide; Procedure MoveTo (dX,dY:integer); end; TPoint= object(TGraphObj) Procedure Draw (aColor:word);virtual; end; TLine= object(TGraphObj) dx,dy:integer; Consttuctor Init(X1,Y1,X2,Y2:integer; aColor:word); Procedure Draw (aColor:word);virtual; end; TCircle= object(TGraphObj) Consttuctor Init(aX,aY,aR:integer; aColor:word); Procedure Draw (aColor:word);virtual; end; TRect= object(TLine) Procedure Draw (aColor:word);virtual; end; Implemention Uses Graph; Consttuctor TgraphObj. Init; Begin X:=aX; Y:=aY; color:=aColor; end; Procedure TgraphObj.Draw; Begin end; Procedure TgraphObj.Show; Begin Draw(color); end; Procedure TgraphObj.Hide; Begin Draw(GetBkcolor); end; Procedure TgraphObj.MoveTo; Begin Hide; X:=X+dX; Y:=Y+dY; Show; end; Procedure Tpoint.Draw; Begin PutPixel (X,Y,Color); end; Consttuctor Tline.Init; Begin Inherited Init (X1,Y1,aColor); dX:=X2-X1; dY:=Y2-Y1; end; Procedure TLine.Draw; Begin SetColor(Color); Line(X,Y,X+dX,Y+dY); end; Consttuctor TCircle.Init; Begin Inherited Init (aX,aY,aColor); R:=aR; end; Procedure TCircle.Draw; Begin SetColor(aColor); Circle(X,Y,R); end; Procedure TRect.Draw; Begin SetColor(aColor); Rectangle(X,Y,X+dX,Y+dY); end; 21 ПРОЦЕДУРЫ И ФУНКЦИИ в PASCAL
Как отмечалось в гл.2, процедуры и функции представляют собой относительно самостоятельные фрагменты программы, оформленные особым образом и снабженные именем. Упоминание этого имени в тексте программы называется вызовом процедуры (функции). Отличие функции от процедуры заключается в том, что результатом исполнения операторов, образующих тело функции, всегда является некоторое единственное значение или указатель, поэтому обращение к функции можно использовать в соответствующих выражениях наряду с переменными и константами. Условимся далее называть процедуру или функцию общим именем «подпрограмма», если только для излагаемого материала указанное отличие не имеет значения. Подпрограммы представляют собой инструмент, с помощью которого любая программа может быть разбита на ряд в известной степени независимых друг от друга частей. Такое разбиение необходимо по двум причинам. Во-первых, это средство экономии памяти: каждая подпрограмма существует в программе в единственном экземпляре, в то время как обращаться к ней можно многократно из разных точек программы. При вызове подпрограммы активизируется последовательность образующих ее операторов, а с помощью передаваемых подпрограмме параметров нужным образом модифицируется реализуемый в ней алгоритм. Вторая причина заключается в применении методики нисходящего проектирования программ (см. гл.2). В этом случае алгоритм представляется в виде последовательности относительно крупных подпрограмм, реализующих более или менее самостоятельные смысловые части алгоритма. Подпрограммы в свою очередь могут разбиваться на менее крупные подпрограммы нижнего уровня и т.д. (рис. 8.1). Последовательное структурирование программы продолжается до тех пор, пока реализуемые подпрограммами алгоритмы не станут настолько простыми, чтобы их можно было легко запрограммировать. В этой главе подробно рассматриваются все аспекты использования подпрограмм в Турбо Паскале.
8.1. ЛОКАЛИЗАЦИЯ ИМЕН Напомню, что вызов подпрограммы осуществляется простым упоминанием имени процедуры в операторе вызова процедуры или имени функции в выражении. При использовании расширенного синтаксиса Турбо Паскаля (см. ниже) функции можно вызывать точно так же, как и процедуры. Как известно, любое имя в программе должно быть обязательно описано перед тем как оно появится среди исполняемых операторов. Не делается исключения и в отношении подпрограмм: каждую свою процедуру и функцию программисту необходимо описать в разделе описаний. Описать подпрограмму - это значит указать ее заголовок и тело. В заголовке объявляются имя подпрограммы и формальные параметры, если они есть. Для функции, кроме того, указывается тип возвращаемого ею результата. За заголовком следует тело подпрограммы, которое, подобно программе, состоит из раздела описаний и раздела исполняемых операторов. В разделе описаний подпрограммы могут встретиться описания подпрограмм низшего уровня, в тех - описания других подпрограмм и т.д. Вот какую иерархию описаний получим, например, для программы, структура которой изображена на риc.8.1 (для простоты считается, что все подпрограммы представляют собой процедуры без параметров): Program ... ; Procedure A; Procedure А1; ….. begin ….. end {A1}; Procedure A2; ….. begin ….. end {A2} begin {A} ….. end {A}.
Procedure В; Procedure B1; ….. begin ….. end {Bl}; Procedure В 2; Procedure B21; …… и т.д.
Подпрограмма любого уровня имеет обычно множество имен констант, переменных, типов и вложенных в нее подпрограмм низшего уровня. Считается, что все имена, описанные внутри подпрограммы, локализуются в ней, т.е. они как бы «невидимы» снаружи подпрограммы. Таким образом, со стороны операторов, использующих обращение к подпрограмме, она трактуется как «черный ящик», в котором реализуется тот или иной алгоритм. Все детали этой реализации скрыты от глаз пользователя подпрограммы и потому недоступны ему. Например, в рассмотренном выше примере из основной программы можно обратиться к процедурам А и В, но нельзя вызвать ни одну из вложенных в них процедур A1, A2, B1 и т.д. Сказанное относится не только к именам подпрограмм, но и вообще к любым именам, объявленным в них - типам, константам, переменным и меткам. Все имена в пределах подпрограммы, в которой они объявлены, должны быть уникальными и не могут совпадать с именем самой подпрограммы. При входе в подпрограмму низшего уровня становятся доступными не только объявленные в ней имена, но и сохраняется доступ ко всем именам верхнего уровня. Образно говоря, любая подпрограмма как бы окружена полупрозрачными стенками: снаружи подпрограммы мы не видим ее внутренности, но, попав в подпрограмму, можем наблюдать все, что делается снаружи. Так, например, из подпрограммы В21 мы можем вызвать подпрограмму А, использовать имена, объявленные в основной программе, в подпрограммах В и B2, и даже обратиться к ним. Любая подпрограмма может, наконец, вызвать саму себя - такой способ вызова называется рекурсией. Пусть имеем такое описание: Program .. ; var V1 : ... ; Procedure A; var V2 : . . .; ….. end{A}; Procedure B; var V3 : . . . ; Procedure B1; var V4 : .. . ; Procedure В 11; var V5; ….
Из процедуры В11 доступны все пять переменных V1,...,V5, из процедуры В1 доступны переменные V1,…, V4, из центральной программы - только VI. При взаимодействии подпрограмм одного уровня иерархии вступает в силу основное правило Турбо Паскаля: любая подпрограмма перед ее использованием должна быть описана. Поэтому из подпрограммы В можно вызвать подпрограмму А, но из А вызвать В невозможно (точнее, такая возможность появляется только с использованием опережающего описания, см. п.8.6.) Продолжая образное сравнение, подпрограмму можно уподобить ящику с непрозрачными стенками и дном и полупрозрачной крышей: из подпрограммы можно смотреть только «вверх» и нельзя «вниз», т.е. подпрограмме доступны только те объекты верхнего уровня, которые описаны до описания данной подпрограммы. Эти объекты называются глобальными по отношению к подпрограмме. В отличие от стандартного Паскаля в Турбо Паскале допускается произвольная последовательность описания констант, переменных, типов, меток и подпрограмм. Например, разделVAR описания переменных может появляться в пределах раздела описаний одной и той же подпрограммы много раз и перемежаться с объявлениями других объектов и подпрограмм. Для Турбо Паскаля совершенно безразличен порядок следования и количество разделовVAR, CONST, TYPE, LABEL, но при определении области действия этих описаний следует помнить, что имена, описанные ниже по тексту программы, недоступны из ранее описанных подпрограмм, например: var V1 : . . . ; Procedure S; var V2 : . . . ; …… end {S}; var V3 : . . . ; …..
Из процедуры S можно обратиться к переменным VI и V2, но нельзя использовать VЗ, так как описание V3 следует в программе за описанием процедуры S. Имена, локализованные в подпрограмме, могут совпадать с ранее объявленными глобальными именами.
В этом случае считается, что локальное имя «закрывает» глобальное и делает его недоступным, например: var i : Integer; Procedure P; var i : Integer; begin writeln(i) end {P}; begin i := 1; P end. Что напечатает эта программа? Все, что угодно: значение внутренней переменной I при входе в процедуру Р не определено, хотя одноименная глобальная переменная имеет значение 1. Локальная переменная «закроет» глобальную и на экран будет выведено произвольное значение, содержащееся в неинициированной внутренней переменной. Если убрать описание var i : Integer; из процедуры Р, то на экран будет выведено значение глобальной переменной I, т.е. 1. Таким образом, одноименные глобальные и локальные переменные - это разные переменные. Любое обращение к таким переменным в теле подпрограммы трактуется как обращение к локальным переменным, т.е. глобальные переменные в этом случае попросту недоступны.
8.2. ОПИСАНИЕ ПОДПРОГРАММЫ Описание подпрограммы состоит из заголовка и тела подпрограммы.
8.2.1. Заголовок Заголовок процедуры имеет вид: PROCEDURE <имя> [ (<сп.ф.п.>) ]; Заголовок функции: FUNCTION <имя> [(<сп.ф.п.»] : <тип>; Здесь <имя> - имя подпрограммы (правильный идентификатор); <сп. ф. п. >- список формальных параметров; <тип> - тип возвращаемого функцией результата.
Сразу за заголовком подпрограммы может следовать одна из стандартных директивASSEMBLER, EXTERNAL, FAR, FORWARD, INLINE, INTERRUPT, NEAR. Эти директивы уточняют действия компилятора и распространяются на всю подпрограмму и только на нее, т.е. если за подпрограммой следует другая подпрограмма, стандартная директива, указанная за заголовком первой, не распространяется на вторую. ASSEMBLER - эта директива отменяет стандартную последовательность машинных инструкций, вырабатываемых при входе в процедуру и перед выходом из нее. Тело подпрограммы в этом случае должно реализоваться с помощью команд встроенного ассемблера (см. п. 11.8). EXTERNAL - с помощью этой директивы объявляется внешняя подпрограмма (см. п.11.1). FAR - компилятор должен создавать код подпрограммы, рассчитанный на дальнюю модель вызова. ДирективаNEAR заставит компилятор создать код, рассчитанный на ближнюю модель памяти. По умолчанию все подпрограммы, объявленные в интерфейсной части модулей, генерируются с расчетом на дальнюю модель вызова, а все остальные подпрограммы - на ближнюю модель. В соответствии с архитектурой микропроцессора ПК, в программах могут использоваться две модели памяти: ближняя и дальняя. Модель памяти определяет возможность вызова процедуры из различных частей программы: если используется ближняя модель, вызов возможен только в пределах 64 Кбайт (в пределах одного сегмента кода, который выделяется основной программе и каждому используемому в ней модулю); при дальней модели вызов возможен из любого сегмента. Ближняя модель экономит один байт и несколько микросекунд на каждом вызове подпрограммы, поэтому стандартный режим компиляции предполагает эту модель памяти. Однако при передаче процедурных параметров (см.п.8.4), а также в оверлейных модулях (см. п. 11.6) соответствующие подпрограммы должны компилироваться с расчетом на универсальную - дальнюю - модель памяти, одинаково пригодную при любом расположении процедуры и вызывающей ее программы в памяти. Явное объявление модели памяти стандартными директивами имеет более высокий приоритет по сравнению с опциями настройки среды Турбо Паскаля. FORWARD - используется при опережающем описании (см. п.8.6) для сообщения компилятору, что описание подпрограммы следует где-то дальше по тексту программы (но в пределах текущего программного модуля). INLINE - указывает на то, что тело подпрограммы реализуется с помощью встроенных машинных инструкций (см. п. 11.2). INTERRUPT - используется при создании процедур обработки прерываний (см. п. 11.4). 8.2.2. Параметры Список формальных параметров необязателен и может отсутствовать. Если же он есть, то в нем должны быть перечислены имена формальных параметров и их типы, например:
Procedure SB(a: Real; b: Integer; c: Char) ; Как видно из примера, параметры в списке отделяются друг от друга точками с запятой. Несколько следующих подряд однотипных параметров можно объединять в подсписки, например, вместо
Function F(a: Real; b: Real): Real;
можно написать проще:
Function F(a,b: Real): Real;
Операторы тела подпрограммы рассматривают список формальных параметров как своеобразное расширение раздела описаний: все переменные из этого списка могут использоваться в любых выражениях внутри подпрограммы. Таким способом осуществляется настройка алгоритма подпрограммы на конкретную задачу. Рассмотрим следующий пример. В языке Турбо Паскаль нет операции возведения в степень, однако с помощью встроенных функций LN(X) и ЕХР(Х) нетрудно реализовать новую функцию с именем, например, POWER, осуществляющую возведение любого вещественного числа в любую вещественную степень. В программе (пример 8.1) вводится пара чисел X и Y и выводится на экран дисплея результат возведения Х сначала в степень +Y, а затем - в степень -Y. Для выхода из программы нужно ввести Ctrl-Z и Enter. Пример 8.1 var х,у: Real; {------------------------} 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 readin(x,y) ; writein(Power(x,y):12:10, Power(x,-y):15:10) until EOF end {main}. Для вызова функции POWER мы просто указали ее в качестве параметра при обращении к встроенной процедуре WRITELN. Параметры x и y в момент обращения к функции - это фактические параметры. Они подставляются вместо формальных параметров А и В в заголовке функции и затем над ними осуществляются нужные действия. Полученный результат присваивается идентификатору функции - именно он и будет возвращен как значение функции при выходе из нее. В программе функция POWER вызывается дважды - сначала с параметрами Х и Y, а затем X и -Y, поэтому будут получены два разных результата. Механизм замены формальных параметров на фактические позволяет нужным образом настроить алгоритм, реализованный в подпрограмме. Турбо Паскаль следит за тем, чтобы количество и тип формальных параметров строго соответствовали количеству и типам фактических параметров в момент обращения к подпрограмме. Смысл используемых фактических параметров зависит от того, в каком порядке они перечислены при вызове подпрограммы. В примере 8.1 первый по порядку фактический параметр будет возводиться в степень, задаваемую вторым параметром, а не наоборот. Пользователь должен сам следить за правильным порядком перечисления фактических параметров при обращении к подпрограмме. Любой из формальных параметров подпрограммы может быть либо параметром-значением, либо параметром-переменной, либо, наконец, параметром-константой. В предыдущем примере параметры А и В определены как параметры-значения. Если параметры определяются как параметры-переменные, перед ними необходимо ставить зарезервированное словоVAR, а если это параметры-константы,- словоCONST, например: Procedure MyProcedure(var a: Real; b: Real; const c: String); Здесь А - параметр-переменная, В -параметр-значение, а С - параметр-константа. Определение формального параметра тем или иным способом существенно, в основном, только для вызывающей программы: если формальный параметр объявлен как параметр-переменная, то при вызове подпрограммы ему должен соответствовать фактический параметр в виде переменной нужного типа; если формальный параметр объявлен как параметр-значение или параметр-константа, то при вызове ему может соответствовать произвольное выражение. Контроль за неукоснительным соблюдением этого правила осуществляется компилятором Турбо Паскаля. Если бы для предыдущего примера был использован такой заголовок функции: Function Power (var a, b : Real) : Real; то при втором обращении к функции компилятор указал бы на несоответствие типа фактических и формальных параметров (параметр -Y есть выражение, в то время как соответствующий ему формальный параметр описан как параметр-переменная). Для того чтобы понять, в каких случаях использовать тот или иной тип параметров, рассмотрим, как осуществляется замена формальных параметров на фактические в момент обращения к подпрограмме. Если параметр определен как параметр-значение, то перед вызовом подпрограммы это значение вычисляется, полученный результат копируется во временную память и передается подпрограмме. Важно учесть, что даже если в качестве фактического параметра указано простейшее выражение в виде переменной или константы, все равно подпрограмме будет передана лишь копия переменной (константы). Любые возможные, изменения в подпрограмме параметра-значения никак не воспринимаются вызывающей программой, так как в этом случае изменяется копия фактического параметра. Если параметр определен как параметр-переменная, то при вызове подпрограммы передается сама переменная, а не ее копия (фактически в этом случае подпрограмме передается адрес переменной). Изменение параметра-переменной приводит к изменению самого фактического параметра в вызывающей программе. В случае параметра-константы в подпрограмму также передается адрес области памяти, в которой располагается переменная или вычисленное значение. Однако компилятор блокирует любые присваивания параметру-константе нового значения в теле подпрограммы. Представленный ниже пример 8.2 поясняет изложенное. В программе задаются два целых числа 5 и 7, эти числа передаются процедуре INC2, в которой они удваиваются. Один из параметров передается как параметр-переменная, другой - как параметр-значение. Значения параметров до и после вызова процедуры, а также результат их удвоения выводятся на экран. Пример 8.2 const а : Integer = 5; b : Integer =7; {--------------------} Procedure Inc2 (var c: Integer; b: Integer); begin {Inc2} с := с + c; b := b + b; WriteLn ('удвоенные :',c:5,b:5) end {inc2}; {---------------------} begin {main} WriteLn ('исходные :', a:5, b:5); WriteLn('результат :', a:5, b:5) end {main}.
В результате прогона программы будет выведено: исходные : 5 7 удвоенные : 10 14 результат : 10 7
Как видно из примера, удвоение второго формального параметра в процедуре INC2 не вызвало изменения фактической переменной В, так как этот параметр описан в заголовке процедуры как параметр-значение. Этот пример может служить еще и иллюстрацией механизма «накрывания» глобальной переменной одноименной локальной: хотя переменная В объявлена как глобальная (она описана в вызывающей программе перед описанием процедуры), в теле процедуры ее «закрыла» локальная переменная В, объявленная как параметр-значение. Итак, параметры-переменные используются как средство связи алгоритма, реализованного в подпрограмме, с внешним миром: с помощью этих параметров подпрограмма может передавать результаты своей работы вызывающей программе. Разумеется, в распоряжении программиста всегда есть и другой способ передачи результатов - через глобальные переменные. Однако злоупотребление глобальными связями делает программу , как правило, запутанной, трудной в понимании и сложной в отладке. В соответствии с требованиями хорошего стиля программирования рекомендуется там, где это возможно, использовать передачу результатов через фактические параметры-переменные. С другой стороны, описание всех формальных параметров как параметров-переменных нежелательно по двум причинам. Во-первых, это исключает возможность вызова подпрограммы с фактическими параметрами в виде выражений, что делает программу менее компактной. Во-вторых, и главных, в подпрограмме возможно случайное использование формального параметра, например, для временного хранения промежуточного результата, т.е. всегда существует опасность непреднамеренно испортить фактическую переменную. Вот почему параметрами-переменными следует объявлять только те, через которые подпрограмма в действительности передает результаты вызывающей программе. Чем меньше параметров объявлено параметрами-переменными и чем меньше в подпрограмме используется глобальных переменных, тем меньше опасность получения непредусмотренных программистом побочных эффектов, связанных с вызовом подпрограммы, тем проще программа в понимании и отладке. По той же причине не рекомендуется использовать параметры-переменные в заголовке функции: если результатом работы функции не может быть единственное значение, то логичнее использовать процедуру или нужным образом декомпозировать алгоритм на несколько подпрограмм. Существует еще одно обстоятельство, которое следует учитывать при выборе вида формальных параметров. Как уже говорилось, при объявлении параметра-значения осуществляется копирование фактического параметра во временную память. Если этим параметром будет массив большой размерности, то существенные затраты времени и памяти на копирование при многократных обращениях к подпрограмме можно минимизировать, объявив этот параметр параметром-константой. Параметр-константа не копируется во временную область памяти, что сокращает затраты времени на вызов подпрограммы, однако любые его изменения в теле подпрограммы невозможны - заэтим строго следит компилятор.
8.3. ПАРАМЕТРЫ-МАССИВЫ И ПАРАМЕТРЫ-СТРОКИ Может сложиться впечатление, что объявление переменных в списке формальных параметров подпрограммы ничем не отличается от объявления их в разделе описания переменных. Действительно, в обоих случаях много общего, но есть одно существеннoe различие: типом любого параметра в списке формальных параметров может быть только стандартный или ранее объявленный тип. Поэтому нельзя, например, объявить следующую процедуру: Procedure S (a: array [1..10] of Real); так как в списке формальных параметров фактически объявляется тип-диапазон, указывающий границы индексов массива. Если мы хотим передать какой-то элемент массива, то проблем, как правило, не возникает, но если в подпрограмму передается весь массив, то следует первоначально описать его тип. Например: аtype = array [1..10] of Real; Procedure S (a: atype); ……. Поскольку строка является фактически своеобразным массивом, ее передача в подпрограмму осуществляется аналогичным образом: type intype = String [15]; outype = String [30]; Function St (s : intype) : outype; …….. Требование описать любой тип-массив или тип-строку перед объявлением подпрограммы на первый взгляд кажется несущественным. Действительно, в рамках простейших вычислительных задач обычно заранее известна структура всех используемых в программе данных, поэтому статическое описание массивов не вызывает проблем. Однако разработка программных средств универсального назначения связана со значительными трудностями. По существу, речь идет о том, что в Турбо Паскале невозможно использовать в подпрограммах массивы с «плавающими» границами изменения индексов. Например, если разработана программа, обрабатывающая матрицу 10х10 элементов, то для обработки матрицы 9х11 элементов необходимо переопределить тип, т.е. перекомпилировать всю программу (речь идет не о динамическом размещении массивов в куче, а о статическом описании массивов и передаче их как параметров в подпрограммы). Этот недостаток, как и отсутствие в языке средств обработки исключительных ситуаций (прерываний), унаследован из стандартного Паскаля и представляет собой объект постоянной и вполне заслуженной его критики. Разработчики Турбо Паскаля не рискнули кардинально изменить свойства базового языка, но, тем не менее, включили в него некоторые средства, позволяющие в известной степени смягчить отмеченные недостатки. Прежде всего, в среде Турбо Паскаля можно установить режим компиляции, при котором отключается контроль за совпадением длины фактического и формального параметра-строки (см. прил.1). Это позволяет легко решить вопрос о передаче подпрограмме строки произвольной длины. При передаче строки меньшего размера формальный параметр будет иметь ту же длину, что и параметр обращения; передача строки большего размера приведет к ее усечению до максимального размера формального параметра. Следует сказать, что контроль включается только при передаче строки, объявленной как формальный параметр-переменная. Если соответствующий параметр объявлен параметром-значением, эта опция игнорируется и длина не контролируется. Значительно сложнее обстоит дело с передачей массивов произвольной длины. Наиболее универсальным приемом в этом случае будет, судя по всему, работа с указателями и использование индексной арифметики. Несколько проще можно решить эту проблему при помощи нетипизированных параметров (см. п.8.5). В версии Турбо Паскаля 7.0 язык поддерживает так называемые открытые массивы, легко решающие проблему передачи подпрограмме одномерных массивов переменной длины. Открытый массив представляет собой формальный параметр подпрограммы, описывающий базовый тип элементов массива, но не определяющий его размерности и границы:
Procedure MyProc(OpenArray: array of Integer);
Внутри подпрограммы такой параметр трактуется как одномерный массив с нулевой нижней границей. Верхняя граница открытого массива возвращается функцией HIGH, упоминавшейся в п.4.1.1. Используя 0 как минимальный индекс и значение, возвращаемое функцией HIGH, как максимальный индекс, подпрограмма может обрабатывать одномерные массивы произвольной длины: {Иллюстрация использования открытых массивов: программа выводит на экран содержимое двух одномерных массивов разной длины с помощью одной процедуры Array Print} Procedure ArrayPrint(aArray: array of Integer); var k: Integer; begin for k := 0 to High(aArray) do Write(aArray[k]:8) ; WriteLn end;
(Эти недостатки практически полностью устранены в языке Object Pascal, используемом в визуальной среде программирования Delphi.) const A: array [-1..2] of Integer = (0,1,2,3); В: array [5..7] of Integer = (4,5,6); begin ArrayPrint(A) ; ArrayPrint(B) end.
Как видно из этого примера, фактические границы массивов А и В, передаваемых в качестве параметров вызова процедуре ArrayPrint, не имеют значения. Однако размерность открытых массивов (количество индексов) всегда равна 1 - за этим следит компилятор. Если бы, например, мы добавили в программу двумерный массив С
var С: array [1..3,1..5] of Integer;
то обращение
ArrayPrint(С)
вызвало бы сообщение об ошибке
Error26: Type mismatch. (Ошибка 26: Несоответствие типов.)
8.4. ПРОЦЕДУРНЫЕ ТИПЫ. ПАРАМЕТРЫ-ФУНКЦИИ И ПАРАМЕТРЫ-ПРОЦЕДУРЫ Процедурные типы - это нововведение фирмы Borland (в стандартном Паскале таких типов нет). Основное назначение этих типов - дать программисту гибкие средства передачи функций и процедур в качестве фактических параметров обращения к другим процедурам и функциям. Для объявления процедурного типа используется заголовок процедуры (функции), в котором опускается ее имя, например:
type Proc1 = Procedure (a, b, c: Real; var d: Real) ; Proc2 = Procedure (var a, b); РгосЗ = Procedure; Func1 = Function: String; Func2 = Function (var s: String): Real;
Как видно из приведенных примеров, существует два процедурных типа: тип-процедура и тип-функция. Пример 8.3 иллюстрирует механизм передачи процедур в качестве фактических параметров вызова. Программа выводит на экран таблицу двух функций:
sinl(x) = (sin(x) +1) * ехр(-х)
и
cosl (х) = (cos(x) +1) * ехр(-х).
Вычисление и печать значений этих функций реализуются в процедуре PRINTFUNC, которой в качестве параметров передаются номер позиции N на экране, куда будет выводиться очередной результат (с помощью этого параметра реализуется вывод в две колонки), и имя нужной функции. Пример 8.3. Uses CRT; type Func = Function (x: Real): Real; {------------------------} Procedure PrintFunc(XPos: Byte; F:Func); {Осуществляет печать функции F (XPos - горизонтальная позиция начала вывода)} const np = 20; {Количество вычислений функций} var x:real; I:integer;
begin {PrintFunc} for i : = 1 to np do begin x := i * (2 * pi / np) ; GotoXY (XPos, WhereY); WriteLn (x:5:3, F(x):18:5) End end; {PrintFunc} {------------------} Function Sinl(x: Real): Real; far; Begin sinl := (sin(x) + 1) * exp(-x) end; {---------------------}
Function Cosl(x: Real): Real; far; begin cosl := (cos(x) + 1) * exp(-x) end; {----------------------} begin {основная программа) CIrScr; {Очищаем экран} PrintFunc (1, sin1); GotoXY (1,1); {Переводим курсор в левый верхний угол} PrintFunc (40, cos1) end.
Обратите внимание: для установления правильных связей функций SIN1 и COS1 с Процедурой PRINTFUNC они должны компилироваться с расчетом на дальнюю модель памяти. Вот почему в программу вставлены стандартные директивыFAR сразу за заголовками функций. В таком режиме должны компилироваться любые процедуры (функции), которые будут передаваться в качестве фактических параметров вызова. Стандартные процедуры (функции) Турбо Паскаля не могут передаваться рассмотренным способом. В программе могут быть объявлены переменные процедурных типов, например, так: var p1 : Proс1; fl, f2 : Func2; ар : array [1..N] of Proс1;
Переменным процедурных типов допускается присваивать в качестве значений имена соответствующих подпрограмм. После такого присваивания имя переменной становится синонимом имени подпрограммы, например: type Proc = Procedure (n: word; var a: Byte) ; var ProcVar: Proc; x, у : Byte; Procedure Proc1(x: word; var y: Byte); far; begin if x > 255 then y:=x mod 255 else у := Byte(x) end; begin {Главная.программа} ProcVar := Proc1; for x := 150 to 180 do begin ProcVar (x + 100, у); Write (у:8) end end.
Разумеется, такого рода присваивания допустимы и для параметров-функций, пример: type FuncType = Function (i : Integer) : Integer; vаг VarFunc : FuncType; i : Integer; Function MyFunc (count : Integer) : Integer; far; Begin .... end; {MyFunc} begin {Основная программа} ….. i :=MyFunc(l); (Обычное использование результата функции} ….. VarFunc := MyFunc; {Присваивание переменной процедурного типа имени функции MyFunc} …… end.
Отметим, что присваивание VarFunc := MyFunc(l);
будет недопустимым, так как слева и справа от знака присваивания используются несовместимые типы: слева - процедурный тип, а справа - INTEGER; имя функции со списком фактических параметров MyFunc(1) трактуется Турбо Паскалем как обращение к значению функции, в то время как имя функции без списка параметров рассматривается как имя функции. В отличие от стандартного Паскаля, в Турбо Паскале разрешается использовать в передаваемой процедуре (функции) любые типы параметров: параметры-значения, параметры-переменные, параметры-константы (в стандартном Паскале только параметры-значения).
8.5. НЕТИПИЗИРОВАННЫЕ ПАРАМЕТРЫ-ПЕРЕМЕННЫЕ ,, Еще одно очень полезное нововведение фирмы Borland - возможность использования нетипизированных параметров. Параметр считается нетипизированным, если тип формального параметра-переменной в заголовке подпрограммы не указан, при этом соответствующий ему фактический параметр может быть переменной любого типа. Заметим, что нетипизированными могут быть только параметры-переменные. Нетипизированные параметры обычно используются в случае, когда тип данных несущественен. Такие ситуации чаще всего возникают при разного рода копированиях одной области памяти в другую, например, с помощью процедур BLOCKREAD, BLOCKWRITE, MOVE и т.п. Нетипизированные параметры в сочетании с механизмом совмещения данных в памяти (см. п.4.4) можно использовать для передачи подпрограмме одномерных массивов переменной длины (этот способ можно использовать в Турбо Паскале версии 6.0 и более ранней, в которых нет открытых массивов). В примере 8.4 функция NORMA вычисляет норму вектора, длина которого меняется случайным образом. Стандартная константа MAXINT содержит максимальное значение целого типа INTEGER и равна 32767. Следует учесть, что при обращении к функции NORMA массив Х помещается в стек и передается по ссылке, поэтому описание локальной переменной А в виде одномерного массива максимально возможной длины в 65532 байта (встроенная константа MAXINT определяет максимально возможное значение типа INTEGER и равна 32767), совпадающего с X, на самом деле не приведет к выделению дополнительного объема памяти под размещение этой переменной. Иными словами, переменная А - фиктивная переменная, размер которой никак не влияет на объем используемой памяти, С таким же успехом можно было бы объявить ее в виде массива из одного элемента, правда, в этом случае необходимо позаботиться об отключении контроля выхода индекса за границы диапазона. Пример 8.4 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(Real)] of Real absolute x; i : Integer; s : Real; 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 begin N := Random(NN) + 1; {Текущая длина вектора} for j := 1 to N do a[j] := Random; WriteLn ('N = ', N:2, ' норма =', Norma (a, N) : 10:7) end end {main}.
Как видно из рассмотренного примера, передача одномерных массивов переменной длины не вызывает никаких трудностей. Сложнее обстоит дело с многомерными массивами, однако и в этом случае использование описанного приема (нетипизированный параметр и совмещение его в памяти с фиктивной переменной) все-таки проще, чем описанная в гл. 6 индексная арифметика. Еще раз напомню, что в случае многомерных массивов их элементы располагаются в памяти так, что при переходе от младших адресов к старшим наиболее быстро меняется самый правый индекс массива.
8.6. РЕКУРСИЯ И ОПЕРЕЖАЮЩЕЕ ОПИСАНИЕ Рекурсия - это такой способ организации вычислительного процесса при котором подпрограмма в ходе выполнения составляющих ее операторов обращается сама к себе. Рассмотрим классический пример - вычисление факториала (пример 18)). Программа вводит с клавиатуры целое число N и выводит на экран значение –N!, которое вычисляется с помощью рекурсивной функции FAC. Для выхода из программы необходимо либо ввести достаточно большое целое число, чтобы вызвать nepеполнение при умножении чисел с плавающей запятой, либо нажать Ctrl-Z и Enter. При выполнении правильно организованной рекурсивной подпрограммы осуществляется многократный переход от некоторого текущего уровня организации алгоритма к нижнему уровню последовательно до тех пор, пока, наконец, не будет получено тривиальное решение поставленной задачи. В примере 8.5 решение при N =0 тривиально и используется для остановки рекурсии. Пример 8.5 Program Factorial; {$S+} {Включаем контроль переполнения стека} var n: Integer; Function Fac(n: Integer): Real; {Рекурсивная функция, вычисляющая n!} begin {Fac} if n < 0 then WriteLn ('Ошибка в задании 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}.
Рекурсивная форма организации алгоритма обычно выглядит изящнее итерационной и дает более компактный текст программы, но при выполнении, как правило, медленнее и может вызвать переполнение стека (при каждом входе в подпрограмму ее локальные переменные размещаются в особым образом организованной области памяти, называемой программным стеком). Переполнение стека особенно ощутимо сказывается при работе с сопроцессором: если программа использует арифметический сопроцессор, результат любой вещественной функции возвращается через аппаратный стек сопроцессора, рассчитанный всего на 8 уровней. Если, например, попытаться заменить тип REAL функции FAC (см. пример 8.5) на EXTENDED, программа перестанет работать уже при N = 8. Чтобы избежать переполнения стека сопроцессора, следует размещать промежуточные результаты во вспомогательной переменной. Вот правильный вариант примера 8.5 для работы с типом EXTENDED:
Program Factorial; {$S+,N+,E+} {Включаем контроль стека и работу сопроцессора} var n: Integer; Function Fac(n: Integer): extended; var F: extended; {Буферная переменная для разгрузки стека сопроцессора} {Рекурсивная функция, вычисляющая п!} begin {Fac} if n < 0 then WriteLn ('Ошибка в задании N') else if n = 0 then Fac := 1 Else Begin F := Fac(n-l); Fac := F * n End end {Fac}; {------------------} begin {main} repeat ReadLn(n) ; WriteLn('n! = ',Fac(n)) until EOF end {main}.
Рекурсивный вызов может быть косвенным. В этом случае подпрограмма обращается к себе опосредованно, путем вызова другой подпрограммы, в которой содержится обращение к первой, например: Procedure A (i : Byte) ; Begin …… B(i); Procedure В (j : Byte) ; ….. begin …… A(j); …… end;
Если строго следовать правилу, согласно которому каждый идентификатор перед употреблением должен быть описан, то такую программную конструкцию использовать нельзя. Для того, чтобы такого рода вызовы стали возможны, вводится опережающее описание:
Procedure В (j : Byte); forward; Procedure A (i : Byte) ; Begin ….. В (i); ….. end; Procedure В; Begin ….. A(j); ….. end;
Как видим, опережающее описание заключается в том, что объявляется лишь заголовок процедуры В, а ее тело заменяется стандартной директивойFORWARD. Теперь в процедуре А можно использовать обращение к процедуре В - ведь она уже описана, точнее, известны ее формальные параметры, и компилятор может правильным образом организовать ее вызов. Обратите внимание: тело процедуры В начинается заголовком, в котором уже не указываются описанные ранее формальные параметры.
8.7. РАСШИРЕННЫЙ СИНТАКСИС ВЫЗОВА ФУНКЦИЙ
В Турбо Паскале есть возможность вызывать функцию и не использовать то значение, которое она возвращает. Иными словами, вызов функции может внешне выглядеть как вызов процедуры, например: {$Х+} {Включаем расширенный синтаксис} Function MyFunc(var x : Integer) : Integer; begin if x<0 then x:=0 else MyFunc := x+10 end; {MyFunc} var i : Integer; begin {main} i := 1; i := 2*MyFunc(i)-100; {Стандартный вызов функции} MyFunc(i) {Расширенный синтаксис вызова} end. {main}
Расширенный синтаксис делает использование функций таким же свободным, как, например, их использование в языке Си, и придает Турбо Паскалю дополнительную гибкость. С помощью расширенного синтаксиса нельзя вызывать стандартные функции. Компиляция с учетом расширенного синтаксиса включается активным состоянием опции EXTENDED SYNTAX диалогового окна OPTIONS/COMPILER (см. прил.1) или глобальной директивой компилятора {$Х+}. |