#12 FILE=филе :)

Вступление

Здраствуйте, дорогие мои!
Это я Аншлага насмотрелся на всю жизнь вперёд :) На сайте произошли некоторые радостные изменения, о которых можно почитать ниже. Так же сегодня мы, как и было заявленно, начинаем проходить файлы. Тема это объёмная и займёт как минимум 2 рассылки, а то и больше.

Теория

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

0 байт 1 байт 2 байт 3 байт 4 байт 5 байт 6 байт 7 байт 8 байт ....

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

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

  1. открытие файла
  2. чтение/запись/ничего не делание :)
  3. закрытие файла
Думаю вам привычна работа с именем файла. Копируя, удаляя или делая ещё что-то вы указываете имя. В Паскале работа с файлом ведётся с помощью файловых переменных. Эти переменные связываются с файлом (с помощью имени) и потом используются только они.

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

file of тип
Например нам нужно описать файл с целыми числами. Тогда мы создадим такой тип:
type
   tIntFile = file of integer;
   tByteFile = file of byte;
второй тип - это тип для файла из байтов. Создав тип мы соответственно можем создать и файловую переменную:
var
f : tByteFile;
Файловые переменные в корне отличаются от привычных нам. С ними нельзя ничего делать (присваивать значения, сравнивать и т.п.) Файловые переменные используются только в специальных процедурах и функциях для работы с файлами.

Итак файловая переменная у нас есть. Теперь нам нужно связать её с файлом. Делается с помощью процедуры:

procedure Assign(var f; String);
Первый параметр - это и есть наша файловая переменная. Второй - это имя файла. Имя файла должно соответствовать требованиям операционной системы. Задаётся оно в виде DRIVE:\path\...\name.expansion Здесь (например для файла на диске С в папке bp с именем readme.txt):
  • Drive - диск (буквы от A до Z) (у нас это С)
  • path - путь (у нас это bp)
  • name - имя файла (у нас - readme)
  • expansion - расширение (у нас - txt)
поэтому получается такое имя файла - C:\bp\readme.txt

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

  • procedure Reset(var F [: File; Recsize: Word ] ); - пока не обращайте внимание на то что находится в квадратных скобках. Для нас процедура выглядит так: procedure Reset (var f);
    Открывает файл, связанный с файловой переменной f, для чтения. Если такого файла не существует (не можем же мы читать не существующий файл !) , то возникает ошибка ввода-вывода. Об этой ошибке мы поговорим ниже :)
  • procedure Rewrite(var F: File [; Recsize: Word ] ); - так же не обращаем внимание на указанное в квадратных скобках.
    Эта процедура открывает новый, пустой файл для записи, ему присваивается имя, которое мы указали в вызове Assign. Если файл с таким именем уже существует, то он уничтожается.
Перенесёмся сразу же на закрытие файла, перепрыгнув запись/чтение/ничего не делание :) Закрывается файл с помощью процедуры:
procedure Close (var F);
Мы обязаны закрыть файл, если мы в него чего-то писали, иначе ничего не сохранится. Вообще закрытие файла, после окончания работы с ним считается одним из правил "хорошего" программирования (с одним правилом вы уже знакомы - не использовать goto). Поэтому мы будем всегда закрывать файл, после работы с ним, даже если мы ничего в него не писали. Тем более, что после закрытия мы можем заново использовать файловую переменную.

Вернёмся к ошибке ввода-вывода. Функция:

function IOResult: Integer;
возвращает целое число, соответствуешее последней ошибке ввода-вывода (отсюда и название: Input-Output Result - результат ввода-вывода, англ.). Если ошибки нет, соответственно вернёт 0. При этом фунция работает только при выключенном режиме контроля ошибок. Например напишем такую программку:
program test;

type
 tfile = file of byte;

var
  f : tfile;

begin
  assign (f, 'C:\bla.bla'); {связываем f c файлом C:\bla.bla }
  reset (f);                         {пытаемся открыть его для чтения } 
  writeLn ('Bla-bla-bla!!!') { делаем что-то }
end.
Если у вас нет файла C:\bla.bla то соответственно программа завершится с ошибкой 2 : File not found (файл не найден). Однако такое развитие программы нас (по крайней мере меня) не устраивает. Что же это будет, если программа каждый раз будет завершаться с надписью run time error 2 ??? пользователи явно ничего не поймут. Поэтому нам нужно отключить контроль ошибок ввода-вывода. Итак лезем сюда Options -> Compiler...

В появившемся диалоге ищем I/O checking. Если крестик стоит - значит контроль есть, если не стоит - нету. Однако сносить крестик с радостным криком не стоит. Вполне возможно что вам впоследствии понадобится проверка ошибок ввода-вывода. Так что мы пойдём другим путём.

Помните, я говорил про штуки которые похожи на комментарии, но таковыми не являются ? Они начинаются с {$ и заканчиваются } похоже на комментарий. Это диррективы компилятору. Мы можем явно указать, где нужна проверка ошибок ввода-вывода, а где нет. Включение проверки ошибок делается с помощью директивы {$I+} выключение - {$I-}. Теперь перепишем нашу программку так:

program test;

type
 tfile = file of byte;

var
  f : tfile;

begin

  {$I-}
  assign (f, 'C:\bla.bla');
  reset (f);
  close (f);
  {$I+}

  if IOResult = 0 then
     writeLn ('bla-bla-bla')
  else
     writeLn ('file not found')
end.
Теперь можно написать функцию общего назначения, которая проверяет наличие файла, не отключая контроль ошибок для всей программы. При этом выполнение программы не завершится, несмотря на отсутствие файла.

Кстати интересная особенность - экран в ДОСе является тоже файлом (файлом ввода и вывода:) Поэтому указав вместо имени файла пустую строку мы получаем доступ к экрану (который у нас и так уже есть :)

Теперь переходим к самому интересному - чтению и записи из файла.

Для чтения из файла используется уже знакомая нам процедура Read:

procedure Read(F , V1 [, V2,...,Vn ] );
Первый параметр f - это файловая переменная, а V1 и т.д. - происходит считывание данных из файла в эти переменные. Переменные должны быть того же типа, что и файл. (т.е. если file of byte, то и Vn должно быть типа byte).

При чтении файла, нам нужно проверять не достигнут ли конец ? Концом файла является специальный символ, называемый EOF - EndOfFile (конец файла - англ.). Узнать достигнут ли конец файла, можно вызвав одноимённую функцию:

function Eof(var F): Boolean;
F - файловая переменная. Функция возвращает true, если конец файла достигнут и false в противном случае. Таким образом чтение файла нужно производить в цикле - прочитали что-то, проверили не конец ли. Если не конец, тогда прочитали дальше. Например напишем программу для чтения и вывода на экран текстового файла. Для простоты я указал имя C:\autoexec.bat если у вас нет такого файла, то укажите какой-либо другой:
Program readfile;

type
 tfile = file of char;

var
 f : tfile;
 c : char;

begin
  assign (f, 'C:\autoexec.bat');
  reset (f);

  while not EOF (f) do
  begin
    read (f, c);
    write (c)
  end;

  close (f)
end.
Думая, что вы уже догадываетесь, какую процедуру нужно использовать, для записи в файл. Правильно! write :)
procedure Write(F, V1 [, V2,...,Vn ] );
где F - это файловая переменная, а V1 ... Vn - это список переменных, значения которых мы должны записать в файл. Для примера напишем программу, которая копирует один файл в другой. Что нам для этого надо? Считать байт из одного файла - записать байт в другой и так до конца. Сохраним копию autoexec.bat в autoexec.txt:
Program copyfile;

type
 tfile = file of byte;

var
 fin, fout : tfile;
 c : byte;

begin
  assign (fin, 'C:\autoexec.bat');
  reset (fin);
  assign (fout, 'C:\autoexec.txt');
  rewrite (fout);

  while not EOF (fin) do
  begin
    read (fin, c);
    write (fout, c)
  end;

  close (fin);
  close (fout)
end.

Программа

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

type
tstudent = record
        name : string;
        result : byte
      end;
После этого создадим тип файла, состоящего из таких записей:
 tfile = file of tstudent;
после записи в файл мы ради эксперимента подсчитаем число отличников. Для этого нам надо закрыть файл (т.к. мы открыли файл на запись), а потом открыть его на чтение.
Program studentdata;

const
  N = 3;

type
 tstudent = record
        name : string;
        result : byte
      end;

 tfile = file of tstudent;

var
 f : tfile;
 i : integer;
 man : tstudent;

begin
  assign (f, 'E:\result.txt');
  rewrite (f);

  for i := 1 to N do
  begin
    write ('Введите имя: ');
    readLn (man.name);
    write ('Введите оценку: ');
    readLn (man.result);
    write (f, man)
  end;

  close (f);

  reset (f);
  i := 0;

  while not EOF (f) do
  begin
    read (f, man);
    if man.result = 5 then
       i := i + 1
  end;

  writeLn ('Число отличников: ', i);
  close (f)
end.
думаю, что текст программы довольно прозрачен.

Новости сайта.

Новостей сегодня будет много. Так, что детки :), садитесь в круг и слушайте:

  • Не радостная новость: [13.12.02] Из-за нехватки времени некоторые последние выпуск не попали в архив. Однако их всегда можно скачать отсюда: http://content.mail.ru/pages/p_8535.html
  • [13.12.02] Сменился дизигн гестбука. Теперь гостевуха представляет собой не траурную чёрную страницу, а вполне лаконично вписывается в убогий дизайн сайта :) И это надо сказать радует! Можете теперь высказывать всю правду о рассылке по этому аддресу :) /gb&mainhtml=gb.txt&messageshtml=sm.txt
  • [13.12.02] Ну и главная новость: на сайте появился патч к BP. Поводом к его появлению послужило такое письмо:
    цитата Александр 12.12.2002
    Получил только что очередной выпуск вашей рассылки (от 11 декабря) и увидел в нем строку "Error 202: Stack overflow error." Это напомнило мне об одной проблеме, которая преследует владельцев современных компьютеров при создании программ на Паскале. Это та самая ошибка 200, которая вылезает неизвестно почему в самый неподходящий момент.

    Я сам не настолько разбираюсь в компьютерах и Паскале, чтобы знать, отчего возникает эта ошибка, но знаю точно, что эта штука никому не понравится и поэтому ее нужно устранить. Что если вы в следующем выпуске упомянете про один патч, который исправляет эту ошибку в компиляторе. :) А то получится нехорошо - захотел человек написать программу, а тут откуда ни возьмись выскакивает эта самая "Error 200". Понятно, разбитой клавиатурой дело не кончится. :) Если что, высылаю вам этот патч аттачем.

    Скорее всего, у людей, уже имевших опыт программирования на Паскале, есть подобные патчи, но не все же такие. Можно разместить его у вас на сайте, например, в "Инструментах". Ну и, конечно же, нужно дать описание того, как с ней работать. Вот такие вот дела.

    Runtime error 200 (Division by zero) возникает из-за ошибки в процедуре delay. Даже, если эта процедура не вызывается, она возникает при инициализации CRT. Суть этой ошибки в том, что время измерялось через производительность процессора, которая сейчас очень увеличилась.

    Патч выложен по линку: http://www.ibp7.narod.ru/bp7p.zip (30 кб). Перед установкой обязательно почитайте файл t7tplfix.doc, там даны инструкции к инсталяции и FAQ (на аглийском). Если возникнут вопросы по поводу работы или установки патча - пишите.

Послесловие

на этом сегодня всё. В следующий раз мы продолжим изучать файлы.


[Назад] [Содержание] [Дальше]
Hosted by uCoz