Обработка ошибок в программе паскаль








English (en)



suomi (fi)





















Free Pascal supports exceptions. Exceptions are useful for error handling and avoiding resource leaks. However, bear in mind that exceptions have a performance impact.

The official documentation is here: Reference guide chapter 17.

By default exceptions are disabled. You can opt in by using the ObjFPC or DELPHI Compiler Mode, or adding -Sx to the compiler commandline. This enables the try, raise, except, and finally reserved words for use in your own code, but it doesn’t enable exception generation from the RTL. To enable the RTL to raise exceptions instead of generating runtime errors, use the SysUtils unit in your program.

The base Exception class is found in the SysUtils unit. All exceptions should preferably use this class or its descendants.

SysUtils automatically sets up a basic exception catcher, so any otherwise uncaught exception is displayed in human-readable form, as the program terminates. To generate readable callstacks from caught exceptions in your own code, without necessarily terminating the program, you can use SysUtils functions ExceptAddr, ExceptFrames, ExceptFrameCount, and BackTraceStrFunc.

Examples

Note that Pascal uses different keywords than some other languages: raise instead of throw, and except instead of catch. Also, as a compiler design choice, each try-block can pair with either an except or finally block, but not both at the same time. You’ll need to nest try-blocks if you need except and finally protecting the same code.

Error handling

uses sysutils;

begin
  try
    // Do something that might go wrong.
  except
    begin
      // Try to recover or show the user an error message.
    end;
  end;
end.

Cleaning up resources

try
  // Do something that might go wrong.
finally
  // Clean-up code that is always called even if an exception was raised.
end;

Exception leaking

Finally-blocks don’t destroy exception objects. Any exception that reaches the program’s «end.» will trigger a memory leak warning. An extra except block can be used to consume such exceptions. This is Delphi-compatible.

begin
  try
    // Your main program, where an exception object is created.
  finally
    try
      // Clean-up code.
    except
    end;
  end;
end.

Signalling problems

raise Exception.Create('Helpful description of what went wrong');

Using specialised exception types to signal different problems

type EMyLittleException = Class(Exception);

begin
  try
    raise EMyLittleException.Create('Foo');
  except
    on E : EMyLittleException do writeln(E.Message);
    on E : Exception do writeln('This is not my exception!');
    else writeln('This is not an Exception-descendant at all!');
  end;
end;

Re-raising exceptions

try
  // Do something that might go wrong.
except
  // Try to recover or show the user an error message.
  if recoveredSuccessfully = FALSE then
    raise;
end;

Exception classes

SysUtils defines and raises many specific exception classes.

With SysUtils included in your program, and exceptions enabled, various runtime errors are changed into exceptions. Processor-level interrupts like SIGSEGV or SIGFPU, which normally translate to run-time errors, are also changed to exceptions. For example, run-time error 200 (division by zero) becomes EDivByZero or EZeroDivide, while run-time error 216 (a general protection fault) becomes EAccessViolation.

Best practice

  • Raise exceptions to signal that an operation could not be completed, where it normally should have succeeded.
  • Do not use exceptions as part of expected control flow. Instead, add checks for common error conditions and return error values the old-fashioned way. For example, input parsing problems or file existence fails are usually not truly exceptional.
  • But do raise an exception if it’s critical that the error is noticed; programmers may forget to check for returned error values.
  • Naming convention: Prefix exception class names with a capital E.
  • Re-raise exceptions in except-blocks using raise; if you were unable to recover from the problem; this preserves the original exception’s callstack.
  • Be careful about using the catch-all base Exception class, since the underlying code or OS/filesystem might produce an unanticipated exception that slips through your exception handler, potentially corrupting the program state.
  • When writing a unit or library, if exceptions are used at all, they should be documented clearly up front. This way the unit user knows what to expect.
  • Keep exception handling away from code that needs to run as fast as possible.

Performance

Compiler optimisation levels don’t make much difference. All exception blocks use a small amount of wrapper code that can’t be optimised away. There are different ways for a compiler to produce exception code, with varying performance implications. As of FPC 3.0.4, the default exception mechanism uses the standard setjmp/longjmp style. FPC also supports OS-provided Structured Exception Handling; this is the default on Win64, and can be enabled on Win32 (recompile the compiler with $define TEST_WIN32_SEH). Other mechanisms may be added to FPC in the future.

To get a better feel for what’s going on, try writing a small test program and compiling it with the -a switch. This leaves behind a human-readable assembly file.

Notes on the setjmp/longjmp method:

  • Try-statements insert some extra address calculations, a stack push and pop, some direct jumps, and a conditional jump. This is the setup cost of an exception frame, even if no exception is raised.
  • Except-statements insert the above, plus a push and pop, more direct jumps, and another conditional jump.
  • Finally-statements add a pop and a conditional jump.
  • Raise-statements create a string and pass control to FPC’s exception raiser. The string creation itself can spawn an implicit exception frame for the function/method, due to dynamic string allocations, even if the raise is not executed. This is why in e.g. container types one often sees the raise moved to a separate local (private) procedure. This localises the exception frame to that procedure, so the performance hit is only taken if the procedure is called.

Furthermore, generating a human-readable callstack from a raised exception involves time-consuming stack traversal and string manipulation.

With all that said, outside of heavy processing code, the convenience of exceptions usually outweighs their performance impact. Don’t feel bad about using them if it makes your code better.

Further reading

Logging exceptions

Avoiding implicit try finally section

Exceptions vs Error codes — a situation where exceptions might be a danger in some code (see middle of this page)

Компилятор Паскаля – сложное приложение,
имеющее множество настроек. При написании
учебных программ большинство этих
настроек не имеют значения, но некоторые
из них окажутся нам полезны. Для управления
компилятором существует 2 основных
возможности: настройка режимов работы
с помощью верхнего меню OptionsоболочкиTurboPascalи настройка конкретной программы с
помощьюдиректив компилятора,
которую мы кратко рассмотрим. В общем
виде директива компилятора представляет
собой конструкцию вида{$X+}или{$X-},гдеX– латинская буква. Вариант со знаком
«+»
включает некоторый режим работы
компилятора (например, строгий контроль
программой соответствия типов данных,
вывод системных диагностических
сообщений и т.д.), а вариант со знаком
«-»
выключает его. Расположение директив,
в общем, произвольно, однако, директивы,
влияющие на всю программу, принято
располагать в самом начале файла с
исходным текстом. Фигурные скобки
комментария{
… }необходимы, чтобы скрыть
действие директив от старых версий
компилятора, которые не умели их
распознавать.

Подробную информацию о назначении всех
директив можно получить в справочной
системе оболочки. Основные директивы
компилятора также кратко описаны в
Приложении 2.

Наиболее полезной для нас выглядит
директива {$I-}/{$I+},
соответственно, выключающая и включающая
автоматический контроль программой
результатов операций ввода/вывода
(в/в). К операциям в/в относятся, в числе
прочего, ввод данных пользователем,
вывод строки на принтер, открытие файла
для получения или вывода данных и т.п.
Понятно, что даже несложная учебная
программа выглядит лучше, если она умеет
реагировать на неправильные действия
пользователя или возникающие ошибки
не просто выводом маловразумительного
системного сообщения на английском
языке, а доступным неискушенному
пользователю текстом. По умолчанию
контроль в/в включен и системные сообщения
об ошибках генерируются автоматически.
Все они кратко приведены в Приложении
3. Для замены системной диагностики
своей собственной следует, во-первых,
отключить директиву контроля оператором{$I-},
а во-вторых, сразу же после оператора,
который мог породить ошибку, проверить
значение, возвращаемое системной
функциейIoResult.
Эта функция возвращаетноль, если
последняя операция в/в прошлауспешно,
в противном случае возвращается ненулевое
значение. После завершения «критического»
оператора директиву следует включить
снова, чтобы не создавать потенциально
опасных ситуаций в коде, который будет
писаться далее! Приведем один пример,
написав «расширенную» программу
решения квадратного уравнения, корректно
реагирующую на возникающие ошибки:

uses printer;

var a,b,c,d,x1,x2:real;

begin

writeln;

writeln (‘Введите 3
вещественных числа:’);

{$I-} read
(a,b,c); {$I+}

if
IoResult<>0 then begin {Возникла
ошибка!}

Writeln (‘Вы не ввели
3 числа, это что-то другое!’);

Reset (input); {очищаем
буфер клавиатуры перед ожиданием

нажатия Enter}

Readln;

Halt; {а этим оператором
можно аварийно завершить программу!}

end;

d:=sqr(b)-4*a*c;

if d<0 then begin

Writeln (‘Ошибка при
вычислении корней — дискриминант<0’);

Reset
(input);

Readln;

Halt;

end;

x1:=(-b+sqrt(d))/(2*a);

x2:=(-b-sqrt(d))/(2*a);

{$I-}

writeln (lst,’x1=’,x1:8:2,’
x2=’,x2:8:2);

{$I+}

if IoResult<>0 then
Writeln (‘Не удалось вывести на принтер’)

else Writeln (‘Результаты
напечатаны’);

Reset (input); {Перед
Readln
очистили стандартный поток ввода}

Readln;

Halt;

end.

Специальной директивы для контроля
математических ошибок в Паскале не
предусмотрено, но это почти всегда можно
сделать обычными проверками корректности
данных. Обратите внимание на альтернативное
решение проблемы «двух readln»
в этом коде, а также на новый операторHaltи способ
контроля того, удалось ли вывести строку
на принтер.

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

Для работы с вещественными числами с
двойной точностью (тип double)
может также понадобиться указать перед
программой директиву{$N+},
позволяющую сгенерировать код для
аппаратной обработки таких чисел.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Если действия написанной программы вступают в противоречие с вычислительными возможностями ПЭВМ или требуют невозможного от ее ресурсов, то они, как правило, прерываются с выдачей сообщения типа «Runtime Error NNN». В таком случае мы говорим о фатальной ошибке времени счета (или во время счета). Такие ошибки могут возникать при запросах на размещение динамических переменных при малом объеме свободной памяти, попытках записи на переполненный диск, чтении с пустых дисководов, делениях на нуль или при нарушениях области допустимых значений аргументов функций Sqrt, Ln и т.д. Список этот можно продолжить. Общим для всех этих причин является то, что неизвестно, где и как они могут

— 308 —

возникнуть. Как правило, ошибки, не связанные с вводом-выводом, возникают из-за недосмотров в логическом построении программ. Причем хорошо, если все закончится выдачей текста «Runtime Error…», ведь программа может «зависнуть» так, что разблокировать ПЭВМ можно будет только полным перезапуском…

Все ошибки времени счета можно разделить на условно и безусловно фатальные. Условно фатальные ошибки — это те, которые могут блокироваться соответствующими режимами компиляции. Например, ошибка при проверке диапазонов с кодом 201 (Range Check Error) может появиться лишь, если программа откомпилирована в режиме {$R+}, ошибка 202 — переполнение стека (Stack Overflow) — в режиме {$S+}. К условно фатальным можно отнести все ошибки, связанные с вводом-выводом (коды ошибок 2-199), подробно рассмотренные в гл. 12. Отключение соответствующих режимов контроля ошибок вовсе не повышает безошибочность программ. Оно всего лишь загоняет «болезнь» программы внутрь и дает лишний повод усомниться в корректности выдаваемых программой ответов. Безусловно фатальные ошибки — это такие, которые нельзя ничем заблокировать. Сюда относятся все ошибки вычислений с плавающей точкой и некоторые другие.

Мы уже обсуждали способы обработки ошибок ввода-вывода и ошибок при распределении памяти (см. разд. 12.11, 11.5.6). Ниже будет рассмотрен способ обработки фатальных ошибок.

Турбо Паскаль дает возможность перехватить стандартную цепочку завершения программы и подставить свою собственную процедуру, которая будет выполнять любые действия вместо выдачи малоинформативного «Runtime Error…». Восстановить нормальную работу программы при возникновении фатальной ошибки уже нельзя, и после выполнения предписанных действий программа все равно прервется, но перед этим она сможет «нормальным» языком объяснить причину останова, восстановить видеорежимы, цвета, размеры курсора и т.п.

Механизм подстановки процедуры несложен. Вот его алгоритм:

1. Пишется процедура завершения программы. Это вполне обычная процедура. Требования к ней таковы: она не должна иметь параметров и должна быть откомпилирована в режиме {$F+}. Кроме того, в ней необходимо предпринять ряд действий по обработке ошибок и восстановлению системных адресов.

2. Объявляется переменная, например, с именем OldExitProc, имеющая тип Pointer.

— 309 —

3. В самом начале программы запоминается значение предопределенной системной переменной ExitProc — адрес стандартной процедуры завершения. Оно записывается в объявленную ранее переменную (у нас — в OldExitProc). А в ExitProc записывается значение адреса новой процедуры выхода.

4. Тело новой процедуры завершения должно начинаться с восстановления старого значения ExitProc. Далее, необходимо как бы обнулить системный указатель на фатальную ошибку (даже если ее не будет в действительности, это не повредит), записав в системную переменную ErrorAddr значение nil. Если этого не сделать, то после выполнения анализа ошибки и других действий на экран может вылезти уже не нужное «Runtime Error…».

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

Хотя по определению процедура выхода не содержит параметров, ей доступно значение кода ошибки и кодов завершения, посылаемых оператором Halt(N). Они хранятся в системной переменной ExitCode типа Integer. Совместно со значением адреса ErrorAddr переменная ExitCode определяет причину остановки программы (N — значение ExitCode):

ExitCode=0

ExitCode<>0

ErrorAddr= nil

Естественное завершение

Выход по Halt(N)

ErrorAddr<> nil

Не может быть

Ошибка N

Если программа была прервана пользователем с помощью комбинации клавиш Ctrl+Break, то в ExitCode запишется значение 255. Рассмотрим пример подстановки процедуры завершения (рис. 14.7).

Программа на рис. 14.7 всегда будет чистить за собой экран и заканчивать свою работу выдачей одного из предписанных сообщений. Если выполнимый код программы создан в режимах $R- и $I-, то варианты 2..199 и 201 могут никогда не сработать. Можно было использовать условную компиляцию для изъятия их из текста при необходимости.

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

— 310 —

| USES CRT; { используется модуль CRT }

| VAR

| OldExitProc : Pointer; { здесь запомнится ExitProc }

| {$F+} { режим компиляции $F+ }

| PROCEDURE NewExit; { новая процедура выхода }

| BEGIN

| ExitProc := OldExitProc; { восстановление пути выхода }

| TextAttr := White; { задание белого цвета (CRT) }

| ClrScr; { очистка всего экрана (CRT) }

| if ErrorAddr <> nil { Выход из-за ошибки? }

| then { Да. Обрабатывается ее код. }

| case ExitCode of

| 2..199:WriteLn(‘Ошибка ввода-вывода.’ );

| 200 :WriteLn(‘ДЕЛЕНИЕ НА 0.Проверьте входные данные’);

| 201 :WriteLn(‘Переполнение диапазона.’ );

| 203 :begin

| WriteLn(‘HE ХВАТАЕТ СВОбОДНОЙ ПАМЯТИ.’ );

| WriteLn(‘Освободите память и повторите запуск’);

| end;

| 205 :WriteLn(‘Переполнение в вещественном числе.’);

| 206 :WriteLn(‘Потеря порядка в вещественном числе.’ );

| else WriteLn (‘Ошибка ‘, ExitCode, ‘. ‘,

| ‘Смотрите описание TURBO PASCAL 5.5’)

| end {case и then}

| else (Нет. Нормальное завершение }

| case ExitCode of

| 255 :WriteLn(‘Программа прервана.’);

| else WriteLn(‘Код завершения программы : ‘, ExitCode )

| end; {case и else}

| ErrorAddr := nil; { обнуление адреса ошибки }

| END; {$F-} { можно вернуть режим $F- }

| { == ОСНОВНОЙ БЛОК ПРОГРАММЫ == }

| BEGIN

| OldExitProc:= ExitProc; {запоминается адрес ExitProc }

| ExitProc:= @NewExit; {назначается новая процедура }

| {…любые остальные действия программы… }

| END.

Рис. 14.7

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

— 311 —

Иногда удобно оформлять процедуры завершения как модули. Программу на рис. 14.7 очень легко переделать в модуль. Надо лишь вписать слова unit Имя, interface и implementation. Тогда при его подключении к основной программе инициализирующая часть настроит процедуру завершения еще до начала выполнения основной программы. Подключать такой модуль надо будет одним из первых в списке раздела USES.

Дополнительные функции работы с файлами

Модуль
System

ChDir

Выполняет
смену текущего каталога

MkDir

Создает
подкаталог

RmDir

Удаляет
подкаталог (пустой)

GetDir

Получить
текущий каталог на заданном диске

Модуль
DOS

DiskFree

Число
свободных байтов на диске

DiskSize

Размер
диска в байтах

GetFAttr

Получение
атрибутов неоткрытого файла

SetFAttr

Задание
атрибутов неоткрытого файла

FSplit

Получение
составных частей имени файла

FExpand

Формирование
полного имени файла

FSearch

Поиск
файла в списке католога

FindFirst

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

FindNext

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

Прототипы
процедур FindFirst
и FindNext
имеют вид:

FindFirst(Path:
String; Attrib: Word; Var SR: SearchRec);

FindNext(Var
SR:
SearchRec);

Для
работы с этими подпрограммами требуются
следующие предопределенные описания:

  1. Константы

Const

ReadOnly
= $01; только
для чтения

Hidden = $02; скрытый

SysFile =
$04; системный (непереносимый)

Volume ID = $08; метка
диска

Directory =
$10; подкаталог

Archive = $20; архивный

AnyFile = $3F; сумма
всех предыдущих

Эти атрибуты можно
складывать или вычитать (из anyfile).

  1. Переменная
    DOSError.
    Она используется для анализа ошибок
    MS
    DOS.
    Значение этой переменной, равное нулю,
    соответствует отсутствию ошибки. Смысл
    некоторых ненулевых кодов следующий:

2
– файл не найден

3
– маршрут не найден

5
– доступ к файлу запрещен

6
– неправильная обработка

8
– недостаточно памяти

10
– неверные установки значений параметров
среды

11
– неправильный формат

18
– файлов нет

При
работе процедуры FindFirst
возможны ошибки с номерами 2 и 18, а при
работе FindNext
– только 18.

3)
Тип

Type

SearchRec
= record

Fill: array[1..21] of byte; {системное поле}

attr: byte; {байт атрибутов}

time: longint; {время
создания}

size: longint; {размер
файла}

name: string[12]; {имя файла}

end;

Поля
переменной этого типа содержат информацию
о последнем файле, найденном с помощью
FindFirst
и FindNext.

Процедура
FindFirst
при заданных имени файла и атрибутах
должна вызываться лишь один раз. Она
записывает в поля переданной ей переменной
SR
типа SearchRec
информацию о первом найденном файле,
удовлетворяющем заданным условиям. Эта
информация в дальнейшем будет
использоваться процедурой FindNext,
которая всегда вызывается после FindFirst
и заполняет поля переменной SR
информацией о следующем найденном
файле.

Контроль
работы этих процедур ведется с помощью
переменной DOSError:
если файла нет, то DOSError<>0.

Обработка ошибок ввода-вывода

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

Runtime
error
<номер> <смещение>.

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

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

случае ошибки) и последующего анализа
этих флагов. Для перехвата ошибок
ввода-вывода на Паскале необходимо:

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

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

Пример:

reset(f);

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

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

вы должны
расположить две директивы компилятора:

{$I-}
— указывает на то, что необходимо
отключить системный контроль ошибок

reset(f);

{$I+}
— указывает на то, что необходимо включить
системный контроль ошибок

При наличии
директивы компилятора {$I-}
даже при наличии ошибок в/в программа
аварийно не завершается и ход выполнения
программы не нарушается.

Замечание:
По умолчанию действует директива {$i+}.

Контроль ошибок
в/в производится с помощью функции
IOResult
. Если ошибка имеет место, то значение
этой функции отлично от нуля. Поэтому
типичный шаблон обработки ошибки
ввода-вывода имеет вид:

If
IOResult
<> 0 Then
{обработка ошибки};

Обычно обработка
ошибки сводится к сообщению об ошибки
и завершению вашей программы (с помощью
процедуры HALT
или EXIT).

Замечание:
Опросить функцию IOResult
можно лишь
один раз

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

Пример.

{$I-}

reset(f);

{$I+}

If
IOResult <> 0

then
begin

Writeln(‘Ошибка
открытия
файла’);

Halt;

end;

Соседние файлы в папке WORD

  • #

    15.04.2015439.06 Кб286.docx

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Рассмотрим несколько практических примеров (везде далее f — файловая переменная).

— 263 —

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 — текст-файловая переменная.

— 264 —

| 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).

| { Здесь используется ряд процедур из библиотеки }

| CRT; { модуля CRT. Они отмечены * в комментариях. }

{Процедура считывает с клавиатуры значение типа Integer, помещая его в переменную V. При этом игнорируется любой ввод, не соответствующий этому типу. X и Y — координаты текста запроса Comment. Проверка корректности значений X и Y не производится. }

PROCEDURE ReadInteger( X,Y : Byte; Comment : String;

| VAR V : Integer );

Рис. 12.14

— 265 —

| 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 столбцов цифр, содержащий в некоторых строках словесные комментарии вме-

— 266 —

сто числовых значений. На рис. 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+}, используя его отключение только там, где действительно нужно обработать ошибку.

7.4 Обработка текстовых файлов в языке Free Pascal

При работе с текстовыми файлами следует учесть следующее:

  1. Действие процедур reset, rewrite, close, rename, erase и функции eof аналогично их действию при работе с компонентными (типизированными) файлами.
  2. Процедуры seek, trunсate и функция filepos не работают с текстовыми файлами.
  3. Можно пользоваться процедурой открытия текстового файла append(f), где f — имя файловой переменной. Эта процедура служит для открытия файла в режиме дозаписи в конец файла. Она применима только к уже физически существующим файлам, открывает и готовит их для добавления информации в конец файла.
  4. Запись и чтение в текстовый файл осуществляются с помощью процедур write, writeln, read, readln следующей структуры:

read ( f, x1, x2, x3,…, xn );

read ( f, x );

readln ( f, x1, x2, x3,…, xn );

readln ( f, x );

write ( f, x1, x2, x3,…, xn );

write ( f, x );

writeln ( f, x1, x2, x3,…, xn );

writeln ( f, x );

В этих операторах f — файловая переменная. В операторах чтения (read, readln) x, x1, x2, x3,…, xn — переменные, в которые происходит чтение из файла. В операторах записи write, writeln x, x1, x2, x3,…, xn — переменные или константы, информация из которых записывается в файл.

Есть ряд особенностей при работе операторов write, writeln, read, readln с текстовыми файлами. Имена переменных могут быть целого, вещественного, символьного и строкового типа. Перед записью данных в текстовый файл с помощью процедуры write происходит их преобразование в тип string. Действие оператора writeln отличается тем, что после указанных переменных и констант в файл записывается символ «конец строки».

При чтении данных из текстового файла с помощью процедур read, readln происходит преобразование из строкового типа к нужному типу данных. Если преобразование невозможно, то генерируется код ошибки, значение которого можно узнать, обратившись к функции IOResult. Компилятор Free Pascal позволяет генерировать код программы в двух режимах: с проверкой корректности ввода-вывода и без неё.

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

  • {$I+} — режим проверки ошибок ввода-вывода включён;
  • {$I-} — режим проверки ошибок ввода-вывода отключён.

По умолчанию, как правило, действует режим {$I+}. Можно многократно включать и выключать режимы, создавая области с контролем ввода и без него. Все ключи компиляции описаны в приложении.

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

Если убрать режим проверки, то при возникновении ошибки ввода-вывода программа не будет останавливаться, а продолжит работу со следующего оператора. Результат операции ввода-вывода будет неопределён.

Для опроса кода ошибки лучше пользоваться специальной функцией IOResult, но необходимо помнить, что опросить её можно только один раз после каждой операции ввода или вывода: она обнуляет своё значение при каждом вызове. IOResult возвращает целое число, соответствующее коду последней ошибки ввода-вывода. Если IOResult=0, то при вводе-выводе ошибок не было, иначе IOResult возвращает код ошибки. Некоторые коды ошибок приведены в табл. 7.9.

Таблица
7.9.
Коды ошибок операций ввода-вывода

Код ошибки Описание
2 файл не найден
3 путь не найден
4 слишком много открытых файлов
5 отказано в доступе
12 неверный режим доступа
15 неправильный номер диска
16 нельзя удалять текущую директорию
100 ошибка при чтении с диска
101 ошибка при записи на диск
102 не применена процедура AssignFile
103 файл не открыт
104 файл не открыт для ввода
105 файл не открыт для вывода
106 неверный номер
150 диск защищён от записи

Рассмотрим несколько практических примеров обработки ошибок ввода-вывода:

  1. При открытии проверить, существует ли заданный файл и возможно ли чтение данных из него.
    assign ( f, ’ abc. dat ’ );
    {$I-}
    reset ( f );
    {$I+}
    if IOResult<>0 then
    writeln ( ’файл не найден или не читается ’ )
    else
    begin
    read ( f,... );
    close ( f );
    end;
    
  2. Проверить, является ли вводимое с клавиатуры число целым.
    var i : integer;
    begin
    {$I-}
    repeat
    write ( ’введите целое число  i ’ );
    readln ( i );
    until ( IOResult =0);
    {$I+}
    {Этот цикл повторяется до тех пор, пока не будет введено целое число.}
    end.
    

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

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

В качестве примера работы с текстовыми файлами рассмотрим следующую задачу.

ЗАДАЧА 7.8. В текстовом файле abc.txt находятся матрицы A(N, M ) и B(N, M ) и их размеры. Найти матрицу C = A + B, которую дописать в файл abc.txt.

Сначала создадим текстовый файл abc.txt следующей структуры: в первой строке через пробел хранятся размеры матрицы (числа N и M), затем построчно хранятся матрицы A и B.

Файл abc.txt

Рис.
7.14.
Файл abc.txt

На рис. 7.14 приведён пример файла abc.txt, в котором хранятся матрицы A(4,5) и B(4,5).

Текст консольного приложения решения задачи 7.8 с комментариями приведён ниже.

program Project1;
{$mode objfpc}{$H+}
uses
	Classes, SysUtils
	{ you can add units after this };
var
	f : Text;
	i, j, N,M: word;
	a, b, c : array [ 1.. 1000, 1.. 1000 ] of real;
begin
//Связываем файловую переменную f с файлом на диске.
	AssignFile ( f, ’ abc. t x t ’ );
//Открываем файл в режиме чтения.
	Reset ( f );
//Считываем из первой строки файла abc.txt значения N и M.
	Read( f,N,M);
//Последовательно считываем элементы матрицы А из файла.
	for i :=1 to N do
	for j :=1 to M do
	read ( f, a [ i, j ] );
//Последовательно считываем элементы матрицы B из файла.
	for i :=1 to N do
	for j :=1 to M do
	read ( f, b [ i, j ] );
//Формируем матрицу C=A+B.
	for i :=1 to N do
	for j :=1 to M do
	c [ i, j ] : = a [ i, j ]+b [ i ] [ j ];
//Закрываем файл f.
	CloseFile ( f );
//Открываем файл в режиме дозаписи.
	Append( f );
//Дозапись матрицы C в файл.
	for i :=1 to N do
	begin
		for j :=1 to M do
//Дописываем в файл очередной элемент матрицы и пробел в
//текстовый файл.
		write ( f, c [ i, j ] : 1 : 2, ’   ’ );
//По окончании вывода строки матрицы переходим на новую
//строку в текстовом файле.
		writeln ( f );
	end;
//Закрываем файл.
	CloseFile ( f );
end.

После работы программы файл abc.txt будет примерно таким, как показано на рис. 7.15.

Файл abc.txt после дозаписи матрицы C

Рис.
7.15.
Файл abc.txt после дозаписи матрицы C

0 / 0 / 0

Регистрация: 04.11.2019

Сообщений: 62

1

Написать программу обработки ошибок

04.11.2019, 20:34. Показов 2032. Ответов 5


Студворк — интернет-сервис помощи студентам

Написать программу обработки ошибок отладить в паскале. Программа должна содержать OSH.
Ошибки выводить через writeln.



0



Programming

Эксперт

94731 / 64177 / 26122

Регистрация: 12.04.2006

Сообщений: 116,782

04.11.2019, 20:34

5

ValentinNemo

2373 / 775 / 561

Регистрация: 15.01.2019

Сообщений: 2,394

05.11.2019, 20:42

2

Лучший ответ Сообщение было отмечено Moonbeater как решение

Решение

Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
program Project1;
var
  osh: integer;
  str: string;
  a: real;
begin
  writeln('Введите число:');
  Readln(str);
  val(str,a,osh);
  if osh = 0 then
    begin
      write('Число введено корректно: ');
      writeln(a:0:4);
    end
  else
    writeln('Число введено некорректно, ошибка произошла на позиции ', osh);
  readln;
end.



2



mr-Crocodile

06.11.2019, 11:50

Не по теме:

Цитата
Сообщение от Moonbeater
Посмотреть сообщение

Программа должна содержать OSH

Цитата
Сообщение от ValentinNemo
Посмотреть сообщение

val(str,a,osh);

ValentinNemo, да ты просто телепат!



0



ValentinNemo

06.11.2019, 15:32

Не по теме:

mr-Crocodile, я посмотрел в интернете что такое OSH, ничего на этот счет не нашел, вот и написал. Если вы знаете что это такое, напишите.



0



mr-Crocodile

06.11.2019, 15:40

Не по теме:

Цитата
Сообщение от ValentinNemo
Посмотреть сообщение

mr-Crocodile, я посмотрел в интернете что такое OSH, ничего на этот счет не нашел, вот и написал. Если вы знаете что это такое, напишите.

Да нет, я наоборот, одобряю этот код. я тоже загуглил OSH — увидел, что так в некоторых уроках по Паскалю называют переменную для контроля ОШибок (в частности, в процедуре VAL() )
Так что — я лично — ОДОБРЯМ-С! (c)



0



0 / 0 / 0

Регистрация: 04.11.2019

Сообщений: 62

08.11.2019, 01:03

 [ТС]

6

Цитата
Сообщение от ValentinNemo
Посмотреть сообщение

Не по теме:

mr-Crocodile, я посмотрел в интернете что такое OSH, ничего на этот счет не нашел, вот и написал. Если вы знаете что это такое, напишите.

Тоже не нашел, поэтому обратился сюда, мало ли кто знает)
Во всяком случае, огромное спасибо за ответ!



0



IT_Exp

Эксперт

87844 / 49110 / 22898

Регистрация: 17.06.2006

Сообщений: 92,604

08.11.2019, 01:03

Помогаю со студенческими работами здесь

Написать программу для ввода, обработки и печати матрицы
Для матрицы А(m, n), 1&lt;(m, n)&lt;10, подсчитать количество элементов, равных нулю. Переставить строки…

Написать программу для создания файла из десяти записей и обработки созданного файла
1. Файл содержит сведения о должностных окладах, каждая запись которого содержит поля: должность и…

Составить программу обработки строки символов, которая вводится в программу
Помогите решить задачу:

составить программу обработки струки символов, которая вводится в…

Написать простой калькулятор который предусматривает обработку ошибок
Помогите пожалуйста , написать простой калькулятор который предусматривает обработку ошибок

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:

6

Понравилась статья? Поделить с друзьями:
  • Обработка запроса провалилась ошибка на этапе обработки документа системой
  • Обработка ошибок в интерфейсе can
  • Обработка документооборот с контролирующими органами 1с ошибка
  • Обработка ошибок в vba access
  • Обработка xml файлов с ошибками