【Delphi奥法 - 第七章】类的初步
delphi吧
全部回复
仅看楼主
level 11
天道玄虚 楼主
【Delphi 奥法】
天道玄虚出品
注意:该教程适用于Delphi 10.1.2。
相关内容见:https://tieba.baidu.com/p/4934621524五楼楼中楼。
第七章 类的初步
先说点题外话。
到此为止,如果跟上了前六章的进度,那么恭喜你,你可以算是入了Delphi的门了,但是还需要清楚的是,入了门并不代表你能够独立开发一个工程了,因为到现在为止,还有一个最为重要的部分还没有涉及到,那便是类。
在开始类的学习之前,我们先来大概回顾一下我们已经完成的部分:首先,第零章我们认识了一下编译器的界面;第一章我们了解了一下Delphi的大致规则;第二章则学习了一下基本语法;第三章我们便开始接触到数组、泛型列表和简单的排序算法;第四章我们认识了一下字符串和TStringHelpe;第五章我们走马观花一般地看了看常用的控件;第六章我们学习了一下使用非常广泛的流。
如果这些都掌握了,那么可以说我们已经对整个软件体系有了一个初步的了解,所以,其实你已经学了很多东西了。
为什么说是软件体系呢?软件体系指是一系列按照特定顺序组织的计算机数据和指令的集合,其实也可以理解为数据、数据处理、以及数据储存的集合。从这个角度来看,是不是已经初步地学习了一下看起来很高级的软件呢?
回到Delphi上,在我看来,对于已经入了门的Delphi开发者而言,大致可以分为四个层次:能够使用控件,能够熟练使用必要控件且能够开发简单控件,能够熟练使用必要控件且能够开发必要控件,能够创造控件。笔者自以为只到第二个层次,也就是能够开发简单控件的层次。
为什么要提这个层次的区分呢?这是为了让诸位对于类有一个更高层次的认识,类并不是所谓的:MyClass = class;这样无聊的东西,这种东西充其量不过是为了说明自己知道类这个名词而已。若是将编程看做创世,那么类就是你要创造的物和法则。
扯远了。
回到正题,让我们来谈一谈类。
7.0、先看一个类
相比各位都知道计时器这个东西(由于这东西通常在于交互动画相关的工程中出现较多,因而暂时没有涉及),其实我们在第零章通过操作控件实现的鬼畜动画根本上就是通过计时器实现的。
其实在Firemonkey之前,Delphi的动画一直是通过计时器实现的。
计时器的源码看起来比较简单(如果我们不深究其中的缘由):
这个是计时器的源码,包含在FMX.Types单元中。推荐各位到Delphi中去看源码,在Word里面结构看起来没那么清晰,如果使用文本更可怕,不信自己试下,所以截图比较好。
我们先来看一看这里面涉及到的保留字。
首先是注明权限的四个:private,protected,public,published。什么是权限,我们之后再说。
其次是声明类型的保留字:constructor,destructor,procedure,property。当然本来还应该有一个function,想必各位已经知道了。
然后是对函数做补充说明的保留字:virtual,override。除此之外还有比较常见的是overload。
最后是对属性做说明的保留字:read,write,default。
首先我们看第一行:
TTimer = class(TFmxObject)
这一行表示,一个名叫TTimer的类,继承自TFMXObject。Delphi中的类(一般类)习惯以字母T开头。除了一般类以外,还有两种相对特殊的类:record,interface。有些人认为record是结构体,不是类,其实record早就是类了,在Delphi的结构查看器中也显示为继承自TObject的类。如下图:
不难看到record类型TCharSct显示继承自TObject,所以我们应该把这个当成一个类,而非简单的结构体。
我们先不考虑record和interface,先看class。
现在我们知道,计时器这个控件(控件的本质上就是类的一种),继承自TFMXObject。
然后我们来说一说权限,这年头到处都是权限。不过,与一般理解的权限不同,这里的权限指访问权限,权限越高的部分,访问限制越大。
首先是最高权限,private。private下的部分只能够被类本身,以及同一个单元中的其余类进行访问。一般,在类中用于记录数据的变量都放在这个位置,并且约定以F(Field,字段)开头。除此之外,处理属性的方法(所谓方法,就是类中对于函数function,和过程procedure的统称)也将要放在这个位置。private还可以在前面加上strict标识,用于规定这个部分只能够被类自己访问,将会拒绝同单元内其余类的访问,用的不多。
略低一级的权限是protected,这个级别的数据只能够被类本身,同一单元的其余类,以及这个类的子类访问。通常将类中的事件等需要,或者可能需要被子类继承的放入此处。
之后就是几乎没有权限的public,一般的public下只放一些属性(property)和用于对类进行操作的方法。public下的部分几乎可以被随意访问,只读属性稍微特殊一些,暂且不表。
既然public就已经没有权限了,那么还要published作甚?当然是为了可视化编辑,public之所以叫几乎可以被随意访问,正是由于还不能够被IDE中的属性查看器访问,也就是不能够被可视化编辑。而published表示,这个部分将会在可视化编辑中显示,通常这里只会有属性,而且不会有只读属性。如果一个属性是只读的,请放在public里面。
在介绍属性之前,先说一说constructor,destructor。这两个标识符分别代表创建函数,释放函数。而且规定,创建函数必须名为Create,释放函数必须名为Destroy。这是一个规定,记住就好。
下面我们来说一说何为属性。
7.1、属性何物
上面我们说到,通常将一个类中用于记录数据的部分放在private下。那么就必然带来一个问题,如果我们需要处理这一部分数据,该怎么办呢?还记得我们提到了同样应该放在private下的,处理属性的方法吗?没错,我们正是通过属性来处理private下的变量的。
换一个角度,我们可以理解为属性是我们和private下的字段接头的中间人,不管我们要做什么,都必须通过这个中间人才能够完成。
各位想必已经知道read和write保留字是做什么用的了。
当我们在其余部分引用一个类的属性的时候,我们相当于调用了read标识的方法,当我们对一个属性赋值的时候,我们相当于调用了write方法。很自然地,属性当然也需要声明类型,而且如果是某个字段对应的属性,类型当然应该一致。
为什么说是某个字段对应的属性呢?难道有属性不对应字段吗?当然有,容我慢慢道来。
我们先来看一个属性。
看,这个属性的read标识FInterval,write标识SetInterval。后面的default指这个属性的默认值为1000。Cardinal是无符号整数,占用四个字节。
从这个属性,我们不难看出对于对应字段的属性,其声明的一般套路是:
property Name: Type read FName write SetName;
default并不是必要的声明,一般其实是不必要的,只是在published下的属性通常需要这个属性,你应该注意到了,你平时创建的控件都有一个初始值。
再看看这个属性:
你看,这个属性的read和write都是函数,这就是我方才说的,不对应字段的属性。
从这些我们可以看出,类和属性的本质就是为了易读性和易用性,甚至可以说就是为了偷懒而生的。比如说我举一个分明有类存在,但是没有面对对象方法的奇妙语言,JASS(魔兽地图编辑器的脚本语言)中,与单位横坐标有关的操作如下:
call GetUnitX(u);
call SetUnitX(u, X);
是不是觉得整个人都不好了?如果是面对对象编程,分明可以写作:
u.X;
u.X := X;
从这个例子我们不难看出,类和属性并不是必要的,但是对于程序的易读性和编写的易用性而言,是非常重要的。而所谓属性的本质,只不过是一个中转函数的媒介,其本质相当于调用函数。明白了这一点,属性也就不难理解了。
7.2、来设计一个简单的类
看完了属性的用法,我们来看一看似乎非常神秘的类。我们暂时只考虑class类,不考虑另外两个。
对于一个非可视化的类,我们如果不考虑其子类——一般初学时不需要考虑这一点,那么我们就不需要protected这个部分。也就是说,我们需要的只有private和public两个部分。
那么我们来设计一个简单的类,比如说圆,就是纯粹几何的圆,只考虑半径,不考虑其圆心位置之类的部分。
那么首先定义:
TRound = class
private
public
end;
然后我们紧接着要做的就是分析一下这个类需要的必要数据,对于可以简单生成或者计算得到的数据,不在这一部分考虑,但是如果生成比较复杂的那种,则同样需要考虑。
先不要想太多,既然说了不考虑这个圆的圆心,那么其唯一的数据就是半径了。由于已知圆半径的时候,计算周长或者面积都非常方便,因此,不需要将这两个数据特别记录。
好了,我们首先把这个半径放在private下,也就是:
TRound = class
private
 FRadius: Extended;
public
end;
其实使用Single就够了,因为我们不是专门为了计算而来。然后我们希望能够获得的数据是圆的半径、周长、面积,应该不会有其余的数据能够让我们感兴趣了,因此,我们需要设置的属性也就是这三个:
TRound = class
private
 FRadius: Extended;
public
 property Radius: Extended read FRadius write SetRadius;
 property Area: Extended read GetArea write SetArea;
 property Circle: Extended read GetCircle write SetCircle;
end;
如果你只写这些,不做其余的处理,当然会提示缺少函数的定义。这个时候,我们把光标放在报错的位置,然后按下:Ctrl + Shift + C,我们将惊奇地发现,这几个函数已经被定义好了,而且还是标准的定义。
现在我们来看应该如何设计这几个函数。
首先先是计算面积和周长的函数,这个应该是小学问题。
请先包含System.Math这个单元。
function TRound.GetArea: Extended;
begin
 Exit(PI * FRadius * FRadius);
end;
function TRound.GetCircle: Extended;
begin
 Exit(2 * PI * FRadius);
end;
然后就是设定了,如何实现设定一个圆的面积、周长。半径呢?其实也很简单,当然就是逆算法。这里注意需要检验输入的面积、周长、半径需要是正数。
procedure TRound.SetArea(const Value: Extended);
begin
 if Value > 0 then
  FRadius := Sqrt(Value / PI);
end;
procedure TRound.SetCircle(const Value: Extended);
begin
 if Value > 0 then
  FRadius := Value / PI / 2;
end;
procedure TRound.SetRadius(const Value: Extended);
begin
 if Value > 0 then
  FRadius := Value;
end;
类是不是其实很简单?这样设定完之后,我们就可以直接通过给圆的半径、周长、面积赋值来设定圆的属性了。
2017年03月10日 11点03分 1
1