Базовый файловый ввод-вывод в Delphi

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


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

var myFile: Text;

 

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

 

procedure AssignFile(var F; FileName: string); AssignFile(myFile, 'с:file:///C:/data/data.txt);

 

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


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

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

 

procedure Rewrite(var F: File [; Recsize: Word ]); Rewrite(myFile);

 

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

 

procedure WriteLn([ var F: Text; ] PI [, P2, ...,Pn ]); WriteLn(myFile, 'cave canem');

 

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

 

procedure CloseFile(var F); CloseFile(myFile);

 

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

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

 

program Project1; {$APPTYPE CONSOLE} uses SysUtils; var myFile: Text; begin AssignFile(myFile, 'c:\data.txt'); Rewrite(myFile); WriteLn(myFile, 'cave canem'); CloseFile(myFile); end.
 

 


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

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

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

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

 

program Project1; {$APPTYPE CONSOLE} uses SysUtils; var myFile: Text; line: string; begin AssignFile(myFile, 'c:\data.txt'); Reset(myFile); ReadLn(myFile, line); WriteLn(line); CloseFile(myFile); ReadLn; end.
 

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


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

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

 

{$1-} Вызов процедуры ввода-вывода {$1 + }

 

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

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

 

 

program Project1;

{$APPTYPE CONSOLE}
uses
  SysUtils;
var
  myFile: Text;
  line: string;
  fileName: string;
begin
  fileName := 'c:\data.txt';
  AssignFile(myFile, fileName);
  {$I-}
  Reset(myFile);
  {$I+}
  if IOResult = 0 then
  begin
    ReadLn(myFile, line);
    WriteLn(line);
    CloseFile(myFile);
  end else
    WriteLn('Cannot open file: ', fileName);
  ReadLn;
end.

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


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

 

function Eof [ (var F: Text) ]: Boolean;

 

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

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

 

program Project1; {$APPTYPE CONSOLE} uses SysUtils; var srcFile: Text; destFile: Text; line: string; begin { try to open the source file } AssignFile(srcFile, 'c:\data.txt'); {$I-} Reset(srcFile); {$I+} if IOResult = 0 then begin { try to open the destination file } AssignFile(destFile, 'c:\copy.txt'); {$I-} Rewrite(destFile); {$I+} if IOResult = 0 then begin { while loop that copies text from source to dest } while not Eof(srcFile) do begin ReadLn(srcFile, line); WriteLn(destFile, line); WriteLn('Copying: ', line); end; // while not Eof CloseFile(destFile); WriteLn; WriteLn('File successfully copied.'); end; // if destFile is OK CloseFile(srcFile); end; // if srcFile is OK ReadLn; end.

 

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


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

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

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

 

program Project1;
{$APPTYPE CONSOLE}
uses
  SysUtils;
var
  myFile: Text;
  lines: array of string;
  cnt: Integer;
  fileName: string;
function GetLineCount(var ATextFile: Text): Integer;
begin
  Result := 0;
  while not Eof(ATextFile) do
  begin
    ReadLn(ATextFile);
    Inc(Result);
  end;
  Reset(ATextFile); { move position to the beginning }
end;
begin
  fileName := 'c:\data.txt';
  AssignFile(myFile, fileName);
  {$I-}
  Reset(myFile);
  {$I+}
  if IOResult = 0 then
  begin
    { resize dynamic array and load the lines into the array}
    SetLength(lines, GetLineCount(myFile));
    for cnt := Low(lines) to High(lines) do
      ReadLn(myFile, lines[cnt]);
    { close file }
    CloseFile(myFile);
    { work with strings in memory }
    for cnt := Low(lines) to High(lines) do
      WriteLn(UpperCase(lines[cnt]));
  end else
    WriteLn('Cannot open file: ', fileName);
  ReadLn;
end.
 

 

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


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

 

type NewFileType=file of DataType;

 

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

 

type TPerson = record FirstName: string[20]; LastName: string[30]; Age: Integer; end; TPersonFile = file of TPerson;

 

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

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

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

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


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

 

 

program Project1;

{$APPTYPE CONSOLE}
 
uses
  SysUtils;
 
type
  TPerson = record
    FirstName: string[20];
    LastName: string[30];
    Age: Integer;
  end;
 
  TPersonFile = file of TPerson;
 
procedure ReadRecord(const AFileName: string; var Rec: TPerson);
var
  F: TPersonFile;
begin
  AssignFile(F, AFileName);
  {$I-}
  Reset(F);
  {$I+}
  if IOResult = 0 then
  begin
    Read(F, Rec);
    CloseFile(F);
  end; // if IOResult
end;
 
procedure WriteRecord(const AFileName: string; var Rec: TPerson);
var
  F: TPersonFile;
begin
  AssignFile(F, AFileName);
  Rewrite(F);
  Write(F, Rec);
  CloseFile(F);
end; 
procedure DisplayRecord(var Rec: TPerson);
begin
  WriteLn('First Name: ', Rec.FirstName);
  WriteLn('Last Name: ', Rec.LastName);
  WriteLn('Age: ', Rec.Age);
  WriteLn;
end; 
var
  TestRec: TPerson;
  ReadRec: TPerson; 
begin
  TestRec.FirstName := 'Stephen';
  TestRec.LastName := 'King';
  TestRec.Age := 58;
  WriteRecord('c:\info.dat', TestRec);
  ReadRecord('c:\info.dat', ReadRec);
  DisplayRecord(ReadRec);
  ReadLn;
end.
 

 

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

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

 

var F: TPersonFile; RecCount: Integer; begin RecCount := FileSize(F); if RecCount = 0 then WriteLn(<sup>1</sup> Файл пуст') else Seek(F, FileSize(F)); end.
 

 


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

 

var F: file;

 

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

 

Reset (F, 1); Rewrite(F,1);

 

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

 

procedure BlockRead(var F: File; var Buf; Count: Integer [; var AmtTransferred : Integer]); procedure BlockWrite(var F: File; var Buf; Count: Integer [; var AmtTransferred: Integer]);

 

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

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

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

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


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

 

program Project1; {$APPTYPE CONSOLE} uses SysUtils; procedure BlockCopyFile(const SrcPath, DestPath: string); var Src: file; Dest: file; Buffer: array[1..1024] of Byte; BytesRead: Integer; begin if LowerCase(SrcPath) = LowerCase(DestPath) then Exit; AssignFile(Src, SrcPath); {$I-} Reset(Src, 1); {$I+} if IOResult = 0 then begin AssignFile(Dest, DestPath); {$I-} Rewrite(Dest, 1); {$I+} if IOResult = 0 then begin BytesRead := -1; while BytesRead <> 0 do begin BlockRead(Src, Buffer, SizeOf(Buffer), BytesRead); BlockWrite(Dest, Buffer, BytesRead); end; CloseFile(Dest); WriteLn('File successfully copied.'); end; // if Dest Rewrite CloseFile(Src); end; // if Source Reset end; var SourcePath: string; DestPath: string; begin { Accept parameters } if ParamCount = 2 then BlockCopyFile(ParamStr(1), ParamStr(2)) else begin { if there are no parameters in the command line, ask the user to enter filenames here } Write('Source path: '); ReadLn(SourcePath); Write('Destination path: '); ReadLn(DestPath); if (SourcePath <> '') and (SourcePath <> '') then BlockCopyFile(SourcePath, DestPath); end; WriteLn('Press Enter to exit.'); ReadLn; end.

 

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

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

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

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

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

 

if Lowercase(SrcPath) = Lowercase(DestPath) then Exit;

 

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

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

 

while BytesRead о 0 do begin BlockRead(Src, Buffer, SizeOf(Buffer), BytesRead); BlockWrite(Dest, Buffer, BytesRead); end;

 

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

dle

Помоги проекту! Расскажи друзьям об этом сайте: