Алгоритмический язык Паскаль
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. Действия над множествами.
Рассмотрим подробнее эти операции.
Принадлежность множеству
В языке Паскаль обеспечен механизм для определения принадлежности некоторого значения множеству его элементов. Этот механизм реализуется в рамках создания булевского выражения с использованием оператора 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. Рассмотрим их синтаксис и назначение.
REWRITE(F)- установка в начальное положение режима записи.
WRITE(F,X) - записывает в файл F (где сейчас стоит окно) очередную компоненту, равную значению выражения X, после чего окно сдвигается вправо на следующую позицию файла:
F
|
F1
|
F2
|
F3
|
|
|
|
F1
|
F2
|
F3
|
X
|
…
|
|
|
|
|
|
^
|
|
|
|
|
|
|
^
|
|
|
|
|
|
окно
|
|
|
|
|
|
|
окно
|
|
|
RESET(F) - перевод в режим чтения и установка окна на первую позицию файла.
READ(F,V) - переменной V присваивается значение текущей позиции файла F, и окно перемещается на следующую позицию.
F
|
F1
|
F2
|
F3
|
F4
|
…
|
|
F
|
F1
|
F2
|
F3
|
F4
|
…
|
|
|
|
|
|
^
|
|
|
|
|
|
|
|
^
|
|
|
|
|
|
окно
|
|
|
|
|
|
|
|
окно
|
|
|
ПРИМЕЧАНИЕ. Файл открывается либо только для записи, либо для чтения - одновременно это делать нельзя!!!
При работе с файлами необходимо знать конец файла. Это делает логическая функция 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^:
Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
|