发信人: atong (sut), 信区: BorlandDev
标  题: 深入delphi编程(转2)
发信站: 哈工大紫丁香 (Wed Jul  3 20:03:11 2002) , 转信


  1.1 数据封装
  
  让我们先看一段代码:
  
  type
  tdate = class
  mouth,day,year:integer;
  procedure setvalue(m,d,y:integer);
  function leapyear:boolean;
  end;
  
  我们首先会看到class关键字,它的中文译名为“类”。类是一个非常重要的概念。根
据权威的定义,类是:一种用户定义的数据类型,它具有自己的说明和一些操作。一个类
中含有一些内部数据和一些过程或函数形式的对象方法,通常来描述一些非常相似的对象
所具有的共同特征和行为。
  这个定义可能比较晦涩。你可以把类想象为一种特殊的record类型,其中不但可能包
含数据,而且可能包含函数和过程(在oop中称之为方法)。这些数据和方法被统称为类的
成员。
  上面这个类很显然是一个日期类型,它包括mouth,day,year这三个数据成员,和se
tvalue、leapyear这两个方法。顺便说一句,在delphi中,习惯以字母t作为每个类的前缀
,就象viusal c++中习惯以字母c作为每个类的前缀一样。
  mouth,day,year这三个数据成员指定了该日期的年、月、日。setvalue方法为这三
个数据成员赋值,而leapyear检查当前日期所在的那一年是否是闰年。下面我们给出这两
个方法的实现部分:
  
  procedure tdate.setvalue(m,d,y):integer;
  begin
  mouth := m;
  day := d;
  year := y; 
  end;
  
  function tdate.leapyear:boolean;
  begin
  if (year mod 4 <> 0) then
  leapyear := false
  else if (year mod 100 <> 0)
  leapyear := true
  else if (year mod 400 <> 0)
  leapyear := false
  else
  leapyear := true;
  end;
  
  实现了这些定义之后,就可以这样调用它们:
  
  var
  aday: tdate;
  begin
  //建立一个对象
  aday := tdate.create;
  //使用之
  aday.setvalue(1,1,2000);
  if aday.leapyear then 
  showmessage('闰年:' + inttostr(aday.year));
  //释放对象
  aday.free;
  end;
  
  我们来逐行解释这些代码的含义。var后面那一行声明了一个tdate类的变量。
  声明了变量之后,我们怎么使用它呢?使用tdate类的create方法可以建立一个该类的
对象,并将其赋予aday变量。
  现在我们又接触到了oop中另一个重要的概念:对象。什么是对象?简言之,对象就是
类的实例,或者说,是类定义的数据类型的变量。当建立一个类的对象时,系统为它分配
一块内存。例如我们定义一个变量a为integer类型,那么,integer是一个数据类型,a就
是一个实例。类与对象的关系就类似于这两者之间的关系。区别类和对象是非常重要的,
甚至一些专业的程序员都往往将他们搞混。
  细心的读者可能注意到,在tdate类的定义中,并没有create这个方法。那么这个cre
ate方法是从哪里来的呢?create方法是每一个class都具有隐含的方法,它的作用是建立
这个类的实例。请注意,在这里,类和其他的数据类型是不同的。其他的数据类型都是声
明了变量之后就可以直接使用,而类类型必须在使用create方法创建它的实例(对象)之
后才能使用。
  事实上,在c++和其他大多数的oop语言中,声明一个类的变量就能够同时建立起这个
类的对象。而delphi(包括它的孪生兄弟c++ builder)在这方面与众不同,必须要creat
e一下才能真正建立对象。同时,在这个对象不再需要时,必须要手工调用free方法释放这
个对象(当然,free方法也是每个类隐含的)。这和delphi独特的“对象引用模型”有关
,有兴趣的朋友可以查阅有关资料,我就不多说了。
  这种情况造成了一个非常有趣的现象,那就是,编程的初学者往往忘记在使用对象之
前create它,从而出错,但从c++转向delphi的高手也常常犯同样的错误……
  顺便告诉大家一个诀窍,当编译器出现“read of address: ffffffff”这样的错误时
,多半是因为在使用对象之前忘了create,可以从这方面入手检查代码。另外,也千万不
要忘记在不需要它时使用free释放掉,否则可能造成内存泄漏。
  在建立和释放对象的代码的中间,是使用对象的代码。访问对象的数据成员非常简单
,和record类型没有什么区别。可以点号表达式来访问它们:
  
  aday.year := 2000;
  aday.mouth := 1;
  aday.day := 1;
  
  同样,也可以使用点号表达式来调用对象的方法。如果你阅读了方法实现部分的代码
,你可以很容易地发现,aday.setvalue(1,1,2000)这一句分别为三个数据成员赋了值,而
aday.leapyear调用则返回当前日期所在年是否为闰年。至此,整段代码的意义也就清楚了

  然而,类不仅仅这么简单。上面这个例子是一个非常简单的类,可以直接访问它的任
何成员(数据和方法)。但某些类的成员是不能被随便访问的。delphi中用三个关键字区
分这些成员的访问权限:
  
  表1


private 该类型的成员只能在声明类中被访问 
public 该类型的成员可以被程序中的任何地方的代码访问 
protected 该类型的成员只能在声明类以及声明类的派生类中被访问 


  
  protected类型的成员以及什么是“派生类”等问题我们留到以后再进行讨论,现在我
们将注意力集中在前两者。
  public类型就是在上面例子中的那种类型,这个很好理解。而private类型,根据表格
中的简单解释,只能在该成员被声明的那个类(也就是该成员所属的那个类啦)中被访问
,越出这个界限,它就是不可见的。那么,private类型的成员将如何被使用呢?简单地说
,就是通过一个public类的方法来访问它。
  让我们看一个新的例子:
  
  type
  tdate = class
  private
  mouth,day,year:integer;
  public
  procedure setvalue(m,d,y:integer);
  function leapyear:boolean;
  function gettext:string;
  end;
  
  
  在这个类中,mouth,day,year这三个成员被声明为private成员,因此它们在类以外
的其它地方是不可访问的。也就是说,如果你使用
  
  aday.year := 2000;
  
  这样的代码,那么编译器将会报错。但是,我们可以照样通过setvalue方法为它们赋
值:
  
  aday.setvalue(1,1,2000);
  
  这行代码是合法的,因为setvalue本身是tdate类的成员,而且它又是一个public成员
。而使用gettext方法则可以得到当前日期值(这也是得到当期日期值的唯一办法)。
  这样的设置使得类的一些成员被隐含起来,用户只能用一些专门的方法来使用它们。
那些可以被外部代码访问的成员称之为类的接口。这样做有什么好处呢?首先,这让类的
作者可以检测被赋值的内容。比如,用户可能给一个对象赋予13月40日这样的无效日期。
而在隐含了一些成员之后,类的作者可以在方法的代码中检测这些值是否有效,从而大大
地减少了产生错误的机会。其次,使用规范的类,作者可以随时修改类内部的代码,而使
用该类的代码却无需任何修改!这样使得代码的维护成了一件轻松的事件,特别是对于多
人协作的大型软件而言。
  这就叫做数据的封装(encapsulation)。这是oop的第一个特征。一个优秀的oop程序
员,应该在设计类的时候,就确定将哪些重要的数据封装起来,并给出一个高效率的接口

  需要指出的一点是,表1中private部分的论述对于“标准的”oop语言(例如c++)是
完全正确的,但对于delphi有一个例外。在delphi中,private成员除了在声明类中可以访
问外,在声明类所在的单元(.pas文件)中的任何地方都能被访问,不论这些代码与声明
类的关系如何。严格来说,这是违反oop的原则的,我不明白borland为何要这么做(据说
是为了方便)。在关于delphi的优劣性的讨论中,这是常被涉及的一个问题。
  
  

--

※ 来源:.哈工大紫丁香 http://bbs.hit.edu.cn [FROM: 202.118.239.3]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:2.320毫秒