@[TOC]
6.2类与对象
模拟一个对象:一个结构,一个变量;
type
MyClass = class
//
end;
var
obj:MyClass;
类中声明的变量称为字段Field,类中声明的例程称为方法Method;
创建对象举例
type
T1 = class
s:Integer;
end;
var
obj:T1;
begin
obj := T1.create;//如果没有这句可以正常编译但是会导致运行错误
obj.s := 239;
end.
套嵌类声明举例
type
MyClass = class
type
NestClass = class
end;
var
NestObj:NestClass;
procedure ShowName(var s:NestClass);
end;
var
procedure OtherFun(var s:NestClass);//错误,NestClass只能用于MyClass中
类中声明的数据类型的规定:
1.类中声明的数据类型只能用于这个类;
2.和普通声明一样,类中的类型只能先声明再使用;
3.类中声明的标识符只能被本类和本类的套嵌类使用,但是一个类却不能使用套嵌类中声明的标识符;
举例
type
T1 = class
type
m2a = class
type
m3a = class
end;
end;
m2b = class
type
m3b = class
end;
end;
end;
m3b可以使用m2b和T1中声明的所有标识符,m2b或m2a可以使用T1中声明的所有标识符;
m2a不能使用m3a中声明的标识符,m2a不能使用m2b中声明的标识符;
类的继承举例
type
子类名称 = class(父类名称)
end;
注:Delphi中任何类在声明时必须指定其父类,如没有指定,默认为TObject;
不希望某个类派生子类的时候,可以用sealed;
举例
type
TA = class sealed(TObject)
end;
Delphi提供六个关键词来用于限定访问权限:
public, private, protected, published, automated strict private, strict protected;
使用格式
type
类名称 = class(父类名称)
private
//
protected
//
...
end;
无先后顺序,无必须有或者没有;
strict private:严格私有,只能用于当前类中;
strict protected:当前类和子类;
以上两种成员,同一个类的不同对象之间也不能访问;
private:只能用于定义这个类的.pas和.dpr;
protected:相当于将楼上扩展到任何子类可见;
public:对任何位置,任何类都可见,没有显示指定访问权限的时候,默认是public;
automated:用于Win32下的COM编程,不做介绍;
published:与public相同;
使用published应当注意:
1.published属性值只能是有序类型、字符串、类、接口、变体、方法指针以及上下界在0到31之间的集合类型,实数不能是Real48类型;
2.不能以同一个名字公布两个或更多的重载方法;
6.3对象字段及对象函数
类中声明字段和方法有两种:
第一种可以通过类访问也可以通过对象访问,成为类字段或类方法;
第二种只能通过对象而不能通过类访问,成为对象字段或对象方法;
举例
type
T1 = class
class var
i: Integer;
var
s: String;
end;
var
obj := T1;
begin
obj := T1.Create;
obj.i := 90;
obj.s := 'this is obj';
T1.i := 100;
T1.s := 'this is T1';//wrong
end.
T1只能访问i不能访问s,obj对象既可以访问i也可以访问s;
类中var之后声明的所有字段都被认为是对象变量,除非:
1.遇到其他的用于声明的关键词,如const或type;
2.遇到了访问权限的限定词,如public、strict private等;
3.遇到了方法的声明;
声明字段而未使用任何关键词时,默认为是对象变量;
type
T1 = class
i: Integer;
end;
对象字段与对象静态绑定,
静态绑定是指使用对象名称引用对象中的成员时,不管实际类型如何,编译器都会以声明时的类型为准而调用相应成员。
举例:
type
T1 = class
i: Integer;
end;
T2 = class(T1)
s: String;
end;
var
O1: T1;
O2: T2;
begin
O1 := T2.Create;
O2 := T2.Create;
O1.i := 90;
O1.s := 'this is O1';//wrong
O2.i := 100;
O2.s := 'this is O1';
end.
T1的对象O1,虽然赋值时给了一个T2类型的对象,但是依然无法引用T2的成员s;
对象方法的声明与普通例程的声明区别不大,相对于普通例程,方法后可以接更多的限定词;
但是限定词的顺序要遵守一定的顺序,下表中列出了顺序,同一行的限定词不可能同时出现;
reintroduce
overload
virtual,dynamic,override
register,pascal,cdecl,stdcall,safecall
abstract
这里复习一下chapter5;
在工程文件中,类作为全局数据类型可以声明于任何地方,但是在声明后应当立即定义,除非使用了提前声明;
方法可以分为四种:静态方法、消息方法、虚方法、动态方法。
方法后未加任何限定词即为静态方法;
此类型方法在编译时确定其所对应的方法体即实现;
举例
type
T1 = class
procedure F1;
end;
T2 = class(T1)
procedure F1;
end;
procedure T1.F1;
begin
Writeln('this is T1.F1');
end;
procedure T2.F1;
begin
Writeln('this is T2.F1');
end;
var
O1: T1;
O2: T2;
begin
O1 := T1.Create;
O2 := T2.Create;
O1.F1; //T1.F1
O2.F1; //T2.F1
O1 := O2;
O1.F1; //T1.F1
end.
注:还是体现了静态绑定;
虚方法
举例
type
T1 = class
procedure F1;virtual;// change
end;
T2 = class(T1)
procedure F1;override;// change
end;
procedure T1.F1;
begin
Writeln('this is T1.F1');
end;
procedure T2.F1;
begin
Writeln('this is T2.F1');
end;
var
O1: T1;
O2: T2;
begin
O1 := T1.Create;
O2 := T2.Create;
O1.F1; //T1.F1
O2.F1; //T2.F1
O1 := O2;
O1.F1; //T2.F1
Readln;
end.
注:关键词virtual用于方法后表示某个方法是虚方法;
T2中改写了T1中的虚方法F1,改写父类的方法只要,
在子类中以完全相同的形式重新声明需要改写的方法,并将virtual(如果有的话)改成override;
注意到最后变成了T2.F1,因为这里是动态绑定;
静态绑定根据对象的真正类型来调用相应的方法,动态绑定是根据对象名称来调用相应的方法;
而对象名称是一个指向对象实体的指针;
动态虚方法表格
Virtual Method Table,VMT;
表里是每个类拥有的虚方法的名称和虚方法所在地址;
包含全部虚方法,没有改写的也在;
很多情况下,子类只用了很少几个父类的虚方法,这样就造成了很大的浪费;
使用动态方法可以应对这种问题,动态方法是一种特殊的虚方法,使用dynamic声明;
举例
type
T1 = class
procedure F1;dynamic;
end;
动态方法与虚方法在使用上没有区别,只是内部存储机制不同;
动态方法表
Dynamic Method Table,DMT
只存储当前类新定义的动态方法和改写的动态方法的地址;
这样一来,在声明父类时,可以将最有可能被子类使用的方法声明为动态方法,然后再改写;
当子类调用这些方法时,将直接查询DMT,避免了大量的无用查询;
然而并不能将所有的方法都声明成动态方法,因为动态方法的查询过程层层递进,在一些复杂的几十代的派生类的系统中反而更慢;
动态方法可以缩小程序占用的空间,但是调用速度稍慢,虚方法速度快,但是占用更多的空间;
大多数情况下,Delphi推荐使用虚方法,除非是明显优于虚方法的时候。
消息方法
一般形式
procedure 方法名称(var 参数名: 消息类型); message 消息ID;
只要系统产生了方法后的ID所标识的信息,此方法就会被调用;
举例
procedure WMClose(var MSG: TMESSAGE); message WM_CLOSE;
在窗口被关闭时调用;
定义消息方法的注意点:
1.必须是procedure,参数只有一个并且是var传址;
2.参数类型必须是Delphi封装的消息类型,这些类型定义于Messages单元中;
3.方法后用message加消息ID限定,消息ID代表Windows消息的编号,在Messages单元中可以找到这些ID的声明;
抽象方法
只声明,不定义;在virtual或dynamic后加abstract;
抽象方法应该是虚方法或动态方法;
任何类只要有一个方法是抽象方法,该类就是抽象类;
抽象类的对象不能调用抽象方法,可以调用非抽象方法;
举例
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm2 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
T1 = class
procedure M1;virtual;abstract;
end;
T2 = class(T1)
procedure M1;override;
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
{ T2 }
procedure T2.M1;
begin
ShowMessage('T2.M1');
end;
procedure TForm2.Button1Click(Sender: TObject);
var
obj: T1;
begin
obj := T1.Create;
obj.M1; //引起运行错误
FreeAndNil(obj);
end;
procedure TForm2.Button2Click(Sender: TObject);
var
obj: T2;
begin
obj := T2.Create;
obj.M1;
FreeAndNil(obj);
end;
end.
实际运行的时候记得在右下角把监听例程绑定组件上,结果是点Button2有反应,Button1没有;
6.4类字段及类方法
类字段是一个类的所有成员都拥有其值相同的信息;
举例
type
TBall = class(TObject)
class var
shape: String;
name: String;
end;
var
obj: TBall;
begin
TBall.shape := 'Round';
TBall.name := 'Ball';
obj := TBall.Create;
Writeln(obj.shape);
Readln;
end.
全部对象的name和shape都是这两个值;
此区域以class var开始,以下四种情形之一结束:
1.var声明另一个class var声明;
2.包括对象方法类方法、构造、析构函数在内的任何一个例程的声明;
3.任何一个属性的声明;
4.任何一个访问权限限定词;
注:const声明的标识符表示此标识符是一个类常量,并不表示class var的结束;
类方法:
(普通)类方法、静态类方法
普通类方法声明不能省略class;
type
T1 + class
class function F2(var S: String): Integer; virtual;
end;
...
class function T1.F2(var S: String): Integer; virtual;
begin
//
end;
类方法和普通方法都可以通过对象来调用,类方法还可以直接通过类调用;
静态类方法举例
type
T1 = class
class function F2(var S: String): Integer; static;
end;
类静态方法没有隐含的self参数,所以类静态方法不能引用任何对象成员,但是可以引用类字段和类方法;
实际上,Delphi引入这种类型的方法完全是为了和微软的.net框架相互兼容,就功能而言,普通的类方法完全可以代替静态类方法;
普通类中的self指向调用此方法的对象或类本身,谁调用指谁;
每个继承于TObject的类中都隐式或显示地声明了两个特殊的类方法:构造函数以及析构函数;
它们负责对象的创建及销毁,如create函数就是构造函数;
特点:
1.构造函数必须使用constructor声明;
2.构造函数一律叫create,与Delphi内部保持一致;
3.一般不需要每个类都声明,TObject中声明了构造函数,可以直接使用;
举例
constructor create(参数列表);
自定义构造函数举例
uses
SysUtils, Dialogs;
type
T1 = class(TObject)
i: Integer;
constructor create;
end;
constructor T1.create;
begin
showmessage('创建T1的对象');
end;
var
O1: T1;
begin
O1 := T1.create;
end;
注:添加了Dialogs单元
自定义构造函数可以在对象的创建过程中执行各种各样的行为;
含有参数列表的构造函数举例
uses
SysUtils, Dialogs;
type
T1 = class(TObject)
i: Integer;
constructor create(value: Integer);
end;
var
O1: T1;
constructor T1.create(value: Integer);
begin
i := 289;
end;
begin
O1 := T1.create(289);
//O1 := T1.create;
Writeln(O1.i);
Readln;
end.
如果运行注释中的语句,编译器会无法通过;
T1的create方法把TObject中的create方法覆盖了;
这点跟Java完全不同,java中是可以直接写很多个构造函数的(
因此需要重载构造函数来执行一些自定义的行为;
举例
program Project11;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
T1 = class
I: Integer;
constructor create; overload;
constructor create(value: Integer); overload;
end;
{ T1 }
constructor T1.create;
begin
end;
constructor T1.create(value: Integer);
begin
I := value;
end;
var
O1: T1;
begin
try
{ O1 := T1.create;
Writeln(O1.I);
O1.create(289);
Writeln(O1.I); }
O1 := T1.create(289);
Writeln(O1.I);
O1.create;
Writeln(O1.I);
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
注:
注释中代码块的结果是0,289;非注释部分的结果是289,289;
这段没有什么意思233领悟精神:I的值可以被改变233;
对象在不需要的时候必须手动调用对象的析构函数来销毁;
destructor Destroy;
析构函数多使用Destroy作为名称也只是一个约定不是规定;
自定义析构函数需要使用override;
一般情况下不需要自定义,继承来的析构函数非常智能,可以满足大部分需求;
自定义析构函数举例
type
T1 = class
F: Integer;
constructor Create(Path: String);
destructor Destroy;override;
end;
constructor T1.Create(Path: String);
begin
F := FileOpen(Path, fmopenreadwrite);
end;
destructor T1.Destroy;
begin
inherited;
FileClose(F);
end;
begin
end.
析构函数只是将对象占用的内存释放,并不是将对象的引用设为空;
因此,在调用析构函数后建议手动将对象的引用置为空;
O1.Destroy;
O1 := nil;
可以使用标准函数FreeAndNil(O1);
统一以上步骤;
当仅仅需要销毁对象占用的内存时,应该使用;
O1.Free;
二者的区别是,Free会在销毁前判断对象占用的空间是不是已经销毁,然后再调用destroy;
试图将已经销毁的内存再销毁一次往往导致系统崩溃;
6.5属性
对象中的字段有时需要满足一定的条件,这时我们可以;
type
M1 = class
strict private
Age: Integer;
public
procedure SetAge(x: Integer);
function GetAge:Integer;
end;
function M1.GetAge: Integer;
begin
Result := self.Age;
end;
procedure M1.SetAge(x: Integer);
begin
if(x < 100)and(x > 0) then
self.Age := x
else
Writeln('Age不符合范围');
end;
var
obj: M1;
begin
obj := m1.Create;
obj.SetAge(98);
Writeln(obj.GetAge);
FreeAndNil(obj);
Readln;
end.
这种显式调用get set的方法可以封装使用;
property 名称: 类型 read 读方法 write 写方法;
类似read和write的限定词还有:stored,default,nodefault,implements;
每个属性至少有一个read或write指定读或写方法;
举例
type
M1 = class
strict private
Age: Integer;//
public
procedure SetAge(x: Integer);
function GetAge: Integer;
property pAge: Integer read GetAge write SetAge;
end;
function M1.GetAge: Integer;
begin
Result := self.Age;
end;
procedure M1.SetAge(x: Integer);
begin
if(x < 100)and(x > 0) then
self.Age := x
else
Writeln('Age不符合范围');
end;
var
obj: M1;
begin
obj := M1.Create;
obj.pAge := 98;
Writeln(obj.pAge);
FreeAndNil(obj);
Readln;
end.
注:pAge不是存在内存中的变量,只是一个代理,它判断被赋的值是否符合要求,不符合则拒绝这个值;
无法用@
的方式获取属性的地址,同样,也不能用var将属性作为参数传递;
属性的访问符必须遵守以下规则:(访问符就是上面的SetAge和GetAge)
1.必须定义于当前类的祖先类的其中之一或当前类中,如果定义在当前类中则要定义在属性之前;
如果定义在祖先类则必须保证访问符能够被当前类访问;
2.如果访问符是一个字段,则其与属性有相同的类型;
3.如何访问符是一个方法,不能是动态方法或者有重载版本的虚方法;
4.如果属性定义在published区域,则读写方法必须用register的方式;
这里联动第五章line304;
5.属性的读方法必须是无参数的函数,返回值必须跟属性数据类型相同;
属性的写方法必须是只有一个参数的过程,参数只能用const和value传递方式;
注:索引属性及数组属性另有特殊规定
数组属性的意思是某个属性像数组一样由多个值构成,且每个值可以通过索引读写;
举例用一个Pixel描述一幅画上某个点的颜色信息
property Pixel[x, y: Integer]: TColor read GetColor write SetColor;
属性定义
Pixel[128, 128] := Green;
调用属性
function GetColor(x, y: Integer): TColor;
procedure SetColor(x, y: Integer; value: Tcolor);
方法定义,只能使用const或value;
数组属性的索引可以是任何类型而不一定是有序类型;
property Name[str: String]: String read GetName;
可以使用default关键字将属性声明为默认属性,读写默认属性时,可以直接用对象加索引进行读写;
property Pixel[x, y: Integer]:TColor read GetColor write SetColor;default;
...
obj.Pixel[128, 128] := Green;
obj[128, 128] := Green;
这两句等效;
数组属性定义
举例
program Project11;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TTriangle = class
strict private
Line1, Line2, Line3: Integer;
procedure SetLine1(value: Integer);
function GetLine1: Integer;
procedure SetLine2(value: Integer);
function GetLine2: Integer;
procedure SetLine3(value: Integer);
function GetLine3: Integer;
published
property Border1: Integer read GetLine1 write SetLine1;
property Border2: Integer read GetLine2 write SetLine2;
property Border3: Integer read GetLine3 write SetLine3;
end;
{ TTriangle }
function TTriangle.GetLine1: Integer;
begin
Result := Self.Line1;
end;
function TTriangle.GetLine2: Integer;
begin
Result := Self.Line2;
end;
function TTriangle.GetLine3: Integer;
begin
Result := Self.Line3;
end;
procedure TTriangle.SetLine1(value: Integer);
begin
Line1 := value;
end;
procedure TTriangle.SetLine2(value: Integer);
begin
Line2 := value;
end;
procedure TTriangle.SetLine3(value: Integer);
begin
Line3 := value;
end;
begin
end.
注意到雷同的代码非常多,使用index限定可以解决这个问题;
将相似的属性存储在一个数组中,用这个数组为对象编写一组读写方法;
举例
program Project11;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TTriangle = class
strict private
Lines: array[1..3] of Integer;
procedure SetLine(Index, value: Integer);
function GetLine(Index: Integer): Integer;
published
property Border1: Integer index 1 read GetLine write SetLine;
property Border2: Integer index 2 read GetLine write SetLine;
property Border3: Integer index 3 read GetLine write SetLine;
end;
{ TTriangle }
function TTriangle.GetLine(Index: Integer): Integer;
begin
Result := Lines[Index];
end;
procedure TTriangle.SetLine(Index, value: Integer);
begin
Lines[Index] := value;
end;
var
obj: TTriangle;
begin
obj := TTriangle.Create;
obj.Border1 := 89;
Writeln(obj.Border1);
Readln;
end.
index限定符紧跟属性的类型名称,之后空格,接一个Integer类型的常量,该常量不能超出Integer类型的值域;
1指向数组的第一个成员,只要你想,border1也可以指向lines[3]
,虽然并不推荐这么做;
存储限定符:stored,default,nodefault;
此限定符决定一个声明于published区域的属性的值是否被存储在窗体文件中;
举例
property Name: TComponentName read FName write SetName stored False;
stored后可以接三种标识符:
1.False和True;
2.当前类或祖先类所含有的Boolean类型的字段的名称;
3.当前类或祖先类中所含有的函数名,此函数不接受任何参数且返回值必须为Boolean类型的值;
当stored缺省时,默认为stored True;
default限定词后接一个与属性同类型的常数,用于标识一个属性的缺省值;
属性的缺省值的作用是:将属性值存储到文件时,程序会将属性的当前值和默认值比较,
当二者不相等且stored后的值为True或根本没有stored时,当前属性值会被存储到文件中;
当属性是一个数组属性时,default限定词有其他含义,其实存储限定符根本不能用于数组属性;
当需要在子类中取消某个在父类中声明的属性的缺省值时可以使用nodefault,缺省值不能指定是2147483648;
二者仅支持有序类型和集合类型,集合类型的子类型的上下界在0..31
之间,当一个属性值没有指定二者之一时,默认nodefault;
简单类型本身就有空值,如0,nil等;
祖先类声明的属性可以在子类中被改写,包括读写方法、访问权限、缺省值等;
格式如下
property 属性名 限定符;
不需要声明属性的数据类型;
限定符只能是read,write,stored,default,nodefault
关键词property后只有属性名时表示只更改属性的访问权限,属性的访问权限只能扩大,
如只能将private改成public;
举例
type
M1 = class
...
property pName: String read GetName write SetName;
property pAge: Integer read GetAge write SetAge;//father
end;
M2 = class(M1)
...
property pName;//change
property pAge read FAge write SetAge;//change
end;
改写属性时不能删除原有的限定符,只能更改原限定符、添加新限定符或更改原有属性的可见性;
在子类声明的属性会隐藏父类的同名属性;
所有属性和对象都是静态绑定的;
类属性举例
class property 名称: 类型 限定符;
这个类所有对象的共同属性,可以通过类直接使用,也可以通过对象使用;
类属性的限定符不能是stored或default,其读写方法必须是当前类中声明的类静态方法;
类属性不能在published区域中声明;
6.6 辅助类 class helper
辅助类声明时必须指定属主类,最好也指定父类;
type
辅助类名称 = class helper (辅助类的父类) for 属主类
end;
举例
type
TTriangle = class
procedure F1;
end;
T1_Hlp = class helper for TTriangle
procedure F1;
procedure F2;
end;
T1_Helper = class helper for TTriangle
class var
s: String;
procedure F1;
procedure F2;
end;
var
obj: TTriangle;
begin
obj := TTriangle.Create;
obj.F1;
obj.F2;
FreeAndNil(obj);
end.
调用的都是T1_Helper的F1和F2,因为就近原则;
辅助类不能单独使用,故不能定义对象字段,只能定义类字段;
一个类可以有多个辅助类,使用辅助类中的成员时,所用到的辅助类是源码中距离当前调用最近的一个辅助类;
当主类和辅助类有相同成员时,优先使用辅助类的成员;
6.7 对象引用和类引用
类引用是一种数据类型,代表一个类和其他类的不同部分;
class reference,类特征;
type
TClass = class of TObject;
TClass就是TObject的一个类引用;
类引用可以引用类中的所有的类方法和类字段,但不能通过类引用使用对象方法和对象字段;
type
TClass = class of TObject;
MyClass = class(TObject);
end;
MyClsRef = class of MyClass;
var
o1: TClass;
o2: MyClsRef;
begin
o1 := o2;
end.
注:TObject是所有类的祖先类,所以对TObject类型的类引用TClass,可以将任何类的类引用变量作为值赋给它;
由于类的特异性代表了某个类,所以它可以用在某些需要将类而不是对象作为实际参数的场合;
此时,应当将参数类型声明为TObject类型,如果这个函数需要接受所有的类作为实际参数,就可以使用TClass作为参数类型;
举例
procedure GetClassName(cls: TClass);
begin
Writeln(cls.ClassName);
end;
begin
GetClassName(TObject);
Readln;
end.
对象的名称仅仅只代表一个指向对象实体的引用而不代表对象实体;
delphi的对象在分配内存的时候采用堆分配的方式,声明一个对象变量只是在栈中留下一个指针
obj := M1.Create;
这样其实是两步,创建一个M1类型的对象;
返回一个指向这个对象的指针并赋予obj;
堆中的对象不会自动销毁,所以对象在不被需要的时候应当由用户手动调用析构函数来销毁内存,否则由内存泄漏的可能;
堆中的对象被销毁以后,栈中的对象变量依然指向这个位置,最好手动设置为nil;
obj.Free;
obj := Nil;
也可以
FreeAndNil(obj);
Delphi提供了两个操作符,as和is,用于对象的转型和对象的类型判断;
is操作符
var
obj: TObject;
begin
obj := TObject.Create;//tag
if obj is TObject then
showmessage('ok');
else
showmessage('no');
FreeAndNil(obj);
end.
显示ok,将tag注释掉,显示no;
将非对象的值进行is操作会无法编译;
as操作符
var
O1: TObject;
obj: TButton;
...
O1 := obj as TObject;
...
这里TButton转成了TObject类型;
as操作实际上是强行截取对象范围,转换后编译器将obj的实体缩小到从TObject中声明的成员,然后将这个引用赋给O1;
如果TButton中改写了TObject中的虚方法,O1引用的是TButton中改写的方法
举例
type
T1 = class(TObject)
procedure M1;virtual;
end;
T2 = class(T1)
procedure M1;override;
end;
procedure T1.M1;
begin
Writeln('T1.M1');
end;
procedure T2.M1;
begin
Writeln('T2.M1');
end;
var
O1: T1;
O2: T2;
begin
O2 := T2.Create;
O1 := O2 as T1;
O1.M1;
Readln;
O2.Free;
o1 := nil;
end.
显示T2.M1;寻找Free时,只有TButton中找不到这个方法,才会祖先类中寻找Free;
TButton中改写的方法也是这样,会被优先选择;
self参数
举例
uses
SysUtils, Classes;
function TList.GetEnumerator: TListEnumerator;
begin
Result := TListEnumerator.Create(Self);
end;
var
obj: TList;
begin
obj := TList.Create;
obj.GetEnumerator;
end.
这里用self作为参数调用构造函数创建了一个TListEnumerator对象,self指向调用当前方法GetEnumerator的TList类对象;
在主程序中以obj调用GetEnumerator方法:
TListEnumerator.Create(Self);
这里self指obj;
类中声明的所有方法(静态的类方法除外),均有一个隐含的参数self,类方法中的self隐含参数指向当前类本身;
对象方法中的self隐含参数则指向正在调用这个方法的对象实体;
6.8其他的对象类型
高级记录类型和类的区别:
1.记录类型不支持继承;
2.记录可以含有变体部分,类不能
3.记录类型是值类型,类是引用类型;
记录类型通过复制来进行值传递,记录在栈中分配(除非是全局的记录类型或者手动分配到堆中),
所以记录类型的对象在声明后可以直接使用,无需构造函数;
4.类不支持运算符重载,记录支持;
5.记录类型可以声明一个由参数的构造函数,但是不能声明任何析构函数;
6.记录类型中不能有任何形式的动态绑定的方法,即不能有虚方法、动态方法、消息方法;
7.记录类型不能实现接口;
object不推荐使用,仅仅是为了兼容,功能上也比较弱;
object类型和class的区别:
1.省略父类名称时,class默认继承TObject,object没有父类,不含有任何成员
2.object变量声明即可使用,无需构造函数,实际上也没有构造函数,也可以使用new和dispose来创建和销毁这类对象;
3.object对象中不允许声明published成员;
6.9多态
面向对象的三大特性:封装,继承,多态;
多态是指将子类的对象赋给父类对象后,父类对象表现出相应的子类行为;
举例
unit Unit3;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm3 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
end;
TGreed = class
procedure Greed; virtual; abstract;
end;
TCHGreen = class(TGreed)
procedure Greed; override;
end;
TENGreen = class(TGreed)
procedure Greed; override;
end;
TJPGreen = class(TGreed)
procedure Greed; override;
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure TForm3.Button1Click(Sender: TObject);
var
obj: TGreed;
begin
obj := TCHGreen.Create;
obj.Greed;
FreeAndNil(obj);
end;
procedure TForm3.Button2Click(Sender: TObject);
var
obj: TGreed;
begin
obj := TJPGreen.Create;
obj.Greed;
FreeAndNil(obj);
end;
procedure TForm3.Button3Click(Sender: TObject);
var
obj: TGreed;
begin
obj := TENGreen.Create;
obj.Greed;
FreeAndNil(obj);
end;
procedure TForm3.FormCreate(Sender: TObject);
begin
// do something
end;
{ TCHGreen }
procedure TCHGreen.Greed;
begin
ShowMessage('zao');
end;
{ TENGreen }
procedure TENGreen.Greed;
begin
ShowMessage('zaoshang');
end;
{ TJPGreen }
procedure TJPGreen.Greed;
begin
ShowMessage('zaoshanghao');
end;
end.