Дописать в файл в delphi

Обновлено: 07.07.2024

Текстовые файлы

Итак у всех потомков класса TStrings (TStringList, memo.Lines и т.п. ) есть методы записи и чтения в файл - SaveToFile, LoadFromFile. Преимущество - простота использования и довольно высокая скорость, недостаток - читать и писать файл можно только целиком.

1. Загрузка текста из файла в Memo:

2. Сохранение в файл:

3. А вот так можно прочитать весь файл в строку:

Паскалевский метод доступа

Для более тонких операций над текстовыми файлами прийдется освоить очень древний паскалевский способ.

Итак, для доступа к текстовым файлам используется переменная типа TextFile. До сих пор не совсем понимаю что это такое физически - что-то типа "внутреннего" паскалевского Handle на файл.

Итак чтобы ассоциировать файл на диске с переменной надо проделать следующие опрерации:

1) Определяем файловую переменную:

2) Ассоциируем ее:

3) Теперь надо этот файл открыть, есть 3 варианта:

  1. файла нет или он должен быть перезаписан, открытие для записи: Rewrite(f)
  2. файл есть и его надо открыть для чтения (с первой строки): Reset(f)
  3. файл есть и его надо открыть для дописования строк в конец: Append(f)

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

  • Перезаписать весть файл
  • Читать с первой строки
  • Дописать что-то в конец
  • Читать и писать файл целиком (см. выше работу через TStrings)

В конце работы открытый файл нужно закрыть:

Теперь пусть у нас есть строковая переменная s для чтения строки из файла

Чтение предварительно открытого файла:

ReadLn(f, s) - будет прочитанна текущая строка и позиция чтения переведена на следующую позицию.

А как прочитать весь файл?

Хорошо, а если файл несколько метров есть ли способ поставить какой-нибудь ProgressBar или Gauge чтобы показывал сколько считанно? Есть, но не совсем прямой - не забыли, сколько строк в файле заранее мы не знаем, узнать можно только прочитав его весь, но показометер мы все-таки сделаем:

Теперь комментарии к коду.

  1. Функию GetFileSize я рсссмотрю после, она немного по другому подходит к чтению файла (кстати я знаю еще по крайней мере 3 способа ее реализации, поэтому не нужно указывать что это можно сделать легче, быстрее или просто по другому - просто давайте разберем это позже)
  2. Переменная i - все время указывает на количество байт которое мы считали - мы определяем длину каждой строки и прибавляем 2 (символы конца строки). Зная длину файла в байтах и сколько байт прочитано можно оценить и прогресс, но
  3. Если ставить изменение прогресса после каждой строки, то это очень сильно тормознет процесс. Поэтому вводим переменную j и обновляем прогресс например 1 раз на 1000 прочитанных строк
  4. Переменная Canceled - глобальная переменная. Поставьте на форму кнопку, в обработчике нажатия поставьте Canceled:=True; и нажатие кнопки прервет чтение файла.

Теперь как писать в текстовый файл:

Запись целой строки:

Запись кусочка строки(те следующая операция записи будет произведена в ту же строку):

Если переменная s содержит больше 255 символов (т.е. является длинной строкой), то таким способом ни фига не запишится, в файл вместо строки попадут 4 байта указателя на нее. Надо делать так:

Работа через WinAPI

Раздел написан Podval (примеры к сожалению на С++)

Любителям WinAPI посвящается. Функции FileOpen, FileSeek, FileRead. Возьмем форму, положим на нее кнопку, грид и Опен диалог бокс. Это для Билдера, но какая нам в данном случае разница?

Потренируемся еще. Функции FileExists, RenameFile, FileCreate, FileWrite, FileClose. Бросим на форму Save dialog box.

(с) Оба примера взяты из хелпа по Borland C++ Builder 5.

Первоисточник тот же.

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

В этом примере идет поиск в текущем каталоге и каталоге Windows

В дополнение к Дате/Времени

Для конвертации возвращаемого значения в TDateTime:

Типизированные файлы

Теперь разберем типизированные файлы. Типизированный файл - это файл в котором записанны идентичные структуры. Например любой файл можно считать файлом байтов - т.е. можно его читать байт за байтом, можно перейти сразу к любому байту по его номеру, можно сразу узнать сколько байт в файле, можно заменить любой байт на другой не перезаписывая файл. Теперь все это в примерах:

Объявляем файл байтов:

Ассоциируем файловую переменную с физическим файлом:

Теперь мы можем либо перезаписать/создать файл:

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

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

Теперь функции работы с файлом:

Все эти функции не работают с файлами большими 2 Gb.

После работы файл надо закрыть:

Приведенные выше механизмы будут работать с любым файлом, так как любой файл можно считать файлом байтов. Теперь где это можно использовать? В принципе везде, но в подавляющем большинстве случаев это будет очень неудобно, ведь скорость считывания при чтении по байтам будет на порядки более низкой чем другими способами. Однако в некоторых случаях этот способ может быть очень полезен. Например в программе вам надо заменить 100й байт файла на другой, или прочитать 100й байт файла, например во всяких читерских программах, при взломе и т.п. Здесь такой доступ будет весьма удобен. Гораздо более интересным представляется дальнейшее развитие технологии типизированных файлов (их еще лет 15 назад называли "Файлы прямого доступа"). Представим себе, что файл состоит не из байт а из более сложных структур. Например мы имеем некоторую информацию в виде:

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

и файл этого типа:

Теперь мы можем читать и писать сразу целую структуру, абсолютно так же как и если бы это был один байт:

Все остальные функции приведенные в предыдущей статье будут работать так же, только одно отличие - Seek и Size оперируют не с количеством байт, а с количеством записей.

Нетипизированные файлы

Идем дальше. Есть такое понятие как нетипизированный файл. Это такой файл который содержит разнородные элементы. Например файл EXE - вначале он имеет заголовок, затем двоичный код, в конце какие-то ресурсы. Все части файла имеют разную длину и разную структуру. Тут уже обратится к произвольному элементу сложно, обычно надо вначале узнать где этот элемент находится, подчас это записано в предыдущем куске информации. Работа с такими файлами достаточно сложна и требует вручную разработки алгоритмов его чтения, но в связи гибкостью структуры и компактностью такие файлы составляют большинство. Для работы с нетипизированными файлами используют процедуры BlockRead и BlockWrite, которые позволяют читать/писать произвольное количество байт. Привожу пример пользования этими функциями из справки по Дельфи:

Этот код копирует из одного файла в другой. Замечания по поводу этого метода работы с файлами - плюсы - очень высокая скорость, особенно если размер буффера увеличить до 64kb-512kb, что позволит считывать файл достаточно большими кусками, чтобы обеспечить отсутствие простоев винчестера, к тому же обеспечивается очень высокая гибкость в работе. Минусы - сложность разработки, необходимость вручную писать все детали механизма чтения/записи и интерпретации данных.

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

Файловые потоки

Теперь разберем возможности работы потомка TStream - TFileStream - файловый поток. Этот класс был специально введен для работы с файлами. Для работы с файловым потоком Вам надо записать в Uses модули classes, Sysutils (classes - включает в себя собственно определение класса, Sysutils - некоторые константы необходимые для работы).

Вот пример записи/перезаписи файла:

Теперь небольшой разбор:

TFileStream.create - конструктор класса, его вызов требует указания имени файла и опций его открытия, следующие опции определены:

Теперь метод Write - этим методом в файл пишется любая информация из буфера любого типа, Вам надо указать только буффер и количество записываемых байтов. В данном случае используется переменная типа String в качестве буффера, но так как для длинных строк она представляет собой лишь указатель, то конструкция "pointer(s)^" заставляет обращаться именно к ее содержимому.

А вот этот код демонстрирует чтение файла с использованием файлового потока:

И пояснения к коду:

  1. Никаких проверок длину файла и его наличие здесь не делается - это демонстрационный код, а не готовая процедура чтения.
  2. Файл мы считываем в буффер типа PChar (с тем же успехом можно использовать массив или любой другой контейнер). Для тех кто не помнит - процедуры GetMem(p, 255) и FreeMem(p) - распределение памяти для строки и освобождение памяти.
  3. Метод потока Seek позволяет установить текущую позицию считывания/записи файла. Первый параметер - номер байта, второй - это от чего считать этот байт (у нас считать от начала файла), возможны варианты:
    • soFromBeginning - от начала файла
    • soFromCurrent - от текущей позиции считывания
    • soFromEnd - от конца файла (в этом случае номер байта должен быть отрицательным или равным нулю)
  4. Собственно считывание из потока осуществляется методом read, в котором указывается в качестве параметров буфер в который мы читаем и желаемое количество байт для чтения. Метод read является функцией, которая возвращает количество байт реально прочитанных из потока.

Заканчивая о файловых потоках хочу упомянуть о методе

CopyFrom который позволяет перекачивать информацию из одного потока в другой и о свойствах:

Size - размер файла
Position - текущая позиция чтения/записи потока

Работа с файловыми потоками весьма быстра, этот класс, являсь классом VCL, в то же время базируется на низкоуровневых функциях Windows, что обеспечивает очень высокую скорость работы и стабильность операций. К тому же многие компоненты и классы VCL поддерживаю прямое чтение и запись с файловыми потоками, что занчительно упрощает работу - например TStringList, TBlobField, TMemoField и другие.

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

Работа через Handle

Еще один способ работы с файлами - это открытие Handle на файл и работу через него. Тут есть 2 варианта - можно использовать функции Дельфи или использовать WinAPI напрямую.

При использовании функций Дельфи можно применять следующие функции:

FileOpen(FileName, fmOpenWrite or fmShareDenyNone) - функция открывает файл и возвращает целое цисло - Handle на файл. Параметры функции - имя файла и тип доступа (все типы доступа я перечислил ранее). Если файл успешно открыт то Handle должен быть положительным цислом, отрицательное число - это код ошибки.

Во всех остальных функциях используется именно значение Handle, возвращаемое этой функцией.

FileClose(Handle: Integer) - закрывает файл

FileRead(Handle: Integer; var Buffer; Count: Integer): Integer;
FileWrite(Handle: Integer; const Buffer; Count: Integer): Integer;

Эти функции для чтения/записи файла, где Buffer любая переменная достаточного размера для чтения/записи куска информации (обычно типа PChar или массив), Count-количество байт, которое Вы желаете записать/прочитать. Функции возвращают количество байт которые реально были прочитанны или записаны.

Этот тип доступа к файлам применяется весьма редко. Дело в том что он практически дублирует соответствующие функции WinAPI и к тому же обычно работает несколько медленнее, чем например потоки. И все же использование функций FileOpen и FileClose не лишено привлекательности. Наряду с тем что эти функции намного легче в использовании соответствующих функций WinAPI (можете сравнить - FileOpen имеет 2 параметра, cooтветствующая функция WinAPI - CreateFile имеет 7 параметров, большая часть из которых реально требуется лишь в ограниченном числе случаев) этот путь доступа открывает возможность прямого использования всех функций WinAPI про работе с файлами, которые требуют Handle на открытый файл.

Файловые операции

Дельфи предоставляет довольно широкие возможности по файловым операциям без использования механизмов открытия/закрытия файлов.

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

ChDir(NewCurrentPath: string); - изменяет текущий каталог (в среде Windows сие конечно не так актуально как в ДОС, но все же), прочитать же текущий каталог можно функцией GetCurrentDir, а текущий каталог для определенного драйва - GetDir.

CreateDir(const Dir: string): Boolean; - создает каталог. При этом предыдущий уровень должен присутствовать. Если вы хотите сразу создать всю вложенность каталогов используйте функцию ForceDirectories(Dir: string): Boolean; Обе функции возвращают True если каталог создан

DiskFree(Drive: Byte): Int64; - дает свободное место на диске. Параметер - номер диска 0 = текущий, 1 = A, 2 = B, и так далее

DiskSize(Drive: Byte): Int64; - размер винта. Обратите внимание на то что для результата этой и предыдущей функций абсолютно необходимо использовать переменную типа Int64, иначе макимум того что вы сможете прочитать правильно будет ограничен 2Gb

FileExists(const FileName: string) - применяется для проверки наличия файла

FileGetAttr(const FileName: string): Integer;
FileSetAttr(const FileName: string; Attr: Integer): Integer; - функции для работы с атрибутами файлов. Вот список возможных атрибутов:

RemoveDir(const Dir: string): Boolean; - удаляет папку(пустую)
DeleteFile(const FileName: string): Boolean; - удаляет файл
RenameFile(const OldName, NewName: string) - переименовывает файл

Информация о файле

Привожу пример функции которая собирает довольно большое количество информации о выбранном файле:

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

Поиск файлов

Теперь поговорим о поиске файлов. Для этой цели могут использоваться процедуры FindFirst, FindNext, FindClose, при участии переменной типа TSearchRec которая хранит информацию о текущем статусе поиска и характеристики последнего найденного файла.

Пример иллюстрирующий поиск всех файлов и каталогов в определенном каталоге:

Delphi 7. Занятие 1_10.

Delphi работа с файлами

Сохранение результатов разработки программы.

Создавая текст программы, в процессе работы над ней и по окончанию её мы сохраняем на диск проект и все модули. Выбрав «Save All … » в меню «File» мы автоматически сохраняем все файлы проекта.

«Close» закрывает активную вкладку.

Но всё это касается файлов самого проекта.

А как быть, если надо сохранить данные, полученные в ходе работы программы? Это могут быть числа, текст, массив, запись (о типе данных запись поговорим позднее), отдельный символ (тип данных char).

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

Разберёмся, как это делается.

Сохранение результатов расчётов.

В файле, как и в оперативной памяти, данные хранятся в виде нулей и единиц.

При записи какого-либо типа данных они превращаются в двоичный поток и записываются на диск.

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

Предположим, мы прочитали с диска последовательные 32 бита (4 байта). Если файл описан как файл целого типа, то эти 4 байта будут интерпретированы как целое число.

Если файл хранит короткие строки (пример: type sh=char[20], то есть короткая строка — это массив символов. В короткой строке не может быть больше 255 символов),то байты будут интерпретированы как 4 символа.

То есть интерпретация содержимого, записанного на диск, зависит от описанного в программе типа файла.

Файл для хранения текста.

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

С текстовыми файлами мы сталкиваемся, например, создавая документ в программе «блокнот».

Для работы с текстовым файлом объявляется переменная:

var vMyTextFile:TextFile;

где TextFile – предопределённый тип файла.

Далее переменная связывается или с создаваемым на диске, или с существующим файлом. Например, мы хотим создать файл «D:\MyFile.txt».

Свяжем переменную vMyTextFile c именем файла с помощью процедуры:

AssignFile(vMyTextFile, s);

Создадим файл процедурой:

Rewrite( vMyTextFile);

Осталось заполнить его данными.

Замечание.

Если файл с таким именем и путём доступа уже существует на диске, то он будет уничтожен и создан заново с пустым содержанием.

Запись в текстовый файл.

Рассмотрим, как осуществляется в delphi запись в файл.

Чтобы текст правильно читался из файла, необходимо, чтобы он был разбит на предложения.

Запись такого символа происходит автоматически, если использовать процедуру:

WriteLn( vMyTextFile, s1 [, … , si, … , sn]); , где si — строковая переменная.

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

Замечание.

Не используйте процедуру Write вместо WriteLn, иначе получите в файле «кашу» или ошибку записи!

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

Если, прочитав из файла несколько значений, возникает необходимость дописать строки в конец файла, то его надо переоткрыть процедурой Append(unit1.vFile).

Замечание.

К сожалению, текстовый файл нельзя исправлять напрямую. Нельзя, например, переписать вторую строку.

Для внесения изменений надо прочитать весь файл, отобразить его в каком либо компоненте, например memo, отредактировать, заново пересоздать файл и перезаписать текст из memo в файл.

Завершение работы с файлом закрывает файл. С практической точки зрения это означает, что к файлу больше нельзя обратиться процедурами записи или чтения. С другой стороны, теперь файл доступен для удаления и редактирования средствами файловой системы, например, с помощью «блокнота».

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

Чтение из текстового файла.

Чтобы организовать в delphi чтение из файла, его надо предварительно открыть процедурой Reset.

Эта процедура устанавливает внутренний указатель файла на нулевую запись.

Открытие файла осуществляется через файловую переменную: Reset(vMyTextFile).

Далее производится считывание записей от нулевой до последней оператором ReadLn( vMyTextFile, s1); , помещённым в цикл.

(Оператор ReadLn( vMyTextFile, s1 [, … , si, … , sn]) нет смысла использовать в таком виде, так как всё рано считывается ровно одна запись. То есть значение будет занесено только в s1, остальные переменные будут пустыми.).

Поэтому достаточно записать ( vMyTextFile, s1); и использовать этот оператор в цикле, отслеживая значение функции признака конца файла EOF(vMyTextFile).

Замечание.

Использование «пустой» процедуры чтения в виде ReadLn( vMyTextFile) осуществляет переход к следующей записи.

Пример программы работы с текстовым файлом рассмотрен в видеоматериале.

Цикл чтения строк из текстового файла.

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

Он будет длиться до тех пор, пока не будет достигнут символ конца файла EOF.

Ранее мы уже рассматривали организацию цикла с помощью оператора for.

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

repeat

until условие

Если «условие» вырабатывает (возвращает) значение false, то цикл продолжается, если true — то завершается.

Или в нашем случае:

repeat

until EOF(vMyTextFile)

Войдя в цикл, выполняются находящиеся в нём операторы. Далее проверяется условие, находящееся в операторе until. Когдафункция EOF() вернётtrue(будет достигнут конец файла), цикл завершится и будет выполнен следующий за ним оператор.

Для примера напишем программу, на главной форме которой разместим новый компонент Panel, предназначенный для группировки на нём других компонент.

Видео. Создаём форму для будущей программы.

Видео. Настраиваем свойства компонентов.

Видео. Создаём обработчики событий для записи и чтения данных из файла.

Видео. Дописываем данные в конец файла.

Заключение.

Рассмотрено понятие «текстовый файл», организация данных внутри файла, дано понятие конца записи и конца файла. Даны операторы создания файловой переменной и её связывания с конкретным файлом, а также операторы записи в него строковой информации и закрытия файла.

Для чтения/записи текстовый файл надо открыть, при этом указатель установится на нулевую запись.

Оператор чтения строки считывает ровно одну запись. Весь текст считывается путём организации цикла.

var F: File ;

Описанная таким образом файловая переменная считается нетипизированной, и позволяет работать с файлами с неизвестной структурой. Данные считываются и записываются побайтно блоками, размер которых указывается при открытии файла, вплоть от 1 байт.

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

var F: File of тип_записи ;

В качестве типа могут использоваться базовае типы, или создаваться свои. Важно только, чтобы для типа был точно известен фиксированный размер в байтах, поэтому, например, тип String в чистом виде применяться не может, а только в виде String[N], как указывалось в уроке Delphi 5.

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

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


Для текстовых файлов отдельно укажу, что тип файловой переменной в этом случае TextFile, а тип обычной - String.

Для открытия файла нужно указать, где он расположен. Для этого файловая переменная должна быть ассоциирована с нужным файлом, который определяется его адресом. Адрес файла может быть абсолютным, с указанием диска и каталогов ('C:\Мои документы\Мои рисунки\FileName.ini'), или относительным, тогда он создаётся в папке с .exe файлом программы. Для задания относительного адреса достаточно указать имя файла с нужным расширением. Делается это оператором AssignFile :

AssignFile(SaveF, 'C:\Мои документы\Мои рисунки\FileName.ini');
AssignFile(SaveF, 'FileName.ini');

Теперь файл должен быть открыт.
Открытие файла оператором Rewrite приведёт воссозданию файла заново, т.е. существующий файл будет без предупреждения уничтожен, и на его месте будет создан новый пустой файл заданного типа, готовый к записи данных. Если же файла не было, то он будет создан.
Открытие файла оператором Reset откроет существующий файл к считыванию или записи данных, и его указатель будет установлен на начало файла :

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

Rewrite(SaveF, 1);
Reset(SaveF, 1);

Чтение файла производится оператором Read :

Запись в файл производится оператором Write :

При этом чтение и запись производится с текущей позиции указателя, затем указатель устанавливается на следующую запись. Можно проверить, существует ли нужный файл, оператором FileExists :

if FileExists('FileName.ini')
then Read(SaveF, SaveV);

Принудительно установить указатель на нужную запись можно оператором Seek(SaveF, N), где N - номер нужной записи, который, как и почти всё в программировании, отсчитывается от нуля:

Seek(SaveF, 49); - установка указателя на 50-ю запись.

При последовательном чтении из файла рано или поздно будет достигнут конец файла, и при дальнейшем чтении произойдёт ошибка. Проверить, не достигнут ли конец файла, можно оператором EOF (аббревиатура End Of File), который равен true, если прочитана последняя запись и указатель находится в конце файла:

while (not EOF(SaveF)) do
Read(SaveF, SaveV);

Для текстовых файлов вместо Read и Write используются операторы Readln и Writeln, умеющие определять конец строки. приведена процедура чтения текстового файла.

Оператор Truncate(SaveF) позволяет отсечь (стереть или, если хотите, удалить!) все записи файла, начиная от текущей позиции указателя, и до конца файла.

В конце работы с файлом его необходимо закрыть. Это делается оператором CloseFile(SaveF) ;

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

Создаём обработчик события Формы OnCreate со следующим содержимым:

procedure TForm1.FormCreate(Sender: TObject) ;
begin
AssignFile(SaveF, 'Init.ini') ;
if FileExists('Init.ini') then
begin
Reset(SaveF) ;
Read(SaveF, SaveV) ;
Form1.Left := SaveV.X ;
Form1.Top := SaveV.Y ;
Form1.Caption:=SaveV.Caption ; //Наши переменные дополнительно сохраняют заголовок Формы!
end ;
end ;

Теперь необходимо создать обработчик события OnClose :

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction) ;
begin
Rewrite(SaveF) ; //Нет необходимости проверять наличие файла, создадим его заново!
SaveV.X := Form1.Left ;
SaveV.Y := Form1.Top ;
SaveV.Caption := Form1.Caption ;
Write(SaveF, SaveV) ;
CloseFile(SaveF) ;
end ;

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


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

Для получения доступа к текстовым файлам служат переменные типа Text:

Прежде чем можно будет приступить к работе с файлом, с помощью процедуры AssignFile его нужно присвоить переменной типа Text. Эта процедура принима­ет два параметра: переменную файла и имя файла.

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

Чтобы подготовить файл к записи, необходимо использовать процедуру Rewrite, которая всегда создаст новый пустой файл. Если файл с таким же именем файла уже существует, процедура Rewrite вначале удаляет существующий файл, а затем заме­няет его новым пустым файлом. Затем процедура открывает файл и устанавливает указатель позиции на начало файла.

При работе с текстовыми файлами процедуре Rewrite необходимо передавать только переменную типа Text:

Когда файл открыт и готов к записи, для записи текста в текстовый файл можно использовать стандартную процедуру WriteLn. При выполнении записи в тексто­вый файл первым параметром, переданным процедуре WriteLn, должна быть пе­ременная файла:

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

Пример программы записи строки текста в текстовый файл приведен в листинге 8.1.

Листинг 8.1. Запись текста в текстовый файл

Для подготовки файла к чтению используется процедура Reset. Эта процедура, подобно процедуре Rewrite, принимает только параметр типа файла. Ее можно считать безопасной в том смысле, что она успешно работает, если дисковод и/или каталог, указанный в имени файла, существует. В отличие от Rewrite, выполне­ние процедуры Reset будет невозможным, если файл, присвоенный переменной файла, не существует.

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

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

Листинг 8.2. Считывание текста из текстового файла

Этот код будет успешно работать до тех пор, пока существует файл data. txt. Если этот файл не существует, программа даст сбой. Во избежание остановки при­ложения при отсутствии файла необходимо выполнять проверку успешности от­крытия файла с помощью процедуры Reset.

Для выяснения наличия ошибок ввода-вывода необходимо непосредственно после вызова процедуры ввода-вывода, такой как Rewrite или Reset, вызвать функцию IOResult. Функция IOResult возвращает результат последней выпол­ненной операции ввода-вывода. Если IOResult возвращает 0, это означает, что операция была выполнена успешно.

Для выполнения проверки ввода-вывода с помощью функции IOResult необхо­димо вначале отключить автоматическую проверку ввода-вывода. Для включения и отключения проверки ошибок ввода-вывода служит директива компилятора SI. Обычно автоматическую проверку ввода-вывода отключают перед вызовом проце­дуры ввода-вывода и снова включают сразу после выполнения этого вызова:

Следующий пример иллюстрирует выполнение проверки ввода-вывода и счи­тывание текста из файла только в случае успешного его открытия.

Листинг 8.3. Проверка ошибок ввода-вывода

Помните, что после обращения к процедуре ввода-вывода функцию IOResult можно вызывать только один раз. Это обусловлено тем, что она сбрасывает ре­зультат выполнения последней операции ввода-вывода в 0. Поэтому, если вызвать функцию IOResult дважды подряд, первое обращение к ней правильно сообщит об ошибке, но второе обращение сообщит (ошибочно), что операция была выпол­нена успешно.

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

Как правило, наиболее рациональный способ считывания текстового файла предусматривает использование цикла while, продолжающего итерации вплоть до достижения конца файла. Следующий пример иллюстрирует копирование со­держимого одного текстового файла в другой с использованиемцикла while not Eof (результаты можно видеть на рис. 8.1).

Листинг 8.4. Копирование текстового файла

Рис. 8.1. Копирование текстового файла



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

В следующем примере цикл while not Eof в функции GetLineCount исполь­зуется для получения количества строк текстового файла. Полученный результат затем используется в вызове функции SetLength для изменения размера динами­ческого массива.

Листинг 8.5. Загрузка текстового файла в динамический массив

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

Типизированные файлы представляют собой двоичные файлы, которые содер­жат элементы одинакового размера. Обычно типизированные файлы — это фай­лы, содержащие записи. Чтобы создать типизированный файл, вначале, исполь­зуя следующий синтаксис, необходимо создать новый файловый тип данных:

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

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

Существует несколько различий между текстовыми и типизированными фай­лами:

  • При сбросе типизированного файла в нем можно выполнять считывание и запись (при сбросе текстового файла в нем можно выполнять только считы­вание).
  • При выполнении считывания или записи из типизированного файла необ­ходимо использовать процедуры Read и Write, а не ReadLn и WriteLn.

В листинге 8.6 демонстрируется работу с типизированными файлами.


Листинг 8.6. Работа с типизированными файлами

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

Для определения количества записей в файле можно использовать функцию FileSize. Для перехода к определенной записи в файле можно использовать про­цедуру Seek. Эта процедура принимает два параметра: переменную файла и цело­численное значение, указывающее номер записи (начиная с О), к которой необхо­димо выполнить переход.

Нетипизированные файлы — это файлы без определенной структуры. В общем случае, нетипизированные файлы — это типизированные файлы, в которых вме­сто записей используются байты. Объявление переменной нетипизированного файла выглядит следующим образом:

При работе с нетипизированными файлами обращения к процедурам Reset и Rewrite выглядят несколько иначе. Обычно обе эти процедуры в качестве задан­ного по умолчанию размера записи используют 128 байт. При работе с нетипизи­рованными файлами этот размер должен быть установлен равным 1 байту. Это можно выполнить, передавая 1 в качестве второго параметра в обоих вызовах:

Для считывания и записи данных в нетипизированные файлы применяются процедуры BlockRead и BlockWrite. Объявления этих процедур показаны ниже:

Первый параметр — переменная нетипизированного файла, используемая для доступа к файлу на диске. Второй параметр — буфер, который процедуры исполь­зуют для передачи данных в файл и из него.

Обычно этот буфер представляет собой статический массив байтов, но он мо­жет быть также и записью. Третий параметр указывает количество передаваемых байтов. Обычно это число равно размеру массива, который легко определить с по­мощью функции SizeOf. Необязательный параметр-переменная AmtTransferred можно использовать для отслеживания точного числа байтов, переданных в файл и из него.

Приложение, код которого представлен в листинге 8.7, использует процедуры BlockRead и BlockWrite для предоставления пользователю возможности копиро­вания файлов. Приложение также дает пользователю возможность указывать имена исходного и целевого файлов в командной строке, как показано на рис. 8.2.

Рис. 8 .2. Передача параметров приложению


Листинг 8.7. Копирование файлов с помощью процедур BlockRead и BlockWrite

Для выяснения количества параметров, которые пользователь передал прило­жению, применяется функция ParamCount. Эта функция не принимает никаких параметров и возвращает значение 0. если пользователь вызвал приложение без дополнительных параметров.

Для считывания параметров, переданных приложению, служит функция ParamStr, которая принимает единственный параметр типа Integer — индекс параметра. Если передать этой функции значение О, она возвратит путь и имя файла приложения. Индексы пользовательских параметров, если они переданы, начинаются с 1.

Вначале приложение проверяет, передал ли пользователь два параметра в ко­мандной строке. Если да, то ParamStr (1) содержит путь к исходному файлу, а ParamStr (2) — путь к файлу назначения.

Копирование выполняется в процедуре BlockCopyFile.

Ее первая строка:

использует функцию LowerCase для временного преобразования имен обоих фай­лов в строчные буквы и проверяет, указывают ли оба имени файлов на один и тот же файл. Если файл назначения и файл-источник совпадают, никакое копирова­ние не требуется и оператор if-then вызывает процедуру Exit для выхода из про­цедуры.

Основная часть процедуры BlockCopyFile — цикл while, который вызывает процедуры BlockRead и BlockWrite:

Процедура BlockRead считывает из файла 1 Кбайт данных, записывает эти данные в массив Buffer и обновляет переменную BytesRead, которая всегда со­держит точное количество переданных байтов. Выполнение цикла будет повто­ряться до тех пор, пока процедура BlockRead продолжает считывать данные из файла. Когда эта процедура достигнет конца файла, значение переменной BytesRead будет сброшено в 0, условие цикла while станет ложным и копирова­ние файла завершится.

Читайте также: