2.封装
通过上一节类与对象的基本概念我们了解到,对象是类的实例,是计算机系统内存中动态分配的一块内存空间,其结构由类定义。在本节中,我们通过对对象进行进行封装,从而形成类。在Delphi中,对象使用构造函数创建,使用析构函数删除。对象封装时,还可以针对属性和方法进行访问控制。
2.1构造函数
在Delphi中,类的对象并不真正保存数据,它只是一个指针,指向类数据在内存中的实际地址。所以在定义类对象时并没有真正地创建对象,而只是一个指针。要创建类的对象,可以调用其类的Create方法,该方法为对象分配内存并进行数据初始化,该方法也就是类的构造函数。在类定义中,使用关键字constructor为类声明构造函数。格式如下:
Type <类名> = Class <数据成员1>: <数据类型1>; <数据成员1>: <数据类型1>; ...... <数据成员1>: <数据类型1>; Constructor Create([参数列表]); Procedure <过程名>([参数列表]); ...... Function <函数名>([参数列表]):<返回类型>; ...... end; Constructor <类名>.Create([参数列表]); ...... Procedure <类名>.<过程名>([参数列表]); ...... Function <类名>.<函数名>([参数列表]):<返回类型>; ......
例如:
Type TStudent = Class name: string; sex: string; chinese: real; math: real; english: real; Constructor Create(name: string; sex: string; chinese: real; math: real; english: real); Function sum(): real; Function avg(): real; End; Constructor TStudent.Create(name: string; sex: string; chinese: Real; math: Real; english: Real); begin self.name := name; self.sex := sex; self.chinese := chinese; self.math := math; self.english := english; end; Function TStudent.sum; begin result := chinese + math + english; end; Function TStudent.avg; begin result := (chinese + math + english) / 3; end;
在创建对象(类的实例)时,就可以通过Create方法来创建,如:
var student1, student2: TStudent; begin student1 := TStudent.Create('zhaohb', 'girl', 98.5, 97, 99); student2 := TStudent.Create('liuqiang', 'boy', 92.5, 93.5, 95); end.
构造函数一般由类来调用,而不是由类的对象调用。当通过类调用构造函数时,Delphi会自动为对象分配内存。也可以使用类对象调用构造函数,但这时不会进行任何内存分配工作。
在实际开发时,上面的代码在程序结束时没有释放创建的对象,要释放对象,就要使用析构函数。
2.2析构函数
可以通过Free方法来实现对象的析构,Free是TObject中的一个对象方法,可以使用关键字 destructor 为类声明析构函数,一般用来在类释放时清除资源。格式如下:
Type <类名> = Class <数据成员1>: <数据类型1>; <数据成员1>: <数据类型1>; ...... <数据成员1>: <数据类型1>; Constructor Create([参数列表]); Procedure <过程名>([参数列表]); ...... Function <函数名>([参数列表]):<返回类型>; ...... Destructor Free; end; Constructor <类名>.Create([参数列表]); ...... Procedure <类名>.<过程名>([参数列表]); ...... Function <类名>.<函数名>([参数列表]):<返回类型>; ...... Destructor <类名>.Free; ......
只有在对象不是nil时才可以调用析构函数,另外,Free不会自动将对象设置为nil。
例如:
Type TStudent = Class name: string; sex: string; chinese: real; math: real; english: real; Constructor Create(name: string; sex: string; chinese: real; math: real; english: real); Function sum(): real; Function avg(): real; Destructor Free; End; Constructor TStudent.Create(name: string; sex: string; chinese: Real; math: Real; english: Real); begin self.name := name; self.sex := sex; self.chinese := chinese; self.math := math; self.english := english; end; Function TStudent.sum; begin result := chinese + math + english; end; Function TStudent.avg; begin result := (chinese + math + english) / 3; end; Destructor TStudent.Free; begin end;
当使用完对象后,就可以使用Free进行析构,上面的代码中,析构函数没有任何语句,是因为在代码中没有使用到资源,如果使用了资源,比如使用了指针,或者是数据库连接等等,在析构函数中,应该进行处理。像上面的代码实际开发中,可以不定义Free,这样的话,我们在使用完对象后,调用析构函数进行对象的释放,实际上是调用TObject的Free方法。例如:
var student1, student2: TStudent; begin student1 := TStudent.Create('zhaohb', 'girl', 98.5, 97, 99); student2 := TStudent.Create('liuqiang', 'boy', 92.5, 93.5, 95); writeln(student1.name, ' ', student1.sex, ' ', student1.sum:5:1, ' ', student1.avg:5:1); writeln(student2.name, ' ', student2.sex, ' ', student2.sum:5:1, ' ', student2.avg:5:1); student1.Free; student2.Free; end.
注:类只能在程序或者单元文件的最外层的Type语句中声明,在变量声明部分或者过程、函数中不能声明类。
2.3访问控制
Object Pascal 提供了5种访问控制符:public、private、protected、published和automated。
2.3.1 public
关键字 public 指定的字段和方法是公有的,也就是说,可以被类外部的程序访问。在编写代码时,关键字 public 之后声明的字段和方法都是公有的,直到遇到另一种访问控制符为止。例如:
Type TStudent = Class public name: string; sex: string; chinese: real; math: real; english: real; Constructor Create(name: string; sex: string; chinese: real; math: real; english: real); Destructor Free; Function sum(): real; Function avg(): real; End;
2.3.2 private
关键字 private 指定的字段和方法是私有的,私有成员不能被类所在单元之外的程序访问,即,只有在包含这个类的单元中,才可以对该类的私有成员进行访问。例如:
Type TStudent = Class private name: string; sex: string; chinese: real; math: real; english: real; public Constructor Create(name: string; sex: string; chinese: real; math: real; english: real); Destructor Free; Procedure SetName(name: string); Function GetName():string; Function sum(): real; Function avg(): real; End;
在上面的代码中,将 TStudent 类的所有字段定义为私有成员,所有的方法定义为公有的;这样,在不同的单元中就不能直接访问字段数据,而需要通过方法来访问字段。
2.3.3 protected
关键字 protected 定义保护字段和方法。保护成员可以被该类的所有派生类访问,并成为派生类的私有成员。使用派生类时,无法访问保护成员。
2.3.4 published
关键字 published 声明发行类型的成员。发行成员的可见性是最高的,公有成员在运行期间是可以访问的,发行成员在设计期间和运行期间都是可以访问的。
一般用于组件类的声明中。组件的属性在对象编辑器中可以访问就是在组件设计时采用了发行成员。
2.3.5 automated
关键字 automated 与 public 基本相同。唯一的区别就是在声明为 automated 的属性和方法会自动生成类型信息。主要用于创建OLE自动化服务器。
2.4 类的封装示例
一般情况下,在程序中常用的访问控制类型就是Public和Private两种类型。下面我们演示在一个 单元中定义一个类,在本单元中访问和在另一个单元中访问。
1.创建一个VCL Forms Application
2.界面设计如下图:
3.在窗体所在单元文件Unit1.pas的interface部分定义类TStudent,代码如下:
TStudent = class private Name: string; public constructor Create; procedure SetName(nm: string); function GetName: string; end;
4.在 Unit.pas 的 implementation 部分实现 TStudent 类的方法,代码如下:
constructor TStudent.Create; begin Name := ''; end; procedure TStudent.SetName(nm: string); begin Name := nm; end; function TStudent.GetName; begin GetName := Name; end;
5.实现界面中按钮的事件,代码如下:
procedure TForm1.Button1Click(Sender: TObject); var student: TStudent; begin student := TStudent.Create; student.Name := '苏曼'; label1.Caption := student.Name; end; procedure TForm1.Button3Click(Sender: TObject); begin form1.Close; end;
6.选择File -> New -> Form,创建一个新的窗体,保存为 Unit2.pas,界面如下:
7.选择 Form1 窗体,然后选择 File -> Use Unit,选择 Unit2,这样在 Unit1.pas 单元文件中就会自动添加一条语句:
uses Unit2;
8.编写 Form1 窗体的 “打开新窗口” 按钮单击事件,代码如下:
procedure TForm1.Button2Click(Sender: TObject); begin form2.Show; end;
9.同样,在Form2窗体中使用Unit1,或者手工将 "uses unit1" 添加到 Unit1.pas文件中。
10.编写 Form2 窗体创建事件,将Form1的“显示学生姓名”按钮单击事件复制过来,如下图:
我们可以看到:程序出错了,原因就是在Unit2中访问了Unit1中TStudent类定义的私有属性,修改如下:
procedure TForm2.FormCreate(Sender: TObject); var student: TStudent; begin student := TStudent.Create; student.SetName('赵波'); label1.Caption := student.GetName; end;
11.最后,编写 Form2 的关闭按钮事件,代码如下:
procedure TForm2.Button1Click(Sender: TObject); begin form2.Close; end;