个Create方法是从哪里来的呢?Create方法是每一个Class都具有隐含的方法,它的作用是建立这个类的实例。请注意,在这里,类和其他的数据类型是不同的。其他的数据类型都是声明了变量之后就可以直接使用,而类类型必须在使用Create方法创建它的实例(对象)之后才能使用。
事实上,在C++和其他大多数的OOP语言中,声明一个类的变量就能够同时建立起这个类的对象。而Delphi(包括它的孪生兄弟C++ Builder)在这方面与众不同,必须要Create一下才能真正建立对象。同时,在这个对象不再需要时,必须要手工调用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的优劣性的讨论中,这是常被涉及的一个问题。
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属性的成员可以在声明类以及声
上一页 [1] [2] [3] [4] [5] [6] [7] [8] 下一页