方法(Method)
方法是在类中实现的函数或者过程。C++中方法被称为“成员函数”。方法与普通的过程和函数的区别是,在方法中有一个隐含的参数称为Self,用来指向调用该方法的对象本身。这里的Self与C++和Java中的相类似。调用一个方法与调用一个普通的函数或过程类似,但得将方法的名称跟在对象引用之后,如: Object.Method(Argument);
类方法(Class method)基于类及其祖先类。在类方法中,Self是对类的引用而不是对对象的引用。C++中类方法称为“静态成员函数”。
你可以调用在对象的类中以及祖先类里声明的对象方法。假如祖先类和派生类中定义了相同名称的方法,Delphi将调用最外层派生的那个方法。如例2-4所示:
例2-4:绑定静态方法 type TAccount = class public procedure Withdraw(Amount: Currency); end; TSavingsAccount = class(TAccount) public procedure Withdraw(Amount: Currency); end; var Savings: TSavingsAccount; Account: TAccount; begin ... Savings.Withdraw(1000.00); //调用TSavingsAccount.Withdraw Account.Withdraw(1000.00); //调用TAccount.Withdraw
普通方法被称为静态方法的原因是编译器直接将该调用和方法的实现绑定在一起。换句话说,静态方法是静态绑定的。在C++中称普通方法被称为“普通成员函数”,在Java中称为“最终方法(final method)”。多数Delphi程序员不愿使用静态方法这个术语,而将之简化称为方法或者非虚拟方法。
虚方法是在运行期间而非编译期间被绑定的一类方法。在编译期间,Delphi根据对象引用的类型来决定可以调用的方法。与编译期间直接指定一个特定的方法的实现不同的是,编译器根据对象的实际类型存放一个间接的对方法的引用。运行期间,程序在类的运行期表(特别是VMT)中查找方法,然后调用实际的类型的方法。对象的真正的类必须是在编译期中声明的类,或者它的一个派生的类——这一点不成问题,因为VMT提供了指向正确的方法的指针。
要声明一个虚方法,可以在基类中使用vritual指示符,然后使用override指示符以在派生的类中提供该方法的新的定义。与Java不同的是,Delphi中方法在缺省情况下是静态的,因此你必须使用virtual指示符来声明一个虚方法。与C++不同的是,Delphi中要在派生类中覆盖一个虚方法必须使用override指示符。
例2-5 使用虚方法。
例2-5 绑定虚方法 type TAccount = class public procedure Withdraw(Amount: Currency); virtual; end; TSavingsAccount = class(TAccount) public procedure Withdraw(Amount: Currency); override; end; var Savings: TSavingsAccount; Account: TAccount; begin ... Savings.Withdraw(1000.00); // 调用TSavingsAccount.Withdraw Account := Savings; Account.Withdraw(1000.00); // 调用TSavingsAccount.Withdraw
除了vritual指示符,你还可以使用dynamic指示符。两者语义相同的,但实现不同。在VMT中查找一个虚方法很快,因为编译器在VMT中建了索引。而查找一个动态方法慢一些。调用一个动态方法虚要在动态方法表(DMT)中进行线性查找。在祖先类中查找直到遇到TObject或者该方法被找到为止。在某些场合,动态方法占用比虚方法更少的内存。除非要写一个VCL的替代物,否则你应当使用虚方法而不是动态方法。参见第三章以详细了解有关内容。
虚方法和动态方法可以在声明时使用abstract指示符,这样该类就不必给出对该方法的定义,但在派生的类中必须覆盖(override)该方法。C++中抽象方法的术语称为“纯虚方法”。当你调用一个包含有抽象方法的类的构造函数时, Delphi将给出编译警告,提示你可能有个错误。可能你要创建的是覆盖(override)并且实现了该抽象方法的派生类的一个实例。定义了一个或者多个抽象方法的类通常称为抽象类,尽管有些人认定该术语只适用于只定义了抽象方法的那些类。
提示: 当你构建一个自其他抽象类继承而来的抽象类时,你应当使用override和abstract指示符将所有的抽象方法重新声明。Delphi并没有要求这么做,因这只是个惯例。这些声明将清楚地告诉代码维护人员有哪些方法是抽象的。否则,维护人员可能对那些方法需要实现而那些方法需要保持抽象感到疑惑。例如: type TBaseAbstract = class procedure Method; virtual; abstract; end; TDerivedAbstract = class(TBaseAbsract) procedure Method; override; abstract; end; TConcrete = class(TDerivedAbstract) procedure Method; override; end;
类方法或构造器也可以是虚拟的。在Delphi中,类引用是一个真的实体,你可以将它赋值给一个变量,当作参数传递,或用作引用来调用类方法。如果构造器是虚拟的,则类引用有一个静态的基类类型,但你可以将一个派生类型的类引用赋值给它。Delphi将在该类的VMT中查找虚拟构造器,而后调用派生类的构造器。,
方法(以及其他函数和过程)可以被重载,也就是说,多个例程可以有相同的名字,但是参数定义必须各不相同。声明重载方法使用overload指示符。在派生类中可以重载继承于基类的方法。这种情况下,只有派生的类才需要使用overload指示符。毕竟,基类的作者不可能预见其他的程序员何时需要重载一个继承的方法。如果派生类中没有使用overload指示符,则基类中的相同名称的方法被屏蔽。如例2-6所示。
例子2-6:方法的重载 type TAuditKind = (auInternal, auExternal, auIRS, auNasty); TAccount = class public procedure Audit; end; TCheckingAccount = class(TAccount) public procedure Audit(Kind: TAuditKind); // Hides TAccount.Audit end; TSavingsAccount = class(TAccount) public // Can call TSavingsAccount.Audit and TAccount.Audit procedure Audit(Kind: TAuditKind); overload; end; var Checking: TCheckingAccount; Savings: TSavingsAccount; begin Checking := TCheckingAccount.Create; Savings := TSavingsAccount.Create; Checking.Audit; // 错误,因为TAccount.Audit被屏蔽了。 Savings.Audit; //正确,因为Audiot被重载了。 Savings.Audit(auNasty); //正确 Checking.Audit(auInternal);//正确 PartI PartIII |