50276 (588702), страница 3

Файл №588702 50276 (Алгоритмический язык Паскаль) 3 страница50276 (588702) страница 32016-07-29СтудИзба
Просмтор этого файла доступен только зарегистрированным пользователям. Но у нас супер быстрая регистрация: достаточно только электронной почты!

Текст из файла (страница 3)

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

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

ПРИМЕР:

IF THEN

ELSE

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

<ветвь 2>

ELSE

Такое вложение используется для уменьшения числа необходимых проверок. Этот метод часто обеспечивает большую эффективность, чем составное условие, однако одновременно он уменьшает надежность программы. Не рекомендуется использовать более двух-трех уровней вложения 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 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 :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.

ПОЯСНЕНИЕ. В программе сначала формируется текстовый файл, у которого строки кончаются символом "$", а сам текст – символом ".". Текст вводится с клавиатуры в виде непрерывной цепочки, например:

Наша Маша громко плачет,Уронила в речку мячик.$Тише, Машенька, не плачь,$Не утонет в речке мяч.$.

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

Наша Маша громко плачет,

Уронила в речку мячик.

Тише, Машенька, не плачь,

Не утонет в речке мяч.

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

T

a

g

c

d

^

окно

WRITELN(T):

T

a

g

c

d

#

^

окно

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

В режиме чтения для работы с литерой "конец строки" есть процедура READLN. По этой процедуре окно файла устанавливается на первый элемент следующей строки файла.

T

d

b

c

d

#

e

f

^

окно

READLN(T):

T

d

b

c

d

#

e

f

^

окно

ПРИМЕР 4. Дано некоторое стихотворение в виде текстового файла ACROSTIH. Напечатать слово, образованное первыми буквами строк стихотворения (акростих)

program SLOVO(ACROSTIH); program FORMFIL;

var L:char; T: text; var F: text; S: char;

begin begin

¦ assign(T,'ACROSTIH'); ¦ assign(F,'ACROSTIH');

reset(T); ¦ rewrite(F); read(S);

¦ while not eof(T) do ¦ while S <> '?' do

¦ ¦ begin

¦ begin ¦ while S <> '#' do

¦ ¦ begin

read(T,L); write(L); ¦ write(F,S); read(S);

¦ ¦ end;

¦ readln(T); ¦ writeln(F);read(S); end;

¦ end; ¦ close(F);

end. end.

ПОЯСНЕНИЕ. Программа FORMFIL формирует текстовый файл ACROSTIH как было показано в примере 3. В программе SLOVO файл ACROSTIH выступает как внешний. Ему соответствует файловая переменная T. Оператор READLN(T) последовательно устанавливает окно файла на начало строк текста.

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

ПРИМЕР 5. Посчитать число знаков препинания в указанном текстовом файле

function PUNCTUATION(var CHARFILE: text): integer;

var SYMBOLNOMB: integer;

SYMBOL: char;

begin

SYMBOLNOMB:=0; reset(CHARFILE);

while not eof(CHARFILE) do

begin

read(CHARFILE, SYMBOL);

if SYMBOL in ['.',',',' ',':','...] then

SYMBOLNOMB:= SYMBOLNOMB + 1

end;

PUNCTUATIОN:= SYMBOLNOMB

end.

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

assign(FIL,'FIL');

reset(FIL);

n:=PUNCTUATION(FIL);

close(FIL);

writeln('число знаков препинания в тексте FIL =', n).


11. ССЫЛОЧНЫЙ ТИП. ПЕРЕМЕННЫЕ С УКАЗАТЕЛЯМИ

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

НАПРИМЕР:

а) с помощью описания VAR A, B: INTEGER в программе вводятся в употребление две статические переменные с именами А и В, значениями которых будут целые числа;

б) описание VAR X: ARRAY[1..10] OF REAL oпределяет (порождает) переменную регулярного типа (массив), значением которой может быть упорядоченная последовательность из десяти вещественных чисел.

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

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

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

Так, например, при описании VAR A, B: INTEGER в памяти ЭВМ резервируются две ячейки, которые маркируются соответственно А и В.

Будем считать, что до начала работы программы эти ячейки пусты (на самом деле они содержат "мусор" или 0). Если теперь в программе переменным А и В присвоить значения А:= 1 и В:= 2, то эти ячейки заполнятся соответствующими данными значениями.

При описании VAR X: ARRAY[1..10] OF REAL в памяти ЭВМ резервируются подряд 10 ячеек памяти, которые идентифицируются соответственно X[1], X[2],..., X[10].

Если теперь сделать

FOR I:= 1 TO 10 DO READ(X[I]),

то эти ячейки заполнятся десятью числами, вводимыми с клавиатуры. При упоминании в программе имен А и В фактически указывается на содержимое ячеек А и В, т.е. на значение переменных. Так, если следует оператор WRITE(A), то печатается не А, а значение переменной А, т.е. число 2.

Из этих рассуждений следует, что для таких переменных (статических) область памяти закрепляется на все время работы программы. Поэтому ячейка с адресом А всегда будет хранить только целые числа, а группа ячеек с адресами Х[1],...,X[10] - 10 вещественных чисел. Ничто другое в эти части памяти не может быть помещено.

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

Мы уже сталкивались с такой ситуацией:

CONST N =.....

VAR X: ARRAY [1..N];

или

VAR Y: STRING [N].

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

1. Пусть в заданном тексте из слов произвольной длины требуется найти первое по порядку слово, которое обладает некоторым свойством (не содержит, например, букву "а").

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

2. Бывает, что какой-то программный объект (например, массив чисел, множество, список) нужен не на все время работы программы, а только на какую-то часть. Хотелось бы после отработки данного объекта разместить на этом месте памяти другой объект.

11.1 Определение ссылочного типа

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

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

Имя ячейки

Такие переменные называются переменными типа указатель (переменными ссылочного типа) или просто указателями (ссылками).

Итак, слова - синонимы:

Значение указателя есть адрес объекта (ссылка на объект), посредством которого он и доступен в программе.

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

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

Как задать ссылочный тип, т.е. как описать указатель? Указатель обозначают обычным идентификатором. О том, что это указатель, говорит присутствие символа "^".

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

НАПРИМЕР:

TYPE MAS = ARRAY [1..100] OF INTEGER;

DINMAS = ^MAS;

VAR P: ^INTEGER; Q: ^CHAR;

RABMAS: DINMAS.

ЗДЕСЬ: P - ссылка на динамический объект целого типа, Q - ссылка на динамический объект литерного типа, RABMAS - ссылка на динамический объект, значением которого является массив из 100 чисел.

11.2 Создание динамических переменных. Процедура NEW

Описание переменных P и Q ссылочного типа в разделе объявлений еще не резервирует память для записи значений динамической переменной соответствующего типа.

Здесь вводятся в употребление статические переменные P и Q (транслятор резервирует место в памяти, необходимое для размещения ссылки). Пустые клетки в схеме означают, что пока переменные P и Q не имеют никаких ссылок на динамические объекты.

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

Это процедура с параметром - ссылочной переменной, сопоставленной порождаемому динамическому объекту.

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

ЗАМЕЧАНИЕ. Резервирование места для динамической переменной идет уже в ходе выполнения программы, а не при ее трансляции, как для статических переменных. Говорят, что NEW устанавливает водораздел между статическими и динамическими переменными.

КАРТА ПАМЯТИ

MSX-DOS

Библиотека PASCAL

TURBO-система

Исходный текст программы

Объектный ход программы

Указатель

-----------------

Куча

ПРИМЕЧАНИЕ. Здесь указана примерная схема карты памяти при работе системы Турбо-Паскаль.


11.3 Переменные с указателями

При использовании в программе ссылочных переменных естественно возникает вопрос, как работать с динамическим объектом, т.е. как присваивать ему то или иное значение и распоряжаться им? Другими словами, как в программе на языке Паскаль ссылаться на динамический объект? Ведь мы знаем, что динамическим переменным, в отличие от статических, не дается имени в обычном понимании. Поэтому для работы с динамическим объектом в языке используется понятие "переменная с указателем".

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

НАПРИМЕР:

Р - указатель: 5 ряд, 6 место,

Р^ - человек, сидящий в 5 ряду на 6 месте;

или

А - указатель,

А^ - имя динамической переменной, на которую указывает А.

Короче, А^ - переменная "старого" типа, которой можно присваивать конкретные значения.

Итак, пусть в программе содержатся:

VAR P: ^INTEGER; Q: ^CHAR;

NEW (P); NEW (Q).

Теперь в программе порождаются динамические переменные типов INTEGER и CHAR, которым с помощью оператора присваивания можно давать конкретные значения: Р^:= 58; Q^:= 'a'.

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

R:= R + P^ + 2; P^:= P^ div 3.

В качестве ссылочной переменной может использоваться и более сложная конструкция, являющаяся частичной переменной, имеющей соответствующий ссылочный тип. Так, если в программе есть описания ссылочного типа TYPE REFREAL = ^REAL и переменной этого типа VAR A: ARRAY[1..50] OF REFREAL (в силу которого значением переменной А может быть массив элементов ссылочного типа, причем каждая из ссылок указывает на вещественное значение), то в качестве ссылочной переменной может фигурировать переменная с индексом, например, А[2]^ или А[K+5]^. Значением этих переменных с указателями будут вещественные числа. Иначе, определив

TYPE P = ARRAY[1..50] OF INTEGER; VAR B: ^P,

переменная В будет указателем на массив типа Р и в этом случае элементы массива обозначаются так - В^[2], B^[K+5].

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

programm SETUK;

type MN = set of char;

var A,B: ^MN; C: MN; I,D: char;

begin

¦ new(A); A^:= ['a','c','o'];

¦ write('введите букву, которую надо найти:'); rеаdln(d);

¦ if D in A^ then writeln('да')

¦ else writeln('нет');

¦ new(B); B^:= ['g','c','o']; C:= A^ * B^;

¦ writeln('общие буквы множеств:');

¦ for I:= 'a' to 'z' do

¦ if I in C then write (I,' ')

end.

ПОЯСНЕНИЕ. В этой программе используются два динамических множества А и В, а также статическое множество С. Место, занимаемое в памяти под запись множеств A и B может быть освобождено после получения множества C.

ВЫВОДЫ (отличия динамической переменной от статической):

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

  2. В определенном месте программы должно быть предусмотрено порождение каждой из динамических переменных с помощью процедуры NEW.

  3. Для идентификации значения динамической переменной используется переменная с указателем.

11.4 Операции над указателями

Значение одного указателя можно присваивать другому указателю того же типа.

Пусть объявлены переменные VAR Q, R: ^INTEGER и указатель R содержит адрес динамической переменной, значение которой равно 1, а Q - адрес динамической переменной, значение которой равно 2:

NEW(Q); NEW(R); Q^:= 2; R^:= 1.

Тогда оператор Q:= R перешлет в Q тот же адрес, что хранится в R, т.е. теперь Q будет "показывать" на то же значение (ту же ячейку памяти), что и R, а значение, на которое показывало Q раньше, будет навсегда утеряно. Этот процесс представлен схемой:

VAR Q,R:^INTEGER;

R

Q

NEW(R);

R

*

R^

NEW(Q);

Q

*

Q^

R^:=1;

R

*

1

R^

Q^:=2;

Q

*

2

Q^

Q :=R

R

*

1

R^ или Q^


Q

*

2

Потеряно

Можно присваивать значения одной динамической переменной другой, но того же типа. В этом случае значения обеих динамических переменных становятся равными, но значения указателей при этом не изменяются. Например, выполнена команда присваивания Q^:= R^, в результате получим одинаковое значение у двух переменных, как это показано на схеме:

До

После

R

*

1

R

*

1

Q

*

2

Q

*

1

Обмен значениями динамических переменных не изменяет значения ссылочных переменных. В этом случае, в отличие от оператора Q:=R, после выполнения которого Q и R "смотрят" на одну и ту же динамическую переменную, содержащую 1, каждая переменная Q и R указывает на свою динамическую переменную, хотя они обе содержат 1.

ЗАМЕЧАНИЕ. Следует различать пустой и неопределенный указатели. Так, при объявлении с помощью VAR некоторой переменной P (VAR P: ^INTEGER), ее значение является неопределенным. Если же имеет место оператор NEW(P), то ссылочная переменная получает свое конкретное значение - адрес ячейки памяти соответствующей динамической переменной P^. Переменная P может получить значение без оператора NEW только в случае присваивания пустой ссылки P:=NIL или ссылки, уже ранее получившей свое значение с помощью оператора NEW.

Итак, никакие действия со ссылочными переменными нельзя производить до действия оператора NEW. В примере

VAR I,J: ^ INTEGER;

NEW (I);

оператор I:= J не законен, т.к. J еще не определена, но допустим оператор J:=I.

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

program FINDLITER;

type MAS = array [1..100] of string[1];

LINK = ^MAS;

var R, REZSLOVO, TEKSLOVO: LINK;

max,i,k,j: integer;

S: string[100]; BUKWA: string[1];

begin

¦ max:= -1; i:= 0; new(TEKSLOVO); new(REZSLOVO);

¦ writeln('Введите текст: '); readln(S);

repeat

¦ for j:= 1 to length(S) do

¦ begin

¦ ¦ BUKWA:= copy(S,j,1);

¦ ¦ if (BUKWA <> ' ') and (BUKWA <> '.')

¦ then begin i:= i+1;

¦ ¦ TEKSLOVO^[i]:= BUKWA; end

¦ ¦ else if i > max then

¦ ¦ begin max:= i; R:= REZSLOVO;

¦ ¦ REZSLOVO:= TEKSLOVO;

¦ ¦ TEKSLOVO:= R; end;

¦ ¦ i:=0;

¦ end;

¦ until BUKWA = '.';

¦ writeln('Введите букву: '); read(BUKWA); k:= 0;

¦ for i:= 1 to max do

¦ if BUKWA = REZSLOVO^[i] then k:= k+1;

¦ writeln;

¦ write(' В слово максимальной длины: ');

¦ for j:= 1 to max do write(REZSLOVO^[j]); writeln;

¦ writeln (' буква ',BUKWA,' входит ', k,' раз');

end.

Заметим также в заключение, что ссылки, которые указывают на идентичные типы, можно сравнивать друг с другом с помощью знаков "=" и "", при этом P = Q, если:

а) P = NIL, Q = NIL;

b) P и Q указывают на один и тот же динамический объект.

11.5 Действия над динамическими переменными

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

Как уже говорилось выше, динамические переменные призваны более рационально использовать память в процессе работы программы. Рациональность заключается, прежде всего, в том, чтобы убирать из памяти уже ненужные данные. Это достигается с помощью оператора DISPOSE, который имеет вид DISPOSE (R), где DISPOSE - имя процедуры стирания, а R - имя ссылочной переменной, указывающей на динамическую переменную R^, подлежащую удалению.

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

ПРИМЕР 3. Порождение и последующее стирание двух динамических объектов

program UKAZATEL;

Характеристики

Тип файла
Документ
Размер
4,02 Mb
Учебное заведение
Неизвестно

Список файлов ВКР

Свежие статьи
Популярно сейчас
А знаете ли Вы, что из года в год задания практически не меняются? Математика, преподаваемая в учебных заведениях, никак не менялась минимум 30 лет. Найдите нужный учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
6294
Авторов
на СтудИзбе
314
Средний доход
с одного платного файла
Обучение Подробнее