类的声明

类的声明格式如下:

1
2
3
4
type
MyClass = class
//声明成员
end;

类的方法声明与实现必须分开来写。方法声明与实现必须在同一个 .pas 文件中。

类的前置声明

类的前置声明,允许类的真实声明延后:

1
2
3
4
5
6
7
8
9
10
type
ClassName2 = class; // ClassName2 为前置声明,实际声明在后面

ClassName1 = class
member1: ClassName2; // 成员为 ClassName2 类型
end;

ClassName2 = class // 这里才是 ClassName2 的声明
member2: integer;
end;

创建对象

Create 为类的构造函数,即使没有声明也会有一个默认隐含的构造函数。

Delphi 中使用类中隐含的构造函数来创建一个新的对象,其语句格式如下:

1
2
3
4
5
var 对象名称 := 类名.create;

// 或者

var 对象名称 := 类名.create(参数列表);

释放对象

Free 为隐含的用于释放对象的方法。(其实 Free 来自 TObject

调用 Free 可以释放一个对象。由 Create 创建的对象,就需要用 Free 进行释放,以免发生内存泄漏。

Free 的实现:

1
2
3
4
5
procedure TObject.Free;
begin
if Self <> nil then
Destroy;
end;

使用样例:

1
2
3
AObj = AClass.Create;           // 创建对象
// do work
AObj.Free(); // 释放

不要用赋值为 nil 的方式来释放对象,这样会导致内存泄漏。如果想释放对象,并将变量名设置为 nil,可以使用 FreeAndNil

1
2
3
AObj = AClass.Create;           // 创建对象
// do work
FreeAndNil(AObj); // 释放

其实 FreeAndNil 也是调用了 Free 来实现:

1
2
3
4
5
6
7
8
procedure FreeAndNil(var obj)
var
temp: TObject;
begin
temp := TObject(obj);
Pointer(obj) := nil;
temp.Free;
end;

DisposeOf 方法:

DisposeOf 方法也可以释放,但是这个方法已废弃,请直接用 Free

访问权限

  • strict private : 此区域成员只有当前类可以访问
  • strict protected : 此区域成员只有当前类和派生来可以访问
  • private : 本 unit 单元可访问
  • protected : 本 unit 单元和派生类可访问
  • public : 公开可以访问(默认值)
  • published : 同 public,但是 IDE 可以自定编辑此区内容
  • automated : 用于 Win32 下的 COM 编程

嵌套类

嵌套类是指在类中定义类。

例如:

1
2
3
4
5
6
7
8
type ClassName1 = class
type InnerClassName1 = class
innerMember: integer;
end;

member0: integer;
obj1 : InnerClassName1;
end;

上述 InnerClassName1 是 ClassName1 的嵌套类。

  • 类中的标识符只能被本类和本类的嵌套类使用。
  • 类不能使用嵌套类类的标识符

继承

类的继承的声明方式如下:

1
2
3
4
type
ClassName = class (父类名称)
// 成员声明
end;

封闭类

封闭类的声明方式如下:

1
2
3
4
type
ClassName = class sealed (父类名称)
// 成员声明
end;

封闭类不允许继承派生子类。

类字段与类方法

在类中,即可由对象名访问,也可以由对象来访问的字段叫类字段;对应的既能通过类名访问,也能用通过对象来访问的方法,叫类方法。

1
2
3
4
5
6
type
ClassName = class (父类名称)
class var member1: integer; // 类字段 (必须加var)
class property prop1 ..... ; // 类属性 (必须加property)
class procedure func1(param1: SmallInt); // 类方法
end;

类字段与类方法,作用相当于 C++/C# 中的静态成员和静态方法。

普通类方法

普通类方法声明的前面加上 class 关键词做限定。声明如上。

普通类方法可以访问类的静态成员。

普通类方法带有一个隐藏的 self 参数,self 指向调用此方法的对象或类本身。
当类调用类方法时,其中的 self 指向类本身,而通过对象来调用时则指向对象。

静态类方法

静态类方法的声明除了前面加上 class 关键词做限定,后面还加上 static 来做限定。

例如:

1
2
3
4
type
T1 = class
class function F2(var s:string): integer; static;
end;

类静态方法没有引含的 self 参数

对象字段

在类中,只能通过创建对象之后,由对象来访问的字段叫对象字段;对应的只能用通过对象来访问的方法,叫对象方法。

1
2
3
4
5
type
ClassName = class (父类名称)
member1: integer; // 对象字段
procedure func1(param1: SmallInt); // 对象方法
end;

对象方法

对象方法,是指类必须经过实例化,创建对象后,由对象名称来访问的方法。

对象方法可以加上一些限定词,下面五组限定词,其中同一组的不能同时出现两个或者两个以上:

限定词
reintroduce
overload
virtualdynamicoverride
registerpascalcdeclstdcallsafecall
abstract

普通对象方法

不加限定词修饰的对象方法,为普通对象方法。(调用约定限定词除外)

普通对象方法,也叫静态对象方法,可以加上 static 限定词修饰,但是通常 static 省略。(此处静态方法的含义跟 C++/C# 的严重不一样)

虚方法

虚方法后面加上 virtual 限定词修饰。虚方法会和派生类的重写虚方法会放置进虚函数表(VMT)中。

派生类如果需要重写虚方法,必须加上 override 限定词指定重写。

通过虚方法,基类的引用可以调用到派生类中重写的方法。

基类有多少个虚方法,就会有多大的 VMT,派生类就会同样存在至少跟基类大小的 VMT。

动态方法

动态方法后面加上 dynamic 限定词修饰。

派生类如果需要重写动态方法,必须加上 override 限定词指定重写。

动态方法在使用上和虚方法是一样,但是内部存储的方式有一点不一样。动态方法是存储在动态方法表(DMT)中。

派生类中的 DMT,只会存储 override 重写的动态方法和自身的动态方法,不会存储基类的动态方法,还有一个指针指向父类的 DMT。

区别:

  • 虚方法的 VMT 访问速度快,但是消耗的内存空间较多
  • 动态方法 DMT 访问速度稍慢,但是消耗的内存空间较少(它访问未重写的父类的动态方法需要先找到父类的DMT)

抽象方法

抽象方法后面加上 abstract 限定词修饰。

抽象方法同时还要加上 virtual 或者 dynamic 来限定。

1
普通方法声明; virtualdynamic; 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
2
3
4
5
6
7
8
type
TMyClass = class
procedure One;
end;

TMySubClass = class (TMyClass)
procedure One; overload;
end;

上面代码编译器会给出警告信息,提示派生类的方法会把基类的 virtual 虚拟方法覆盖隐藏。

可以加入 reintroduce 指示字来告知编译器,你的真实意图:

1
2
3
4
5
6
7
8
type
TMyClass = class
procedure One;
end;

TMySubClass = class (TMyClass)
procedure One; reintroduce; overload; // 加入了 reintroduce,不会警告
end;

reintroduce 表示重新引入一个方法。

reintroduce 还可以改变访问权限,基类的 private 的方法,可以利用 reintroduce 重新定义引入新方法并声明为 public

构造函数与析构函数

构造函数声明用 constructor 关键字,格式如下:

1
constructor create (参数列表);

一般用 Create 的名称,因为 TObject 用的就是 Create。 也可以用其他名称,例如:

1
constructor TDate.CreateFromValues (M, D, Y: Integer);

构造函数可以同名重载,重载时在后面加 overload 指示字限定。

调用基类的构造函数采用关键字 inherited,例如:

1
2
3
4
5
constructor T1.create (参数列表);
begin
inherited Create; // 调用基类的构造器来构造基类部分
// 派生类构造器的其余代码
end;

析构函数声明用 destructor 关键字,不能带参数,不能重载,格式如下:

1
2
3
4
5
6
7
destructor Destroy; override;   // 析构函数在 TObject 声明为虚方法,加 override 重写

destructor T1.Destroy;
begin
inherited;
FileClose(F);
end;

析构函数会将对象占用的内存释放。不要试图直接调用析构函数。用 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
2
3
4
5
6
7
8
9
// 属性读写方法声明
function GetColor(x,y: integer): TColor;
procedure SetColor(x,y: integer; value: TColor);

// 属性的声明
property Pixel[x,y: integer]: TColor read GetColor write SetColor;

// 属性的访问:
obj.Pixel[128,128] := Green;

限定词 default 可以将属性声明为对象的默认属性:

1
2
3
4
property Pixel[x,y: integer]: TColor read GetColor write SetColor; default;
// default 使得 可以使用属性名来访问,还可以直接使用对象名来访问
obj.Pixel[128, 128] := Green;
obj[128, 128] := Green; // 直接用对象名访问

Index 限定符

Index 限定符可以将不同名称的属性映射到同一个内部方法中。举例:

1
2
3
4
5
6
7
8
9
10
11
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;

上例中,Border1、Border2、Border3 为不同的属性名称,都是采用同一个 readwrite 方法,通过添加 index 限定符来实现。

存储限定符

属性的存储限定符有:storeddefaultnondefault

作用:与 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 名称: 类型 限定符;
  • 类属性的限定符不能 storeddefault。其读写方法必须是当前类中声明的类字段、类方法
  • 类属性不能在 published 区域中声明

安全类型转换

每个对象在运行时都能知道自己的类型和继承的父类型。 is 运算符可以判断对象是否属于某种类型:

1
2
3
4
5
if FMyAnimal is TDog then
begin
MyDog := TDog (FMyAnimal); // 对象转换为目标类型再使用
Text := MyDog.Eat;
end;

is 运算符返回 TrueFalse。 用于判断对象是否属于某种类型或属于某种父类型。

上述例子的代码也可以用 as 运算符缩小:

1
2
MyDog := FMyAnimal as TDog;
Text := MyDog.Eat;

或者

1
(FMyAnimal as TDog).Eat;

如果 as 转换遇到不兼容的类型转换,则会抛出 EInvalidCast 异常。(不同于 C#,在 C#里面的 as 表达式在型别不兼容的时候,会返回 null)

动态创建组件

组件可以用 Self 关键字来表示自己本身。

组件的 Parent 属性表示自己依附于哪个上级组件,只要指定了上级组件,它的所有权和生命周期就是受到上级组件的自动管辖。如果它失去了所有权(从父组件中移除),该组件也会被释放掉。

下面代码展示如何动态创建一个组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 窗体的鼠标点击方法:
procedure TForm1.FormMouseDown (Sender: TObject;
Button: TMouseButton;
Shift: TShiftState;
X, Y: Integer);
var
Btn: TButton;
begin
Btn := TButton.Create (Self); // 创建一个按钮组件
Btn.Parent := Self; // 把按钮组件的 Parent 设置为窗体本身(Self代表窗体组件)
Btn.Position.X := X;
Btn.Position.Y := Y;
Btn.Height := 35;
Btn.Width := 135;
Btn.Text := Format ('At %d, %d', [X, Y]);
end;

类引用

类引用其实是一种数据类型,它描述本类跟其他类的不同部分的特征。

类引用类型的声明语法为:

1
2
type
类引用名称 = class of 类名称;

例如下面的代码中声明了 TObject 类的类引用 TClass:

1
2
type
TClass = class of TObject;

任何类的类引用都可以赋值给 TClass 的类引用。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
procedure GetClassName(cls: TClass);
begin
writeln(cls.ClassName);
end;

type
MyClassName = class
end;

begin
GetClassName(MyClassName); //用于显示参数的类名称
readln;
end.