Статьи Королевства Дельфи

         

с обычными логическими операция над


1. Введение. Логические операции. Введение
Наряду с обычными логическими операция над логическими типами Boolean, часто приходятся выполнять операции и над отдельными битами, обычно используемыми, как флаги. Для эффективной работы необходимо понятие логических операций. Паскаль поддерживает следующие логические операции AND - логическое И; OR - (включающие) логическое ИЛИ; XOR - (исключающие) логическое ИЛИ; NOT - отрицание или инверсия бита; SHL - логический сдвиг влево; SHR - логический сдвиг вправо. Другие логические операции над числами в Паскаль не включены, но доступны через ассемблерные вставки.

Каждый бит может иметь только два состояния ЛОЖЬ (FALSE) или ИСТИНА (TRUE)

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

Для значения ЛОЖЬ, альтернативные варианты такие - [НЕТ, НОЛЬ, ВЫКЛЮЧЕНО, НЕ УСТАНОВЛЕНО, СБРОШЕНО, FALSE, F, 0, -] и другие.

Для значения ИСТИНА, альтернативные варианты такие - [ДА, ЕДИНИЦА, ВКЛЮЧЕНО, УСТАНОВЛЕНО, ВЗВЕДЕНО, TRUE, T, 1, +] и другие.

Рассмотрим эти операции по отдельности AND - логическое И, эта операции выглядит так
A B Y
0 0 0
0 1 0
1 0 0
1 1 1
Выражение истинно, когда истинны оба бита. Присказка "И там И там"

OR - (включающие) логическое ИЛИ, эта операции выглядит так
A B Y
0 0 0
0 1 1
1 0 1
1 1 1
Выражение истинно, когда истинен хотя бы один бит. Присказка "ИЛИ там ИЛИ там, включая и там и там"

XOR - (исключающие) логическое ИЛИ, эта операции выглядит так
A B Y
0 0 0
0 1 1
1 0 1
1 1 0
Выражение истинно, когда истинен только один бит. Присказка "ИЛИ там ИЛИ там, исключая и там и там"

NOT - отрицание или инверсия бита, эта операции применяется только к одному биту, действие простое — текущее значение бита изменяется на противоположное
A Y
0 1
1 0


SHL - логический сдвиг влево, операции применяется только к группе битов, одного из целочисленных типов Паскаля, например к байту, слову и т.д. Сдвиг байта влево на один разряд.
РазрядыB7B6B5B4B3B2B1B0
До10011101
После00111010

Сдвиг байта влево на два разряда
РазрядыB7B6B5B4B3B2B1B0
До10011101
После01110100


Байт смещается влево на один или более разрядов, позиции справа замещаются нулями, позиции слева теряются.

SHR - логический сдвиг вправо, операции применяется только к группе битов, одного из целочисленных типов Паскаля, например к байту, слову и т.д.
Сдвиг байта вправо на один разряд.
РазрядыB7B6B5B4B3B2B1B0
До10011101
После01001110

Сдвиг байта вправо на два разряда.
РазрядыB7B6B5B4B3B2B1B0
До10011101
После00100111


Байт смещается вправо на один или более разрядов, позиции слева замещаются нулями, позиции справа теряются.



На этом описание операций заканчивается, и переходим к практическим примерам. Но вначале немного слов о нотации

Применяемая нотация при отображении чисел в литературе

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

Нумерация разрядов начинается с нуля в соответствии со степень разряда и описывается формулой K*M^N, где K это коэффициент в диапазоне от 0 до M-1, M это основание числа, а N это степень. Число в степени 0 для всех оснований равно 1.

Посмотрим на примере следующей таблицы для четырех основных оснований.

Для числа 100
Основание Значение Формула
2 4 1*2^2 + 0*2^1 +0*2^0
8 64 1*8^2 + 0*8^1 +0*8^0
10 100 1*10^2 + 0*10^1 + 0*2^0
16 256 1*16^2 + 0*16^1 + 0*2^0


Для числа 123
Основание Значение Формула
2 X Недопустимая комбинация
8 83 1*8^2 + 2*8^1 + 3*8^0
10 123 1*10^2 + 2*10^1 + 3*10^0
16 291 1*16^2 + 2*16^1 + 3*16^0
Практические примеры

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

Получение позиции бита или его значения
1 shl N

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

Установка бита

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

function SetBit(Src: Integer; bit: Integer): Integer; begin Result := Src or (1 shl Bit); end; Здесь происходит следующее:
Сначала мы рассчитываем позицию бита - (1 shl Bit), затем устанавливаем полученный бит и возвращаем результат через предопределенную переменную Result. Пример использования: DummyValue := SetBit(DummyValue, 2);
РазрядыB7B6B5B4B3B2B1B0
До (1)10011101
После10011101
До (2)10011001
После10011101


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

Сброс бита
Для сброса отдельного бита или группы битов используется операция И совместно с инверсной маской, использование иллюстрируется ниже приведенным кодом в виде отдельной функции и результатом выполнения в виде таблицы. function ResetBit(Src: Integer; bit: Integer): Integer; begin Result := Src and not (1 shl Bit); end; Здесь происходит следующее:
Сначала мы рассчитываем позицию бита - (1 shl Bit), затем с помощью операции NOT инвертируем полученную маску, устанавливая, не затрагиваемые биты маски в единицу, а затрагиваемый бит в ноль, затем сбрасываем этот бит, а результат возвращаем результат через предопределенную переменную Result.
Пример использования:
DummyValue := ResetBit(DummyValue, 2);
РазрядыB7B6B5B4B3B2B1B0
До (1)10011101
После10011001
До (2)10011001
После10011001
Как видим, вне зависимости от начального состояние бита, после выполнения операции бит становится равны нулю.

Переключение бита
Для переключения отдельного бита или группы битов используется операция исключающие ИЛИ, использование иллюстрируется ниже приведенным кодом в виде отдельной функции и результатом выполнения в виде таблицы. function InvertBit(Src: Integer; bit: Integer): Integer; begin Result := Src xor (1 shl Bit); end; Здесь происходит следующее:
Сначала мы рассчитываем позицию бита - (1 shl Bit), затем с помощью операции XOR переключаем бит, а результат возвращаем результат через предопределенную переменную Result.
Пример использования: DummyValue := InvertBit(DummyValue, 2);
РазрядыB7B6B5B4B3B2B1B0
До (1)10011101
После10011001
До (2)10011001
После10011101
Как видим, состояние бита B2 изменяется на противоположное

Проверка бита
Для проверки бита используется операция AND и анализ результата на равенство нулю. if Value and (1 shl N) <> 0 then ... установлен if Value and (1 shl N) = 0 then ... не установлен чаще всего это используется в другой форме, вместо расчета позиции используется именованная константа, например const B2 = 4 // B2 (1 shl 2) Begin if Value and B2 = B2 then ... установлен if Value and B2 = 0 then ... не установлен end; Это более наглядно, особенно если константе дано более значимое имя, чем B2, например, для проверки готовности передатчика мы можем определить константу с именем TxReady, тогда это будет выглядеть очень красиво. const TxReady = 4 Begin if Value and TxReady then begin ... обработка готовности передатчика end; end;



Ну, вот с базисом мы покончили и пора приступить к более полезным и практическим примерам. В качестве примера выберем поиск папок и файлов. Пример был разработан для FAQ конференции fido7.ru.delphi, в дальнейшем был немного модернизирован по замечаниям от Юрия Зотова. Полный пример и остальные статьи из FAQ доступны для загрузки с моего .

procedure ScanDir(StartDir: string; Mask:string; List:TStrings); var SearchRec : TSearchRec; begin if Mask = '' then Mask := '*.*'; if StartDir[Length(StartDir)] <> '\' then StartDir := StartDir + '\'; if FindFirst(StartDir + Mask, faAnyFile, SearchRec) = 0 then begin repeat Application.ProcessMessages; if (SearchRec.Attr and faDirectory) <> faDirectory then List.Add(StartDir + SearchRec.Name) else if (SearchRec.Name <> '..') and (SearchRec.Name <> '.') then begin List.Add(StartDir + SearchRec.Name + '\'); ScanDir(StartDir + SearchRec.Name + '\', Mask, List); end; until FindNext(SearchRec) <> 0; FindClose(SearchRec); end; end; Рассмотрим ключевые моменты, относящиеся к данной статье. if FindFirst(StartDir + Mask, faAnyFile, SearchRec) = 0 then

Здесь является битовой маской, описанной в модуле SysUtils, ее значение равно $3F, она предназначена для включения в поиск специальных файлов и одновременно для изоляции лишних бит из структуры TsearchRec, отдельные биты данной маски описаны как именованные константы.

НаименованиеЗначениеОписание
FaReadOnly$00000001Read-only files Файлы с защитой от записи
faHidden$00000002Hidden files Невидимые файлы
faSysFile$00000004System files Системные файлы
faVolumeID$00000008Volume ID files Метка тома
faDirectory$00000010Directory files Папки
faArchive$00000020Archive files Архивные файлы (для системы архивации)
faAnyFile$0000003FAny file Все файлы - комбинация выше указанных флагов
if (SearchRec.Attr and faDirectory) <> faDirectory

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

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

Например, у нас есть такая структура некоторого устройства, и при поступлении данных происходит прерывание, обработка которого поступает в наш обработчик и в другие вместе с кодом состояния, если мы обработали сообщение, то мы должны возвратить значение TRUE, если то FALSE и тогда управление будет передано следующему в цепочке обработчику. Бит TxReady проверять не надо, управление будет поступать, только тогда когда он установлен.

abcccddd - где a - бит готовности b - бит разрешения прерывания ccc - тип операции ddd - счетчик function MyHandler(Code: byte): Boolean; const TxReady = $80; IntBit = $40; TypeMask = $38; CounterMask = $07; var I: Integer; TypeBits: Byte; begin if (Code and Intbit) = Intbit then begin // изоллируем биты типа и смещаем вправо для дальнейшей обработки TypeBits := (Code and TypeMask) shr 3; Case TypeBits of 0: begin for I := 1 to (Code and CounterMask) do begin считываем N данных, количесво указано в битах CounterMask, которые мы изолировали и использовали в качестве значения для окончания цикла. end; Result := TRUE; // обрабатали, пусть больше никто не трогает end; 1: begin команда 1, что то делаем Result := TRUE; // обрабатали, пусть больше никто не трогает end; 2: begin команда 2, что то делаем Result := TRUE; // обрабатали, пусть больше никто не трогает end; else Result := FALSE; // другие команды не наше дело end; end else begin Result := FALSE; // пусть другой обрабатывает end; end;



Ошибки при работе с битами

Например, для сложения бит мы можем использовать два варианта или операцию + или операцию OR. Первый вариант является ошибочным. AnyValue + 2, если бит два установлен, то в результате этой операции произойдет перенос в следующий разряд, а сам бит окажется сброшенным вместо его установки, так можно поступать если только если есть уверенность в результате, то если заранее известно начальное значение. А вот в случае использования варианта AnyValue or 2, такой ошибки не произойдет. Тоже относится к операции вычитания для сброса бита. faAnyFiles - faDirectory ошибки не даст, а вот AnyFlags - AnyBit может, дать правильный вариант, а может нет. Зато AnyFlags and not AnyBit всегда даст то что задумали, использования этой техники будет правильнее и для работы с аттрибутами файлов - faAnyFiles and not faDirectory. В качестве домашнего задания попробуйте выполнить это на бумаге для разных комбинацияй бит. Еще одна распростаненая ошибка, это логическая при выполнении операций над группами бит. Например неверено выполнять операцию сравнения над следующей конструкцией AnyFlags and 5 <> 0, если истина должна быть при установке обеих бит, надо писать так AnyFlags and 5 = 5, зато если устраивает истина при установке любого из бит, выражение AnyFlags and 5 <> 0 будет верныи.

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

Приложения

Таблица весовых множителей для 32 битного числа
БитDecHexБитDecHexБитDecHexБитDecHex
011825610016655361000024167772161000000
1229512200171310722000025335544322000000
244101024400182621444000026671088644000000
3881120488001952428880000271342177288000000
4161012409610002010485761000002826843545610000000
5322013819220002120971522000002953687091220000000
664401416384400022419430440000030107374182440000000
7128801532768800023838860880000031214748364880000000
С уважением,

6 сентября 2003 года

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

Для разработки архива использован PHP 4.3.5, разработка скрипта


в Паскале была собственная поддержка




FONT.Quoted {color : #996600; font-size : x-small; font-style : italic;} CODE {font-size : x-small;} 2. Работа с файлами Паскаля Работа с файлами Паскаля
Введение
Еще с древних времен в Паскале была собственная поддержка файлов, а к тому времени, когда мамонты уже вымерли, в нем появилась поддержка работы с файлами через ОС, а когда наши предки уже научились добывать огонь, появилась VCL.

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

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

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


Текстовые файлы – рассматривается простая работа с текстовыми файлами;
Типизированные файлы – то же самое, но для типизированных файлов;
Не типизированные файлы – немного будет рассмотрена работа и с не типизированными файлами, основные моменты, но вместо этого лучше использовать класс TFileStream, работа с которым и проще, а возможности шире;
Расширенная работа с тестовыми файлами, в данной главе будут рассмотрены более сложные методы работы, работа со строкой не как с целой строкой, а как с набором различных типов, Паскаль поддерживает автоматическое преобразование типов в текстовый формат и обратно, производя разбор строки при чтении и ее формирование при записи;
Использование текстовых файлов для импорта/экспорта, рассмотрим импорт в Эксель.

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

Объявляется файловая переменная нужного типа;
С этой файловой переменной связывается файл, функцией AssignFile;
Затем файл открывается Reset/Rewrite/Append;
Производятся операции чтения или записи, разновидности Read/Write;
Файл закрывается с помощью функции CloseFile.

С уважением,
Анатолий Подгорецкий

06..13 сентября 2003 года

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

Для разработки архива использован PHP 4.3.5, разработка скрипта


Для начала разберемся, что такое


FONT.Quoted {color : #996600; font-size : x-small; font-style : italic;} CODE {font-size : x-small;} 3. Текстовые файлы Текстовые файлы
Для начала разберемся, что такое текстовые файлы и в чем их различие от двоичных файлов. Текстовые файлы являются подмножеством двоичных файлов, но в отличие от двоичных не могут содержать весь набор символов. Вся информация в файле разбивается на строки, ограниченные символам возврат каретки (CR) и перевод строки (LF). Допустимые символы это символы с кодами от 32 до 255, символы с кодами ниже 32 являются управляющими и допустимы только следующие коды:

08 (BS) - возврат на шаг
09 (TAB) - табуляция
0A (LF) - перевод строки
0C (FF) – перевод листа
0D (CR) – возврат каретки
1A (EOF) – конец файла

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

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

Техническое задание:

Программа должна работать в консольном режиме и получать входные параметры через командную строку;
Имя программы Oem2Ansi;
На вход поступают два параметра, имя исходного файла и имя выходного файла;
Имя выходного файла может быть опущено, в этом случае используется имя входного файла, с изменением расширения выходного на .ANS;
Если имена не указаны, то должна выводиться справка о синтаксисе команды;
ошибки обрабатывать будем в минимальном объеме, коды ошибок выдаются как ErrorLevel и доступны для обработки в .bat файле.

Текст программы

Program Oem2Ansi;

{$APPTYPE CONSOLE}

uses
Windows,
SysUtils;
var
InFile: TextFile;
OutFile: TextFile;
InFilename: string;
OutFilename: string;
S: string;
begin
if ParamCount = 0
then
begin
WriteLn('Syntax is: Oem2Ansi Infile [outfile]');
Halt(1); // Ошибка синтаксиса
end;
InFilename := ParamStr(1);
if ParamCount = 2
then
OutFileName := ParamStr(1)
else
OutFileName := ChangeFileExt(InFilename, '.ans');
AssignFile(InFile, InFilename); // связываем входной файл
AssignFile(OutFile, OutFilename); // и выходной выходной файл
try
try
Reset(InFile); // открываем входной файл
Rewrite(OutFile); // создаем выходной файл
while not EOF(InFile) do // крутим пока не конец файла
begin
Readln(Infile, S); // читаем строку
if Length(S) > 0 // на вход функции можно
then // подавать только не пустые строки
begin
OemToChar(Pchar(S), Pchar(S));
WriteLn(Outfile, S); // записываем строку
end
else
begin
WriteLn(Outfile); // записываем пустую строку
end;
end;
except
Halt(2); // любая ошибка
// не удалось преобразовать файлы
end;
finally
CloseFile(InFile);
CloseFile(OutFile);
end;
end.

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

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

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

Первое действие состоит в открытии файлов, входной файл открывается с помощью процедуры Reset - это открытие текстового файла в режиме чтения, а второй с помощью Rewrite – открытие в режиме записи, если файл существует, то он переписывается. Есть еще одна форма открытия текстовых файлов, это функция Append(FileVar), открытие в режиме добавления строк, если файл существует, то курсор позиционируется в конец файла и файл открывается в режиме записи, если файла нет, то он создается. После нее управление передается в блок finally. В случае ошибки управление сначала передается в блок except, а затем в блок finally.

После этого создается цикл чтения строк, пока не будет достигнут конец файла, или физический или будет встречен символ EOF. Функция EOF(FileVar).

Внутри цикла читается строка во временную переменную Readln(Infile, S) и тут принята одна предосторожность, в функцию Oem2Char НЕЛЬЗЯ передавать пустые строки, поэтому производится анализ длины строки, если строка не нулевая, то производится конвертирование и запись ее в выходной файл, процедурой WriteLn(Outfile, S), иначе в файл пишется пустая строка.

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

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

Для особо желающих предлагается сделать GUI версию, с диалогами выбора файлов, с прогресс-бар, с предварительным просмотром первых 10-20 строк входного файла (переключение кнопкой OEM/ANSI), с целью определения направления перекодирования, с остальными наворотами, которые сумеет придумать душа, например пакетная обработка всех файлов из папки.

Для разработки архива использован PHP 4.3.5, разработка скрипта


Второй тип файлов, для которого


FONT.Quoted {color : #996600; font-size : x-small; font-style : italic;} CODE {font-size : x-small;} 4. Типизированные файлы Типизированные файлы
Второй тип файлов, для которого нет поддержки в OS и VCL – это типизированные файлы. Это такой вид файлов, в котором содержатся записи одного типа и фиксированной длины. Часто используется или для организации мини баз, конфигураций, иногда для импорта/экспорта в специальных форматах. Работа с такими файлами не сложнее, чем работа с текстовыми файлами, наряду с освоенными методами добавляется только одно новое свойство. Если текстовые файлы чисто последовательные, то в типизированных файлах можно перемещаться на любую запись и затем производить последовательное чтение или запись. Это очень похоже на работу с TFileStream за одним исключением, единицей информации является не байт, а тип.

Типизированный файл определяется следующим образом

var
FileVar: file of тип;

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

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

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

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

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

Замечания по поводу открытия файлов, для этого используются две ранее описанные процедуры: Rewrite - создает новый файл для чтения/записи, если такой файл существует, его длина устанавливается в ноль, а Reset - открывает файл для чтения/записи и не изменяет его длины. Сразу видно различие в этих процедурах по отношению к текстовым файлам.

Примечание: записи считаются с нуля

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

type
TPhoneRec = packed record
PersonName: string[25];
Address: string[25];
Phone: string[16];
end;

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

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

// опредение глобальных для модуля или программы переменных
var
PersonFile: file of TPhoneRec; // определили файл нашего типа
DbOpen: Boolean; // флаг состояния базы
PhonesCount: Integer; // количество записей в базе

// открытие базы и нициализация
// не пытаться работать с базой если результат = FALSE
function OpenDB(const DbName: string): Boolean;
begin
AssignFile(PersonFile, DbName);
try
Reset(PersonFile); // открываем базу
PhonesCount := FileSize(PersonFile); // текущее кол. записей
DbOpen := TRUE; // открытие прошло нормально
except
PhonesCount := 0;
DbOpen := FALSE;
end;
Result := DbOpen;
end;

// создание новой базы и инициализация
// не пытаться работать с базой если результат = FALSE
function CreateDB(const DbName: string): Boolean;
begin
AssignFile(PersonFile, DbName);
try
Rewrite(PersonFile); // открываем базу
DbOpen := TRUE; // открытие прошло нормально
except
DbOpen := FALSE;
end;
PhonesCount := 0; // записей нет
Result := DbOpen;
end;

// закрытие базы
procedure CloseDB;
begin
if DbOpen
then
CloseFile(PersonFile);
end;

// Удалить все после указанной записи
procedure TruncateDB(const RecNo: Integer);
begin
Seek(PersonFile, RecNo);
Truncate(PersonFile);
end;

// Читать следующую запись
function ReadNextRec: TPhoneRec;
begin
Read(PersonFile, Result);
end;

// Читать указанную запись
function ReadRec(const RecNo: Integer): TPhoneRec;
begin
Seek(PersonFile, RecNo);
Result := ReadNextRec;
end;

// изменить текущую запись
procedure ModifyNextRec(const Rec: TPhoneRec);
begin
Write(PersonFile, Rec);
end;

// изменить указанную запись
procedure ModifyRec(const RecNo: Integer; const Rec: TPhoneRec);
begin
Seek(PersonFile, RecNo);
ModifyNextRec(Rec);
end;

// Добавить новую запись в конец файла
procedure AddRec(const Rec: TPhoneRec);
begin
Seek(PersonFile, PhonesCount); // переместиться на последнею запись
ModifyNextRec(Rec); // и добавить запись
PhonesCount := FileSize(PersonFile); // новое кол. записей
end;

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

program ReadPhoneBook;

{$APPTYPE CONSOLE}

uses
PhoneDb;
var
I: Integer;
PhoneEntry: TPhoneRec; // отдельная запись
PhoneBook: array of TPhoneRec; // телефонный справояник
begin
if not CreateDB('C:\DB\Phones.dbf') then Exit;
WriteLn('Created C:\DB\Phones.dbf');
for I := 0 to 3 do
begin
PhoneEntry.PersonName := 'PersonName ' + IntToStr(I);
PhoneEntry.Address := 'Address ' + IntToStr(I);
PhoneEntry.Phone := '(012) 3456789-' + IntToStr(I);
AddRec(PhoneEntry);
WriteLn(PhoneEntry.PersonName,' ',
PhoneEntry.Address,' ' ,
PhoneEntry.Phone);
end;
CloseDB;

WriteLn('Check database');
if OpenDB('C:\DB\Phones.dbf')
then
begin

SetLength(PhoneBook, PhonesCount);
for I := 0 to PhonesCount - 1 do
begin
PhoneBook[I] := ReadNextRec;
WriteLn(PhoneBook[I].PersonName,' ',
PhoneBook[I].Address,' ' ,
PhoneBook[I].Phone);
end;
end;
WriteLn('Press ENTER to exit');
ReadLn;
end.

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

Для разработки архива использован PHP 4.3.5, разработка скрипта


Третий тип файлов Паскаля, это


FONT.Quoted {color : #996600; font-size : x-small; font-style : italic;} CODE {font-size : x-small;} 5. Нетипизированные файлы Нетипизированные файлы
Третий тип файлов Паскаля, это нетипизированные файлы, этот тип характеризуется тем, что данные имеют не регулярную структуру, например записи различных типов или записи переменной длины, или это просто двоичные данные.

После появления поддержки файлов в VCL, на уровне потоков, а также прямой доступ к файловой системе, через функции АПИ, его ценность стала низкой и почти во всех случаях правильнее использовать потоковые методы из VCL, и только тогда когда требуется небольшой размер программы, стоит использовать их или Win API.

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

Новых понятий немного, это понятие размер блока, режим открытия и вместо процедур Read/Write используются процедуры BlockRead/BlockWrite.

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

Объявление файла делается так:

var
FileVar: file;

Сразу бросается в глаза отсутствие типа, вместо file of тип, просто file, то есть файл данных любого типа.

Функции открытия файла Reset и Rewrite имеют дополнительный параметр, который указывает размер записи, если этот параметр не указан, то используется значение по умолчанию в 128 байт, кстати, это часто является одной из причин для возникновения ошибок, забываем указать этот размер, а при работе считаем, что работаем с байтом. Что бы работать с файлом, как с байтом, надо или установить размер записи в 1 или использовать типизированный файл следующего типа - file of byte.

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

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

Reset(F, 1); // открытие с сохранением файла, файл должен существовать
Rewrite(F, 1); // открытие с созданием нового файла, или с удалением старого

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

В модуль Sysutils находится определение констант для потоков, часть констант совпадает с нужными нам. Для полноценного управления режимами доступа надо использовать класс TFileStream.

FmOpenRead = 0 открытие только в режиме чтения
FmOpenWrite = 1 открытие только в режиме записи
fmOpenReadWrite = 2 открытие в режиме чтения/записи

Примечание: Переменная FileMode не является потоко безопасной.

Теперь можно приступить к примерам и поскольку трудно придумать практический пример, то я приведу по три основных примера использования данного типа и с использованием TFileStream. Это позволит оценить оба метода.
Пример 1 - абстрактные данные (file)
Использование с двоичными данными абстрактного типа. Просто набор байт.

Для демонстрации возьмем простой набор строк, скажем из TStringList. Запись в файл будем производить в следующем формате - длина, строка.

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

Умолчания для примера:
1. SL создан и содержит строки;
2. Переменная FileName инициализирована и содержит имя файла;
3. Обработка ошибок не ведется, кроме необходимых случаев.

var
SL: TStringList;
I: Integer;
F: file;
FileName: string;
begin
try
AssignFile(F, Filename); // связали файл с переменной
FileMode := fmOpenWrite; // только запись
Rewrite(F, 1); // размер записи один байт
for I := 0 to Sl.Count –1 do // проход по всем строкам
bеgin
BlockWrite(F, Length(Sl.Strings[I]), SizeOf(LongInt));
BlockWrite(F, Sl.Strings[I], Length(Sl.Strings[I]);
end;
finally
CloseFile(F);
end;
end.

Пример 2 - абстрактные данные (TFileStream)var
SL: TStringList;
I: Integer;
FS: TFileStream;
FileName: string;
I: Integer;

begin
FS := TFileStream.Create(Filename, fmOpenWrite or fmShareExclusive);
try
for I := 0 to Sl.Count –1 do // проход по всем строкам
bеgin
FS.Write(Length(Sl.Strings[I]), SizeOf(LongInt));
FS.Write(Sl.Strings[I], Length(Sl.Strings[I]));
end;
finally
FS.Free;
end;
end.

Преимущество состоит в использовании расширенных режимов открытия файлов и нет нужды объявлять размер записи, поскольку самих записей не существует, при записи в файл просто указывается длина данных.
Пример 3 – записи фиксированной длины (file)
Использование записей, но разного типа. Обратите внимание, что в записях используется не Integer, а LongInt, это связано с тем, что Integer не является фундаментальным типом и его размер зависить от версии компилятора, в то же время LongInt всегда 4 байта. Также что размер string[3] совпадает с размером LongInt, этим обеспечивается одинаковый размер записи. Вторым параметром, влияющим на размер записи - являет выравнивание элементов записи, на какую либо границу 2, 4 8 байт, это также предмет для изменений в различных версиях компилятора или его настроек. Использование ключевого слова packed позволяет избежать этой неприятности, в этом случае запись занимает ровно столько место, сколько требуется и не байтом больше. Это обеспечит нам переносимость. Настоятельно рекомендую обратить особое внимания на эти замечания, поскольку это распространенная ошибка при написании программ.

Умолчания для примера:

1. Массив DevArray создан и содержит данные;
2. Переменная FileName инициализирована и содержит имя файла;
3. Обработка ошибок не ведется, кроме необходимых случаев.

type
TCmd: string[3]; // команда устройству, аббревиатура из 3 символов

TRecType = (rtNone, rtCmd, ctData);

THdr = packed record
TypeID: TRecType; // идентификатор записи,
// общая часть во всех остальных типах.
end;

TCmd = packed record
Hdr: THdr; // идентификатор записи
DevCmd: TCmd; // команда устройству, аббревиатура из 3 символов
end;

TData = packed record
Hdr: THdr; // идентификатор записи
DevData: LongInt; // данные для устройства или из устройства
end;

TDevEntry = packed record
Cmd: TCmd;
Data: LongInt;
end;

var
Cmd: TCmd;
Data: Tdata;
DevArray: array[1..100] of TDevEntry;
F: file;
FileName: string;
I: Integer;

begin
try
AssignFile(F, Filename); // связали файл с переменной
FileMode := fmOpenWrite; // только запись
Rewrite(F, SizeOf(TCmd)); // TData имеет тот же размер
for I := Low(DevArray) to High(DevArray) do // проход по массиву
bеgin
Cmd.Hdr.TypeID := rtCmd;
Cmd.DevCmd := DevArray[I].Cmd;
BlockWrite(F, Cmd, SizeOf(TCmd));
Data.Hdr.TypeID := rtData;
Data.DevData := DevArray[I].Data;
BlockWrite(F, Data, SizeOf(TData));
end;
finally
CloseFile(F);
end;
end.

Пример 4 – записи фиксированной длины (TFileStream)
Объявления типов прежние.

var
DevArray: array[1..100] of TDevEntry;
FS: TFileStream;
I: Integer;

begin
FS := TFileStream.Create(Filename, fmOpenWrite or fmShareExclusive);
try
for I := Low(DevArray) to High(DevArray) do // проход по массиву
bеgin
FS.Write(rtCmd, SizeOf(THdr.TypeID));
FS.Write(DevArray[I].Cmd, SizeOf(TCmd.DevCmd));
FS.Write(rtData, SizeOf(THdr.TypeID));
FS.Write(DevArray[I].Data, SizeOf(TData.DevData));
end;
finally
FS.Free;
end;
end.

Как только код стал сложнне, так сразу стало видно, что использование TFileStream проще и прозрачнее, код более четкий. Отпала необходимости в копировании данных во временные переменные.
Пример 5 - записи переменной длины (file)Использование записей переменной длины для организации сложных структур. Записи состоят из двух частей, фиксированной с информацией о дальнейших записях и переменной 0 сами записи. Возможно построение сложных иерархических структур, когда одна запись содержит в себе другие вложенные данные, наглядным примером являются объектовые (.obj) и исполнимые файлы (.exe).

Умолчания для примера:

1. Массив DevArray создан и содержит данные;
2. Переменная FileName инициализирована и содержит имя файла;
3. Обработка ошибок не ведется, кроме необходимых случаев.

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

type
THdr = packed record
RecID: TRecType; // идентификатор записи, необязательная часть,
// зависит от задачи,
// но очень полезная в сложных структурах
RecLg: Integer; // длина данных, следуют сразу за заговоком
// данные могут быть простыми, но также и сложными
// то есть включать другие структуры
// со своими заголовками
end;

TCmd = string[6];

TPacked = packed record
DevCmd: TCmd; // команда устройству, аббревиатура из 3 символов
DevData: string; // переменная длина
end;

TDevEntry = packed record
Cmd: TCmd;
Data: string;
end;

В файл будем писать данные в следующим формате

1. Заголовок типа THdr, В качестве RecID будем использовать порядковый номер записи начиная с 1. RecLg будет включать полную длину последующего пакета, размер которого переменный.
2. Данные в формате TPacked, где DevCmd аббревиатура команды из 6 символов (string[6]), фиксированной длины и строковые данные переменной длины. Общая длина пакета отражается в заголовке записи, в поле RecLg.

var
Hdr: THdr;
DevArray: array[1..100] of TDevEntry;
F: file;
FileName: string;
I: Integer;

begin
try
AssignFile(F, Filename); // связали файл с переменной
FileMode := fmOpenWrite; // только запись
Rewrite(F, 1); // так как записи переменной длины,
// то размер записи 1 байт
for I := Low(DevArray) to High(DevArray) do // проход по массиву
bеgin
Hdr.RecId := I;
Hdr.RecLg := SizeOf(TCmd) + Length(DevArray[I].Data);
BlockWrite(F, Hdr, SizeOf(THdr)); // записали заголовок
BlockWrite(F, DevArray[I].Cmd, SizeOf(TCmd));
BlockWrite(F, DevArray[I].Data[0], Length(DevArray[I].Data);
end;
finally
CloseFile(F);
end;
end.

В примере происходит следующее:
Во временную переменную записывается номер записи
Рассчитывается длина переменного блока
Заголовок пишется в файл
Затем в файл пишется фиксированная часть блока DevArray[I].Cmd
И затем пишется переменная часть блока DevArray[I].Data[0]
Цикл повторяется по всему массиву, по окончанию файл закрывается, теперь реализуем это пример с помощью TFileStream.
Пример 6 - записи переменной длины (TFileStream)

var
DevArray: array[1..100] of TDevEntry;
FS: TFileStream;
I: Integer;

begin
FS := TFileStream.Create(Filename, fmOpenWrite or fmShareExclusive);
try
for I := Low(DevArray) to High(DevArray) do // проход по массиву
bеgin
FS.Write(I, SizeOf(THdr.RecID));
FS.Write(SizeOf(TCmd)+Length(DevArray[I].Data),SizeOf(THdr.RecLg));
FS.Write(DevArray[I].Cmd, SizeOf(TCmd));
FS.Write(DevArray[I].Data[0], Length(DevArray[I].Data);
end;
finally
FS.Free;
end;
end.

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

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

Для разработки архива использован PHP 4.3.5, разработка скрипта


с текстовыми файлами Расширенная работа


FONT.Quoted {color : #996600; font-size : x-small; font-style : italic;} CODE {font-size : x-small;} 6. Расширенная работа с текстовыми файлами Расширенная работа с текстовыми файлами
Кроме работы со строками в текстовых файлах, Паскаль поддерживает и более расширенные методы, можно оперировать и данными в строке. Можно читать данные из строки в одну или более переменных. Паскаль сам обеспечивает разбор строки на составляющие части.

Полный синтаксис процедур следующий

procedure ReadLn([ var F: TextFile; ] V1 [, V2, ...,Vn ]);
procedure WriteLn([ var F: TextFile; ] P1 [, P2, ...,Pn ] );


F - файловая переменная типа TextFile, это означает, что процедуры Readln и Writeln могут работать с текстовыми файлами и не применимы для других типов файлов. Если эта переменная опущена, то в качестве текстового файла используется консоль, это также означает, что вывод возможен на консоль и не применим для GUI приложений. На самом деле эта конструкция равносильна следующему вызову Readln([Input) или Writeln(Output). Просто это значение по умолчанию и компилятор сам подставляет эти файловые переменные, который уже описаны в модуле System. Если создано консольное приложение, то Дельфи автоматически ассоциирует эти файловые переменные с окном консоли приложения.
Vn – это одна или несколько переменных строкового, символьного, целочисленного или плавающего типа и также логические переменные. Возможно это не полный список типов, но можете попробовать проверить сами. Не поддержанные типа можно выводить с помощью функций преобразования в строку, например DateTimeToStr(Now).
Pn – это один или более параметров процедуры, которые могут являться строкой, символом, целым числом или числом с плавающей запятой. В справке по процедуре Write ошибочно указано, что в качестве параметров могут использоваться только переменные, на самом деле это могут быть как переменные, так и константные выражения.

Кроме того, справка полностью умалчивает о формате вывода данных в файл, о возможности форматирования данных непосредственно в параметрах процедуры. Форматирования вывода осуществляется в следующем виде X [:Width [:Decimals]], где Width общая длина вывода, а Decimals это количество знаков после десятичной точки. Для получения нужной длины вывода слева добавляется нужное количество пробелов, если результат не помещается в указанную длину, то тогда длина увеличивается до должного количества знаков.

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

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

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

Writeln(F, A,' ',B,' ',C);

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

WriteLn(F, 1, ' ', 2.5:3:1, ' ', ‘string’, ' ', 3);
ReadLn(F, Int1, Real1, Str1, Int2);

В файле будет следующая информация

1 2.5 string 3

После считывания значения переменных следующие:

Int1 = 1
Real1 = 2.5
Str1 = string 3
Int2 = 0

Правильно написать так

WriteLn(F, 1, ' ', 2.5:3:1, ' ', 3, ' ', ‘string’);
ReadLn(F, Int1, Real1, Int2, Str1);

Теперь в переменных правильные значения:

Int1 = 1
Real1 = 2.5
Int2 = 3
Str1 = string

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

var
Column: Integer;
F: TextFile;
IntArray: array[1..10, 1..3] of Integer;
Row: Integer;
S: string;
TmpInt: Integer;
begin
// Инициализация
for Row := 1 to 10 do
begin
for Column := 1 to 3 do
begin
IntArray[Row, Column] := Row*100 + Column;
end;
end;

AssignFile(F, 'Test.txt');
try
Rewrite(F); // открытие для записи
for Row := 1 to 10 do
begin
for Column := 1 to 3 do
begin
Write(F, IntArray[Row, Column]:11);
end;
WriteLn(F, ' Строка: ', Row);
end;
finally
CloseFile(F);
end;

// Чтение
try
WriteLn('Test Reading');
Reset(F); // открытие для чтения
for Row := 1 to 10 do
begin
for Column := 1 to 3 do
begin
Read(F, TmpInt);
Write(TmpInt:11);
end;
ReadLn(F, S);
WriteLn(S);
end;
finally
CloseFile(F);
end;
Readln; // Закрытие окна по ENTER
end.

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

Примечание: при выводе текстовых сообщений учтите, что на консоль надо выводить в OEM кодировке, поэтому если надо вывести текст на национальном языке, то предварительно преобразуйте его из ANSI в OEM, см. главу «Текстовые файлы - домашнее задание». То есть, любой текст надо преобразовывать функцией CharToOem. Это же касается и текстовых констант в коде программы.

Для разработки архива использован PHP 4.3.5, разработка скрипта


Использование текстовых файлов для импорта


FONT.Quoted {color : #996600; font-size : x-small; font-style : italic;} CODE {font-size : x-small;} 7. Использование текстовых файлов для импорта и экспорта Использование текстовых файлов для импорта и экспорта
Текстовые файлы являются универсальным средство импорта/экспорта, например, Excel может очень легко импортировать текстовые файлы, в одном из распознаваемых им форматов. Допустимы следующие форматы:


Comma Separated Value (CSV), данные разделенные запятой;
Tab Delimited (TXT), данные разделенные символом табуляции, легко распознается Экселем;
Symbol Delimited (TXT), данные разделенные указанным определенным символом, частный случай это Tab delimited, но его достоинство состоит в том, что в Экселе можно определить множество символов разделения, например одновременно разделителями могут быть ЗАПЯТАЯ, ТАБУЛЯЦИЯ и ТОЧКА С ЗАПЯТОЙ, Эксель разделит правильно;
Fixed (TXT), данные имеют фиксированную длину колонок.

Все четыре формата имеют свое назначение, по умолчанию CSV формат считается универсальным форматом, поскольку многие программы и даже некоторые языки программирования поддерживают его. Недостатком является некоторая избыточность. Самые экономные это Tab delimited и Symbol Delimited, поскольку для разделение используется только один символ. Самый не экономный формат Fixed, поскольку для размещения данных всегда используется полная длина, его достоинством является простота обработки файла, можно просто читать фиксированными порциями или даже определить структуру в программе. Многие программы пишут свои логи именно в этом формате.

Какой использовать формат определяется задачей. Но я рассмотрю в примерах все форматы. Данные для экспорта могут находиться где угодно: в базе данных, в TStringList, в другом текстовом файл, поступать из потока. В примерах будет использоваться экспорт из TStringGrid, это позволит нам убить двух зайцев, дополнить возможности TStringGrid и освоить экспорт. В дополнение к примерам по экспорту, я рассмотрю и обратную операцию, загрузку данных в TstringList из ранее сохраненных данных в текстовой файл.
Пример 1, экспорт в файл в формате Comma Separated ValueОсновой для экспорта в CSV понимание некоторых вещей:

1. Первая строка должна быть строкой заголовков колонок;
2. Данные разделяются запятой;
3. Числовые данные пишутся, как есть;
4. Строковые данные заключаются в двойные кавычки;
5. Даты распознаются если они в формате MM/DD/YYYY, заключать в кавычки не надо;
6. Расширение файла должно быть CSV.

Будьте осторожнее, обратный экспорт из Экселя работает не так как ожидается, формат далек от CSV, для обратного экспорта правильнее использовать формат Tab Delimited, с ним не ожидается таких сложностей и странностей. Есть еще странности, например, очень отличаются по действию открытие этих файлов из меню и открытие по ассоциации с расширением. Результаты очень удивят. Попробуйте поэкспериментировать с файлами и с Экселем, только сохраняйте в файлы с различными именами. При открытии по ассоциации (двойной щелчок) и расширении CSV получается полностью автоматический импорт.

Умолчания для примера:

1. StringGrid создан и содержит некоторое количество колонок и строк, количество определяется при экспорте;
2. Информация о типах данных в колонках StringGrid, то будем считать, что первая колонка целое число, вторая это дата в региональном формате и третья колонка это текст, больше колонок у нас нет.
3. Количество строк зависит от наполнения.
4. Нулевая строка как обычно содержит заголовки колонок.
5. Переменная FileName инициализирована и содержит имя файла, с должным расширением;
6. Обработка ошибок не ведется, кроме необходимых случаев.

var
F: TextFile;
FileName: string;
I: Integer;
SG: TStringGrid;
TempStr: string;
Y, M, D: Word;
begin
try
AssignFile(F, Filename); // связали файл с переменной
Rewrite(F); // создаем пустой файл
// если строка с заголовком не нужна, то можно эту строку удалить.
WriteLn(F,
'"', SG.Cells[0, 0], '",',
'"', SG.Cells[1, 0], '",',
'"', SG.Cells[2, 0], '"');
for I := 1 to SG.RowCount – 1 do // проход по всем строкам
begin
try
// конвертирование строки из регионального в американский формат
DecodeDate(StrToDate(Trim(SG.Cells[1, I])), Y, M, D);
TempStr := IntToStr(M)+'/'+ IntToStr(D)+'/'+IntToStr(Y);
except
TempStr := ' '; // дата не указана или неверная
end;
WriteLn(F,
SG.Cells[0, I], // число
TempStr, // конвертированная дата
'"', SG.Cells[2, I], '"'); // текст
end;
finally
CloseFile(F);
end;
end;

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

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

Первая колонка у нас число, поэтому выводим, как есть;
Вторая колонка у нас дата, приводим ее к формату MM/DD/YYYY, но также без кавычек;
Третья колонка у нас строка, ее выводим в кавычках.
Если дата опущена или неверная, то экспортируем пустое значение.

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

Этот же пример пригоден и для формата Symbol Delimited, достаточно заменить символ табуляции на любой нужный символ.

Умолчания для примера:

1. StringGrid создан и содержит некоторое количество колонок и строк, количество определяется при экспорте;
2. Так как отсутствует информация о типах данных в колонках StringGrid, то будем считать, что первая колонка целое число, вторая это дата в региональном формате и третья колонка это текст, больше колонок у нас нет. Но для экспорта данная информация не нужна.
3. Количество строк зависит от наполнения.
4. Нулевая строка как обычно содержит заголовки колонок.
5. Переменная FileName инициализирована и содержит имя файла, с должным расширением;
6. Обработка ошибок не ведется, кроме необходимых случаев.

const
TAB = #9; // код символа табуляции
// константа для удобства
// можно было бы использовать и #9
var
F: TextFile;
FileName: string;
I: Integer;
SG: TStringGrid;
begin
try
AssignFile(F, Filename); // связали файл с переменной
Rewrite(F); // создаем пустой файл

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

for I := 0 to SG.RowCount – 1 do // проход по всем строкам
begin
WriteLn(F,
SG.Cells[0, I] + TAB +
SG.Cells[1, I] + TAB +
SG.Cells[2, I]);
end;
finally
CloseFile(F);
end;
end;

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

Умолчания для примера:

1. StringGrid создан и содержит некоторое количество колонок и строк, количество определяется при экспорте;
2. Так как отсутствует информация о типах данных в колонках StringGrid, то будем считать, что первая колонка целое число, вторая это дата в региональном формате и третья колонка это текст, больше колонок у нас нет. Но для экспорта данная информация не нужна.
3. Количество строк зависит от наполнения.
4. Нулевая строка как обычно содержит заголовки колонок.
5. Переменная FileName инициализирована и содержит имя файла, с должным расширением;
6. Обработка ошибок не ведется, кроме необходимых случаев.

var
F: TextFile;
FileName: string;
I: Integer;
SG: TStringGrid;
begin
try
AssignFile(F, Filename); // связали файл с переменной
Rewrite(F); // создаем пустой файл

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

for I := 0 to SG.RowCount – 1 do // проход по всем строкам
begin
WriteLn(F,
Format('%-25s %-25s %16s',
[SG.Cells[0, I], SG.Cells[0, I], SG.Cells[0, I] ]));
end;
finally
CloseFile(F);
end;
end;

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

Разберем форматную строку

%-25.25s – Символ % это признак спецификатора формата, символ «-» означает выравнивание влево, 25 означает, что длина будет дополняться до 25 символов, а .25 означает максимальное количество символов в строке будет 25, остальные символы будут отбрасываться, символ s указывает не тип данных, в данном случае это означает, что в функцию передается строковое значение.

Количество спецификаторов не ограничено, в нашем случае их три и мы обязаны передать именно три параметра, что и делается, значения передаются как открытый массив. Символы пробелов, между спецификаторами, у нас выполняют роль разделителей колонок. На первый взгляд это сложно, но после того, как Вы освоите синтаксис этой функции, Вы сможете оценить всю ее мощь.
Пример 4, импорт и экспорт данных для TStringList
Остался еще один, последний пример, обеспечения импорта и экспорта данных из таблицы TStringList и обратно. Для этого выберем формат Tab Delimited, как очень экономичный и гибкий для нашей цели. Нам не придется бороться с их количеством, поскольку эти значения будут восстановлены автоматически. Нулевая строка будет содержать всю необходимую информацию о таблице. Единственно, что требуется обеспечить, чтобы все колонки данных в таблице имели свой заголовок.

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

Умолчания для примера:

1. StringGrid создан и содержит некоторое количество колонок и строк, количество определяется во время выполнения;
2. Информация о типах данных в колонках StringGrid отсутствует, но она нам и не нужна, мы должны уметь экспортировать любую информацию.
3. Количество строк зависит от наполнения.
4. Нулевая строка как обычно содержит заголовки колонок.
5. Переменная FileName инициализирована и содержит имя файла, с должным расширением;
6. Обработка ошибок не ведется, кроме необходимых случаев.

const
TAB = #9; // код символа табуляции
// константа для удобства
// можно было бы использовать и #9
procedure Export(const FileName: string; SG: TStringGrid);
var
F: TextFile;
I: Integer;
J: Integer;
begin
try
AssignFile(F, Filename); // связали файл с переменной
Rewrite(F); // создаем пустой файл
for I := 0 to SG.RowCount – 1 do // проход по всем строкам
begin
for J := 0 to SG.ColCount – 1 do // проход по всем колонкам
begin
Write(F,SG.Cells[J, I]); // пишем отдельную ячейку
if J < SG.ColCount – 1
then
Write(F, TAB) // тогда пишем разделитель
else
WriteLn(F); // иначе закрываем строку
end;
end;
finally
CloseFile(F);
end;
end;

procedure Import(const FileName: string; var SG: TStringGrid);
var
F: TextFile;
S: string;
begin
try
AssignFile(F, Filename); // связали файл с переменной
Reset(F); // открываем файд с данными
SG.ColCount := 1; // начальные значения
SG.RowCount := 1; // количества колонок и строк
while not EOF(F) do // проход по всем строкам
begin
ReadLn(F, S); // читаем строку данных
SG.Col := 0; // проход всегда начинается с нуля
while Pos(TAB, S) > 0 do
begin // вычленение колонок
SG.Cells[SG.Col, SG.Row] := Copy(S, 1, Pos(TAB, S) - 1);
Delete(S, 1, Pos(TAB, S));
if SG.ColCount - SG.Col = 1
then
begin
SG.ColCount := SG.ColCount + 1;// нужна новая колонка
end;
SG.Col := SG.Col + 1; // следующая колонка
end;
SG.Cells[SG.Col, SG.Row] := S; // последняя колонка
SG.RowCount := SG.RowCount + 1; // добавим еще одну строку
SG.Row := SG.Row + 1; // следующая строка
end;
SG.RowCount := SG.RowCount - 1; // лишняя строка
finally
SG.FixedCols := 1; // восстанавливаем
SG.FixedRows := 1; // значение по умолчанию
CloseFile(F);
end;
end;

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

В дополнение к обычной работе с файлами, можно отметить еще и следующее. Все ранее изученные нами методы пригодны для создания стандартных консольных приложений для динамических ВЕБ страниц. Для создания достаточно использования процедур ReadLn и WriteLn, если конечно этот сервер работает под управлением Windows. Это так называемые консольные CGI приложения (Standalone CGI Application).
Вот выдержка из книги доктора Боба «Интернет решения от доктора Боба», которую можно найти на моем сайте



Для начала посмотрим на стандартное "hello world" CGI приложение. Единственное, что оно должно сделать, это вернуть HTML страницу со строкой "hello, world". Перед тем как мы начнем делать это - обратим внимание на следующее: CGI приложение должно сообщить миру какой (MIME) формат оно выдает. В нашем случае это "text/html", которое мы должны указать как: content-type: text/html, и затем одну пустую строку.

Вот код нашего первого "Hello, world!" CGI приложения:

program CGI1;
{$APPTYPE CONSOLE}
begin
writeln('content-type: text/html');
writeln;
writeln('<HTML');
writeln('<BODY');
writeln('Hello, world!');
writeln('</BODY');
writeln('</HTML')
end.

Если вы откомпилируете данную программу в Дельфи 2 или 3 и затем запустите ее из web браузера подключенного к web серверу, где оно записано в исполнимом виде в исполняемом каталоге таком как cgi-bin, то вы увидите текст "Hello, world!" на странице.
Заключение

Ну вот теперь вы знаете про файлы Паскаля все, ну или почти все :), остальное в ваших руках.

Для разработки архива использован PHP 4.3.5, разработка скрипта


Лицей.


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

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

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

Статьи будут рассматривать отдельную тему с практическими примерами и надеюсь на уровне доступном для начинающих. Не знаю как получится, поскольку опыт писательской работы у меня не большой, зато есть приличный опыт работы в дискуссионных группах. Кроме того вся микропроцессорная техника и ее программирование прошли совместно с моей жизнью, начинал я с 8 разрядных машин, затем вплотную от самых первых персональных компьютеров и по текущие дни. Паскаль же от 4 версии до самой последней, а после уже Дельфи, начиная с первой версии.

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

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


Анатолий Подгорецкий