1.抽象 (相当于制造前 画图纸/捏造/想象对象的阶段,还没开始造)
1.1.类在 type 之下定义.
1.2.类的结构:
type
TPerson = class
end;
TPerson,你可以随便取,但应该符合命名规范.前面的T 为类名约定前缀,你可以不要.
2.实例化.(把抽象好的对象制造出来)所有的类都需要实例化才能使用.
2.1.如果你的类,抽象在A单元(窗体)中,想要在B单元(窗体)中使用,必须要先在B单元(窗口)的implementation下面引用A单元才行,不然程序找不到你说的类
implementation //在这里引用, A 只能被本单元使用.即作用域为局部的.如果需要作为全局变量来使用,请把 A 放到顶部 interface 下的 Uses 下面
uses A;
2.2.我们添加了一个button,在它的事件里面使用这个类:
procedure TForm1.Button1Click(Sender: TObject);
var
yadang: TPerson;
begin
yadang := TPerson.Create; //Create 制造实例
//some code
yadang.free; //有Create ,就要有free (销毁,释放内存).
end;
3.销毁实例.
上一段代码中,我们用TPerson这个类,制造出了一个真实存在的对象 yadang.然后yadang经历了他的一生,最后被 free 销毁掉了,尘世间只留下yadang的事迹,不再有yadang这个对象(释放内存).当然这里要留意一下,这里的yadang是个局部变量,在过程结束前free掉就可以了,但如果yadang是个全局变量,我们应该如何销毁呢?
var
Form1: TForm1;
yadang: TPerson; //声明为全局变量
implementation
{$R *.dfm}
//在窗口被销毁前,销毁yadang
procedure TForm1.FormDestroy(Sender: TObject);
begin
if yadang <> nil then //这里要做个判断,如果yadang没有被create,则销毁一个不存在的对象,程序会出错
yadang.Free;
end;
4.类的数据成员.
4.1.给类添加属性.在上面1.2里,我们认识了类的结构,现在我们丰富它的属性,当然,这还是处于 '纸上谈兵' 的抽象阶段.
数据成员多用于存放程序数据,方便各个窗体间调用,比如用户名,权限设置等等.
type
TPerson = class
FName:string; //你可以管它叫做(数据成员|变量|字段)都可以.开头的F为类成员约定前缀,你也可以不要
FAge:integer;
end;
4.2.设置与调用数据成员.
4.2.1.设置类的数据成员.
//注意,这里的 yadang 是全局变量,所以free是在窗体的Destroy事件里执行.
procedure TForm1.Button1Click(Sender: TObject);begin
yadang := TPerson.Create; //Create 制造实例
yadang .FName := '亚当'; //设置属性值
yadang.FAge:= 20;
end;
4.2.2.调用类的数据成员.如果没有预先设置数据成员的值,程序会抛出异常错误;
edit1.text:=yadang.FName;
edit2.text:=IntToStr(yadang.FAge);
下面是整体的运行效果:
5.类的属性.
5.1抽象类的属性
TPerson = class
private
Fname: string; //数据成员
Fage: integer; //数据成员
published
property name: string read Fname write Fname; //属性,属性只是一个接口
property age: integer read Fage write Fage; //属性,属性只是一个接口
end;
上面的代码看起来是写了很多行,其实实际到写时,只要写两行标记为红色的代码,外加一个快捷方式就可以实现了
首先,输入" propf "选择第一个提示,就会得到一行属性 :property xxx : type read Fxxx write Fxxx ; xxx表示属性名称,这个你可以改成你想要的名字.后面的 read Fxxx write Fxxx 也要跟着改过来
然后,改 type 为你需要的数据类型,即 xxx
最后,用快捷方式: ctrl+shift+c快速完成其它部分
5.2属性的调用:
name和age 属性,而不是Fname 和 Fage.因为这两个属性是私有的,外部不能直接访问,只能够通过接口来读写数据 ,这和直接访问数据成员的方式 略有不同.
5.3类的成员的访问权限 初识Private和Public关键字
private和published.字面意思 :
private就是私有的,只允许本单元访问.
published允许所有窗口访问.这里多说一句published 与 public 大体上可以通用,但published 多用于组件开发.一般情况下,我们用 public 就可以了,
published 与 public 是可以同时存在类里面的,在Dev的TcxRTTIInspector (RTTI属性编辑器)里面,RTTI 只能获取到published 发布的内容, public 里面的东西,它是获取不到的.
TPerson = class
private //私有的,只允许本单元访问
Fname: string;
public //改为 public,公共的,允许所有窗口访问
Fage: integer; //注意这一行,此时所有窗口都能直接访问这个Fage成员
property name: string read Fname write Fname;
property age: integer read Fage write Fage;
end;
private和public 此时,程序会默认为public ,此时所有窗口都能直接访问这个类下的数据成员.
type
TPerson = class
FName:string; //没有设置字段作用域,默认为public,所有窗口都可以访问
FAge:integer;
end;
6.类的方法 (成员函数或者过程)-- 定义一个过程
6.1 在public下定义一个SayHellow过程 :
TPerson = class
private
Fname: string;
Fage: integer;
public
property name: string read Fname write Fname;
property age: integer read Fage write Fage;
procedure SayHellow(msg: string); //过程
end;
ctrl+shift+c,自动在下面生成过程框架
procedure TPerson.SayHellow(msg: string);
begin
end;
写上业务代码后,运行情况如下:
这里要啰嗦一句,调用类的方法时,仍然要判断一下这个类是否存在:
procedure TForm1.Button4Click(Sender: TObject);
begin
if yadang <>nil then
yadang .SayHellow('Hellow!');
end;
在上面的SayHellow过程里面,引用了私用成员Fname,因为是在本单元内使用,所以一点问题也没有.这个Fname你还可以把它替换成其他过程或者函数,靠你自由发挥吧
接口的作用就是把内部数据整理出来,然后通过接口发布出去,避免外部直接操作内部数据.
6.2 创建一个自定义函数
TPerson = class
private
Fname: string;
Fage: integer;
Fbrithday: TDate;
public
property name: string read Fname write Fname;
property age: integer read Fage write Fage;
property brithday: TDate read Fbrithday write Fbrithday;
procedure SayHellow(msg: string);
function IsAdult: Boolean; //定义一个函数
end;
按下 ctrl+shift+c,自动在下面生成函数框架,与过程的操作大同小异
function TPerson.IsAdult: Boolean;
begin
if Now >= IncYear(Fbrithday,18) then
IsAdult := True
else
IsAdult := False;
end;
调用:
procedure TForm1.scDateEdit1Change(Sender: TObject);
begin
if yadang <> nil then
begin
yadang.brithday := scDateEdit1.Date; //设置生日为当前控件的日期
if yadang.IsAdult then
Label3.Caption := '我已成年'
else
Label3.Caption := '我未成年';
end;
end;
DateEdit 这里有个坑,手动输入的日期有时候是不会触发此事件的,原因是什么我也不知道 。所以最好的办法就是不要用change事件,应该设置一个button来执行这段代码。
7.类的构造函数
7.1 构造关键字 : constructor .说白了就是让我们抽象好的类,在成创建时,初始化一些参数。
TPerson = class
private
Fname: string;
Fage: integer;
Fbrithday: TDate;
public
property name: string read Fname write Fname;
property age: integer read Fage write Fage;
property brithday: TDate read Fbrithday write Fbrithday;
procedure SayHellow(msg: string);
function IsAdult: Boolean;
constructor Create(AOwner: TComponent);
constructor A(AOwner: TComponent);
constructor B(AOwner: TComponent);
constructor C(iFname:string);
constructor D();
end;
constructor 是可以有N多个的!而且名字也不一定要是Create !但我劝你还是按规范来写,你可以写成CreateA ,CreateB ,但请不要像上面一样来个ABCD,让人看得想砸电脑。
constructor TPerson.Create(AOwner: TComponent);
begin
Fname := '张飞';
Fage := 20;
Fbrithday := StrToDate('1999-05-07');
end;
但是相应的,在创建类实例时,你就不能再写XXX.Create了!要写你对应的 constructor A
通过初始化后,当这个TPerson类被创建之后,它的三个字段都有了初始值了,在很大程度上也避免了因为值为nil而产生的错误。
这里要吐槽一下Delphi的日期类型。我想写一个日期常量的,结果发现,我这点水平,真的写不出来。。。后来还是问了猫哥才知道,需要把字符串格式的日期转换一下,最简单的方案 StrToDate('1999-05-07')。如果需要把日期转成文本,可以用DateToStr( yadang.brithday)
怎么调用我就不说了,跟函数一样用就行。
再来看下面这两个类,注意class后面参数与create后面参数的不同用法.
TTest=class
constructor Create();
end;
TTest=class(TComponent)
constructor Create(AOwner: TComponent);override;
end;
delphi的override、overload和virtual方法
8. 类的继承、派生 -- 认识TObject类
这一节太理论了,我不知道怎么总结。我们打了个比方,上面第一节里说的类的定义,它就像是在乞丐家的儿子出生.....
type
TPerson = class
end;
下面这段就像是马斯克的儿子出生。。。
type
TPerson = class(Tobject)
end;
大家明白意思了吧?父亲那里有什么东西可以用的,儿子就有什么东西可以用,这就是继承(不是我们遗产继承的那种继承)。
然后呢,儿子们从父亲那里拿到了所有的东西,就开始作妖,自己又定义了一些自己的方法和属性,比如它用父亲的钱,做了一件花裤衩,这花裤衩是父亲们所没有的。这就叫派生。。。
最后大家来看一个人生赢家(它有五个爸爸):
type
TPerson = class(Tobject,A,B,C,D)
end;
9.深入了解类的属性:读取和写入
9.1.属性具有三个要素:
1.数据类型(type)
2.如何读取(Get)
3.如何写入(Set)
其中数据类型是每个属性都必须的,读和写按业务来选择.
我们来看下面的几个属性设置:
property name: string read Fname write SetFname; //数据类型:string, 读取数据时直接读Fname的值,在赋值时执行SetFname过程
property age: integer read GetFage ; //数据类型:integer , 读取数据时会执行GetFage函数,不能给age属性赋值,只读
property brithday: TDate read Fbrithday write Fbrithday; //数据类型:TDate , 读取数据时直接读Fbrithday 的值,赋值时,直接赋给Fbrithday ; 可读写
按下ctrl+shift+c,在private下面会自动添加这两行代码
procedure setFname(const Value: string);
function getFage: integer;
同时也会在业务代码区,生成对应的过程或者函数框架.我们可以看到,set方法生成的是过程来给属性赋值,get方法生成的是函数,因为需要返回值 .
function TPerson.getFage: integer;
begin
end;
procedure TPerson.setFname(const Value: string);
begin
if value.trim.isempty then exit; //这里写一些你的业务代码
Fname := Value;
end;
调用方式与上面讲的一样用就可以了.大家不要被这get和set迷惑,只要前期设置好业务逻辑就可以了.这两个家伙都是后台自动执行的,跟调用无关.
10. 内部类.类里面是可以再套类的
type
TOne = class
private
FSomeData: Integer;
public
// Nested constant
const
Foo = 12;
// Nested type
type
TInside = class
public
procedure InsideHello;
private
FMsg: string;
end;
public
procedure Hello;
end;
procedure TOne.Hello;
var
Ins: TInside;
begin
Ins := TInside.Create;
Ins.Msg := 'Hi';
Ins.InsideHello;
Show('Constant is ' + IntToStr(Foo));
Ins.Free;
end;
procedure TOne.TInside.InsideHello; //注意这里名称的写法
begin
FMsg := 'New msg';
Show('Internal call');
if not Assigned(InsIns) then
InsIns := TInsideInside.Create;
InsIns.Two;
end;
procedure TOne.TInside.TInsideInside.Two;
begin
Show('This is a method of a nested/nested class');
end;
最后,转载一个很详细的案例,看你能否读懂:
Delphi: 圆形进度(环形进度)