6. Добавление информации в файлы

Добавление информации в текстовые файлы

Для добавления информации в конец файла файл следует открывать не процедурой Rewrite, а процедурой Append (от англ. Append – добавить):

Append(<Файловая переменная>);

После вызова данной процедуры файл открывается для записи, но информация в нём не стирается, как в случае использования процедуры Rewrite.

Добавление информации в двоичные файлы

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

7. Работа с файловой системой

Удаление файлов

Удаление файлов с диска производится процедурой Erase (англ. Erase – стирать), имеющей следующий вид:

Erase(<Файловая переменная>);

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

Переименование и перенос файлов

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

Rename(<Файловая переменная>, <Новое имя файла>);

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

Работа с каталогами

Если программа работает с множеством изображений, их можно хранить в каталоге с названием Pictures, текстовые файлы можно хранить в каталоге Texts, и так далее.

Создание каталога

Создание каталогов на диске производится оператором MkDir (англ. Make Directory – создать каталог), имеющим следующий вид:

MkDir(<Имя каталога>);

Параметр Имя каталога является строковой переменной или константой, содержащей название каталога по правилам, аналогичным именам файлов. При попытке создания уже существующего каталога информация в нём не стирается, а выдаётся ошибка времени выполнения программы Error 5: File access denied – Ошибка 5: Запрещён доступ к файлу.

Удаление каталога

Удаление каталогов на диске производится оператором RmDir (англ. Remove Directory – удалить каталог), имеющим следующий вид:

RmDir(<имя каталога>);

Изменение текущего каталога

Для изменения текущего каталога – каталога, относительно которого рассматриваются относительные имена файлов и каталогов – используется процедура ChDir (англ. Change Directory – изменить каталог):

ChDir(<Имя каталога>);

Поиск файлов

В Turbo Pascal для этого предназначена пара функций FindFirst (от англ. Find First – найти первый) и FindNext (англ. Find Next – найти следующий), находящиеся в стандартном модуле WinDos.

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

Например, текущим является каталог C:\MyProgs, содержащий следующие файлы: 1.txt, 12.txt, 2.pas, 3.pas, 34.pas.

Тогда маске *.* соответствуют все файлы, так как она задаёт любое сочетание символов в имени и любое сочетание символов в расширении. Маске 1*.* соответствуют файлы 1.txt и 12.txt как начинающиеся с символа 1, имеют любое сочетание символов после него и любое расширение. Маске 1?.* соответствует только один файл – 12.txt, как состоящий из двух символов – 1 и любого другого. Соответственно, маске *.zzz не соответствует ни одного файла, так как файлов с таким расширением в каталоге нет.

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

1. Процедура FindFirst ищет первый файл, имя которого соответствует маске:

FindFirst (<Маска>, <Флаги поиска>, <Приёмник информации о найденном файле>);

Параметр Маска является строковой переменной или константой и задаёт маску, которой должны соответствовать имена найденных файлов.

Параметр Флаги поиска определяет типы файлов, которые рассматриваются при поиске и является набором специальных констант, объединённых бинарным оператором OR:

Таблица 6

Флаги поиска файлов

Флаг

Тип файла

faReadOnly

Искать файлы с атрибутом «Только для чтения»

faHidden

Искать файлы с атрибутом «Скрытый»

faSysFile

Искать файлы с атрибутом «Системный»

faVolumeld

Возвратить идентификатор носителя

faDirectory

Возвращать информацию о найденных каталогах

faArchive

Искать файлы с атрибутом «Архивный»

faAnyFile

Искать файлы с любым атрибутом

Параметр Приёмник информации о найденном файле представляет собой переменную сложного типа TsearchRec, описанного в модуле WinDos следующим образом:

Type

TSearchRec = Record {заголовок описания типа данных}

Fill: Array[1..21] Of Byte; {зарезервировано и не используется}

НЕ нашли? Не то? Что вы ищете?

Attr: Byte; {атрибуты найденного файла (см. табл.)}

Time: LongInt; {время создания файла}

Size: LongInt; {размер файла}

Name: Array[0..12] Of Char; {имя найденного файла}

end;

2. В глобальной целочисленной переменной DosError модуля WinDos находится результат выполнения операции поиска файла (0 – поиск произведён успешно, ненулевое значение – файл не обнаружен).

3. Процедура FindNext ищет следующий файл, соответствующий условиям поиска, заданным при последнем вызове процедуры FindFirst:

FindNext(<Приёмник информации о найденном файле >);

Тема 7: ОРГАНИЗАЦИЯ ХРАНЕНИЯ ИНФОРМАЦИИ.

Лекция 28

Цельпознакомить студентов с динамическими массивами и списками

1. Динамические структуры данных

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

Type {раздел описания типов данных}

THuman = Record {описание типа данных запись}

Name: String; {поле для хранения имени сотрудника}

Patronymic: String; {поле для хранения отчества сотрудника}

SurName: String; {поле для хранения фамилии сотрудника}

HomeTel: Array[1..6] Of Char; {поле для хранения домашнего телефона сотрудника}

MobilTel: Array[1..11] Of Char; {поле для хранения мобильного телефона сотрудника}

end; {окончание описания типа данных}

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

Var {раздел описания переменных}

Humans: Array[1..20] Of THuman; {описание переменной – массива из двадцати элементов типа THuman}

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

Var {раздел описания переменных}

Humans: Array[1..82] Of THuman; {описание переменной – массива }

Кроме данных о сотруднике может храниться информация о транспортном средстве.

Type {раздел описания типов данных}

TCar = Record {описание типа данных запись}

Name: String; {поле для хранения марки транспортного средства}

Number: Array[1..8] Of Char; {поле для хранения государственного регистрационного номера}

Year: Integer; {поле для хранения года выпуска}

end; {окончание описания типа данных}

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

Var {раздел описания переменных}

Cars: Array[1..10] Of TCar; {описание переменной – массива из десяти элементов типа TCar}

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

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

Динамические массивы

Type {раздел описания типов данных}

THuman = Record {описание типа данных запись для хранения сведений об одном сотруднике}

Name: String;

Patronymic: String;

SurName: String;

HomeTel: Array[1..6] Of Char;

MobilTel: Array[1..11] Of Char;

end;

THumans = Array[1..82] Of THuman; {описание типа данных – массива элементов типа THuman}

PHumans = ^THumans; {описание типа данных – типизированного указателя на массив элементов типа THuman}

Var {раздел описания переменных}

Humans: PHumans; {описание переменной – типизированного указателя на массив записей типа THuman}

Выделить место в Heap-памяти под типизированный указатель можно сделать вызовом процедуры New. Освобождение памяти в таком случае следует провести процедурой Dispose.

Переменная Humans представляет собой типизированный указатель на массив записей. Указание оператора разыменовывания после имени данной переменной указывает на необходимость обращения к самому массиву: Humans^. Указание индекса элемента в квадратных скобках указывает на необходимость обращения к элементу с данным индексом.

Описанная структура занимает в сегменте данных всего четыре байта (один типизированный указатель), а в динамической памяти – 82*785 = 64370 байтов (размер записи типа THuman составляет 785 байт, массив содержит 82 таких записи). Таким образом, при размещении структуры THumans в сегменте данных памяти на другие переменные практически не остаётся, что решается размещением такой же структуры в Heap-памяти, размер которой значительно больше (почти в десять раз).

Если нужно выделить место в динамической памяти не под весь массив, а под несколько его элементов, то для этого нужно воспользоваться не процедурой New, а процедурой GetMem, которой пользуются при работе с нетипизированными указателями. Соответственно, освобождение выделенной памяти будет осуществляться не процедурой Dispose, а процедурой FreeMem.

Списки

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

Type {раздел описания типов данных}

PRec = ^TRec; {описание типа данных – типизированного указателя на запись типа TRec; описание типа данных TRec располагается ниже}

TRec = Record {заголовок описания типа данных TRec }

……………. {описание полей записи TRec }

End; {окончание описания типа данных TRec }

В приведённом примере область видимости идентификатора PRec начинается с момента его описания и заканчивается в конце программы. Таким образом, возможно описание поля записи TRec, имеющего тип PRec, то есть запись может ссылаться одним или несколькими своими полями на записи такого же типа, в том числе и на себя.

Type {раздел описания типов данных}

PRec = ^TRec; {описание типа данных – типизированного указателя на запись типа TRec; описание типа данных TRec располагается ниже}

TRec = Record {заголовок описания типа данных TRec}

Next: PRec; {описание поля типа PRec – типизированного указателя на запись типа TRec}

……………. {описание информативных полей записи TRec}

End; {окончание описания типа данных TRec}

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

На первую запись списка ссылается типизированный указатель, расположенный в сегменте данных (описанный в разделе описания переменных Var).

Лекция 29

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

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

1. Описание типа данных «Запись», содержащую информативные поля для хранения информации, которая должна находиться в каждой записи, и одно поле – типизированный указатель на такую же запись.

2. Описание переменной – типизированного указателя в разделе описания переменных Var.

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

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

5. Методы работы со второй записью списка стандартны – для неё допустимы обычные операции с записями, например, изменение значений полей, а также выделение памяти под поле-указатель на следующую запись.

Type

PRec = ^TRec;

TRec = Record

Next: PRec; {описание поля типа PRec – типизированного указателя на запись типа TRec }

Name: String; {описание информационного поля (предназначенного для хранения информации)}

End;

Var

First: PRec; {описание переменной – типизированного указателя}

Begin

New (First); {выделение памяти под переменную-указатель}

First^.Name := ‘795FT+’; {изменение значения информационного поля}

New(First^.Next); {выделение динамической памяти под поле-указатель}

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

First^.Next^.Name := ‘CTXPR711’; {изменение значения информационного поля второй записи списка}

New(First^.Next^.Next); {выделение динамической памяти под поле-указатель второй записи списка}

End.

ПРИМЕР 18. Упростить процесс доступа к конкретной записи по её номеру можно с помощью зацикливания оператора присваивания вида:

<Переменная типа PRec> := <Переменная типа PRec>^.Next;

Type {описание типов данных, необходимых для создания списка}

PRec = ^TRec;

TRec = Record

Next: PRec;

Name: String;

End;

Var

First: PRec; {описание переменной, ссылающейся на начало списка}

Function GetRecPointer (recNum: Integer) : Prec;

{заголовок функции; целочисленный параметр определяет номер записи, ссылку на которую нужно вернуть; тип возвращаемого значения – Prec (ссылка на запись TRec)}

Var

i: Integer; {переменная для использования в качестве счётчика цикла}

tmpRec: PRec; {переменная для временного хранения ссылки на запись}

Begin

tmpRec := First; {по умолчанию считаем, что возвращается ссылка на первую запись}

For i := 2 To RecNum Do {цикл от 2 до номера необходимой записи; выполняется RecNum-1 раз, что обеспечивает перемещение по списку от первой записи, ссылка на которую находится в переменной tmpRec до необходимой, заданной параметром RecNum}

tmpRec := tmpRec^. Next; {в переменную tmpRec помещаем ссылку на запись, следующую за той, на которую указывает переменная tmpRec до изменения (на первом шаге цикла переменная tmpRec указывает на первую запись)}

GetRecPointer := tmpRec; {возвращаем значение переменной tmpRec в качестве значения функции}

end;

…………………………..

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

ПРИМЕР 19. Работа со списком.

1. Пусть описание записи имеет следующий вид:

Type

PListRec = ^TListRec; {описание типа данных для ссылок на записи, из которых состоит список}

TListRec = Record {описание типа данных для записей, из которых состоит список}

Name: String; {поле для хранения имени}

Patronymic: String; {поле для хранения отчества}

SurName: String; {поле для хранения фамилии}

Telephone: String[30]; {поле для хранения телефона}

Next: PListRec; {поле для ссылки на следующую запись}

End;

2. Процедура CreateList создаёт список, выделяя память под его первый элемент.

Procedure CreateList (var First: PListRec; var ListSize: Integer);

Параметр First задаёт ссылку на начало списка, параметр ListSize – размер списка. Оба параметра являются параметрами-переменными, так как процедура изменяет их значения – выделяет место под переменную First и заносит единицу в переменную ListSize.

3. Функция GetRecord – возвращает ссылку на заданный элемент заданного списка и имеет следующий заголовок:

Function GetRecord (First: PListRec; RecNum: Integer) : PListRec;Данная функция аналогична рассмотренной функции GetRecPointer. Параметр First задаёт ссылку на начало списка, параметр RecNum – номер записи, указатель на которую необходимо возвратить.

4. Процедура AddRecord – добавляет элемент в конец списка, получая последний текущий элемент и выделяя память под его поле Next.

Procedure AddRecord (First: PListRec; var ListSize: Integer);

Параметр First задаёт ссылку на начало списка, параметр ListSize – размер списка. Параметр ListSize является параметром-переменной, так как его значение изменяется процедурой (увеличивается на единицу).

5. Процедура DeleteRecord – удаляет последний элемент списка, получая ссылку на предпоследний текущий элемент и освобождая память, выделенную под его поле Next.

Procedure DeleteRecord (First: PListRec; var ListSize: Integer);

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

6. Процедура DestroyList удаляет список из памяти, последовательно работая с его последними элементами. После удаления всех элементов с номерами большими единицы, освобождается память, выделенная под переменную First.

Procedure DestroyList (var First: PListRec; var ListSize: Integer);

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

Тема 8. РЕКУРСИЯ

Лекция 30

Цельпознакомить студентов с рекурсией и использованием рекурсии

1. Рекурсия

Рекурсия – это вызов подпрограммой (процедурой или функцией) самой себя.

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

Размер стека ограничен и не может содержать элементов более, чем указано в настройках компилятора (пункт главного меню интегрированной среды разработчика Options | Memory Syze).

Структуры данных, в которых происходит добавление элемента и извлечение первого элемента, называют очередями. Для стеков используется обозначение LIFO (Last In First Out – пришёл последним, выйдет первым), для очередей используется обозначение FIFO (First In First Out – первым пришёл, первым выйдет).

Когда программа пытается добавить в полностью заполненный стек ещё один элемент, её выполнение прерывается и на экран выдаётся сообщение об ошибке Error 202: Stack overflow errorОшибка 202: Переполнение стека.

2. Использование рекурсии

ПРИМЕР 20. Имеется прямоугольное поле некоторого размера, разбитое на клетки. Начав с левой верхней клетки, нужно последовательно расставлять числа в клетках ходом шахматного коня, то есть месторасположение двух соседних чисел должно отличаться на две клетки в одну сторону и на одну клетку в перпендикулярном направлении. Задача – заполнить все клетки.

Решение. На каждом шаге (для текущей клетки с номером N) выполняются одни и те же действия:

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

2.  Если решение ещё не найдено (заполнены не все клетки), то:

·  по правилу перемещения коня определяются клетки, в которых ещё не находятся числа;

·  из этих клеток выбирается одна, в неё ставится очередное число (N + 1) и весь процесс повторяется для новой клетки;

·  если свободных клеток нет, то следует возвратиться на шаг назад и выбрать другую клетку для установки в неё числа N.

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

Field: Array [1..20, 1..20] Of Integer;

Две глобальные переменные MaxX и MaxY, вводимые пользователем, определяют реальные размеры поля, для которого необходимо найти решение задачи:

MaxX, MaxY: Integer;

Глобальная переменная N определяет текущее число (номер шага):

N: Integer;

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

Procedure WritePosition;

Var

x, y: Integer; {описание локальных переменных для использования в качестве счётчиков цикла}

begin

Clrscr;

for y :=1 to MaxY do {внешний цикл по высоте поля}

for x :=1 to MaxX do {вложенный цикл по ширине поля}

Begin

GotoXY(x*4, y*2); {позиционирование каретки – расстояние между клетками будет 4 символа по ширине и 2 символа по высоте}

Write(Field[x, y]); {вывод числа из очередной ячейки поля на экран}

End;

End;

Рекурсивная процедура, осуществляющая перебор всех возможных вариантов заполнения клеток поля:

Procedure Move (x, y: Integer); {заголовок процедуры, параметры х и у определяют координаты клетки на поле}

begin

N:=N + 1; {увеличение текущего числа на единицу}

Field[x, y] := N; {занесение текущего числа в текущую клетку}

If N = MaxX * MaxY Then {если текущее число равняется количеству клеток на поле, значит, все клетки заполнены и задача решена}

Begin

WriteLn (‘Есть решение!!!’);

WritePosition;

ReadKey;

Halt;

End;

{сообщение пользователю об успешном решении задачи, вывод на экран заполненного поля, ожидание нажатия на клавишу и выход из программы (использована процедура Halt безусловного завершения программы)}

If (x+2<=MaxX) and (y+1<=MaxY) Then

{если клетка, находящаяся на две клетки правее и на одну клетку ниже текущей клетки, не выходит за пределы рабочего пространства игрового поля, ограниченного значениями переменных MaxX и MaxY, то выполняется следующая строка}

If Field[x+2, y+1]=0 Then move(x+2,y+1);

{если клетка, находящаяся на две клетки правее и на одну клетку ниже текущей клетки содержит число 0, то для неё следует выполнить процедуру Move}

If (x+1<=MaxX) and (y+2<=MaxY) Then

If Field[x+1, y+2]=0 Then move(x+1,y+2);

{аналогичная проверка для клетки, находящейся на одну клетку правее и две клетки ниже текущей клетки}

If (x-1>0) and (y+2<=MaxY) Then

If Field[x-1, y+2]=0 Then move(x-1,y+2);

{аналогичная проверка для клетки, находящейся на одну клетку левее и две клетки ниже текущей клетки }

If (x-2>0) and (y+1<=MaxY) Then

If Field[x-2, y+1]=0 Then move(x-2,y+1);

{аналогичная проверка для клетки, находящейся на две клетки левее и одну клетку ниже текущей клетки }

If (x-2>0) and (y-1>0) Then

If Field[x-2, y-1]=0 Then move(x-2,y-1);

{аналогичная проверка для клетки, находящейся на две клетки левее и одну клетку выше текущей клетки }

If (x-1>0) and (y-2>0) Then

If Field[x-1, y-2]=0 Then move(x-1,y-2);

{аналогичная проверка для клетки, находящейся на одну клетку левее и две клетки выше текущей клетки }

If (x+1<=MaxX) and (y-2>0) Then

If Field[x+1, y-2]=0 Then move(x+1,y-2);

{аналогичная проверка для клетки, находящейся на одну клетку правее и две клетки выше текущей клетки }

If (x+2<=MaxX) and (y-1>0) Then

If Field[x+2, y-1]=0 Then move(x+2,y-1);

{аналогичная проверка для клетки, находящейся на две клетки правее и одну клетку выше текущей клетки }

N := N – 1;

Field[x,y] := 0;

{если управление перешло к этой строке, значит, все перемещения относительно текущей клетки закончились неудачно, следовательно, текущий номер нужно удалить, а в текущую клетку поставить признак незанятости (значение 0)}

end; {возврат к предыдущему шагу}