Реферат: Основы программирования в паскале
Название: Основы программирования в паскале Раздел: Рефераты по информатике Тип: реферат | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Модуль 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; обучение программированию и научно-технические публикации в значительной степени базируются на этом языке. Характеристика и особенности языкаСуществует ряд объективных причин, обусловивших выдающийся успех языка 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 Прошло много времени с момента появления Паскаля на рынке программных продуктов, прежде чем он получил всеобщее признание. Признание программистов и простых пользователей пришло вследствие появления языка программирования Турбо Паскаль (ТП) -диалекта языка, созданного американской фирмой Борланд. Эта фирма объединила очень быстрый компилятор с редактором текста и добавила к стандартному Паскалю мощное расширение, что способствовало успеху первой версии этого языка. Появилась возможность выделять определенным цветом различные элементы исходного текста (зарезервированные слова, идентификаторы, числа и т.д.), позволяющая даже неопытным пользователям устранять ошибки на этапе ввода исходного текста. Язык программирования ТП 7.0 был расширен (появилась возможность использовать типизированный адресный оператор, открытые массивы и строки и т.д.), что предоставило пользователю дополнительные возможности при решении повседневных задач. Был улучшен компилятор, вследствие чего "коды программ" стали более эффективными. Был улучшен интерфейс пользователя. Кроме того, в ТП 7.0 расширены возможности объектно-ориентированного программирования (в частности, расширены и улучшены возможности Turbo Vision). Основы языка Pascal. Лингвистическая концепция языка ПаскальЦелью работы И. Вирта было создание языка, который строился бы на небольшом количестве базовых понятий, имел бы простой синтаксис, допускал бы перевод программ в машинный код простым компилятором. По природе своей компьютер может выполнять только простейшие операции, которые можно вводить одну за другой в его память прямо в машинных кодах. Изнурительная монотонность такой работы привела когда-то первых программистов к естественному решению - созданию Ассемблеров, т.е. средств, упрощающих подготовку машинных кодов программ пользователя за счет написания их в некоторых мнемонических обозначениях с последующим автоматическим переводом. Дальнейшее развитие этих идей привело к созданию языков программирования высокого уровня, в которых длинные и сложные, но часто применяемые последовательности машинных операций были заменены каждая одним-единственным обозначающими ее словом - оператором. В области малых ЭВМ среди языков программирования высокого уровня следует в первую очередь назвать БЕЙСИК. Программы, написанные на этом языке, к сожалению, часто содержат запутанные последовательности операторов, называемые иногда на жаргоне программистов блюдо спагетти. Лингвистическая концепция Паскаля отрицает методы программирования, которые могут привести к подобным эффектам. В mei, напротив, пропагандируется системный подход, выражающийся, в частности, в расчленении крупных проблем на меньшие по сложности и размеру задачи, легче поддающиеся решению. Основные принципы Паскаля таковы: Структурное программирование. Суть его заключается в оформлении последовательностей команд как замкнутых функций или процедур и в объединении данных, связанных по смыслу, в сложные структуры данных. Благодаря этому повышается наглядность текста и упрощается его отладка. Проектирование сверху вниз. Программист разбивает свою задачу на несколько более простых, после чего каждая из задач решается по отдельности. Затем компонуются результаты проектирования простых задач и решается задача проектирования сверху вниз в целом. Объектно-ориентированное программирование делает следующий шаг от ремесла к науке программирования. Данные объединяются со свойственными им операциями обработки в некоторые объекты (Инкапсулирование). Например, данным "Человек" присуща операция "Идти". При этом свойства одних объектов могут передаваться другим, переноситься на другие классы объектов (Наследование). С другой стороны, в объектно-ориентированном программировании существует явление полиморфизма: часы тоже могут "Идти", но не ногами. Влияние Паскаля ощущается в настоящее время в разных языках программирования. Так, среди новых диалектов Бейсика есть Паскаль с символикой Бейсика. Даже в язык С встраивается все больше элементов, порожденных Паскаль-концепцией. Необходимо отметить, что все эти явления находятся в русле характерной для современных языков программирования тенденции к конвергенции. Набор операторов стандартного Паскаля относительно мал и легко изучаем. Но это порождает проблему расширения языка в приложениях. В ТП эта проблема решается за счет поставок большого количества библиотек разнообразных процедур, готовых к употреблению в прикладных программах. Широкое распространение Паскаля привело к появлению на рынке программного обеспечения большого числа инструментальных и прикладных программ. Подобные программы разработаны для многих проблемных областей, однако задача их настройки в соответствии с требованиями пользователей продолжает оставаться достаточно важной. Алфавит языка и специфика использования символов Язык программирования ТП 7.0, как и любой другой, имеет свой алфавит. Как правило, алфавитом языка программирования называют набор символов, разрешенный к использованию и воспринимаемый компилятором, с помощью которого могут быть образованы величины, выражения и операторы данного языка. Алфавит языка ТП 7.0 включает в себя все символы, представленные в кодировочной таблице, которая в настоящий момент загружена в оперативную память или хранится в ПЗУ Вашего компьютера. Каждому символу алфавита соответствует индивидуальный числовой код от 0 до 255. Символы, используемые для составления идентификаторов: латинские строчные и прописные буквы, арабские цифры от 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:= В+С - в ; Write (А); Специальные символы - символы, выполняющие определенные функции при построении различных конструкций языка: Составные символы - группа символов, которые воспринимаются компилятором как единое целое: "Неиспользуемые" символы. Зарезервированные слова. Программа на Паскале состоит из последовательности лексических единиц - лексем. идентификаторы; числа без знака; специальные знаки (слова-символы и специальные знаки); символьные константы (строки), директивы; метки. Блок лексического анализа Паскаль-компилятора, рассматривая символы входного языка, должен определить, какому классу принадлежит лексема. Между лексемами разрешено вставлять один и более разделителей. В качестве разделителей в стандарте Паскаля используются пробелы, комментарии, символы «конец строки». В Турбо-Паскале кроме этих в качестве разделителей разрешено использование и других символов. Комментарии заключают в фигурные скобки. Вместо них также могут использоваться пары символов «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 играет важную роль в подключении к тексту программы системных модулей из библиотек. В этом операторе Вы указываете компилятору, из какой библиотеки использует модули данная программа, чтобы компилятор выбрал соответствующие модули из этой библиотеки и включил их в текст программы. Понятия "библиотека", "модуль", "блок" составляют основу терминологии программирования на Паскале. Библиотека включает набор модулей, каждый из которых замкнут, имеет свое имя, компилируется отдельно и к нашей программе подключается уже как "черный ящик" с известным интерфейсом. Каждый модуль (блок (UNIT), как его называют на Паскале) представляет собой программу, включающую декларации типов и переменных, процедуры и функции. Следом за строкой, содержащей оператор USES, идут строки объявляющие: метки (LABEL) (хотя их использование противоречит классической технике программирования на Паскале, дающей превосходную стройность и однозначность понимания кода программы); константы (CONST); определенные пользователем типы данных (TYPE); переменные (VAR). В Турбо Паскале жесткое соблюдение именно такого порядка объявлений не требуется. В этом отношении данный диалект весьма "либерален". На практике в большинстве программ часть, заключающая в себе объявления глобальных объектов, непосредственно предшествует основному блоку программы. В разделе CONST содержатся определения констант, используемых в программе. Например: CONST Year=1995; Month='Июль'; Day='Понедельник'; Примечание: Заметьте, что при присвоении значений константам вместо оператора присвоения “:=” используется просто знак равенства “=”. Тип константы определяется автоматически по виду значения, присваемового константе и не может быть сложным. VAR А, В, С: INTEGER; {Переменным А, В и С присваивается тип INTEGER} DDT: REAL; {Переменной DDT присваивается тип REAL } Примечание: Разделы LABEL, CONST, TYPE и VAR могут располагаться в произвольном месте программы. При этом каждый из этих разделов может встречаться в программе несколько раз или вообще не встречаться в ней. Основной блок программы Непосредственно за заголовком программы следует основной блок программы, ограниченный операторами BEGIN и END.. Как уже говорилось, оператор END. указывает компилятору, что программа закончена, в отличие от операторов END;, которые завершают блоки, процедуры, модули и т.п. Объем основного блока программы, написанной нами, невелик: writeLn (' привет. Ваня !' ); writeLn; Wlitelin(' Я надеюсь, что мы отлично'); WrlteLn (' сработаемся !'); Оператор WriteLn (Write Line - записать строку) - представляет собой стандартную процедуру, с помощью которой можно вывести на экран или на другие носители и средства отображения информации текст и числа. Подробное истолкование применения этой процедуры в первом операторе программы может быть, например, таким: "Вывести (Write) строку символов (String) "Привет, Ваня!" на экран и перевести курсор на начало следующей строки (Line) экрана". Перевод курсора на начало следующей строки вызван окончанием Ln в имени процедуры. Существует также стандартная процедура Write для вывода информации на экран без перевода курсора. После выполнения оператора WriteLn последующий вывод приведет к выдаче информации в следующую строку экрана, а после оператора Write - в ту же строку, следом за уже выведенным текстом, пока хватит места, а затем вывод продолжится уже на следующей строке экрана. Оператор WriteLn; без параметров просто переведет курсор на начало следующей строки. Операторы WriteLn и Write присутствуют практически в каждой Паскаль-программе. Скобки, следующие за оператором, необходимы для задания параметров процедуры. Причем, компилятор не рассматривает слова "Привет" и "Ваня" как идентификаторы для каких-либо переменных, т.к. они стоят внутри фрагмента текста, ограниченного апострофами. Заканчивается этот оператор, как и любой другой, точкой с запятой. Ступенчатое оформление программы преследует только "эстетические" цели и не влияет на эффективность работы компилятора или программы. Компилятор обрабатывает Паскаль- программы с любым расположением операторов: как разделенные построчно нажатием клавиши [Enter] при подготовке текста программы, так и записанные подряд в одну строку. Например: program hello; begin writeln('Привет, Ваня!'); writeln; writeln('Я надеюсь, что мы отлично'); writeln (' сработаемся!'); end. Однако "воспитанные" программисты не могут себе позволить оформлять программы подобным образом, ведь таже программа, записанная с табуляцией, читается гараздо легче: program hello; {заголовок программы} begin {начало сегмента команд программы} writeln('Привет, Ваня!'); {вывод строки} writeln; {перевод позиции курсора на строку ниже} writeln('Я надеюсь, что мы отлично'); {вывод строки} writeln('сработаемся!'); {вывод строки} end. {конец программы} Вернемся еще раз к символу точка с запятой, завершающему каждый оператор. Наличие точки с запятой обязательно, т.к. этот символ показывает Паскаль-компилятору, где заканчивается один оператор и начинается следующий. Благодаря тому, что Паскаль - язык со "свободной формой записи", можно без опасений "растянуть" оператор на несколько строк. С помощью символа точка с запятой Вы сообщаете Паскаль-компилятору, какую часть текста программы следует рассматривать как цельный, неделимый фрагмент. Зарезервированное слово BEGIN, с которого начинаются блоки программы, не требует после себя символа точка с запятой. Типы переменных. Тип переменной задает вид того значения, которое ей присваивается и правила, по которым операторы языка действуют с переменной, например: 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: При присвоении этой переменной строки длиннее X, присваиваемая строка будет обрезана с конца после X-того символа. Функция SizeOf() возвращает размер, занимаемый переменной, служащей параметром. Параметром может служить и тип переменной; строка: Выведет на экран число 256, так как по умолчанию под все строки отводится по 256 байт. Кроме того, можно узнать, сколько символов в строке (индекс последнего непустого символа в строке): Используется ибращение к нулевому элементу (символу) строки, в котором хранится ее длина, но 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 байт. Обращение к элементам массива происходит следующим образом: В паскале существуют ограничения на присвоение значений одних переменных другим. Если переменные которую и которой присваивают одного типа, то никаких проблем не возникнет. Но если они разных типов, присвоение не всегда может быть произведено. Это связано стем, что при таком присвоении необходимо отсечь часть информации, а какую – компьютер “не знает”. Проблема возникает при следующих присвоениях: 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>] Var A, B, C, D: Integer; begin A:=1; B:=2; C:=3; D:=4; If A > B Then If C < в Then If C < 0 Then C:=0 {обратите внимание, что перед Else пустой оператор ";"не ставится} Else A:=B; end. а могло быть и так: If A > B Then If C < в 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 – уменьшается. 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 это приведет к зависанию компыютера). Если условие зарание ложно, блок операторов не исполнится ни разу. Program Epsilondetect; Var Epsilon: Real; Begin Epsilon:=1; While Epsilon + 1 > 1 Do Epsilon: = Epsilon/2; Writeln ('Эпсилон = ', Epsilon); End. Условный цикл с проверкой после выполнения блока операторов.Repeat <тело цикла> Until <условие> Блок операторов независимо от значения условия будет выполнен хотябы один раз. Цикл заканчивается, если после очередного исполнения блока операторов условие имеет значение true. 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. 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 (исключение - переменные файлового типа). Переменную типа Packed Array [0..N] of 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. 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]; Операции над множествами: 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]; Можно использовать операции сравнения. 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; В Паскале разрешено использовать массивы записей. Записи также можно использовать в качестве компонент файлов. Однако поля записи не могут быть файлового типа. Чтобы обратиться к отдельной компоненте записи необходимо задать имя записи, за ним точку и сразу за точкой написать название нужного поля. 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 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); Имена полей должны быть уникальными в пределах той записи, где они объявлены, однако если записи содержат поля-записи то есть вложены одна в другую, имена могут повторяться на разных условиях вложенности. Совместимость и преобразования типов. Турбо-Паскаль - типизированный язык, следовательно, все применяемые операции определены только над операндами совместимых типов. оба они есть один и тотже тип. один тип есть тип-диапазон второго типа. оба они являются типами-диапазонами одного и того же базового типа. один тип есть строка, а другой - строка или символ. оба они есть процедурные типы с одинаковым типом результата (для типа-функции), одинаковым количеством параметров и одинаковым типом взаимно соответствующих параметров. В Турбо-Паскалевской программе данные одного типа могут преобразовываться в данные другого, явным или неявным образом. 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 - за ним помещается либо абсолютный адрес, либо идентификатор ранее определённой переменной). 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 = [ ]; Типизированные константы указатели. Const Pr: ^Real = Nil; P: POINTER = Nil; Процедуры и функции. Блочная структура программ. Подпрограмма - обособленная именованная часть программы со своим собственным локальным контекстом имён. Структура подпрограммы в Турбо-Паскале почти буквально повторяет структуру всей программы, и в общем случае состоит из 3-х основных компонент: Интерфейс подпрограммы (информация, необходимая для её вызова). Сосредоточен в заголовке. Локальный контекст подпрограммы (совокупность описаний объектов, с которыми осуществляются действия) Собственно действия, составляющие смысл подпрограммы (последовательность операторов и вызовов подпрограмм, тоесть программа на Паскале). Процедуры и функции различаются назначением и способом использования. Имена объектов, описанных в некотором блоке, считаются известными в пределах данного блока, включая и все вложенные блоки. Имена объектов, описанные в блоке, должны быть уникальными в пределах данного блока и могут совпадать с именами объектов из других блоков. Если в некотором блоке описан объект, имя которого совпадает с именем объекта, описанного в объемлющем блоке, то последнее становится недоступным в данном блоке (экранировка). Пример: 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 - параметры-переменные). Параметры - значения.Наиболее распространенный и простой способ. Параметр - обычная локальная переменная. Может использовать выражение. Любые действия внутри подпрограммы никак не отражаются на значениях переменной вне подпрограммы. Параметры - переменные. Передаются по ссылке. Способ используется, когда необходимо передать некоторое значение в точку вызова подпрограммы. 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; Вычисление значения функции, завершение подпрограммы. Смысл функции заключается в задании алгоритма вычисления некоторого значения и организации возврата этого значения в точку вызова. Как правило, телом процедуры является блок, но есть несколько исключений из данного правила. Две подпрограммы, описанные на одном уровне, содержат взаимные вызовы друг друга. 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-программе. {$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, тоесть она не может быть обработчиком прерывания. Рекурсия и побочный эффект. В теле подпрограммы доступны все объекты, описанные в объемлющем блоке, в том числе и имя самой подпрограммы. Подпрограммы, вызывающие сами себя, называются рекурсивными. 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 байт - сегмент данных программы). Турбо-Паскаль позволяет вводить переменные специального типа, значениями которых могут служить подпрограммы. Можно интерпретировать подпрограмму как значения, которые можно присваивать переменным и передавать их в качестве параметров (речь идёт о подпрограммах как о целостных объектах, а не о значениях, возникающих в процессе их выполнения). {$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 как таковая, но её исполнения не происходит.Теперь можно: 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 и сравнить их значения. Определены следующие типы и переменные: 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 байт), что может вызвать определённые трудности при описании и обработке больших массивов данных. обработке больших массивов данных; разработке САПР; временном запоминании данных при работе с графическими и звуковыми средствами ЭВМ. Размещение статических переменных в памяти осуществляется компилятором в процессе компиляции. 12 - регистры данных и адресов; 1 - указатель команд (регистр адреса команды); 1 - регистр состояния (регистр флагов). Регистры данных и адресов делятся на три группы: регистр данных; регистр указателей и индексов; регистр сегментов. Процессор 8086 всегда генерирует 20-ти бытовые адреса за счёт добавления 16-ти битового смещения к содержимому регистра, умноженному на 16: пусть: смещение = 10H регистр сегмента = 2000H тогда: 0000 0000 0001 0000 (смещение) 0010 0000 0000 0000 (0000) (номер блока) 0010 0000 0000 0001 0000 (физический адрес) физический адрес = 20010H У 8086 адрес ячейки задаётся номером блока и смещением, что обусловлено тем, что команды 8086 и её данные должны располагаться в разных частях памяти (в разных сегментах). 16 - старших байт - команды начальной загрузки системы. Первые 1024 ячейки - вектора прерываний. Типы прерываний. Существуют 2 вида прерываний - одни можно игнорировать, другие обязательно обслужить как можно скорее. Как правило, в Турбо-Паскале указатель связывается с некоторым типом данных (типизированные указатели). 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; {- неверно} но можно сделать так: Вся динамическая память – пространство ячеек, называемое кучей. Физически куча располагается в старших адресах, сразу за программой. Указатель на начало кучи храниться в предопределенной переменной HeapOrg, конец - FreePtr, текущую границу незанятой динамической памяти указывает указатель HeapPtr. Var I,J: ^Integer; R: ^Real; Begin New(I); {под I выделяется область памяти,} {адрес первого байта этой области помещается в I} End. После выполнения этого фрагмента указатель I приобретёт значение, которое перед этим имел указатель кучи HeapPtr, а HeapPtr увеличится на два (т.к. он типа Integer); New(R) - вызовет смещение указателя на 6 байт. 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, однако память под переменную не резервировалась. Для работы с нетипизированными указателями используются также процедуры GetMem(P, Size) и FreeMem(P, Size) - резервирование и освобождение памяти. 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 байт), доступный для статического размещения данных. 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 подтверждает потерю определенной области. 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. Стандартный Паскаль не предусматривает никаких механизмов раздельной компиляции частей программы с последующей их сборкой перед выполнением. Более того, последовательное проведение в жизнь принципа обязательного описания любого объекта перед его ис пользованием делает фактически невозможным разработку разнообразных библиотек прикладных программ. Точнее, такие библиотеки в рамках стандартного Паскаля могут существовать только в виде исходных текстов и программист должен сам включать в программу подчас весьма обширные тексты различных поддерживающих процедур, таких, как, например, процедуры матричной алгебры, численного интегрирования, математической статистики и т.п. Модуль имеет следующую структуру: Unit < имя >; Interface < интерфейсная часть > Implementation < исполняемая часть > [ Begin < инициирующая часть > ] End. Здесь: Таким образом, модуль состоит из заголовка и трех составных частей, любая из которых может быть пустой. Заголовок модуля и связь модулей друг с другом Заголовок модуля состоит из кодового слова Unit и следующего за ним имени модуля. Для правильной работы среды Турбо-Паскаля и возможности подключения средств, облегчающих разработку крупных программ, это имя должно совпадать, с именем дискового файла, в который помещается исходный текст модуля. Интерфейсная часть открывается кодовым словом 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. Исполняемая часть начинается кодовым словом 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. Пусть, например, создан модуль, реализующий арифметику комплексных чисел (напомню, что такая арифметика ни в стандартном Паскале, ни в Турбо-Паскале не предусмотрена). К сожалению, в Турбо-Паскале нельзя использовать функции, значения которых имели бы структурированный тип (запись, например), поэтому арифметика комплексных чисел реализуется четырьмя приведенными ниже процедурами. 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. Как видим, программе стали доступны все объекты, объявленные в интерфейсной части. При необходимости мы можем переопределить любой из этих объектов, как это произошло, например, с объявленной в модуле типизированной константой С. Переопределение объекта означает, что вновь объявленный объект "закрывает" ранее определенный в блоке одноименный объект. При необходимости мы можем получить доступ к "закрытому" объекту. Для этого нужно перед именем объекта поставить имя модуля и точку. Так, оператор В Турбо-Паскале имеется восемь стандартных модулей, в которых содержится большое число разнообразных типов, констант, процедур и функций. Этими модулями являются: System, Dos, Crt, Printer, Graph, Overlay, Turbo3 и Graph3. Ниже приводится краткая характеристика стандартных модулей. В модуль System входят все процедуры и функции стандартного Паскаля, а также встроенные процедуры и функции Турбо-Паскаля, которые не вошли в другие стандартные модули (например, Inc, Dec, Getdir и т.п.). Как уже говорилось, модуль System подключается к любой Турбо-Паскалевой программе независимо от, того, объявлен ли он в предложении Uses или нет. Модуль Printer упрощает вывод текстов на матричный принтер. В нем определяется файловая переменная Lst типа Text, которая связывается с логическим устройством Prn. После подключения модуля может быть выполнена, например, такая программа: USES Printer; Begin WriteLn (Lst, ‘TEST’); {Выводит строку на принтер} End. В модуле Crt сосредоточены процедуры и функции, обеспечивающие управление текстовым режимом работы экрана. С помощью входящих в модуль блоков можно перемещать курсор в произвольную позицию экрана, менять цвет выводимых символов и окружающего их фона, создавать окна. Кроме того, в модуль включены также процедуры "сле пого" чтения клавиатуры и управления звуком. Модуль Graph содержит обширный набор типов, констант, процедур и функций для управления графическим режимом работы экрана. С помощью блоков, входящих в модуль Graph, можно создавать самые разнообразные графические изображения и выводить на экран текстовые надписи стандартными или разработанными программистом шрифтами. Программы модуля Graph после соответствующей настройки могут поддержать любой тип аппаратных графических средств (CGA, EGA, VGA). В модуле Dos собраны процедуры и функции, открывающие доступ Турбо-Паскалевым программам ко всем средствам дисковой операционной системы PC DOS (MS DOS). Два библиотечных модуля Turbo3 и Graph3 введены для совместимости с ранней версией 3.0 системы Турбо-Паскаль. Ключи и директивы компилятора. Позволяют управлять процессами компиляции программы. Директива представляет собой комментарий со специальным синтаксисом: начинается с {$, далее пишется имя директивы (одна или несколько букв) и параметры. В Турбо-Паскале 7.0 существует три основных вида директив: Директивы - переключатели. Директивы с параметрами. Условные директивы. Все директивы, кроме директив-переключателей, должны иметь по крайней мере один пробел между именем директивы и параметром. Различаются по диапазону своего действия на глобальные и локальные: Глобальные - влияют на ход компиляции и выполнение всей программы, локальные - только на определённую часть программы. Глобальные директивы могут быть объявлены в разделе объявлений и соглашений прежде, чем будет использовано хотя бы одно из следующих зарезервированных слов: Uses, Label, Const, Type, Procedure, Function, Begin, а Локальные директивы могут быть объявлены в любом месте программы или программного модуля. Выравнивание данных - (Word Align Data) на переменные размером в байт, на поля структур или элементы массивов. Булевы вычисления (Complete Boolean Evaluation) 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) Эмуляция сопроцессора. (Emulation). Дальний тип вызова (Force Far Calls). Генерация кода для 80286 (286 Instruction). Проверка ошибок ввода-вывода (I/O checking). Информация о локальных идентификаторах. (Local Symbols) Использование математического сопроцессора (8087/80287). Использование оверлейных структур. (Overlays allowed). Использование в качестве параметров массивов открытого типа (Open parameters). Проверка переполнения при математических операциях (Overflow checking). Проверка границ. (Range-Checking). Проверка переполнения стека (Stack Checking) Использование типизированного адресного оператора @ (Typed @ operator). Проверка параметров переменных строкового типа (Strict Var String). Расширенный синтаксис. (Extended Syntax). Включение файла для компиляции (Include Directories). Компоновка объектного файла. (Object Directories). Размеры выделяемой памяти (Memory Sizes). Компоновка оверлейного модуля. Установить условие (Conditional Defines). Установить условие - значит с помощью данной директивы ввести некоторое слово, которое затем будет управлять компиляцией какого- либо фрагмента программы. За условием не должно следовать ничего кроме }. {$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; . Здесь < имя > - имя файлового типа или файловой переменной, 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 - для вывода на экран. Стандартный Паскаль требует обязательного упоминания этих файлов в заголовке программы. В Турбо-Паскале это необязательно, вот почему заголовок программы можно опускать. Имя файла - это любое выражение строкового типа, которое строится по правилам определения имен в дисковой операционной ДОС) ПЭВМ: имя содержит до восьми разрещенных символов (разрешенные символы - это произвольные символы с кодами от 33 до 255, кроме символов пробел, точка, запятая, двоеточие, звездочка, знак вопроса, обратная косая черта, а также символ Забой - код 127 по стандарту АSCII); имя начинается с любого разрешенного символа; за именем может следовать расширение - последовательность до трех разрешенных символов; расширение, если оно есть, отделяется от имени точкой. Перед именем может ставиться так называемый путь к файлу - имя диска и/или имя текущего каталога и имена каталогов вышестоящих уровней. 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 нельзя, например, прочитать данные с экрана ПЭВМ, хотя такая аппаратная возможность существует. PRN - логическое имя принтера. Если к ПЭВМ подключено несколько принтеров, доступ к ним осуществляется по логическим именам LРТ1, LРТ2 и LРТЗ. Имена РRN и LРТ1 первоначально синонимы. Средствами ДОС можно переназначить имя РRN на любое другое логическое устройство, способное принимать информацию. АUХ - логическое имя коммуникационного канала, который обычно используется для связи ПЭВМ с другими машинами. Коммуникационный канал может осуществлять и прием, и передачу данных, однако в Турбо-Паскалевой программе в каждый момент времени ему можно назначить только одну из этих функций. Как правило, в составе ПЭВМ имеются два коммуникационных канала, которым даются имена логических устройств СОМ1 и СОМ2. Первоначально имена АUХ и СОМ1 синонимы. NUL - логическое имя "пустого" устройства. Это устройство чаще всего используется в отладочном режиме и трактуется как устройство неограниченной емкости - приемник информации. При обращении к NUL как к источнику информации выдается признак конца файла ЕОF. Инициировать файл означает указать для этого файла направление передачи данных. В Турбо-Паскале можно открыть файл только для чтения, только для записи, а также для чтения и записи информации одновременно. Для чтения файл инициируется с помощью процедуры: Встроенная процедура REWRITE(< ф.п. >) инициирует запись информации в файл или логическое устройство, связанное ранее с файловой переменной < ф.п. >. Процедурой REWRITE нельзя инициировать запись информации в ранее существовавший дисковый файл: при выполнении этой процедуры старый файл уничтожается и никаких сообщений об этом в программу не передается. Новый файл подготавливается к приему информации и его указатель устанавливается в нуль. Встроенная процедура АРРЕND(< ф.п. >) инициирует запись в ранее существовавший текстовый файл для его расширения - указатель файла устанавливается в его конец. Не следует забывать, что процедура АРРЕND применима только к текстовым файлам. Если текстовый файл ранее уже был открыт с помощью RESET или REWRITE, использование процедуры APPEND приведет к закрытию этого файла и открытию ею вновь, но уже для добавления записей. Процедуры и функции для работы с файлами.Ниже описываются процедуры и функции, которые можно использовать с файлами любого вида. Специфику работы с типизированными, текстовыми и нетипизированными файлами рассмотрим позднее. CLOSE(< ф.п. >) RENAME (< ф.п. >, < новое имя >) ERASE(< ф.п. >). FLUSH(< ф.п. >). Функция ЕОF(< ф.п. >): BOOLEAN. 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 центрального процессора в самом начале своей работы и восстановить содержимое этих регистров перед возвратом управления в Турбо-Паскалевую программу. Остальные регистры можно не сохранять и соответственно не восстанавливать. Ассемблерные функции должны возвращать результат с помощью регистров центрального процессора или сопроцессора в зависимости от длины внутреннего представления результата по следующим правилам: длиной в 1 байт - в регистре АL; длиной в 2 байта - в регистре АХ; длиной в 4 байта - в регистрах DХ:АХ (старшее слово в DХ); тип REAL (б байт) - в регистрах DХ:ВХ:АХ; типы SINGLE, DOUBLE, EXTENDED и COMP - через стек сопроцессора 8087/80287; указатели - в регистрах DХ:АХ (сегмент в DХ); строки возвращаются по ссылке: адрес начала строки помещается в DХ:АХ (сегмент в DХ). Все ассемблерные процедуры должны размещаться в сегменте с именем СОDЕ, а имена процедур и функций должны быть объявлены директивой PUBLIC. Локальные переменные необходимо размещать в сегменте с именем DАТА. Все имена, объявленные в интерфейсной части модулей Турбо-Паскалевой программы, становятся доступны ассемблерной процедуре (функции) после их объявления директивой ЕХТRN. Использование встроенных машинных кодов. В Турбо-Паскале имеется возможность включения в программу фрагментов, написанных непосредственно в машинных кодах. Для этого используется зарезервированное слово INLINE, за которым в круглых скобках следует один или несколько элементов машинного кода, разделяемых косыми чертами. Элемент кода, в свою очередь, строится из одного или более элементов данных, разделенных знаками "+" или "-". 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, однако ясно что таким способом невозможно запрограммировать все допустимые обращения к средствам ДОС. Вот почему в Турбо-Паскаль включены две процедуры, с помощью которых программист может сам сформировать вызов той или иной функции ДОС. Всего в ДОС имеется около 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-битным регистрам. Так, прерывание с номером 18 ($12) возвращает в регистре AX объем оперативной памяти ПЭВМ. Пример программы выводящей на экран сообщение об этом объеме: Program IntrDem; Uses DOS; Var r: registers; Begin Intr($12, r); writeln('Объем памяти = ', r.АХ, ' Кбайт') END. Процедура MSDOS. Инициирует прерывание с номером 33 ($21); Программное прерывание с номером 33 ($21) стоит особняком: как уже говорилось, оно дает доступ к большому количеству функций ДОС (этим прерыванием вызывается 85 функций). Рассматриваемая процедура полностью эквивалентна вызову процедуры INTR c номером прерывания 33. Программа примера выведет на экран версию операционной системы: Program MsDosDemo; Uses DOS; Var R: registers; Begin r.AH: = $30; MsDos(r); write1n ('Версия операционной системы: г.АL, '.', г.АН) END. Поддержка процедур обработки прерываний. При написании процедур обработки прерываний существенными являются два обстоятельства. Во-первых, процедура обработки прерывания не должна искажать работу прерванной программы. Для этого необходимо сначала сохранить регистры центрального процессора, а перед выходом из процедуры - восстановить их. Во- вторых, процедура должна строиться по принципу реентерабельности (повторной входимости): ее работа может быть прервана в любой момент другими прерываниями и ДОС может обратиться к соответствующей функции до завершения обработки предыдущего прерывания. 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 В самой процедуре обработки прерывания не рекомендуется обращение к другим функциям ДОС, так как некоторые из них, в том числе все функции ввода-вывода, нереентерабельны. 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. Устанавливает новое значение вектора прерывания; Из Турбо-Паскалевой программы можно запустить любую другую готовую к работе программу. Для этого используется процедура ЕХЕС из библиотечного модуля DOS; Далее, специфические особенности исполнения Турбо-Паскалевых программ требуют изменения стандартных значений некоторых векторов прерываний. К ним относятся векторы со следующими шестнадцатеричными номерами: $00, $02, $18, $23, $24, $34, $35, $36, $37, $38, $39, $3А, $3В, $3С, $3D, $3Е, $3F, $75. 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, в котором содержится общее количество установленных в ДОС параметров. Как уже говорилось в, максимальный размер модуля не может превышать 64 Кбайт, однако количество модулей не ограничено, что дает возможность разрабатывать весьма крупные программы, занимающие, например, всю доступную оперативную память ПЭВМ (приблизительно 580 Кбайт). Однако в некоторых случаях и этот объем может оказаться недостаточным. Турбо-Паскаль предоставляет в распоряжение программиста простой и достаточно эффективный механизм оверлея, с помощью которого Вы сможете создавать программы практически неограниченной длины (следует оговориться, что речь идет только о длине кода программ; два важных размера - длина стека данных и программного стека - в Турбо-Паскале не может превышать 64 Кбайт независимо от структуры программы). Вначале необходимо выделить главную часть программы и разбить оставшуюся часть на несколько модулей. Отметим, что никаких дополнительных ограничений на модули по сравнению с описанными в, за одним исключением: в оверлейных модулях нельзя использовать процедуры обработки прерываний. Желательно продумать состав модулей таким образом, чтобы минимизировать количество их перезагрузок в буфер в процессе исполнения программы. В главной части программы необходимо указать с помощью директив компилятора вида {$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+}, строго говоря, не является обязательной. Если она указана, то при вызове любой процедуры или функции программа будет помещать все фактические параметры обращения в резидентную (неоверлейную) часть памяти, что позволяет из одного оверлейного модуля вызывать процедуры и функции любого другого оверлейного же модуля. 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бычно размер оверлейного буфера автоматически определяется таким, что в нем может разместиться самый крупный из всех оверлейных модулей. Программист может увеличить размер буфера. Тогда при загрузке в буфер очередного модуля программа проверит, достаточно ли в буфере свободного места, и, если места достаточно, загрузит новый модуль сразу за старым, который таким образом не будет уничтожен. Такой механизм способствует минимизации потерь времени на перезагрузку модулей. Если установлен очень большой размер буфера, то в нем, возможно, смогут разместиться все оверлейные модули и потери времени будут сведены к нулю, однако в этом случае оверлейная структура становится просто ненужной. Если Ваша ЭВМ имеет расширенную память (общий объем памяти свыше 1024 Кбайт), Вы можете использовать эту память для размещения в ней оверлейного файла .ОVR. Поскольку время доступа к расширенной памяти значительно меньше времени чтения с диска, такое размещение увеличивает скорость исполнения оверлейной программы. В Турбо-Паскале имеется пять предварительно объявленных массивов: МЕМ, МЕМW, МЕМL, РОRТ и РОRТW. Первые три обеспечивают доступ к любому участку оперативной памяти по абсолютному адресу, два другие - доступ к портам ввода-вывода. 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 и структурное программирование. Мы уже знаем, что на этапе проектирования архитектуры Турбо- Паскаль поддерживает модульное проектирование. Но наиболее эффективен Турбо-Паскаль на этапе детального проектирования. их нотации несовместимы с нотациями, используемыми в программных спецификациях и в реализациях; невозможен прямой ввод в ЭВМ и вывод из нее, практически мало средств автоматизированной поддержки; нет эффективном способа управления уровнем детализации в рамках каждой схемы; нотации недостаточны для проектирования крупномасштабных систем ПО. Диаграммы Нэсси-Шнейдерман призваны поддерживать структурное программирование. Предложены специальные графические прямоугольные изображения для базовых структур. Программа описывается с использованием этих изображений. Основные характеристики: функциональная область хорошо определена; не разрешены произвольные передачи управления; легко определяются границы локальных и глобальных данных; легко представляются рекурсивные свойства. Среди приемов языкового представления наиболее известным является использование псевдокодов или языков проектирования программ. суть проектирования на этом этапе. ТурбоПаскаль и объектно-ориентированное программирование. Основные определения типа <объект>. Начиная с версии 5.5, Турбо-Паскаль охватывает еще один современный метод проектирования программ, описанный выше как обьектно-ориентированное проектирование. < ИмяПотомка > = Object(< ИмяПредка >) поле; ............ поле; метод; ............. метод; End; Метод - это процедура или функция, объявленные внутри объявления элемента типа объект и предназначенная в обычно для работы с полями этого объекта. Procedure Объект.Метод; (< параметры >); Begin ....... ....... End; Методы подразделяются на статические и виртуальные. Виртуальный метод отличается от статического тем, что реализующий его код подсоединяется к вызову не в процессе компиляции, а в процессе выполнения, что достигается так называемым поздним связыванием. Это дает возможность строить иерархию объектов с одинаковыми названиями методов, реализуемыми, однако, различными кодами. Основными отличительными свойствами объекта являются: инкапсуляция - объединение записей с процедурами и функциями, работающими с этими записями; наследование - задание объекта, затем использование его для построения иерархии порожденных объектов с наследованием доступа каждом из порожденных объектов к коду и данным предка; полиморфизм - задание одного имени действию, которое передается вверх и вниз по иерархии объектов, с реализацией этом действия способом, соответствующим каждому объекту в иерархии. Рассмотрим смысл каждого из приведенных свойств. ИнкапсуляцияДопустим, наши практические интересы лежат в области построения изображений тел звездном неба в двумерной проекции. Очевидно, что основой всякого изображения является положение (позиция) отдельного элемента на экране, описываемая координатами Х и У. Для задания двумерной позиции подходит тип запись, имеющйся в Турбо- Паскале. 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; Заметим, что имена формальных параметров метода не могут совпадать с именами полей данных объекта. 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. 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, обладая всеми его свойствами, кроме того, быть "больше" по размеру (точнее - видимому размеру). Однако, даже начинающему программисту ясно, что нарисоавть на экране точку и закрашенную окружность не удастся одними и теми же командами. Turbo Vision - это оболочка оконной программы, управляемой событиями, включающая: многократные перекрывающиеся окна с изменяемыми размерами, выпадающие меню, поддержку мышки, диалоговые окна, встроенную установку цвета, кнопки, полосы скроллинга, окна ввода, зависимые и независимые кнопки, стандартную обработку клавиш и нажатий мышки. Turbo Vision использует понятия видимых элементов, событий и невидимых объектов. Если Вы забыли, как точно пишется имя процедуры, какие аргументы ей требуются, то ИПО может помочь, предоставив в Ваше распоряжение свою справочную систему. Ее можно вызвать, нажав клавишу [F1]. Справка, которую выдает система, является контекстно-зависимой. Это означает, что Вы получите сообщение об объекте, указываемом курсором в тексте программы или в меню, либо сообщение о текущей ситуации в системе. Передвигая курсор по тексту справки, выбирая те или иные отмеченные элементы и нажимая затем клавишу [Enter], можно получить более подробную информацию. Целесообразно пользоваться справочной системой и при появлении сообщений об ошибках. Это избавляет от необходимости выяснять их причины в руководствах и справочниках.
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. Блок-схема должна содержать все разветвления, циклы и обращения к подпрограммам, содержащиеся в программе. ![]() Вычислительные процессы, выполняемые на ЭВМ по заданной программе, можно разделить на три основных вида: линейные; ветвящиеся; циклические. На Рис.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(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(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-х гг. Керниганом и Риччи и реализованное в ОО-версиях языков Си и Паскаль, представляет собой отображение объектов реального мира, их свойств (атрибутов) и связей между ними при помощи специальных структур данных. Структурное программирование подразумевает наличие ряда встроенных структур данных: целых, вещественных и строковых переменных, массивов, записей - при помощи которых и производится отображение свойств объектов реального мира. При объектно-ориентированном подходе для объекта создается своя структура данных (класс), содержащая как свойства объекта (поля), так и процедуры для управления объектом (методы). Функциональный стиль В основе функционального стиля лежит понятие функции как "черного ящика", имеющего вектор параметров (аргументов) Допустим случай , т.е. вектор параметров отсутствует. В функциональных языках программирования отсутствуют операторы: все действия, в том числе и управляющие конструкции, выполняются при помощи вызовов функций. Поскольку каждая функция возвращает значение, то: Каждую функцию можно подставить в качестве аргумента другой функции, что позволяет записывать сложные выражения в функциональной форме. Одним из первых функциональных языков стал язык Лисп, созданный в конце 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 Типы линий
Таблица 2 Толщина линии
Процедура GetLineSettings (Var lineInfo: LineSettingsType) возвращает текущий стиль, образ и толщину линии, установленные SetLineStyle. Работа с цветом В видеобуфере (видеопамяти) для хранения информации о цвете пиксела отводится фиксированное количество битов памяти. Размер палитры и ее организация зависят от типа используемого видеоадаптера. При использовании видеокарты EGA/VGA для установки цвета пиксела используется 6 битов. Для формирования цвета используется система RrGgBb, где RGB - красный, зеленный и голубой цвета нормальной яркости, а rgb - те же цвета, но яркость их в два раза меньше. Для EGA/VGA-карт драйвер EGAVGA..BGI устанавливает 54 цвета. В Таблице 4 приведены 16 основных цветов. Таблица 3 Цвета видеокарты EGA/VGA
До того момента, пока цвет не определен, для вывода используется цвет, имеющий максимальный номер палитры и фон, устанавливаемый по минимальному номеру. Для 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 Типы шрифтов
Таблица 5 Ориентация текста
Для выравнивания текста необходимо использовать процедуру SetTextJastify (Horiz, Vert: word). Выравнивание относительно СР выполняется по вертикали и по горизонтали с помощью параметров Horiz и Vert (Табл.7). Таблица 6 Типы линий
Пример: В следующем примере приведена программа, выводящая заставку. Заставка представляет собой фразу "Демонстрация заставки", напечатанную коричневыми буквами обычного размера и синими буквами увеличенного размера; по периметру экрана нарисованы три разноцветных прямоугольника линиями различной толщины и стиля. 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 Килобайт. Выход - использовать динамическую память. Динамическую память обычно используют при: обработке больших массивов данных; разработке САПР; временном запоминании данных при работе с графическими и звуковыми средствами ЭВМ. Размещение статических переменных в памяти осуществляется компилятором в процессе компиляции. Динамические переменные - размещаются в памяти непосредственно в процессе работы программы. При динамическом размещении заранее неизвестны ни тип, ни количество размещаемых данных, к ним нельзя обращаться по именам, как к статическим переменным. Турбо-Паскаль представляет средство управления динамической памятью: указатели. В 8086 адреса задаются совокупностью двух шестнадцатиразрядных слов - сегмента и смещения. Сегмент - участок памяти, имеющий максимальную длину 64 К и начинающийся к физического адреса, кратного 16 (то есть 0, 16, 32, 48 и т.д.). Смещение - указывает, сколько байт от начала сегмента нужно пропустить, чтобы обратиться по нужному адресу. Фрагмент памяти в 16 байт называется параграфом. Сегмент адресует память с точностью до параграфа, а смещение - с точностью до байта. Таким образом любой указатель по своей внутренней структуре представляет собой совокупность двух слов (типа Word), трактуемых как сегмент и смещение. Указатель адресует лишь первый байт типа данных.
12 - регистры данных и адресов; 1 - указатель команд (регистр адреса команды); 1 - регистр состояния (регистр флагов). Регистры данных и адресов делятся на три группы: регистр данных; регистр указателей и индексов; регистр сегментов. Процессор 8086 всегда генерирует 20-ти бытовые адреса за счёт добавления 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. (на доске) // Var I,J: ^Integer; R: ^Real; Begin New(I); {под I выделяется область памяти,} {адрес первого байта этой области помещается в I} End. // После выполнения этого фрагмента указатель I приобретёт значение, которое перед этим имел указатель кучи HeapPtr, а HeapPtr увеличится на два (т.к. он типа Integer); New(R) - вызовет смещение указателя на 6 байт. После того как указатель приобрёл некоторое значение, то есть стал указывать на конкретный байт памяти, по этому адресу можно разместить значения соответствующего типа: I^:= 2; Допустима запись: R^:= Sqr (R^) + I^ - 17; Возврат динамической памяти обратно в кучу осуществляется оператором Dispose: Чтобы указать, что указатель свободен, нужно использовать зарезервированное слово 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, однако память под переменную не резервировалась. Для работы с нетипизированными указателями используются также процедуры GetMem(P, Size) и FreeMem(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 байт), доступный для статического размещения данных. Однако в Турбо-Паскале над указателями не определены никакие операции, кроме присваивания и отношения. Но задачу решить можно: Таким образом, сначала с помощью 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 - указатель; поле в - данные. Описание этой компоненты дадим следующим образом: (на доске) // 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
{описание объектов 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.1var х,у: 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) или глобальной директивой компилятора {$Х+}. |