Файлы
Файлы используются для хранения результатов работы,
данных, которые могут потребоваться при следующем запуске программы.
Через файлы одна программа может передать информацию другой. В Паскале
используются файлы MS-DOS, но по-своему. Само понятие файла не
изменилось. По прежнему под этим словом понимается последовательность
байтов, снабженная именем.
Имя записывается по правилам MS-DOS. Скажем, что-то вроде d:\tp7\bin\tania.pas.
Смысл последовательности байтов зависит от типа файла, а проще говоря,
от того, какие данные туда записали. Это может быть текст, машинный код,
массив целых чисел, набор записей, база данных, таблица и т.п. Для
человека файл непрерывен, т.е. составляющие его байты идут друг за
другом без перерыва. Файлы в чем-то напоминают массивы, однако в отличие
от них имеют переменную длину. Размер файла ограничен только свободным
местом на жестком диске. В конец файла можно добавлять новые данные, его
можно очистить и т.д. (еще более близкую аналогию можно провести со
строками).
Определение файла с точки зрения Паскаля: файл - это непрерывная
упорядоченная последовательность элементов одного типа произвольной
длины.
Т.е. паскалевские файлы могут состоять только из целых чисел, только из
строк, только из записей какого-то одного типа, но никак из всего этого
вперемешку.
В Паскале есть 3 разновидности файлов - файлы с типом ( типизированные
файлы, как раз те, о которых говорилось), файлы без типа (или, вернее, с
произвольным типом) и текстовые файлы (файлы из строк в специальном
оформлении). Они несколько отличаются в использовании, поэтому
особенности работы для каждой разновидности излагаются отдельно.
Общие принципы работы с файлами.
Файлы MS-DOS в Паскале называются "внешними файлами".
Программа общается с ними не напрямую, а через специальную файловую
переменную. Иногда ее еще называют файловым указателем.
Для того, чтобы получить доступ к внешнему файлу, необходимо во-первых,
связать его с файловой переменной, а во-вторых, открыть его. Открытие
файла инициализирует внутренние структуры MS-DOS, без которых работа с
файлом невозможна. В зависимости от типа работы файл можно открыть
несколькими способами ( для чтения, для записи, для добавления).
После выполнения этих двух операций файловая переменная окажется
доступной для использования.
Сущность файловой переменной в том, что она указывает на один из
элементов внешнего файла, с которым она связана. В процессе работы
файловая переменная может перемещаться с одного элемента на другой.
Причем в каждый момент времени доступен только один элемент файла - тот,
на который указывает файловая переменная.
-----------T----------T----------T----------T---------T--------------¬
¦1-ый элем.¦2-ой элем.¦3-ий элем.¦4-ый элем.¦ ..... ¦Конечный элем.¦
L----------+----------+----------+----------+---------+---------------
------------¬ ^
¦файловая ¦ ¦
¦переменная-+----
¦указатель ¦
L------------
На рисунке файловая переменная установлена на 2-ой элемент файла. Это
означает, что если мы сейчас запишем что-нибудь в файл, то это
"что-нибудь" попадет на место 2-го элемента, а файловая переменная
"переедет"
на 3-ий элемент.
Работа с файлом в основном заключается в чтении из файла данных, записи
их в файл и перемещении файловой переменной по файлу для выбора нужного
элемента. Причем обычно доступно либо чтение, либо запись (смотря как
открыт файл), но не оба действия сразу.
После операции чтения или записи файловая переменная устанавливается на
следующий за последним прочитанным или записанным элемент. Например,
если файловая переменная указывала на 5-ый элемент и было прочитано 3
элемента, она будет указывать на 9-ый. Поэтому при последовательном
(элемент за элементом) чтении из файла или записи в него нет
необходимости специально устанавливать файловый указатель.
После открытия файла файловая переменная всегда указывает на первый
элемент файла.
После того, как проделаны все необходимые операции с файлом, файл
необходимо закрыть. Это действие освобождает внутренние структуры MS-DOS,
которые были выделены файлу при открытии. Если этого не сделать, запас
этих структур быстро исчерпается, и машина "зависнет", а Ваша программа,
возможно, потеряется.
Итак процесс работы с файлом в Паскале всегда включает в себя 4 этапа:
1. Связывание файловой переменной с внешним файлом. Отныне вся работа с
этим файлом происходит через файловую переменную.
2. Открытие файла.
3. Обработка файла - чтение и/или запись, позиционирование.
4. Закрытие файла.
Это справедливо для всех разновидностей файлов. Однако процедуры,
выполняющие данные действия, немного отличаются для разных типов файлов.
Замечание. В дальнейшем изложении файловый указатель, указатель файла -
это то же, что и файловая переменная; компонента файла - то же, что и
его элемент.
Работа с типизированными файлами.
Типизированные файлы состоят из непрерывной
последовательности компонент одного типа. Тип компонент может быть любым
разрешенным в Паскале, за исключением, конечно, файловых. Может быть
массивом, записью, множеством и т.д.
Файловая переменная типизированного файла объявляется следующим образом:
var <имя файловой переменной> : file of <тип компонент>;
Операции чтения-записи производятся покомпонентно, т.е. одна компонента
за один раз. В типизированный файл можно записывать данные только
совместимого с <тип компонент> типа.
Рассмотрим процедуры и функции работы с типизированными файлами.
1. Связывание файловой переменной с внешним файлом выполняется с по-
мощью процедуры
Assign( <файловая переменная>, <имя внешнего файла>: string );
<имя внешнего файла> записывается по правилам MS-DOS. Файл с этим именем
может и не существовать, однако обязан существовать каталог, в котором
должен находится файл. Если каталог не указан, каталогом по умолчанию
считается тот, из которого была запущена программа.
2. Открытие файла. Типизированный файл можно открыть двумя способами -
для записи и для чтения:
2.1. Rewrite( <файловая переменная> );
Открывает файл, связанный с <файловой переменной>, для записи. Создает
новый файл, если его не было. Если файл с таким именем уже был, он
очищается (становится пустым, все информация из него исчезает). В файл
можно записывать информацию сразу за концом файла.
2.2. Reset( <файловая переменная> );
Открывает существующий файл, связанный с <файловой переменной>, для
чтения. Если такого файла нет, возникает ошибка ввода-вывода. Файловая
переменная устанавливается на первый элемент файла. Из файла можно
читать информацию. Можно также записывать, но только в конец файла (за
последним уже записанным элементом).
3. Операции ввода-вывода. Для чтения из типизированного файла
используется стандартная процедура read, для записи - стандартная
процедура write.
3.1. Read( <файловая переменная>, <список ввода> );
Читает из файла, связанного с <файловой переменной>, элементы и заносит
их по порядку в переменные, указанные в <списке ввода>. В <списке ввода>
могут присутствовать только переменные, причем их тип дол- жен быть
совместим с <типом компонент> файла. Будет прочитано столько компонент,
сколько переменных в <списке ввода>. После выполнения процедуры
указатель файла будет установлен на следующий за последним прочитанным
элемент. Читать можно из только из файла, открытого для чтения.
3.2. Write( <файловая переменная>, <список вывода> );
Записывает в файл, связанный с <файловой переменной>, переменные,
указанные в <списке вывода>. В <списке вывода> могут присутствовать
только переменные, причем их тип должен быть совместим с <типом
компонент> файла. Будет записано столько компонент, сколько переменных в
<списке вывода>. После выполнения процедуры указатель файла будет
установлен на следующий за последним записанным элемент. Записывать
можно только в конец файла.
4. Закрытие файла осуществляется с помощью процедуры
Close( <файловая переменная> );
Закрыть можно только открытый файл. Попытка закрыть неоткрытый файл
приводит к ошибке ввода-вывода. При закрытии не разрывается связь между
<файловой переменной> и внешним файлом, так что файл можно открыть еще
раз, не выполняя операцию связывания заново. В закрытый файл нельзя
писать и из него нельзя читать (возникает ошибка ввода-вывода).
5. Дополнительные процедуры и функции. Существуют несколько функций и
процедур, которые выполняют позиционирование, определяют длину файла и
т.д.
5.1. FileSize( <файловая переменная> ) : LongInt
Функция возвращает количество компонент в файле в настоящее время.
Файл уже должен быть открыт. Заметьте, что возвращается не длина файла в
байтах, а именно количество компонент. Например, если в файл записано 10
чисел типа integer, его размер - 10, а длина в байтах - 20 (т.к. тип
integer занимает 2 байта).
5.2. FilePos( <файловая переменная> ) : LongInt
Функция возвращает номер текущей компоненты в файле (той, на которую
указывает <файловая переменная>). Первая компонента имеет номер 0!
5.3. Seek( <файловая переменная>, <номер компоненты> : LongInt );
Процедура передвигает указатель файла (<файловую переменную>) на
компоненту с указанным номером. Первая компонента имеет номер 0.
Например, Seek(f,0) устанавливает указатель в самое начало файла f, а
Seek(f, FileSize(f)) - в конец этого файла.
5.4. EoF( <файловая переменная> ) : Boolean
Функция возвращает логическое значение, показывающее, достигнут ли конец
файла. Для того, чтобы функция вернула true, необходимо любым способом
позиционировать указатель файла на следующую за последней компонентой
(это и будет конец файла). Например, после того, как Вы прочитаете
последний элемент файла, функция вернет true.
Пример каркаса программы, поэлементно обрабатывающей файл из
вещес-твенных чисел:
var f: file of real;
r: real;
...
begin
...
assign(f, 'd:\tp7\bin\tania.dat');
reset(f);
while not eof(f) do { пока не конец файла }
begin
read(f, r);
{ что-то сделать с переменной r }
end;
close(f);
...
end.
Работа с текстовыми файлами.
Текстовые файлы состоят из последовательности строк,
записанной в специальном формате. Текстовые файлы содержат текст
(а что же еще?). Формат имеет такой вид:
---------------------------------------T--T--¬
¦Текстовый файл представляет собой пос-¦CR¦LF¦L--------------------------------------+--+---
------------T--T--¬¦ледователь-¦CR¦LF¦L-----------+--+------------------------T--T--¬
¦ность строк, каждая ¦CR¦LF¦L--------------------+--+---------------------------------------------------T--T--¬
¦из которых заканчивается парой символов CR/LF, ¦CR¦LF¦L-----------------------------------------------+--+---
----------------------------------------------T--T--¬¦а конец файла определяется по символу Сtrl-Z.¦CR¦LF¦
L---------------------------------------------+--+----------¬¦Ctrl-Z¦L-------
CR, LF, и Ctrl-Z - это специальные управляющие символы, которые не имеют
графического изображения. Поэтому когда Вы смотрите текст, скажем, в
редакторе Norton Commander'а, их не видно. Но они есть! Пара символов CR/LF
разделяет строки между собой (CR расшифровывается как Carett Return -
возврат каретки, а LF - Line Feed, перевод строки; исторически
сложилось, что они употребляются парой, хотя для разделения строк
достаточно и одного). Символ Ctrl-Z сигнализирует о конце текста. Как
только Паскаль встречает этот символ, он считает, что конец файла
достигнут. На самом деле файл может продолжаться, но... все, что после
Ctrl-Z, игнорируется.
Наиболее существенное отличие текстовых файлов от типизированных -
непостоянный размер элементов. Элементы-строки текстового файла имею
различную длину (см. схему), а элементы типизированного файла все одного
типа, и потому их "длина" (размер в байтах) фиксирована.
Поэтому текстовые файлы являются файлами с последовательным доступом.
Т.е. чтобы прочитать 10-ую строку, надо прочитать предыдущие 9 - доступ
к элементам осуществляется друг за другом, последовательно. В
типизированных же файлах можно установить указатель файла на элемент с
любым номером и прочитать его. Типизированные файлы - файлы с
произвольным доступом. К текстовым файлам неприменимы функции FilePos,
FileSize, процедура Seek - именно по этой причине. Файловая переменная
для текстового файла объявляется так:
var <имя файловой переменной>: text;
Запись/чтение в текстовые файлы имеет ряд особенностей. В текстовые
файлы можно записывать (и читать из них) данные строкового, символьного,
вещественного и целого типов. В принципе, минимальным элементом
текстового файла является символ (
char), т.е. текстовый файл - это типизированный file of char в
специальном формате. Соответственно текстовый файл можно читать
посимвольно. Однако наиболее часто считается, что элементом текстового
файла является строка, и все операции записи/чтения происходят
построчно. Числа, целые и вещественные, записываются в текстовый файл в
том же виде, в каком выводятся на экран. Вообще вывод в текстовый файл и
чтение из него имеют много общего с теми же операциями на экране. Т.е.
как Вы выводите на экран, можно выводить и в текстовый файл - результат
по внешнему виду будет тот же. И так, как Вы читаете с клавиатуры, можно
читать и из текстового файла.
Процедуры и функции работы с текстовыми файлами во многом повторяют те
же процедуры работы с типизированными файлами, поэтому привожу только
отличия:
1. Связывание текстовых файлов выполняется точно так же, как и
связывание типизированных.
2. Открыть текстовый файл можно тремя способами: для записи, для чтения
и для добавления. Имейте в виду, что как текстовый можно открыть любой
существующий файл (даже исполнимый), но если там содержится не текст,
прочитать из него Вы сможете только абракадабру, беспорядочную мешанину
символов.
2.1. Rewrite( <файловая переменная> );
Открывает файл, связанный с <файловой переменной>, для записи. Создает
новый файл, если его не было. Если файл с таким именем уже был, он
очищается (становится пустым, все информация из него исчезает). В файл
можно записывать информацию сразу за концом файла.
2.2. Reset( <файловая переменная> );
Открывает существующий файл, связанный с <файловой переменной>, для
чтения. Если такого файла нет, возникает ошибка ввода-вывода. Файловая
переменная устанавливается на первый символ файла. Из файла можно только
читать.
2.3. Append( <файловая переменная> );
Открывает существующий файл, связанный с <файловой переменной>, для
добавления. Если такого файла нет, возникает ошибка ввода-вывода.
Файловая переменная устанавливается на конец файла. К концу файла можно
добавлять новую информацию. Читать из файла нельзя.
3. Операции записи- чтения. Для записи в текстовый файл и чтения из него
используются стандартные процедуры записи Write и WriteLn и чтения Read
и ReadLn. От тех, к которым Вы привыкли, они отличаются только тем, что
первым аргументом у них указывается файловая переменная, связанная с
файлом из которого Вы хотите читать (записывать).
3.1. Read( <файловая переменная>, <список ввода> );
Читает из файла, связанного с <файловой переменной>, элементы и заносит
их по порядку в переменные, указанные в <списке ввода>. В <списке ввода>
могут присутствовать переменные строкового, символьного, целого и
вещественного типов. Что именно читается, зависит от типа переменной:
- строковая - в переменную заносятся все символы до конца строки (т.е.
до CR/LF). Сами символы конца строки в переменную не заносятся. Если вся
строка из файла в переменную не помещается, записывается сколько
поместится.
- символьная - в переменную заносится текущий символ, включая символы CR,
LF
, Ctrl-Z.
- целая или вещественная - пропускаются пробелы и другие разделители и
считывается значение арифметической константы до появления пробела,
символа конца строки или файла. Символ-ограничитель не считывается.
Чтение происходит до символа конца строки или файла. Сам символ конца
строки не считывается. Если строка закончилась раньше, чем исчерпался
<список ввода> (т.е. достигнут символ конца строки, а в <списке ввода>
еще есть невведенные переменные), оставшиеся переменные получают
значение: строковая - пустую строку, целая или вещественная переменная -
0, в символьную заносятся и управляющие символы.
3.2. ReadLn( <файловая переменная>, <список ввода> );
Отличается от Read только тем, что после прочтения данных в переменные
из <списка ввода> пропускаются все оставшиеся символы в данной строке,
включая символы конца строки (CR/LF). Если <список ввода> отсутствует,
происходит переход к следующей строке.
3.3. Write( <файловая переменная>, <список вывода> );
Записывает в файл, связанный с <файловой переменной>, переменные,
указанные в <списке вывода>. В <списке вывода> могут присутствовать
любые переменные и выражения, допустимые при выводе на экран. Можно
использовать форматированный вывод (т.е. выражения вида x:5:2). Элементы
списка вывода выводятся по тем же правилам, что и на экран. После
выполнения процедуры указатель файла будет установлен на следующий за
последним записанным символ. Записывать можно только в конец файла.
3.4. WriteLn( <файловая переменная>, <список вывода> );
Отличается от Write только тем, что после вывода в файл всего <списка
вывода> он заносит туда символы конца строки (CR/LF), так что следующий
вывод начнется с новой строки.
4. Закрытие файла осуществляется с помощью процедуры Close( <файловая
переменная> ); так же, как и для типизированных файлов.
5. Дополнительные процедуры и функции. Функции FileSize,
FilePos и процедура Seek с текстовыми файлами не работают.
5.1. EoF( <файловая переменная> ) : Boolean
Эта функция так же, как и для типизированных файлов, определяет,
достигнут ли конец файла.
5.2. SeekEoF( <файловая переменная> ) : Boolean
Аналогична EoF, однако до проверки на конец файла пропускает все
пробелы, разделители, символы конца строки (т.е. незначащие символы).
5.3. EoLn( <файловая переменная> ) : Boolean
Эта функция определяет, достигнут ли конец строки (т.е. является ли
текущий символ символом CR/LF).
5.4. SeekEoLn( <файловая переменная> ) : Boolean
Аналогична EoLn, однако до проверки на конец строки пропускает все
пробелы и разделители.
Пример чтения из текстового файла.
Пусть указатель файла f установлен в начале строки
'It's great! a 475.23 2 rt' CR/LF
Тогда после выполнения оператора
var s1, s2, s3: string[12];
c: char;
i: integer;
r: real;
...
read(f, s1, c, r, i, s2, s3);
переменные из списка вывода получат следующие значения
s1 = 'It's great! ' r = 475.23 s2 = 'rt'
c = 'a' i = 2 s3 = ''
Следующая попытка чтения из файла f без перехода на новую строку не
приведет ни к чему (переменные не получат значений), т.к. достигнут
конец строки CR/LF, а процедура Read не может через него перейти
(необходимо поставить Readln(f); только после этого можно читать
следующую строку). Отсюда совет: используйте Read, только если Вы
читаете строку по частям. Если все информацию из нее Вы считываете одним
оператором, пусть это будет ReadLn.
Работа с файлами без типа.
Файлы без типа состоят из компонент одинакового размера,
структура которых не важна или не имеет значения. Такая разновидность
обычно используется, например, чтобы скопировать один файл в другой или
выполнить над ним какое-то преобразование (скажем, переставить
компоненты).
Файловая переменная для файла без типа объявляется так:
var <имя файловой переменной> : file;
В файл без типа могут записываться любые данные, лишь бы их размер
соответствовал размеру компонент.
Процедуры и функции:
1. Связывание выполняется так же (с помощью Assign).
2. Открыть файл без типа можно двумя способами:
2.1. Rewrite( <файловая переменная> [, <размер компонент>] );
Открывает файл, связанный с <файловой переменной>, для записи. Создает
новый файл, если его не было. Если файл с таким именем уже был, он
очищается. В файл можно записывать информацию сразу за концом файла.
Второй параметр определяет размер компонент файла. Если он отсутствует,
то размер компонент принимается 128 байт.
2.2. Reset( <файловая переменная> [, <размер компонент>] );
Открывает существующий файл, связанный с <файловой переменной>, для
записи/чтения. Если такого файла нет, возникает ошибка ввода-вывода.
Файловая переменная устанавливается на первый символ файла. Из файла
можно читать и в него можно записывать. Второй параметр определяет
размер компонент файла. Если он отсутствует, то размер компонент
принимается 128 байт.
3. Операции записи- чтения. Для записи в файл без типа используется
процедура блоковой записи BlockWrite, для чтения из него - блокового
чтения BlockRead.
3.1. BlockRead( <ф.п.>, var <буфер>, <количество> [, var <результат>] );
Процедура читает из файла, связанного с <файловой переменной>,
<количество> компонент и записывает их по порядку в <буфер> (некую
область памяти, в которой достаточно для этого места. Например, это
может быть массив array [1..10000] of char). Реальное количество
прочитанных компонент заносится в <результат>. Если четвертый параметр
отсутствует, а количество запрошенных компонент не совпадает с
количеством прочитанных, возникает ошибка ввода-вывода. Компоненты имеют
размер, указанный при открытии файла. Для <буфера> контроль типов не
производится, так что это может быть любая структура (однако, повторяю,
способная вместить прочитанную информацию).
3.2. BlockWrite( <ф.п.>, var <буфер>, <количество> [, var <результат>]);
Процедура записывает в файл <количество> компонент из <буфера> в файл,
связанный с <файловой переменной>. Реальное количество записанных
компонент заносится в <результат>. Если четвертый параметр отсутствует и
количество записанных компонент не совпадает с требуемым, возникает
ошибка ввода-вывода.
4. Закрытие файла без типа происходит точно так же, как и других, с
помощью процедуры Close.
5. Дополнительные процедуры и функции у файлов без типа те же, что и у
типизированных файлов (FilePos, FileSize, Seek, EoF). Работают они точно
также, только размер компонент определяется не типом файла, а указан при
его открытии.
Пример копирования файла при помощи файлов без типа.
var f1, f2: file;
buffer: array[1..1024] of char;
result: word;
...
begin
...
assign(f1, 'tania.dat');
assign(f2, 'tania2.dat');
reset(f1,1024);
rewrite(f2,1024);
while not eof(f1) do
begin
blockread(f1, buffer,1, result);
if result = 1 then blockwrite(f2, buffer,1, result);
end;
close(f1);
close(f2);
end.
Файл tania2.dat будет копией файла tania.dat (с точностью до размера
компоненты, если размер файла не является кратным размеру компонент, его
"хвостик" будет потерян).
Стандартные файлы.
Существует несколько стандартных внешних имен файлов, которые Вы можете
использовать в процедуре Assign. Это
- CON - консоль (клавиатура при вводе, дисплей при
выводе)
- PRN - принтер
- LST1, LST2 - первый и второй принтеры
- COM1, COM2 - первый и второй коммуникационные порты
- NUL - нулевой файл, "черная дыра"
Файловая переменная, связанная с одним из этих файлов, приобретает
особые свойства:
- CON - при вводе через эту файловую переменную машина считывает данные
с клавиатуры, при выводе - выводит их на экран. Так действуют
стандартные процедуры ввода-вывода (их первый параметр не указан, но это
CON).
- PRN - данные через эту файловую переменную выводятся на принтер. Для
ввода это файл не предназначен.
- NUL - файловая переменная связывается с фиктивным файлом. При попытке
ввода он сразу заканчивается, а при выводе в него вообще ничего не
происходит.
Остальные стандартные файлы используются для общения с соответствующим
устройством.
Помимо необычного направления потоков ввода-вывода, файловые переменные,
связанные со стандартными файлами, ничем не отличаются от других.
ПОЛЕЗНЫЕ СОВЕТЫ.
1. Программы 1, 2, 3 следует запускать друг за другом
именно в таком порядке. Только тогда Вы получите правильный результат.
2. При чтении строк из текстового файла почти всегда лучше использовать
readln.
3. Всегда закрывайте файлы в конце работы программы.
4. Каждая файловая переменная может быть связана только с одним файлом
одновременно.
5. Настоятельно не рекомендуется одновременно открывать файл через одну
переменную - для чтения, через другую - для записи.
6. Не пытайтесь открыть несвязанную файловую переменную.
7. Не пытайтесь читать за концом файла - все равно ничего не прочитаете.
8. Если файл закончился сразу, то он пустой. Ошибку следует искать в той
программе, которая его создает, а не в той которая его использует.
9. Никогда не давайте Вашим файлам расширения pas , exe т.п. Эти
приводит к путанице и иногда к трагическим последствиям. |