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; {возврат к предыдущему шагу}


![]() |



