Seek — Процедура Delphi


Клуб программистов

Delphi programming

Подписаться на рассылку:

Устанавливает указатель в двоичном файле в позицию новой записи

Описание:

Процедура Seek перемещает позицию текущей записи в открытом двоичном файле, заданном переменой FileHandle, в новую позицию RecordNumber.

Файл должен быть связан с файловой переменной с помощью функции AssignFile и открыт с помощью Reset или ReWrite.

Дня нетипизированных файлов размер записи устанавливается с помощью процедуры Reset или ReWrite.

Для типизированных файлов размер записи равен SizeOf(тип записи файла).

Первая запись в файле начинается с номера 0.

Пример кода:

var
myWord, myWord1, myWord2, myWord3, myWord4 : Word;
myFile : File of Word;

begin
// Try to open the Test.cus binary file in write only mode
AssignFile(myFile, ‘Test.cus’);
ReWrite(myFile);

// Write a few lines of Word data to the file
myWord1 := 12;
myWord2 := 34;
myWord3 := 56;
myWord4 := 78;
Write(myFile, myWord1, myWord2, myWord3, myWord4);

// Close the file
CloseFile(myFile);

// Reopen the file for read only purposes
FileMode := fmOpenRead;
Reset(myFile);

// Seek (move) to the start of the 3rd record
Seek(myFile, 2); // Records start from 0

// Show this record
Read(myFile, myWord);
ShowMessage(‘Record 3 = ‘+IntToStr(myWord));

// Close the file
CloseFile(myFile);
end;

Seek — Процедура Delphi

Moves the current position of a file to a specified component.

Use Seek in Delphi code to move to a specified position in open typed or untyped files. The current file position of F moves to component number N, where the number of the first component of a file is 0.

In the above syntax, F is a typed or untyped file variable. The file specified by F must be open. N is an expression of type Longint

To expand a file, seek one component beyond the last component; that is, the statement Seek(F, FileSize(F)) moves the current file position to the end of the file.

Seek — Процедура Delphi

Технология работы с файлами в системе Delphi требует определённого порядка действий:

  1. Прежде всего файл должен быть открыт. Система следит, чтобы другие приложения не мешали работе с файлом. При этом определяется, в каком режиме открывается файл — для изменения или только считывания информации. После открытия файла в программу возвращается его идентификатор, который будет использоваться для указания на этот файл во всех процедурах обработки.
  2. Начинается работа с файлом. Это могут быть запись, считывание, поиск и другие операции.
  3. Файл закрывается. Теперь он опять доступен другим приложениям без ограничений. Закрытие файла гарантирует, что все внесённые изменения будут сохранены, так как для увеличения скорости работы изменения предварительно сохраняются в специальных буферах операционной системы.

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

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

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

В качестве типа могут использоваться базовае типы, или создаваться свои. Важно только, чтобы для типа был точно известен фиксированный размер в байтах, поэтому, например, тип 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 ;

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

Seek — Процедура Delphi

Читаешь Length(Str), помня что символы перехода на новую строку есчо есть, вот и получается:
Seek(F,-Length(Str)-2)
Вот будет ли работать раз s-mike говорит.

Моно тогда есчо юзать этот текстовый файл как двоичный, тогда ReadLn работать не будет. ИМХО палка о двух концах.

procedure Seek(var F; N: Longint);

Use Seek in Delphi code to move to a specified position in open typed or untyped files. The current file position of F moves to component number N, where the number of the first component of a file is 0.

In the above syntax, F is a typed or untyped file variable. The file specified by F must be open. N is an expression of type Longint

Seek — Процедура Delphi

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

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

1. Объявить файловую переменную необходимого типа.

2. При помощи функции AssignFile связать эту переменную с требуемым файлом.

3. Открыть файл при помощи функций Append, Reset, Rewrite.

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

5. Закрыть файл при помощи функции CloseFile .

По сравнению с Turbo Pascal изменились названия только двух функций: Assign стала AssignFile , a Close превратилась в CloseFile .

В качестве примера рассмотрим небольшой фрагмент исходного кода.

then AssignFiie(F, OpenDlg.FileName)

else Exit; Reset(F);

while Not EOF(F) do

Если в диалоге открытия файла OpenDlg был выбран файл, то его имя связывается с файловой переменной F при помощи процедуры AssignFiie . В качестве имени файла рекомендуется всегда передавать полное имя файла (включая его маршрут). Как раз в таком виде возвращают результат выбора файла диалоги работы с файлами TOpenDialog, TOpenPictureDiaiog . Затем при помощи процедуры Reset этот файл открывается для чтения и записи.

В цикле выполняется чтение из файла текстовых строк и запись их в компонент TMemo . Процедура Readin осуществляет чтение текущей строки файла и переходит на следующую строку. Цикл выполняется, пока функция EOF не сообщит о достижении конца файла.

После завершения чтения файл закрывается.

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

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

Открытие файла может осуществляться тремя процедурами — в зависимости от типа его дальнейшего использования.

procedure Reset(var F: File [; RecSize: Word ]);

открывает существующий файл для чтения и записи, текущая позиция устанавливается на первой строке файла.

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

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

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

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

Чтение данных из типизированных и текстовых файлов выполняют процедуры Read И Readin.

Процедура Read имеет различное объявление для текстовых и других типизированных файлов:

  • procedure Read([var F: Text;] VI [, V2. Vn]);
  • procedure Read(F, VI [, V2. Vn]);

для других типизированных файлов.

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

procedure Readln([ var F: Text; ] VI [, V2. Vn ]);

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

Процедуры для записи в файл write и writein описаны аналогично:

procedure Write([var F: Text; ] PI [, P2. Pn]) ; procedure Writein([ var F: Text; ] PI [, P2. Pn ]);

Параметры P1, P2, . Pn могут быть одним из целых или вещественных типов, одним из строковых типов или логическим типом. Но у них есть возможность дополнительного форматирования при выводе. Каждый параметр записи может иметь форму:

Р n — выводимая переменная или выражение;

MinWidth — минимальная ширина поля в символах, которая должна быть больше 0;

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

Обратите внимание, что для текстовых файлов в функциях Read и write файловая переменная F может быть опущена. В этом случае чтение и запись осуществляются в стандартные файлы ввода/вывода. Когда программа компилируется как консольное приложение (флаг <$APPTYPE CONSOLE>), Delphi автоматически связывает входной и выходной файлы с окном консоли.

Для контроля за текущей позицией в файле применяются две основные функции. Функция EOF (F) возвращает значение True , если достигнут конец файла. Функция EOLN(F) аналогично сигнализирует о достижении конца строки. Естественно, в качестве параметра в функции необходимо передавать файловую переменную.

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

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

Для реализации этого режима необходимо использовать только нетипизированные файловые переменные. Размер блока определяется в процедуре открытия файла ( Reset, Rewrite ). Непосредственно для выполнения операций используются процедуры BlockRead и BlockWrite .

procedure BlockRead(var F: File; var Buf; Count: Integer

[; var AmtTransferred: Integer]);

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

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

При использовании блочного чтения или записи размер блока необходимо выбирать таким образом, чтобы он был кратен размеру одного значения того типа, который хранится в файле. Например, если в файле хранятся значения типа Double (8 байт), то размер блока может быть равен 8, 16, 24, 32 и т. д. Фрагмент исходного кода блочного чтения при этом выглядит следующим образом:

DoubleArray: array [0..255] of Double;

if OpenDlg.Execute then AssignFile(F, OpenDlg.FileName) else Exit;

BlockRead(F, DoubleArray, 32, Transferee!);

ShowMessage(‘Считано ‘+IntToStr(Transfered)+’ блоков’);

Как видно из примера, размер блока установлен в процедуре Reset и кратен размеру элемента массива DoubleArray , в который считываются данные. В переменной Transfered возвращается число считанных блоков. Если размер файла меньше заданного в процедуре BlockRead числа блоков, ошибка не возникает, а в переменной Transfered передается число реально считанных блоков.

procedure BlockWrite(var f: File; var Buf; Count: Integer

[; var AmtTransferred: Integer]);

Оставшиеся функции ввода/вывода, работающие с файловыми переменными, подробно описаны в табл. 9.1.

Таблица 9.1. Процедуры и функции для работы с файлом

function ChangeFileExt (const FileName, Extension: string): string;

Функция позволяет изменить расширение файла. При этом сам файл не переименовывается

procedure ChDir(S: string);

Процедура изменяет текущий каталог на другой, путь к которому описан в строке s

procedure CloseFile (var F) ;

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

Имя этой процедуры изменено из-за конфликта имен в Delphi (в Borland Pascal используется процедура Close )

function DeleteFile (const FileName: string): Boolean;

Функция производит удаление файла FileName с диска и возвращает значение False , если файл удалить не удалось или файл не существует

function ExtractFileExt (const FileName: string): string;

Функция возвращает расширение файла

function ExtractFileName (const FileName: string) : string;

Извлекает имя и расширение файла, содержащегося в параметре FileName

function ExtractFilePath( const FileName: string): string;

Функция возвращает полный путь к файлу

procedure Erase (var F);

Удаляет файл, связанный с файловой переменной F

function FileSearch (const Name, DirList: string) : string;

Данная процедура производит поиск в каталогах DirList файла Name . Если в процессе выполнения FileSearch обнаруживается искомое имя файла, то функция возвращает в строке типа string полный путь к найденному файлу. Если файл не найден, то возвращается пустая строка

function FileSetAttr (const FileName: string; Attr: Integer): Integer;

Присваивает файлу с именем FileName атрибуты Attr. Функция возвращает 0, если присвоение атрибутов прошло успешно. В противном случае возвращается код ошибки

function FilePos (var F): Longint;

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

function FileSize (var F): Integer;

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

Для текстовых файлов функция FileSize не используется

procedure Flush (var F: Text);

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

Когда текстовый файл открыт для записи с использованием функции Rewrite или Append, Flush очищает выходной буфер, связанный с файлом. После выполнения данной процедуры все символы, которые направлены для записи в файл, будут гарантированно записаны в нем

procedure GetDir(D: Byte; var S: string);

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

0 — по умолчанию (текущий);

Процедура не генерирует код ошибки. Если имя диска в D оказывается ошибочным, то в строке S возвращается значение Х:\, как если бы текущая папка была на этом ошибочно указанном диске

function lOResult: Integer;

Функция возвращает статус последней произведенной операции ввода/вывода, если контроль ошибок выключен

procedure MkDir(S: string);

Процедура создает новый каталог, который описывается в строке S

procedure Rename (var F; NewName: string) ;


Процедура изменяет имя файла, связанного с файловой переменной F. Переменная NewName является строкой типа string или PChar (если включена поддержка расширенного синтаксиса)

procedure RmDir(S: string);

Процедура удаляет пустой каталог, путь к которому задается в строке S. Если указанный каталог не существует или он не пустой, то возникает сообщение об ошибке ввода/вывода

procedure Seek (var F; N: Longint) ;

Перемещает текущую позицию курсора на N позиций. Данная процедура используется только для открытых типизированных или нетипизированных файлов для проведения чтения/записи с нужной позиции файла. Началу файла соответствует нулевой номер позиции. Для добавления новой информации в конец существующего файла необходимо установить указатель на символ, следующий за последним. Для этого можно использовать выражение Seek (F, FileSize(F))

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

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

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

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

SeekEoln может быть использован только с открытым текстовым файлом

procedure SetTextBuf (var F: Text; var Buf [; Size: Integer] );

Связывает с текстовым файлом буфер ввода/вывода. F — файловая переменная текстового типа. Каждая файловая переменная текстового типа имеет внутренний буфер емкостью 128 байт, в котором накапливаются данные при чтении и записи. Такой буфер пригоден для большинства операций. Однако при выполнении программ с интенсивным вводом/выводом буфер может переполниться, что приведет к записи операций ввода/вывода на диск и, как следствие, к существенному замедлению работы приложения. SetTextBuf позволяет помещать в текстовый файл F информацию об операциях ввода/вывода вместо ее размещения в буфере. Size указывает размер буфера в байтах. Если этот параметр опускается, то полагается размер, равный SizeOf (Buf). Новый буфер действует до тех пор, пока F не будет связана с новым файлом процедурой AssignFile

Удаляет все позиции, следующие после текущей позиции в файле. А текущая позиция становится концом файла. С переменной F может быть связан файл любого типа за исключением текстового

Илон Маск рекомендует:  Что такое код asp getnthdescription
НОВОСТИ ФОРУМА
Рыцари теории эфира
01.10.2020 — 05:20: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Youtube]69vJGqDENq4[/Youtube][/center]
[center]14:36[/center]
Osievskii Global News
29 сент. Отправлено 05:20, 01.10.2020 г.’ target=_top>Просвещение от Вячеслава Осиевского — Карим_Хайдаров.
30.09.2020 — 12:51: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Ok]376309070[/Ok][/center]
[center]11:03[/center] Отправлено 12:51, 30.09.2020 г.’ target=_top>Просвещение от Дэйвида Дюка — Карим_Хайдаров.
30.09.2020 — 11:53: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Youtube]VVQv1EzDTtY[/Youtube][/center]
[center]10:43[/center]

интервью Раввина Борода https://cursorinfo.co.il/all-news/rav.
мой телеграмм https://t.me/peshekhonovandrei
мой твиттер https://twitter.com/Andrey54708595
мой инстаграм https://www.instagram.com/andreipeshekhonow/

[b]Мой комментарий:
Андрей спрашивает: Краснодарская синагога — это что, военный объект?
— Да, военный, потому что имеет разрешение от Росатома на манипуляции с радиоактивными веществами, а также иными веществами, опасными в отношении массового поражения. Именно это было выявлено группой краснодарцев во главе с Мариной Мелиховой.

[center][Youtube]CLegyQkMkyw[/Youtube][/center]
[center]10:22 [/center]

Доминико Риккарди: Россию ждёт страшное будущее (хотелки ЦРУ):
https://tainy.net/22686-predskazaniya-dominika-rikardi-o-budushhem-rossii-sdelannye-v-2000-godu.html

Завещание Алена Даллеса / Разработка ЦРУ (запрещено к ознакомлению Роскомнадзором = Жид-над-рус-надзором)
http://av-inf.blogspot.com/2013/12/dalles.html

[center][b]Сон разума народа России [/center]

[center][Youtube]CLegyQkMkyw[/Youtube][/center]
[center]10:22 [/center]

Доминико Риккарди: Россию ждёт страшное будущее (хотелки ЦРУ):
https://tainy.net/22686-predskazaniya-dominika-rikardi-o-budushhem-rossii-sdelannye-v-2000-godu.html

Завещание Алена Даллеса / Разработка ЦРУ (запрещено к ознакомлению Роскомнадзором = Жид-над-рус-надзором)
http://av-inf.blogspot.com/2013/12/dalles.html

[center][b]Сон разума народа России [/center]

DelphiComponent.ru — бесплатно видеоуроки по Delphi, статьи, исходники

Работа с файлами в Delphi

Вот поэтому рекомендуется использовать специализированный в Delphi объект TFiiestream. Вы можете написать и свой класс, который будет делать то же самое, но TFiiestream делает это достаточно хорошо.

У TFileStream есть еще одно преимущество — вам не нужно отслеживать ново­введения от Microsoft. В этой корпорации постоянно появляются новые идеи, из-за которых появляются новые функции, а мы, программисты, должны постоянно изу­чать новинки и переписывать код. Лично я намучился уже с функциями работы с файлами. Их так много, что с первого взгляда даже не поймешь, какие новые и рекомендуются к использованию, а какие устарели. В любом случае, использова­ние этого объекта намного проще, чем WinAPI, поэтому желательно начинать изу­чение работы с файлами именно с него.

Итак, давайте взглянем на объект TFileStream. Первое, что надо сделать, — это объявить какую-нибудь переменную типа TFileStream:

Вот так мы объявили переменную f типа объекта TFileStream. Теперь можно проинициапизировать переменную.

Инициализация — выделение памяти и установка значений по умолчанию. За эти действия отвечает метод create. Нужно просто вызвать его и результат выполнения присвоить переменной. Например, в нашем случае нужно вызвать TFileStream.create и результат записать в переменную f.

Давайте разберемся, какие параметры могут быть при инициализации объекта TFileStream. У метода create может быть три параметра, причем последний мож­но не указывать.

  • Имя файла (или полный путь к файлу), который надо открыть. Этот параметр — простая строка.
  • Режим открытия. Здесь вы можете указать один из следующих параметров открытия файла:
    • fmCreate— создать файл с указанным в первом параметре именем. Если файл уже существует, то он откроется в режиме для записи;
    • fmOpenRead — открыть файл только для чтения. Если файл не существует, то произойдет ошибка. Запись в файл в этом случае не возможна;
    • fmopenwrite — открыть файл для записи. При этом во время записи текущее содержимое уничтожается;
    • fmOpenReadWrite — открыть файл для редактирования (чтения и записи).
  • Права, с которыми будет открыт файл. Здесь можно указать одно из следую­щих значений (а можно вообще ничего не указывать):
    • fmshareCompat — при этих правах другие приложения тоже имеют права ра­ботать с открытым файлом;
    • fmShareExciusive — другие приложения не смогут открыть файл;
      • fmShareDenyWrite — при данном режиме другие приложения не смогут от­крывать этот файл для записи. Файл может быть открыт только для чтения;
      • fmshareDenyRead— при данном режиме другие приложения не смогут от­крывать этот файл для чтения. Файл может быть открыт только для записи;
      • fmShareDenyNone — не мешать другим приложениям работать с файлом.

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

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

После того как вы поработали с файлом, достаточно вызвать метод Free, чтобы закрыть его:

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

  • Число, указывающее на позицию, в которую надо перейти. Если вам нужно пе­редвинуться на 5 байт, то просто поставьте цифру 5.
  • Откуда надо двигаться. Тут возможны три варианта:
  • soFromBeginning — двигаться на указанное количество байт от начала файла.
    • soFromCurrent — двигаться на указанное количество байт от текущей пози­ции в файле к концу файла.
    • soFromEnd — двигаться от конца файла к началу на указанное количество байт.

Не забывайте, что 1 байт— это один символ. Единственное исключение — файлы в формате Unicode. В них один символ занимает 2 байта. Так образом, надо учитывать, в каком виде хранится информация в файле.

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

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

В следующем примере устанавливается позиция в файле на 0 байт от конца, т. е. в самый конец. Тем самым получается полный размер файла:

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

Для чтения из файла нужно использовать метод Read. И снова у этого метода два параметра:

  • переменная, в которую будет записан результат чтения;
  • количество байт, которые надо прочитать.

Давайте взглянем на код чтения из файла, начиная с 15-й позиции. Этот код вы можете увидеть в листинге

f:TFileStream; //Переменная типа объект

TFileStream. buf: array[0..10] of char; // Буфер для хранения прочитанных данных begin

//В следующей строке я открываю файл filename.txt для чтения и записи,

f:= TFileStream.Create(1 с:\filename.txt’, fmOpenReadWrite);

f.Seek(15, soFromCurrent); // Перемещаюсь на 15 символов вперед,

f.Read(buf, 10); // Читаю 10 символов из установленной позиции.

f.Free; // Уничтожаю объект и соответственно закрываю файл, end;

Обратите внимание, что в методе seek движение происходит на 15 символов не от начала, а от текущей позиции, хотя нужно от начала. Это потому, что после от­крытия файла текущая позиция и есть начало. Но лучше на это не рассчитывать.

Метод Read возвращает количество реально прочитанных байт (символов). Если не возникло никаких проблем, то это число должно быть равно количеству запро­шенных для чтения байт.

Есть только два случая, когда эти числа отличаются:

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

Осталось только разобраться с записью. Для этого мы будем использовать метод write. У него так же два параметра:

  • переменная, содержимое которой нужно записать;
  • число байт для записи.

Пользоваться этим методом можно точно так же, как и методом для чтения.

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

Блог GunSmoker-а

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

12 ноября 2011 г.

Сериализация — потоки данных

Оглавление

Общие сведения

Если вы вспомните определение файла, то оно звучало так: «файл — это устройство с последовательным доступом, к которому можно обратиться по имени». При этом физические файлы на диске являются лишь частным случаем. Отнимите из этого определения часть «имеющие имя» — и вы получите определение потока данных. Поток данных представляет собой «что-то» с последовательным доступом. Вы можете читать из него данные или записывать. Некоторые потоки поддерживают позиционирование (вроде физического файла на диске), другие — нет (вроде сетевого сокета).

Потоки данных являются де-факто стандартом для обмена данными в Delphi. Всюду в своих процедурах, где вам необходимо принимать или отправлять нетипизированный набор данных, используйте потоки данных. Многие механизмы Delphi по умолчанию умеют работать именно с потоками данных, предоставляя методы вроде LoadFromStream и SaveToStream (и иногда предоставляя к ним обёртки-переходники вроде LoadFromFile и SaveToFile ).

Примечание: поток данных (stream) не следует путать с потоком кода (thread), который также иногда называют нитью. Они не имеют между собой ничего общего, кроме слова «поток» в названии. Если какой-то текст говорит про «потоки», не уточняя кода или данных, то значение термина должно быть ясно из контекста. Эти понятия не пересекаются, так что здесь не должно быть никаких проблем с пониманием. Замечу, что подобная путаница возможна только в русском языке, где два разных понятия переводятся одинаково. В английском языке для них используются разные слова (stream и thread).

В Delphi все потоки данных реализованы как объекты (экземпляры) классов, наследуемых от TStream . TStream является абстрактным базовым классом, который поддерживает операции чтения, записи и позиционирования, но сам при этом не умеет делать ничего. Конкретная работа реализуется его классами-наследниками.

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

Вот (неполный) список классов-наследников TStream (примеры):

  • TFileStream (для работы с файлами)
  • TResourceStream (для работы с ресурсами программы)
  • TStringStream (для работы со строками)
  • TMemoryStream (для работы с буфером в памяти)
  • TBlobStream (для работы с BLOB полями)
  • TWinSocketStream (для работы с сетевым сокетом)

По сравнению с прошлой темой, где было всего три вполне конкретных файловых типа, это может быть немного непонятно: зачем нужен какой-то абстрактный класс и классы-наследники? Очень просто: пусть вы хотите уметь загружать растровые изображения (bitmap). Но ведь рисунок может лежать не только в файле, он может быть и в ресурсах программы и в памяти. Не писать же три разных метода, которые делают одно и то же? Вот поэтому и нужен абстрактный класс. Он объявляет общий контракт, которому обязуются следовать все его наследники. Поэтому вы можете спокойно написать (один раз) код, который грузит рисунок из TStream . А уж вызывающий подставит вам TFileStream для загрузки рисунка из файла, TResourceStream для загрузки из ресурса и TMemoryStream для загрузки из памяти. В определённом смысле все эти классы-наследники представляют собой простые переходники (от общей спецификации, определённой TStream , до конкретного метода доступа: файл, ресурс, память, сеть и так далее; и наоборот). В общем, полиморфизм в действии.

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

Кроме того, потоки обеспечивают поддержку для загрузки/сохранения компонентов и форм. Именно благодаря этому механизму работает загрузка .dfm файлов в run-time. Этот механизм работает автоматически. Впрочем, вы можете использовать его и в своих целях. Но на это мы посмотрим в другой раз, потому что он тесно связан с RTTI. Это будет темой одной из следующих статей.

Общие принципы работы с потоками данных

Чтение/запись
Класс TStream имеет два метода для чтения данных и два метода для записи данных. Работают они обычным образом: поток читает (или пишет) указанный блок данных и сдвигает текущую позицию на размер блока данных, так что следующая операция начинается там, где закончилась предыдущая. Ничего сложного.

Итак, для чтения класс TStream предлагает методы Read и ReadBuffer , а для записи — методы Write и WriteBuffer . Эти методы используются одинаково: первым параметром указывается буфер (это нетипизированный параметр), а вторым параметром — его размер в байтах, например: Разница между методами с «Buffer» и без него заключается в том, что методы с суффиксом «Buffer» гарантируют выполнение операции до конца. Если же это невозможно (к примеру, в файле 12 байт, а вы командуете прочитать 24 байта), то будет возбуждено исключение (см. также ниже раздел про обработку ошибок).

А вот методы без суффикса «Buffer» допускают частичное выполнение операции. Они никогда не возбуждают ошибку, а вместо этого возвращают, сколько реально байт было прочитано или записано. Иногда, это может быть и 0 байт. К примеру, если в файле 12 байт, а вы вызываете Read , указывая 24 байта, то метод Read прочитает 12 байт и вернёт вам число 12 (это метод-функция). Ещё пример: если у вас поток связан с сетевым сокетом и вы вызываете Read , но пока никаких данных ещё не пришло: метод завершится тут же, возвращая 0.

Замечу, что некоторые потоки данных могут быть только для чтения или только для записи. К примеру, однонаправленный pipe может не допускать чтения, а ресурсы, очевидно, не разрешают запись. В таких случаях попытка вызова запрещённого метода приведёт к ошибке.

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

Разумеется, некоторые из наследников TStream могут вводить свои специальные методы для чтения/записи. Мы посмотрим на некоторые примеры ниже.

Кроме чтения и записи TStream имеет и некоторые другие методы.

Позиционирование
Во-первых, это методы позиционирования. Вы можете вызвать свойство Size , которое вернёт вам размер потока данных в байтах (кроме того, вы можете устанавливать свойство Size , чтобы менять размер потока, но это поддерживается далеко не всеми видами потоков данных). Свойство Position указывает текущую позицию в потоке данных, где 0 соответствует началу, а значение, равное Size , — концу. Вы можете читать свойство Position , чтобы узнать текущую позицию, и записывать значение в Position , чтобы изменить текущую позицию (позиционирование поддерживается не всеми видами потоков). К примеру: Кроме абсолютного позиционирования через свойство Position , потоки данных поддерживают относительное позиционирование в стиле файловых Seek-процедур: метод Seek . Этот метод имеет два параметра: позицию и точку отсчёта. Причём последнее может иметь значения soBeginning (отсчёт от начала потока, аналог абсолютного позиционирования), soCurrent (отсчёт от текущей позиции, относительное смещение) и soEnd (отсчёт от конца потока). Для soCurrent позиция может быть и положительным числом (смещение в сторону конца потока) и отрицательным (смещение в сторону начала), для soBeginning смещение может быть только положительным (или нулём), а для soEnd — только отрицательным (или нулём). Есть также устаревшие константы вида soFromBeginning , soFromCurrent , soFromEnd — не используйте их в новом коде. При этом метод Seek возвращает предыдущее значение текущей позиции (до позиционирования). К примеру:
Копирование
У TStream есть ещё один метод: CopyFrom . Этот метод копирует указанное количество данных (в байтах) из указанного массива. Копирование производится с текущей позиции. Метод работает аналогично методу записи WriteBuffer , сдвигая текущую позицию на указанное количество байт и возбуждая исключение при ошибках. Использование CopyFrom позволяет избежать создания буфера, чтения в него данных из исходного потока, запись буфера в выходной поток и удаления буфера — все эти действия выполняются автоматически внутри метода CopyFrom . К примеру, вот простейший пример копирования файлов (см. ниже описание TFileStream ): У CopyFrom есть специальный случай: если последний параметр (размер) равен 0, то CopyFrom скопирует весь поток целиком — начиная с начала потока (вне зависимости от текущей позиции) и до конца. Так что если число байт для записи у вас не фиксировано, а как-то вычисляется, то вставьте перед вызовом CopyFrom проверку на 0: иначе вы скопируете поток целиком вместо 0 байт.

Обработка ошибок

Здесь нет ничего сложного, потоки данных используют стандартную обработку исключений. Вы можете просто писать код, не производя проверок — он по умолчанию будет иметь обработку ошибок. Конечно же, вам нужно использовать try..finally для корректной обработки утечек ресурсов программы: Во втором примере try..finally не используется при работе с потоком, потому что он вынесен во внешний код (вызывающий).

Все исключения, возбуждаемые самим потоком, наследуются от EStreamError . Наиболее типичными случаями являются ошибки EFileStreamError , EFCreateError , EFOpenError , EReadError и EWriteError .

Так что код обработки исключений потоков данных (если он вам нужен) может выглядеть как-то так:
Всё вышеуказанное — это типичное поведение для работы с исключениями. Тут не должно быть ничего нового и нетипичного.

Так что единственное «ага!» в обработке ошибок — разница между Read и ReadBuffer и между Write и WriteBuffer . Запомните, что если вы вызываете методы без суффикса «Buffer», то вы должны либо явно проверять результат их вызова (прямо как с кодом на кодах ошибок), либо иметь в виду, что данные могут прочитаться/записаться не полностью.

Правила использования

Хотелось бы сказать о «правилах использования» — а, скорее, о наиболее типичных ошибках новичков при работе с потоками данных.

    (Почти) всегда используйте ReadBuffer и WriteBuffer . Используйте Read и Write только если вам не важно выполнение операции до конца (к примеру, вы не знаете, сколько данных нужно прочитать). Иными словами, ReadBuffer и WriteBuffer должны быть вашим вариантом по умолчанию, а не наоборот.

  • Если вы в своей процедуре принимаете или отправляете какие-то данные — используйте TStream . Не используйте для этого нетипизированные параметры, указатели или конкретные экземпляры TStream . Т.е. вместо: должно быть:
  • Частая ошибка новичков: они забывают про позиционирование. К примеру: Должно быть:
  • TStream является абстрактным классом. Это значит, что в вашем коде не должно быть строк вида TStream.Create . Вы всегда должны использовать конкретного наследника — например, TFileStream или TResourceStream . Если вам нужен «просто поток», то вы можете использовать TMemoryStream — это создаст поток в памяти программы. Если при этом вы хотите использовать большой объём данных, то вы можете использовать TFileStream для временного файла. См. также.

    Не забывайте, что потоки работают с большими данными, поэтому всё позиционирование осуществляется на базе Int64 (8 байт/64 бита), а не Integer (4 байта/32 бита). Поэтому если вы по недосмотру где-то используете для позиционирования выражение/переменную типа Integer , то этим вы автоматически ограничите свои данные 2 Гб. Но этот момент также зависит и от вида используемого потока. К примеру, в 32-битных приложениях TMemoryStream не может работать с памятью больше 4 Гб, а TResourceStream ограничен 2 Гб — потому что он работает с ресурсами в исполняемых файлах формата PE. А любой файл формата PE не может быть больше 2 Гб.

  • Ну и под конец упомяну ещё такой момент: поскольку базовые методы чтения/записи работают с нетипизированными параметрами, то тут возможны ошибки при использовании динамических типов и указателей. Я уже приводил подобный пример в предыдущей статье, но приведу его ещё раз и тут:
  • Практика

    Все примеры ниже приводятся с полной обработкой ошибок. Примеры используют файл в папке с программой. Это удобно для тестирования и экспериментов, но, как указано ранее, этого нужно избегать в реальных программах. После тестирования и перед использованием кода в релизе вы должны заменить папку программы на подпапку в Application Data.

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

      Одно значение: Double

    Одно значение переменного размера: String Данный случай прост — размер данных определяется по размеру файла. Для примера я выбрал AnsiString , а не String по двум причинам: во-первых, String — это псевдоним либо на AnsiString , либо на UnicodeString (в зависимости от версии Delphi). Так что вам нужно использовать явные типы: AnsiString , WideString (или UnicodeString ), а не String — иначе файл, созданный в одном варианте программы, нельзя будет прочитать в другом варианте программы.

    Во-вторых, используя AnsiString , я показал, как вы можете загрузить в строку весь файл целиком, «как есть». Хотя, если подобный подход использовать в реальных программах, то уж лучше использовать array of Byte или хотя бы RawByteString — чтобы подчеркнуть двоичность данных.

    Набор однородных значений: array of Double И снова, благодаря фиксированности размеров элементов, мы можем установить размер массива ещё до чтения из файла. Обратите внимание, что мы могли бы свести этот пример к предыдущему, прочитав/записав весь массив за раз, вместо поэлементного копирования.

    Также обратите внимание, что не имеет значения, какой индекс используется внутри выражения у SizeOf . Более того, не требуется даже наличие (существование) этого элемента. Это потому, что мы не обращаемся к нему — мы только просим у компилятора его размер. Это, по сути, константа. Так что всё выражение вообще не вычисляется — оно просто заменяется числом. Это удобный трюк для написания подобного кода, потому что это удобнее, чем писать тип явно: SizeOf(Double) . Почему? А что, если мы изменим объявление типа с Double на Single ? И забудем обновить SizeOf ? Тогда это приведёт к порче памяти — т.к. писаться или читаться будет больше, чем реально есть байт в элементе. Это выглядит не очень страшно для массива из Double , но рассмотрите вариант, скажем, строки — изменение размера Char гораздо более вероятно. А вот если мы используем форму SizeOf как в примере, то такой проблемы не будет — размер изменится автоматически.

    Набор однородных значений переменного размера: array of String С записью набора динамических данных возникает проблема — как отличить один элемент от другого? Мы не можем более использовать переход на другую строку, как это было с текстовыми файлами. Тут есть несколько вариантов.

    Самый простой и очевидный — ввести разделитель данных. Т.е. элементы отделяются друг от друга специальным символом. В качестве такого чаще всего выступает нулевой символ (#0) — это аналог разделителя строк в текстовых файлах. Тогда чтение-запись сведётся к примеру два. Но я не стал показывать этот путь, т.к. он очевидно вводит ограничение на возможные данные: теперь данные не могут содержать в себе разделитель (каким бы вы его ни выбрали). Конечно, вы можете его экранировать, но гораздо проще будет выбор другого подхода.

    И я его показал — это явная запись размера данных до записи самих данных. Т.е. мы пишем два значения для каждого элемента: длину и сами данные.

    Кроме того, в этом же примере показано, как можно сделать так, чтобы внутри программы работать с хорошо знакомым String , а в файле хранить фиксированный тип ( AnsiString / RawByteString или WideString / UnicodeString ). Вообще говоря, даже если вы работаете на Delphi 7 или любой другой версии Delphi до 2007 включительно — я бы рекомендовал всегда писать Unicode-данные в формате WideString во внешние хранилища.

    Обратите внимание, что в качестве счётчика длины используется LongInt , а не Integer — по причинам, указанным выше для типизированных файлов: String , Extended , Integer и Cardinal могут менять свои размеры в зависимости от окружения — поэтому мы используем другие типы, которые гарантированно всегда имеют один и тот же размер.

    Ещё в этом примере показан вариант пример использования метода Read : идея в том, что если будет достигнут конец потока, то вызов метода Read вернёт 0. Т.е. это аналог функции EoF. Альтернативным решением является код Вы также можете создать вспомогательную функцию И тогда этот пример будет эквивалентен примеру для нетипизированных файлов Pascal. Я буду использовать подпрограмму EoS в примерах ниже.

    Запись — набор неоднородных данных: В отличие от типизированных файлов, для нетипизированных файлов нет никаких проблем с записью неоднородных данных — вы просто пишете одно за другим. Данные фиксированного размера самодокументируются, а для динамических данных вы пишете сначала их размер, а затем сами данные. При чтении повторяете всё это в обратном порядке.

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

    Вообще, конечно же, более правильный код получится при использовании шаблона «декоратор». Суть заключается в создании класса, который реализует методы вида WriteBufferDyn / ReadBufferDyn , выполняя их над потоком, который ему указали. Например: В этом примере реализовано 3 вещи: во-первых, мы реализовали хранение ссылки на поток данных, над которым будем выполнять операции. Во-вторых, мы реализовали несколько вспомогательных низкоуровневых операций в секции private . В-третьих, мы реализовали высокоуровневые операции Read и Write в секции public . Тогда сохранение и загрузка сводятся к такому простому коду: Ну не красота-ли?

    Имейте в виду, что именно этот подход вам нужно применять в реальных программах. В примерах ниже я буду применять процедуры вроде WriteBufferDyn / ReadBufferDyn исключительно по соображениям «меньше писать».

    Набор (массив) из записей — иерархический набор данных: Для начала хочу сразу же заметить, что странное выражение для поля Salary сделано для обхода бага Delphi. Вообще, там должно стоять просто F.WriteBuffer(Values[Index].Salary, SizeOf(Values[Index].Salary)) , но в настоящий момент это выражение даёт ошибку «Variable required», поэтому используется обходной путь: мы берём указатель и разыменовываем его. Вообще говоря, это NOP-операция. А смысл её заключается в потере информации о типе. Это достаточно частый трюк, когда мы хотим запустить свои шаловливые руки под капот языка, минуя информацию типа, но в данном случае он используется для более благих целей: обхода бага компилятора. Вы можете использовать F.WriteBuffer(Values[Index].Salary, SizeOf(Values[Index].Salary)) , если ваша версия компилятора это позволяет, или просто выбрать другой тип данных (не Currency ).

    В любом случае, надо заметить, что достаточно часто при записи/чтении массива записей новички пытаются сделать такую вещь, как запись элемента целиком ( F.WriteBuffer(Values[Index], SizeOf(Values[Index])) ). Это будет работать для записей фиксированного размера, не содержащих динамические данные (указатели). Ровно как это работает для типизированных файлов. Но если в записях у вас встречаются строки, динамические массивы и другие данные-указатели, то этот подход не будет работать. Собственно, если вы используете типизированные файлы, то компилятор даже не даст вам объявить такой тип данных ( file of String , например, или file of Запись , где Запись содержит String ). Но суть потоков данных — в прямом доступе, минуя информацию типа. Так что по рукам за это вам никто не даст. Вместо этого код будет просто вылетать или давать неверные результаты. А проблема тут в том, что для динамических данных, поле — это просто указатель. Записывая элемент «как есть» вы запишете в файл значение указателя, но не данные, на которые он указывает. Запись в файл произойдёт нормально, но в файле вы не найдёте своих строк. Чтение из файла тоже пройдёт отлично. Но как только вы попробуете обратиться к прочитанной строке — код вылетит с access violation, потому что указатель строки указывает в космос, на мусор.

    Аналогично предыдущим обсуждениям, самый простой способ решения проблемы (но не всегда достаточный) — замена String в записях на ShortString или статический массив символов. Я не буду рассматривать этот вариант, т.к. он сводится к предыдущим примерам с записью данных фиксированного размера.

    Вместо этого в примере я показал уже известную технику: запись длины строки вместе с её данными. Это избавляет вас от всех недостатков ShortString /массива символов, но даёт новый недостаток: теперь вы не можете сохранить данные одной строчкой, вам нужно писать их поле-за-полем.

    Также по аналогии с предыдущим примером я покажу, как будет выглядеть код, если вы введёте класс-декоратор. Само описание класса я опущу для краткости — оно аналогично (но не тождественно) предыдущему примеру: Мы также могли внести методы Read и Write , работающие с массивом из TPerson в класс-декоратор.

    Массив из записей внутри записи — составные данные: Как видите — здесь нет никаких проблем, вы просто соединяете воедино техники из предыдущих примеров. Мы используем технику с записью счётчика длины для динамических данных в двух местах: при записи строк и при записи массивов (поле Related ).

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

    Заметьте, что вы всё ещё должны писать записи по отдельным полям. И если вы меняете объявление записи — вам лучше бы не забыть поменять код, сериализующий и десериализующий запись.


    И снова: не забывайте, что это только пример. В реальных программах вам следует посмотреть в сторону класса-декоратора. Я не буду приводить тут пример: он делается по аналогии с предыдущими случаями. Просто добавьте новые методы чтения/записи, вот и всё.

    Особенности

    Посмотрим теперь на различные конкретные наследники класса TStream и то, что они нам предлагают.

    Примечание: не все из нижеперечисленных возможностей существуют во всех версиях Delphi. Если у вас старая версия Delphi, то у вас могут отсутствовать некоторые описываемые классы, свойства или методы. В этом случае вам придётся обходится без них или писать аналоги самостоятельно.

    THandleStream

    THandleStream предназначен для работы с файлами в смысле операционной системы.

    Короче говоря, THandleStream является оболочкой к файлам в стиле ОС. Объект этого типа можно связать с THandle , полученный любым способом — скажем, от CreateFile , CreatePipe и так далее: лишь бы на этот описатель можно было вызывать ReadFile и WriteFile .

    Используйте THandleStream в двух случаях:

    1. Функция ОС вернула вам описатель THandle , а код, который вы хотите вызвать, требует TStream . Тогда просто создайте THandleStream , передав в его конструктор описатель от функции ОС, и передайте полученный объект коду. Например:
    2. Вы хотите использовать возможность, которую предоставляет функция ОС, но не объект Delphi. См. первый пример в следующем пункте.

    Сам THandleStream не имеет ограничений и поддерживает все операции: чтение, запись, позиционирование и изменение размера. Однако нижележащий дескриптор объекта ядра может поддерживать не все операции. К примеру, описатель от файла на диске может быть открыт только для чтения, а описатель pipe не поддерживает позиционирование.

    Описатель, которым инициализирован объект, доступен через свойство Handle .

    Обратите внимание, что сам THandleStream никогда не закрывает описатель (ему нельзя передать ответственность за него). Поэтому вы должны закрывать описатель вручную или использовать TFileStream (см. ниже).

    TFileStream

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

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

    Кроме того, TFileStream поддерживает перегруженный конструктор, позволяющий открывать файлы. У него есть два параметра: (имя файла) и (режим открытия + режим разделения).

    Допустимые режимы открытия:

    1. fmCreate — создаёт новый файл. Если такой файл уже есть, он удаляется перед созданием. Файл открывается в режиме записи.
    2. fmOpenRead — открывает файл только для чтения.
    3. fmOpenWrite — открывает файл только для записи.
    4. fmOpenReadWrite — открывает файл для чтения-записи.

    Режимы разделения:

    1. fmShareExclusive — запретить совместное использование.
    2. fmShareDenyWrite — запретить другим приложениям запись.
    3. fmShareDenyRead — запретить другим приложениям чтение.
    4. fmShareDenyNone — не вводить ограничения.

    Режимы следует соединять друг с другом операцией or .

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

    Типичный пример открытия файла выглядит так: А создания файла — так: При работе с файлами типа логов (для которых необходимы совместное использование или мониторинг) могут использоваться такие вызовы:
    Далеко не все возможности функций открытия файлов ОС доступны через конструктор TFileStream , но зато он является универсальным для любых платформ. Предпочтительно использовать именно его для доступа к файлам вместо функций ОС. Если же вам нужны какие-либо возможности, недоступные через конструктор TFileStream , то используйте TFileStream , инициализировав его описателем файла от системной функции открытия файлов, как указано в примерах выше.

    Если объект TFileStream создавался через конструктор с именем файла, то это имя файла доступно в свойстве FileName , иначе доступно только свойство Handle .

    TMemoryStream

    TMemoryStream реализует потоковую обёртку к данным в памяти программы. Т.е. к буферу в динамической памяти осуществляется последовательный доступ.

    Используйте TMemoryStream , если вам нужен «просто поток» или вам нужен промежуточный буфер-поток.

    TMemoryStream имеет конструктор без параметров, который создаёт пустой объект. Для заполнения потока после создания можно использовать обычные методы записи или же специальные методы LoadFromStream (аналог вызова CopyFrom(Stream, 0) ) и LoadFromFile .

    TMemoryStream не имеет ограничений и поддерживает все операции, включая изменение размера.

    TMemoryStream хранит данные в динамической куче процесса, выделяя память по мере необходимости. Он сам автоматически управляет памятью. Реально памяти может быть выделено больше, чем лежит данных в потоке — т.н. «capacity > size». Это стандартная оптимизация для «побайтовых записей».

    Дополнительной возможностью TMemoryStream является предоставление свойства Memory , позволяющего обратиться к данным потока напрямую, через указатель, минуя последовательные методы чтения/записи. По этой причине вы можете рассматривать TMemoryStream как «переходник» между TStream и нетипизированным указателем.

    Простейший пример использования TMemoryStream (в данном случае — для конвертации строки в TStream ): Примечание: в данном примере более эффективной была бы другая конструкция. Данный пример по сути копирует данные строки в поток. Но поскольку реально нам нужно здесь только чтение, то можно поступить по другому: создать поток, который будет работать прямо поверх данных строки, без их копирования. Это будет настоящий адаптер. Мы посмотрим на такой пример ниже.

    TResourceStream

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

    TResourceStream похож на TMemoryStream . Он тоже работает с памятью программы (только не с кучей, а с ресурсами), он поддерживает методы SaveToStream и SaveToFile , а также свойство Memory .

    Но в отличие от TMemoryStream , TResourceStream поддерживает только методы чтения, но не записи, а также не поддерживает изменение размера. Иными словами, TResourceStream — это read-only.

    Собственно для инициализации у TResourceStream есть два варианта конструктора, которые имеют по три параметра: описатель модуля, имя ресурса и тип ресурса. А разница между ними заключается в способе указания имени ресурса (второго параметра): по ID или по имени.

    Ну и несколько примеров:

    TBytesStream

    TBytesStream хранит данные потока в массиве байтов.

    Используйте TBytesStream как переходник между TBytes и TStream .

    Собственно, TBytesStream аналогичен TMemoryStream , только вместо блока памяти в куче он использует TBytes в качестве хранилища для данных. Он также не имеет ограничений на операции, поддерживая чтение, запись, позиционирование и изменение размера. Он поддерживает методы LoadFromStream , LoadFromFile , SaveToStream и SaveToFile , а также свойство Memory .

    В отличие от TMemoryStream , у TBytesStream есть конструктор, который принимает переменную типа TBytes — это будут начальные данные потока. При этом не производится копирование данных (используется счётчик ссылок динамического массива). Все операции чтения-записи будут оперировать с исходными данными в оригинальной переменной типа TBytes . Однако если вы измените размер потока (либо явно через Size / SetSize , либо неявно через запись данных в конец потока данных), то поток сделает копию данных и будет работать уже с ней. При этом все будущие изменения в потоке не затронут оригинальной переменной типа TBytes .

    Вы также можете передать nil в конструктор, чтобы инициализировать пустой поток. В этом случае он не будет связан с переменной.

    Дополнительно TBytesStream вводит свойство Bytes оно работает аналогично свойству Memory , только имеет тип TBytes , а не Pointer . Предупреждение: не пытайтесь использовать Length для определения размера данных. Размер хранилища может быть больше актуального размера («Capacity > Size»). Используйте свойство Size для определения размера данных.

    Простой пример использования TBytesStream как переходника (обратите внимание на усечение данных до их актуального размера, указанного в свойстве Size ):
    TBytes является динамическим массивом, т.е. автоуправляемым типом. Явно освобождать его не нужно, заботиться о вопросах владения — тоже.

    TStringStream

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

    Используйте TStringStream для хранения данных в строках. Использование TStringStream даст вам в руки мощные возможности TStream . TStringStream удобен как промежуточный объект, который умеет хранить данные в строке, а также читать и писать их.

    По сути, TStringStream является обёрткой к TBytesStream , которая просто конвертирует строку в байты и обратно. У TStringStream есть несколько вариантов конструкторов, которые инициализируют поток по разным типам строк. В Unicode версиях Delphi конструкторы также позволяют вам указывать кодировку для ANSI строк.

    Методы чтения-записи TStringStream не затрагивают исходную строку, а всегда работают с копией данных (внутреннее хранилище в виде TBytes ).

    Ну и, конечно же, TStringStream предоставляет строко-ориентированные свойства и методы. Во-первых, это методы WriteString и ReadString, которые пишут и читают данные из потока в виде строки. При этом кодировка (в Unicode-ных версиях Delphi) контролируется свойством Encoding . И, равно как и предыдущие классы, TStringStream выставляет наружу хранилище в «родном» формате: DataString .

    Простой пример использования TStringStream как переходника между строками и TStream :
    Заметьте, что несмотря на наличие методов чтения строк и загрузки/сохранения данных из/в файлы, TStringStream не пригоден для работы с текстовыми файлами. Он не работает с BOM и не позволяет прочитать одну строку (в смысле line) от разделителя до разделителя (он читает только указанное количество символов). По этой причине для работы с текстовыми файлами используют вспомогательный класс — TStringList . Этому классу будет посвящена следующая статья (где и будут показаны методы работы с текстом), а здесь же я только приведу примеры шифрования/расшифровки текстового файла, использующие оба этих класса: Конечно, на практике такой пример не имеет большого смысла, потому что гораздо проще просто работать с текстовым файлом как с двоичным — обработав его через TFileStream . Но более удачного и простого примера мне сейчас в голову не приходит, а код выше прекрасно показывает пример соединения трёх классов для работы.

    TStreamAdapter

    Понятие «потока данных» есть не только в Delphi, но и практически в любом другом современном языке. Разумеется, другие языки понятия не имеют, как работать с объектами Delphi, и наоборот: Delphi не знает, как устроены классы и объекты в других языках. К счастью, под Windows у нас есть COM и интерфейсы. С ними умеют работать почти все языки, так что это является де-факто стандартом межязыкового взаимодействия. И, конечно же, не могло быть иначе: для такой популярной концепции как «поток данных» существует свой интерфейс — IStream .

    Иными словами, если вам нужно передать куда-то поток данных — вы используете IStream . Если вам кто-то передаёт поток данных, то это будет IStream .

    Тут возникает маленькая проблемка: ваши Delphi объекты вообще-то не умеют работать с интерфейсом IStream : они работают с классом TStream . Что же делать?

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

    TStreamAdapter предоставляет переходник от TStream к IStream . Он принимает в конструкторе экземпляр TStream и выставляет наружу IStream , который вы можете передать в чужой код.

    TStreamAdapter поддерживает те же операции, что и оригинальный поток, который в него завёрнут.

    TStreamAdapter может взять на себе ответственность за удаление исходного потока данных, а может оставить её вам. Вот два примера использования TStreamAdapter , иллюстрирующие оба подхода:

    TOleStream

    TOleStream представляет собой обратный класс к TStreamAdapter : переходник от IStream к TStream .

    Используется он аналогично. Единственная разница — нет вопроса владения, поскольку интерфейсы относятся к автоуправляемым типам.

    TOleStream поддерживает те же операции, что и оригинальный поток, который в него завёрнут.

    Пример использования TOleStream :

    Прочие потоки

    Это далеко не все стандартные потоки, реализованные в Delphi. К примеру, есть ещё TWinSocketStream , TBLOBStream , TZCompressionStream и многие-многие другие. Многие из них являются переходниками, но много классов также предоставляют свою собственную функциональность.

    Создание своих классов-потоков

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

    К примеру, помните пример для TMemoryStream ? Там мы копировали данные строки в поток. Давайте сейчас напишем класс, который позволял бы работать с блоком памяти напрямую, без копирования. Разумеется, такой класс не может поддерживать операцию изменения размера, но чтение, запись и позиционирование — вполне.

    Это несложно, если использовать в качестве базового класса TCustomMemoryStream — он уже умеет читать данные из блока памяти, поддерживает позиционирование, но не умеет писать и никак не управляет нижележащим хранилищем. Тогда мы получаем: Вот список методов, которые вы можете захотеть переопределить в своих классах-наследниках:

    • (protected) function GetSize: Int64; и procedure SetSize(const NewSize: Int64); — для управление размером хранилища. Если вы не зададите свой GetSize , то реализация по умолчанию будет использовать Seek для поиска конца потока и определения размера (равному текущей позиции в конце потока). SetSize по умолчанию просто ничего не делает.
    • (public) function Read(var Buffer; Count: Longint): Longint; и function Write(const Buffer; Count: Longint): Longint; для чтения и записи данных. По умолчанию оба метода абстрактные. В любом наследнике вы должны их замещать.
    • (public) function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; для перемещения по потоку. Реализация по умолчанию вызывает исключение. Вы должны заместить этот метод, если ваши данные поддерживают позиционирование. Обычно это так. Исключение составляют случаи вроде сетевых сокетов.

    Вот и всё. Всего три категории и всего пять методов. Реализуйте их — и у вас будет готовый поток. Все прочие методы и свойства являются переходниками к вышеуказанным. К примеру, метод GetPosition (get-акцессор для свойства Position ) реализован как Result := Seek(0, soCurrent);

    В общем, как видите, создать свой класс-поток — это очень просто.

    Преимущества и недостатки потоков данных

    Плюсы:

    • Де-факто стандарт языка Delphi
    • Являются основой для других (более высокоуровневых) механизмов
    • Имеют готовые оболочки для самых типичных случаев («не надо писать самому» — в отличие от файлов в стиле Pascal)
    • Гибкость
    • Стандартная обработка ошибок на исключениях
    • Поддержка произвольных файлов (нет ограничения на размер)
    • Нет проблем с многопоточностью*
    • Межъязыковая совместимость (через IStream )
    • Поддержка Unicode и кодировок при работе с текстовыми данными (но не текстовыми файлами — нет поддержки BOM)
    • Легко расширяются написанием классов-наследников

    Минусы:

    • Необходимость ручной сериализации данных
    • Ориентированы на побайтовую обработку, слабо пригодны для работы с текстовыми файлами
    • Часто неопытные программисты используют Read и Write вместо ReadBuffer и WriteBuffer , не делая проверку результатов. Часто это приводит к некорректному коду без обработки ошибок
    • Круче кривая обучения: сам TStream не умеет делать ничего. Значит, чтобы делать в программах что-то полезное, нужно изучать многочисленные наследники TStream , чтобы знать кто что умеет и когда что кого нужно применять. К примеру, если вам нужно отправить растр по сети, то вы должны сообразить, что вы можете создать THandleStream для описателя сетевого сокета и использовать его в сочетании с методом SaveToStream объекта растра. Сравните это с файлами в стиле Pascal, где было всего три файловых типа, покрывавших все случаи использования

    Вывод: если вы работаете в Delphi и хотите работать с любыми данными, то потоки данных должны быть вашим первым вариантом. Используйте что-то другое, только если это «другое» больше подходит для вашей задачи (к примеру, динамический массив для типизированных данных; также некоторые люди могут рассматривать файлы в стиле Pascal более подходящими для работы с текстовыми данными).

    (*) — некоторые люди неверно читают это предложение. Обратите внимание, что речь идёт о достоинствах и недостатках по сравнению с другими способами работы с файлами. Например, файлами Паскаля. Поэтому, в этом предложении утверждается, что у вас не будет проблем если вы работаете с двумя разными потоками данных (двумя объектами) в двух разных потоках кода, а вовсе не (как читают это некоторые) то, что вы можете обращаться к одному объекту из разных потоков.

    Seek — Процедура Delphi

    Язык:
    Русский
    English

    Перемещает текущий указатель позиции файла на определенный компонент.

    Объявление

    Procedure Seek(Var F; N : Longint);

    Режим

    Windows, Real, Protected

    Замечания

    F — переменная любого файлового типа за исключением текстового, и N — выражение типа Longint. Указатель позиции файла F перемещается на номер компонента N. Номер первого компонента файла равен нулю. Чтобы расширить файл, вы можете передвинуть указатель на один компонент за последний компонент в файле. То есть, оператор Seek(F, FileSize(F)) перемещает текущий указатель позиции файла на конец файла.

    В режиме <$I->функция IOResult вернет 0, если операция была успешна, иначе, она вернет отличный от нуля код ошибки.

    Ограничения

    Не может быть использована на текстовых файлах. Файл должен быть открыт.

    См. также

    Пример

    Язык:
    Русский
    English

    var
    F : file of Byte ;
    Size : LongInt ;

    begin
    < Get File name from command line >
    Assign(F , ParamStr ( 1 )) ;
    FileMode := 0 ;
    Reset (F) ;
    Size := FileSize (F) ;
    WriteLn ( ‘Размер файла : ‘ , Size , ‘ байт.’ ) ;
    WriteLn ( ‘Установка указателя позиции на середину файла. ‘ ) ;
    Seek (F , Size div 2 ) ;
    WriteLn ( ‘Текущая позиция : ‘ , FilePos (F)) ;
    Close (f) ;
    end .

    Seek — Процедура Delphi

    Delphi 4 для начинающих. Часть 3
    Разгребаясь с письмами читателей, я решил начать следующую статью с работы с файлами и строковыми переменными. На такой ход меня натолкнуло следующее письмо: «Я только начал программировать на Delphi. У меня к вам большая просьба: при создании различных программ часто приходится работать с файлами (создавать, удалять, копировать, переписывать файлы), не могли бы вы в ближайшем номере «Компьютерной газеты» мне рассказать о методах работы с файлами». Что ж, желание просящих выполняю.

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

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

    Основные принципы и структура файловой системы мало изменились еще со времен MS-DOS. Если не принимать во внимание способы защиты файлов и организацию их хранения на уровне кластеров, то все остается без изменений вот уже скоро двадцать лет. Новые варианты файловых систем (FAT32, NTFS) не изменяют главного – понятия файла и способов обращения к нему. Поэтому современный программный код Delphi, например, для чтения данных из файла, удивительно похож на аналогичный, написанный, к примеру, на Turbo Pascal 4.0.

    При организации операций файлового ввода/вывода в приложении большое значение имеет тип используемых данных. В основном это строки, но встречаются числовые данные или структурированная информация, например, записи или массивы данных. О типе хранимых в файле данных необходимо знать заранее. Для работы с файлами необходимо для начала описать тип файла. Для этого используются специальные файловые переменные. Они делятся на нетипизированные и типизированные. В Delphi имеется одна нетипизированная переменная. Для ее обозначения используется ключевое слово file :

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

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

    v ar ByteFile: file of byte;

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


    type Country = record

    var CountryFile: file of Country;

    Для работы с текстовыми файлами используется специальная файловая переменная TextFile или Text :

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

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

    1. Объявить файловую переменную;
    2. При помощи функции AssignFile связать эту переменную с требуемым файлом;
    3. Открыть файл при помощи функций Append, Reset или Rewrite;
    4. Выполнить операции чтения и/или записи. При этом, в зависимости от сложности задачи и структуры данных, может использоваться целый ряд вспомогательных функций.
    5. Закрыть файл при помощи функции CloseFile .

    В качестве примера рассмотрим небольшой фрагмент исходного кода программы:

    If OpenDlg1.Execute then AssignFile(F, OpenDlg1.fileName) else exit;

    While Not EOF(F) do

    Если в диалоге OpenDlg1 был выбран файл, то его имя связывается с файловой переменной F при помощи процедуры AssignFile . В качестве имени файла всегда рекомендуется передавать полное имя файла (включая путь к нему). Как раз в таком виде возвращают результат выбора файла диалоги работы с файлами, описанные в материале прошлого номера. Затем при помощи процедуры Reset этот файл открывается для чтения и записи. В цикле осуществляется чтение из файла текстовых строк и запись их в компонент TMemo . Процедура Readln осуществляет чтение текущей строки файла и переходит на следующую строку. Цикл выполняется до тех пор, пока функция EOF не сообщит о достижении конца. По завершении цикла файл закрывается.

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


    procedure Reset(var F [: File; RecSize: Word ] );
    — открывает существующий файл для чтений и записи, текущая позиция устанавливается на первой строке файла;


    procedure Append(var F: Text);
    — открывает файл для записи информации после его последней строки, текущая позиция устанавливается на конец файла;

    procedure Rewrite(var F: File [; Recsize: Word ] ); — создает новый файл и открывает его, текущая позиция устанавливается в начало файла. Если файл с таким именем уже существует, то он перезаписывается.

    Чтение данных из файла выполняют процедуры Read и Readln.


    procedure Read( [ var F: Text; ] V1 [, V2. Vn ] ); —
    для текстовых файлов;


    Procedure Read(F, V1 [, V2,…,Vn ] );
    — для типизированных файлов.

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

    procedure Readln([ var F: Text; ] V1 [, V2, . Vn ]); — считывает одну строку текстового файла и устанавливает текущую позицию на следующую строку. Если использовать процедуру без переменных, то она просто передвигает текущую позицию на следующую строку.

    Процедуры для записи данных в файл Write и Writeln работают аналогично.

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


    procedure Seek(var F; N: Longint); —
    обеспечивает смещение текущей позиции на N элементов. Размер одного элемента в байтах зависит от типа данных файла (от типизированной переменной).

    Рассмотрим теперь режим блочного чтения и записи в файл. Он обеспечивает ввод/вывод данных между файлом и областью адресного пространства (буфером). Этот режим отличается значительной скоростью, причем скорость пропорциональна размеру одного передаваемого блока – чем больше блок, тем больше скорость.

    Для реализации этого режима необходимо использовать только нетипизированные файловые переменные. Размер блока определяется в процедуре открытия файла ( Reset, Rewrite, Append ). Непосредственно для выполнения операций используются процедуры BlockRead и BlockWrite . Процедура


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

    выполняет запись блока из памяти в файл. При этом параметр F содержит нетипизированную файловую переменную, связанную с нужным файлом, параметр Buf определяет переменную (число, строку, массив, структуру), в которую читаются байты из файла, параметр Count определяет число считываемых блоков, а параметр AmtTransferred возвращает число реально считанных блоков.

    В блочном режиме чтения/записи размер блока необходимо выбирать таким образом, чтобы он был кратен размеру одному значению типа, который хранится в файле. Например, если в файле хранятся значения типа Double (8 байт), то размер блока может быть равен 8, 16, 24, 32 и т.д. Фрагмент исходного кода блочного чтения из такого файла выглядит так:

    DoubleArray: array [0..255] of Double;

    If OpenDlg.Execute then AssignFile(F, OpenDlg.FileName) else Exit;

    BlockRead(F, DoubleArray, 32, Transfered);

    ShowMessage(‘Считано ‘+IntToStr(Transfered)+’ блок(-а,-ов)’);

    Как видно из примера, размер блока установлен в процедуре Reset и кратен размеру элемента массива DoubleArray, в который считываются данные. В переменную Transfered возвращается число считанных блоков. Если размер файла меньше заданного в процедуре BlockRead числа блоков, ошибка не возникает, а в переменную Transfered передается число реально считанных блоков.

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


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

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

    Эта запись обеспечивает хранение характеристик файла при удачном поиске. Дата и время создания файла хранятся в формате MS-DOS, поэтому для получения этих параметров в формате TDateTime необходимо использовать функцию


    function FileDateToDateTime(FileDate: Integer): TDateTime;

    Обратное преобразование выполняет функция


    function DateTimeToFileDate(DateTime: TDateTime): Integer;

    Свойство Attr может содержать комбинацию следующих значений:

    faReadOnly – только для чтения;

    Для определения параметров файла используется оператор AND :


    If SearchRec.Attr AND faReadOnly) > 0 then ShowMessage(‘
    файл только для чтения’);

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

    function FindFirst(const Path: string; Attr: Integer; var F: TSearchRec): Integer;

    находит первый файл, заданный полным маршрутом Path и параметрами Attr . Если заданный файл найден, функция возвращает 0, иначе – код ошибки. Параметры найденного файла возвращаются в записи F типа TSearchRec .

    function FindNext(var F: TSearchRec): Integer;

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

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

    procedure FindClose(var F: TSearchRec);

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

    FindFirst(DirEdit.Text, faArchive + faHidden, SearchRec);

    While FindNext(SearchRec) = 0 do

    Напоследок, как и обещал в прошлом номере, немного о создании дополнительных форм. Итак, для создания дополнительной формы необходимо воспользоваться пунктом меню New Form ( File/New Form ) или кнопкой New Form на панели инструментов. Перед Вами появится новая форма проекта с названием Form2 . При запуске программы эта форма не будет активизирована. Для ее запуска необходимо воспользоваться процедурой Show из кода исходной программы. Список существующих форм и возможность активизация форм проекта доступны при нажатии на кнопку ViewForm на панели инструментов или при нажатии на пункт Forms… ( View/Forms… ). Таким образом, необязательно загромождать экран бесконечными формами проекта, когда на данный момент из них необходима только одна. Достаточно закрыть ненужные формы, а при необходимости активизировать их при помощи диалога ViewForm .

    Процедуры Delphi

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

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

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

    Общий вид процедуры Delphi:

    Общий вид процедуры Delphi выглядит следующим образом:

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

    Если процедура обладает какими-либо параметрами, то программист их указывает в скобках, сразу после имени процедуры. В конце заголовка процедуры ставится символ «;». В случае, когда в процедуре имеются именованные константы, программист объявляет их в разделеconst.

    Далее за этим разделом идет раздел type, использующийся для объявления типов. После располагается раздел var, содержащий все переменные, которые впоследствии программист использует в своей программе. Затем следует раздел инструкций , которые включает в себя исполняемые инструкции данной процедуры.

    Примеры процедуры Delphi:

    Приведем пример процедуры Delphi, вычисляющей стоимость некоторой покупки:

    Илон Маск рекомендует:  Что такое код ncurses_mvaddchstr
    Понравилась статья? Поделиться с друзьями:
    Кодинг, CSS и SQL