"Нетипизированные" файлы, то есть файлы без типа.

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

Итак, в чем же разница? Чтобы лучше это понять, советую вспомнить как производиться чтение из типизированных файлов. Работая с ними, мы заранее знаем с переменными какого типа мы имеем дело и, соответственно, без проблем читаем эти переменные. Здесь же все наоборот: работая с файлами без типа мы не знаем, что за данные в них находятся и в переменные какого типа их надо помещать. Вы не поняли? Вот смотрите. Имеем файл, в котором находиться два элемента: 1) число "100" (символ "d" - насчет ASCII кодов см. пред. выпуски), 2) строка - "Here is string". Как прочитать из этого файла две переменные разных типов? Вспомните еще раз - если мы зададим этот файл как типизированный или текстовый, то сможем читать либо только числа (File of Integer) либо символы и строки (Text).

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

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

dHere is string

Номер буквы "d" в таблице символов - 100. Если мы прочитаем из файла один байт, указав, что его нужно поместить в переменную типа Byte, эта переменная приобретет значение 100. Если же мы будем читать этот байт в переменную типа Char, то получим символ "d". Вот и вся особенность, согласитесь, иногда довольно полезная. Скажу, что таким образом очень удобно читать записи, особенно разные.

Ну а теперь давайте посмотрим, каким образом производиться работа с файлами без типа.


Чтение из файлов без типа

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

var
F: File;
begin
{ связываем файл с переменной }
Assign(F, '1.txt');
Reset(F);
end.

Чтение происводиться с помощью процедуры BlockRead. Посмотрим, как она работает:

BlockRead(F: File, Buf: Var, Size: Word, Result: Word)

F: File;

- переменная типа File; Именно из этой переменной и происходит чтение данных.

Buf: Var; - переменная любого типа. В эту переменную помещаются прочитанные данные.

Size: Word; - количество считываемых байт.

Result: Word; - в эту переменную помещается реальное количество байт, которые были прочитаны.

Работает эта процедура следующим образом: из файла F считывается Size записей, которые помещаются в память, начиная с первого байта переменной Buf. После выполнения процедуры реальное количество прочитанных байт помещается в переменную Result. Здесь надо сказать, что эта переменная совсем не обязательно должна присутствовать в качестве параметра, то есть ее попросту можно опустить. Однако иногда она довольно полезна и даже необходима - например, если чтение было окончено до того, как было прочитано требуемое количество байт (достигнут конец файла), мы можем это отследить через переменную Result. Если же в этом случае (чтение данных после конца файла) переменная Result не будет указана, то образуется ошибка времени выполнения N100 "Disk read error" (Runtime error 100).

Вот пример использования этой процедуры:

{ допустим имеем такой файл:
dЦHello!
Здесь:
d - ASCII 100
Ц - ASCII 150
Hello! - строка из 6ти символов
}
type
R = record
A: Byte;
C: Array[1..6] of Char;
end;
var
F: File;
I: Byte;
Rec: R;
Result: Word;
begin
{ связываем файл с переменной }
Assign(F, '1.txt');
{$I-}
Reset(F, 1);
{$I+}
if IOResult<>0 then Halt;
BlockRead(F, I, Sizeof(I), Result);
BlockRead(F, Rec, Sizeof(Rec), Result);
Writeln(I);
Writeln('Rec values: ');
Writeln('A: ', Rec.A);
Writeln('S: ', Rec.C);
Readln;
Close(F);
end.

Обращаю ваше внимание на новую функцию, которую я использовал в этой программе: Sizeof. Эта функция принимает в качестве параметра любую переменную и возвращает ее размер в байтах. Размер переменных стандартных типов (Integer, Byte....) можно найти в таблицах типов из пред. выпусков, в то время как размер определяемых пользователем типов, таких как запись иногда подсчитать довольно не просто. Поэтому SizeOf иногда очень выручает, упрощая работу. Далее хочу указать вам на дополнительный параметр процедуры Reset. Он указывает размер буфера, который используется для передачи данных. В прошлом выпуске, когда я говорил о текстовых файлах и рассказывал о процедуре Reset я не упоминал об этом параметре. Дело в том, что с текстовыми файлами он не используется.

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


Запись в файлы без типа

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

BlockWrite(F: File, Buf: Var, Size: Word, Result: Word)

F: File;

- переменная типа File;

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

Size: Word; - количество записываемого блока данных в байтах.

Result: Word; - в эту переменную помещается реальное количество байт, которые были записаны.

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

uses Crt;
type
R = record
A: Integer;
B: Word;
end;
var
F: File;
Result: Word;
C: Char;
I: Integer;
S: String;
Rec: R;
begin
ClrScr;
{ считываем и задаем исходные данные }
Write('Enter CHAR: '); Readln(C);
Write('Enter INTEGER: '); Readln(I);
Write('Enter STRING: '); Readln(S);
Randomize;
Rec.A := Random(1000);
Rec.B := Random(1000);
Writeln('Rec.A: ', Rec.A);
Writeln('Rec.B: ', Rec.B);
Readln;
{ выполняем действия по записи в файл }
Assign(F, '1.txt');
ReWrite(F, 1);
BlockWrite(F, C, SizeOf(C)+SizeOf(I)+255+SizeOf(Rec), Result);
Close(F);
end.

Как видите, в процедуре BlockWrite я использовал целое выражение в качестве указания размера записываемого буфера. Составлено оно из сумы 3х результатов функции SizeOf и числа 255, которое является длиной строки S. На мой взгляд использование в таких ситуациях выражений гораздо более рационально, чем использование, например, предварительно посчитанной переменной. См. подчеркнутые моменты:

uses Crt;
type
R = record
A: Integer;
B: Word;
end;
var
Size: word;
F: File;
Result: Word;
C: Char;
I: Integer;
S: String;
Rec: R;
begin
ClrScr;
{ считываем и задаем исходные данные }
.......
{ выполняем действия по записи в файл }
Assign(F, '1.txt');
ReWrite(F, 1);
Size := SizeOf(C)+SizeOf(I)+255+SizeOf(Rec); { я это имею в виду }
BlockWrite(F, C, Size, Result);
Close(F);
end.

А вы как считаете? Кстати, здесь хочу напомнить лишний раз о функции Length, которая возвращает длину строки. С ее помощью можно было бы написать так:

Size := SizeOf(C)+SizeOf(I)+Length(S)+SizeOf(Rec);

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

{ Программа быстрого копирования файлов }
Uses Crt;
Var
FromF, ToF : File;
NumRead, NumWritten : Word;
Buf : Array [1..2048] Of Char;
Begin
{ Открываем входной файл }
Assign(FromF, ParamStr(1));
Reset(FromF, 1);
{ Размер буфера записи = 1 байт }
{ Открываем выходной файл }
Assign(ToF, ParamStr(2));
ReWrite(ToF, 1);
{ Размер буфера записи = 1 байт }
WriteLn('Копирую ', FileSize(FromF), ' байт...');
Repeat
BlockRead(FromF, Buf, SizeOf(Buf), NumRead);
BlockWrite(ToF, Buf, NumRead, NumWritten);
Until (NumRead = 0) Or (NumWritten <> NumRead);
Close(FromF);
Close(ToF);
end.

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

C:copy.exe 1.txt A:1.txt

То функция ParamStr(1) вернет строку "1.txt". Функция ParamStr(2) - строку "A:1.txt". Разобрались? Все очень просто!

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

Итак, псевдографика. Что же это такое? Собственно говоря, это обычные символы, которые имеются в стандартном наборе ASCII. Что такое ASCII коды я уже говорил, но случаев непосредственного применения и работы с ними не показывал. К сожалению, корректно отобразить эти символы в браузере и почтовой программе затруднительно, поэтому для понимания дальнейшей работы вам нужно запустить такую программку, по которой и будем работать:

Program Border;
uses Crt;
Procedure MakeWindow(X, Y, X1, Y1: Byte);
Var
I: Byte;
Begin
GotoXY(X, Y); Write(#201);
GotoXY(X1, Y); Write(#187);
GotoXY(X, Y1); Write(#200);
GotoXY(X1, Y1); Write(#188);
For I := X+1 to X1-1 do
Begin
GotoXY(I, Y); Write(#205);
GotoXY(I, Y1); Write(#205);
end;
For I := Y+1 to Y1-1 do
Begin
GotoXY(X, I); Write(#186);
GotoXY(X1, I); Write(#186);
end;
end;
var
B: Byte;
Begin
ClrScr;
B := 187;
Writeln('201: ', #201);
Writeln('205: ', chr(205));
Writeln('187: ', chr(B));
Write('186: ', #186, #13, #10);
Write('188: ', #188);
Write(#$0d, #$0a, '200: ', #200);
MakeWindow(10, 10, 40, 20);
Readln;
end.

Она выводит несколько ASCII символов из стандартного набора. Это - символы рамки, которые нам и потребуются. В качетсве наглядного примера есть процедура (MakeWindow), которая рисует рамку по заданным координатам. Запустили? Посмотрели? Тогда для начала давайте разберем, как работает эта программа.

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

Writeln('201: ', #201);

Вывод числа с предшествующим знаком # - это не что иное, как вывод символа по его коду. Используется чаще всего, что и вам рекомендую делать.

Writeln('205: ', chr(205));

Функция Chr(byte): Char; - возвращает символ, номер которого в ASCII таблице заданный параметром. В этом выражении мы сразу выводим возвращаемый символ на экран.

Writeln('187: ', chr(B));

То же самое - выводим символ, номер которого задан переменной.

Write('186: ', #186, #13, #10);

Интересный вариант. Вы видите, я использую процедуру Write, т.е. она не переносит после своей работы строку? Но строка переносится. Это - совокупный вывод символов #13 (перенос строки) и #10 (возврат каретки).

Write(#$0d, #$0a, '200: ', #200);

Не пугайтесь внешнего вида таких символов. На самом деле это просто символа с номером, который задан числом в 16й системе. В этом случае числа: $0d - 13, $0a - 10. Вот и все, как и в пред. примере - просто моделируем Writeln.

Как видите, нет ничего сложного. Ну и, естественно, налицо пример использования всех жти символов на практике. Процедура MakeWindow - построение окошек по заданным координатам. Работает очень просто:

  • Сначала печатаем уголки рамки;
  • После циклами выводим бока, т.е. поля окна.

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

Ну а теперь хочу предложить вам выполнить два задания

Задание N1: Смастерите процедуру создания окна с заголовком: наподобие тех, которые использует сам Turbo Pascal. Естественно, без служебных символов и областей, просто заголовок, расположенный посередине. Сделаете? Конечно!

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



Опубликовал admin
16 Ноя, Воскресенье 2003г.



Программирование для чайников.