参考万一的博客:
http://www.cnblogs.com/del/archive/2008/01/17/1042187.html
http://www.cnblogs.com/del/archive/2007/12/05/983657.html
http://www.cnblogs.com/del/archive/2007/12/05/983684.html
http://www.cnblogs.com/del/archive/2008/01/16/1041168.html
http://www.cnblogs.com/del/archive/2008/01/17/1042735.html
http://www.cnblogs.com/del/archive/2008/01/15/1039998.html
1.虚方法 包括 virtual 和dynamic ,虚方法 必须再父类实现(当然你可以弄个空方法体),子类可以选择是否覆盖,孙类也可以再次覆盖。
2.纯虚方法 也叫抽象方法,父类必须只定义,不实现,子类必须覆盖overide,没有选择性。孙类也可以再次覆盖。
3.抽象类 与 普通类 的唯一区别是 抽象类中 可以包含 纯虚方法(也叫抽象方法),纯虚方法 必须只定义不能实现,只能有子类来实现。
4.抽象类也不能 实例化 ,但是抽象类 也可以拥有普通的方法,切记抽象类中 并非 只有抽象方法,也可以有普通方法。
5.但是一个类 一旦包含抽象方法(即纯虚方法)那么这个类一定是抽象类 不能自己实例化。
------------------------------------------------------------------------------------------------------------------------------------
普通虚方法-------父类必须实现,子类 可以选择是否覆盖。
纯虚方法---------父类只定义 不能实现,子类必须覆盖重写,且会把类变成抽象类 不能自己实例化。
unit Unit4; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs; type TForm4 = class(TForm) private { Private declarations } public { Public declarations } end; /// <summary> /// 定义一个普通的车类 /// </summary> TCar = class function name(): string; virtual; //虚方法父类也是必须实现的,子类可以选择是否覆盖,不必须 end; /// <summary> /// 定义一个车的抽象类 /// </summary> TCarAbstract = class abstract function name(): string; //抽象类与普通的类一样,是可以拥有普通的方法的,但是抽象类不能实例化 仅此而已,其它类有关的功能都有 end; /// <summary> /// 定义一个普通的车类 + 增加一个纯虚方法 就可以把这个类变成一个抽象类 不能实例化了 /// 间接抽象类 /// </summary> TCarIndirectAbstract = class function name(): string; virtual; abstract;//这个是纯虚方法,只定义不实现 纯虚方法也叫做抽象方法;子类没有选择的权利,必须覆盖overide end; var Form4: TForm4; implementation {$R *.dfm} { TCar } function TCar.name: string; begin //这里必须得实现,否则编译会报错. Exit('car'); end; { TCarAbstract } function TCarAbstract.name: string; begin //抽象类中也可以包含任何剖通方法 与普通类一样。 Exit('CarAbstract'); end; end.
另外关于接口与抽象类的区别 我手机拍了下 疯狂java书中的总结 感觉 抽象类 还是 无法淘汰的技术,接口 并非能取代 抽象类。
用虚方法实现多态的举例:
============================2017.04.20 补充:=========================
重定义 与 覆盖重写 的区别如下:
unit Unit5; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TForm5 = class(TForm) Button1: TButton; Memo1: TMemo; Button2: TButton; Button3: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private declarations } public { Public declarations } end; //重新定义的方式 TCar1 = class public function getColor(): string; end; TRedCar1 = class(TCar1) public function getColor(): string; end; //覆盖的方式 TCar2 = class public function getColor(): string; virtual; end; TRedCar2 = class(TCar2) public function getColor(): string; override; end; var Form5: TForm5; implementation {$R *.dfm} procedure TForm5.Button1Click(Sender: TObject); var c1: TCar1; r1: TRedCar1; begin c1 := TCar1.Create; r1 := TRedCar1.Create; try Memo1.Lines.Add(c1.getColor);//无色 --- 常规使用 Memo1.Lines.Add(r1.getColor);//红色 --- 常规使用 finally c1.Free; r1.Free; end; end; procedure TForm5.Button2Click(Sender: TObject); var c2: TCar2; r2: TRedCar2; begin c2 := TCar2.Create; r2 := TRedCar2.Create; try Memo1.Lines.Add(c2.getColor);//无色 --- 常规使用 Memo1.Lines.Add(r2.getColor);//红色 --- 常规使用 finally c2.Free; r2.Free; end; end; procedure TForm5.Button3Click(Sender: TObject); var c1: TCar1; c2: TCar2; begin //父类的实例用子类来创建,这个时候区别就出来了。 c1 := TRedCar1.Create; c2 := TRedCar2.Create; try Memo1.Lines.Add(c1.getColor); //无色 --- 重定义【老子的是老子的,儿子的是儿子的】 Memo1.Lines.Add(c2.getColor); //红色 --- 重写【父类的实例被子类创建了,老子的方法指针被儿子的取代了。老子的就是儿子的,儿子的还是儿子的】 finally c1.Free; c2.Free; end; end; { TRedCar2 } function TRedCar2.getColor: string; begin Result := '红色'; end; { TRedCar1 } function TRedCar1.getColor: string; begin Result := '红色'; end; { TCar1 } function TCar1.getColor: string; begin Result := '无色'; end; { TCar2 } function TCar2.getColor: string; begin Result := '无色'; end; end.
网友的回答粘贴如下:
再探讨个问题,为什么要使用虚方法????,
这是再delphi中特有的,在Java中直接对普通方法就可以overide重写,再delphi中只能对用virtual修饰的方法才可以被overide。
我们通常的需求是这样的,我们定义了一个父类和一个很多子类,我们再使用的时候,只关注用途而不关心具体是哪个类来实现。还有一种情况是
我们事先并不知道是哪个类来实现,需要根据情况动态的判断用哪个类来实现 TStrings,TStream就是典型的应用。见如下demo:
unit Unit5; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TForm5 = class(TForm) Button1: TButton; Memo1: TMemo; Edit1: TEdit; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; /// <summary> /// 定义一个汽车基类 /// </summary> TCar = class public function run(): string; virtual; end; /// <summary> /// 轿车类 /// </summary> TJiaoChe = class(TCar) public function run(): string; override; end; /// <summary> /// 货车类 /// </summary> THuoChe = class(TCar) public function run(): string; override; end; var Form5: TForm5; implementation {$R *.dfm} { THuoChe } function THuoChe.run: string; begin Result := '货车跑了'; end; { TJiaoChe } function TJiaoChe.run: string; begin Result := '轿车跑了'; end; { TCar } function TCar.run: string; begin Result := '车跑了'; end; procedure TForm5.Button1Click(Sender: TObject); var //我们再使用的时候,往往事先并不知道到底哪个车跑了,或者说车跑了,是根据条件来跑的 c: TCar; begin //根据条件来动态的判断哪个车跑了,然后实例化相应的对象 if Trim(Edit1.Text) = '轿车' then begin c := TJiaoChe.Create; Memo1.Lines.Add(c.run); end else if Trim(Edit1.Text) = '货车' then begin c := THuoChe.Create; Memo1.Lines.Add(c.run); end else begin c := TCar.Create; Memo1.Lines.Add(c.run); end; c.Free; end; end.
这样有利于多态的情况下,即定义父类的实例,然后用不同的子类来实现,运行的方法 都是对应的各个子类的。
结论:若用多态,即一个父类 + N多子类,且父类的方法需要再子类中加强的情况下,尽量不要用重定义的方法,应该用 virtual + overide的方式;
或者说 尽量用 virtual + overide 的方式 而不要用 重定义的方法,这样做的好处是,多态使用的时候,用父类实例去调用方法的时候,实际调用的是
各个子类中加强的方法,而不是父类原有的方法。
另外还有个现象你要留意下:
/// <summary> /// 一个普通类 /// </summary> TPerson = class public /// <summary> /// 注意这里是重定义,而不是覆盖,这个估计是重定义用的最多的地方了,因为TObject的Create方法不是虚方法 /// 所以一般都是只能重定义,除此之外,重定义尽量少用。你想想父类有个方法,你子类中又定义一个重名方法 /// 这样做是不安全的,有一天你会搞混的。 /// </summary> constructor Create; /// <summary> /// TObject.Destroy是虚方法,子类覆盖 /// </summary> destructor Destroy; override; end;