异常 Exception(Error) (转贴)

来源:互联网 发布:淘宝买家评论怎么写 编辑:程序博客网 时间:2024/06/10 18:11

 设计健壮程序的关键之一是,如果程序分配了资源,最后就必须释放它,而不管异常是否发生。

  例如,如果你的程序分配了一块内存,必须确保最终释放这块内存;如果打开了一个文件,必须确保最后关闭这个文件。

  在正常情况下,程序在申请资源后,能够释放它。然而,就算异常发生了,也必须保证释放它。
  异常往往由一些偶然的事件导致。例如,程序中调用一个RTL例程,或使用一个构件都可能产生异常。你的程序必须确保即使这些偶然事件发生了,也要释放被占用的资源。
  常见的需要保护的资源有:文件、内存、Windows资源 、各种对象等。

  try...except...end;  try...finally...end; 捕获异常、资源分配!  未知、意外的异常处理,信息提示(异常分支流程,异常抛出)?何时。。如何?精度(粗?细)

        但需要强调的是,异常用来标志错误发生,却并不因为错误发生而产生异常产生异常仅仅是因为遇到了 raise,

在任何时候,即使没有错误发生,raise 都将会导致异常的发生。 注意:异常的发生,仅仅是因为 raise,而非其他!
       一旦抛出异常,函数的代码就从异常抛出处立刻返回,从而保护其下面的敏感代码不会得到执行。对于抛出异常的函数本身来说,通过异常从函数返回和正常从函数返回(执行到函数末尾或遇到了 Exit)是没有什么区别的,函数代码同样会从堆栈弹出,局部简单对象(数组、记录等)会自动被清理、回收。
       采用抛出异常以处理意外情况,则可以保证程序主流程中的所有代码可用,而不必加入繁杂的判断语句。

  “在似乎有用的时候,就应该使用 try...except 块。但是要试着让自己对这种技术的热情不要太过分”

  异常提供了一种更加灵活和开放的方式,使得后来的编程者可以来根据实际的情况处理这种错误,而不是使用预先设定好的处理结果。

  异常:理解为一种特殊的事件,该事件发生时,程序的正常执行将被打断.由程序导致的不正常情况是错误而不是异常,程序错误与异常不是相同的概念.
  异常是为方便用户报告错误并处理错误而创建的机制,一般是由操作系统完成的运行期错误处理

    异常处理的基本思想是通过提供规范方式处理软,硬件错误的能力,使程序更加健壮;  异常处理可以将处理错误的代码与正常的逻辑处理代码相分离.
  Delphi缺省的方式是在应用程序收到异常之前捕获异常.IDE会给出一个”预警”对话框,以指明应用程序将要产生异常.
  异常处理机制是一种程序设计安全策略,它是建立在保护块思想上,通过try和end语句块对代码的封装确保在程序发生异常时,

 程序能够正常运行或释放所占用的资源.

9.2 异常处理语句
  要处理异常,首先必须识别异常,判断异常在什么时候发生,然后对出现的异常作出处理。

    因此对于程序员来说,其工作就是确定程序中哪个地方产生错误及发生错误时的处理程序,特别是容易导致数据或系统资源丢失的错误。
 
被保护代码块是用户处理异常的基本工具。对异常的响应是在代码保护块中进行的。当有若干条语句需要处理同一个错误时,可以将这些语句放在一个代码块里,并对整个代码块定义一个响应过程。这个对异常响应的代码块称为保护块。

  处理代码保护块异常及其异常处理有两种方式,即try...finally语句和try...except语句。这两种语句可以互相嵌套,形成更加牢固的异常处理结构。

9.4 Exception类
  Exception类是所有异常类的基类,其本身直接继承于TObject类,在Delphi的Sysutils单元中,Exception类的定义如下(只列出公共成员):
Type Exception=Class(TObject) 
  public 
   Constructor Create(const Msg:string); 
   Constructor CreateFmt(const Msg:string;const Args:array of const); 
   Constructor CreateRes(Ident:Integer); 
   Constructor CreateResFmt(Ident:Integer;const Args:array of const); 
   Constructor CreateHelp(const Msg:string;HelpContext:Integer); 
   Constructor CreateFmtHelp(const Msg:string;const Args:array of const;HelpContext:Integer); 
   Constructor CreateResHelp(Ident,HelpContext:Integer); 
   Constructor CreateResFmtHelp(Ident:Integer;const Args:array of const;HelpContext:Integer); 
   Property HelpContext:Integer; 
   Property Message:String;
  end;
  Exception类中只申明了两个特性:一个是Message特性,用于给出有关异常的简短说明;另一个是HelpContext特性,用于指定帮助的上下文编号,任何一个异常都可以访问这两个特性 。
  Exception类提供了一些构造异常的方法。所有构造函数的名字都以Create开头,它们都有相同的目的,就是根据不同的参数来创建一个错误提示信息字符串。用户可以调用这些构造函数来创建自己的派生类的异常实例,或者可以为Exception对象调用它们。
  例如,把一个字符串或变量作为错误消息来传递,生成异常的语句可以是:
  raise Exception.Create('Error In Memory');
 又例如,下面语句生成的异常显示带有两个整数的错误信息字符串:
  raise Exception.CreateFmt('Error:X=%d Y=%d',{X,Y]);
 下面是这个语句的一个运行结果: Error:X=-1 Y=12 
9.5 自定义异常
  当用户需要产生异常时,可以选择从Exception中派生出来的一个类或自己创建异常类。用户可以从Exception中派生自己的类,也可以设计一个全新的类,这样的类不需要建立在Exception的基础上。但是,Delphi建议定义异常时,最好用Exception类作为基类,因为不是从Exception中派生的类必须处理所有不是基于Exception的异常,任何未处理的异常都会引起致命的应用程序错误。
  自定义异常类的程序如下: 
  type EMyException = class(Exception)
  上面,定义了一个异常类,该类的基类是Exception.
  当然,异常不一定必须直接从Exception类继承,也可以用其它Exception类的派生类作为自己的基类,例如: 
type 
  EMathError = class(Exception); 
  EInvalidOp = class(EMathError); 
  EZeroDivide = class(EMathError); 
  EOverflow = class(EMathError); 
  EUnderflow = class(EMathError);
  在声明异常时,可以根据需要声明一些字段、方法和特性。例如,SysUtils单元中的EInOutError异常就声明了一个ErrorCode字段,用于存储引起异常的文件I/O错误代码,示例如下:
type EInOutError = class(Exception) 
  ErrorCode: Integer; 
end;
  声明了自己的异常后,需要在程序中触发这个异常,使用Raise语句。Raise语句的语法格式:
  raise object at address
  这里object和 at address都是可选的。如果省略了object,表示要重新触发当前异常。 这种形式只能出现在try...Except结构的Except部分,主要用于过程或函数的调用中出现无法完全处理异常情况。raise后面的object是异常的对象实例,而不是异常的类型。通常把创建对象实例与触发这个异常并在一句中,因为构造函数返回的总是该类型的对象实例。如
 EPasswordInvalid = class(Exception);
  在触发异常时,创建对象实例与触发这个异常并在一句中。
  if Password <> CorrectPassword then
   raise EPasswordInvalid.Create('Incorrect password entered');
  注意:虽然程序创建了异常对象实例,但程序不必自己删除它,因为异常处理句柄会自动删除异常的实例。
 关键步骤是程序中定义了一个TMouseException类。其定义方法是:
type 
  TMouseException = class(Exception) 
  X, Y: Integer; 
constructor Create(const Msg: string; XX, YY: Integer);
end; 
 TMouseException类是从Exception类派生出来的。除了从Exception继承来的成员外,TMouseException类还增加了整型成员X和Y,并说明了一个构件函数,可以调用这个构造函数来初始化该类的对象。其构造函数的编程为:
constructor TMouseException.Create(const Msg: string; XX, YY: Integer);
begin 
  X := XX; // Save X and Y values in object 
  Y := YY; 
  Message := // Create message string Msg + ' (X=' + IntToStr(X) + ', Y=' + IntToStr(Y) + ')';
end;
  这里,Message是从Exception来继承过来的,它被赋值给了包括这两个值的一个消息。 
  Width := 65;{ Change inherited properties } 
  Height := 65; 
  FPen := TPen.Create;{ Initialize new fields } 
  FPen.OnChange := PenChanged; 
  FBrush := TBrush.Create; 
  FBrush.OnChange := BrushChanged;
  end;
  构造的第一行是Inherited Create(Owner),其中Inherited是保留字,Create是祖先类的构造名,事实上大多数构造都是这么写的。这句话的意思是首先调用祖先类的构造来初始化祖先类的字段,接下来的代码才是初始化派生类的字段,当然也可以重新对祖先类的字段赋值。用类来引用构造时,程序将自动做一些缺省的初始化工作,也就是说,对象在被创建时,其字段已经有了缺省的值。所有的字段都被缺省置为0(对于有序类型)、nil(指针或类类型)、空(字符串)、或者 Unassigned (变体类型)。除非想在创建对象时赋给这些字段其它值,否则在构造中除了Inherited Create(Owner)这句外,不需要写任何代码。
  如果在用类来引用构造的过程中发生了异常,程序将自动调用析构来删除还没有完全创建好的对象实例。效果类似在构造中嵌入了一个try協inally语句,例如:
  try 
  ...{ User defined actions }
  except{ On any exception } 
  Destroy;{ Destroy unfinished object } 
  raise;{ Re-raise exception }
  end;
  构造也可以声明为虚拟的,当构造由类来引用时,虚拟的构造跟静态的构造没有什么区别。当构造由对象实例来引用时,构造就具有多态性。可以使用不同的构造来初始化对象实例。
2.析构
  析构的作用跟构造正相反,它用于删除对象并指定删除对象时的动作,通常是释放对象所占用的堆和先前占用的其它资源。构造的定义中,第一句通常是调用祖先类的构造,而析构正相反,通常是最后一句调用祖先类的析构,程序示例如下:
  destructor TShape.Destroy;
  begin 
  FBrush.Free; 
  FPen.Free; 
  inherited Destroy;
  end;
  上例中,析构首先释放了刷子和笔的句然后调用祖先类的析构。
  析构可以被声明为虚拟的,这样派生类就可以重载它的定义,甚至由多个析构的版本存在。事实上,Delphi中的所有类都是从TObject继承下来的,TObject的析构名为Destroy,它就是一个虚拟的无参数的析构,这样,所有的类都可以重载Destroy。
  前面提到,当用类来引用构造时,如果发生运行期异常,程序将自动调用析构来删除还没有完全创建好的对象。由于构造将执行缺省的初始化动作,可能把指针类型和类类型的字段清为空,这就要求析构在对这样字段操作以前要判断这些字段释放为Nil。有一个比较稳妥的办法是,用Free来释放占用的资源而不是调用Destroy,例如上例中的FBrush.Free和FPen.Free,Free方法的实现是:
procedure TObject.Free;
begin 
if Self <> nil then Destroy;
end;
  也即Free方法在调用Destroy前会自动判断指针是否为Nil。如果改用FBrush.Destroy和FPen.Destroy,当这些指针为Nil时将产生异常导致程序中止。


原创粉丝点击