Функция IOResult : Integer возвращает целое число, соответствующее коду последней ошибки ввода-вывода (см. табл. 12.6). Если же операция ввода-вывода прошла без сбоев, то функция вернет значение 0.
Опросить функцию IOResult можно только один раз после каждой операции ввода или вывода, ибо она обнуляет свое значение при каждом вызове. Обычно это обходится запоминанием значения функции в какой-либо переменной. При режиме компиляции операций ввода и (или) вывода {$I+} функция не имеет смысла.
Возможность управлять режимом обработки ошибок и наличие функции IOResult позволяют писать программы, никогда не дающие сбоев при вводе или выводе данных и при работе с каталогами и файлами.
Примеры обработки ошибок ввода-вывода
Рассмотрим несколько практических примеров (везде далее f — файловая переменная). {262}
1. Обработка отсутствия файла с данными. Если файл отсутствует, то действие процедуры открытия Reset вызовет ошибку (рис. 12.10 ).
Assign( f, 'NoFile.TXT' );
{$I-} { выключение проверки ввода-вывода }
Reset( f ); { попытка открыть файл f }
{$I+} { восстановление проверки }
if IOResult<>0 { Если файл не может быть открыт, }
then { то дать сообщение: }
WriteLn( 'Файл не найден или не читается' )
else begin { Иначе (код равен 0) все хорошо }
Read( f, ... ); { и можно нормально работать с }
... { файлом f... }
Close(f)
end; {else и if}
Рис. 12.10
В случае неудачи при открытии файла к нему не надо применять процедуру закрытия Close.
По тому же принципу можно построить функцию анализа существования файла (рис. 12.11).
FUNCTION FileExists( FileName : String ) : Boolean;
VAR
f : File; { тип файла не важен }
BEGIN
Assign( f, FileName ); { связывание файла f }
{$I-} Reset( f ); {$I+} { открытие без контроля }
if IOResult=0 { Если файл существует, }
then begin { то его надо закрыть }
Close{ f );
FileExists := True end {then}
else { иначе просто дать знать}
FileExists := False;
END;
Рис. 12.11
2. Выбор режима дозаписи в текстовый файл или его создания. Механизм остается тот же (рис. 12.12). Здесь f — текст-файловая переменная. {263}
Assign(f,'XFile.TXT'); {связывание файла f }
{$I-} Append( f ); {$I+} {попытка открыть его для дозаписи}
if IOResult<>0 {Если файл не может быть открыт, }
then Rewrite( f ); {то создать его. }
...
Write( f, ...); { нормальная работа с файлом }
...
Close( f );
Рис. 12.12
3. Переход в заданный каталог или его создание, если переход возможен (рис. 12.13, S — строковая переменная).
S := 'C:\NEWDIR'; { задано имя каталога }
{$I-} ChDir( S ); {$I+} { попытка перейти в него }
if IOResult<>0 { Если не получается, }
then begin
MkDir( S ); {то сначала создать его, }
ChDir( S ) { а уж потом перейти. }
end; {if}
{ Подразумевается, что каталог S в принципе создаваем. }
Рис. 12.13
4. Построение «умных» ждущих процедур чтения данных с клавиатуры. Такие процедуры не будут реагировать на данные не своего формата (рис. 12.14).
USES { Здесь используется ряд процедур из библиотеки }
CRT; { модуля CRT. Они отмечены * в комментариях. }
{Процедура считывает с клавиатуры значение типа Integer, помещая его в переменную V. При этом игнорируется любой ввод, не соответствующий этому типу. X и Y — координаты текста запроса Comment. Проверка корректности значений X и Y не производится. }
PROCEDURE ReadInteger( X,Y : Byte; Comment : String;
VAR V : Integer );
Рис. 12.14 {264}
CONST
zone =12; { ширина окна зоны ввода числа }
VAR
WN.WX : Word; {переменные для хранения размеров окна }
BEGIN
WN:=WindMin; WX:=WindMax; {Сохранение текущего окна }
{$I-} { отключение режима проверки }
GotoXY( X,Y ); {*перевод курсора в X,Y }
Write( Comment ); { печать комментария ввода }
Inc(X, Length(Comment)); { увеличение координаты X }
Window( X,Y, X+zone,Y ); {*определение окна на экране }
Repeat { Главный цикл ввода числа: }
ClrScr; {* очистка окна ввода, }
ReadLn( V ); { считывание значения при $I- }
until (IOResult=0); { пока не введено целое }
{$I+} { включение режима проверки }
{*восстановление окна: }
Window( Lo(WN)+1, Hi(WN)+1, Lo(WX)+1, Hi(WX)+1 )
END; {proc}
VAR i : Integer; { === ПРИМЕР ВЫЗОВА ПРОЦЕДУРЫ === }
BEGIN
ClrScr; {* очистка экрана }
ReadInteger(10,10,'Введите целое число: ',i); { вызов }
WriteLn; WriteLn( 'Введено i=', i ); { контроль }
ReadLn { пауза до нажатия ввода}
END.
Рис 12.14 (окончание)
В примере можно попутно устроить проверку диапазона значений V, переписав условие окончания цикла в виде
until (IOResult=0) and (V<Vmax) and (V>Vmin);
где Vmax и Vmin — границы воспринимаемых значений V. Аналогичным способом, меняя лишь типы переменной V, можно определить процедуры ReadByte, ReadWord, ReadReal и т.п. Справедливости ради надо отметить, что хотя описанная процедура ReadInteger спокойно относится к попыткам впихнуть в нее буквы, дроби и прочие неподходящие символы, она чувствительна к превышению диапазона значений типа Integer во входном числе и не обрабатывает его.
5. Работа с текстовыми файлами данных произвольного формата. Пусть существует файл из N столбцов цифр, содержащий в некоторых строках словесные комментарии вме-{265}сто числовых значений. На рис. 12.15 показано, как можно прочитать из файла все цифровые данные, игнорируя строки-комментарии, текстовые строки или строки пробелов (а так же пустые).
CONST N=3; { пусть в файле данные даны в трех столбцах }
VAR
f : Text; { текст-файловая переменная }
i : Byte; { счетчик }
D : Array [1..N] of Real; { значения одной строки }
{ данных в формате Real }
BEGIN
Assign(f,'EXAMPLE.DAT'); { связывание файла f }
Reset( f ); { открытие файла для чтения }
{$I-} { отключение режима проверки }
while not SeekEOF(f) do { Цикл до конца файла: }
begin
Read( f, D[1] ); { попытка считать 1-е число }
if IOResult=0 { Если это удалось,то затем }
then begin { читаются остальные числа: }
for i:=2 to N do Read( f, D[i] );
{ и как-либо обрабатываются: }
WriteLn( D[1]:9:2, D[2]:9:2, D[3]:9:2 )
end; {if 10...}
ReadLn( f ) { переход на следующую строку }
end; {while} { конец основного цикла }
{$I+} { включение режима проверки }
Close( f ); { закрытие файла f }
ReadLn { пауза до нажатия ввода }
END.
Рис. 12.15
По тому же принципу можно построить обработку ошибок позиционирования при прямом доступе в файлы и прочих задач, связанных с вводом-выводом.
Обращаем внимание на то, что во всех примерах подразумевается общий режим компиляции {$I+}, который в них всегда восстанавливается после завершения операции ввода-вывода. Советуем компилировать программы и модули в режиме {$I+}, используя его отключение только там, где действительно нужно обработать ошибку.