zoukankan      html  css  js  c++  java
  • 构造函数 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 补充:

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

  • 相关阅读:
    Linux文件误删除恢复操作【转】
    segment fault异常及常见定位手段【转】
    Linux AUFS 文件系统【转】
    Linux MTD系统剖析【转】
    Linux UBI子系统设计初探【转】
    python笔记54-re正则匹配替换字符串(sub和subn)
    python笔记53-Leetcode面试题:请实现一个函数,把字符串 s 中的每个空格替换成"%20"
    咏南中间件支持客户端控制数据库事务
    mormot2 tbsonwriter
    firedac获取自增长字段值
  • 原文地址:https://www.cnblogs.com/del88/p/2325722.html
Copyright © 2011-2022 走看看