Главная » Статьи » Программирование на языке Pascal

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

Например:
c:\autoexec.bat - файл с именем "autoexec" и расширением "bat" находится в корневом каталоге диска "c:".
d:\work\doc\mywork.doc - файл с именем "mywork" и расширением "doc" находится в подкаталоге "doc" каталога "work" на диске "d:".
 
Надо заметить, что физически единой эта область данных вовсе не является: файл обычно состоит из целого набора так называемых кластеров (участков диска фиксированного для операционной системы размера), которые разбросаны по диску тем прихотливее, чем чаще мы стираем и записываем на него информацию.
Вообще, система окажется наиболее производительной, если кластеры файла будут идти не подряд, а с неким постоянным интервалом: пока система "переварит" данные с одного кластера, диск как раз успеет повернуться до другого.
Для того, чтобы упорядочить (еще говорят, дефрагментировать) информацию на диске, используют специальные утилиты-дефрагментаторы. Одна из таких входит в поставку Windows.
Расширения указывают на тип файлов, при пользовательской работе мы четко различаем их.

Например, файл tet.exe является исполняемым модулем, а myprog.pas - очевидно, текстом программы на языке Паскаль.
Однако, как мы увидим дальше, в программировании таких четких различий для файлов данных не существует, если мы сами их не задаем. Файлы данных, с которыми мы будем работать, могут иметь любое расширение, например, ".dat" для исходных данные и ".res" для файла результатов.
С точки зрения программирования все файлы можно разделить на две группы: исполняемые программы; файлы данных, которые этими программами используются. Логический файл в Паскале описывается как переменная одного из файловых типов.

Мы связываем логический файл с физическим файлом на диске и через логический получаем доступ к физическому. В результате, как мы скоро увидим, нам не нужно задумываться о технических проблемах, чтобы организовать обмен данными между нашей программой и файлом.
В Паскале существует три типа файловых переменных: текстовые файлы (тип Text); компонентные или типизированные файлы (тип File of...); бестиповые или нетипизиованные файлы (тип File). Текстовый файл, с которым мы работаем в DOS-среде, состоит из последовательности ASCII-кодов, среди которых могут быть и управляющие.

Байты текстового файла организуются по строкам. Информацию любого типа (числовую, символьную, строковую) текстовый файл хранит в виде символов, ее изображающих.
Самый привычный нам текстовый файл - это программа на Паскале.
ASCII-код, о котором мы уже говорили раньше, это текстовая кодировка, использующаяся операционной системой DOS.
Управляющие символы не выводятся, а служат для выполнения определенных команд при выводе. Например, если вывести символ с кодом 7, мы услышим короткий звуковой сигнал со спикера. Компонентный (типизированный) файл содержит любые структурированные данные в машинном представлении, то есть в том же виде, что и в оперативной памяти. Напрямую выводить такие данные на экран или пытаться прочитать их бессмысленно.

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

Бестиповые файлы удобно использовать в задачах, где данных не так много, чтобы группировать их в несколько файлов по типам, но они настолько разноплановые, что в один компонентный их тоже не объединить. Кроме этого бестиповые файлы применяются там, где происходит работа с содержимым файла без распознавания информации, например, когда нужно побайтово скопировать один файл в другой.
Можно сказать, что нетипизированный файл - это самый низкоуровневый канал ввода-вывода данных в Паскале.
Файл любого типа обязательно содержит специальный код, который указывает на конец файла.
Объявления файловых переменных в Паскале:
 
type
Student = record Family,
           Name: string;
           Group: byte;
           MidMark: real;
end;
var TextFile: Text; {текстовый файл}
ElseTextFile: File of string; {компонентный файл строк - не текстовый!}
ComponentFile: File of real; {компонентный файл вещественных чисел}
StudentFile: File of Student; {компонентный файл записей}
OtherFile: File; {бестиповой файл}
Общие принципы работы с файлами
Байт - минимальная единица хранения данных для любых файлов. Перед тем, как использовать файл любого типа, нужно связать файловую переменную (логический файл) с физическим файлом на диске. Эта операция проделывается директивой assign. Файловые переменные нельзя использовать в операторах присваивания. Причем с любой стороны. Если файловая переменная используется в качестве формального параметра подпрограммы, она обязана описываться как var-параметр (по адресу).
 
2. Основные операции работы с файлами
1) Assign (var FileVar; FileName : string)

Эта директива связывает файловую переменную FileVar с именем физического файла, заданным в строке FileName. Таким образом, все операции, которые мы будем проделывать с переменной FileVar, на самом деле будут изменять физический файл с именем FileName.
Эта связь сохранится до следующего вызова assign с той же переменной FileVar.
После установления связи логический файл считается закрытым и работать с ним еще нельзя.
 
2) Reset (var FileVar) и Rewrite (var FileVar)

Процедура Reset открывает файл FileVar для чтения. Если открытие прошло успешно, файл считается открытым, и курсор устанавливается на начало его первого элемента. То есть считывать компоненты файла мы всегда начинаем с начала.
Процедура Rewrite открывает файл FileVar для записи. Опять, если все прошло нормально, файл будет готов к записи первого элемента. Обратите внимание, что если файла не существует, он будет создан, а вот если такой файл есть и непустой, все данные в нем автоматически сотрутся, и заполнение начнется с самого начала.
И Reset, и Rewrite мы можем применить к одному и тому же файлу многократно. При этом он каждый раз будет автоматически закрываться и открываться вновь, а курсор снова окажется в начале. Потери данных при считывании исключаются.
 
3) Close (var FileVar)

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

program Example;
 
var F: File; {файл данных}
bFileIsOpen: boolean; {флаг: файл был открыт}
 
Begin
Assign (F, 'c:\mywork\mydoc.dat');
{$I-}
Reset (F);
bFileIsOpen := TRUE;
{$I+} if IOResult<>0 then begin {файл открыть не удалось} writeln ('Ошибка: невозможно открыть файл данных.');
bFileIsOpen := FALSE;
end;
{некие нужные действия} if bFileIsOpen then Close(F);
end.

Если мы попытаемся открыть для чтения несуществующий файл, произойдет сбой программы. Обычно такие ошибки автоматически отслеживаются программой, и если что-то не так, пользователь получает сообщение системы, а программа аварийно завершается. Это не слишком хорошо, поскольку сообщение скорее всего будет на английском, а кроме того программа не должна "выпадать" в систему даже при ошибках.
Поэтому мы отключаем автоматическую проверку ошибок ввода-вывода (директива {$I-}) перед тем, как открыть файл, и включаем ее снова (директива {$I+}) сразу после оператора Reset.
Однако, мы должны знать, открыт ли файл на самом деле, а пользователь должен получить адекватное сообщение об ошибке, если она произошла. Код ошибки ввода-вывода после каждой операции содержится в системной переменной IOResult, и она как обычно равна нулю, если операция закончилась нормально.
Чтобы убедиться, что файл действительно открыт, мы вводим логическую переменную bFileIsOpen, которой присваиваем FALSE, если IOResult не равно нулю, а значит, при открытии F произошла ошибка. Теперь мы закроем файл данных, только если он на самом деле был открыт.
 
 4) Функция EOF (FileVar) : boolean

Эта функция возвращает TRUE, если файловый курсор стоит на маркере конца файла, то есть после последней его записи. При считывании данных из файла практически всегда используется форма: while not EOF(FileVar) do ...

То есть работа выполняется, пока не закончится файл. 3. Ввод-вывод данных

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

Как мы помним, типизированный файл состоит из однотипных компонентов. Соответственно ввод или вывод осуществляется только через переменную того же типа, что и компоненты файла. Мы можем считать в эту переменную значение следующего компонента или записать значение этой переменной в конец типизированного файла. Оператор Read (F: File of AnyType; value1, value2,...: AnyType) считывает очередные компоненты файла F в переменные value1, value2,... соответствующего типа по порядку. Оператор Write (F: File of AnyType; value1, value2,...: AnyType) записывает в файл F набор значений из переменных value1, value2,... в порядке перечисления. Вот простейший пример: требуется скопировать все отрицательные числа из одного файла вещественных чисел в другой.
program Example;
 
var SrcFile, DestFile: File of real; {файл-источник и файл-результат}
      SrcFileName, DestFileName: string; {имена файлов}
      nextelem: real; {следующий компонент файла}
      ask: char; 
      procedure CreateFileOfReal;
var i, num: word;
min, max, value, sign: real;
 
begin
Rewrite(SrcFile); {открываем файл на запись}
writeln('Введите количество компонентов файла ');
readln(num);
writeln('Введите нижнюю границу значений ');
readln(min); writeln('Введите верхнюю границу значений ');
readln(max); {если перепутаны границы, меняем их} if min>max then begin value:= min; min:= max;
max:= value;
end;
{заполняем файл значениями} for i:=1 to num do begin value := random*(max-min)+min; sign := random;
if sign>=0.5 then value := -value;
write (SrcFile, value); {запись компонента в файл}
end;
Close(SrcFile); {закрываем файл - все данные сохранены}
end; begin writeln('Введите имя исходного файла ');
readln(SrcFileName);
Assign(SrcFile, SrcFileName);
writeln('Нужно ли предварительно заполнить его? (д/н)');
readln(ask);
if (ask='y')or(ask='Y')or(ask='д')or(ask='Д') then CreateFileOfReal;
writeln('Введите имя файла-результата ');
readln(DestFileName);
Assign(DestFile, DestFileName); {открываем файлы}
{$I-}
Reset(SrcFile);
{$I+} if IOResult<>0 then begin writeln('Ошибка: невозможно открыть исходный файл.');
exit;
end;
Rewrite(DestFile); {собственно копирование значений}
while not EOF(SrcFile) do begin read (SrcFile, nextelem);
 write ('Следующее значение = ',nextelem:8:3);
 if nextelem<0 then begin write (DestFile, nextelem);
writeln (' - копируем');
end else writeln(' - не копируем');
end; {закрываем оба файла - сохраняем данные}
Close (SrcFile);
Close (DestFile);
end.

Вернемся к ошибкам ввода-вывода: закрывать программу, если произошла ошибка, вообще некрасиво. Такой прием годится только для очень простых учебных программ. По хорошему, нужно дать пользователю ввести имя файла заново, например:
 
repeat writeln('Введите имя копируемого файла ');
readln(SrcFileName);
Assign(SrcFile, SrcFileName);
{$I-}
Reset(SrcFile);
{$I+}
errcode:=IOResult;
 if errcode<>0 then writeln('Ошибка: невозможно открыть исходный файл. Повторите ввод.'); until (errcode=0);

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

Текстовый файл состоит из символов, организованных по строкам.
Операторы Read (F, x1, х2, ...) и Readln (F, x1, х2, ...) считывают из текстового файла F следующие от курсора значения в переменные х1, х2 и т.д. При этом Read считывает информацию и оставляет курсор на следующем байте, а Readln после ввода переводит курсор на начало следующей строки текстового файла, причем делает это и в том случае, если в предыдущей строке остались непрочитанные данные.
Например: while not EOF (TextFile) do begin Read (TextFile, char1, char2); {считаем, что charN имеет тип char} n Readln (TextFile, char3); end;

В этом слегка корявом фрагменте из каждой строки файла считывается по три символа, после чего мы переходим на следующую строку. Корявость заключается не только в форме, но и в том, что если в строке файла окажется менее трех символов, в оставшиеся переменные занесется символ конца строки.
Функция EOLn (F) : boolean - возвращает TRUE, если курсор в файле F стоит на символе конца строки #13. Эта функция используется только для текстовых файлов. Давайте рассмотрим пример. Пусть необходимо вывести на экран все русские буквы из заданного текстового файла. Программа для решения такой задачи, очевидно, будет выглядеть так:
program Example;
 
const RusLetters : set of char = ['а'..'п','р'..'я','А'..'Я'];
var TextFile: Text;
TextFileName: string; ch: char; errcode: integer;
 
begin
{вводим имя исходного файла} repeat writeln('Введите имя исходного файла ');
readln(TextFileName);
Assign(TextFile, TextFileName);
{$I-}
Reset(TextFile);
{$I+}
errcode:=IOResult;
if errcode<>0 then writeln('Ошибка: невозможно открыть исходный файл. Повторите ввод.'); until (errcode=0); {выводим русские буквы} while not EOF (TextFile) do
     begin
     while not EOLn (TextFile) do begin Read (TextFile, ch);
      if ch in RusLetters then write(ch);
    end;
Readln (TextFile);
end;
end.

Как видите, каждую строку мы обрабатываем посимвольно до конца, а затем переходим на следующую строку оператором Readln.
Текстовый файл состоит из символов, и в этой задаче мы именно с символами и работали. Однако из текстового файла можно вводить и значения других типов.
Правила ввода информации из текстового файла: Аргументы операторов Read и Readln могут иметь целочисленный, вещественный, символьный, строковый тип или совместимые с ними. Сложные структурированные типы (массивы, множества, записи) вводятся только по элементам. При вводе числовых значений два числа считаются разделенными, если между ними есть хоть один пробел, символ табуляции (#9) или символ конца сроки (#13). Последовательность символов от текущего положения курсора до следующего разделителя преобразовывается в число, соответствующее типу переменной. Если такое преобразование невозможно, произойдет сбой.
Например, пусть первая строка текстового файла равна: '3.1415926 -38 Х'. Объявим переменные:
var IntValue: integer;
RealValue: real;
CharValue: char;
И введем значения этих переменных из файла: Reset (F); Readln (F, RealValue, IntValue, CharValue); Writeln (RealValue,' ', IntValue,' ', CharValue);
На экран выведется следующее: 3.1415926000Е+00 -38 Х
При вводе символа никакие разделители не учитываются: в переменную запишется символ, на котором стоит файловый курсор, даже если это символ конца строки. Строковое значение вводится с позиции курсора. Считывание продолжается, пока количество символов не станет большим объявленного для строковой переменной или пока не встретится символ конца строки. Сам символ конца строки в переменную не записывается.
Например, пусть первая строка текстового файла такова: 'На всякого мудреца довольно простоты.'. Объявляем переменные: var str1: string; str2: string[18];
Фрагмент программы последовательно вводит содержимое этой строки в обе переменные: Reset (F); Readln (F, str1); Reset (F); Readln (F, str2);
После выполнения этих операторов строка str1 будет равна 'На всякого мудреца довольно простоты.' (ввод, пока не закончилась строка файла), а str2 - 'На всякого мудреца' (ввод, пока позволяет длина строковой переменной).
Как видно, Read и Readln применительно к текстовому файлу данных и консоли (вводу с клавиатуры) действуют практически одинаково. Рассмотрим пример: дан текстовый файл, содержащий программу на некотором языке, в котором один оператор занимает строго одну строку. Проверить ее на правильность скобочных конструкций.
program Example; 
 
var Prg : text; {обрабатываемый файл}
FileName : string;
Opened, Closed : byte; {количество левых и правых скобок}
StrNum: word; {номер строки} ch : char;
ErrCode: integer;
 
begin
writeln('Программа решает следующую задачу:');
writeln('Дан текстовый файл, содержащий программу на ');
writeln('некотором языке, в котором один оператор занимает');
writeln('строго одну строку. Проверить ее на правильность');
writeln('скобочных конструкций.');
writeln;
repeat write('Введите имя проверяемого файла ');
readln(FileName);
Assign(Prg, FileName);
{$I-}
Reset(Prg);
{$I+}
ErrCode:= IOResult; if ErrCode<>0 then writeln('Ошибка: невозможно открыть файл. Повторите ввод.');
    until ErrCode=0;
writeln('Проверяется файл "',FileName,'" :');
StrNum:=1;
   while not EOF(Prg) do
   begin
Opened:=0; Closed:=0; {считаем левые и правые скобки} 
while not EOLN(Prg) do
  begin read(Prg, ch);
if ch='(' then Opened:=Opened+1 else if ch=')' then Closed:=Closed+1;
  end;
{проверяем их соответствие} if Opened>Closed then writeln('В [',StrNum,'] строке не хватает ',Opened-Closed, ' закрывающих скобок') else if Closed>Opened then writeln('В [',StrNum,'] строке не хватает ',Closed-Opened, ' открывающих скобок');
readln(Prg); {переходим на следующую строку}
StrNum:=StrNum+1; end; Close(Prg);
end.

Операторы Write (F, x1, x2, ...) и Writeln (F, x1, x2, ...) записывают в файл F значения переменных x1, x2, ... точно так же, как это делают операторы Write и Writeln для консольного вывода. Аргументы могут иметь любой арифметический тип и для них действуют те же правила форматирования: Write(F, x:n) - для строчных и символьных значений (n - число позиций, отводимых под значение); Write(F, x:n) - для целых значений (n - число цифр); Write(F, x:n:m) - для вещественных в обычной форме (n - общее количество позиций, m - число знаков после запятой); Write(F, x:n) - для вещественных в экспоненциальной форме (n - длина поля вывода, n>=8).
При n>0 выравнивание производится по правому краю, при n< 0 - по левому. Задача: дан массив, содержащий температуру воздуха для каждого часа на каждый день месяца. Вывести эти значения в текстовый файл в виде таблицы, при этом добавить колонку средних температур.
program Example;
 
const MONTHNAME='Июнь';
MINTEMPER=15;
MAXTEMPER=36;
var OutFile: Text; {файл таблицы}
FileName: string;
Temper : array [1..31,1..24] of byte; {массив температур} i, j,
Sum: integer;
procedure CountArray;
var i, j: integer;
 
begin
for i:=1 to 31 do for j:=1 to 24 do
Temper[i,j] := Random(MAXTEMPER-MINTEMPER)+MINTEMPER;
end;
begin {Заполняем массив температур} CountArray;
write('Введите имя файла для вывода таблицы ');
readln(FileName);
Assign(OutFile, FileName);
Rewrite(OutFile); {Шапка таблицы}
writeln(OutFile,'Температура воздуха на месяц: ',MONTHNAME);
for i:=1 to 26 do
write(OutFile,'+---');
writeln(OutFile,'+');
write(OutFile,'| |Ср.|');
for i:=1 to 24 do write(OutFile,i:3,'|');
writeln(OutFile);
for i:=1 to 26 do write(OutFile,'+---');
writeln(OutFile,'+'); {Вывод значений температуры}
for j:=1 to 31 do begin {Сумма дневных температур для вычисления средней}
Sum:=0;
for i:=1 to 24 do Sum:=Sum+Temper[j,i]; {Выводим среднюю и почасовые температуры}
write(OutFile,'|',j:3,'|');
if Sum>0 then write(OutFile, '+',(Sum div 24):2,'|') else write(OutFile, (Sum div 24):3,'|');
for i:=1 to 24 do
if Temper[j,i]>0 then write(OutFile,'+',Temper[j,i]:2,'|') else write(OutFile,Temper[j,i]:3,'|'); writeln(OutFile);
end;
{Закрываем таблицу и файл}
for i:=1 to 26 do
write(OutFile,'+---');
writeln(OutFile,'+');
Close(OutFile);
end.

В случае, когда массив температур заполняется случайными числами, мы получим в файле результат вроде этого (приведена левая часть таблицы): Температура воздуха на месяц: Июнь +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |Ср.| 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| 11| 12| 13| 14| 15| 16| 17| 18| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1|+23|+15|+15|+33|+19|+20|+29|+21|+18|+22|+23|+16|+24|+16|+32|+16|+21|+34|+22| | 2|+24|+18|+21|+24|+20|+32|+20|+25|+18|+33|+21|+31|+35|+25|+33|+32|+15|+17|+18| | 3|+27|+31|+29|+26|+19|+29|+27|+35|+28|+35|+20|+29|+21|+16|+31|+25|+33|+25|+27| ... | 30|+22|+30|+34|+20|+18|+17|+35|+17|+22|+18|+23|+25|+17|+22|+21|+20|+16|+21|+32| | 31|+23|+16|+31|+23|+15|+29|+25|+24|+30|+15|+17|+20|+26|+31|+28|+20|+32|+25|+24| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

Конечно, такая таблица никоим образом не соответствует реальному распределению температур, но мы стремились только проиллюстрировать табличный вывод данных.
Еще одна дополнительная особенность текстовых файлов в Паскале - возможность их дозаписи. При этом файл открывается директивой Append вместо Rewrite. Например, после выполнения фрагмента: Rewrite (F); writeln (F, 'Куда идем мы с Пятачком -'); Close (F); Append (F); writeln (F, 'Большой-большой секрет.'); Close (F);

Содержимое файла F будет иметь вид: Куда идем мы с Пятачком - Большой-большой секрет.
Ввод-вывод для бестиповых файлов
Буфер - это область памяти, которая выделяется для каждого файла при его открытии.

Мы уже говорили, что информация, записываемая в файл, вначале оказывается в буфере оперативной памяти, а в файл перебрасывается, только когда весь буфер заполнен. Точно так же и при чтении из файла сразу считывается столько байт, каков размер буфера. Дальнейший запрос данных будет обращен именно к буферу.
Например, если буфер вмещает 64 байта, а программа запрашивает из файла 4, то в буфер считается 64 байта из файла, затем 4 первых из них будет передано программе. Остальные либо передадутся по запросу позже, либо будут потеряны, если чтение этого участка файла прекратится.
Стандартные операторы Reset(F) и Rewrite(F) при открытии бестипового файла устанавливают размер буфера в 128 байт. Но можно задать этот размер программно при помощи операторов Reset (var F: File; BufSize: Word) и Rewrite (var F: File; BufSize: Word). Чем больше размер буфера, тем меньше будет при выполнении программы обращений к диску и значит, тем быстрее будет выполняться программа, поскольку обращение к диску производится гораздо медленнее, чем к памяти.
Однако, если мы считываем информацию небольшими блоками и не последовательно, а напрямую, такое увеличение буфера может даже снизить производительность.
Минимально возможный размер считываемого или записываемого блока - 1 байт, максимальный - 64 килобайта.
Поскольку в бестиповых файлах речь может идти только о работе с отдельными байтами, то считывание и запись информации также производится блоками по несколько байт.
Оператор BlockRead (var F: File; var Where; Count: Word [; var ReadIn: Word]) считывает из бестипового файла F Count емкостей буфера (размер буфера объявлен при открытии файла) и помещает всю считанную информацию в память начиная с адреса Where.
Параметр ReadIn необязателен, в него записывается целое число блоков (буферов), которое действительно считалось в процессе операции. ReadIn удобно использовать для отслеживания ошибок чтения.
Count*Размер_буфера не может превышать 64 килобайта. Это обязательное условие для DOS-программы.
Для записи информации в бестиповой файл используется оператор BlockWrite (var F: File; var Where; Count: Word [; var WriteOut: Word]). Здесь параметр Where указывает на адрес начала буфера в оперативной памяти, откуда берется информация, а необязательный аргумент WriteOut после окончания операции будет содержать действительное количество записанных в файл блоков.
Например:
 
var {объявляем буфер в виде массива в памяти}
Buf : Array [1..10] of integer;
F : File;
Value : integer; ... Reset(F,16); {открываем бестиповый файл, размер буфера 16 байт} {чтение из файла 32 байт (2*16) и запись по адресу 1 элемента массива}
BlockRead (F,Buf,2); {чтение из файла 64 байт и запись по адресу 5 элемента массива} BlockRead (F,Buf[5],4); - чтение в Х 64 байт начиная с 5 элемента
Рассмотрим задачу: требуется побайтово скопировать один произвольный файл в другой. Программу для решения этой задачи можно составить так:
program Example;
 
var SrcFile,
DestFile: File; {файл-источник и файл-приемник} NumRead, NumWrite: Word; {количество обработанных блоков}
Buf: array [1..1024] of Char; {буфер можно задать и так}
 
begin {проверяем количество параметров командной строки}
if ParamCount< 3 then begin writeln('Ошибка: недостаточно параметров командной строки.'); exit;
end;
{открываем оба файла}
Assign(SrcFile, ParamStr(2));
{$I-}
Reset(SrcFile, 1); {размер буфера 1 байт}
{$I+}
if IOResult<>0 then
begin
writeln('Ошибка: невозможно открыть исходный файл.');
exit;
end;
Assign(DestFile, Param Str(3));
Rewrite(DestFile, 1); {размер буфера тоже 1 байт} {
копируем}
Writeln('Копирую файл ',ParamStr(2),' в ', FileSize(SrcFile), ' байт...');
repeat BlockRead(SrcFile, Buf, SizeOf(Buf), NumRead);
BlockWrite(DestFile, Buf, NumRead, NumWrite);
until (NumRead = 0) or (NumWrite <> NumRead);
writeln('Копирование завершено.');
Close(SrcFile);
Close(DestFile);
end.
4. Прямой доступ к файлу данных

Мы уже говорили, что самый естественный доступ к файлу - последовательный, он лучше всего соответствует линейной структуре файла. Если задача требует прямого доступа к компонентам файла, мы можем запрограммировать такой доступ "в лоб" или воспользоваться стандартными процедурами и функциями Паскаля.
Например, пусть файл F - типизированный: Reset(F); {сейчас мы можем считать 1-й компонент файла} for i:=1 to 20 do Read (F, v); {в переменной v значение 20-го компонента файла} Reset(F); {а сейчас мы снова готовы читать 1-й компонент}
Процедуры и функции прямого доступа применяются только к компонентным и бестиповым файлам, но не к текстовым.

Функция FileSize(var F) : LongInt возвращает число записей в открытом файле F. Записями будут считаться компоненты типизированного файла или блоки размером с буфер бестипового. Причем для нетипизированного файла функция возвращает число полных блоков: оставшийся неполный "хвост" не учитывается.
Функция FilePos(var F) : LongInt возвращает номер последнего считанного компонента или блока. Для начала файла функция вернет 0, для состояния конца файла - номер последнего компонента (блока).
Процедура Seek(var F; N: LongInt) устанавливает файловый курсор на компонент или блок с номером N, считая нулевым блоком начало файла. N-й компонент будет считан или записан следующей операцией ввода-вывода. Файл при этом должен быть открыт.
Для демонстрации прямого доступа давайте рассмотрим следующий пример:
 
const FILENAME = 'example.dat';
var F: File of char;
ch: char;
i: integer;
begin
writeln('Работаем с файлом "example.dat"');
Assign(F, FILENAME);
Rewrite(F);
writeln('Текущий размер файла = ', FileSize(F));
writeln('Текущая позиция в файле = ', FilePos(F));
for i:=1 to 20 do
begin
ch:=chr(ord('0')+random(10));
Write(F,ch);
end;
writeln('Теперь размер файла - ', FileSize(F));
writeln('И текущая позиция в нем - ', FilePos(F));
Seek(F, FileSize(F) div 2);
writeln('Теперь текущая позиция = ', FilePos(F));
ch:='У';
Write(F,ch);
ch:='Р';
Write(F,ch);
ch:='А';
Write(F,ch);
end.

Вывод на экран при выполнении этой программы будет следующим: Работаем с файлом "example.dat" Текущий размер файла = 0 Текущая позиция в файле = 0 Теперь размер файла - 20 И текущая позиция в нем - 20 Теперь текущая позиция = 10

А в файл будет записана информация вроде этой (цифры могут меняться, но положение "УРА" в файле останется таким же): 0082263134УРА8029373

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

Библиотеку можно заполнять и вручную, но это долго и неудобно. Давайте вначале создадим модуль создания файла "базы данных" библиотеки, для которого книги будут генерироваться случайным образом из заданных списков значений.
 
program CreateLibrary; 
 
const
{константы для жанров}
KINDSNUM = 6;
KINDMAX = 10;
CKinds : array [1..KINDSNUM] of string[KINDMAX] = ('Проза','Учебная','Детская','Фантастика','Классика','Поэзия');
{константы для авторов} AUTHORSNUM = 17;
AUTHORMAX = 15;
CAuthors : array [1..AUTHORSNUM] of string[AUTHORMAX] = ('А.С.Пушкин','Н.В.Гоголь','Ф.М.Достоевский','В.Астафьев','С.Лем', 'А.П.Чехов','Ф.Купер','А.Дюма','С.Лукьяненко','О.Дивов','В.Лукин', 'Т.Сван','Б.Страуструп','У.Гибсон','Н.Стивенсон','Р.Рюкер','Н.Носов');
{константы для названий}
NAMESNUM = 34;
NAMEMAX = 40;
CNames : array [1..NAMESNUM] of string[NAMEMAX] = ('Руслан и Людмила','Сказка о рыбаке и рыбке','Мертвые души', 'Вечера на хуторе близ Диканьки','Преступление и наказание', 'Братья Карамазовы','Идиот','Царь-рыба','Рассказы','Жалобная книга', 'Лошадиная фамилия','Зверобой','Следопыт','Три мушкетера','Две Дианы', 'Императоры иллюзий','Фальшивые зеркала','Спектр','Выбраковка', 'К-10','Рыцари кувалды','Миссионеры','Программирование в Delphi', 'Программирование в Borland Builder C++','Язык С++ для профессионалов', 'Джонни-мнемоник','Нейромант','Все вечеринки завтрашнего дня', 'Лавина','Алмазный век','Белый свет','Приключения Незнайки', 'Веселая семейка','Мишкина каша');
{константы для издательств}
PUBLICSNUM = 9;
PUBLICMAX = 15;
CPublics : array [1..PUBLICSNUM] of string[PUBLICMAX] = ('АСТ','Дрофа','Амфора','Альфа-книга','Радио и связь','Питер','Наука', 'Эксмо','Медицина');
type TBook = record Kind: string[KINDMAX];
Author: string[AUTHORMAX];
Name: string[NAMEMAX];
Public: string[PUBLICMAX];
Year: word;
end;
 
var Library: File of TBook;
FileName: string; book: TBook;
num, tmp, minyear, maxyear, i: word;
ask: char;
 
begin
{вводим диапазоны}
write('Введите количество книг в библиотеке: ');
readln(num);
write('Введите минимальный год издания: ');
readln(minyear);
write('Введите максимальный год издания: ');
readln(maxyear); {минимум должен быть меньше максимума}
if minyear>maxyear then
   begin
tmp:=minyear;
minyear:=maxyear;
maxyear:=tmp;
end; write('Введите имя файла библиотеки: ');
readln(FileName);
Assign(Library, FileName);
Rewrite(Library);
for i:=1 to num do
   begin
{генерируем книгу из случайных полей}
tmp:=random(KINDSNUM)+1;
book.Kind := CKinds[tmp];
tmp:=random(AUTHORSNUM)+1;
book.Author := CAuthors[tmp];
tmp:=random(NAMESNUM)+1;
book.Name := CNames[tmp];
tmp:=random(PUBLICSNUM)+1;
book.Public := CPublics[tmp];
book.Year := random(maxyear-minyear)+minyear; {записываем получившуюся книгу в файл} Write(Library, book);
   end;
Close(Library);
writeln('Библиотека создана');
write('Хотите ли вы просмотреть получившуюся библиотеку? (д/н)');
readln(ask);
if (ask='y')or(ask='Y')or(ask='д')or(ask='Д') then
  begin
Reset(Library);
writeln;
writeln('КАТАЛОГ БИБЛИОТЕКИ');
while not EOF(Library) do
  begin
read(Library, book);
writeln(book.Kind:KINDMAX,'|',book.Author:AUTHORMAX,'|', book.Name:NAMEMAX,'|',book.Public:PUBLICMAX,'|', book.Year:4);
  end;
  end;
end.

Теперь сама программа подсчета:
 
program Library1;
 
const
{Максимальные размерности полей}
KINDMAX = 10;
AUTHORMAX = 15;
NAMEMAX = 40;
PUBLICMAX = 15;
type TBook = record Kind: string[KINDMAX];
Author: string[AUTHORMAX];
Name: string[NAMEMAX];
Public: string[PUBLICMAX];
Year: word;
end;
 
var
Library: File of TBook;
FileName: string;
author: string[AUTHORMAX];
book: TBook;
num, ErrCode: word;
 
begin
{открываем библиотеку}
repeat write('Введите имя файла библиотеки: ');
readln(FileName);
Assign(Library, FileName);
{$I-}
Reset(Library);
{$I+}
ErrCode:=IOResult;
if ErrCode<>0 then writeln('Ошибка: невозможно открыть файл. Повторите ввод.');
until ErrCode=0;
write('Введите имя автора: ');
readln(author);
{подсчитываем книги этого автора}
num:=0;
while not EOF(Library) do
   begin
Read(Library, book);
if book.Author=author then num:=num+1;
   end;
Close(Library);
writeln('Количество книг автора ',author,' в библиотеке = ',num,' шт.');
end.
Как видите, мы последовательно перебираем все книги библиотеки и, когда находим подходящую, увеличиваем счетчик.
В настоящих базах данных записи организованы так, что мы можем получать доступ к ним практически напрямую. Но для этого используются отдельные файлы индексов, хранящие значения ключевых полей и ссылки на позицию данной записи в основном файле базы.


Источник: http://lectureroom.net/315.html
Категория: Программирование на языке Pascal | Добавил: YourAmur (16.11.2010)
Просмотров: 29552 | Рейтинг: 3.4/21
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]