Delphi8 异常处理

@[TOC]

8.2异常对象

定义于System单元中的Exception类;

所有的异常都是类类型的对象;

一般情况下不需要自定义异常类,需要时可以查询帮助文档;

自定义异常类举例

type
  EMyException = class(Exception)
    FMessage: Integer;
    Constructor Create(ID: Integer);
  end;

理论上可以使用任意对象代码,但是不推荐;

EMyException实现举例

program Project2;
{$APPTYPE CONSOLE}
uses
  SysUtils, Dialogs;
type
  EMyException = class(Exception)
    FMessage: Integer;
    Constructor Create(ID: Integer);
  end;
constructor EMyException.Create(ID: Integer);
begin
  ShowMessage('EMyException is created, its ID is ' + IntToStr(ID));
end;
var
  I: Integer;
begin
  Readln(I);
  try
    raise EMyException.Create(1001);
  except
    on EMyException do ShowMessage('产生的异常已经被处理');
  end;
  Readln;
end.

8.3异常处理语句

try 语句1 except 语句2 [else 语句3] end;

语句3部分是可选部分;

执行过程:

1.执行语句1,没有异常产生则直接执行end后;

2.产生异常则绕过语句1中剩下未执行的部分,直接跳到语句2;

3.在语句中寻找合适的处理语句,如果没找到则到else;

4.如果else缺省,则当前try无法处理这个异常,程序将这个异常提交到上一层的try;

5.如果还不能处理,继续往上; 6.如果最上层无法处理则提交给Delphi自动插入的异常处理语句,

这样的结果往往是程序强行中断并退出;

语句2有两种形式

1.普通代码:break,exit等,无法搞定则提交给else,不会直接跳到上一层;

2.类似选择语句

except
  on obj1: type1
    do 语句1;
  on obj2: type2
    do 语句2;
    ...

type1和type2等都是异常类的名字;

obj1和obj2等是异常对象的名称,可以省略,如果省略,之后的冒号一并省略;

这段语句中,如果某个异常对象所属的类的名称是TE,

程序会将TE和type1比较,如果TE是type1的派生类或者type1本身,则执行语句1,不是则继续type2;

如果都没有合适的,则提交到else,如果else缺省,提交给上一层;

try…finally…end

产生异常直接到finally,也就是finally部分一定会执行;

在使用try...finally...类型的异常处理语句时,try...finally部分执行的exit例程会被当做一个异常来处理;

举例

uses
  SysUtils, Dialogs;
begin
  try
    Exit;
    Writeln('input a num :');
    Readln(i);
    Writeln(9 div i);
  finally
    ShowMessage('delphi');
  end;
end.

这里exit并不会退出全部程序,只退出finally之前,然后显示delphi;

8.4手动触发异常

使用raise举例

begin
  Writeln('input a num i:');
  Readln(i);
  try
    try
      Writeln(IntToStr(9 div i));
    except
      ShowMessage('Inner Except');
      raise Exception.Create('error');  //here
    end;
  except
    ShowMessage('Outer Except');
  end;
end.

这里会连续显示两个对话框,分别是Inner和Outer;

因为内层处理了异常后,raise语句将当前异常移交给外层,所有外层也执行了;

在实际应用中,如果无法完整处理这个异常,可以处理一部分,然后用raise语句丢给外层;

举例

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils, Dialogs;

procedure ReRaiseErr(i: Integer);
begin
  // := TComponent.Create(Self);
  try
    Writeln(5 div i);
  except
    ShowMessage('Run Error');
    raise;
  end;
end;

begin
  try
    ReRaiseErr(0);
  except
    ShowMessage('Error in ReRaiseErr');
  end;
end.

先显示Run Error,然后是Error in ReRaiseErr;

raise可以放在程序的任何地方;

raise语句只能放在异常处理语句中;

这其实并不矛盾,因为整个程序的最外边会被包进一个异常处理语句;

8.5Abort语句

Abort例程可以自动创建一个EAbort异常类的对象;

EAbort直接继承于Exception,成为静默异常类,引发此类异常不会显示任何对话框提示用户;

即不会对程序有任何干扰;

举例

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils, Dialogs;

var
  i: Integer;
begin
  Writeln('input a i');
  Readln(i);
  try
    try
      Writeln(IntToStr(9 div i));
    except on Exception do
      Abort;
    end;
  except on EAbort do
    ShowMessage('EAbort Exception');
  end;
end.

这里只显示了EAbort Exception;

因为abort不会有提示;

8.6套嵌的异常处理语句

以try…except…end;为例进行嵌套;

如上一节所示;

套嵌语句中,只有内层处理不了异常或者手动触发异常时,这个异常才会被提交给外层处理;

异常丢失举例

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils, Dialogs;

type
  E1 = class(Exception)

  end;

var
  i: Integer;

begin
  Writeln('input a i');
  Read(i);
  try
    try
      Writeln(IntToStr(9 div i));  //sentence x
    except
      raise E1.Create('Error Message');
    end;
  except on E1 do                  //attention
    ShowMessage('Outer Exception');
  end;
end.

注意到,原本的异常不见了,好像异常就是E1;

虽然这里的异常一定是Exception的派生类,而且一定不是E1对象,也不是E1派生类的对象;

但是手动触发了E1类型的异常,被外层捕获并处理;

原本的异常就不见了,这就是异常对象的丢失;