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

1.2 继承与派生
  
  我们再来看一段代码:
  
  type
  tnewdate = class(tdate)
  public
  function gettextnew:string;
  end;
  
  function gettext:string;
  begin
  return := inttostr(mouth) + ':' + inttostr(day) + ':' + inttostr(year);
  end;
  
  可以看到,在class后面出现一个包含在括号中的类名。这种语法表示新的类继承了一
个旧的类。继承了原有类的类称之为派生类,也叫子类,被继承的类称之为基类,也叫父
类。
  派生类与基类之间是什么关系呢?当派生类继承自一个基类时,它自动具有基类的所
有数据、方法以及其他类型,无须在派生类中再做说明。例如,可以象下面这段代码这样
使用tnewdate类:
  
  var
  aday: tnewdate;
  begin
  aday := tnewdate.create;
  aday.setvalue(1,1,2000);
  if aday.leapyear then 
  showmessage('闰年:' + inttostr(aday.year));
  aday.free;
  end;
  
  而且,派生类还可以在基类的基础上加入自己的数据和方法。可以看到在tnewdate类
中增加了一个新的方法gettextnew。下面给出这个方法的实现部分:
  
  function gettextnew:string;
  begin
  return := gettext;
  end;
  
  然后调用它:
  
  aday.gettextnew;
  
  这个新的方法工作得很好。
  为什么gettextnew方法必须调用基类中的gettext方法,而不能直接使用gettext方法
中的那些代码呢?原因是,mouth,day,year这三个成员被声明为private成员,因此它们
即使在派生类中也是不能被访问的,所以必须调用基类中的gettext方法,间接地使用它们
。如果要直接使用它们的话,可以将这三个成员的属性从private改为protected。在表1中
可以看到,protected属性的成员可以在声明类以及声明类的派生类中被访问,然而仍然不
能被这两种情况以外的其他代码所访问。现在我们终于可以理解了,这个特殊的属性实际
上提供了极大的方便:它使得类的成员被封装,避免了混乱,同时又能够让派生类方便地
使用它们。
  (如果你是一个细心的人,你可能发现上面的话中间有一个小小的仳漏。当你真的在
gettextnew方法中访问了基类的private成员的话,你可能会惊奇地发现程序也能够编译通
过而且正常运行!其实,这个问题和oop本身没有关系。上面我已经说过,在delphi中,p
rivate成员在声明类所在的单元文件中的任何地方都能被访问,因此如果tnewdate类和td
ate类在同一个.pas文件中时,这种情况就不足为怪了。)
  怎么样,是不是觉得非常奇妙?通过这种继承的机制,类不再仅仅是数据和方法的封
装,它提供了开放性。你可以方便地继承一个功能强大的类,然后添加进自己需要的特性
,同时,你又不需要对基类进行任何的修改。相反,原作者对基类的任何改动,都可以在
你的新类中立即反映出来。这非常符合代码的重用要求。
  这种继承机制也非常符合现实世界中的情形。可以设想,一般意义上的“动物”是一
个类,具有自己的一些特征(成员);而“狗”是“动物”的派生类,它具有动物的所有
特征,同时还具有自己独有的特征(四条腿,汪汪叫,等等)。而“狗”这个类可以继续
派生下去,例如“黑狗”“白狗”,它们除了保留狗的全部特征之外,还具有自己的特征
(黑颜色,白颜色,等等)。而具体到一只活生生的狗,可以认为它就是“黑狗”或“白
狗”(或其他什么狗)的一个实例(对象)。
  oop这种对现实世界的模拟不仅极大地简化了代码的维护,而且使得整个编程思想产生
了革命性的变化,较之模块化编程有了飞跃的进步。
  如果你曾经仔细阅读过vcl的资料甚至它的源代码,你就可以发现,整个vcl都是建立
在这种强大的封装-继承的机制之上的。你可以看到一张详细的vcl层次结构图,就象是一
个庞大的家谱,各种vcl构件通过层层继承而产生。例如,一个简简单单的tform类,就是
许多次继承之后的产物:
  tobject - tpersistent - tconponent - tcontrol - twincontrol - tscrollingwi
ncontrol - tcustomform - tform
  不但delphi的vcl,visual c++中的著名的mfc(microsoft foundation class,微软基
本类库),以及以前borland c++中风光一时的owl(object window library,对象窗口类
库),都是建立在这种机制之上。所不同的是,对于前两种语言,你要花上好几个月的功
夫去基本掌握那些繁复无比的类,才能写出比较有实用价值的程序,而在delphi中,大部
分的工作delphi都已经自动帮你完成了。例如,每次你向程序中加入一个窗体时,delphi
就自动为你从tform派生一个新类(默认为tform1),并且为这个新类创造一个实例。你对
这个窗体的改动(添加构件和代码之类),无非是为这个派生类加入一些新的特性而已;
你再也用不着自己去处理最大化、最小化、改变大小这一类的情况,因为这些代码都在基
类中被实现,而被派生类所继承了。这就是delphi的伟大之处。当然,delphi的vcl也绝不
比mfc或owl逊色(事实上它是由后者演变而来)。
  (可能有人会问起vb的情况。vb不支持继承,因此并没有什么复杂的类库,它自己的
控件也少得可怜,主要是使用activex控件。)。
  也许你已经若有所悟,为你的发现而心痒难骚了吧。但是,我们要讨论的东西当然不
会仅仅这么简单。
  在1.1部分(“数据封装”),我们讲到了“create方法是每一个class都具有隐含的
方法”。其实,这种说法是不准确的。事实是,在delphi中,所有的类都默认继承自一个
最基础的类toject,甚至在你并未指定继承的类名也是如此。create方法是tobject类具有
的方法,因此理所当然,所有的类都自动获得了create方法,不管你是否实现过它。想想
看就知道了:如果没有create方法的话,怎样建立一个对象呢?
  你可能注意到了create方法是一个特殊的方法。不错,create方法的确非常特殊,甚
至于它的“头衔”不再是function或procedure,而是constructor(构造器)。你可以在
vcl的源码中见到这样一些例子:
  
  constructor create;
  
  构造器不仅是一个delphi关键字,而且是一个oop方法学的名词。与之相对应的,还有
destructor(毁坏器)。前者负责完成创建一个对象的工作,为它分配内存,后者负责释
放这个对象,回收它的内存。要注意的一点是,constructor的名字一般是create,但des
tructor的名字却不是free,而是destroy。例如:
  
  destructor destroy;
  
  那么,在以前的代码,为什么又使用free来释放对象呢?二者的区别是,destroy会直
接释放对象,而free会事实检查该对象是否存在,如果对象存在,或者对象不为nil,它才
会调用destroy。因此,程序中应该尽量使用free来释放对象,这样更加安全一些。(但要
注意,free也不会自动将对象置为nil,所以在调用free之后,最好是再手动将对象置为n
il。)
  象对待一般的函数或过程那样,也可以向构造器传递参数:
  
  type
  tdate = class
  private
  mouth,day,year:integer;
  public
  function leapyear:boolean;
  function gettext:string;
  constructor create(m,d,y:integer);
  end;
  
  procedure tdate.create(m,d,y):integer;
  begin
  mouth := m;
  day := d;
  year := y; 
  end;
  
  调用它:
  
  aday: tdate;
  begin
  aday := tdate.create(1,1,2000);
  if aday.leapyear then 
  showmessage('闰年:' + inttostr(aday.year));
  aday.free;
  end;
  
  这样,在create方法里就完成了对数据的初始化,而无须再调用setvalue方法了。
  
  接下来,我们将要涉及到另一个重要的、也是很有趣的问题:方法的虚拟与重载。
  可能你已经有点晕了吧……还是先看一个新的例子:
  
  type
  tmyclass = class
  procedure one;virtual;
  end;
  
  type
  tnewclass = class(tmyclass)
  procedure one;override;
  end;
  
  procedure tmyclass.one;virtual;
  begin
  showmessage('调用了tmyclass的方法!');
  end;
  
  procedure tnewclass.one; override;
  begin
  inherited;
  showmessage('调用了tnewclass的方法!');
  end;
  
  可以看到,从tmyclass派生了一个新类tnewclass。这两个类都声明了一个名字相同的
方法one。所不同的是,在tmyclass中,one方法后面多了一个virtual关键字,表示这个方
法是一个虚拟方法(virtual method)。而在tnewclass中,one方法后面多了一个overri
de关键字,表示该方法进行了重载(override)。重载技术能够实现许多特殊的功能。
  让我们来仔细分析它们的实现部分。在tmyclass.one方法的实现部分,调用showmess
age过程弹出一个对话框,说明该方法已被调用;这里没有任何特别的地方。在tnewclass
.one方法中,出现了一条以前从未出现过的语句:
  
  inherited;
  
  这个词的中文意思是“继承”。我们暂时不要去涉及到太过复杂的oop概念,只要知道
这条语句的功能就是了。它的功能是调用基类中相当的虚拟方法中的代码。例如,你如果
使用以下代码:
  
  var
  aobject: tnewclass;
  begin
  aobject := tnewclass.create;
  aobject.one;
  aobject.free;
  end;
  
  那么程序将弹出两次对话框,第一次是调用tmyclass类中的one方法,第二次才是tne
wclass.one方法中的代码。
  重载技术使得我们不但可以在派生类中添加基类没有的数据和方法,而且可以非常方
便地继承基类中原有方法的代码,只需要简单地加入inherited就可以了。如果你不加入i
nherited语句,那么基类的相应方法将被新的方法覆盖掉。但是必须注意,重载只有在基
类的方法被标志为virtual时才能进行,而且重载的方法必须具有和虚拟方法完全相同的参
数类型。
  虚拟方法还有一种特例,即抽象方法:
  
  procedure one;override;abstract;
  
  在one方法后面,不但有override关键字,还多了一个abstract关键字(意为抽象)。
这种方法称为抽象方法(在c++中称为纯虚拟函数)。含有抽象方法的类称为抽象类。抽象
方法的独特之处在于,它只有声明,而根本没有实现部分,如果你企图调用一个对象的抽
象方法,你将得到一个异常。只有当这个类的派生类重载并实现了该方法之后,它才能够
被调用。(在c++中,甚至根本就不能建立一个抽象类的实例。)
  既然如此,那么这种抽象方法又有什么用呢?这个问题我们将在接下来的“多态”部
分进行讨论。
  
  

--

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