• Delphi 盒子上面看见的一个问题:关于类型转换Type Cast


    技术交流,DH讲解.

    问题如下:

      TBase = class
    
      end;
    
      TChild = class(TBase)
      public
        F1:Integer;
        procedure Say;
      end;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm3.FormCreate(Sender: TObject);
    var
      A:TBase;
      B:TChild;
    begin
      A:=TBase.Create;
      try
        B:=A as TChild;//编译成功,但是运行报错
        B.Say;
      finally
        A.Free;
      end;
    end;
    
    { TChild }
    
    procedure TChild.Say;
    begin
      ShowMessage('%D',[F1]);
    end;
    首先,为什么会报错?编译成功代表语法没有问题.
    我们看看As 是怎么实现的?
    function _AsClass(Child: TObject; Parent: TClass): TObject;
    {$IFDEF PUREPASCAL}
    begin
      Result := Child;
      if not (Child is Parent) then
        Error(reInvalidCast);   // loses return address
    end;
    {$ELSE}
    asm
            { ->    EAX     left operand (class)    }
            {       EDX VMT of right operand        }
            { <-    EAX      if left is derived from right, else runtime error      }
            TEST    EAX,EAX//如果对象是Nil就退出
            JE      @@exit
            MOV     ECX,EAX
    @@loop:
            MOV     ECX,[ECX]//获取自己的类型
            CMP     ECX,EDX//与要转换的类型进行比较
            JE      @@exit//一样就退出
            MOV     ECX,[ECX].vmtParent//不一样就取父类来比较
            TEST    ECX,ECX//判断父类是否为空.
            JNE     @@loop
    
            {       do runtime error        }
            MOV     AL,reInvalidCast//如果都不能匹配就报错
            JMP     Error
    
    @@exit:
    end;
    {$ENDIF}
    可以看到As会对类型进行一些列要求,而且是必须的.
    如果我们把代码改一下用强制类型转换看看:
    procedure TForm3.FormCreate(Sender: TObject);
    var
      A:TBase;
      B:TChild;
    begin
      A:=TBase.Create;
      try
        B:=TChild(A);//没有错误
        B.Say;//但是 弹出来的数据非0
      finally
        A.Free;
      end;
    end;

    现在运行不会错了,但是弹出来的数据却不是0,为什么?成员变量会初始化的,那么如果创建一个TChild对象的话,
    F1就应该是0,不管我们写没有写F1:=0;
    要解释这个就需要用到我们之前的知识了.
    正常情况下,A的内存:

    偏移 0-3 4-7
    内容 TBase的地址 $00000000
    正常情况下,B的内存:
    偏移 0-3 4-7 8-B
    内容 TChild地址 $00000000 F1变量的值

    也就是F1位于对象地址后面8个字节处.
    那么上面的代码中,我们TChild.Say; 会用到F1,而我们把A当成TChild的实例了,是吧?那么去找F1,它就会跑到A+8的地方去,
    但是我们看见实际A+8的地方不属于A管,所以这4个字节是未知的,没有被初始化成0,所以报出来也不太可能是0了.
    谁叫A是下黑手去抢的内存.

    从上面我们可以看到直接强制类型转换速度效率会快一些,所以我们在明白这样转换不会出问题就多用这样转换,但是这中间缺少检验的过程,所以有时候转换后结果可能会错误.
    所以我们需要注意了:

    If Sender Is TButton then
    	TButton(Sender).XXX;//别再用Sender As TButton了,都判断过了

    好,今天就唠叨到这里,我是DH.

  • 相关阅读:
    centos7 安装jdk及mysql8
    centos7 挂载数据盘
    windows server r2 之如何设置共享文件夹访问不需要输入用户名和密码
    svn 常见问题记录
    设计模式之代理模式
    ASP.Net在64位环境开发部署常见问题
    oracle 之创建用户,表空间,授权,修改用户密码
    oracle 常用语句
    IIS服务器 远程发布(Web Deploy)配置 VS2010 开发环境 Windows Server 2008服务器系统
    vs2012使用64位IIS EXPRESS调试
  • 原文地址:https://www.cnblogs.com/huangjacky/p/1624702.html
Copyright © 2020-2023  润新知