构造函数 Create 与 析构函数 Destroy

参考了万一的博客:

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 补充:

对于继承组件的类 构造方法必须覆盖;

原文地址:https://www.cnblogs.com/del88/p/2325722.html