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


Содержание

CloseFile()

Вставлять лишние проверки в стандартные функции — это как раз НЕ НОРМАЛЬНО.

Тогда объясните мне дураку как можно избежать лишнего CloseFile

Если бы мне нужен был аскетизм , тогда я бы на асемблере писал бы

Тогда объясните мне дураку как можно избежать лишнего CloseFile

Тогда объясните мне дураку как можно избежать лишнего CloseFile

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

Содержание:


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


Тип TSearchRec.


Тип TWin32FindData.


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

Ниже приведена таблица, содержащая стандартные процедуры и функции Delphi7 для работы с файлами и их краткое описание. Функции в таблице расположены в алфавитном порядке.

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

procedure AssignFile(var F; FileName: string);

F — имя файловой переменной
FileName — имя файла

Изменяет текущую директорию.

procedure ChDir(const S: string); overload;
procedure ChDir(P: PChar); overload;

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

procedure CloseFile(var F);

Создает новую директорию.

function CreateDir(const Dir: string): Boolean;

Удаляет файл с диска.

function DeleteFile(const FileName: string): Boolean;

Определяет существует ли указанная директория.

function DirectoryExists(const Directory: string): Boolean;

Возвращает число свободных байт на указанном диске.

function DiskFree(Drive: Byte): Int64;

Drive — номер диска, где 0 = текущий диск, 1 = A, 2 = B . и т.д.

Возвращает размер указанного диска в байтах.

function DiskSize(Drive: Byte): Int64;

Drive — номер диска, где 0 = текущий диск, 1 = A, 2 = B . и т.д.

Возвращает время последней модификации файла (timestamp) в формате операционной системы. Полученное значение может быть преобразовано в формат TDateTime при помощи функции FileDateToDateTime.

function FileAge(const FileName: string): Integer;

Закрывает указанный файл.

procedure FileClose(Handle: Integer);

Создает новый файл.

function FileCreate(const FileName: string): Integer; overload;
function FileCreate(const FileName: string; Rights: Integer): Integer; overload;

Если возвращаемое значение больше 0 функция выполнена успешно и его значение соответствует handle открытого файла. -1 — произошла ошибка открытия файла.
Параметр Rights используется только для Linux. В Windows он игнорируется.

Преобразует значение времени файла (timestamp) из формата операционной системы в TDateTime.

function FileDateToDateTime(FileDate: Integer): TDateTime;

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

function FileExists(const FileName: string): Boolean;

Возвращает файловые аттрибуты заданного файла как строку бит. Возвращаемое значение полностью соответствует полю Attr в TSearchRec..

function FileGetAttr(const FileName: string): Integer;

Возвращает время последней модификации файла (timestamp) в формате операционной системы. Используйте её для файла, заданного его handle.

function FileGetDate(Handle: Integer): Integer;

Возвращает TRUE если файл можно открыть только для чтения.

function FileIsReadOnly(const FileName: string): Boolean;

Открывает указанный файл в режиме, заданном при помощи одной из констант File open mode.

function FileOpen(const FileName: string; Mode: LongWord): Integer;

Если возвращаемое значение больше 0 функция выполнена успешно и его значение соответствует handle открытого файла. -1 — произошла ошибка открытия файла.

Читает указанное число байт из файла в буфер. Перед этим файл должен быть открыт при помощи функции FileOpen или FileCreate.

function FileRead(Handle: Integer; var Buffer; Count: Integer): Integer;

Handle — хендл файла, возвращаемый функциями FileCreate или FileOpen
Buffer — буфер
Count — размер буфера

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

Ищет файл в указанных папках.

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

Name — короткое имя файла
DirList — список директорий для поиска. Для Windows директории для поиска в списке отделяются друг от друга точкой с запятой, а в Linux — двоеточием.

Возвращаемое значение — полное имя файла. Если файл не найден — пустая строка.

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

function FileSeek(Handle, Offset, Origin: Integer): Integer; overload;
function FileSeek(Handle: Integer; const Offset: Int64; Origin: Integer): Int64;overload;

Handle — хендл файла, возвращаемый функциями FileCreate или FileOpen
Offset — указывает число байт смещения от Origin, куда будет перепозизиционирована точка.
Origin — определяет три варианта позиционирования:
0 — смещение задается относительно начала файла;
1 — смещение задается относительно текущей позиции;
2 — смещение задается относительно конца файла.

Если FileSeek выполнена успешно, она возвращает новую позицию точки чтения/записи; иначе она возвращает -1.

Устанавливает файловые аттрибуты заданного файла.

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

Значение Attr формируется комбинацией соответствующих констант файловых аттрибутов как показано ниже:

Возвращаемое значение — 0; иначе — код ошибки.

Примечание: Значения констант файловых аттрибутов приведены в описании TSearchRec.

Примечание: FileSetAttr доступна только для Windows.

Устанавливаетвремя последней модификации файла (timestamp) в формате операционной системы.

Для Windows:
function FileSetDate(Handle: Integer; Age: Integer): Integer; overload;

Cross-platform:
function FileSetDate(const FileName: string; Age: Integer): Integer; overload;

Handle — handle файла для изменения (Этот синтаксис доступен только для Windows.);
FileName — имя файла;
Age — устанавливаемое время модификации в формате операционной системы. Используйте функцию DateTimeToFileDate для перевод времени из формата TDateTime в формат операционной системы.

Возвращаемое значение — 0; иначе — код ошибки.

Разрешает использовать файл только для чтения.

function FileSetReadOnly(const FileName: string; ReadOnly: Boolean): Boolean;

Записывает содержимое буфера в текущую позицию в файле.

function FileWrite(Handle: Integer; const Buffer; Count: Integer): Integer;

Handle — хендл файла, возвращаемый функциями FileCreate или FileOpen
Buffer — буфер
Count — число байт, передаваемых файлу из буфера

Возвращаемое значение — действительное количество записаных байт или -1 в случае ошибки.

Освобождает память, выделенную под функцию FindFirst и прерывает последовательность FindFirst/FindNext.

Ищет первый файл с заданными аттрибутами в указанной папке.

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

Path — имя папки и маска имен файлов для поиска, включая wildcard characters. (Например, ‘.\test\*.*’ задает все файлы в текущей папке.)
Attr — аттрибут, указывающий включать в поиск специальные файлы в дополнение к нормальным. Аттрибуты можно комбинировать, складывая значения. Например, (faReadOnly + faHidden).
F — возвращаемый параметр. Результат работы функции.

Возвращаемое значение — 0, если файл найден; иначе — код ошибки.

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

F — возвращаемый параметр. Результат работы функции.

Возвращаемое значение — 0, если файл найден; иначе — код ошибки.

Создает новую папку, включая родительские папки, если они до этого не существовали, и родительские папки.

function ForceDirectories(Dir: string): Boolean;

Возвращает полное имя рабочей папки.

function GetCurrentDir: string;

Возвращает имя рабочей папки.

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

D — номер диска, где 0 = текущий диск, 1 = A, 2 = B, 3 = C . и т.д.
S — возвращаемый параметр. Результат работы процедуры.

Удаляет существующую пустую папку.

function RemoveDir(const Dir: string): Boolean;

Изменяет имя файла.

function RenameFile(const OldName, NewName: string): Boolean

Назначает рабочую папку.

function SetCurrentDir(const Dir: string): Boolean;

Тип TSearchRec

TSearchRec содержит информацию о файле, найденом при помощи функции FindFirst или FindNext.

Тип TSearchRec определяет информацию о файле, найденую путем вызова функции FindFirst или FindNext. Если файл найлен, поля параметров TSearchRec изменяются в соответствии с найденным файлом.

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

В Windows константы аттрибутов полностью соответствуют файловым аттрибутам DOS.

Наименование Тип
модуль
Описание
AssignFile процедура
System
ChDir процедура
System
CloseFile процедура
System
CreateDir функция
SysUtils
DeleteFile функция
SysUtils
DirectoryExists функция
SysUtils
DiskFree функция
SysUtils
DiskSize функция
SysUtils
File mode константы
System
Используются для открытия и закрытия дисковых файлов.

const fmClosed = $D7B0; // closed file
const fmInput = $D7B1; // reset file (TTextRec)
const fmOutput = $D7B2; // rewritten file (TTextRec)
const fmInOut = $D7B3; // reset or rewritten file (TFileRec)
const fmCRLF = $8; // DOS-style EoL and EoF markers (TTextRec)
const fmMask = $D7B3; // mask out fmCRLF flag (TTextRec)

Эти константы используются в первую очередь в Delphi коде, где поле Mode в TFileRec и TTextRec содержит одно из этих значений.

File open mode константы
SysUtils
Константы режима открытия файла используются для контроля режима доступа к файлу или потоку.

Для Windows:

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

TFileStream конструктор имеет параметр Mode в котрый вы можете прописать одну из следующих констант:

Константа Описание
fmCreate Если файл уже существует, то он открывается для записи, иначе создается новый файл. В отличие от других констант, которые декларируются в модуле SysUtil, эта константа декларируется в модуле classes.
fmOpenRead Открываетдоступ только для чтения.
fmOpenWrite Открываетдоступ только для записи.
fmOpenReadWrite Открывает доступ для чтения и записи.
fmShareCompat Compatible with the way FCBs are opened. Не используйте этот режим в кросс-платформенных приложениях.
fmShareExclusive Доступ к чтению и записи запрещен.
fmShareDenyWrite Доступ для записи запрещен.
fmShareDenyRead Доступ для чтения запрещен. Не используйте этот режим в кросс-платформенных приложениях.
fmShareDenyNone Открывает полный доступ для других.
FileAccessRights пременная Points to the command-line arguments specified when the application is invoked. Только для Linux. В Windows эта переменная игнорируется.
FileAge функция
SysUtils
FileClose процедура
SysUtils
FileCreate функция
SysUtils
FileDateToDateTime функция
SysUtils
FileExists функция
SysUtils
FileGetAttr функция
SysUtils
FileGetDate функция
SysUtils
FileIsReadOnly функция
SysUtils
FileOpen функция
SysUtils
FileRead функция
SysUtils
FileSearch функция
SysUtils
FileSeek функция
SysUtils
FileSetAttr функция
SysUtils
FileSetDate функция
SysUtils
FileSetReadOnly функция
SysUtils
FileWrite функция
SysUtils
FindClose процедура
SysUtils
FindFirst функция
SysUtils
FindNext функция
SysUtils
ForceDirectories функция
SysUtils
GetCurrentDir функция
SysUtils
GetDir процедура
System
RemoveDir функция
SysUtils
RenameFile функция
SysUtils
SetCurrentDir функция
SysUtils
Константа Значение Описание
faReadOnly 1 Файлы Только для чтения
faHidden 2 Скрытые файлы
faSysFile 4 Системные файлы
faVolumeID 8 Файлы идентификации тома
faDirectory 16 Файлы папок
aArchive 32 Архивные файлы
faSymLink 64 Символьная ссылка
faAnyFile 71 Любые файлы

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

Чтобы проверить файл на определенный аттрибут, комбинируйте значение поля Attr с соотвтствующей константой при помощи оператора AND. Если файл имеет этот аттрибут, результат будет больше 0. Например, если найденый файл является скрытым, то следующие выражения дадут значение TRUE:

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

Size содержит размер файла в байтах.

Name содержит короткое имя файла с расширением.

FindHandle is an internal handle used to track find state.

FindData (только для Windows) содержит дополнительную информацию, такую как время создания файла, время последнего доступа, а так же длинное и короткое имя файла. См. тип TWin32FindData.

Функции, процедуры файлового ввода и вывода в Дельфи

Append (Var F:Text) – открывает текстовый файл, связанный с переменной F, для добавления его в конец.

CloseFile (var F) – закрывает открытый файл, связанный с файловой переменной F.

AssignFile (var F; FileName: String) – связывает имя внешнего файла FileName с файловой переменной F.

BlockRead / BlockWrite (var F:file; var Buf; Count: Integer[; var Result: Integer]) – читает Count записей из нетипизированного файла, связанного с файловой переменной F, в буфер Buf. Если задан параметр Result, то в него возврашается число действительно прочитанных записей. BlockWrite соответственно записывает Count записей из буфера Buf в файл.

Eof (var F):Boolean – возвращает true при достижении конца файла.

Eoln (var F:Text):Boolean – возвращает признак конца строки в текстовом файле.

Eraise (var F) – удаление внешнего файла связанного с переменной F.

FileSize (var F):Integer – текущий размер файла, связанного с переменной F. Не используется для текстовых файлов.

Flush (var F:Text) – очистка буфера выходного текстового файла, связанного с переменной F.

FilePos (var F): LongInt – возвращает текущую позицию типизированного или нетипизированного файла, связанного с переменной F. Позиция, соответствующая началу файла – 0.

IOResult: Integer – возвращает целое значение, характеризующее выполнение последней операции ввода или вывода.

Read (F, ) – читает из файла, связанного с переменной F, одно или более значений в одну или более переменную.

ReadLn (F, ) – читает одно или более значений в одну или более переменную и переходит на начало новой строки текстового файла.

Rename (var F: File; NewName:String) – переименование файла. В NewName указан полный путь к переименованному файлу.

Reset (var F[: File; RecSize: Word]) – открывает существующий файл связанный с файловой переменной F. RecSize задается для нетипизированных файлов и устанавливает длину записи в байтах (по умолчанию 128).

ReWrite (var F[: File; RecSize: Word]) – создает и открывает новый файл, связанный с файловой переменной F. RecSize задается для нетипизированных файлов и устанавливает длину записи в байтах (по умолчанию 128).

Seek (var F; N:LongInt) – перемещает текущую позицию в позицию N. Для текстовых файлов не используется.

SeekEof (var F:Text): Boolean – возвращает true при достижении конца текстового файла, связанного с файловой переменной F.

SeekEoln (var F:Text): Boolean – возвращает true при достижении конца строки текстового файла, связанного с файловой переменной F.

Truncate (var F) – усекает типизированный или нетипизированный файл, связанный файловой переменной F, на текущей позиции.

Write (var F; ) – записывает значение(я) в файл, связанный файловой переменной F.

WriteLn (var F; ) – записывает значение(я) в файл, затем заносит маркер конца строки в текстовый файл, связанный файловой переменной F.

. Программирование файлового ввода Delphi функции файлового ввод вывод в с++

Форма (close)

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

вот моя процедура:

но программа, при нажатии, ничего не делает! Что надо изменить в процедуре? Заранее спс!

Добавлено через 19 минут
Сделал так:

12.07.2010, 12:27

Обработка Close
Здравствуйте. при попытки закрытия у меня всплывает окно (да\нет\отмена) procedure.

Операторы Exit, Close, Application.Terminate в чем разница между ними?
Хочу понять разницу между этими операторами. Например, оператор Application.Terminate выходит из.

Сделать, чтобы при нажатии на форме 2 на кнопку, форма 2 закрывалась, а форма 1 открывалась
Здравствуйте подскажите как обнулить форму. Вобщем у меня форма form1 как бы с тестом там есть.

Delphi три кнопки с надписями «Влево», «Вправо», «Центр»; радиогруппа с аналогичными функциями и кнопка Close
На форме располагаются: редактор Memo; линейка ScrollBar с разметкой от 10 до 20; метка с названием.

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

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

О подпрограммах в Object Pascal

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

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

Использование подпрограммы состоит из 2 этапов: сначала подпрограмму описывают, а затем, уже в блоке инструкций программы, вызывают. Отметим, что в библиотеке Delphi имеется описание тысяч готовых подпрограмм, описывать которые, разумеется, уже не надо. А их вызовом мы уже неоднократно занимались — достаточно взглянуть на любой пример, где мы встречали инструкции, подобные таким:

write(‘Hello, world!’); readln;

Здесь и write, и readln — стандартные подпрограммы Object Pascal. Таким образом, с вызовом подпрограмм мы уже знакомы. Осталось узнать, как создавать собственные, или пользовательские, подпрограммы. Но прежде отметим, что все подпрограммы делятся на 2 лагеря: процедуры и функции. Мы уже использовали эти термины, и даже давали им описание, однако повторимся: процедуры — это такие подпрограммы, которые выполняют предназначенное действие и возвращают выполнение в точку вызова. Функции в целом аналогичны процедурам, за тем исключением, что они еще и возвращают результат своего выполнения. Результатом работы функции могут быть данные любого типа, включая объекты.

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

Как процедурам, так и функциям могут передаваться данные для обработки. Делается это при помощи списка параметров. Список параметров в описании подпрограммы и список аргументов, указываемых при ее вызове должен совпадать. Иначе говоря, если в описании определено 2 параметра типа Integer, то, вызывая такую подпрограмму, в качестве аргументов так же следует указать именно 2 аргумента и именно типа Integer или совместимого (скажем, Word или Int64).

ПРИМЕЧАНИЕ
На самом деле, Object Pascal позволяет довольно гибко обращаться с аргументами, для чего имеются различные методы, включая «перегружаемые» функции, значения параметров по умолчанию и т.д. Тем не менее, в типичном случае, количество, тип, и порядок перечисления аргументов при объявлении и при вызове процедуры или функции, должны совпадать.

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

Процедуры

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

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

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

ПРИМЕЧАНИЕ
Вопросы локальных и глобальных переменных, и вообще видимости в программах, будет рассмотрен позже в этой главе.

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

procedure TriplePrint(str: string); var i: integer; begin for i := 1 to 3 do begin writeln(‘»‘+str+'»‘); end; // конец цикла for end; // конец процедуры TriplePrint

Здесь мы определили процедуру TriplePrint, которая будет трижды выводить переданную ей в качестве аргумента строку, заключенную в двойные кавычки. Как видно, данная процедура имеет все составные части: ключевое слово procedure, имя, список параметров (в данном случае он всего один — строковая переменная str), блок объявления собственных переменных (целочисленная переменная i), и собственное тело, состоящее из оператора цикла for.

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

Отметим так же, что рассмотренная нами процедура сама содержит вызов другой процедуры — writeln. Процедуры могут быть встроенными. Иначе говоря, объявление одной процедуры можно помещать в заголовочную часть другой. Например, наша процедура TriplePrint может иметь вспомогательную процедуру, которая будет «подготавливать» строку к выводу. Для этого перед объявлением переменной i, разместим объявление еще одной процедуры. Назовем ее PrepareStr:

procedure PrepareStr; begin str := ‘»‘+str+'»‘; end;

Отметим, что переменная str, хотя и не передается этой процедуре в качестве параметра, тем не менее может в ней использоваться, поскольку данная процедура является составной частью процедуры TriplePrint, внутри которой данная переменная доступна для использования.

Таким образом, мы получаем две процедуры, одна из которых (TriplePrint) может использоваться во всей программе, а другая (PrepareStr) — только внутри процедуры TriplePrint. Чтобы преимущество использования процедур было очевидно, рассмотрим их на примере программы, которая будет использовать ее неоднократно, для чего обратимся к листингу 6.1 (см. так же пример в Demo\Part1\Procs).

Листинг 6.1. Использование процедур

program procs; <$APPTYPE CONSOLE>procedure TriplePrint(str: string); procedure PrepareStr; begin str := ‘»‘+str+'»‘; end; var i: integer; begin PrepareStr; for i := 1 to 3 do begin writeln(str); end; end; // конец процедуры TriplePrint begin // начало тела основной программы TriplePrint(‘Hello. ‘); // первый вызов TriplePrint TriplePrint(‘How are you. ‘); // 2-й вызов TriplePrint(‘Bye. ‘); // 3-й readln; end.

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

Функции

Подобно процедурам, описание функции состоит из заголовка и тела. Однако описание заголовка имеет 2 отличия: прежде всего, для функций используется ключевое слово function. Кроме того, поскольку функции всегда возвращают результат, завершается строка заголовка типом возвращаемого значения. Таким образом, для объявления функции мы получаем следующий синтаксис:

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

Рассмотрим пример функции, которая будет возвращать куб числа, переданного ей в качестве аргумента:

function cube(value: integer) : integer; result := value * value * value; >

Здесь определена функция, имеющая параметр value типа целого числа, которое она возводит в третью степень путем троекратного умножения, и результат присваивается специальной переменной result. Таким образом, чтобы в любом месте программы вычислить значение числа в 3-й степени, достаточно написать такое выражение:

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

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

procedure TriplePrint(str: string); function PrepareStr(s: string) : string; begin result := ‘»‘+s+'»‘; end; var i: integer; begin for i := 1 to 3 do begin writeln(PrepareStr(str)); // функция использована как переменная end; end;

Как уже отмечалось, помимо специальной переменной result, в функциях можно использовать другую автоматически объявляемую переменную, имя которой соответствует имени функции. Так, для функции cube имя переменной также будет cube:

function cube(value: integer) : integer; cube := value * value * value; >

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

Рекурсия

Таким образом мы подошли к теме рекурсии — вызову подпрограммы из самой себя. Это не является ошибкой, более того, целый ряд алгоритмов решить без рекурсии вообще было бы затруднительно.

Рассмотрим вопрос рекурсии на следующем примере:

function recfunc(x: integer) : integer begin dec(x); // функция декремента, уменьшает целое на 1 if x > 5 then x := recfunc(x); result := 0; // возвращаемое значение тут не используется end;

Здесь мы объявили функцию recfunc, принимающую один аргумент, и вызывающую саму себя до тех пор, пока значение этого аргумента больше 5. Хотя на первый взгляд может показаться, что такое поведение функции похоже на обычный цикл, на самом деле все работает несколько по-иному: если вы вызовите ее со значением 8, то она выдаст вам 3 сообщения в следующей последовательности: 5, 6, 7. Иначе говоря, функция вызывала саму себя до тех пор, пока значение x было больше 5, и собственно вывод сообщений начала 3-я по уровню получившейся вложенности функция, которая и вывела первое сообщение (в данном случае им стало 5, т.е. уменьшенное на единицу 6).

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

Листинг 6.2. Рекурсия с комментариями

program recurse; <$APPTYPE CONSOLE>function recfunc(x, depth: integer) : integer; begin dec(x); if x > 5 then begin write(‘Current recursion depth is: ‘); write(depth); write(‘, current x value is: ‘); writeln(x); inc(depth); depth:=recfunc(x, depth); end else writeln(‘End of recursive calls. ‘); write(‘Current recursion depth is: ‘); write(depth); write(‘, current x value is: ‘); writeln(x); dec(depth); result := depth; end; begin recfunc(8,0); readln; end.

Исходный код находится в Demo\Part1\Recurse, там же находится и исполняемый файл recurse.exe, результат работы которого вы можете увидеть на своем экране.

Использование параметров

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

procedure Circle (square: real; var radius, length: real);

Данная процедура принимает «на обработку» одно значение — площадь (square), а возвращает через свои параметры два — радиус (radius) и длину окружности (length). Практическая ее реализация может выглядеть таким образом:

procedure Circle (square: real; var radius, length: real); begin radius := sqrt(square / pi); // функция pi возвращает значение числа ? length := pi * radius * 2; end;

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

var r,l: real; . Circle(100,r,l);

После вызова функции Circle, переменные r и l получат значения радиуса и длины окружности. Остается их вывести при помощи writeln. Исходный код программы приведен в листинге 6.3.

Листинг 6.3. Процедура с параметрами

program params; <$APPTYPE CONSOLE>procedure Circle (square: real; var radius, length: real); begin //функция sqrt извлекает корень, а функция pi возвращает значение числа ? radius := sqrt(square / pi); length := pi * radius * 2; end; var r,l: real; begin Circle(100,r,l); writeln(r); writeln(l); readln; end.

Запустив такую программу, можно убедиться, что она работает и выводит верные результаты, однако вид у них получается довольно-таки неудобочитаемый, например, длина окружности будет представлена как «3,54490770181103E+0001». Чтобы сделать вывод более удобным для восприятия, нам понадобится функция FloatToStrF. С ее помощью мы можем определить вывод числа на свое усмотрение, например:

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

writeln(‘Radius is: ‘+FloatToStrF(r,ffFixed,12,8)); writeln(‘Length is: ‘+FloatToStrF(l,ffFixed,12,8));

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

. var s,r,l: real; begin write(‘Input square: ‘); readln(s); Circle(s,r,l); writeln(‘Radius is: ‘+FloatToStrF(r,ffFixed,12,8)); writeln(‘Length is: ‘+FloatToStrF(l,ffFixed,12,8)); readln; end.

В принципе, это уже лучше, однако не помешало бы добавить обработку возможных ошибок ввода. Скажем, площадь должна быть больше 0. Проверку на то, является ли значение s больше нуля, можно производить непосредственно в основном коде программы, но в целях создания более универсального кода, вынесем ее в подпрограмму. Для этого первой инструкцией процедуры Circle должна быть проверка значения площади:

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

function Circle(square: real; var radius, length: real) : boolean; begin result := false; if (square

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

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

if Circle(s,r,l) then begin // вывод end else // сообщить об ошибке

Результатом проделанной работы будет программа, приведенная в листинге 6.4. Она же находится в Demo\Part1\Params.

Листинг 6.4. Функция с параметрами

program params; <$APPTYPE CONSOLE>uses sysutils; //этот модуль соджержит функцию FloatToStrF function Circle(square: real; var radius, length: real) : boolean; begin result := false; if (square

Итак, при помощи ключевого слова var в списке параметров подпрограммы мы можем добиться использования передаваемых аргументов в том блоке, где был произведен вызов данной подпрограммы. В несколько другом аспекте используется ключевое слово const. Фактически, оно объявляет локальную константу, т.е. значение, которое нельзя изменять внутри данной процедуры или функции. Это бывает полезным в том случае, когда такое изменение недопустимо по логике программы и служит гарантией того, что такое значение не будет изменено.

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

function MyBetterFunc(val1: integer; const val2: integer = 2); begin result := val1*val2; end;

Обращение же к такой функции может иметь 2 варианта: с указанием только одного аргумента (для параметра val1), или же с указанием обоих:

x := MyBetterFunc(5); // получим 10 x := MyBetterFunc(5,4); // получим 20

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

Области видимости

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

program Project1; procedure Proc1; var a: integer; begin a := 5; //верно. Локальная переменная a здесь видна end; begin a := 10; //Ошибка! Объявленная в процедуре Proc1 переменнаая здесь не видна end.

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

program Project2; var a: integer; // глобальная переменная a procedure Proc1; begin a := 5; // верно b := 10; // Ошибка! Переменая b на этот момент еще не объявлена end; var b: integer; // глобальная переменная b begin a := 10; // верно b := 5; // тоже верно. Здесь видны все г var a: integer; // глобальная переменная end.

Теперь рассмотрим такой вариант, когда у нас имеются 2 переменных с одним и тем же именем. Разумеется, компилятор еще на стадии проверки синтаксиса не допустит, чтобы в программе были объявлены одноименные переменные в рамках одного диапазона видимости (скажем, 2 глобальных переменных X, или 2 локальных переменных X в одной и той же подпрограмме). Речь в данном случае идет о том, что произойдет, если в одной и той же программе будет 2 переменных X, одна — глобальная, а другая — локальная (в какой-либо подпрограмме). Если с основным блоком программы все ясно — в нем будет присутствовать только глобальная X, то как быть с подпрограммой? В таком случае в действие вступает правило близости, т.е. какая переменная ближе (по структуре) к данному модулю, та и есть верная. Применительно к подпрограмме ближней оказывается локальная переменная X, и именно она будет задействована внутри подпрограммы.

program Project3; var X: integer; procedure Proc1; var X: integer; begin X := 5; // Здесь значение будет присвоено локальной переменной X end; begin X := 10; // Здесь же значение будет присвоено голобальной переменной X end.

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

program Project1; procedure Proc1; procedure SubProc; begin end; begin SubProc; // Верно. Вложенная процедура здесь видна. end; begin Proc1; // Верно. Процедура Proc1 объявлена в зоне глобальной видимости SubProc; // Ошибка! Процедура SubProc недоступна за пределами Proc1. end.

Наконец в том случае, когда имена встроенной и некой глобальной процедуры совпадают, то, по аналогии с переменными, в области видимости встроенной процедуры, именно она и будет выполнена.

Видимость в модулях

Все то, что мы уже рассмотрели, касалось программ, умещающихся в одном единственном файле. На практике же, особенно к тому моменту, когда мы перейдем к визуальному программированию, программы будут включать в себя множество файлов. В любом случае, программа на Object Pascal будет иметь уже изученный нами файл проекта — dpr, или основной модуль программы. Все прочие файлы будут располагаться в других файлах, или модулях (units), с типичным для Pascal расширением pas. При объединении модулей в единую программу возникает вопрос видимости переменных, а так же процедур и функций в различных модулях.

Для начала вернемся к рассмотрению структуры модуля, которая имеет ряд отличий от структуры программы. Итак, в простейшем случае, модуль состоит из названия, определяемого при помощи ключевого слова unit, и 2 секций — interface и implementation. Так вот как раз первая секция, interface, и служит для определения (декларации) типов данных, переменных, функций и процедур данного модуля, которые должны быть доступны за пределами данного модуля.

Чтобы лучше в этом разобраться, создадим программу, состоящую из 2 модулей — основного (dpr) и дополнительного (pas). Для этого сначала создайте новый проект типа Console Application, а затем добавьте к нему модуль, для чего из подменю File ‘ New выберите пункт Unit. После этого сохраните проект, щелкнув по кнопке Save All (или File ‘ Save All). Обратите внимание, что первым будет предложено сохранить не файл проекта, а как раз файл дополнительного модуля. Назовем его extunit.pas, а сам проект — miltiunits (см. Demo\Part1\Visibility). При этом вы увидите, что в части uses файла проекта произошло изменение: кроме постоянно добавляемого модуля SysUtils, появился еще один модуль — extunit, т.е. код стал таким:

uses SysUtils, extunit in ‘extunit.pas’;

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

uses SysUtils, extunit;

Тем не менее, оставим код как есть, и приступим к разработке модуля extunit. В нем, в части implementation, напишем 2 процедуры — ExtProc1 и ExtProc2. Обе они будут делать одно и то же — выводить строку со своим названием. Например, для первой:

Теперь вернемся к главному модулю программы и попробуем обратиться к процедуре ExtProc1:

. begin ExtProc1; end.

Попытка компиляции или запуска такой программы приведет к ошибке компилятора «Undeclared identifier», что означает «неизвестный идентификатор». И действительно, одного лишь описания процедуры недостаточно, чтобы она была доступна вне своего модуля. Так что перейдем к редактированию extunit и в секции interface напишем строку:

Такая строка, помещенная в секцию interface, является объявлением процедуры ExtProc1, и делает ее видимой вне данного модуля. Отметим, что в секции interface допускается лишь объявлять процедуры, но не определять их (т.е. тело процедуры здесь будет неуместно). Еще одним полезным эффектом от объявления процедур является то, что таким образом можно обойти такое ограничение, как необходимость определения подпрограммы до ее вызова. Иначе говоря, поскольку в нашем файле уже есть 2 процедуры, ExtProc1и ExtProc2, причем они описаны именно в таком порядке — сначала ExtProc, а потом ExtProc2, то выведя объявление ExtProc2 в interface, мы сможем обращаться к ExtProc2 из ExtProc1, как это показано в листинге 6.5:

Листинг 6.5. Объявление процедур в модуле

unit extunit; interface procedure ExtProc1; procedure ExtProc2; implementation procedure ExtProc1; begin writeln(‘ExtProc1’); ExtProc2; // Если объявления не будет, то компилятор выдаст ошибку end; procedure ExtProc2; begin writeln(‘ExtProc2’); end; end.

Отметим, что теперь процедуры ExtProc2, так же, как и ExtProc1, будет видна не только по всему модулю extunit, но и во всех использующей этот модуль программе multiunits.

Разумеется, все, что было сказано о процедурах, верно и для функций. Кроме того, константы и переменные, объявленные в секции interface, так же будут видны как во всем теле модуля, так и вне него. Остается лишь рассмотреть вопрос пересечения имен, т.е. когда имя переменной (константы, процедуры, функции) в текущем модуле совпадает с таковым в подключенном модуле. В этом случае вновь вступает в силу правило «кто ближе, тот и прав», т.е. будет использоваться переменная из данного модуля. Например, если в extunit мы объявим типизированную константу Z, равную 100, а в multiunits — одноименную константу, равную 200, то обратившись к Z из модуля extunit, мы получим значение 100, а из multiunits — 200.

Если же нам в multiunits непременно понадобится именно та Z, которая находится в модуле extunit, то мы все-таки можем к ней обратиться, для чего нам пригодится точечная нотация. При этом в качестве имени объекта указывают название модуля:

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

Некоторые стандартные функции

В Object Pascal, как уже отмечалось, имеются огромное количество стандартных процедур и функций, являющихся составной частью языка, и с некоторыми мы уже знакомы (например, приведенные в табл. 5.1 и 5.2 функции преобразования). Детальное описание всех имеющихся в Object Pascal процедур и функций можно получить в справочной системе Delphi, однако мы все-таки рассмотрим здесь некоторые из них, чтобы составить общее представление — см. таблицу 6.1.

Таблица 6.1. Некоторые стандартные процедуры и функции Delphi

Синтаксис Группа Модуль Описание
function Abs(X); арифметические System Возвращает абсолютное значение числа
procedure ChDir(const S: string); управления файлами System Изменяет текущий каталог
function Concat(s1 [, s2. sn]: string): string; строковые System Объединяет 2 и более строк в 1
function Copy(S; Index, Count: Integer): string; строковые System Возвращает часть строки
function Cos(X: Extended): Extended; тригонометрические System Вычисляет косинус угла
procedure Delete(var S: string; Index, Count: Integer); строковые System Удаляет часть строки
function Eof(var F): Boolean; ввод-вывод System Проверяет, достигнут ли конец файла
procedure Halt [ ( Exitcode: Integer) ]; управления System Инициирует досрочное прекращение программы
function High(X); диапазона System Возвращает максимальное значение из диапазона
procedure Insert(Source: string; var S: string; Index: Integer); строковые System Вставляет одну строку в другую
function Length(S): Integer; строковые System Возвращает длину строки или количество элементов массива
function Ln(X: Real): Real; арифметические System Возвращает натуральный логарифм числа (Ln(e) = 1)
function Low(X); диапазона System Возвращает минимальное значение из диапазона
procedure New(var P: Pointer); размещения памяти System Создает новую динамическую переменную и назначает указатель для нее
function ParamCount: Integer; командной строки System Возвращает количество параметров командной строки
function ParamStr(Index: Integer): string; командной строки System Возвращает указанный параметр из командной строки
function Pos(Substr: string; S: string): Integer; строковые System Ищет вхождение указанной подстроки в строку и возвращает порядковый номер первого совпавшего символа
procedure RmDir(const S: string); ввод-вывод System Удаляет указанный подкаталог (должен быть пустым)
function Slice(var A: array; Count: Integer): array; разные System Возвращает часть массива
function UpCase(Ch: Char): Char; символьные System Преобразует символ в верхний регистр
function LowerCase(const S: string): string; строковые SysUtils Преобразует ASCII-строку в нижний регистр
procedure Beep; разные SysUtils Инициирует системный сигнал
function CreateDir(const Dir: string): Boolean; управления файлами SysUtils Создает новый подкаталог
function CurrentYear: Word; даты и времени SysUtils Возвращает текущий год
function DeleteFile(const FileName: string): Boolean; управления файлами SysUtils Удаляет файл с диска
function ExtractFileExt(const FileName: string): string; имен файлов SysUtils Возвращает расширение файла
function FileExists(const FileName: string): Boolean; управления файлами SysUtils Проверяет файл на наличие
function IntToHex(Value: Integer; Digits: Integer): string; форматирования чисел SysUtils Возвращает целое в шестнадцатеричном представлении
function StrPCopy(Dest: PChar; const Source: string): PChar; строковые SysUtils Копирует Pascal-строку в C-строку (PChar)
function Trim(const S: string): string; строковые SysUtils Удаляет начальные и конечные пробелы в строке
function TryStrToInt(const S: string; out Value: Integer): Boolean; преобразования типов SysUtils Преобразует строку в целое
function ArcCos(const X: Extended): Extended; тригонометрические Math Вычисляет арккосинус угла
function Log2(const X: Extended): Extended; арифметические Math Возвращает логарифм по основанию 2
function Max(A,B: Integer): Integer; арифметические Math Возвращает большее из 2 чисел
function Min(A,B: Integer): Integer; арифметические Math Возвращает меньшее из 2 чисел

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

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

Функции в действии

В целом мы уже ознакомились с несколькими десятками предопределенных процедур и функций, а так же умеем создавать собственные. Пора применить полученные знания на практике, для чего вновь вернемся к программе, рассмотренной в главе, посвященной операторам — игре «Угадай-ка». В ней, по сути, был реализован только один из рассмотренных в самом начале книги алгоритмов — угадывания числа. Что касается алгоритма управления, то на тот момент мы оставили его без внимания.

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

  1. Реализовать-таки возможность повторного прохождения игры без перезапуска программы;
  2. Добавить немного «геймплея». Иначе говоря, введем уровни сложности и подсчет очков. Новые уровни можно реализовать как повторное прохождение игры с увеличением сложности (скажем, за счет расширения диапазона загадываемых значений);
  3. В продолжение п. 2 добавить еще и таблицу рекордов, которая будет сохраняться на диске.

Поскольку часть работы уже выполнена, то для того, чтобы приступить к разработке новой версии игры (назовем ее «Угадай-ка 2.0»), мы не будем как обычно создавать новый консольный проект в Delphi, а откроем уже существующий (Ugadaika) и сохраним его под новым именем, скажем, Ugadaika2, и в новом каталоге. Таким образом, мы уже имеем часть исходного кода, отвечающую за угадывание, в частности, цикл while (см. листинг 4.5). Этот фрагмент логичнее всего выделить в отдельную процедуру, вернее даже функцию, которая будет возвращать число попыток, сделанное пользователем. Для этого создадим функцию, которая будет принимать в качестве аргумента число, которое следует угадать, а возвращаемым значением будет целое, соответствующее числу попыток. Ее объявление будет таким:

function GetAttempts(a: integer):integer;

Данная функция так же должна иметь в своем распоряжении переменную, необходимую для ввода пользователем своего варианта ответа. Еще одна переменная нужна для подсчета результата, т.е. количества попыток. В качестве первой можно было бы использовать глобальную переменную (b), однако во избежание накладок, для локального использования в функции следует использовать локальную же переменную. Что касается переменной-счетчика, то для нее как нельзя лучше подходит автоматическая переменная result. Еще одним изменением будет использование цикла repeat вместо while. Это вызвано тем, что с одной стороны, тем, что хотя бы 1 раз пользователь должен ввести число, т.е. условие можно проверять в конце цикла, а с другой мы можем избавиться от присвоения лишнего действия, а именно — присвоения заведомо ложного значения переменной b. Ну и еще одно дополнение — это второе условие выхода, а именно — ограничение на число попыток, которое мы установим при помощи константы MAXATTEMPTS:

const MAXATTEMPTS = 10;

В результате код функции получится таким, как представлено в листинге 6.6.

Листинг 6.6. Функция GetAttempts

function GetAttempts(a: integer):integer; var b: integer; begin Result:=0; repeat inc(Result); // увеличиваем счетчик числа попыток write(#13+#10+’?:’); read(b); if (b>a) then begin write(‘Too much!’); continue; end; if (b

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

var level, score, attempt: integer; f: TextFile; s: string;

Теперь инициализируем счетчик псевдослучайных чисел (т.е. оставим randomize на месте) и инициализируем нулем значения счета и уровня:

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

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

repeat writeln(‘Level ‘+IntToStr(level)+’:’); writeln(‘From 0 to ‘+IntToStr(level*100)); attempt:=GetAttempts(random(level*100+1)); score:=score+(MAXATTEMPTS-attempt)*level; writeln(#10+’You current score is: ‘+IntToStr(score)); inc(level); until attempt>MAXATTEMPTS;

После завершения работы цикла, т.е. когда пользователь хоть раз истратит на отгадывание все 10 попыток, следует сообщить итоговый результат и сравнит его с предыдущим значением, которое следует считать из файла. Файл мы назовем records.txt, и сопоставим с переменной f:

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

if not FileExists(‘record.txt’) then begin Rewrite(f); writeln(f,’0′); // первая строка содержит число-рекорд writeln(f,’None’); // а вторая — имя последнего победителя CloseFile(f); end;

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

Reset(f); readln(f, attempt); readln(f,s); writeln(#10+’BEST SCORE: ‘+IntToStr(attempt)+’ by ‘+s); CloseFile(f);

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

Вот, собственно, и все. Полный код получившейся программы можно увидеть на листинге 6.7, или же в файле проекта в каталоге Demo\Part1\Ugadaika2.

Листинг 6.7. Программа угадай-ка, окончательный вариант

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

Работа с файлами в Delphi: классика Pascal. Работа с типизированными файлами в Delphi

Удивительно, но факт — запрос «delphi файлы» в Яндексе — это один из самых популярных запросов, касающихся Delphi. Популярнее только «delphi скачать» — видимо ещё не все слышали про такую штуку как Delphi Community Edition. Раз есть в Сети запрос — должен быть и ответ. Посмотрим, что получится в итоге.

Содержание статьи

Классика работы с файлами в Delphi — ключевое слово File

Этот способ, без преувеличения, можно назвать древнейшим способом работы с файлами в Pascal/Delphi. Однако и он до сих пор используется в работе, особенно, если это, например, лабораторная работа по информатике в школе или ВУЗе.

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

Для типизированного фала мы можем задать тип данных фиксированного размера (ShortString, String[20], Integer, Single и так далее), например, мы можем определить такие типизированные файлы:

Или, как в примере выше использовать для указания типа запись (record), в которой все поля имеют фиксированный размер. Для типизированного файла нельзя указывать типы данных, размер которых не фиксирован, например, вот такие определения файловых переменных недопустимы:

Более того, даже компилятор Delphi укажет вам на ошибку, сообщив следующее:

Определив файловую переменную можно приступать к работе с файлом. Алгоритм работы при этом будет следующим:

  1. Ассоциировать файловую переменную с файлом на диске
  2. Открыть файл
  3. Записать/Прочитать файл
  4. Закрыть файл

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

Работа с типизированными файлами в Delphi

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

Пример №1. Запись данных в типизированный файл Delphi

Запишем в наш файл две записи:

Рассмотрим методы, используемые в этом примере:

Связывает файловую переменную F с внешним файлом FileName. В качестве второго параметра может задаваться как абсолютный путь к файлу, например, ‘C:/MyFile.txt‘, так и относительный, например, в коде выше файл будет создан рядом с exe-файлом.

Создает новый файл и открывает его. Если внешний файл с таким именем уже существует, он удаляется и на его месте создается новый пустой файл. Если F уже открыт, он сначала закрывается, а затем воссоздается. Текущая позиция файла устанавливается в начале пустого файла.
F — это переменная, связанная с внешним файлом с использованием AssignFile. RecSize — это необязательное выражение, которое можно указывать, только если F является нетипизированным файлом (об этом ниже).

Используется для записи в типизированный файл. F — файловая переменная, P1..PN — это переменная того же типа, что и тип файла F.

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

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

Для того, чтобы в delphi не перезаписывать каждый раз файл, а добавлять в конец файла новые записи необходимо открывать типизированные файлы методом Reset. Рассмотрим пример добавления новых записей в типизированные файлы Delphi.

Пример №2. Добавление записей в типизированный файл Delphi

Рассмотрим такой пример Delphi:

Разберемся с тем, что здесь делается. Во-первых, условие:

проверяет, существует ли файл на диске. Метод FileExist имеет следующее описание:

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

Таким образом, в нашем примере, если файла нет на диске то он создается пустым. Далее выполняется цикл:

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

Здесь, в принципе, весь алгоритм расписан в комментариях к процедуре.
Метод Reset не воссоздает файл снова, как Rewrite, а открывает его для чтения/записи (в случае двоичных файлов). Что касается метода Seek, то он имеет следующее описание:

F — файловая переменная, ассоциированная с файлом на диске, N — номер записи в файле (первый номер — 0). Чтобы переместиться сразу в конец файла, мы сделали такой вызов:

где FileSize — это метод Delphi имеющий следующее описание:

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

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

Здесь мы, опять же, открываем файл методом Reset и в цикле while..do проходим по всем записям файла, пока не дойдем до конца. Здесь мы использовали два новых метода Delphi:

Eof возвращает True, если текущая позиция файла находится за последним символом файла или файл пуст. В противном случае Eof возвращает False.
По аналогии с методом Write, метод Read производит чтение очередной записи из файла и имеет следующее описание:

Результат работы нашего примера может быть следующим:

Ещё одним полезным методом для работы с типизированными файлами может быть процедура Truncate:

Удаляет все записи после текущей позиции файла. Вызовите Truncate в коде Delphi, чтобы текущая позиция файла стала концом файла (Eof (F) вернет true).
Рассмотрим пример использования этой процедуры.

Пример №3. Удаление последних записей типизированного файла в Delphi

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

В этом примере мы делаем следующее:

  1. Открываем файл существующий AssignFile/Reset
  2. Определяем количество записей в файле (Count:=FileSize(TypedFile))
  3. Если количество записей меньше двух, то спрашиваем у пользователя стереть ли все записи и, в случае положительного ответа, вызываем метод Tuncate
  4. Если количество записей в файле больше двух, то смещаемся на нужную нам позицию в файле (Seek(TypedFile, Count-2)) и затираем две последние записи методом Truncate.

Подведем итог

Для работы с типизированными файлами в Delphi в самом общем случае нам необходимо выполнить следующую последовательность операций:

  1. Определить тип записей в файле — это могут быть стандартные типы данных Delphi с фиксированным размером: ShortString, integer, single и так далее или собственные типы данных, например, записи (record), но, в этом случае, главное условие — размер записи должен быть фиксированным.
  2. В коде Delphi/Pascal определить файловую переменную, используя ключевое слово fileи, указав тип записей файла, определенный в пункте 1.
  3. Ассоциировать файловую переменную с внешним файлом на диске, используя метод AssignFile.
  4. Открыть файл для чтения/записи, используя методы Rewrite/Reset.
  5. Чтобы сделать в файл очередную запись используем метод Write, для чтения очередной записи из файла — используем метод Read.
  6. Закрыть файл методом CloseFile.

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

Функции и процедуры для работы с типизированными файлами в Delphi/Pascal

Блог GunSmoker-а

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

17 октября 2011 г.

Сериализация — файлы в стиле Pascal

Оглавление

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

В языке Pascal (и, следовательно, Delphi) есть встроенные средства по работе с файлами, не зависящие от нижележащей операционной системы.

В отличие от файловых API уровня операционной системы, файлы Pascal ориентированы на работу с типами данных языка (а не нетипизированным байтовым потоком, как это имеет место для API операционной системы, которая понятия не имеет про типы данных языка Pascal) и включают в себя высокоуровневую оболочку для работы со строками и наборами однородных данных. Поэтому работа с файлами Pascal удобнее, чем обращение к API операционной системы.

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

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

Сразу же замечу, что я не буду рассказывать для совсем уж начинающих — тут будет подробное изложение. Предполагается, что вы уже прочитали какую-то «книжку по Delphi».

Всего в языке есть три файловых типа:

  • текстовые файлы ( var F: TextFile; )
  • типизированные файлы ( var F: file of Тип-Данных; )
  • нетипизированные файлы ( var F: file; )

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

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

Под текстовым файлом понимают файлы, содержащие читабельный для человека текст в кодировках ANSI, UTF-8 или Unicode. Текст представлен в явном виде, при этом некоторые специальные символы (с кодами от 0 до 31) являются специальными — они всегда имеют один и тот же смысл и обычно отвечают за форматирование текста: перенос строк, табуляцию и т.п. Обычно текстовыми файлами не называют документы — файлы, содержащие текст, но оформленные в формате определённой программы (вроде Word), потому что кроме самих данных (текста) такие файлы содержат служебную мета-информацию (заголовки, данные форматирования, атрибуты и т.п.).

В Pascal и Delphi в настоящий момент поддерживаются только текстовые файлы в кодировке ANSI. Файловые типы Pascal считаются устаревшим средством — появившись в языке давно, они уже не развиваются. В те далёкие дни люди мало волновались о кодировках, а о Unicode и UTF-8 и слыхом не слыхивали. Поэтому вы не сможете работать с текстовыми файлами, отличными от канонического ANSI-формата (кодовая страница ANSI, без BOM).

Примечание: начиная с Delphi XE2 (год выхода 2011), файлы Pascal впервые за пару десятков лет получили обновление API: теперь становится возможным указывать кодировку файла, которая может быть отличной от системной кодовой страницы, но вы всё ещё не можете использовать BOM.

Как я уже сказал, «текстовый файл» — это просто удобный ярлык, навешиваемый на файл, чтобы как-то охарактеризовать его содержимое. Это не однозначная и неизменная характеристика. Вы можете открыть текстовый файл в двоичном режиме и вы можете открыть произвольный бинарный файл как текстовый (весь вопрос в осмысленности результатов от таких действий; если первое ещё имеет смысл, то открытие двоичного файла как текстового — весьма сомнительная операция).

Типизированные файлы содержат записи одного типа и фиксированной длины. Чаще всего компонентами файла выступают именно записи ( record ), но это может быть и любой другой тип фиксированного размера. К примеру, текстовый файл можно открыть как типизированный — file of AnsiChar .

Наконец, под нетипизированными файлами понимают файлы произвольной, нерегулярной структуры. Это означает открытие файла в двоичном режиме — как набор байт, без какой-либо предопределённой структуры. Именно в этом режиме работают файловые API операционной системы. В подобном режиме можно открыть любой файл и это всегда будет иметь смысл.

Следует заметить, что понятие «типизированный файл» может трактоваться в двух смыслах. Во-первых, про файл можно говорить, что он типизированный в смысле строгого определения типизированного файла Pascal. Т.е. это файл, определяемый как » file of что-то «, состоящий из набора блоков одинаковой длины. Иными словами, это файл-массив однотипных элементов. Но на практике «типизированный файл» может употребляться и в более широком смысле — как файл с однородными данными. При этом файл не обязан состоять исключительно из последовательности записей, и сами записи файла не обязаны иметь одинаковый размер. К примеру, в начале файла может быть записан заголовок (сигнатура, контрольная сумма, число записей, версия файла и т.п.), а за ним идти набор записей одинакового вида (скажем, три числа и строка), но записи будут иметь переменную длину (строка разной длины). Такой файл могут называть типизированным (в смысле однородности его данных), но надо понимать, что он не будет типизированным в смысле языка Pascal — и работать с ним нужно будет как с нетипизированным, двоичным файлом. Применение термина «типизированный файл» к файлам нерегулярной структуры не корректно. Примером такого файла является .exe файл: в нём содержится первичный заголовок, который ссылается на дополнительные заголовки, в нём есть секции кода и данных (произвольных размеров), оглавление ресурсов (и сами ресурсы) и т.п. Все части файла имеют разную длину и разную структуру.

Общие принципы работы с файлами Pascal

Работа с любыми типами файлов имеет общие элементы, которые мы сейчас и рассмотрим.

Во-первых, работа с файлами в стиле Pascal почти всегда следует одному шаблону (кратко):

  1. AssignFile
  2. Reset/Rewrite/Append
  3. работа с файлом
  4. CloseFile

Подробно:

  1. Вы начинаете с объявления файловой переменной нужного типа. В зависимости от выбранного вами вида файла, вам нужно использовать var F: TextFile; var F: file of Тип-Данных; или var F: file; (текстовый, типизированный и двоичный файл соответственно), где «Тип-Данных» является типом данных фиксированного размера.

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

    Параметр CodePage является необязательным и он появился только начиная с Delphi XE 2. Он применим только для текстовых файлов. Если он не указан, то используется DefaultSystemCodePage , что для «русской Windows» равно Windows-1251.

    Он указывает кодовую страницу для выполнения перекодировки текста перед записью и после чтения данных из файла. Например: Текстовый файл должен быть в указанной вами кодовой странице. Кодовая страница — любая из принимаемых функцией WideCharToMultiByte (или её аналога на не Windows платформах). Файл не может иметь BOM. Так что на практике эта возможность полезна только для открытия ANSI файлов в кодировке, отличной от системной, но не текстовых файлов в UTF-8 и UTF-16, которые почти всегда имеют BOM.

    Данная операция НЕ открывает файл. Она просто записывает имя файла в файловую переменную. Так что когда вы откроете файл в будущем (как правило — сразу же после этой операции), то будет открыт именно файл с указанным тут именем.

    Данная операция всегда выполняется успешно, но вам нужно быть внимательным и никогда не вызывать её для уже открытых файлов — это приведёт к утечке открытых описателей файла (фактически, AssignFile просто тупо затирает нулями переменную перед выполнением своей работы; так что, чтобы там у вас ни было — оно будет потеряно). Собственно, эта операция нужна не столько для ассоциации с файловым путём, сколько для инициализации переменной. Как правило, за всё время жизни файловой переменной, эта операция применяется к ней единственный раз — первым действием.

    В Pascal вместо AssignFile используется Assign . Это ровно эта же процедура, просто в Delphi Assign переименовали в AssignFile , чтобы избежать конфликтов имён с другим кодом, который тоже использует имя Assign (к примеру — форма, компоненты, да и любой TPersistent ). Assign всё ещё существует в Delphi, так что любой старый код может быть перекомпилирован в Delphi и он будет работать. Но для нового кода вам лучше использовать AssignFile .

    Далее файл открывается. Открывать файл можно в трёх режимах: чтение, запись и чтение-запись. Как несложно сообразить, при открытии файла в режиме чтения из него можно только читать, но не писать — и так далее. Таким образом, всего у файловой переменной может быть 4 режима: закрыт, открыт на чтение, открыт на запись и открыт на чтение-запись (любые другие значения указывают на отсутствие инициализации файловой переменной — т.е. закрытый файл): Открытие файла выполняется с помощью функций Reset , Rewrite и Append .

    Что касается общих моментов: Rewrite создаёт новый файл; Reset открывает существующий файл, а Append является модификацией Reset и открывает файл для дополнения: т.е. после открытия файла переходит в его конец для дозаписи данных. Append применима только к текстовым файлам.

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

    1. Rewrite создаёт новый файл. Если файл с таким именем уже существует, то он удаляется и вместо него создаётся новый файл. Если файловая переменная уже открыта к моменту вызова Rewrite , то файл закрывается и пересоздаётся. После открытия файл позиционируется на начало файла, а функция EOF возвращает True — указывая на конец файла.
    2. Reset открывает существующий файл в режиме, указываемом в глобальной переменной FileMode (по умолчанию — чтение-запись; возможные значения — только чтение, только запись и чтение-запись). Если файл не существует или не может быть открыть в нужном режиме (заблокирован, отказ доступа) — то возникнет ошибка. Если файловая переменная уже открыта к моменту вызова Reset , то файл закрывается и открывается заново. После открытия файл позиционируется на начало файла, а функция EOF возвращает True , если файл существует и имеет размер 0 байт или (обычно) False — если файл существует и имеет ненулевой размер.
    3. Append применимо только к текстовым файлам и будет рассмотрена ниже.

    Глобальная переменная FileMode является именно глобальной переменной, а потому установка доступа не является потоко-безопасным действием. В любом случае, переменная может содержать комбинацию (через «or») следующих флагов: Вы можете указать один флаг вида fmOpenXYZ и один флаг из второй группы. Первый флаг определяет режим открытия: только чтение ( fmOpenRead ), только запись ( fmOpenWrite ) или чтение-запись ( fmOpenReadWrite ), вторые флаги определяют режим разделения файла: без разделения ( fmShareExclusive ), запретить другим читать ( fmShareDenyRead ), запретить другим писать ( fmShareDenyWrite ) и без ограничений ( fmShareDenyNone ). И два флага являются специальными. Флаги разделения используют устаревшую deny-семантику MS-DOS, в отличие от современного API. См. также: взаимодействие флагов режима открытия и разделения.

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

    Затем идёт работа с файлом — чтение и/или запись данных. Эти операции специфичны для разных типов файловых переменных. Их мы отдельно рассмотрим ниже.

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

    Примечание: аналогично AssignFile (см. выше), CloseFile является переименованной Close , которая тоже всё ещё доступна, но чаще всего замещается другим кодом (к примеру — Close у формы). Всегда используйте CloseFile в новом коде.

Итак, на этом мы закончили разбор общих принципов работы с файлами Pascal. Но прежде чем перейти к обсуждению способов работы с каждым конкретным типом файловых переменных, нужно обсудить ещё один важный общий момент: обработку ошибок.

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

Тут надо сказать, что обработка ошибок работы с файлами Pascal весьма запутывающа. Дело в том, что обработку ошибок подпрограммы ввода-вывода файлов Pascal производят в двух режимах — которые переключаются весьма нестандартно: опцией компилятора. Она называется I/O Checking и расположена на вкладке Compiling (Compiler в старых версиях Delphi) в опциях проекта в Delphi (Project/Options). Что ещё хуже — эта настройка может быть изменена прямо в коде, используя директиву компилятора. В коде можно использовать <$I+>для включения этой опции и <$I->для выключения.

Итак, если эта опция выключена (либо в коде стоит <$I->), то обработку ошибок любых функций по работе с файлами в стиле Pascal (кроме AssignFile , которая всегда успешна) нужно проводить так: вам доступна функция IOResult , которая возвращает статус последней завершившейся операции. Если она вернула 0 — то операция была успешно выполнена (файл открыт, данные записаны и т.п.). Если же она возвращает что-то иное — произошла ошибка. Какая именно ошибка — зависит от значения, которое она вернула, которое (значение) представляет собой код ошибки. Возможные коды ошибок можно посмотреть здесь (только не закладывайтесь на неизменность этих кодов и неизменность таблицы). Помимо ошибок ввода-вывода, вы можете получать и системные коды ошибок. Их список можно увидеть в модуле Windows. Откройте его и запустите поиск по «ERROR_SUCCESS» (без кавычек). А ниже вы увидите список системных кодов ошибок. Наиболее частые ошибки при работе с файлами — ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_ACCESS_DENIED, ERROR_INVALID_DRIVE, ERROR_SHARING_VIOLATION. Но вообще код может быть почти любым. В любом случае, вам доступен только код ошибки, но не её текстовое описание. Если произошла ошибка, то все последующие вызовы функций ввода-вывода будут игнорироваться, пока вы не вызовете IOResult .

Итак, это старый режим обработки ошибок, который пришёл в Delphi из языка Pascal. С ним код выглядит примерно так: Вызов функции очищает код ошибки, так что если вы хотите обработать ошибку, нужно поступать так: Поскольку ошибка блокирует вызовы функций ввода-вывода, то обработку ошибок удобно делать один раз — в конце работы. Например: Этот код основан на том факте, что если при работе с файлом возникнет ошибка (к примеру — при открытии файла в момент вызова Rewrite из-за того, что в Edit1 указано недопустимое имя файла), то все нижеследующие функции не будут ничего делать, а код ошибки будет сохранён до вызова IOResult .

Обратите внимание, что правило «ничего не делать при ошибке» не распространяется на CloseFile . Вот почему мы вставили обработку ошибок в самый конец, а не до CloseFile .

Окей, с этим способом всё. Теперь, если вы включаете опцию I/O Checking или используете <$I+>, то функция IOResult вам не доступна, а всю обработку ошибок берут на себя сами функции ввода-вывода: если при вызове любой из них возникнет ошибка — функция сама обработает её путём возбуждения исключения (если модуль SysUtils не подключен — то возбуждением run-time ошибки; подробнее — тут). Исключение имеет класс EInOutError : Собственно, код в этом режиме всегда будет выглядеть так: А при возникновении проблем — у вас будет исключение, которое в стандартной VCL Forms программе в конечном итоге обрабатывается показом сообщения:

Я допускаю, что вы можете не очень быть знакомы с работой с исключениями (и в таком случае вам можно начать с этой статьи), но я бы всё же рекомендовал использовать режим <$I+>по одной очень простой причине: с исключениями поведение по умолчанию — показ ошибки; в режиме <$I->— скрытие и игнорирование. Иными словами, если в <$I->по незнанию или из-за лени вы опустите обработку ошибок, а при выполнении программы ошибка всё же возникнет — вы останетесь ни с чем: код просто не работает и вы понятия не имеете почему. С исключениями же такое невозможно: вам нужно специально прикладывать усилия, чтобы скрыть ошибку, так что это не может произойти случайно, по недосмотру.

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

Персональное примечание: напомню, что именно запутанность обработки ошибок в файлах Pascal привела к обнаружению вируса Virus.Win32.Induc.a.

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

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

Итак, текстовый файл — это обычный файл, открываемый через переменную типа TextFile , для которого делается предположение о чисто текстовом содержимом. Тип TextFile в Delphi имеет псевдоним Text — это текстовый тип в Pascal. И снова: тип был переименован, старый Text всё ещё существует по соображениям совместимости, но в новых программах нужно использовать тип TextFile .

Когда вы открываете текстовый файл, его содержимое интерпретируется специальным образом: предполагается, что файл содержит последовательности читабельных символов, организованных в строки, при этом каждая строка заканчивается маркером end-of-line («конец строки»). Иными словами, текстовый файл трактуется не как просто file of Char , а с дополнительной смысловой нагрузкой. Для текстовых файлов есть специальные формы Read и Write , на которые мы посмотрим чуть позже.

В Delphi есть три стандартные глобальные переменные типа текстовых файлов: Input для чтения или Output для вывода — это специальные имена для стандартных каналов ввода-вывода; ещё есть ErrOutput — стандартный канал вывода ошибок, по умолчанию он направляется в канал вывода, но вызывающая программа может отделить его. Соответственно, Input открывается только для чтения и обычно представляет собой клавиатуру в консольных приложениях (если ввод не был перенаправлен), а остальные два — только для записи (и обычно представляют собой экран). Эти файловые переменные открываются автоматически. В Windows они открываются только для консольных программ, но на других платформах это может быть и не так.

В любом случае, для открытия текстовых файлов можно использовать Rewrite (создание), Reset (открытие) и Append (дозапись в существующий файл). Все три используются одинаково — им передаётся переменная файлового типа. Больше аргументов у них нет. Особенность текстовых файлов: Rewrite открывает файл (вернее — создаёт) в режиме только запись, Reset всегда открывает файл в режиме только чтение, а Append открывает файл в режиме только запись. Текстовые файлы нельзя открыть в режиме чтение-запись.
Append аналогична Reset , только устанавливает текущую позицию файла в конец и открывает в режиме записи, а не чтения.

После открытия вам доступны процедуры Write , WriteLn , Read и ReadLn для записи и чтения строк (варианты функций с *Ln допустимы только для текстовых файлов). Эти подпрограммы не являются настоящими функциями и процедурами, а представляют собой магию компилятора. Первым параметром у них идёт файловая переменная — она указывает файл, с которым будет производится работа (чтение строк или запись), а далее идёт произвольное число параметров — что пишем или читаем.

Если опустить файловую переменную — будет подразумеваться консоль ( Input и Output ). Это используется для ввода-вывода в консольных программах. Вам необязательно работать со стандартными каналами ввода-вывода именно через файлы Pascal, вы можете использовать и API.

Иными словами, вызовы этих функций имеют форму: Где «что-то» — это одна или более строковых или числовых переменных, указанных через запятую — как обычные аргументы. Мы рассмотрим это чуть позже.

При этом, краткая форма: эквивалентна: В Windows Input , Output и ErrOutput доступны только для консольных программ. Попытка использовать их в GUI приложении приведёт к ошибке (операция над закрытой файловой переменной). Это — частая ошибка новичков. Они забывают указать первым параметром файловую переменную. Эта ошибка не страшна в Windows, поскольку вы тут же увидите проблему при запуске программы, но достаточно коварна на других платформах, где стандартные каналы ввода-вывода могут быть открыты для всех программ. Таким образом, если вы забудете указать файловую переменную, то ваша программа будет работать, не выдавая ошибки — но будет работать неправильно.

Функции с постфиксом Ln отличаются от своих собратьев тем, что используют разделитель строк. Иными словами, Write записывает данные в файл, а WriteLn дополнительно после этого вписывает в файл перенос строки. Т.е. несколько вызовов Write подряд будут писать данные в одну строку. Аналогично, Read читает данные из файла, а ReadLn после этого ищет конец строки и делает переход к следующей строке в файле. В качестве разделителя строк используется умолчание для платформы, если вы пишете в файл, или Enter, если вы работаете с консолью. К примеру, для Windows разделителем строк является последовательности из двух символов #13#10 — известные как CR (carriage return) и LF (line feed), они имеют коды 13 и 10, соответственно. По историческим причинам выбор разделителя строк зависит от платформы. Вы можете изменить умолчание вызовом SetLineBreakStyle , но замечу, что работа с файлами другой платформы — это достаточная головная боль. Я не буду это подробно рассматривать. Часто наилучшее решение — предварительная нормализация данных и файлов. В частности, в Delphi есть функция AdjustLineBreaks.

Read читает все символы из файла вплоть до конца строки или конца файла, но она не читает сами маркеры. Чтобы перейти на следующую строчку — используйте ReadLn . Если вы не вызовите ReadLn , то все вызовы Read после встречи маркера конца строки будут возвращать пустые строки (для чисел — нули). Опознать конец строки или конец файла можно с помощью функций EoLn и EoF (или функций SeekEoLn и SeekEoF — и эти функции не следует путать с функцией Seek ). Если же читаемая строка длиннее, чем аргумент у Read / ReadLn , то результат обрезается без возбуждения ошибки. Но если вы читаете числа, то Read пропускает все пробелы, табуляторы и переносы строк, пока не встретит число. Иными словами, при чтении чисел они должны отделяться друг от друга пробелами, табуляторами или размещаться на отдельных строках. При чтении строк вы должны переходить на следующую строку сами.

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

В общем случае параметр выглядит так:

Где в квадратных скобках указываются опциональные (необязательные) части. OutExpr — это само выражение для записи. Оно может быть переменной, константой или непосредственным значением. MinWidth и DecPlaces являются спецификаторами форматирования и должны быть числами. MinWidth указывает общую длину вывода и должно быть числом большим нуля. По необходимости слева добавляется нужное количество пробелов. А DecPlaces указывает число знаков после десятичной точки и применимо только при записи чисел. Если эти данные не указаны, то используется научный формат представления чисел. Форматирование значений при выводе — наследие Pascal, где не было функций форматирования строк. В современных программах предпочтительнее использовать функцию Format и её варианты. Этот современный вариант форматирования данных является стандартным решением в Delphi и предоставляет больше возможностей.

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

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

Далее необходимо заметить, что переменные текстового файлового типа имеют буфер. Все данные, записываемые в файл, на самом деле записываются в этот буфер. И лишь при закрытии файла или переполнения буфера данные сбрасываются на диск. Аналогичные действия производятся и при чтении файла. Это делается для оптимизации посимвольного ввода-вывода. Буфер можно сбросить и вручную в любой момент — с использованием подпрограммы Flush . По умолчанию размер буфера равен 128 байтам, но его можно изменить на произвольное значение вызовом SetTextBuf .

Текстовые файлы не поддерживают позиционирование.

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

Типизированный файл — это очень простая БД в виде «array of что-то». Как уже было сказано, «что-то» должно иметь фиксированный размер в байтах, поэтому строки и динамические массивы хранить нельзя (но можно — короткие строки или статические массивы символов). Для открытия файлов доступны Rewrite и Reset . Здесь нет никаких особенностей по сравнению с вышеуказанными общими принципами. Запись и чтение из файла осуществляется с помощью Write и Read .

В отличие от текстовых файлов, типизированные и нетипизированные файлы поддерживают позиционирование. Вы можете установить текущую позицию в файле с помощью Seek . Процедура принимает два параметра — файл и номер позиции, на которую нужно переместиться. Положение отсчитывается не в байтах, а в размере записи файла. Иными словами, если вы работаете с, к примеру, file of Integer , то Seek(F, 0) переместит вас в начало файла, Seek(F, 1) — ко второму элементу (т.е. через 4 байта от начала файла), Seek(F, 2) — к третьему (через 8 байт), а Seek(F, FileSize(F)) — в конец файла. Т.е. функция FileSize тоже возвращает размер файла не в байтах, а в записях. Этот размер совпадает с размером в байтах только для file of byte и аналогичных типов — с однобайтовыми записями. Текущую файловую позицию (снова в записях) всегда можно узнать вызовом FilePos .

Ещё одной особенностью типизированных (и нетипизированных) файлов является функция Truncate . Она удаляет содержимое файла за текущей позицией. После её вызова функция EoF возвращает True .

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

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

Для нетипизированных файлов нам доступны Rewrite и Reset — равно как и для типизированных файлов. Но тут есть одно важное отличие: для нетипизированных файлов эти процедуры принимают два параметра. Первый параметр, как обычно, файловая переменная. А второй параметр — размер блока. Размер блока измеряется в байтах и является аналогом размера записи у типизированных файлов. Размер блока влияет на все подпрограммы работы с нетипизированными файлами, которые принимают размеры или позицию. Все они подразумевают указание размеров/позиции в блоках, а не байтах. Вы можете указать 1, чтобы производить измерения в байтах. Плохая новость — второй параметр является опциональным, его можно не указывать. Проблема тут в том, что если вы его не укажете, то размер блока по умолчанию будет 128 байт — не самое очевидное поведение.

Далее, для чтения и записи в нетипизированный файл вместо Read и Write используются функции BlockRead и BlockWrite . Обе они используются одинаково: первый параметр — файловая переменная, второй параметр — что пишем/читаем, третий параметр — сколько пишем/читаем (в блоках). Функция возвращает сколько реально было прочитано/записано. Если блок прочитан/записан целиком, то результат равен третьему параметру. У обеих функций есть перегруженные варианты, у которых результат функции возвращается четвёртым параметром.

Замечу, что второй параметр — нетипизированный. Это значит, что компилятор не выполняет проверок типа. И вам лучше бы не напутать, что туда передавать. Я в первую очередь сейчас говорю про указатели и динамические типы. К примеру: Фух, достаточно сложно. Но только если вы не понимаете как работают указатели.

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

Прочие подпрограммы и особенности

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

Практика

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

Практика: текстовые файлы

  1. Одно значение: Double Обратите внимание, что запись чисел всегда использует фиксированный формат числа — вне зависимости от региональных установок. Иными словами, конвертация чисел в строки и обратно выполняется процедурами Str и Val . Это имеет как плюсы, так и минусы.

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

С другой стороны, текстовый файл, очевидно, предназначен для редактирования человеком. Иначе зачем делать его текстовым? А человек вправе ожидать, что числа будут использовать «правильный формат». К примеру, для России это — использование запятой в качестве разделителя целой и дробной частей, а не точки. Простого решения этой проблемы для текстовых файлов в стиле Pascal нет.

Одно значение переменного размера: String Ключевой момент при записи данных с динамическим размером — размещать каждое такое значение на отдельной строке. Тогда размер данных = размеру строки.

Набор однородных значений: array of Double Обратите внимание, что записываем мы все значения в одну строчку — поэтому нам нужно вставить разделитель (пробел). Кроме того, мы могли бы также писать каждое число на отдельной строке — используя WriteLn . Тогда пробел был бы уже не нужен.

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

Поэтому вместо этого я показал, как вы можете считать файл произвольной структуры — лишь бы в нём были бы числа. Для этого мы делаем цикл чтения, пока не будет встречен конец файла ( while not SeekEof(F) do ), а в самом цикле считываем и добавляем в массив каждое число. При этом мы пользуемся тем фактом, что Read при чтении числа будет пропускать все пробельные символы, включая переносы строк. Вот почему нам не нужно явно вызывать ReadLn .

Ещё один момент — использование SeekEoF вместо просто EoF . Если бы мы использовали EoF , то тогда в массив добавлялся бы ноль в конец, если в конце файла стоит несколько пробелов или пустых строк.

Набор однородных значений переменного размера: array of String Здесь всё оказывается ещё проще — динамические данные должны размещаться на отдельной строке, так что мы просто используем WriteLn / ReadLn . Обратите внимание, что в этом случае, поскольку мы записываем строки, то нет никакой возможности отличить пустую строку в конце файла — часть ли это данных или просто человек случайно добавил её. Если в ваших данных пустые строки недопустимы, то вы можете заменить EoF на SeekEoF , как это сделано в предыдущем примере.

Запись — набор неоднородных данных: Тут всё достаточно прозрачно — каждое поле на новой строке.

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

Массив из записей внутри записи — составные данные: С вложением самих записей проблем не возникает — я даже не буду писать пример, т.к. он эквивалентен предыдущему. Просто выпишите в ряд WriteLn полей TCompose . Для полей-записи вместо одного WriteLn вам нужно будет написать несколько — по одному на каждое поле вложенной записи. Ну а ReadLn будут зеркальным отражением WriteLn .

Но вот вложение массива записей уже является непреодолимым препятствием для общего случая. Дело в том, что в текстовом файле нет возможности как-то указать размер вложенных данных, ведь обычная техника записи динамических данных в текстовый файл — использование разделителей (чаще всего — переноса строк). В частных случаях вы можете найти решение. Скажем, отделять поле Related пустой строкой от следующего элемента/поля. Но в общем случае приемлемого решения нет — вам нужно использовать нетипизированный файл. В некоторых случаях вы можете предложить введение формата в текстовый файл. Вроде INI, XML, JSON и т.п. Но на это мы посмотрим в следующий раз.

Практика: типизированные файлы

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

Тут надо сделать примечание, почему вообще для этого примера выбран именно тип Double , а не Extended , который в Delphi является де-факто стандартом для чисел с плавающей запятой. Дело в том, что Extended зависит от платформы. Его размер может меняться. Так что если вы компилируете, скажем 32-битную и 64-битную программы — у них размер Extended будет разным. Что означает, что из одной программы вы не сможете прочитать данные, созданные в другой. Это не проблема, если вы планируете работу только в одной платформе — можете спокойно использовать Extended . В противном случае вам нужно использовать Double . Ну а если вам нужно прочитать Extended , созданный на другой платформе, то вы можете использовать тип TExtended80Rec (появился, начиная с Delphi XE2, где, собственно, и появилась поддержка нескольких платформ) вместо Extended .

Аналогично, по этой же причине вам следует избегать Integer и Cardinal при работе с типизированными файлами — потому что это generic-типы, размер которых может меняться. Используйте вместо них LongInt и LongWord соответственно.

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

К примеру, если брать строки, то вы можете использовать ShortString — это ограничит ваши данные ANSI и 255 символами. Ещё вариант — статический массив символов. Скажем array[0..4095] of Char ( AnsiChar / WideChar ). Обратите внимание, что запись в файл ShortString или массива из символов — это не аналог текстовых файлов, потому что кроме значимого текста в файле появляется т.н. padding — мусорные данные, не несущие смысловой нагрузки, а служащие для дополнения данных до нужного размера. Вы можете вызывать FillChar или ZeroMemory для предварительной очистки данных перед записью — чтобы визуально подчеркнуть неиспользуемость дополнения.

Надо понимать, что чем больше (по байтовому размеру) вы возьмёте тип, тем больше места у вас будет тратиться зря (на padding), если в основной массе у вас короткие строки. С другой стороны, если вы возьмёте недостаточно большой тип, то ваши данные будут обрезаться. Так что подобный вариант далеко не всегда возможен — а только, если вы можете предложить подходящее ограничения размера.

Здесь и далее я не буду приводить пример — он всегда эквивалентен предыдущему примеру с данными фиксированного размера. Только замените типы.

Набор однородных значений: array of Double Обратите внимание, что нам не нужен разделитель, поскольку элементы имеют фиксированный размер. Кроме того, нет проблемы с пробелами в конце, ранее решаемой SeekEoF . Кроме того, фиксированность элементов позволяет узнать длину массива заранее — по размеру файла, что в итоге позволяет написать более эффективный код.

Запись — набор неоднородных данных: Аналогично второму примеру, записать неоднородные данные в типизированный файл невозможно. Если вы объявите file of LongWord , то вы не сможете записать в него строку и наоборот. В общем, нужно использовать текстовые или нетипизированные файлы.

Вы можете подумать, что вы могли бы объявить file of TData — ну, с заменой динамических строк на фиксированные аналоги, конечно же. А затем использовать первый пример. Да, это будет работать для конкретного объявления TData , но только этот пример — не на запись в файл record -а, а на запись неоднородных данных.

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

Практика: нетипизированные файлы

  1. Одно значение: Double
    Обратите внимание на 1 у Rewrite / Reset . Конечно, мы могли бы использовать вместо неё SizeOf(Double) . Но это фактически означало бы, что мы используем нетипизированный файл как типизированный. А в чём тогда смысл примера?

Одно значение переменного размера: String Данный случай прост — размер данных определяется по размеру файла. Для примера я выбрал AnsiString , а не String по друм причинам — во-первых, String — это псевдоним либо на AnsiString , либо на UnicodeString , в зависимости от версии Delphi. Иными словами, тут получается ситуация, аналогичная ситуации с Extended в разделе примеров для типизированных файлов. Так что вам нужно использовать явные типы — 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 могут менять свои размеры в зависимости от окружения — поэтому мы используем другие типы, которые гарантировано всегда имеют один и тот же размер.

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

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

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

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

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

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

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

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

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

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

Преимущества и недостатки файлов в стиле Pascal

Плюсы:

  • Отлично подходят для начального изучения языка
  • Вы наверняка знаете, как с ними работать, ибо это первый способ работы с файлами, который все изучают
  • Удобство работы с (текстовыми) данными: форматирование и переменное число аргументов
  • Гибкий подход, позволяющий работать с текстовыми, типизированными и произвольными данными
  • Встроенная буферизация даёт прирост производительности при записи небольших кусочков из-за экономии на вызовах в режим ядра
  • Могут быть расширены на поддержку любых файловых устройств, а не только дисковых файлов (т.е. IPC, pipes, сетевых каналов и т.п.) — путём написания своих адаптеров ввода-вывода, называемых «Text File Device Drivers». Подробнее см. Text File Device Drivers в справке Delphi

Минусы:

  • Необходимость ручной сериализации данных
  • Неудобная (и неоднозначная) обработка ошибок
  • Поведение кода зависит от директив компилятора
  • Нет поддержки Unicode и кодировок (улучшено начиная с Delphi XE2)
  • Проблемы с обработкой больших файлов (более 2 Гб)
  • Проблемы с глобальными переменными
  • Проблемы с многопоточностью
  • В некоторых случаях требуется ручной сброс буфера
  • Недостаточная гибкость для некоторых задач
  • Нестандартный код

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

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

Режимы компиляции

18.04.2005
Иван Шихалев

Режимы компиляции, или иначе — режимы совместимости, в Free Pascal определяют, каким диалектом языка Pascal вы хотите вос­поль­зо­вать­ся. Режим определяет возможные конструкции языка, некоторые типы данных и системные модули подгружаемые по умол­ча­нию.

Режим компиляции выставляется при помощи ключа командной строки -S или непосредственно в модуле при помощи директивы <$MODE xxx>.

В этом же обзоре я намерен описать прочие ключи компилятора, определяющие расширения языка.

Режим по умолчанию. Соответственно в ключах командной строки своего символа не имеет. Что, впрочем, не есть хорошо, по­сколь­ку существует же еще и файл ppc386.cfg — его установки таким образом мы можем сменить на FPC только через директиву.

При присваивании значения процедурной переменной вы можете использовать как @

. Что са­мое интересное — это распространяется и на функции. Выражение @

однозначно трактуется как адрес переменной, а не процедуры, на которую она ссылается. Кстати, в справке по этому поводу ошибка, или я что-то не так понял: «You must use the address operator to assign procedural variables.»

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

Разрешена перегрузка (overloading) процедур и функций. Кстати, в отличие от Delphi, директиву overload использовать не­обя­за­тель­но.

Разрешены вложенные комментарии, таким образом, конструкция < comment < nested comment >> не вызовет ошибки ком­пи­ля­то­ра.

Не поддерживаются классы. Из-за того, что обработка исключений реализована a-la Delphi, не поддерживается оператор try .

Не подгружается модуль ObjPas . Что это за зверь, я слегка опишу, когда перейдем к <$MODE OBJFPC>, пока лишь замечу, что тип integer без ObjPas соответствует 2м байтам, а с ним — 4м.

Не поддерживается ключевое слово resourcestring .

До кучи ко всем огорчениям, не поддерживается псевдо(?)переменная result — результат функции присваивается ее имени.

Зато поддерживается директива cvar и внешние переменные.

Автоматически производится присваивание PChar -> string .

Поддерживается такая замечательная вещь, как перегрузка операторов.

Режим совместимости с Turbo Pascal фирмы Borland версии 7. Соответствует ключу командной строки -So .

Совместимость обеспечивается полная — единственное различие в том, что исполняемый файл 32-разрядный, других Free Pascal не создает.

Писать программы непосредственно в этом режиме, на мой взгляд — мазохизм. А вот портировать старые программы для TP легко — как правило, не требуется вносить каких-либо изменений в код.

В силу полной совместимости, воздержусь от подробного описания этого режима — чего-чего, а уж литературы по Turbo Pascal до­ста­точ­но, и в Сети, и в книжных магазинах.

По идее — режим совместимости с GPC — GNU Pascal. Ключ командной строки — -Sp .

Судя по всему — разработка данного режима для команды FPC — мягко говоря, задача не приоритетная. Реально никаких свой­ствен­ных GPC расширений не поддерживается, и от <$MODE TP>этот режим отличается только использованием адресного опе­ра­то­ра для процедурных переменных.

Это — режим Object Pascal. Соответствует ключу командной строки -S2 .

Работа с процедурными переменными полностью аналогична режиму <$MODE FPC>. Аналогично с заголовками функций и про­це­дур — они должны повторяться в точности при реализации. Вообще, режимы очень похожи, главное отличие — поддержка клас­сов.

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

Автоматически подгружается модуль ObjPas . Данный модуль по сути дополняет функциональность модуля System с уровня Turbo Pascal до уровня Delphi. Хотя, конечно, это грубое упрощение. Что реально он делает?

  • Во-первых, определяет тип smallint — целое со знаком длиной в слово, и переопределяет тип integer — в отличие от других режимов он означает целое со знаком размером в двойное слово, что соответствует типу longint , который остается таким же.
  • Во-вторых, для совместимости с Delphi определены процедуры AssignFile и CloseFile .
  • В-третих, переопределяет процедуры GetMem и FreeMem . К сожалению, я пока не ковырялся в исходниках и не могу сказать, что именно в их реализации изменено.
  • И в-четвертых, в этом модуле определяются функции для работы с ресурсными строками.

Free Pascal при <$MODE OBJFPC>поддерживает работу с ресурсными строками, через resourcestring .

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

Преобразование PChar -> string производится при присваивании автоматически.

Второе (после классов) по важности преимущество данного режима — обработка исключений в блоках try . except . end и try . finally . end . Данный механизм Free Pascal поддерживает полностью аналогично Delphi — то есть вы можете пе­ре­хва­ты­вать исключения, обрабатывать в зависимости от класса с помощью ключевого слова on , определять собственные классы ис­клю­че­ний и вызывать их (или стандартные) посредством оператора raise . Для корректной работы с исключениями нужно под­клю­чать, как и в Delphi, модуль SysUtils , который содержит базовый класс Exception и стандартные классы исключений.

В общем и целом данный режим очень похож на Object Pascal a-la Borland Delphi. Разработчики намерены реализовать и ин­тер­фей­сы к версии 1.2, но когда она выйдет, пока неизвестно.

Режим совместимости с Borland Delphi версии 2, если я не ошибаюсь. Соответствует ключу командной строки -Sd .

От <$MODE OBJFPC>отличается весьма незначительно:

  • Во-первых, необязательно в секции implementation повторять заголовок процедуры или функции.
  • И, во-вторых, не разрешена перегрузка функций, процедур и операторов.

В целом, я бы рекомендовал все-таки <$MODE OBJFPC>, который соединяет в себе объектную модель Object Pascal со всеми пре­лес­тя­ми собственно FPC.

Прочие расширения языка

Поддержка inline- процедур, функций и операторов. Определяется директивой компилятора <$INLINE ON/OFF>и ключом ко­манд­ной строки -Si . Inline-процедуры описываются директивой inline в заголовке, могут быть как нормальными пас­ка­лев­ски­ми, так и ассемблерными (при использовании директивы assembler ), то есть не требуют знания машинных кодов, как директива inline в Turbo Pascal. При использовании этой возможности следует помнить, что в случае ошибки (рекурсия, etc.) компилятор по­че­му-то не выдает осмысленного сообщения, а слетает на «Internal Error». Если inline-процедура используется за пределами про­грамм­но­го модуля, где она непосредственно описана, то трактуется как обычная вызываемая процедура.

Поддержка простых макросов. Определяется директивой компилятора <$MACRO ON/OFF>и ключом командной строки -Sm . Са­ми макросы определяются так <$DEFINE MACRO_NAME := EXPRESSION>, где MACRO_NAME — идентификатор, который в даль­ней­шем тексте программы будет заменяться на выражение EXPRESSION .

Поддержка специальных операторов присваивания в стиле Си. Определяется ключом командной строки -Sc , собственной ди­рек­ти­вы компилятора не имеет. Разрешает присваивание с одновременным сложением, вычитанием, умножением или делением, ис­поль­зуя операторы += , -= , *= и /= соответственно.

CloseFile — Процедура 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 может быть связан файл любого типа за исключением текстового

НОВОСТИ ФОРУМА
Рыцари теории эфира
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]

Создание и работа с текстовыми файлами в Delphi

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

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

1. подключение к файлу – связь с внешним файлом, указание режима подключения;

2. выполнение операций записи в файл или чтения из файла;

3. завершение работы с файлом.

Подключение к файлу

Для связи программы с файлом используется специальная переменная – «Файловая переменная». Объявляется она так же как и любая переменная в Delphi. Тип это переменной может быть File для типизированных (хранящих данные определенного типа) файлов, а можно указать TextFile, что будет означать тип обычного текстового файла. Объявление переменной:

var
f : TextFile;

В исполняемом коде программы выполняется подключение к внешнему файлу:

Команда AssignFile, выполняет связь файловой переменной с внешним файлом. Вторым параметром указывается адрес файла. Он может быть задан относительным или абсолютным. Если указать только имя файла, то программа будет пытаться обнаружить его в той же директории, где она сама и находится. Абсолютный путь указывается от корневого диска:

Использование относительной директории дает возможность не привязываться к конкретным дискам и адресам. Например:

AssignFile(f, ‘data\input.txt’ ); // во вложенной папке относительно директории с программой
AssignFile(f, ‘..\input.txt’ ); // на уровень выше директории с программой
AssignFile(f, ‘..\data\input.txt’ ); // во вложенной папке на уровень выше директории с программой

После того как выполнено подключение, выполняется процедура, устанавливающая режим работы с файлом. В основном это режим чтения или записи. Эти режимы назначаются процедурами Reset() ( для чтения) и rewrite() (для записи):

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

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

Reset(f, ‘C:\myprog\input.txt’ ); // чтение
Rewrite(f, ‘C:\myprog\input.txt’ ); // запись

Операции с файлами

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

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

while ( not EOF(f)) do begin
Readln(f, s);
end ;

Для записи, назначение режим записи в файл и командой Writeln() производится запись по строкам.

Закрытие файла

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

Примеры работы с текстовыми файлами в Delphi

Чтение в переменную одного значения из файла:

var
f : TextFile;
s : String;
begin
AssignFile(f, ‘input.txt’ );
Reset(f);

Загрузить все строки файла в компонент Memo:

var
f : TextFile;
s : String;
begin
AssignFile(f, ‘input.txt’ );
Reset(f);

while ( not EOF(f)) do begin
Readln(f, s);
myMemo.Lines.Add(s);
end ;

Следует отметить, что для этой задачи проще воспользоваться командой самого компонента Memo LoadFromFile().

Записать строку в файл:

var
f : TextFile;
begin
AssignFile(f, ‘input.txt’ );
Rewrite(f);

Writeln(f, ‘My text’ );

Записать в текстовый файл все строки из компонента Memo:

var
f : TextFile;
i : Integer;
begin
AssignFile(f, ‘input.txt’ );
Rewrite(f);

for i := 0 to myMemo.Lines.Count — 1 do
Writeln(f, myMemo.Lines[i]);
CloseFile(f);
end ;

Как и для чтения из файла в Memo, так и здесь, имеется специальная команда:

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