Главная      Учебники - Разные     Лекции (разные) - часть 33

 

Поиск            

 

Алгоритмический язык Паскаль

 

             

Алгоритмический язык Паскаль

МИНИСТЕРСТВО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ

ЧЕРЕПОВЕЦКИЙ ГОСУДАРСТВЕННЫЙ ПЕДАГОГИЧЕСКИЙ ИНСТИТУТ им. А.В. ЛУНАЧАРСКОГО

КАФЕДРА ИНФОРМАТИКИ

ЧЕРЕПО ВЕЦ

2010

1. ОБЩАЯ ХАРАКТЕРИСТИКА ЯЗЫКОВ ПРОГРАММИРОВАНИЯ

ЭВМ - это устройство для автоматической обработки информации (данных). Эвм может выполнять только специальные, присущие ей команды. Чтобы машина сделала что-либо полезное, необходимо задать последовательность команд на том языке, который она понимает. Такая последовательность называется программой.

Известно, что ЦП каждого типа ЭВМ имеет свою систему команд и каждая команда внутри ЭВМ (в памяти) представляется в виде последовательности нулей и единиц - машинного кода. На этапе становления программирования программы для ЭВМ составлялись именно в машинных кодах, что стало довольно затруднительно при решении более сложных задач. Поэтому были разработаны языки программирования.

1.1 Языки программирования

Язык низкого уровня представляет собой систему двоичных или шестнадцатеричных команд, написанных в машинных кодах. Программист общается с машиной на "ее языке". Он понимается ею без преобразований. К таким языкам можно отнести АССЕМБЛЕР. Этот язык используется, в основном, программистами профессионалами и обладает существенным недостатком - машинная зависимость, т.е. невозможность переноса программы на другой тип машин.

Работа с языками высокого уровня в машине происходит более сложно. Она вначале преобразует команды языка в шестнадцатеричные коды, затем расшифровывает их (ставит в соответствие каждому коду одну или несколько своих команд) и только после этого выполняет программу. Примерами языков высокого уровня являются Паскаль, Бейсик, Си и другие языки. В отличие от языков низкого уровня, на языках высокого уровня легче программировать, т.е. общаться с машиной. Однако часто с простотой общения теряются некоторые возможности машины, поэтому практически в каждом языке высокого уровня есть возможность писать команды непосредственно на машинном языке (программировать в "кодах").

1.2 Трансляторы

Процесс перевода программы с языка программирования высокого уровня на машинный язык называется трансляцией. Трансляция осуществляется с помощью специальной программы, называемой транслятором. Различают два вида трансляции: интерпретация и компиляция, а соответствующие им программы - интерпретаторы и компиляторы. Трансляторы с языков являются составной частью самой системы программирования, поэтому запуск программы предполагает сначала ее трансляцию, а потом собственно выполнение.

Рассмотрим процесс выполнения программы, написанной на языках интерпретаторах, а затем компиляторах.

I. ИНТЕРПРЕТАТОР

1. Машина считывает очередной оператор программы.

2. Переводит оператор в свои, ей понятные команды.

3. При обнаружении ошибки интерпретация прерывается и машина указывает на это.

4. Выполняет переведенные команды.

5. "Забывает" считанный оператор.

6. Продолжает данный процесс, пока не выполнятся все операторы, т.е. пока не дойдет до указателя конца программы.

7. "Забывает" выполненную программу.

Процесс интерпретации можно также проиллюстрировать в виде следующей схемы :

II. КОМПИЛЯТОР

1. Машина считывает очередной оператор, написанный на языке.

2. Переводит оператор в свои, ей понятные команды.

3. При попадании на ошибку процесс перевода прерывается, и машина указывает на это.

4. Продолжает данный процесс, пока не иссякнут все строки программы, т.е. пока не дойдет до указателя конца программы.

5. Выполняет переведенную программу целиком.

Из указанного выше процесса выполнения программы следует, что языки интерпретаторы работают медленнее, при запуске не "вылавливают" всех ошибок (лишь при попадании на них машина указывает на ошибку).

1.3 История создания языков

Одним из первых языков программирования, созданных специально для учебных целей, был БЕЙСИК, разработанный в 1964 году в Дартмутском колледже (США). Его создание преследовало цель предоставить возможность студентам пользоваться средствами ЭВМ без длительной предварительной подготовки. Предполагалось также, что БЕЙСИК будет использоваться в качестве универсального языка людьми, не имеющими опыта работы на ЭВМ - рядовыми пользователями. Одним из достоинств языка является его удобство для работы в интерактивном режиме, что послужило использованием БЕЙСИКа при разработке диалоговых обучающих программ.

К концу 60-х годов сложилась ситуация, когда для профессиональных целей использовались языки типа ФОРТРАН, КОБОЛ и пр., а весь учебный мир предпочитал БЕЙСИК. Естественно, что многие считали такую ситуацию неудовлетворительной. По этой причине две группы исследователей приступили к созданию универсального языка программирования, отвечающего современным требованиям. Этот язык должен был включать в себя все достоинства существующих языков, иметь логически обоснованную структуру и быть легким для восприятия. Такие языки были созданы. Одним из них являлся АЛГОЛ-68, другой же был разработан в Институте информатики г. Цюриха (Швейцария) Николасом Виртом в 1971 г. Этот язык получил название ПАСКАЛЬ в честь великого французского ученого XYII века, сумевшего первым в мире изобрести автоматическое устройство для проведения вычислений. Транслятор с языка был разработан в 1973 г.

Так же как и Бейсик, Паскаль довольно просто изучать. Главное, чем обладает Паскаль - он удовлетворяет требованиям обыкновенных пользователей и специалистов по ВТ. Известно, что первым нужен язык, который легко изучать, а вторым - логически правильно построенный язык. Паскаль имеет практически все конструкции языков ПЛ/1 и АЛГОЛ-68, однако он более лаконичен. Грамматические правила языка можно уместить на 4-х страницах.

Хотя Паскаль почти так же прост, как и Бейсик, он имеет перед ним ряд преимуществ. Так, Паскаль способствует внедрению современной технологии программирования, основанной на поэтапном построении программы (принцип "cверху-вниз"), состоящих из небольших, четко определенных процедур (структурный подход). Таким образом, преодолевается главный недостаток, свойственный Бейсику, - неэффективная организация подпрограмм. Разработанный Н. Виртом вариант языка является стандартом. Помимо стандарта языка, в связи с разработкой различных компиляторов, появились версии Паскаля, среди которых наиболее популярной является система Turbo-Pascal, используемая на IBM - совместимых компьютерах.


1.4 Базовые структуры языков программирования

Понятие "структурное программирование" появилось в 1968 году, когда была опубликована статья одного из видных программистов того времени Дейкстры. Он в своей статье констатировал вредность применения оператора безусловного перехода (оператора, позволяющего сделать переход от одного оператора к другому, находящемуся в любом месте программы) и нашел хорошее объяснение причине, по которой он вреден.

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

Таким образом, структурным языком назовем тот, у которого блок-схема любой программы состоит только из базовых структур.

Имеется четыре типа управляющих структур: следование, выбор, повторение (цикл) и группирование.

Для реализации следования есть правило: все команды выполняются в порядке их следования.

Для выбора и повторения есть свои специальные инструкции (операторы, команды). Выбор предусматривает проверку условия с последующим выполнением одной или нескольких команд в зависимости от истинности или ложности условия. Выбор (или развилка) бывает полный или неполный, в зависимости от выполняемых команд:

Таким образом, конструкция работает следующим образом: при истинности условия P выполняется серия команд S1, в противном случае либо S2, либо управление передается следующей за развилкой конструкции.

Повторение, или цикл, представляет собой конструкцию, которая состоит, как и выбор, из проверки условия и серии команд. Однако, в отличие от выбора, данная серия команд может выполняться неоднократно, в зависимости от проверки условия. Повторения подразделяются на циклы с предусловием (циклы-пока) и циклы с постусловием (циклы-до).

Серия команд S выполняется циклически до тех пор, пока условие истинно в первом случае и ложно - во втором.

Группирование означает объединение одной или нескольких инструкций внутри специальной инструкции. Во всех языках имеются средства для формирования единого блока из группы инструкций (подпрограммы в Бейсике, составные инструкции и процедуры в Паскале). Примером группирования может являться также выполнение в конструкциях циклов следования или выбора и т.д.

1.5 Синтаксические диаграммы

Программирование на любом языке предполагает знание определенных форм записи, которые необходимо соблюдать при написании каждой программы. Правила записи часто бывают, сложны в написании, и поэтому прибегают к различным способам их отображения. Рассмотрим один из них - синтаксическую диаграмму.

Синтаксическая диаграмма - это графическое представление отдельных объектов языка и строения самой программы. Возьмем, например, правило записи команды проверки условия в школьном алгоритмическом языке: команда начинается со служебного слова "если", за ней следует логическое выражение, далее обязательно слово "то", серия команд 1 и, наконец, не обязательно "иначе" и серия команд 2. Синтаксическая диаграмма выражает данное правило в виде схемы игрушечной железной дороги.

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

диаграммы; если там он прибывает к месту назначения, то ему нужно проехать в первой синтаксической диаграмме к выходу прямоугольного отсека. При движении локомотива следите за направленностью его движения по стрелкам.


2 . ОПИСАНИЕ ЯЗЫКА ПАСКАЛЬ

2.1 Основные объекты языка

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

Буквы : латинские от A до Z, от a до z и русские от А до Я, от а до я

Цифры : 0 1 2 3 4 5 6 7 8 9

Специальные символы : + - * / = ^ < > () [ ] { }.,:; ' # $

Зарезервированные слова :

absolute downto function nil record To
and else goto not repeat Type
array end if of set Until
begin external in or shl Var
case file inline packed shr While
const for label procedure string With
div forward mod program then Xor
do

Стандартные идентификаторы (имена):

Arctan ConInPtr FilePos Length Port Sqr

Assign ConOutPt FileSize Ln Pos Sqrt

Aux Concat FileChar Lo Pred Str

AuxInPrt ConstPtr Flush LowVideo Ptr Succ

AuxOutPrt Copy Frac Lst Random Swap

BlockRead Cos GetMem LstOutPtr Randomize Text

BlockWrite CrtExit GotoXY Mark Read Trm

Boolean CrtInit HeapPtr MaxInt Readln True

BufLen DelLine Hi Mem Real Trunc

Byte Delay IOresult MemAvail Release UpCase

Chain Delete Input Move Rename Usr

Char EOF InsLine New Reset UsrInPtr

Chr EOLN Insert NormVideo Rewrite UsrOutPtr

Close Erase Int Odd Round Val

ClrEol Execute Integer Ord Seek Write

ClrScr Exp Kbd Output Sin Writeln

ÐÀÇÄÅËÈÒÅËÈ

Символами-разделителями считаются пробелы, концы строк (разделители строк) и комментарии. Внутри лексем ни разделители, ни их части встречаться не могут. Между двумя следующими друг за другом лексемами должен обязательно следовать один или несколько разделителей.

Комментарии в Паскаль-программе начинаются с символа { или (*и заканчиваются } или *). Сам комментарий может содержать любые символы, кроме } и *). Любой комментарий можно заменить в программе на пробел.

Символы-разделители применяются часто для улучшения читаемости программы.

Íàïðèìåð:

program PRIMER;

{Программа сложения натуральных чисел}

var I,J,K: integer;

begin

readln(I,J); { Вводдвухслагаемых }

K:=I+J;

writeln(I,'+',J,'=',K); {Печать результата в форме 12+3=15}

end.


2.2 Структура Паскаль - программы

Язык Паскаль, как учебный алгоритмический язык, лег в основу разработки школьного алгоритмического языка, поэтому оба они имеют много общего и прежде всего это касается структуры программ (алгоритмов).

РАЯ ПАСКАЛЬ
АЛГ<имя> PROGRAM<имя>
ДАНО Раздел
НАДО объявлений
НАЧ BEGIN
- -
- Блок программы
- Серия команд (серия операторов)
- -
- -
КОН END

Сравнительный анализ представленной схемы показывает, что по своему внешнему оформлению запись алгоритма на школьном алгоритмическом языке и программы на языке Паскаль во многом схожи. Действительно, оба этих описания начинаются с заголовка, в котором обязательно указывается имя алгоритма (программы). Наличие имени связано с тем обстоятельством, что описанный алгоритм в РАЯ и программа в Паскале могут служит вспомогательным алгоритмом (процедурой) для других, более сложных алгоритмов (программ).

В обоих языках принято описывать (объявлять) все переменные, фигурирующие в алгоритме (программе) с указанием их типов. Правда, в РАЯ эти переменные подразделяются еще на аргументы, результаты и промежуточные переменные, а в Паскале они просто перечисляются в разделе объявлений.

Идентификатор - это последовательность букв или цифр, начинающаяся с буквы. Отметим, что в системе TURBO в идентификаторах могут встречаться не любые буквы, а только латинские. Под оператором понимается указание ЭВМ по выполнению каких-либо действий.

Как видно из диаграммы, любая Паскаль-программа имеет имя, за которым может следовать список идентификаторов, заключенных в скобки. Заголовок программы заканчивается точкой с запятой. Затем идут объявления, служащие для описания типов данных, процедур и функций. Далее BEGIN, один или несколько операторов, разделенных точками с запятой, и в конце ставится END с точкой. При написании программ используются лексемы и разделители, определенные алфавитом языка.

По написанию инструкций (операторов) Паскаль, как и язык РАЯ, довольно свободен. Инструкция может занимать не одну, а несколько строк. На одной строке можно разместить несколько инструкций. Здесь можно вставлять пробелы и пустые строки (но пробелы в служебных словах недопустимы). Для лучшей читабельности программы строки можно располагать лесенкой.

2.3 Типизация данных

Данные - это общее понятие всего того, с чем оперирует ЭВМ. Любой тип данных определяет множество значений, которые может принимать та или иная переменная, и те операции, которые можно к ним применять. Каждая встречающейся в программе переменная может иметь один и только один тип.

В Паскале имеется три типа данных: простые, составные и ссылочные. Рассмотрим вначале простой тип данных, представленный на следующей схеме:

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

К любому ординальному значению X применимы три следующие встроенные функции:

ORD(X) - дает порядковый номер, соответствующий X. Результат относится к типу INTEGER;

SUCC(X)- дает следующее за X значение, если X не максимальный элемент соответствующего типа. В последнем случае SUCC(X) суть ошибка;

PRED(X)- дает предыдущее X значение, если только X не минимальный элемент соответствующего типа. В последнем случае PRED(X) суть ошибка.

Наиболее простыми из ординальных типов являются предописанные или встроенные типы: INTEGER, BOOLEAN и CHAR, которые определяют соответственно числовые, логические (булевские) и литерные (символьные) величины. К встроенному (но не ординальному) типу данных относится также тип REAL.

Кроме предописанных, в Паскале существует возможность задавать свои типы данных. К ним относятся перечислимый и диапазонный типы. Каждый из них состоит из элементов предописанных типов, однако, в совокупности они дают совершенно новый тип.

Перечислимый тип задается перечислением всех своих элементов, что видно на следующей синтаксической диаграмме:

DEN_NED = (MO, TU, WE, TH, FR, SA, SU);

MONETA = (1, 2, 3, 5, 10, 20, 50).

Диапазонный тип представляет собой подмножество одного из ординальных типов. Его часто называют еще интервальным.

ÍÀÏÐÈÌÅÐ:

DEN_MES = 1..31;

RAB_DEN = MO..SA;

LATBUKW = 'A'..'Z'.

ЗАМЕЧАНИЕ . Все типы, рассмотренные ранее, включая перечислимый и символьный, называются скалярными. Величины, принадлежащие скалярному типу, - упорядочены (не путать с ординальностью):

3 < 5; 1.2 > -6.8; 'A' < 'C'; true > false; MO > TH.

2.4 Объявление данных

С помощью объявлений программист сообщает компилятору, какие данные, процедуры и функции пользователя будут задействованы в программе. Описательная часть программы (объявления) состоит из 5 разделов, которые должны располагаться в следующем порядке:

- раздел модулей;

- раздел меток;

- раздел констант;

- раздел типов;

- раздел переменных;

- раздел процедур и функций.

Любой из перечисленных разделов может в объявлении отсутствовать.

Раздел описания модулей начинается со служебного слова USES, за которым идет перечень используемых в программе модулей типа CRT, DOS, GRAPH и др. Все эти модули находятся в библиотеке модулей и каждый из них поддерживает соответствующий набор встроенных процедур и функций.

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

LABEL 342,11,1445;

Раздел определения констант начинается со служебного слова CONST. Определение каждой константы содержит идентификатор (имя) константы, знак равенства и значение. Определения отделяются друг от друга точкой с запятой, как показано на диаграмме:

НАПРИМЕР:

const PI = 3.1415927; E = 2.7182818; Z = 'информатика'.

Таким образом, определенные константы принято называть именованными, в отличие от обычных констант программы.

Раздел определения типов начинается со служебного слова TYPE.

Каждому определенному типу соответствует имя. Данный раздел применяется для описания нестандартных типов (перечислимых, диапазонных и др.).

НАПРИМЕР:

type COLOR = (black, white, blue, green, red);

DEN = 1..31;

За разделом типов следует раздел описания переменных. Этот раздел начинается со служебного слова VAR. При объявлении переменных компилятору указывается, сколько переменных используется в программе, какие имена у них и данные, какого типа будут храниться в этих переменных.

НАПРИМЕР:

var X,Y,Z Ж integer; AD1,AD2: real;

TEXT: char; Q: DEN; D: 17..76.

Как видно из примера, интервальный тип не обязательно описывать в разделе TYPE, а достаточно это сделать в настоящем разделе. Это замечание касается и других типов данных, о которых речь пойдет позднее.

3. ПРОСТЫЕ ОПЕРАТОРЫ. ВВОД/ВЫВОД ДАННЫХ

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

Синтаксис оператора есть правило его описания, которое может быть задано либо в виде общей формы записи оператора, либо в виде синтаксической диаграммы. Синтаксическая диаграмма задает и семантику оператора, т.е. определяет те действия, которые заложены в этом операторе, и порядок выполнения этих действий. Для некоторых сложных операторов помимо синтаксической диаграммы необходимо давать дополнительные пояснения по их семантике.

Различают простые и структурные операторы. Простым оператором является такой оператор, который не содержит в себе других операторов. В простом операторе определяется, как правило, одно элементарное действие. В Паскале имеются 3 простых оператора: присваивания, процедуры и перехода. Структурные операторы подразделяются, в свою очередь, на составные, условные, цикла и операторы над записями. Структурный оператор включает в себя другие операторы (как простые, так и составные). Существует несколько способов формирования структурных операторов, о которых речь пойдет в разделе "Структурные операторы".

3.1 Оператор присваивания и выражения

Оператор присваивания относится к простым операторам и его синтаксис, и семантика определяются следующей синтаксической диаграммой:

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

а) переменные выражения получают свои значения;

б) вычисляется значение выражения;

в) переменной присваивается полученное значение.

В простейшем случае, когда выражение задано константой или другой переменной, вычислений не производится и переменная сразу получает свое значение.

НАПРИМЕР:

RAZN:= A - 3.5;

N:= 25; C:= D; Y:= 'программа';

L:= true; P:= X > 10.

В языке Паскаль существует несколько типов выражений: арифметические, литерные, логические (булевские). В этом пункте мы рассмотрим только арифметические выражения.

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

В Tурбо-Паскале определены следующие операции над числами:

*, /, +, -, DIV, MOD, где DIV - деление нацело, MOD - вычисление остатка от деления.

Приоритет: *, /, DIV, MOD - высший;

+, - - низший.

НАПРИМЕР:

A:=13 DIV 5;(результат: A=2),

B:=13 MOD 5;(результат: B=3).

Выражения арифметического типа включают в себя числовые константы, переменные и математические функции. Каждое арифметическое выражение может иметь типы: INTEGER и REAL. Тип константы определяется самим видом константы; тип переменной задается в ее объявлении.

Тип арифметического выражения определяется по следующему правилу:

а) для операций "*, +, -", результат имеет тип REAL, если один из операндов имеет тип REAL; если оба операнда типа INTEGER, то результат имеет тип INTEGER;

б) для "/" результат всегда имеет тип REAL;

в) для "DIV, MOD" операнды и результат имеют тип INTEGER.

Значение переменной интервального типа, образованной на основе INTEGER, всегда имеет тип INTEGER. При использовании оператора присваивания нужно соблюдать типизацию объектов слева и справа от знака ":=". Смешение типов недопустимо, за исключением, когда слева от знака ":=" может стоять тип REAL, а справа - тип INTEGER.

В Паскале при написании выражений используются стандартные функции, которые разделяются на следующие виды.

1. Арифметические (математические) функции:

а) ABS(X), X - REAL и INTEGER, на выходе тот же тип;

б) ARCTAN(X), COS(X), SIN(X), EXP(X), LN(X), SQR(X), SQRT(X).

Для этих функций X есть REAL или INTEGER, а результат всегда REAL.

2. Функции преобразования типов:

а) CHR(X), где X - INTEGER;

Результат - символ, кодом которого является число X.

Например: CHR(65) = 'А'.

б) ORD(X), где X - CHAR;

Результат - число типа INTEGER.

Например: ORD('А') = 65.

Эту функцию можно использовать в определении номера элемента в перечислимом типе. Например, пусть имеется фрагмент программы:

type DAY=(mo,tu,we,th,fr,sa,su);

var DEN: DAY;

DEN:=tu; I:=ORD(DEN);

Значением переменной I будет 1, т.к. нумерация начинается с нуля.

в) ROUND(X), где X - REAL;

Результат INTEGER - ближайшее целое к X.

г) TRUNC(X), где X - REAL.

Результат INTEGER - целая часть X.

НАПРИМЕР:

TRUNC(5.8)=5; ROUND(3.14)=3;

ROUND(5.8)=6; TRUNC(-7.7)=-7;

TRUNC(3.14)=3; ROUND(-7.7)=-8.

Функцию ROUND можно выразить через TRUNC следующим образом:

-

¦ TRUNC(X+0.5), если X Є 0;

ROUND(X)={

¦ TRUNC(X-0.5), если X < 0.

L

3. Функции упорядоченных типов:

а) PRED (N) - предшествующий N элемент;

Функция не определена, если N - первый по порядку элемент.

Например: PRED(TU)=MO.

б) SUCC(N) - следующий за N элемент.

Функция не определена, если N - последний элемент типа.

Например: SUCC(MO)=TU.

в) ODD(I), где I - INTEGER, результат - BOOLEAN;

Если I - четное, то значение TRUE;

Если I - нечетное, то значение FALSE.

Эти функции работают в области упорядоченных (ординальных) скалярных типов, т.е. всех простых типов, исключая REAL.

3.2 Операторы процедур. Ввод/вывод информации

Оператор процедуры определяет активизацию процедуры, обозначенную с помощью идентификатора (имени) процедуры. Другими словами, с помощью операторов этого типа осуществляется вызов процедур с указанием в них входных и выходных параметров (подробнее об этом будет сказано в разделе "Процедуры"). Мы начнем знакомство с операторами-процедурами на базе организации ввода/вывода данных в языке Паскаль.

Для организации ввода и вывода данных используются следующие встроенные (машинные) процедуры: READ, WRITE, READLN, WRITELN.

Процедура READ вызывается с помощью соответствующего оператора процедуры, который описывается в виде следующей синтаксической диаграммы:

ОБЩАЯ ФОРМА ЗАПИСИ:

READ(X,Y,..., Z),

где X,Y,..., Z - переменные, называемые списком ввода.

При выполнении процедуры READ работа программы приостанавливается, ЭВМ ждет ввода информации. Пользователь должен с клавиатуры ввести значения переменных, указанных в списке, отделяя их одним пробелом. Ввод завершается нажатием клавиши ENTER. Можно нажимать клавишу ввода и после набора каждого элемента ввода. В этом случае каждое нажатие клавиши ENTER осуществляет присваивание очередной переменной списка ввода ее значения, набранного с клавиатуры. По завершению ввода программа возобновляет свою работу.

Для лучшего понимания работы данной процедуры и ее умелого использования при задании значений нескольких переменных необходимо знать, что при вводе значений с клавиатуры они сначала идут в буфер клавиатуры, а потом считываются в переменные. При считывании буфер очищается по принципу очереди (первым зашел - первым вышел). Это означает, что при вводе сразу нескольких констант и последующем нажатии клавиши ENTER из буфера клавиатуры будет считано столько констант, сколько переменных в операторе READ, а остальные останутся в буфере. При следующем операторе READ остановки работы ЭВМ не будет, и его переменные получат свои значения из буфера (если только в нем достаточно констант для всех переменных).

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

READ (A,B,C);

READ (D,E);

и пусть по первому оператору на клавиатуре набраны 5 констант. Тогда при работе второго READ останова работы программы не будет, и переменные C и D получат значения последних двух ранее введенных констант. Если же ввести 4 константы, то второй оператор READ затребует еще одну константу с клавиатуры.

Вызов процедуры READLN имеет тот же синтаксис, что и оператор READ, однако ее работа отличается от работы первой процедуры. При однократном вводе констант отличий нет, а при одноразовом вводе нескольких констант происходит очистка буфера клавиатуры. Так, если в нашем примере заменить первый READ на READLN, и тоже ввести сразу 5 констант, то второй оператор READ произведет остановку работы программы и затребует повторного ввода последних двух значений для переменных D и E. Заметим также, что оператор READN используется преимущественно при вводе текстовых констант.

Эти процедуры служат для вывода на экран констант (как числовых, так и текстовых), значений переменных и выражений. Они вызываются с помощью одноименных операторов процедур.

НАПРИМЕР: WRITE ('программа', X, Y-Z*3).

По этому оператору на экране будет напечатано в одной строке слово "программа" и далее без пробелов значения переменной X и выражения Y-Z*3. Чтобы отделить элементы вывода друг от друга, используется прием форматирования вывода. Так, WRITE(А:20) показывает, что значение переменной А занимает 20 позиций на экране. Если в А входит менее 20 символов, то они сдвигаются вправо, а слева строка заполняется пробелами.

Двойное форматирование используется только для вывода вещественных констант. Например, WRITE(C:17:7) означает, что для вывода C отведено всего 17 позиций, из них 7 позиций предназначены на представление дробной части. Если формат не указан, то вещественные константы выводятся на экран в экспоненциальной форме.

Работа оператора WRITE отличается от работы оператора WRITELN тем, что по завершению вывода у WRITE курсор остается в конце списка вывода, а у WRITELN он переходит на следующую строку. Часто используют оператор WRITELN без списка вывода для печати пустой строки.

Проиллюстрируем работу всех операторов на следующем примере:

program AVERAGE;

var FIRST, SECOND, TROIS, SUM: integer;

begin

writeln(' Введите 3 числа ');

readln(FIRST,SECOND,TROIS);

SUM:= FIRST + SECOND + TROIS;

writeln(' Среднеезначение ',FIRST:4,',',SECOND:4,',');

write(TROIS:4,' равно ';(SUM div 3):3)

end.

На экране будет напечатано:

Ввести 3 числа
2 12 9
среднее значение 3, 12, 9
равно 8

ПРИМЕЧАНИЕ. Мы рассмотрели только два вида операторов процедур READ и WRITE. Более подробно об операторах процедур речь пойдет в разделе "Процедуры".

3.3 Оператор перехода GOTO

GOTO - это оператор безусловного перехода на оператор или процедуру с указанной меткой. Все метки должны быть описаны в объявлении в разделе LABEL.

При использовании оператора перехода необходимо соблюдать следующие правила:

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

2. Метка, которая указывается в операторе перехода, должна находиться в том же блоке, что и сам оператор перехода. Другими словами, недопустим выход из блока или вход внутрь него.

3. Íåäîïóñòèì âõîä âíóòðü ñòðóêòóðíîãî îïåðàòîðà.

label 12;

12: write('Введитезначение N > 0 '); readln(N);

iF N <= 0 then goto 12;

Не рекомендуется часто применять оператор GOTO, т.к. это ведет к нарушению структурности программы. Наиболее распространенным случаем использования оператора GOTO является обработка ошибки неправильного ввода данных с клавиатуры, как это показано в рассмотренном примере.

4. СТРУКТУРНЫЕ ОПЕРАТОРЫ. ОРГАНИЗАЦИЯ ВЕТВЛЕНИЙ И ЦИКЛОВ

Структурные операторы строятся из других операторов по определенным правилам. Операторы, входящие в структурный оператор, выполняются последовательно в составных операторах и операторах над записями, альтернативно в условных операторах, многократно в операторах цикла.

4.1 Составной и пустой операторы

При формировании структурных операторов существуют некоторые ограничения на число входящих в него операторов. В частности, в операторе выбора IF (в школьном алгоритмическом языке команда "если"), после служебного слова THEN (аналог - "то") может стоять только один оператор. Поэтому в Паскале возникла необходимость группирования операторов в единое целое - в один составной оператор.

Любая группа операторов, размещенных между словами BEGIN и END (иначе, операторные скобки), рассматривается как один - составной оператор. При выполнении составного оператора все его компоненты (операторы) выполняются в порядке их написания (линейно).

Составные операторы обрабатываются как единое целое, как один оператор, что имеет значение там, где синтаксис языка допускает использование одного оператора. Это имеет место практически во всех структурных операторах. Примеры использования составных операторов будут рассмотрены в соответствующих разделах данного пособия.

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

Однако практика показывает, что иногда полезно иметь такое средство, например, при выполнении искусственной задержки выполнения программы:

FOR I:=1 TO 10000 DO;

При выполнении данного цикла машина переменной I последовательно присвоит значения от 1 до 10000. В теле цикла нет операторов, значит, кроме счета ничего не будет выполнено, однако время на это затрачивается, и, следовательно, некоторое время программа "висит" на данном операторе.

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

4.2 Организация ветвлений. Операторы выбора

Оператор IF можно представить в виде следующей синтаксической диаграммы:

Конструкция "Условие" есть логическое выражение, которое принимает два значения типа BOOLEAN: TRUE, FALSE (истинно или ложно).

Само выражение (логическое) складывается из операций сравнения >, >=, <, <=, =, <>. Результат сравнения может быть TRUE или FALSE.

Логические выражения могут формироваться также и с помощью трех логических операций: NOT, AND, OR. Приоритеты операций:

Высший: ()

NOT *, /, DIV, MOD

AND

OR +, -

Низший: >, =, <, >=, <>, <=

В качестве условия может быть использована и логическая переменная.

Например:

I and J or K ---> (I and J) or K;

not X and Y ---> (not X) and Y,

где I, J, K, X, Y переменные типа BOOLEAN;

(A<B) or (B=0), где A,B - переменные простого типа.

В операторе IF всегда за словами THEN и ELSE должен следовать один оператор. Если хотя бы один из них является оператором IF, то полученную конструкцию называют вложением.

ПРИМЕР:

IF <условие1> THEN

<ветвь 1>

ELSE

IF <условие2> THEN

<ветвь 2>

ELSE

<ветвь 3>

Такое вложение используется для уменьшения числа необходимых проверок. Этот метод часто обеспечивает большую эффективность, чем составное условие, однако одновременно он уменьшает надежность программы. Не рекомендуется использовать более двух-трех уровней вложения IF. Вложения могут идти и после слова THEN. Ниже следуют два способа вложения конструкции IF в конструкцию IF:

1 способ 2 способ
IF c1 THEN IF c1 THEN
s1 IF c2 THEN
ELSE IF c2 THEN IF c3 THEN
s2 ELSE s2
ELSE IF c3 THEN ELSE s3
ELSE s4 ELSE s4

Первый способ предпочтительнее, чем второй, т.к. конструкция THEN-IF менее удобна, чем ELSE-IF. С помощью конструкции ELSE-IF чаще всего осуществляется выбор одного из нескольких альтернативных путей. Заметим, однако, что иногда такое вложение лучше заменить на последовательность короткой формы оператора IF-THEN. Этовиднонаследующемпримере:

program QUARD;

var A,B,C: real; DETER: real;

begin

read(A,B,C); DETER:= sqr(B)-4*A-C;

1 вариант 2 вариант
if DETER<0 then if DETER<0 then
write('Не имеет корней'); write('Нет корней');
if DETER=0 then else
write('Один корень'); if DETER=0 then
if DETER>0 then write('Один корень');
write('Два корня'); else
write('Два корня');

end

однако в Паскале существует оператор, позволяющий сделать выбор из нескольких альтернатив - множественное ветвление.

Оператор варианта состоит из выражения и списка операторов, каждому из которых предшествует одна или более констант, называемых константами выбора, что видно из синтаксической диаграммы:

ОБЩАЯ ФОРМА ЗАПИСИ:

CASE <выражение> OF

константы: оператор;

константы: оператор

ELSE < оператор >

END.

Выражение, стоящее между CASE и OF, называется селектором.

Константы (значения выражения), предшествующие двоеточию, называются метками случаев. Порядок работы оператора - сначала вычисляется значение селектора, затем выполняется оператор, метка которого совпадает со значением селектора. Все остальные операторы не выполняются, управление передается на следующий после END оператор. Если же в операторе есть строка ELSE, то при несовпадении значения селектора ни с одной константой выполняется оператор, следующий за ELSE.

Выражение "селектор" может относиться к любому скалярному типу, кроме REAL. Метки случаев должны принадлежать тому же типу, что и селектор. Недопустимо, чтобы одна и та же метка появлялась более одного раза в операторе CASE.

Оператор CASE особенно удобно использовать, во-первых, когда характер действий определяется значениями, которые не попадают в последовательно расположенные интервалы, во-вторых, когда нескольким дискретным значениям селектора соответствует одно и то же действие.

ПРИМЕР 1. Печать названия десятичных цифр

program DICITS;

var DIGIT: integer;

label 1;

begin

1: writeln ('Введитецифру');

readln(DIGIT);

if (DIGIT < 0) or (DIGIT > 9) then

begin

writeln ('Это не цифра');

GOTO 1

end

else

case DIGIT of

0: writeln('нуль');

1: writeln('один');

9: writeln('девять');

end;

end.

ПРИМЕР 2. Печать номера квартала года

program NUMKVART;

var MESIATZ: 1..12;

begin

write('Введите номер месяца года - '"; read(MESIATZ);

case MESIATZ of

1,2,3: writeln('Первыйквартал');

4,5,6: writeln('Второй квартал');

7,8,9: writeln('Третий квартал');

10,11,12: writeln('Четвертый квартал');

end;

end.

ПРИМЕР 3. Вывод на печать, является ли введенный с клавиатуры символ гласной буквой или знаком препинания

program SIMVOL;

var CH: char;

begin

write('Введитесимвол - '"; readln(CH);

write (CH,' есть ');

case CH of

'A','E','I','O','U': write('гласная');

'.',';',',',':','?','!': write('знак препинания');

end;

end.

ЗАМЕЧАНИЕ. В операторе CASE нет условий как таковых, однако проверка условий осуществляется в неявном виде. Действительно, строке

'A','E','I','O','U': WRITE('гласная')

примера 3 равносилен оператор

IF (ch='A") OR (ch='E') OR (ch='I') OR (ch='O') OR (ch='U')

THEN WRITE(' гласная').

4.3 Организация циклов. Операторы повторения

Оператор цикла задает повторное выполнение определенных операторов. Для реализации циклов в Паскале предусмотрены три различных структурных оператора: WHILE, REPEAT, FOR. Первые два используются, если число повторений (итераций) заранее не определено, но известно условие завершения цикла. Оператор FOR применяется тогда, когда число повторений тела цикла известно.

Этот оператор является наиболее мощным из всех трех, реализующих циклы. Два других оператора можно выразить с его помощью.

Логическое выражение, стоящее после WHILE, называется условием возобновления цикла и должно иметь булевский тип. Оператор, следующий за DO, является телом цикла. Он повторяется до тех пор, пока истинно условие возобновления цикла. Как только условие возобновления цикла становится ложным, управление переходит к оператору, стоящему за WHILE. Если условие возобновления не удовлетворяется до начала выполнения цикла, то тело цикла пропускается.

Из указанного описания видно, что оператор WHILE реализует базовую структуру "цикл - пока", т.к. здесь проверка условия идет до тела цикла. Поэтому оператор WHILE называют оператором цикла с предусловием.

Рассмотрим пример программы вычисления для данного аÎ0 по известной итерационной формуле:

×

Здесь надо организовать циклический процесс и остановиться тогда, когда

,

где Е - заданная точность приближения.

program SQUR;

var A,X,EPS: real;

begin

write('Введите число ');

readln(A);

write('Введитеточность ');

readln(EPS);

X:=(A+1)/2;

while abs(sqr(X)-A) >= EPS do

X:=0.5*(X+A/X);

write('Кореньиз ',A,' = ',X);

end.

ЗАМЕЧАНИЕ. Грамотное использование оператора WHILE предполагает умение правильно написать условие возобновления цикла. Здесь надо иметь в виду следующие рекомендации:

а) в условии, как в логическом выражении, должны обязательно фигурировать переменные, изменяющие свои значения в теле цикла;

б) во избежание зацикливания лучше сначала написать условие прекращения цикла и взять потом в оператор его отрицание;

в) переменные логического выражения должны получить свои исходные значения до входа в оператор WHILE.

Оператор REPEAT называют оператором цикла с постусловием, т.к. здесь выражение, управляющее повторным выполнением последовательности операторов, помещается после тела цикла.

В этом операторе тело цикла выполняется до тех пор, пока ложно условие, стоящее после UNTIL. Условием выхода из цикла является истинность выражения. Мы видим, что это есть форма "цикла-до".

ПРИМЕР. Даны числа A, B (A>1). Получить все степени числа A, меньшие числа B

program STEPENI;

var A,B,C: real;

begin

readln(A,B); C:=A;

repeat

writeln(C);

C:= C*A;

until C >= B;

end.

ПРИМЕЧАНИЕ. Между операторами WHILE и REPEAT существуют три основных различия:

1. В операторе REPEAT проверка условия выхода из цикла выполняется в конце, а не в начале цикла, как в операторе WHILE. Поэтому в операторе REPEAT тело цикла выполняется хотя бы один раз.

2. В REPEAT выход из цикла осуществляется по истинности условия, а в WHILE - по ложности.

3. В операторе WHILE тело цикла чаще всего имеет форму составного оператора, в операторе REPEAT для организации тела цикла операторные скобки не нужны.

Он предназначен для организации циклов, когда заранее известно, сколько раз должно повториться тело цикла. Здесь управление числом повторений осуществляется с помощью специальной переменной - параметра цикла (управляющей переменной), которой присваивается возрастающая (убывающая) последовательность значений.

Здесь "переменная" есть параметр цикла, "выражение 1" - начальное значение параметра, "выражение 2" - его конечное значение. В качестве управляющей переменной должен использоваться идентификатор переменной, объявленный локальной в блоке, который содержит данный оператор FOR. Управляющая переменная должна иметь ординальный тип. Начальное и конечное значения имеют тип, совместимый с типом параметра цикла.

Когда начинает выполняться оператор FOR, начальное и конечное значения определяются один раз, и эти значения сохраняются на протяжении всего выполнения оператора.

Оператор, который содержится в теле оператора FOR, выполняется один раз для каждого значения управляющей переменной в диапазоне между начальным и конечным значениями. Управляющая переменная всегда инициализируется начальным значением. Она принимает все свои значения из диапазона с шагом 1, если TO, и с шагом -1, если DOWNTO. В случае TO, если начальное значение превышает конечное, то тело цикла не выполняется.

Для случая DOWNTO это имеет место, когда начальное значение меньше, чем конечное. Отсюда заключаем, что оператор цикла FOR реализует, как и WHILE, схему цикла "пока".

ПРИМЕЧАНИЕ:

1. Если тело цикла в этом операторе состоит из более одного оператора, то они все заключаются в операторные скобки (реализуют конструкцию составного оператора).

2. В отличие от школьного алгоритмического языка, цикл FOR нельзя прервать путем присваивания управляющей переменной ее конечного значения. Изменения переменной цикла не влияют на число повторений тела цикла. Для досрочного выхода из цикла используют оператор GOTO.

3. После выполнения оператора значение управляющей переменной становится неопределенным, если только выполнение оператора FOR не было прервано с помощью оператора перехода.

Рассмотрим примеры использования оператора FOR для организации циклических процессов:

ПРИМЕР 1. Печать отсчета цифр при старте

program START;

var SEC: integer;

begin

writeln ('До старта осталось...');

for SEC:=10 downto 1 do

writeln (SEC:4);

writeln ('ноль'); writeln ('Старт !!')

END.

В данном примере управляющая переменная SEC принимает значения типа INTEGER, однако в Паскале она определена как переменная ординального типа, следовательно, может принимать значения типа CHAR или принадлежать перечислимому типу, как показано в следующем примере:

ПРИМЕР 2. Подсчет числа часов рабочей недели

program WORKTIME;

type DAYS = (MO,TU,WE,TH,FR,SA,SU);

var DEN: DAYS; WT: integer;

begin

WT:=0;

for DEN:= MO to SA do

if DEN <> SA then WT:= WT+8

else WT:= WT+7; writeln (WT)

end.

5 . ОРГАНИЗАЦИЯ ПОДПРОГРАММ. ПРОЦЕДУРЫ И ФУНКЦИИ

Мы уже видели, что Паскаль-программа состоит из последовательности операторов (простых и структурных). Есть в языке также понятие функции. Система Turbo-Pascal имеет целый набор встроенных машинных функций. Однако Паскаль предоставляет возможность создавать новые операторы и функции на основе стандартных. Такие процедуры и функции принято называть пользовательскими.

В Паскале два типа подпрограмм - процедуры и функции. Создание процедур и функций является дальнейшим развитием идеи группирования, когда несколько операторов объединяются в программный блок, который имеет свое имя и к которому можно обратиться по этому имени с помощью соответствующих средств вызова. Процедура вызывается с помощью оператора процедуры с одноименным именем. Функция пользователя также имеет свое имя и ее активизация происходит при вычислении выражения, содержащего имя функции, и возвращаемое функцией значение подставляется в это выражение.

5.1 Процедуры и их типизация

Итак, процедура - это часть программы (подпрограмма), имеющая имя и предназначенная для решения некоторой частной задачи (подзадачи). Они делятся по способам описания и обращения к ним следующим образом:

Процедура встроенная (машинная) - процедура в программировании, описание которой считается известным транслятору, в связи с чем ее можно использовать без описания.

Процедура пользователя - процедура, которую создает (описывает) программист на основе имеющихся операторов и встроенных процедур и функций данного языка по определенным правилам данного компилятора.

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

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

Процедура с параметрами-переменными - процедура, не требующая начальных значений, однако передающая в основную программу результаты своей работы (передает значения некоторых переменных).

Комбинированная процедура - процедура, имеющая параметры-переменные и параметры-значения, т.е. входные и выходные данные.

Эти процедуры являются составной частью системы программирования. Среди этих процедур есть стандартные процедуры, которыми можно пользоваться в любом месте программы без какого-либо предварительного объявления. Сюда относятся уже ранее упомянутые процедуры ввода/вывода, управления работой программы, динамического распределения памяти, строковые процедуры и пр. Полный перечень встроенных процедур можно найти в справочнике для языка.

Помимо стандартных процедур в Паскале есть также стандартные модули, представленные в виде TPU - файлов, каждый из которых содержит в себе целый набор процедур и функций. Для того, чтобы использовать процедуры из модулей, необходимо вызвать нужный модуль в разделе USES. Система Turbo-Pascal имеет модули PRINTER, DOS, CRT, GRAPH и др.

CRT - позволяет использовать все возможности дисплея и клавиатуры, включая управление режимом экрана, расширенные коды клавиатуры, цвет, окна и звуковые сигналы.

DOS - поддерживает различные функции ДОС, включая установку и получение текущего значения даты и времени, поиск по каталогам файлов и выполнение программ.

PRINTER - позволяет легко организовать доступ к устройству печати.

GRAPH - мощный графический пакет с набором процедур и функций обработки графических объектов (точек, отрезков, прямоугольников, окружностей и пр.).

Рассмотрим несколько примеров встроенных процедур:

1) CLRSCR - процедура очистки экрана. Результатом работы является стирание всей информации с экрана. Данная процедура является примером процедур без параметров;

2) GOTOXY(A,B) - процедура позиционирования курсора на экране дисплея в точку с координатами (A,B). A и B являются входными данными, следовательно, это пример процедуры с параметрами-значениями;

3) WRITE([A],[B],.....,[Q]) процедура вывода информации на экран дисплея. Данная процедура - процедура с параметрами-значениями.

4) READ([A],[B],....,[Q]) процедуры ввода информации в ЭВМ.

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

В основной программе все процедуры (а также и функции) пользователя должны быть объявлены. Объявление процедур и функций осуществляется после объявления переменных и перед первым словом BEGIN программы.

Заголовок программы;

Описание модулей USES;

Описание меток LABEL;

Описание констант CONST;

Описание типа TYPE;

Описание переменных VAR;

Описание процедур и функций PROCEDURE/FUNCTION;

BEGIN

Тело программы (блок);

END.

Процедура, как видно из ее определения, оформляется так же, как и основная программа. Вообще процедуру нужно воспринимать как программу в миниатюре. В свою очередь основная программа может быть легко переделана в процедуру с заменой слова PROGRAM на PROCEDURE. Если процедура объявлена, ее можно использовать в последующих частях программы, просто задавая ее имя, за которым, если необходимо, следует список параметров. Вызов процедуры для основной программы становится новым оператором. Обращение к процедуре активизирует эту процедуру, то есть приводит к выполнению группу операторов, содержащихся в ее теле. После этого управление переходит к оператору, следующему за вызовом процедуры.

Описание процедур будем рассматривать по той же схеме, что и машинные процедуры, а именно: вначале без параметров, далее с параметрами - значениями, с параметрами - переменными и, наконец, комбинированные процедуры.

Заголовок процедуры без параметров можно описать в виде:

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

Рассмотрим несколько примеров, в которых представлены эти варианты.

ПРИМЕР 1. Нарисовать три вертикальных квадрата 3х3 с помощью символа "*".

Очевидно, что в этой программе надо выделить рисование квадрата в виде процедуры без параметров, а затем трижды вызвать ее в основной программе:

program RISUNOK;

procedure KVADRAT;

begin

¦ writeln('***');

¦ writeln('* *');

¦ writeln('***');

end;

begin

¦ clrscr; KVADRAT;

¦ writeln; KVADRAT; writeln; KVADRAT;

end.

ПРИМЕР 2. Вычислить площадь четырехугольника ABCD

Зная длины сторон четырехугольника и длину одной из его диагоналей, например BD, можно найти по формуле Герона площади двух вспомогательных треугольников и сложить их. Отсюда следует, что в программе надо выделить процедуру вычисления площади треугольника:

program PLOCHAD_1;

var AB,BC,CD,AD,BD,S1,S,a,b,c,p:real;

procedure GERON_1;

begin p:=(a+b+c)/2;

S:=sqrt(p*(p-a)*(p-b)*(p-c));

end;

begin {*ОСНОВНАЯПРОГРАММА*}

read (AB,BC,CD,AD,AC);

a:=AB;b:=AD;c:=BD; GERON_1; S1:= S;

a:=BC;b:=CD;c:=BD; GERON_1; S1:= S1+S;

write(S1);

end.

Прежде чем переходить к рассмотрению процедур с параметрами, необходимо определить сами эти понятия. Сделаем это на примере программы вычисления площади четырехугольника (пример 2).

Рассмотренная программа имеет недостаток в том, что в основной ее части идет ряд присваиваний, которые предшествуют вызову процедуры. Чтобы избавиться от этих присваиваний, вводят процедуры с параметрами. С помощью параметров осуществляется передача информации в процедуру, а также и прием информации из процедуры в основную программу.

Различают формальные и фактические параметры. Формальные параметры - это имена переменных, фигурирующих в самой процедуре. Их описание производится в заголовке процедуры - после имени процедуры в скобках. Среди них имеются параметры-аргументы и параметры-результаты. Эти параметры называют формальными, потому что они служат только для фиксации числа параметров процедуры, их имена могут быть произвольными, смена имен не отражается на ее работе.

Фактические параметры задаются в вызове процедуры, т.е. они принадлежат основной части программы. В отличие от формальных параметров, которые всегда есть переменные, фактические параметры имеют вид:

параметры-аргументы - это константы, переменные, выражения;

параметры-результаты - это всегда переменные.

Рассмотрим заново программу вычисления площади четырехугольника. В ней все переменные объявлены в основной программе. Однако среди них есть переменные, которые задействованы только в процедуре. Это переменные A, B, C, P, S. Такие переменные принято называть локальными по отношению к процедуре. Остальные переменные называются глобальными. Среди локальных переменных можно выделить параметры-аргументы A, B, C и параметр-результат S. Принято формальные параметры описывать в заголовке процедуры с указанием их типа. Локальная же переменная P является вспомогательной и описывается в процедуре так же, как в основной программе.

Порядок следования формальных параметров в заголовке процедуры и фактических параметров в вызове процедуры важен, поскольку определяет, какому параметру будет присвоено то или иное значение.

Все переменные должны быть отнесены к какому-то типу, поэтому необходимо задать тип каждого параметра. Передаваемое значение и соответствующий ему параметр (фактический) должны быть одного и того же типа.

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

В предыдущем разделе уже было дано определение локальных и глобальных переменных. Уточним еще раз, что локальные переменные объявляются внутри блока процедуры и они бывают двух видов: формальные параметры заголовка процедуры и вспомогательные переменные в разделе VAR процедуры. Эти переменные неизвестны основной программе и их использование в основном блоке вызовет сообщение об ошибке.

Глобальные переменные объявляются в разделе VAR основной программы. Глобальные переменные называют глобальными, потому что они могут фигурировать не только в основной программе, но и в теле процедуры. Заметим, что иногда одна и та же переменная может быть и локальной, и глобальной. Например, если переменная J является одновременно локальной и глобальной, то локальная переменная J отличается от переменной J из главной программы. Изменения, происходящие с J в процедуре, не влияют на значение переменной J из главной программы, и наоборот. Это происходит потому, что, несмотря на сходство имен переменных, компилятор отводит локальной переменной J одну ячейку памяти, а глобальной переменной J - другую. Обычно стараются избегать совпадения имен переменных во избежание нежелательных посторонних эффектов, которые могут возникнуть из-за использования одних и тех же имен для разных переменных.

Для ясности рассмотрим пример, связанный с использованием локальных и глобальных переменных:

program NEST;

var A,B:integer;

procedure NESTEGG;

var A,X: char;

begin

A:= "!";

X:= "?";

B:= B+1;

end;

begin { ОСНОВНАЯ ПРОГРАММА }

A:= 0; B:= 100;

NESTEGG;{ ВЫЗОВ ПРОЦЕДУРЫ }

writeln(A,' ',B);

end.

В этой программе X - локальная переменная для процедуры NESTEGG, поэтому основная программа не может ни изменить ее значение, ни обратиться к ней. С другой стороны, переменная B (глобальная) известна и в программе, и в процедуре. Если переменная B является глобальной, т.е. объявлена в главной программе, то все входящие в состав этой главной программы процедуры могут ссылаться к ней, но только в том случае, если в них нет другого объявления для B. Любое объявление имени в процедуре делает недоступным объект, имеющий то же самое имя и объявленный в основной программе. Так, у нас A для основной программы есть INTEGER, но в процедуре есть A типа CHAR. Процедуре NESTEGG недоступна переменная A из главной программы. Все изменения, происходящие с A в процедуре, становятся несущественными при выходе из этой процедуры. Но, поскольку B известна в главной программе, то все изменения с B в процедуре важны и сохраняются после выхода из нее. Итак, будет напечатано: 0 101.

Как было сказано ранее, процедуры с параметрами-значениями требуют входных данных (смотри п. 5.1). Где они записываются и как задаются? На этот вопрос может ответить синтаксическая диаграмма заголовка процедуры:

Здесь под параметром понимают имя переменной, которая является "входной" для процедуры (формальный параметр-аргумент). Этот параметр с синтаксической точки зрения является параметром-значением, при его описании в заголовке процедуры не требуется писать слов VAR. Параметры-значения принимают из основной программы при вызове процедуры свои конкретные значения.

При обращении к процедуре с параметрами-значениями в основной программе фактическими параметрами могут служить как имена переменных (которые описаны и определены выше), так и конкретные значения (константы) и выражения. При обращении необходимо следить за соответствием списка параметров при обращении и описании. Кроме этого, следует строго соблюдать "типизацию" параметров.

Рассмотрим работу процедур такого типа на примерах.

ПРИМЕР 1. Нарисовать квадрат с произвольной длиной стороны в левом верхнем углу (длина стороны задается с клавиатуры).

В этой программе также надо оформить рисование квадрата в виде процедуры, но уже с входным параметром-аргументом - длиной стороны квадрата:

program RISUNOK_2;

var I: integer;

procedure KVADRAT(N: integer);

var J,K: integer;

begin

for J:=1 to N do write('*'); writeln;

for J:=1 to N-2 do

begin

write('*'); for K:=1 to N-2 do write(' ');

writeln('*');

end;

for J:=1 to N do write('*');

end;

begin { Основная программа }

write('Введите длину стороны - ');

readln(I); clrscr; KVADRAT(I);

end.

ПРИМЕР 2. Вычисление площади четырехугольника с применением процедуры с параметрами-значениями:

program PLOCHAD_2;

var AB,BC,CD,AD,AC,S1,S: real;

procedure GERON_2(a,b,c: real);

var P: real;

begin

P:= (a+b+c)/2; S:= sqrt(P*(P-a)*(P-b)*(P-c));

end;

begin {*ОСНОВНАЯПРОГРАММА*}

read (AB,BC,CD,AD,AC); GERON_2(AB,BC,AC); S1:= S;

GERON_2(AD,AC,CD); write ('S = ', S1+S)

end.

В данной программе определена процедура GERON_2 с тремя параметрами-значениями и локальной переменной P. Значение же площади треугольника помещается в глобальную переменную S. При вызове этой процедуры формальные параметры a, b, c замещаются на фактические параметры AB, BC, AC при первом обращении, и на AD, AC, CD - при втором.

Заметим также, что здесь фактические параметры представлены переменными, которые получают свое значение с помощью процедуры READ. Однако, если известны длины сторон треугольника, например, 6, 7, 4, то можно вычислить площадь этого треугольника, вызвав процедуру GERON_2(6,7,4), и получить ответ в переменной S.

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

При детальном ознакомлении с синтаксической диаграммой видно, что параметрам-переменным должно предшествовать служебное слово VAR, причем оно пишется столько раз, сколько различных типов представлено в выходных данных.

Например:

PROCEDURE PRIMER(VAR a,b,c:INTEGER; VAR m:CHAR; VAR i,j:REAL);

При обращении к процедурам с параметрами-переменными фактическими параметрами должны являться имена переменных, которые описаны в основной программе.

ПРИМЕР 1. Обмен значениями переменных A и B

program ZERKALO;

var A,B: integer;

procedure OBMEN(var X,Y: integer);

begin X:= B; Y:= A end;

begin

A:= 1; B:= 2; writeln(A,B);

OBMEN(A,B); write(A,B);

end.

ПРИМЕР 2. Вычисление площади четырехугольника

program PLOCHAD_3;

var AB,BC,CD,AD,AC,S1,S2,a,b,c: real;

procedure GERON_3(var S: real);

var P: real;

begin

P:= (a+b+c)/2; S:= sqrt(P*(P-a)*(P-b)*(P-c));

end;

begin { Основнаяпрограмма }

read (AB,BC,CD,AD,AC);

a:=AB; b:= BC; c:= AC; GERON_3(S1);

a:=AD; b:= AC; c:= CD; GERON_3(S2);

write ('S = ', S1+S2)

end.

Комбинированные процедуры включает в себя входные и выходные данные. В заголовке процедуры выходные параметры предваряются, словом VAR. Порядок следования параметров может быть произвольным.

НАПРИМЕР:

PROCEDURE PRIMER(VAR a,b,c:INTEGER; m:CHAR; VAR i,j:REAL);

Здесь a,b,c,i,j - параметры-результаты (переменные);

m - пераметр-аргумент (значение).

В качестве иллюстрации комбинированных процедур рассмотрим последний вариант вычисления площади четырехугольника:

program PLOCHAD_4;

var AB,BC,CD,AD,AC,S1,S2: real;

procedure GERON_4(a,b,c:real; var S: real);

var P: real;

begin

P:= (a+b+c)/2;

S:= sqrt(P*(P-a)*(P-b)*(P-c));

end;

begin {*ОСНОВНАЯПРОГРАММА*}

read (AB,BC,CD,AD,AC);

GERON_4(AB,BC,AC,S1);

GERON_4(AD,AC,CD,S2);

write ('S = ', S1+S2)

end.

ПРИМЕЧАНИЕ. Для более полного усвоения введенных ранее терминов перечислим на базе последнего примера все виды параметров и переменных:

- глобальные переменные AB, BC, CD, AD, AC, S1, S2;

- локальные переменные a, b, c, S, P;

- формальные параметры a, b, c, S;

a) параметры-значения (аргументы) a,b,c;

б) параметр-переменная (результат) S;

- фактические параметры AB, BC, CD, AD, AC, S1, S2;

a) параметры-значения (аргументы) AB, BC, CD, AD, AC;

б) параметры-переменные (результаты) S1,S2.

Попытка же описать выходной параметр в виде параметра-значения (без слова VAR в заголовке процедуры) приведет к тому, что результат работы процедуры не будет возвращен в основную программу. Это происходит потому, что характер "поведения" параметров-значений и параметров-переменных в процессе работы процедуры различен. Разница эта состоит в том, что преобразования, которые претерпевают формальные параметры-значения в процедуре, не вызывают изменения соответствующих им фактических параметров, в то время как изменения параметров-переменных может изменять значения соответствующих фактических параметров.

Причиной этого феномена является неодинаковое распределение памяти под хранение параметров процедуры. Формальному параметру-значению отводится некоторая область (ячейка) памяти, куда заносится значение соответствующего фактического параметра, вычисленного на момент обращения к процедуре. На этом связь между ними обрывается. Действительно, если фактическим параметром является константа или выражение, как изменения в формальном параметре-значении (а это есть всегда переменная) могут повлиять, например, на выражение.

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

Именно поэтому, объявив в процедуре параметр-результат как параметр-значение, этот результат так и останется в формальном параметре-переменной без его передачи в соответствующий фактический параметр.

5.2 Функции пользователя. Рекурсивные функции

Функция как объект языка Паскаль является другой версией реализации технологии построения программ с использованием структуры группирования. Можно также сказать, что функция есть частный вид определенного типа процедур, а именно, процедур с одним параметром - переменной.

Функция отличается от процедуры только тем, что всегда возвращает в точку вызова одно скалярное значение. При этом функция, как процедура, может содержать параметры-значения или быть без оных. Общая форма записи заголовка функции:

FUNCTION имя (список параметров: тип): тип;

или

FUNCTION имя: тип;

Тип результата есть тип значения функции. Список параметров такой же, что и для процедуры, только здесь все параметры-аргументы. Имя переменной, которая хранит значение функции, совпадает с именем функции.

Итак, заголовок функции отличается от заголовка процедуры не только сменой слова PROCEDURE на FUNCTION, но и удалением из списка параметров параметра-результата с присвоением его типа имени функции:

PROCEDURE <имя процедуры>(аргументы; VAR параметр-результат: тип);

| |

¾¾¾¾¾¬¾¾¾¾¬¾¾¾¾

FUNCTION <имя функции> (аргументы): тип;

Другой особенностью описания функции является наличие в нем хотя бы одного оператора присваивания, в левой части которого стоит имя определяемой функции, а в правой - выражение для вычисления результата функции. Очевидно, что тип этого выражения должен совпадать с указанным в заголовке типом функции.

Вызов функции также отличается от вызова процедуры. Если вызов процедуры осуществляется с помощью специального оператора вызова (оператора процедуры), функция вызывается только с помощью некоторого выражения. Для того чтобы осуществить обращение к функции, необходимо использовать ее имя со списком фактических параметров в каком-либо выражении, тип которого совпадает с типом значения функции. Само же выражение, внутри которого вызывается функция, может быть правой частью оператора присваивания, частью логического выражения и пр.

Известно, что Паскаль имеет набор стандартных функций. Однако этот набор ограничен. Пользователь может по желанию расширить список функций, создав свои функции - функции пользователя. Так, например, в Паскале есть SQR(X) = X2 , а вот функции F(X)= Xn , где n принадлежит множеству целых чисел Z, нет. Используя определенное ранее понятие функции, можно создать для этого универсальную функцию, которая давала бы степени произвольного вещественного числа с любым целым показателем.

Определим вещественную функцию POWER, которая должна иметь два параметра-аргумента - для показателя и для основания степени:

function POWER (FACTOR:real;EXPONENT:integer):real;

var COUNT: integer; TFACTOR: real;

begin

¦ if EXPONENT = 0 then POWER:= 1

¦ else begin

¦ ¦ TFACTOR:= FACTOR;

¦ ¦ for COUNT:= 2 to ABS(EXPONENT) do

¦ ¦ TFACTOR:= TFACTOR*FACTOR;

¦ ¦ if EXPONENT<0 then POWER:= 1/TFACTOR

¦ ¦ else POWER:= TFACTOR

¦ end

end;

Теперь можно эту функцию вызывать следующим образом:

а) РI:=POWER(3.14,1);

б) WRITELN("PI=",POWER(3.14,1):5:2);

в) IF X > 2*POWER(6.2,3) THEN WRITE('ДА');

г) A:= POWER(X,2) + POWER(X,3) + POWER(X,4).

К функциям можно обращаться тремя способами: из тела основной программы, из тела другой функции, из тела самой функции, т.е. функция, может вызывать саму себя. Функции называются рекурсивными, если в описании функции происходит вызов самой себя, а процесс обращения - рекурсией. Продемонстрируем использование рекурсии на примере вычисления значения факториала произвольного натурального числа N.

В математике известно рекурсивное определение факториала:

n! = 1, при n = 0;

n! = (n-1)!×n, при n > 0.

Это рекурсивное определение можно реализовать с помощью соответствующей рекурсивной функции:

function FACTORIAL(VALUE:integer):integer;

begin

iF VALUE=0 then FACTORIAL:=1

else FACTORIAL:= VALUE*FACTORIAL(VALUE-1)

end;

Теперь можно обращаться к этой функции в теле основной программы, как показано в следующем примере:

program FINDFACTORIAL;

var N:integer;

begin

writeln('Введитечисло');

readln(N);

if N<0 then writeln('Нетфакториала')

else writeln('Фактрориал',N,'равен',FACTORIAL(N))

end.

Мы видим, что характерной особенностью построенной функции является наличие в ее теле оператора присваивания.

FACTORIAL:= VALUE*FACTORIAL(VALUE-1), где происходит вызов определяемой функции. Здесь идентификатор FACTORIAL в левой части оператора обозначает имя переменной для хранения значения функции, а в правой - имя вызываемой функции.

Важным моментом при составлении любой рекурсивной функции является организация выхода из рекурсии. В некоторых простых случаях должно существовать не рекурсивное решение. Рекурсивный процесс должен шаг за шагом так упрощать задачу, чтобы в конце концов для нее появилось не рекурсивное решение. В этих функциях должны проверяться значения аргумента для принятия решения о завершении. В нашем случае условием завершения рекурсии является VALUE=0.

При описании рекурсивных функций необходимо хорошо представлять процесс вычислений. Всякая рекурсия состоит из двух этапов: углубление (погружение) внутрь рекурсии и выход из нее. На первом этапе никаких вычислений не производится, а идет только настройка рабочей формулы на конкретные операнды. На втором этапе происходит процесс вычислений по настроенным формулам.

Рассмотрим рекурсивный процесс на примере вычисления факториала для N = 3. Получим следующие шаги:

1) N = 3, где N<>0, следовательно, FACTORIAL:=3*FACTORIAL(2);

2) N = 2, где N<>0, следовательно, FACTORIAL:=2*FACTORIAL(1);

3) N = 1, где N<>0, следовательно, FACTORIAL:=1*FACTORIAL(0);

4) N =0, следовательно, FACTORIAL:=1,

т.е. получили не рекурсивное значение. Углубление в рекурсию закончено, далее пойдет процесс выхода из нее с выполнением необходимых вычислений.

В выражение 1*FACTORIAL(0) вместо FACTORIAL(0) подставляется его значение 1, вычисляется произведение 1*1 и оно становится значением FACTORIAL(1). В выражение 2*FACTORIAL(1) вместо FACTORIAL(1) подставляется значение 1, вычисляется 2*1 и становится значением FACTORIAL(2). В выражение 3*FACTORIAL(2) вместо FACTORIAL(2) подставляется значение 2, вычисляется 3*2 и становится значением переменной FACTORIAL, которая возвращает в основную программу значение 3!.

Весь этот двухэтапный рекурсивный процесс реализуется в памяти ЭВМ с помощью организации в ней стека рекурсии. Дело в том, что для хранения значений переменной N (а значит, и переменной VALUE) отводится не одна ячейка, а стек с именем N. В этот стек последовательно заносятся значения 3, 2, 1, 0, причем значение 0 есть признак конца заполнения стека. Затем начинает работать цикл с телом FACTORIAL:= FACTORIAL * N, где значения N выбираются последовательно из стека в порядке 1,2,3. Исходным же значением переменной FACTORIAL является 1, как значение 0!.

Работа стека представлена на следующей схеме:

Заполнение стека Стек № Вычисление
(углубление) (разуглубление)
FACTORIAL:=1 0 FACTORIAL:=1
FACTORIAL:=1*FACTORIAL(0) 1 FACTORIAL:=1*FACTORIAL
FACTORIAL:=2*FACTORIAL(1) 2 FACTORIAL:=2*FACTORIAL
FACTORIAL:=3*FACTORIAL(2) 3 FACTORIAL:=3*FACTORIAL

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

Данная функция явно носит рекурсивный характер, исходя из ее определения: Xn = 1, если n = 0;

Xn = (Xn -1)*X, если n > 1.

function POWER(FACTOR:real; EXPONENT:integer): REAL;

begin

if EXPONENT < 0

then POWER:=1/POWER(FACTOR,abs(EXPONENT))

else if EXPONENT > 0

then POWER:= FACTOR*POWER(FACTOR,EXPONENT-1)

ELSE POWER:=1

end;

ЗАМЕЧАНИЕ. Помимо рекурсивных функций в языке Паскаль можно определять по тому же принципу и рекурсивные процедуры. Подробно о них будет сказано в следующих разделах, а пока покажем, как рекурсивная функция может быть переделана в рекурсивную процедуру на примере вычисления факториала:

procedure FACTORIAL(VALUE:integer; var F: integer);

begin

iF VALUE=0 then F:=1

else begin FACTORIAL(VALUE-1,F);

F:=F*VALUE

end;

end;

Здесь уже, в отличие от функции FACTORIAL, для вычисления N! необходимо вызвать эту процедуру с помощью оператора процедуры FACTORIAL(N,FN), где FN - переменная для возвращения из процедуры значения N!.

6 . МАССИВЫ. ДАННЫЕ ТИПА ARRAY

Скалярный тип - простой тип данных. Скалярное данное неделимо. Массивы - это структурированные типы данных. Массив состоит из нескольких элементов. Ко всему массиву можно обращаться по его имени. Можно обращаться к его элементу, но для этого надо задать индекс (индексы). Массивы бывают одномерные и многомерные. Для объявления массива необходимо задать типы его индексов и компонент.

Тип компонент массива - это просто тип данных, ассоциированный с каждой компонентой массива. Тип компонент может быть любым REAL, INTEGER, CHAR, BOOLEAN, перечислимым, интервальным. В качестве компоненты массива может быть взят и тип массив.

Тип индекса должен быть одним из упорядоченных типов, т.е. любым скалярным типом, кроме REAL: INTEGER, CHAR, интервальный, перечислимый. Тип индекса определяет границы изменения индекса. Если сделана попытка использовать несуществующую компоненту, то возникает ошибка (ошибка неверного индекса).

6.1 Одномерные массивы

Одномерный массив можно задать двумя способами:

а) с помощью служебного слова TYPE описывается тип массива, а затем с помощью VAR вводится переменная этого типа;

б) с помощью слова VAR сразу описывается переменная типа массив;

Например, объявление массива из 100 элементов типа REAL можно осуществить следующими двумя способами:

а) type R100 = array[1..100] of real;

var A: R100;

б) var A: array[1..100] of real.

Здесь задан массив с именем "А" и его элементы имеют имена: А[1],..., A[100]. Чаще всего для типа индекса используют интервальный тип на основе типов INTEGER и CHAR. Однако можно в качестве индексов брать перечислимый тип.

ПРИМЕР 1. Подсчет числа вхождений букв в текст определенной длины

program COUNTER;

var COUNT: array['a'..'z'] of integer;

CH: char; N: integer;

begin

for CH:= 'a' to 'z' do

COUNT [CH]:= 0; N:= 0;

repeat

read(CH); N:= N+1;

if (CH >= 'a') and (CH <= 'z') then

COUNT [CH]:= COUNT [CH]+1;

until CH = '.';

for CH:= 'a' to 'z' do

writeln(CH, COUNT [CH]:10, COUNT [CH]*100/N:10:2);

end.

ПОЯСНЕНИЕ. В этом примере тип индекса есть интервальный тип на базе типа CHAR, а тип компонент есть целое число. Таким образом, элементы массива - числа, а их индексы - буквы, т.е. число элементов массива равно 26 (число букв латинского алфавита). Рассмотрим теперь случай, когда тип индекса задан перечислимым типом, а компоненты массива представлены компонентами интервального типа на базе типа INTEGER.


ПРИМЕР 2. Присваивание переменной с именем месяца числа дней этого месяца

DAY: Значение элементов
31 28 31 30 31 30 31 31 30 31 30 31
Значение Индексов
JAN FEB MAR APR MAY JUN JUL AUG SEP OKT NOV DEC

program NUMBRDAY;

type MONAT = (JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG,

SEP, OKT, NOV, DEC);

var DAY: array [MONAT] of 28..31; T: MONAT;

begin

for T:= JAN to DEC do

case T of

JAN, MAR, MAY, JUL, AUG, OKT, DEC: DAY[T]:= 31;

APR, JUN, SEP, NOV: DAY[T]:= 30;

FEB: DAY[T]:= 28;

end;

end.

6.2 Многомерные массивы

Для определения позиции элемента в двумерном массиве необходимы два индекса. Любой двумерный массив есть матрица, а матрица есть таблица. Поэтому удобно описывать двумерные массивы путем указания границ изменения индексов (номеров) строк и столбцов.

Например, таблица символов M x N, где M - число строк и N - число столбцов, может быть описана:

var TAB: array[1..M, 1..N] of char.


ОБЩАЯ ФОРМА ЗАПИСИ
VAR <имя>:ARRAY [тип индекса строки, тип индекса столбца]
OF <тип компонент>;

Однако, двумерный массив можно интерпретировать как вектор-столбец, каждый элемент которого в свою очередь является одномерным массивом (вектор - строка). Этот подход к определению двумерного массива влечет его описание с помощью двух строк, где первая содержит описание строки, а вторая - описание столбца:

type LINE = array[1..N] of char;

STOLB = array[1..M] of LINE;

var TAB: STOLB.

Здесь TAB[I] - переменнаятипа LINE, а TAB[I][J] - переменная

типа CHAR.

ОБЩАЯ ФОРМА ЗАПИСИ
TYPE <тип строки>=ARRAY [тип индекса] OF <тип компонент>;
<тип столбца> = ARRAY[тип индекса] OF <тип строки>;
VAR <переменная массива>: <тип столбца(массива)>;

Эти два вида определения массивов задают и два способа обращения к элементам массива: TAB[I,J] - в первом случае и TAB[I][J] - во втором.

Вполне очевидно, что сказанное выше для двумерного массива распространяется и на массивы большей размерности. Например, описание VAR CUBE: ARRAY[1..M, 1..N, 1..K] OF INTEGER определяет задание трехмерного массива целых чисел.

6.3 Способы работы с массивами

Обработка массивов включает в себя, как правило, следующие компоненты: ввод массива (с клавиатуры или с помощью датчика случайных чисел), вывод полученного массива на экран и собственно его обработка. Все эти компоненты рекомендуется оформлять в виде отдельных процедур. При этом надо учитывать следующий фактор: если процедуре (или функции) будет передаваться массив, то надо объявить в ней этот массив как параметр с атрибутом VAR даже в том случае, если значение массива внутри процедуры не изменяется. Это нужно для того, чтобы не тратить времени и памяти на размещение внутри процедуры копии массива. Заметим, что параметр обязательно должен относиться к типу, имеющему имя.

ПРИМЕР 3. Сумма элементов таблицы над верхней диагональю

program SUMMA;

const M =...; {числостроктаблицы}

N =...; {числостолбцовтаблицы}

type LINE = array[1..n] of integer;

TAB = array[1..m] of LINE;

var s,i,j:integer; MAS:TAB;

procedure VVODMASSIV(var MAS:TAB);

begin

¦ for i:=1 to M do

¦ for j:=1 to N do

¦ readln(MAS[i][j]);

end;

procedure VIVODMASSIV(var MAS:TAB);

begin

¦ for i:=1 to M do

¦ begin

¦ ¦ for j:=1 to N do

¦ ¦ write(MAS[i][j]:5,' '); writeln;

¦ end;

end;

procedure OBRABOTKA(MAS:TAB; var SUM:integer);

begin

¦ SUM:= 0;

¦ for i:=1 to M do

¦ for j:=1 to N do

¦ if j > i then SUM:= SUM+MAS[i][j];

end;

begin

¦ VVODMASSIV(MAS); writeln('исходныймассив');

¦ VIVODMASSIV(MAS); OBRABOTKA(MAS,s);writeln;

¦ writeln('сумма элементов = ',s);

end.


7 . ОБРАБОТКА ЛИТЕРНЫХ ВЕЛИЧИН. ДАННЫЕ ТИПА CHAR И STRING

В Паскале, как и в других языках программирования, предусмотрена обработка текстов или строк. Для этой цели в языке существуют два типа данных: SHAR и STRING.

7.1 Тип данных CHAR

Типу данных CHAR соответствуют символьные константы и переменные. Символьная константа есть какой-то символ алфавита, взятый в кавычки. Символьные переменные получают значения символьных констант через оператор присваивания:

ALPFA:='p'; A:='t'; B:='3'; C:=' '; D:=''.

Все символы алфавита образуют множество литер. Каждый символ имеет свой код в ASCII. Это позволяет использовать булевские сравнения: =, <>, <, <=, >, >=.

Данные этого типа описываются с помощью служебного слова CHAR.

Например, переменную ALPFA можно описать VAR ALPFA: CHAR.

ОБЩАЯ ФОРМА ЗАПИСИ: VAR <переменная>: CHAR;

При работе с данными типа CHAR, если у нас есть последовательность символов, существуют два способа ввода этих символов с клавиатуры.

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

ПРИМЕР 1. С клавиатуры последовательно вводятся символы. Признаком конца ввода является точка. Составить программу выбрасывания групп символов, расположенных между скобками (,). Сами скобки тоже выбрасываются

program SKOBKI;

var c: char; i: integer;

begin

¦ i:=0; read(c);

¦ while c <> '.' do

¦ begin

¦ ¦ if c='(' then i:=1

¦ ¦ else if c = ')' then i:=0

¦ ¦ else if i=0 then write(c);

¦ ¦ read(c);

¦ end;

end.

ПОЯСНЕНИЕ. I = 1 означает, что ранее была прочитана левая скобка, которой пока еще не нашлось парной правой. В этой ситуации прочитанные символы не выводятся на экран. В результате работы этой программы на экране будет представлена строка символов. Здесь вся последовательность символов вводится сразу по первому оператору READ, а затем в цикле из буфера клавиатуры выбираются, анализируются и печатаются символы вне круглых скобок. Например, если вводится последовательность "asg(zx)ytr.", то экран будет выглядеть так:

asg(zx)ytr. - результат работы оператора READ;

asgytr - результат работы оператора WRITE.

В этой программе можно было бы использовать оператор READLN, но тогда после набора каждого символа необходимо нажимать клавишу ввода. Кроме того, на экран будет выводиться не строка символов, а столбец, состоящий из вводимых и отпечатанных элементов. Например, при вводе последовательности "asg(zx)ytr." экран уже будет выглядеть так:

a g x t

a | g |) | t

s | (| y | r

s z y r.

Упорядоченность символов языка используется при написании циклов с параметром, где параметр цикла может пробегать буквенные значения.

ПРИМЕР 2. Программа вывода последовательности букв:

a,ab,abc,...,abc...xyz

program SUITE; РАБОТА ПРОГРАММЫ

var c,d: char; a

begin ab

for c:='a' to 'z' do abc

begin abcd

for d:='a' to c do write(d); abcde

writeln(' ');...

end; abcde...xyz

7.2 Массивы литер

В рассмотренных программах все символы вводились последовательно в процессе работы цикла или хранились временно в буфере клавиатуры. Это не всегда удобно. Поэтому в языках делают строки как последовательность литер. Строку можно задать, как массив литер, при этом в качестве длины строки может выступать верхняя граница массива. Например, VAR HAMLET: ARRAY[1..17] OF CHAR.

Здесь HAMLET - массив литер, компоненты которого имеют тип CHAR; индекс имеет нижнюю границу, равную 1, верхнюю - 17. Для ввода строки в массив HAMLET необходимо организовать цикл из 17 повторений. При каждом повторе этого цикла с клавиатуры вводится очередной символ строки и нажимается клавиша ввода:

Поскольку массивы литер являются обычными массивами, но их компоненты имеют тип CHAR, то они обладают всеми свойствами регулярных массивов. Для извлечения из массива-строки отдельного символа необходимо использовать индекс этого элемента. Например, можно вывести на экран строку HAMLET в обратном порядке с помощью следующего цикла:

for n:=17 downto 1 do write (HAMLET [n]).

ПРИМЕР 3. Дана последовательность символов CHAR: S1,S2,...,S10. Определить, совпадает ли начальная часть с ее конечной частью

program SOWPADENIE;

label 1;

type t = array[1..5] of char;

var s:t; y:char; i:integer;

begin

¦ for i:=1 to 5 do read(s[i]); readln;

¦ for i:=1 to 5 do

¦ begin

¦ ¦ read(y);

¦ ¦ if s[i] <> y then

¦ ¦ begin

¦ ¦ ¦ write('несовпадает');

¦ ¦ ¦ goto 1;

¦ ¦ end;

¦ end;

¦ write('совпадает'); 1:;

end.

ПОЯСНЕНИЕ. В данной программе сначала вводятся по циклу первые пять членов последовательности в массив S[I], причем все пять символов набираются сразу и набор завершается клавишей ввода. Затем с помощью оператора READLN очищается буфер клавиатуры, куда оператор READ(Y) заносит следующие пять символов. Во втором цикле из этого буфера поочередно выбираются символы и сравниваются с ранее введенными. Если все символы совпадают, то печатается текст 'совпадает'. В случае несовпадения печатается текст 'не совпадает' и дальнейшее считывание символов из буфера клавиатуры прекращается.

Чтобы не вводить всю вторую половину символов, а ограничиться только вводом до первого несовпадающего символа, необходимо в программе заменить оператор READ(Y) на оператор READLN(Y).

7.3 Тип данных STRING

Наряду с тем положительным, что дают нам массивы литер, они обладают существенным недостатком: их длину нельзя менять во время выполнения программы. Так в рассмотренном примере пункта 2.2 переменная HAMLET есть массив из 17 элементов и, следовательно, туда можно поместить только текст, содержащий ровно 17 символов.

Это не всегда удобно. Хотелось бы иметь такую переменную, в которую можно было бы поместить текст произвольной (но ограниченной) длины. Такую возможность предоставляет тип STRING. Здесь, объявив переменную:

var HAMLET: string[17],

можно ей путем оператора присваивания (а не через цикл) задать значение текста произвольной длины (от 0 до 17).

НАПРИМЕР:

HAMLET:= 'Быть или не быть';

HAMLET:= 'Бедный Йорик';

HAMLET:= ' '; HAMLET:= ''.

Отметим также, что при компиляции программы в случае объявления строки-массива в памяти ЭВМ резервируется место под массив, который должен быть полностью заполнен потом в процессе работы программы. Для типа STRING также резервируется место в памяти того же объема, но здесь необязательно его заполнять целиком. Незаполненные места представлены пробелами.

ОБЩАЯ ФОРМА ЗАПИСИ:
TYPE <имятипа> = STRING [N];
VAR <имя переменной>: <имя типа>;
или
VAR <имя переменной>: STRING [N];

Здесь N - целая константа, задающая максимальную длину текста.

Доступ к элементам строки производится с помощью индексов, т.к. в этом типе также все элементы имеют свой (числовой) индекс от 1 до N. В результате получается величина типа CHAR.

НАПРИМЕР: HAMLET:= 'ПРОГРАММА';

HAMLET [1] = 'П'; HAMLET [9] = 'А'.

Строковое выражение состоит из строковых (символьных) констант, переменных, указателей строковых функций и операции конкатенации (склеивания) строк, обозначаемой знаком "+". Строки можно сравнивать. В результате сравнения двух строк, получается истина только в том случае, если сравниваемые строки совпадают посимвольно и имеют одинаковую длину (принадлежат одному и тому же типу).

Текущая длина строковой переменной может быть определена с помощью встроенной функции LENGTH. Например, можно распечатать в цикле значение строки HAMLET:

for c:=1 to length(HAMLET) do write(HAMLET [c]).

Конечно, подобные циклы не надо использовать в реальных программах. Переменные типа STRING могут быть напечатаны с помощью единственного оператора WRITE или WRITELN. Для того, чтобы ввести значение типа STRING, необходимо использовать READLN или READ.

При этом, в отличие от ввода строки-массива, в типе STRING вся строка вводится целиком - клавиша ENTER нажимается один раз после последнего введенного символа.

ПРИМЕР 4. С клавиатуры вводится последовательность слов длиной в 4 символа. Напечатать эти слова, пока не встретится слово STOP.

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

repeat

readln(LINE_OF_TEXT); writeln(LINE_OF_TEXT);

until LINE_OF_TEXT = 'STOP',

где LINE_OF_TEXT есть переменная типа STRING[4].

Последовательность слов может быть введена сразу целиком, и для этого совсем необязательно вводить специальную переменную для хранения этого "длинного" слова. Здесь можно воспользоваться буфером клавиатуры для временного хранения всей последовательности слов. Оператор же READ в цикле будет "откусывать" от буфера по 4

символа, а оператор WRITELN - печатать это слово:

repeat

read(LINE_OF_TEXT);

writeln(LINE_OF_TEXT);

until LINE_OF_TEXT = 'STOP'.

Заметим, кстати, что если в программах с подобным циклом еще будут операторы READ, то рекомендуется перед ними сделать очистку буфера с помощью READLN.

Следует отметить также, что в первом случае слова можно вводить и меньшей длины, но работа завершится по набору слова STOP. Во втором случае (при наборе сплошной последовательности слов) выход из цикла будет реализован только при наличии в этой последовательности числа символов, кратных 4, из них последние 4 символа есть слово STOP.

7.4 Строковые функции и процедуры

Они введены для облегчения манипуляции со строками. Имеется 8 строковых функций и процедур.

1. ФункцияCONCAT (склеивание).

Синтаксис: concat(S1, S2,..., Sn: string): string.

Возвращает строку, полученную конкатенацией строк S1,...,Sn.

ПРИМЕР: NUMBER:= concat('12','34','50'); NUMBER = '123450'.

2. ФункцияLENGTH (длина).

Синтаксис: length(S: string): integer.

Возвращает длину строки S.

ПРИМЕР: N:= length('345'); N = 3.

3. Функция POS (позиция).

Функция POS в качестве аргументов использует две строки и определяет, содержится ли первая строка во второй. Возвращает номер символа, начиная с которого S входит в T. Если вхождения нет, то возвращает 0.

ПРИМЕР: N:= pos('E','HELLO'); N:= pos('A','HELLO');

N = 2. N = 0.

4. Функция COPY (вырезка фрагмента).

Синтаксис: copy(S: string; N1,N: integer): string.

Возвращает подстроку, полученную из N символов строки S, начиная с позиции N1. Значение переменной S при этом не меняется.

ПРИМЕР: FRAGMENT:= copy('PROGRAMM',2,3);

FRAGMENT = 'ROG'.

5. Процедура DELETE (стирание фрагмента).

Убирает из строки S LEN символов, начиная с POS, при этом длина строки уменьшается на LEN позиций.

ПРИМЕР: delete(FRAGMENT,2,3);

FRAGMENT:= 'PROGRAMM'; -FRAGMENT = 'PRAMM'.

6. ПроцедураINSERT (вставка).

Синтаксис: insert(S: string; var D: string; POS: integer).

Вставляет строку S в строку D перед символом с номером POS, при этом длина строки D увеличивается на LENGTH(S) позиций.

ПРИМЕР: insert ('ROG', FRAGMENT, 2);

FRAGMENT:= 'PRAMM'; -FRAGMENT = 'PROGRAMM'.

7. Процедура STR (преобразование в строку).

Синтаксис: str(I: integer; var S: string);

str(R: real; var S: string).

Преобразует I или R из числа в строку и записывает эту строку в S, причем R и I могут записываться форматно, как в процедуре WRITE.

ПРИМЕР: a) R:= 123.654; str(R:5:2, S); S = '123.65';

б) I:= 5683; str(I, S); s = '5683'.

8. Процедура VAL (преобразование в число).

Синтаксис: val(S: string; var I, J: integer).

val(S: string; var I: real; var J: integer).

Преобразует строковую переменную S в число типа I. Переменная J получает значение 0, если перевод прошел без ошибок. Если же сделана попытка конвертировать в число строку, где есть нецифровые символы, то переменная J принимает значение позиции первого нецифрового символа, при этом работа процедуры будет прервана.

ПРИМЕР: S:= '4326'; S:= '43p8';

val (S,I,J); val (S,I,J);

I = 4326, J = 0 I - не определено, J = 3.

Рассмотрим теперь пример на применение указанных функций и процедур обработки строк.

ПРИМЕР 4. Изменение порядка слов в строке

program REVERSE;

var OLD_LINE, NEW_LINE: string[50];

PROBEL: integer; WORD: string[50];

begin

¦ NEW_LINE:= ''; readln(OLD_LINE);

¦ OLD_LINE:= concat(OLD_LINE,' ');

¦ while OLD_LINE <> '' do

¦ begin

¦ ¦ PROBEL:= pos(' ', OLD_LINE);

¦ ¦ word:= copy(OLD_LINE, 1, PROBEL);

¦ ¦ NEW_LINE:= concat(WORD, NEW_LINE);

¦ ¦ delete(OLD_LINE, 1, PROBEL);

¦ end;

¦ writeln(NEW_LINE)

end.

ПОЯСНЕНИЕ. С клавиатуры вводится строка OLD_LINE и к ней справа подклеивается пробел. Это делается для того, чтобы строка имела одну и ту же структуру: слово плюс пробел. Затем в цикле, признаком конца которого является пустая константа, выделяется очередное по порядку слово и подклеивается слева в переменную NEW_ LINE. После выборки очередного слова из OLD_LINE оно оттуда выбрасывается, что приводит к постепенному уменьшению строки. Здесь переменная PROBEL служит для хранения позиции первого пробела в строке, а WORD - для выбранного из OLD_LINE слова.

Например, строка ' Наша Таня громко плачет' преобразуется в строку ' плачет громко Таня Наша'.

8. МНОЖЕСТВА. ДАННЫЕ ТИПА SET

Тип в программировании - это множество, для которого определен некоторый набор операций над его элементами. Сами элементы множества называются объектами (или значениями) данного типа. В языке Паскаль рассматриваются различные типы данных, которые по своей организации подразделяются на отдельные виды. Прежде всего следует отметить, что все типы данных делятся на стандартные и нестандартные.

Стандартные: REAL, INTEGER, CHAR, BOOLEAN. Для каждого из этих типов рассматриваются соответствующие операции над его элементами. В Паскале имеются средства, позволяющие определять, исходя из имеющихся типов, новые нестандартные типы. Примерами таких нестандартных типов являются данные типа STRING и ARRAY, т.е. литерный тип и массивы. Массив - это упорядоченный набор данных одного типа, у каждого из которых есть индекс (номер). Способ индексации, тип элементов, длина массива содержатся в определении того типа, которому принадлежит массив:

TYPE T = ARRAY[1..20] OF REAL.

Это определение типа, имя которого T. Объектами типа T будут упорядоченные наборы по 20 элементов, имеющих тип REAL; диапазон изменения значения индекса от 1 до 20. Определив с помощью TYPE тип T, можно теперь описать некоторую переменную этого типа:

VAR А: T.

Значениями переменной "А" будут массивы длины 20, элементы которых имеют тип REAL. Для того, чтобы рассматривать эти элементы по отдельности, применяются обозначения A[1], A[2],..., A[20].

Переменная А - переменная типа T, переменные A[1],...,A[20] - переменные типа REAL. С ними можно обращаться как с обычными переменными типа REAL: X, Y, Z и т.д. В квадратных скобках необязательно должно быть целое число, им может быть произвольное выражение типа INTEGER, например: A[I], A[2*I], A[2*I-1]. Значение индекса обязано лежать в указанном диапазоне от 1 до 20. Операции над объектами типа T - это доступ к отдельным элементам массивов через индексы и изменение отдельных элементов массивов с помощью операций, связанных с типом REAL.

Итак, если в Паскаль-программе определен тип с помощью конструкции ARRAY..OF, то он называется регулярным типом. Общий вид регулярного типа есть:

type U = array [N1..N2] of R.

Тип R называется базовым по отношению к типу U. Объекты регулярного типа называются массивами. Пусть R в свою очередь определен как регулярный тип:

type R = array [M1..M2] of S;

и пусть переменная А - переменная типа U. Тогда A[I] – переменная типа R, а А[I][J] - переменная типа S. Таким образом, получается переменная, представляющая собой двумерный массив как массив массивов.

8.1 Определение типа множество

Переменные типа массив относятся к так называемым структурированным типам данных. В языке Паскаль имеются и другие структурированные типы данных, к которым принадлежит и тип данных множество. Математическое понятие множества подразумевает совокупность элементов. В отличие от массива (одномерного) множество состоит из элементов, где порядок их следования не играет роли:

{1,3,5}, {5,3,1}, {1,5,3} - одно и то же множество.

В математике для обозначения множеств используются скобки {,}. В Паскале вместо фигурных скобок для представления множеств используются квадратные: [1,3,5].

Подобно массивам, переменная типа множество имеет тип компонент. Каждый элемент, входящий в множество, имеет значение, которое должно принадлежать к типу компонент.

ОБЩАЯ ФОРМА ЗАПИСИ:
TYPE <имятипа>: SET OF <типкомпонент>;
VAR <имя переменной>: <имя типа>;
или
VAR <имя переменной>: SET OF <тип компонент>;

ПРИМЕРЫ: var LETTERS: set of 'A'..'Z';

DAYS: set of 1..31; MNOGCHAR: set of char.

Итак, мы увидели, что в описании типа множество есть общее с описанием типа массив, но есть и существенные отличия:

а) нет типа индекса (элементы множества не индексируются);

б) есть, как в массиве, тип компонент.

НАПРИМЕР:

type DAYSOFWEEK = (SUN, MON, TUE, WED, THU, FRI, SAT);

var WEEKDAYS, WEEKEND: set of DAYSOFWEEK.

Теперь этим описанным переменным можно присваивать различные значения, которые суть множества, состоящие из элементов перечислимого типа - названий дней недели:

a) WEEKDAYS:= [MON, TUE, WED, THU, FRI];

б) WEEKEND:= [SAT, SUN],

причем в случае а) можно поступить иначе: WEEKDAYS:= [MON..FRI].

Заметим также, что указанные множества из элементов перечислимого типа нельзя сформировать с помощью оператора READ (в силу специфики этого типа).

Аналогом нуля в типе множество есть пустое множество: [].

8.2 Операции над множествами

Над множествами можно производить следующие операции:

1. Определение принадлежности элемента множеству.

2. Сравнение множеств.

3. Действия над множествами.

Рассмотрим подробнее эти операции.

1. Принадлежность множеству

В языке Паскаль обеспечен механизм для определения принадлежности некоторого значения множеству его элементов. Этот механизм реализуется в рамках создания булевского выражения с использованием оператора IN. Структура применения этого оператора имеет вид:

В результате работы этого оператора получается булевское выражение. Например, выражения WED in WEEKDAYS, SAT in WEEKEND являются истинными булевскими выражениями, а выражения SAT in WEEKDAYS, MON in WEEKEND являются ложными.

Булевские выражения этого типа могут входить составной частью в различные операторы, в частности, в оператор IF.

ПРИМЕР 1. Пусть переменная DAY принимает значения всех дней недели. Тогда можно написать программу печати, где этот день недели является рабочим или днем отдыха:

for DAY:= SUN to SAT do

if DAY in WEEKDAY

then WRITELN('Сегоднярабочийдень')

else WRITELN('Сегодня день отдыха').

Заметим, что здесь перед циклом нужно определить переменную DAY как переменную перечислимого типа:

var DAY: DAYSOFWEEK.

Итак, мы видим, что на базе перечислимого типа DAYSOFWEEK можно сформировать переменную DAY и множества WEEKDAYS и WEEKEND.

Булевское выражение на базе IN можно сочетать с другими типами булевских выражений.

НАПРИМЕР:

if (DAY in WEEKEND) and (DAY <> SAT) then

writeln('Сегодня - воскресенье').

Множества имеют различные применения в организации программ.

Одним из них является упрощение написания оператора IF.

Рассмотримдвапримера:

1) if (T=0) or (T=32) or (T=212) or (T=276) then...

2) if T in [0, 32, 212, 276] then...

Эти операторы эквивалентны, но второй значительно проще.

Использование множеств позволяет улучшить наглядность и понимание алгоритма работы программы. Например, можно определить, является ли литерная переменная, именуемая ONE_CHAR, цифрой, записав: if ONE_CHAR in ['0'..'9'] then...


Действия над множествами

В Паскале, как и в математике, над множествами можно выполнять следующие логические операции:

а) объединение;

б) пересечение;

в) разность.

Рассмотрим эти операции подробно, но предварительно произведем описание:

type COUNTRIES = (ENG, FR, USA, SP, IT);

var MAP1, MAP2: COUNTRIES.

а) ОБЪЕДИНЕНИЕ (+):

[ENG, FR] + [IT]-> [ENG, FR, IT];

б) ПЕРЕСЕЧЕНИЕ (*):

[ENG, FR, USA] * [ENG, USA, IT] -> [ENG, USA];

в) РАЗНОСТЬ (-):

[ENG..IT] - [ENG..SP] -> [IT].

Эти три операции используются для построения выражений над множествами.

НАПРИМЕР: MAP1:= [FR]; MAP1:= MAP1 + [USA]; MAP2:= MAP1;

MAP1:= MAP1 * (MAP2 + [IT]).

ПРИМЕР 2. РЕШЕТО ЭРАТОСФЕНА. Найти простые числа, не превосходящие заданного.

Алгоритм базируется на вычеркивании чисел, кратных выбранному:

program ERATOS;

const MAXPRIM = 15;

var PRIMES: set of 2..MAXPRIM;

COUNT, MULTIPLE: integer;

begin

¦ writeln('простыечисла, меньше ', MAXPRIM);

¦ PRIMES:= [2..MAXPRIM];

¦ for COUNT:= 2 to MAXPRIM do

¦ if COUNT in PRIMES then

¦ begin

¦ ¦ writeln(COUNT);

¦ ¦ for MULTIPLE:=1 to (MAXPRIM div COUNT) do

¦ ¦ PRIMES:= PRIMES-[COUNT*MULTIPLE]

¦ end;

end.

ПОЯСНЕНИЕ. Начинаем с набора множества, состоящего из всех целых чисел в интервале 2..15. Программа при помощи цикла FOR проверяет каждое целое число, входящее в множество. Если целое число является элементом множества, то оно печатается, и из множества удаляются все целые числа, кратные данному числу.

Сравнение множеств

Операция IN весьма полезна, и она позволяет, например, выяснить, являются ли два множества равными. Например, если мы хотим узнать, равны ли множества MAP1 и MAP2, то можно написать:

EGALE:= true;

for MEMBER:= ENG to IT DO

if (MEMBER in MAP1) <> (MEMBER in MAP2) then EGALE:= false.

Это громоздко, поэтому в Паскале есть булевские выражения с применением операций сравнения: =, <>, >=, <=.

НАПРИМЕР: MAP1 = MAP2;

MAP1 <> MAP2;

MAP1 - MAP2 <> [FR];

MAP1 + MAP2 <> [ENG..IT];

MAP1 >= MAP2 (eсли выражение истинно, то MAP2 есть подмножество MAP1).


8.3 Печать множеств

При работе с множествами немаловажным является вопрос распечатки элементов множества. Отметим, что в большинстве версий языка в операторах WRITE нельзя называть переменные типа "множество". Например, нельзя распечатать множество таким образом:

VAR A: SET OF 1..9;

WRITE(A).

Здесь нет ничего удивительного, т.к. даже если А есть массив, то его тоже нельзя распечатать сразу с помощью одного оператора WRITE(А). Для вывода элементов массива организуются циклы.

Для печати элементов множества также нужно организовать цикл (однократный), внутрь которого вводится некоторая переменная, пробегающая все возможные значения этого множества, а перед оператором WRITE в рамках конструкции IF проверяется, входит ли этот элемент в конкретное множество:

if K in SET1 then write(K).

Как правило, для целей распечатки элементов множеств организуются свои процедуры. Пусть мы имеем дело с множествами, состоящими из целых чисел в границах NIZ и VERH. Зададим множественный тип TS для этих границ:

type INT = NIZ..VERH; TS = set оf INT.

Тогда можно написать процедуру, содержащую в качестве параметра множество:

procedure PRINTSET (OS: TS);

var M: INT;

begin

¦ for M:= NIZ to VERH do

¦ if M in OS then writeln(M);

end.

Теперь можно обращаться к этой процедуре для печати множеств, если только они состоят из элементов, не выходящих из интервала NIZ..VERH. Пусть в разделе констант было описано:

const NIZ = 0; VERH = 10;

тогда можно распечатать множества, обратившись к процедуре:

а) PRINTSET ([5,6,7]); б) PRINTSET ([2]); в) PRINTSET ([3..8]).

Обращение к процедуре можно организовать также в виде:

var SET1, SET2: TS;

SET1:= [..... ]; SET2:= [......]

PRINTSET (SET1); PRINTSET (SET1+SET2); ит.д.

ПРИМЕР 3. В заключение рассмотрим пример целиком, где продемонстрируем все те действия, которые определены над множествами:

program IGRA;

type KOST = 1..6; BROSOK = set of KOST;

var A,B,C: BROSOK;

procedure SRAWNENIE (D: BROSOK);

var K: KOST;

begin

¦ for K:= 1 to 6 do

¦ if K in D then write(K:4); writeln;

end;

begin

¦ A:= [1,3,4]; B:= [2,4,6]; C:= A + B;

¦ write('[1,3,4] + [2,4,6] ='); SRAWNENIE (C);

¦ C:= A - B;

¦ write('[1,3,4] - [2,4,6] ='); SRAWNENIE (C);

¦ C:= A * B;

¦ write('[1,3,4] * [2,4,6] ='); SRAWNENIE (C);

end.

ПОЯСНЕНИЕ. В программе определяются множества A, B, C типа BROSOK, элементами которых являются целые числа из диапазона [1..6], и процедура вывода на печать элементов таких множеств.

ЗАМЕЧАНИЕ 1. Если множество задано перечислимым типом, то его элементы напечатать нельзя. На печать можно вывести элементы только ординального типа: INTEGER, CHAR, BOOLEAN, интервальный.

ЗАМЕЧАНИЕ 2. Один и тот же набор данных можно организовать в виде линейного массива ARRAY, в виде множества SET и в виде строки типа STRING. Какой из этих видов предпочтительнее? Если над элементами (числами) производятся действия, то лучше ARRAY. Если же стоит задача о взаимосвязи элементов нескольких множеств или вопрос о вхождении каких-то объектов в множество, то лучше SET.


9. КОМБИНИРОВАННЫЙ ТИП - ЗАПИСИ. ДАННЫЕ ТИПА RECORD

Ранее было рассмотрено, как удобно работать с множествами и массивами. Однако все элементы множества всегда должны иметь один и тот же тип. Хотя в ряде случаев это вызывает определенные ограничения.

Рассмотрим в качестве примера задачу заполнения анкеты с некоторыми данными, например: имя, адрес, телефон, возраст, пол, семейное положение. Каждое из этих данных имеет свой тип. Однако все эти данные взаимосвязаны, они принадлежат всегда одному человеку, и хотелось бы, чтобы все они имели общее имя. Для таких случаев Паскаль предоставляет новый, комбинированный тип переменной, а именно RECORD - запись.

9.1 Определение типа RECORD

Так же, как и массив, запись объединяет переменные. Однако у записи переменные могут быть различных типов! Каждая компонента записи называется полем. Каждое поле записи имеет свой тип.

Мы уже знаем, что элементы массива всегда могут использоваться как отдельные переменные. Например, определив:

type RY = array [1..10] of integer;

var A: RY,

можно писать А[1],...,A[10]. Аналогичная ситуация имеет место и для записи. Здесь также можно использовать поля записи как отдельные переменные.

ПРИМЕР: type PATIENT = record

NAME: string [20];

MALADI: string [40];

AGE: integer;

MARIE: boolean;

end;

var NEKTO:PATIENT.

Это есть описание типа RECORD. Структура записи такого типа определяется здесь с помощью всех полей между RECORD и END.

В рассмотренном выше примере всей структуре этого типа присвоено имя PATIENT (пациент). Запись типа PATIENT состоит из четырех отдельных переменных, т.е. полей, которые имеют имена: NAME, MALADI, AGE, MARIE. Каждое из этих полей имеет свой тип. В разделе TYPE описывается тип PATIENT, который затем присваивается переменной NEKTO. Именно NEKTO есть переменная типа PATIENT, т.е. переменная типа RECORD.

Чтобы обратиться к некоторым полям записи, следует написать имя переменной и имя поля. Эти два идентификатора должна разделять точка.

ПРИМЕР:

NEKTO.NAME: = 'MANUELA'; NEKTO.AGE:= 20;

NEKTO.MALADI: = 'GRIP'; NEKTO.MARIE: = true.

Отметим, что поле записи, например поле NEKTO.AGE, может рассматриваться как обычная простая переменная целого типа:

NEKTO.AGE:= NEKTO.AGE + 1. Вместе с тем, запись может рассматриваться как единое целое. Пустьимеетсяследующееописание:

type DATE = record

DAY: 1...31;

MONTH: (JAN, FEB, MAR, APR, MAY, JUN, JUL,

AUG, SEP, OCT, NOV, DEC);

YEAR: integer;

end;

var HB, MB: DATE.

Мы видим, что HB и MB имеют тип DATE. Помимо действий над отдельными полями записей HB и МB можно выполнять операции над всей записью: HB:= MB.

Это присваивание эквивалентно следующей последовательности операторов: HB.DAY:= MB.DAY;

HB.MONTH: = MB.MONTH;

HB.YEAR:= MB.YEAR.

Для переменных этого типа вводятся сравнения: " = " и " <> ".

Так в нашем случае логическое выражение МB=HB является истинным.

Так как на тип компонент массива не накладывается ограничений, то можно образовывать массивы, компонентами которых являются записи. Например, вместоVAR NEKTO: PATIENT можнозаписать VAR NEKTO: ARRAY [1..N] OF PATIENT. Тогда фамилию первого пациента можно указать как NEKTO [1].NAME. Аналогично можно задать множество дат рождений N персон VAR BD: ARRAY[1..N] OF DATE.

Отсюда мы видим, что компоненты (элементы) массива BD есть записи. Чтобы обратиться к некоторому полю определенной записи массива, следует определить имя массива, индекс интересующей записи и имя необходимого поля. Например, для печати года рождения3-й персоны необходим оператор:

WRITELN (BD[3].YEAR).

Как и в случае с массивом, на типы компонент записи не накладывается каких-либо ограничений. Поля могут быть массивами, множествами или даже записями.


9.2 Оператор WITH

При работе с записями, при обращении к различным полям каждый раз приходится писать сначала имя самой записи, что не всегда удобно, если полей много. ПоэтомувПаскалеприменяетсяоператор WITH:

with NEKTO do

begin

NAME:= 'MANUELA'; AGE:= 20;

MALADI:= 'GRIP';

MARIE:= true;

end.

Другими словами, в рамках оператора, помещенного внутри оператора WITH, к полям определенной переменной можно обращаться просто по имени (префикация имен опускается).

Особенно эффективно использовать WITH, когда речь идет о вложенных записях, т.е. таких, где поля есть тоже записи. Например, запись типа PATIENT можно расширить добавлением поля DATE, которое снова есть запись с 3-мя полями:

type PATIENT = record

NAME: string [10];

MALADI: string [30];

DATE: record

DEN: integer;

MESJATS: string [10];

GOD: integer;

end;

MARIE: boolean;

end;

var NEKTO: PATIENT.

При таком вложении доступ, например, к полю GOD уже должен сопровождаться указанием двух префиксных имен, например:

read (NEKTO.DATE.GOD).

Здесь уже WITH может значительно упростить работу с полями:

with NEKTO, DATE do

begin

NAME:= 'MANUELA'; AGE:= 20;

MALADI:= 'GRIP';

DEN:= 18;

MESJATS:= 'MART';

GOD:= 1944;

MARIE:= TRUE;

end.

Оператор WITH принято называть оператором присоединения. В общем случае он выглядит так: WITH R1, R2,..., Rn do S, что эквивалентно WITH R1 do WITH R2, R3,..., Rn do S.

Имя поля в операторе присоединения обозначает компоненту комбинированной переменной из ближайшего объединяющего оператора присоединения, в котором указана переменная с таким полем. Следовательно, если две переменные из списка комбинированных переменных оператора присоединения имеют поля, обозначенные одним и тем же именем, то внутри оператора WITH это имя обозначает поле той переменной, которая указана в списке позже.

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

Пусть, например, имеется простая переменная AGE и поле AGE некоторой комбинированной переменной NEKTO. В этом случае их можно отличить, т.к. простая переменная имеет имя AGE, а переменная-поле имеет полное имя NEKTO.AGE. А что будет в операторе WITH, где префикс NEKTO опускается?

В этом случае в операторе предпочтение отдается именам полей записи, т.е. считается, что внутри оператора WITH соответствующее имя обозначает имя поля, а не имя переменной.

Проиллюстрируем этот тезис на примере. Пусть даны типы:

const N_STUD =...;

N_SOTR =...;

n =...;

type SEX = (M,F);

STUD = RECORD

FAM,IM,OTH: array [1..N_STUD] of string[n];

POL: SEX;

GR: 111..154;

STIP: boolean;

end;

SOTR = record

FAM,IM,OTH: array [1..N_SOTR] of string[n];

POL: SEX;

DOLGN: (LAB, ASS, STPR, DOZ, PROF);

ZARPL: integer;

end;

var X: STUD; Y: SOTR;

STIP: integer;

Тогда можно дать такой фрагмент программы:

with X, Y do

begin

IM[5]:= 'ALEXANDR ';

POL:= M;

STIP:= true;

GR:= 122;

end;

STIP:= 160.

Здесь поля IM, POL относятся к переменной Y типа SOTR, т.к. эта переменная в списке переменных-записей заголовка WITH фигурирует после переменной X типа STUD. Кроме того, в этом фрагменте имя STIP в теле оператора WITH есть имя поля переменной Х.

9.3 Записи с вариантами

Записи, описанные ранее - это записи с фиксированными частями. Они имеют в различных ситуациях строго определенную структуру. Однако бывают ситуации, когда нужно отказаться от этой строгой определенности. Поэтому появляются записи с вариантами.

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

const MAXNOMBRE =...

type ENTRY = record

AUTOR, TITLE, PUBLISHER, SITY: STRING [100];

YEAR: 1...9999;

end;

var REFLIST: array [1...MAXNOMBRE] of ENTRY;

Здесь ENTRY - вход, т.е. данные о какой-либо научной работе. Если же некоторые работы входят в журналы, то нужно создавать новый массив данных только для журналов и работать с этими двумя массивами, что не очень удобно. В Паскале есть возможность образовать структуру с вариантами, каждый вход которой соответствует содержанию записи. Это достигается путем введения в описание записи специального оператора CASE- переключателя, который в чем-то похож на ранее введенный, но имеет свои синтаксические и семантические отличия.

В нашем примере, помимо описанного уже типа ENTRY, вводим еще один переменный тип:

ENTRYTYPE = (BOOK,MAGAZINE);

Теперь можно скорректировать раннюю запись:

type ENTRY = record

AUTOR, TITLE: string [100];

YEAR: 1..9999;

case TAG: ENTRYTYPE of

BOOK: (PUBLISHER, SITY: STRING [100]);

MAGAZINE: (MAGNAME: STRING; VOLUME, ISSUE: integer)

END;

Это описание делится на две части: фиксированную и вариантную. Поля: AUTOR, TITLE и YEAR - фиксированная часть. Остальная часть - вариантная, структура которой может меняться в пределах двух вариантов. Вариантная часть записи начинается со строки CASE, где в качестве селектора выступает не выражение, а идентификатор некоторого перечислимого типа. Элементы (компоненты) этого перечислимого типа (в нашем случае ENTRYTYPE) используются в качестве альтернативного определения записи: BOOK и MAGAZINE. В каждой альтернативе имеется свой набор полей:

BOOK: MAGAZINE:

AUTOR AUTOR

TITLE TITLE

YEAR YEAR

PUBLISHER MAGNAME

CITY VOLUME

ISSUE

Для того, чтобы различать, какую из ветвей нужно выбрать для работы, в такую запись вводится так называемое поле ТЕГА (tag fild) или узловое поле. Это дополнительное поле с именем TAG имеет тип ENTRYTYPE и помещается в качестве селектора в оператор CASE - OF:

ENTRY = record

AUT, TIT: string[100];

YEAR: 1..9999;

case TAG: ENTRYTYPE of

BOOK: (PUB,CYTY: string[100]);

MAGAZINE: (MAGNAME: string[100]; VOL,ISSU: integer);

end;

Здесь поле с именем TAG имеет тип ENTRYTYPE и принимает два значения. Если это поле имеет значение BOOK, то это ссылка на книгу, в противном случае - на журнал. Для определения составления записи с вариантами достаточно проверить значение поля TAG.

ПРИМЕР: Процедура печати значений записей типа ENTRY

procedure PRINTREF (CITATION: ENTRY);

begin

with CITATION do begin

writeln (AUTOR); writeln (TITLE); writeln (YEAR);

if TAG = BOOK then

writeln (PUB,',',CITY)

else

begin writeln (MAGNAME);

writeln (VOL,',',ISSUE)

end;

end;

end;

ЗАМЕЧАНИЯ:

1. Вариантная часть может содержать произвольное число аргументов, которые задействуются или перечислимыми типами, или произвольными порядковыми типами (интервалами).

2. Любая запись имеет только одну вариантную часть, которая должна всегда располагаться в конце описания, поэтому END оператора CASE совпадает с END всего описания.

3. Имя поля не может встречаться в двух вариантах одной записи.

4. В вариантной части могут встречаться другие новые вариантные части.


10. ФАЙЛОВЫЙ ТИП

До сих пор все рассмотренные типы переменных отличались тем, что в них заранее известно число компонент и тип этих компонент. Например, массив ARRAY[1..N] OF REAL состоит из N вещественных чисел, а запись:

record

POL1: string[M];

POLN: real;

end;

состоит из N полей, каждое из которых имеет свой тип. Кроме того, характерной особенностью всех рассмотренных ранее типов данных является то обстоятельство, что все эти данные неразрывно связаны с самим текстом программы и "живут" вместе с ней. Это означает, что все данные, присущие некоторой программе, не могут быть отделены от нее и использоваться в другой программе.

Но существует класс задач, когда количество компонент (пусть одного и того же типа) заранее определить невозможно. Оно выясняется только в процессе решения задачи, т.е. во время работы программы. Поэтому возникает необходимость в таком типе значений, которые представляют собой произвольные последовательности элементов одного и того же типа, но длина этих последовательностей заранее не ограничена. Такие типы называют файловыми.

Итак, файл (FILE) представляет собой совокупность данных одинакового типа. В этом файл напоминает массив. Однако у массива с помощью индекса можно указать любой его элемент, например, A[7] - седьмой элемент. У файла же вызывать данные таким образом нельзя.

Условно файлы можно изобразить как некоторую ленту, у которой есть начало, а конец не фиксируется. Элементы файла записываются на эту ленту последовательно, друг за другом:

Файл напоминает магнитную ленту для записи мелодий, начало которой заполнено, а конец пока свободен. Новые записи помещаются в конец ленты. Прокрутить какую-то мелодию на ленте означает сделать протяжку ленты. Существует несколько разновидностей файлов, отличающихся методом доступа к ним. По способу доступа к элементам файла они бывают ПОСЛЕДОВАТЕЛЬНОГО и ПРЯМОГО доступа.

Файлы - это единственный тип данных, посредством которого данные получаются извне (входной файл) и передаются из ЭВМ во внешний мир (выходной файл). Файлы - средство связи с внешним миром.

10.1 Определение и описание файла

Файл представляет собой последовательность однотипных компонент произвольной длины. Каждый файл имеет свое имя, являющееся именем соответствующей файловой переменной, которая должна быть заявлена либо с помощью слова TYPE, либо - VAR. Для обозначения этого типа данных используется служебное слово FILE:

ПРИМЕР: a) type AZMORZE = (TOCHKA, TIRE);

MESSAGE = file of AZMORZE;

var TELEGRAM: MESSAGE;

б) var PISMO: file of char;

F: file of integer.

Здесь тип компонент может быть любым, кроме файлового типа.

Итак, в последнем примере определена F - переменная файлового типа. Это означает, что на ленте могут быть записаны только целые числа. Обратите внимание, что здесь никак не упоминается точное число элементов, которые можно записать в файл. Это можно делать постоянно, хотя в реальности лента, т.е. объем памяти, отведенной под запись файла, когда-то кончится (есть предел!!!).


10.2 Типы файлов. Процедуры работы с файлами

По своей связи с работающей программой файлы бывают внутренними и внешними.

ВНЕШНИЕ - это файлы, имена которых включены в список заголовка программы, и которые существуют вне программы, т.е. находятся на внешних носителях (дисках). Такие файлы заполнены заранее, записаны на дискету и могут быть использованы различными программами. Как уже сказано выше, их имена должны быть объявлены в заголовке программы: program OBRABOTKA (...,MESSAGE,PISMO,...).

ВНУТРЕННИЕ - это файлы, имена которых не внесены в заголовок программы. Они существуют только во время исполнения программы. Работа с ними идет одинаково, только внутренний файл пропадает после окончания работы программы.

Мы знаем, что каждый тип Паскаля имеет свой набор операций, определенный типом компонент этого объекта. Для внутренних файлов, рассматриваемых как единое целое, никаких операций нет – ни сравнения файлов, ни операции присваивания. Можно работать только с отдельными компонентами, и эта работа зависит от типа компонент файла. Для доступа к отдельным компонентам файла в Паскале введены стандартные процедуры RESET, GET, REWRITE, PUT и функции EOLN и EOF.

Обращение к ним идет с помощью процедур-операторов. Все эти процедуры тем или иным образом связаны с установкой режима работы с заданными файлами (чтение или запись). При этом происходит следующее:

ЧТЕНИЕ - присваивание переменной значения компоненты файла;

ЗАПИСЬ - запись значения переменной в конец файла.

Для удобства описания этих действий введено понятие "окно файла" или просто "окно". Окно определяет позицию доступа, т.е. компоненту файла, которая доступна для чтения (в режиме чтения), для записи (в режиме записи). Последняя позиция файла помечается специальным образом, что определяет конец файла.

Для работы с файлами в режиме чтения и записи используются операторы REWRITE, WRITE, RESET, READ и EOF. Рассмотрим их синтаксис и назначение.

1. REWRITE(F)- установка в начальное положение режима записи.

F
^
окно

1. WRITE(F,X) - записывает в файл F (где сейчас стоит окно) очередную компоненту, равную значению выражения X, после чего окно сдвигается вправо на следующую позицию файла:

F F1 F2 F3 ® F1 F2 F3 X
^ ^
окно окно

1. RESET(F) - перевод в режим чтения и установка окна на первую позицию файла.

F
^
окно

1. READ(F,V) - переменной V присваивается значение текущей позиции файла F, и окно перемещается на следующую позицию.

F F1 F2 F3 F4 ® F F1 F2 F3 F4
^ ^
окно окно

ПРИМЕЧАНИЕ. Файл открывается либо только для записи, либо для чтения - одновременно это делать нельзя!!!

1. При работе с файлами необходимо знать конец файла. Это делает логическая функция EOF:

EOF(F) = FАLSE, если конец файла не достигнут;

EOF(F) = TRUE - признак конца файла.

Функция EOF неразрывно связана с окном файла, которое всегда "смотрит" на одну из компонент файла. Эта функция всегда имеет определенное значение в зависимости от местонахождения окна файла. При заполнении файла последняя ее компонента всегда снабжается признаком конца. Пустой файл имеет единственную компоненту, содержащую признак конца файла.

ПРИМЕР 1. Расшифровка текста

Пусть все буквы от A до Z имеют коды от 65 до 90. Имеется файл SHIFRTEXT, состоящий из чисел от 65 до 90. Напечатать расшифрованный текст:

program RASSHIFROVKA (SHFRTXT);¦ program KODIROVKA;

type KOD = 65..90; ¦ type KOD = 65..90;

LITERA = 'A'..'Z'; ¦ SHIFR = file of KOD;

SHIFR = file of KOD; ¦ var x: KOD;

var x: KOD; ¦ SH: SHIFR;

y: LITERA; ¦ begin

SH: SHIFR; ¦ ¦ assign(SH,'shfrtxt');

begin ¦ ¦ rewrite (SH);

¦ assign(sh,'shfrtxt'); ¦ ¦ read(x);

¦ reset(SH); ¦ ¦ while x<> 00 do begin

¦ while not eof(SH) do¦ ¦ ¦ write (SH,x);

¦begin ¦ ¦ ¦

¦ ¦ read(SH,x); ¦ ¦ ¦ read(x);

¦ ¦ y:=chr(x); ¦ ¦ end;

¦ ¦ write(y); ¦ ¦ close(SH);

¦ end; close(sh); ¦ end.

end.

ПОЯСНЕНИЕ. В рассмотренном примере программа RASSHIFROVKA производит расшифровку строки числовых кодов файла SHFRTXT, сформированного с помощью программы KODIROVKA. В программе KODIROVKA предусмотрен непрерывный ввод кодов - двухзначных чисел от 65 до 90, разделяемых пробелом. Признаком конца ввода является код 00.

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

10.3 Буферная переменная

Мы знаем, что для чтения компоненты файла используется процедура READ(F,V). По этой процедуре выполняются два действия:

1. Копирование компоненты файла F, на которую смотрит окно, и присваивание этого значения переменной V.

2. Перемещение окна на следующую компоненту.

Однако, иногда удобно эти два действия разделить. Для этого вводится понятие буферной переменной. Она имеет имя: F^. Эту переменную не надо описывать, она определяется автоматически, как только описывается файл F. Тип F^ совпадает с типом компоненты файла. С переменной F^ можно выполнять все действия, как над данными типа компонент файла.

Если выполнена процедура RESET(F), то происходит установка окна на первую компоненту и значение этой компоненты идет в F^:

F X F^
^
окно

Если выполнен RESET(F), а файл F пуст, т.е. EOF(F)=TRUE, то значение F^ неопределенно. Окно файла указывает на его конец. Для передвижения окна файла и заполнения (чтения) буферной переменной используются в некоторых версиях Паскаля специальные процедуры-операторы GET и PUT:

а) GET(F) - передвижение окна на следующую компоненту и засылка значения этой компоненты в переменную F^.

F^ 7 f 3 5 7 8
^
окно

после GET(F):

F^ 8 f 3 5 7 8
^
окно

б) PUT(F) - запись в файл значения F^ и сдвиг вправо. Здесь до PUT(F) надо F^ присвоить очередное значение. В режиме записи значений в файл F^ служит поставщиком значений компонент. После процедуры REWRITE(F) окно устанавливается на первую компоненту, значение F^ не определено. Затем надо определить значение F^ с помощью команды присваивания: F^:=3. Если теперь написать процедуру PUT(F), то значение F^ идет в компоненту, где стоит окно, после чего значение F^ становится неопределенным:

F^ 3 f 6 4 7 8
^
окно

PUT(F)

F^ f 6 4 7 8 3
^
окно

ПРИМЕР 2. Запись чисел v1 - v5 в файл BUFFER и последующий их вывод на печать

program KVADRKOREN;

var R: real; I: integer; BUFFER: file of real;

begin

¦ rewrite(BUFFER);

¦ for I:=1 to 5 do

¦ begin

¦ ¦ BUFFER^:=sqrt(I); put(BUFFER);

¦ end;

¦ reset(BUFFER);

¦ for I:=1 to 5 do

¦ begin

¦ ¦ R:=BUFFER^;

¦ ¦ get(BUFFER); writeln(R);

¦ end;

end.

ЗАМЕЧАНИЕ. В некоторых версиях процедуры GET и PUT не существуют (в частности, это имеет место для Турбо-Паскаля). Но без них можно обойтись, т.к. существуют эквивалентные им операторы READ и WRITE. Им эквивалентны следующие составные операторы:

READ(F,V) => BEGIN V:=F^; GET(F) END;

WRITE(S,W) => BEGIN S^:=W; PUT(S) END.


10.4 Основные приемы работы с файлами

Известно существование многих версий Паскаля, каждая из которых имеет свои особенности и отличия от стандарта Паскаля. Рассмотрим некоторые приемы работы с файлами в системах Turbo-Pascal для ПЭВМ "Ямаха" и IBM PC.

Перед началом работы с файлами (до первого обращения к файлу) должна быть выполнена процедура ASSIGN. Эта процедура отождествляет имя файла с соответствующей файловой переменной.

СИНТАКСИС: assign(var F: file; NAME: string), где NAME - имя файла на диске, F - имя файловой переменной.

После выполнения этой процедуры NAME и F отождествляются, например, ASSIGN(F,'nomfile') отождествляет файловую переменную F с его именем на диске. В качестве имени файла может быть указаноего полное имя, т.е. путь к этому файлу, например:

ASSIGN(F,'С:\WORK\ MIM\nomfile').

После окончания работы с файлом он должен быть закрыт процедурой CLOSE, иначе результат может быть потерян, т.к. директория не будет обновлена.

СИНТАКСИС: CLOSE(var F:file), где F - имя файловой переменной.

Процедуры ASSIGN и CLOSE взаимосвязаны и работают в паре друг с другом. Как уже сказано выше, перед началом работы с файлом выполняется процедура ASSIGN(F, 'nomfile'), которая для логического файла F готовит (ищет) на диске в указанной директории файл с именем NOMFILE. При окончании работы с файлом по выполнению процедуры CLOSE происходит его обновление (в случае записи) и закрытие (в случае чтения).

В программе надо уметь задавать исходные файлы. Эти файлы надо делать в цикле, используя при этом формирование компонент, либо в форме некоторого выражения по RANDOMIZE, либо задействовать обычную команду READ для ввода данных с клавиатуры. Цикл можно делать FOR, если формирование файла идет по RANDOMIZE, или WHILE (REPEAT), если файл формируется по признаку конца ввода.

Напомним, что RANDOMIZE - процедура инициализации генератора случайных величин; RANDOM - функция генерации случайных чисел.

Рассмотрим все эти особенности на примере формирования, обработки и вывода файлов.

ПРИМЕР 2. Для двух целочисленных файлов F и G одинаковой длины образовать третий целочисленный файл H, компоненты которого определяются по правилу: Hi=MAX{Fi,Gi}. В программе предусмотреть вывод на экран все трех файлов

program MAXELEM;

type FT = file of integer;

var F,G,H: FT;

I,J: integer;

procedure VIVODFILE(var A:FT);

begin

¦ reset(A);

¦ while not eof(A) do

¦ begin

¦ read(A,I); write(I:4);

¦ end; writeln;

end;

begin { формирование исходных файлов }

¦ assign(F,'F'); assign(G,'G');

¦ randomize; rewrite(F); rewrite(G);

¦ for I:=1 to 10 do

¦ begin

¦ J:= random(10)-5; write(F,J);

¦ ¦ J:= random(10)-5; write(G,J);

¦ end;

¦ VIVODFILE(F); close(F);

¦ VIVODFILE(G); close(G);

¦ assign(H,'H');

¦ { формирование файла результата }

¦ reset(F); reset(G); rewrite(H);

¦ while not eof(F) do

¦ begin

¦ ¦ read(F,I); read(G,J);

¦ ¦ if I > J then write(H,I) else write(H,J);

¦ end; VIVODFILE(H);

¦ close(H);

end.

5. Файл в данный момент времени открыт либо для чтения, либо для записи. Поэтому для добавления к файлу новых элементов необходимо сначала переписать во вспомогательный файл исходный, затем добавить к нему новые элементы. При этих двух операциях вспомогательный файл открыт для чтения. После заполнения вспомогательного файла он переписывается в исходный, при этом начальные элементы исходного файла заносятся туда во второй раз. Все эти операции представлены в виде следующей процедуры:

procedure DOBAVLENIE(N: integer; var A:file);

var B: file; I,J: integer;

begin

¦ { Запись файла А в файл B }

¦ assign(B,'B');reset(A; rewrite(B);

¦ while not eof(A) do

¦ begin read(A,I); write(B,I); end;

¦ { Добавление новых элементов в файл B }

¦ for I:=1 to n do

¦ begin

¦ J:= random(10)-5; write(B,J);

¦ end;

¦ { Запись файла B в файл A }

¦ rewrite(A); reset(B);

¦ while not eof(B) do

¦ begin read(B,I); write(A,I); end;

end.

10.5 Текстовые файлы

Среди всех файлов особое место занимают текстовые файлы. Особенностью текстовых файлов является объединение в них символов в строки. Каждая строка кончается специальным символом конца строки. Этот специальный символ (литера) не входит в стандартный тип CHAR и не имеет графического представления. Нас и не интересует вид этого символа. Главное, что с ним связана логическая функция EOLN (конец строки). EOLN(F) = TRUE, если окно указывает на признак конца строки. Заметим, что если EOLN(F) = TRUE, то при чтении элементов из файла в символьную переменную она принимает значение пробела (пробел - аналог конца строки). Для записи в файл признака конца строки служит стандартная процедура WRITELN.

Текстовые файлы, т.е. файлы с делением на строки, описываются с помощью слова TEXT, например, VAR X, D: TEXT.

ПРИМЕР 3. Определить количество строк в файле с именем BOOK

program NOMBRELINE;

var K: integer; BOOK: text; S: char;

begin { Формированиефайла BOOK }

¦ assign(BOOK,'f1'); rewrite(BOOK); read(S);

while S<> '.' do begin

¦ while S <> '#' do begin

¦ write(BOOK,S); read(S); end;

¦ writeln(book);read(S); end; close(BOOK);

¦ { Подсчет числа строк в текст; BOOK }

¦ K:= 0; reset(BOOK); writeln;

¦ while not eof(BOOK) do

¦ begin

¦ if eoln(BOOK) then K:=K+1; read(BOOK,S); write(S);

¦ end;

¦ writeln('В текстовом файле BOOK ', K,' - строк');

end.