类
类
类的声明
类的声明格式如下:
1 | type |
类的方法声明与实现必须分开来写。方法声明与实现必须在同一个 .pas
文件中。
类的前置声明
类的前置声明,允许类的真实声明延后:
1 | type |
创建对象
Create 为类的构造函数,即使没有声明也会有一个默认隐含的构造函数。
Delphi 中使用类中隐含的构造函数来创建一个新的对象,其语句格式如下:
1 | var 对象名称 := 类名.create; |
释放对象
Free
为隐含的用于释放对象的方法。(其实 Free
来自 TObject
)
调用 Free
可以释放一个对象。由 Create
创建的对象,就需要用 Free
进行释放,以免发生内存泄漏。
Free 的实现:
1 | procedure TObject.Free; |
使用样例:
1 | AObj = AClass.Create; // 创建对象 |
不要用赋值为 nil
的方式来释放对象,这样会导致内存泄漏。如果想释放对象,并将变量名设置为 nil
,可以使用 FreeAndNil
:
1 | AObj = AClass.Create; // 创建对象 |
其实 FreeAndNil
也是调用了 Free
来实现:
1 | procedure FreeAndNil(var obj) |
DisposeOf
方法:
DisposeOf
方法也可以释放,但是这个方法已废弃,请直接用 Free
。
访问权限
strict private
: 此区域成员只有当前类可以访问strict protected
: 此区域成员只有当前类和派生来可以访问private
: 本 unit 单元可访问protected
: 本 unit 单元和派生类可访问public
: 公开可以访问(默认值)published
: 同 public,但是 IDE 可以自定编辑此区内容automated
: 用于 Win32 下的 COM 编程
嵌套类
嵌套类是指在类中定义类。
例如:
1 | type ClassName1 = class |
上述 InnerClassName1 是 ClassName1 的嵌套类。
- 类中的标识符只能被本类和本类的嵌套类使用。
- 类不能使用嵌套类类的标识符
继承
类的继承的声明方式如下:
1 | type |
封闭类
封闭类的声明方式如下:
1 | type |
封闭类不允许继承派生子类。
类字段与类方法
在类中,即可由对象名访问,也可以由对象来访问的字段叫类字段;对应的既能通过类名访问,也能用通过对象来访问的方法,叫类方法。
1 | type |
类字段与类方法,作用相当于 C++/C#
中的静态成员和静态方法。
普通类方法
普通类方法声明的前面加上 class
关键词做限定。声明如上。
普通类方法可以访问类的静态成员。
普通类方法带有一个隐藏的 self 参数,self 指向调用此方法的对象或类本身。
当类调用类方法时,其中的 self 指向类本身,而通过对象来调用时则指向对象。
静态类方法
静态类方法的声明除了前面加上 class
关键词做限定,后面还加上 static
来做限定。
例如:
1 | type |
类静态方法没有引含的 self 参数
对象字段
在类中,只能通过创建对象之后,由对象来访问的字段叫对象字段;对应的只能用通过对象来访问的方法,叫对象方法。
1 | type |
对象方法
对象方法,是指类必须经过实例化,创建对象后,由对象名称来访问的方法。
对象方法可以加上一些限定词,下面五组限定词,其中同一组的不能同时出现两个或者两个以上:
限定词 |
---|
reintroduce |
overload |
virtual 、 dynamic 、 override |
register 、 pascal 、 cdecl 、 stdcall 、 safecall |
abstract |
普通对象方法
不加限定词修饰的对象方法,为普通对象方法。(调用约定限定词除外)
普通对象方法,也叫静态对象方法,可以加上 static
限定词修饰,但是通常 static
省略。(此处静态方法的含义跟 C++/C# 的严重不一样)
虚方法
虚方法后面加上 virtual
限定词修饰。虚方法会和派生类的重写虚方法会放置进虚函数表(VMT)中。
派生类如果需要重写虚方法,必须加上 override
限定词指定重写。
通过虚方法,基类的引用可以调用到派生类中重写的方法。
基类有多少个虚方法,就会有多大的 VMT,派生类就会同样存在至少跟基类大小的 VMT。
动态方法
动态方法后面加上 dynamic
限定词修饰。
派生类如果需要重写动态方法,必须加上 override
限定词指定重写。
动态方法在使用上和虚方法是一样,但是内部存储的方式有一点不一样。动态方法是存储在动态方法表(DMT)中。
派生类中的 DMT,只会存储 override
重写的动态方法和自身的动态方法,不会存储基类的动态方法,还有一个指针指向父类的 DMT。
区别:
- 虚方法的 VMT 访问速度快,但是消耗的内存空间较多
- 动态方法 DMT 访问速度稍慢,但是消耗的内存空间较少(它访问未重写的父类的动态方法需要先找到父类的DMT)
抽象方法
抽象方法后面加上 abstract
限定词修饰。
抽象方法同时还要加上 virtual
或者 dynamic
来限定。
1 | 普通方法声明; virtual 或 dynamic; abstract; |
抽象方法只声明,不需要实现。抽象方法必须重写才能使用。
消息方法
消息方法的定义方式:
1 | procedure 方法名称(var 参数名:消息类型); message 消息 ID; |
例如:
1 | procedure WMClose(var MSG: TMESSAGE); message WM_CLOSE; |
消息方法必须是一个过程,而且参数格式固定。后面带 message 限定词,message 后加上消息ID (整数)。
方法重载
有两个或者以上同名的方法,成为重载。方法重载必须在后面加上 overload
指示字做限定。
- 方法重载必须加
overload
- 方法重载是以参数类型或者个数不一样为依据,返回值不同不作为方法重载的依据
调用父类同名方法
inherited
这个关键词来调用父类的同名方法。
最终方法
重写虚方法或者动态方法使用 override
限定词,如果想要不允许派生类再次重写,可加 final
限定词来限制派生类重写。例如:
1 | procedure func1 (参数列表); override; final; |
重新定义与重新引入
overload
指示字表示定义一个重载的同名方法。可以重载本类或基类的同名方法,只要参数类型或者个数不同即可。
如果基类已有一个方法,派生类利用 overload
重载了此方法,而且参数类型与个数一样,则相当于重新定义了这个方法,会直接覆盖掉基类的同名方法。此为 重新定义
。
如果基类存在一个虚方法,派生类利用 overload
重载了此方法,如下:
1 | type |
上面代码编译器会给出警告信息,提示派生类的方法会把基类的 virtual
虚拟方法覆盖隐藏。
可以加入 reintroduce
指示字来告知编译器,你的真实意图:
1 | type |
reintroduce
表示重新引入一个方法。
reintroduce
还可以改变访问权限,基类的 private
的方法,可以利用 reintroduce
重新定义引入新方法并声明为 public
构造函数与析构函数
构造函数声明用 constructor
关键字,格式如下:
1 | constructor create (参数列表); |
一般用 Create 的名称,因为 TObject 用的就是 Create。 也可以用其他名称,例如:
1 | constructor TDate.CreateFromValues (M, D, Y: Integer); |
构造函数可以同名重载,重载时在后面加 overload
指示字限定。
调用基类的构造函数采用关键字 inherited
,例如:
1 | constructor T1.create (参数列表); |
析构函数声明用 destructor
关键字,不能带参数,不能重载,格式如下:
1 | destructor Destroy; override; // 析构函数在 TObject 声明为虚方法,加 override 重写 |
析构函数会将对象占用的内存释放。不要试图直接调用析构函数。用 Free
或者 FreeAndNil
代替。
属性
普通属性
属性使用关键字 property
,属性的声明形式:
1 | property 名称: 类型 read 读方法 write 写方法; |
- 读方法表示读取属性的方法,由
read
指定 - 写方法表示写入属性的方法,由
write
指定
例如:
1 | property pAge:integer read GetAge write SetAge |
上例中,读取属性调用 GetAge 方法,写入属性调用 SetAge 方法。
属性中有效的限定词有:
read
, write
, stored
, default
, nodefault
, implements
read
为一个字段,字段类型必须与属性相同read
为一个方法,不能带参数而且返回值必须跟属性相同,不能有参数而且不能是动态或者虚方法重写或重载write
只有一个参数而且参数类型跟属性相同,只能值传递或者const
传递published
区域的属性,其对应方法必须register
调用约定implements
用于接口的代理实现
数组属性
数组属性的声明形式:
1 | property 属性名称 [索引名称: 索引类型]: 属性类型 访问符; |
注意参数类型需要与属性声明一致,例如:
1 | // 属性读写方法声明 |
限定词 default
可以将属性声明为对象的默认属性:
1 | property Pixel[x,y: integer]: TColor read GetColor write SetColor; default; |
Index 限定符
Index
限定符可以将不同名称的属性映射到同一个内部方法中。举例:
1 | type |
上例中,Border1、Border2、Border3 为不同的属性名称,都是采用同一个 read
和 write
方法,通过添加 index
限定符来实现。
存储限定符
属性的存储限定符有:stored
、 default
、 nondefault
作用:与 RAD IDE 配合,决定 published 区域的属性的值是否被存储至窗体文件中
(1)stored
stored
后带:
False
True
(默认值)- 当前类或祖先类中所含有的
Boolean
类型的字段名称 - 当前类或祖先类中所含有的函数名,此函数不接受任何参数且其返回值必须为
Boolean
(2)default
default
限定词后接一个与属性同类型的常数,用于标识一个属性的缺省值
(3)nondefault
nondefault
用于子类中取消某个在父类中声明的属性的缺省值
属性改写与重定义
子类可以对父类继承过来的属性进行改写:
可以通过下面指示字进行改写:read
, write
, stored
, default
, nodefault
- 改变访问方法
- 改变访问权限(只能由 private、protected 改 public、published,不能把权限改小)
- 改缺省值
改写属性会隐藏祖先类中的同名属性。所有的属性与对象均是静态绑定。
类属性
类属性使用 class property 来声明:
1 | class property 名称: 类型 限定符; |
- 类属性的限定符不能
stored
或default
。其读写方法必须是当前类中声明的类字段、类方法 - 类属性不能在
published
区域中声明
安全类型转换
每个对象在运行时都能知道自己的类型和继承的父类型。 is 运算符可以判断对象是否属于某种类型:
1 | if FMyAnimal is TDog then |
is
运算符返回 True
或 False
。 用于判断对象是否属于某种类型或属于某种父类型。
上述例子的代码也可以用 as
运算符缩小:
1 | MyDog := FMyAnimal as TDog; |
或者
1 | (FMyAnimal as TDog).Eat; |
如果 as 转换遇到不兼容的类型转换,则会抛出 EInvalidCast
异常。(不同于 C#,在 C#里面的 as 表达式在型别不兼容的时候,会返回 null)
动态创建组件
组件可以用 Self
关键字来表示自己本身。
组件的 Parent
属性表示自己依附于哪个上级组件,只要指定了上级组件,它的所有权和生命周期就是受到上级组件的自动管辖。如果它失去了所有权(从父组件中移除),该组件也会被释放掉。
下面代码展示如何动态创建一个组件:
1 | // 窗体的鼠标点击方法: |
类引用
类引用其实是一种数据类型,它描述本类跟其他类的不同部分的特征。
类引用类型的声明语法为:
1 | type |
例如下面的代码中声明了 TObject 类的类引用 TClass:
1 | type |
任何类的类引用都可以赋值给 TClass 的类引用。
例如:
1 | procedure GetClassName(cls: TClass); |