Delphi的面向对象编程基础笔记
1.面向对象。一门面向对象的编程语言至少要实现以下三个OOP的概念
封装:把相关的数据和代码结合在一起,并隐藏细节。封装的好处是利用程序的模块化,并把代码和其他代码分开
继承:是指一个新的类能够从父类中获取属性和方法,这种概念能够用来建立VCL这样的多层次的对象,首先建立通用对象,然后创建这些通用对象的有专用功能的子对象。继承的好处是能够共享代码
多态性:从字面上看,是指多种形状。调用一个对象变量的方法时,实际被调用的代码与实际在变量中的对象的实例有关。
2.Object Pascal不像C++那样支持多继承。Object Pascal不能用多继承来建立一个candy apple(苹果糖)对象,它继承于a p p l e (苹果)类和其他一个称为candy(糖)的类。
Object Pascal提供了两种方法来解决这个问题。第一种方法是在一个类中包含其他的类,你能从D e l p h i的V C L中看到这种解决方法。为了创建candy apple,先使c a n d y对象称为a p p l e对象的一个成员。第二种方法是用接口,用了接口你能创建一个既支持candy接口又支持apple接口的对象
3.在深入了解对象的概念之前,应该先理解下面三个术语:
域(field):也被称为预定义或实例变量,域是包含在对象中的数据变量。在对象中的一个域就像是在Pascal记录中的一个域,在C++中它被称为数据成员
方法(method):属于一个对象的过程和函数名,在C++中它被称为成员函数
属性(property):属性是外部代码访问对象中的数据和代码的访问器,属性隐藏了一个对象的具体实现的细节。
最好不要直接访问对象的域,因为实现对象的细节可能改变。相反用访问器属性来访问对象,它不受对象细节的影响
4.在Object Pascal中通过调用它的一个构造器来建立一个对象的实例,构造器主要是用来为对象创建实例并为对象中的域分配内存并进行初始化使得对象处在可以使用的状态。Object Pascal的对象至少有一个构造器Create(),但是一个对象可以有多个构造。根据不同的对象类型,Create()可以有不同的参数。
不像在C++中,在Object Pascal中构造器不能自动调用,程序员必须自己调用构造器。
5.为对象创建方法用两个步骤,首先是在对象类型的声明中声明这个方法。然后用代码定义方法。例子
type TBoogieNights = class Dance : Boolean; procedure DoTheHustle; end; procedure TBoogieBights.DoTheHustle; begin Dance := True; end;
6.对象的方法能被定义为静态(static)、虚拟(virtual)、动态(dynamic)或消息处理(message)。例子
TFoo = class procedure IAmAStatic; procedure IAmAVirtual; virtual; procedure IAmADynamic; dynamic; procedure IAmAMessage(var M: tMessage); message wm_SomeMessage; end;
静态方法: IAmAStatic是一个静态方法,静态方法是方法的缺省方法,对它就像对通常的过程和函数那样调用。编译器知道这些方法的地址,所以调用一个静态方法时它能把运行信息静态的链接进可执行文件。静态方法执行的速度最快,但它们却不能被覆盖来支持多态性
虚拟方法:IAmAVirtual是一个虚拟方法。虚拟方法和静态方法的调用方式相同。由于虚拟方法能被覆盖,在代码中调用一个指定的虚拟方法时编译器并不知道它的地址。因此,编译器通过建立虚拟方法表(VMT)来查找在运行时的函数地址。所有的虚拟方法在运行时通过VMT来调度,一个对象的VMT表中除了自己定义的虚方法外,还有它的祖先的所有的虚拟方法,因此虚拟方法比动态方法用的内存要多,但是执行速度更快。
动态方法:IAmADynamic是一个动态方法,动态方法跟虚拟方法基本相似,只是它们的调度系统不同。编译器为每一个动态方法指定一个独一无二的数字,用这个数字和动态方法的地址构造一个动态方发表(DMT)。不像VMT表,DMT表中仅有它生命的动态方法,并且这个方法需要祖先的DMT表来访问它其余的动态方法。正因为这样,动态方法比虚拟方法用的内存要少,但是执行起来较慢,因为有可能要到祖先对象的DMT中查找动态方法。
消息处理方法:IAmAMessage是一个消息处理方法,在关键字message后面的值指明了这个方法要响应的消息。用消息处理方法来响应Windows的消息,这样就不用直接来调用它。
7.published,对象的这一部分将产生运行期类型信息(RTTI),并使程序的其他部分能访问这部分。Object Inspector用RTTI来产生属性的列表
8.凡是在相同单元声明的对象都认为是友类,都可以访问其他对象的私有成员。
9.在Object Pascal中的类实例实际上是指向堆中的类实例数据的32位指针。当访问对象的域、方法和属性时,编译器会自动产生一些代码来处理这个指针。因此对于新手来说,对象就好像是一个静态变量。这意味着Object Pascal无法向C++那样在应用程序的数据段为类分配内存,而只能在堆中分配内存。
10.在一个方法前面加上关键字class,使得方法向其它通常的过程和函数一样调用而不需要生成一个包含这个方法的类的实例,这个功能是从C++的static函数借鉴而来的。要小心,不要让一个类方法依赖于任何实例信息,否则编译时将出错。
11.如果一个类要实现多个接口,而这些接口中包含同名的方法,必须把同名的方法另取一个别名。例子
type IFoo = interface [‘{2137BF60-AA33-11D0-A9BF-9A4537A42701}’] function F1 : Integer; end; IBar = interface [‘{2137BF61-AA33-11D0-A9BF-9A4537A42701}’] function F1 : Integer; end; TFooBar = class(TInterfacedObject, IFoo, IBar) //为同名方法取别名 function IFoo.F1 = FooF1; function IBar.F1 = BarF1; //接口方法 function FooF1 : Integer; function BarF1 : Integer; end; function TFooBar.FooF1 :Integer; begin Result := 0; end; function TFooBar.BarF1 : Integer; begin Result := 1; end;
12.implements指示符在开发中提供了两个好处:首先,它允许以无冲突的方式进行接口聚合。它的作用是把多个类合在一起共同完成一个任务。其次它能够延后占用实现接口所需的资源,直到确实需要资源。例如,假设实现一个接口需要分配一个1MB的位图,但是这个接口很少用到。因此,可能平时你不想实现这个借口,因为它太耗费资源了,用inplements指示符之后,可以只在属性被访问的时候才创建一个类来实现接口。
13.一个接口是生存期自管理类型的,这意味着,它通常被初始化为nil,它是引用计数的,当获得一个接口时自动增加一个引用计数;但它离开作用域或赋值为nil时它被自动释放。例子:
var I : ISomeInterface; begin //I被初始化为nil I := FunctionReturningAnInterface; //I的引用计数加1 I.SomeFunc; //I的引用计数减1,如果为0,则自动释放。 end;
14.使用try...finally代码块但不捕捉特定种类的异常是有一定意义的。当代码中使用try...finally块的时候,意味着程序并不关心是否发生异常,而只是想最终总是能进行某项任务。finally块最适合于释放先前分配的资源(例如文件或Windows资源),因为它总是执行的,即使发生了错误。不过,很多情况下,可能需要对特定的异常做特定的处理,这时候就要用try...except块来捕捉特定的异常。例子
Program HandleIt; {$APPTYPE CONSOLE} var R1, R2 : Double; begin while True do begin try Write(‘Enter a real number:’); ReadLn(R1); Write(‘Enter another real number:’); ReadLn(R2); Writeln(‘I will not divide the first number by the second…’); Writeln(‘The answer is :’, (R1/R2):5:2); except On EZeroDivide do Writeln(‘You cannot divide by zero!’); On EInOutError do Writeln(‘That is not a valid number!’); end; end; end;
15.异常是一种特殊的对象实例,它在异常发生时才被实例化,在异常被处理后自动删除。异常对象的基本是Exception
在异常对象中最重要的元素是M e s s a g e属性,它是一个字符串,它提供了对异常的解释,由Message所提供的信息是根据产生的异常来决定的
如果定义自己的异常对象,一定是要从一个已知的异常对象例如Exception或它的派生类派生出来的,因为这样通用的异常处理过程才能捕捉这个异常。
凡是没有显式地处理的异常最终将被传送到Delphi运行期库中的默认处理过程并在此得到处理。这个默认的处理过程将打开一个消息框,告诉用户一个异常发生了。
16.处理异常对象的时候,可能需要访问异常对象的实例,以便获得更多的有关异常的信息。要访问异常对象的实例有两种方法:一是在on ESomeException结构中使用可选的标示符,二是使用ExceptObject()函数。
可以在except块的on ESomeException部分插入一个可选的标示符并且具有一个到当前产生的异常的实例的标示符的映射。这么做的语法是在异常类型前面放上一个标示符和冒号,例如
try Something except on E:ESomeException do ShowMessage(E.Message); end;
这里,标示符E成为当前产生的异常的实例。这个标示符的类型总是和它后面的异常类型一致。
也可以使用Exception函数返回当前异常对象的实例,不过, ExceptObject()函数的返回类型是TObject,必须把它强制转换为需要的异常对象。例子
try Something except on ESomeException do ShowMessage(ESomeException(ExceptObject).Message); end;
如果当前没有异常,ExceptObject()函数将返回nil
17.触发一个异常的语法类似于创建一个对象实例,例子
Raise EbadStuff.Create(‘Some bad stuff happened’);
18.当一个异常触发后,应用程序的流程就跳到了下一个异常处理过程,知道这个异常实例被处理结束并释放空间。执行的流程取决于调用栈,因此,跳转的范围是整个程序(而不限于一个过程或单元)。
演示了当发生异常时程序的执行流程。从这个清单中可以看出,单元的名称为main,它有一个窗体,窗体上有一个按钮。当单击按钮时, Button1Click()就调用Proc1(),而Proc1()又会调用Proc2(),Proc2()又会调用Proc3()。如果在Proc3()中发生了异常,你将看到程序的执行流程是怎样在try... finally块中跳转的,直到Button1Click()最终处理了异常。
提示在Delphi的IDE中运行此程序时,最好不要选中Tools|Debugger Options|的对话框中的Stopon Delphi Exception 复选框。这样能更好地看到执行流程的变化情况
19.运行期类型信息( RTTI )是一种语言特征,能使应用程序在运行时得到关于对象的信息。RTTI是Delphi的组件能够融合到IDE中的关键。它在IDE中不仅仅是一个纯学术的程。
因为对象都是从TObject继承下来的,因此,对象都包含一个指向它们的RTTI的指针以及几个内建的方法。
关键字as是类型转换的一种新形式。它能把一个基层的对象强制类型转换成它的派生类,如果转换不合法就产生一个异常。假如有一个过程,想让它能够传递任何类型的对象,应该定义如下:
Procdure Foo(AnObject : TObject);
在这个过程如果要对AnObject进行操作,要把它能够传递给任何类型的对象,假定把AnObject看成是一个TEdit派类型,并想要改变它所包含的文本(TEdit是一个Delphi VCL编辑控件),用下列代码:
(Foo as TEdit).Text := ‘Hello World’;
能用比较运算符来判断两个对象是否是相兼容的类型,用is运算符把一个未知的对象和一个已知的类型或实例进行比较,确定这个位置对象的属性和行为。例如在对AnObject进行强制类型转换之前确定AnObject和TEdit是否指针兼容
If(Foo is TEdit) then
TEdit(Foo).Text := ‘Hello World’;
注意在这个例子里不能用as进行强制类型转换,这是因为它要大量使用RTTI,另外还因为,在第一行已经判断Foo就是TEdit,可以通过在第二行进行指针转换来优化。