参考了万一的博客:
http://www.cnblogs.com/del/archive/2007/12/13/993757.html
http://www.cnblogs.com/del/archive/2008/01/17/1042904.html
=====================================================================================
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; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; /// <summary> /// 定义一个 人类 /// </summary> TPerson = class(TObject) private Fname: string; Fage: Integer; procedure Setage(const Value: Integer); procedure Setname(const Value: string); public /// <summary> /// 1由于 TObject类的构造方法 不是 虚方法 和 动态方法, 所以不能 overide /// 所以重定义一个Create /// 标准写法1 /// </summary> constructor Create;//overload; 若有多个重载,这里不要忘记加 overload /// <summary> /// 重载构造方法, 标准写法2 /// </summary> //constructor Create(const aName: string; const aAge: Integer); overload; /// <summary> /// 1.重写即overide析构方法,由于tobject的析构方法是个虚方法,但是比较特殊, /// 子类可以选择是否重写(普通方法是不可以选择的) /// /// 2.这样写是重定义,虽然允许这样玩,但是你要知道父类是个虚方法,尽量不要这样写 /// </summary> //destructor Destroy; /// <summary> /// 标准写法, 重写覆盖父类的虚方法, 加上override 关键词 /// </summary> destructor Destroy; override; /// <summary> /// 重载析构方法, 不要这样玩, 因为你要知道, 我们通常释放对象都是用 MyObj.Free;来 /// 调用Destroy的,而Free是没有参数的, 所以若你这么玩, 那么你必须释放的时候这样写 /// MyObj.Destroy('a123') 且为了安全你还得与Free一致,方法体内释放前判断下对象是否为nil /// 不如直接用Free来的简单,所以这种方法可以不用. /// </summary> //destructor Destroy(a: string); overload; property name: string read Fname write Setname; property age: Integer read Fage write Setage; end; /// <summary> /// 定义一个人类的子类 妇女类 /// </summary> TWoman = class(TPerson) public /// <summary> /// 重定义一个构造方法,测试默认不写inherited Create的时候,是否调用了父类的构造方法 /// 试验证明: inherited Create 不可省略, 不写的时候不调用父类的构造函数,这样才是最 /// 合理的。 /// </summary> constructor Create; function makeLove(): string; end; var Form5: TForm5; implementation {$R *.dfm} { TPerson } constructor TPerson.Create; begin //标准写法 inherited Create; end; //constructor TPerson.Create(const aName: string; const aAge: Integer); //begin // inherited Create; // Fname := aName; // Fage := aAge; //end; //destructor TPerson.Destroy(a: string); //begin // //end; destructor TPerson.Destroy; begin end; procedure TPerson.Setage(const Value: Integer); begin Fage := Value; end; procedure TPerson.Setname(const Value: string); begin Fname := Value; end; procedure TForm5.Button1Click(Sender: TObject); var pp: TPerson; mm: TWoman; begin pp := TPerson.Create; mm := TWoman.Create; try finally pp.Free; mm.Free; end; end; { TWoman } constructor TWoman.Create; begin //不写这句不调用父类的构造函数,所以还是写上标准 安全。 inherited Create; end; function TWoman.makeLove: string; begin end; end.
从哲学的角度讲创建一个类的实例是这样的;
创建走正序:父亲.Create ----->> 儿子.Create ----->> 孙子.Create
销毁走逆序:父亲.Destroy <<----- 儿子.Destroy <<----- 孙子.Destroy
即先有父亲,父亲把一些基本通用的成员属性或方法初始化后 才能让儿子继承啊;没有父亲何来儿子呢;
所以一般不要把构造函数Create弄成虚函数。这样做也没意义;构造函数 一个类 重定义一个构造函数,子类的构造函数中可以使用inherit
来调用父类的构造函数,一般构造函数不会定义成虚函数,即不允许 overide ;比如 TObject的构造函数。
析构函数一般都是弄成虚函数,要求子类必须overide(虽然不overide也不报错,但是你要养成良好的习惯,尽量这么做),这样才能保证多态的
使用场景下,调用的是子类的析构函数,即:父类的实例 := 子类的.Create ;父类的实例.Free 依然是调用的
子类.Destroy 确保了;先销毁子类的成员,再销毁父类的成员。
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; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } end; /// <summary> /// 父亲类 /// </summary> TFather = class private Fname: string; Flist1: TStringList; procedure Setname(const Value: string); procedure Setlist1(const Value: TStringList); public //若需要初始化的时候做一些特殊的事的话,那么重定义构造函数 constructor Create; //若需要销毁的时候做一些特殊事的话,由于祖先类是虚函数,那么父类子类都需要overide到底. destructor Destroy; override; property name: string read Fname write Setname; property list1: TStringList read Flist1 write Setlist1; end; /// <summary> /// 儿子类 /// </summary> TSon = class(TFather) private Fage: Integer; Flist2: TStringList; procedure Setage(const Value: Integer); procedure Setlist2(const Value: TStringList); public //若需要初始化的时候做一些特殊的事的话,那么重定义构造函数 constructor Create; //若需要销毁的时候做一些特殊事的话,由于祖先类是虚函数,那么父类子类都需要overide到底. destructor Destroy; override; property age: Integer read Fage write Setage; property list2: TStringList read Flist2 write Setlist2; end; /// <summary> /// 孙子类 /// </summary> TGrandson = class(TSon) private Fsex: Boolean; procedure Setsex(const Value: Boolean); public //若需要初始化的时候做一些特殊的事的话,那么重定义构造函数 constructor Create; //没必要了,因为销毁的时候不需要做事 //destructor Destroy; override; property sex: Boolean read Fsex write Setsex; //如果不需要销毁的时候做一些事,比如这里我不定义一个TStringList,那么就没有必要去重写父类的Destroy //property list3: TStringList read Flist3 write Setlist3; end; var Form5: TForm5; implementation {$R *.dfm} procedure TForm5.Button1Click(Sender: TObject); var sz: TGrandson; begin sz := TGrandson.Create; try ShowMessage(sz.name); ShowMessage(sz.list1.Text); finally sz.Free; end; end; { TFather } constructor TFather.Create; begin inherited Create; Self.Fname := '小李飞刀'; Self.Flist1 := TStringList.Create; Self.Flist1.Add('111'); OutputDebugString('父亲'); end; { TSon } constructor TSon.Create; begin inherited Create; Self.Fage := 100; Self.Flist2 := TStringList.Create; Self.Flist2.Add('222'); OutputDebugString('儿子'); end; { TGrandson } constructor TGrandson.Create; begin inherited Create; Self.sex := True; OutputDebugString('孙子'); end; destructor TFather.Destroy; begin Self.Flist1.Free; inherited; end; procedure TFather.Setlist1(const Value: TStringList); begin Flist1 := Value; end; procedure TFather.Setname(const Value: string); begin Fname := Value; end; destructor TSon.Destroy; begin Self.Flist2.Free; inherited; end; procedure TSon.Setage(const Value: Integer); begin Fage := Value; end; procedure TSon.Setlist2(const Value: TStringList); begin Flist2 := Value; end; procedure TGrandson.Setsex(const Value: Boolean); begin Fsex := Value; end; procedure TForm5.FormCreate(Sender: TObject); begin ReportMemoryLeaksOnShutdown := True; end; end.
创建孙子类的实例的时候,会逐级先向上一直追溯到TObject.Create,先把继承过来的逐渐初始化一遍,才能轮到自己的。
以下是网上的摘抄,不一定正确,切图:
我觉得这个人 说的非常好,还是要看基类的,即祖先类。若祖先类 用了 virtual 那么无论是 构造函数 还是 析构函数,若你需要在构造和析构的时候 做一些特殊的事的话,那么你必须overide ,子类继续overide,overide到底。养成良好的编程习惯。
接下来我来举个例子来说明为什么,析构函数要能弄成虚方法:
例子1,重定义Destroy然后用Free来释放的话,那么会内存泄露;
针对这个问题,当然有多重解决方案,比如重定义Free方法,或者释放的时候用实例.Destroy ,然后为了安全 大不了 Destroy里 也判断下 Self是否为nil; 但是这些解决方案都是把问题 复杂化的方案了。何必不用overide呢。把父类的Destroy给覆盖掉。不就好了。即使再父类调用Free;由于 是子类创建的实例,那么父类的Destroy也是被子类的覆盖掉了的,那么就能保证TObject.Free;实际上是调用了T人类.Destroy,这样就不会有内存泄露了,这块设计的复杂吧,一般人不深入研究根本不会明白,因为析构的时候,我们并没有纯纯的使用Destroy,而是为了安全使用了Free; 而Free又是定义再父类的。
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; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } end; /// <summary> /// 人类 /// </summary> TPerson = class private Flist1: TStringList; procedure Setlist1(const Value: TStringList); public constructor Create; //这里Destroy我选择了重定义,没有覆盖父类TObject.Destroy //当调用.Free来释放的时候将永远不会执行这个析构函数 destructor Destroy; property list1: TStringList read Flist1 write Setlist1; end; var Form5: TForm5; implementation {$R *.dfm} { TPerson } constructor TPerson.Create; begin inherited; Self.Flist1 := TStringList.Create; Self.Flist1.Add('111'); end; destructor TPerson.Destroy; begin Self.Flist1.Free; inherited; end; procedure TPerson.Setlist1(const Value: TStringList); begin Self.Flist1 := Value; end; procedure TForm5.Button1Click(Sender: TObject); var pp: TPerson; begin pp := TPerson.Create; try ShowMessage(pp.list1.Text); finally //这里根本就没有调用我们上面声明的Destroy,依然是调用父类的Destroy, //因为Free是Free是个普通的方法声明父类,所以他依然是调用了父类的Destroy什么都没有做 //所以这里就会有内存泄露 pp.Free; end; end; procedure TForm5.FormCreate(Sender: TObject); begin ReportMemoryLeaksOnShutdown := True; end; end.
这篇博客可以说是百忙之中写出来的,由于这块设计的逻辑 会有点绕;我来个结论吧:
1.构造方法,如果子类需要加强,或需要重载,那么就需要重定义;
2.析构方法,如果子类需要加强,那么就需要重写覆盖overide父类的,且析构方法一般不重载。
3.普通方法,如果子类需要加强,那么就需要父类定义成虚方法,然后子类覆盖overide;只有这样才能做到多态的情况下使用。
2017-05-23 补充:
对于继承组件的类 构造方法必须覆盖;