虚方法、抽象方法、抽象类、重定义、覆盖重写------我自己

参考万一的博客:

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;

 

转载于:https://www.cnblogs.com/del88/p/6360180.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第十一章 指针 11.1 理解指针 在计算机中,所有的数据都是存放在存储器中的。一般把存储器中的一个字节称为一个内存单元。为了正确访问内存单元,必须为每个内存单元编号,根据一个内存的编号即可准确找到该内存单元,内存单元的编号也叫地址,通常把这个地址称为指针(pointer)。一个指针变量的值就是某个内存单元的地址(或称某个内存单元的指针)。 在一个指针变量中存放一个数组的首地址,因为数组是连续存放的,通过访问指针变量取得数组的首地址,也就找到了该数组。在C语言中,一种数据类型或数据结构往往占有一组连续的内存单元,用指针描述一个数据结构的首地址,该指针指向这个数据结构。 11.2 指向变量的指针 #include int main() { int i=1,*pi=&i; printf("%d",sizeof(pi)); return 0; } 变量i的三个属性: (1)值:为1,通过变量名访问,如i+5。 (2)地址:占用内存空间的位置,通过&i访问。 (3)类型:为int,决定该变量能参加的运算,决定了其占用空间的大小(从起始地址开始占用的连续字节数),占用空间的大小用sizeof运算符计算,sizeof(i)或sizeof(int)。 变量的指针就是变量的地址,存放变量地址的变量是指针变量。C语言中允许用一个变量存放指针,称为指针变量。 11.2.1 指针变量定义 指针变量定义的一般形式: 类型说明符 *变量名; 如: int *pi; 对指针变量的定义包括3个内容: (1)指针类型说明:*表示这是一个指针变量。 (2)指针变量名:pi为指针变量名。 (3)指针指向的变量的数据类型int指针变量所指向的变量的数据类型,说明pi只能存储整型变量的地址。 如: float *pf; /*pf为指向浮点变量的指针变量*/ char *pc; /*pc为指向字符变量的指针变量*/ 11.2.2 指针变量引用 未经赋值的指针变量不能使用,否则将造成系统紊乱,甚至死机。指针变量只能赋予地址。C语言中,变量的地址是由编译系统分配的。 与指针相关的两个运算符: (1)&:取地址运算符 一般形式: &变量名 表示取一个内存变量的地址。 (2)*:指针运算符(或称“间接访问”运算符) 一般形式: *指针变量名 通过指针变间接访问指针变量所指向变量的数据。 #include int main() { int i=1,*pi=&i; printf("%d",sizeof(pi)); return 0; } 对指针变量的应用的说明: a.对*要区别类型说明符与间接访问符。 b.不能用一个数给指针变量赋值,但是指针可用0赋值,代表空指针,即不指向任何数据。 c.给指针变量赋值时,指针变量前不能加*。 如:int i; int *pi; *pi=&i; /*写法错误,应该为pi=&i*/ pi赋值&i后可用*pi间接访问i d.指针变量为指向具体有效地址时,直接访问会有危险。 如: int *pi; /*指针变量pi为赋值,不知道指向哪里*/ *pi=200; /*向pi所指向的地址空间赋值200*/ C语言对未赋值的指针变量的值是不确定的。上面语句中使pi所指向的空间赋值200,这时,当pi指向有用数据空间时,该数据将被200覆盖,导致数据破坏;当指针pi指向系统空间时,系统遭到破坏,严重时将导致系统瘫痪。 指针变量定义时,编译系统就会给定一个值,如何判定一个指针变量是否指向有用数据空间,建议定义指针时初始化为0,间接访问前让它指向有效空间,这样间接访问时可以判断指针是否指向有效地址。如: int *pi=0; · · · if(pi!=0) *pi=200; 省略号部分,若未使pi指向有效空间,对*pi的赋值不会进行。 e.指针变量的值可以改变,像普通变量一样被重新赋值,就是说可以改变指针变量的指向。 f.指针变量只能用同类型的地址赋值。 g.同类型指针变量间可以相互赋值。 例:交换指针变量的值。 #include int main() { int i1=3,i2=4; int *pi1=&i1;,*pi2=&i2;,*pi3=0; printf("*pi1=%d\t*pi2=%d\n",*pi1,*pi2); pi3=pi1; pi1=pi2; pi2=pi3; printf("*pi1=%d\t*pi2=%d\n",*pi1,*pi2); return 0; } 运行结果: *pi1=3 *pi2=4 *pi1=4 *pi2=3 交换了指针变量的值,导致指针变量交换了指向。 例:交换指针变量所指向的数据的值。 #include int main() { int i1=3,i2=4,temp=0,*pi1=&i1;,*pi2=&i2; printf("i1=%d\ti2=%d\n",i1,i2); temp=*pi1; *pi1=*pi2; *pi2=temp; printf("i1=%d\ti2=%d\n",i1,i2); } 运行结果: i1=3 i2=4 i1=4 i2=3 11.3 数组与指针 一个数组包含若干元素,每个数组元素都在内存中占用内存单元。数组的指针是指数组的起始地址,数组元素的指针是指数组元素的地址。 11.3.1 一维数组与指针 一个数组是由连续的一块内存单元组成的,数组名就是这块连续内存单元的首地址(常量)。一个数组元素的首地址也是指它所占有的内存单元的首地址。 #include int main() { int arr[5]; printf("%d",arr==&arr;[0]); return 0; } 运行结果: 1 arr与&arr;[0]指向同一内存单元,都是数组的首地址,也是0号元素的首地址。arr是常量地址,&arr;[0]是整型变量arr[0]的地址。 1.指针相关的运算符 (1)取地址运算符& (2)间接访问运算符* (3)赋值运算符=,给指针变量赋值。 (4)算术运算符+、-、++、-- p1+i:结果为p1地址值位置跳过(i*p1所指类型字节数)个字节后的地址。 p1-i:结果为p1地址值位置跳回(i*p1所指类型字节数)个字节后的地址。 p2-p1:结果为相差字节数÷指针变量所指类型字节数。 p2++:结果为p1地址值位置跳过p1所指类型字节数后的地址。 (5)关系运算,支持六种关系运算符,用来比较地址的大小。 例: int arr[5]; int *p1,*p2; p1=&arr;[0]; p2=&arr;[4]; ①*p1++:*和++同优先级,结合方向从右到左,所以*p1++等价*(p1++),先执行*p1,然后p1加1。表达式的值为arr[0],p1的值为&arr;[1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值