如何正确传递带有 var 前缀的对象类型的参数?

Posted

技术标签:

【中文标题】如何正确传递带有 var 前缀的对象类型的参数?【英文标题】:How to correctly pass through argument of object type with var prefix? 【发布时间】:2011-07-09 22:19:18 【问题描述】:

总结:

type 
  MyObject = object
  end;

  MyRecord = record
  end;

  MyClass = class
  end;

  procedure ProcA(aMyObject: MyObject);
  procedure ProcB(var aMyObject: MyObject);
  procedure ProcC(aMyRecord: MyRecord);
  procedure ProcD(var aMyRecord: MyRecord);
  procedure ProcE(aMyClass: MyOClass);
  procedure ProcF(var aMyClass: MyClass);
    MyObjectMyRecord 是值类型,而 MyClass 是引用类型。 值类型变量的赋值将复制该变量;引用类型变量的赋值将复制引用。 ProcAProcC 中的参数是原始参数的副本。 ProcBProcD 中的参数是原始参数。 ProcE 中的参数是原始引用的副本。 ProcF 中的参数是原始引用。 关于如何包装在 agg_2D.pas 单元中声明的 Agg2D 对象进行绘制,请参阅下面大卫的回答。

============================================= 我正在学习使用 AggPas,它是一个纯帕斯卡矢量图形绘图 API。具体来说,使用包含 Agg2D 对象的单元 agg_2D.pas 代替包含 TAgg2D 类的单元 Agg2D.pas。选择单位 agg_2D.pas 而不是单位 Agg2D.pas 的原因是为了跨平台能力。

但是,我无法正确传递带有 var 前缀的 Agg2D 对象类型的参数。如以下代码所示,我想将 TForm1 创建的 Agg2D 对象传递给另一个实际负责绘制形状的类。但是,它不起作用。你能帮忙评论一下可能的原因吗?看来我一定错过了有关对象类型的重要概念。任何建议表示赞赏!可以新建一个VCL应用,附加FormCreate handler,一行一行注释掉绘制代码看看效果。

    unit Unit1;

    interface

    uses
      agg_2D,
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs;

    type
      TRenderEngine_BMP = class;
      TRenderEngine_Agg = class;
      TForm1 = class;

      TRenderEngine_BMP = class
      private
        fBMP: TBitmap;
      public
        constructor Create(var aBMP: TBitmap);
        procedure DrawEllipse;
      end;

      TRenderEngine_Agg = class
      private
        fVG: Agg2D;
      public
        constructor Create(var aVG: Agg2D);
        procedure DrawEllipse;
      end;

      TForm1 = class(TForm)
        procedure FormCreate(Sender: TObject);
      private
         Private declarations 

        fBMP: TBitmap;
        fVG: Agg2D;
        fEngine_BMP: TRenderEngine_BMP;
        fEngine_Agg: TRenderEngine_Agg;

        procedure AttachBMP(var aVG: Agg2D; var aBMP: TBitmap);
        procedure OnSceneResize(Sender: TObject);
        procedure OnScenePaint(Sender: TObject);
      public
         Public declarations 
      end;

    var
      Form1: TForm1;

    implementation

    $R *.dfm

    uses
      Math;

     TRenderEngine_BMP 

    constructor TRenderEngine_BMP.Create(var aBMP: TBitmap);
    begin
      Self.fBMP := aBMP;
    end;

    procedure TRenderEngine_BMP.DrawEllipse;
    begin
      Self.fBMP.Canvas.ellipse(20, 20, 80, 80);
    end;

     TRenderEngine_Agg 

    constructor TRenderEngine_Agg.Create(var aVG: Agg2D);
    begin
      Self.fVG := aVG;
    end;

    procedure TRenderEngine_Agg.DrawEllipse;
    begin
      Self.fVG.ellipse(50, 50, 30, 30);
    end;

     TForm1 

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      Self.OnResize := $IFDEF FPC @ $ENDIF OnSceneResize;
      Self.OnPaint := $IFDEF FPC @ $ENDIF OnScenePaint;

      fBMP := TBitmap.Create;
      fBMP.PixelFormat := pf32bit;
      fBMP.Canvas.Brush.Style := bsSolid;
      fBMP.Canvas.Brush.Color := clBlue;
      fBMP.Width := ClientWidth;
      fBMP.Height := ClientHeight;

      fVG.Construct;
      Self.AttachBMP(fVG, fBMP);

      fEngine_BMP := TRenderEngine_BMP.Create(fBMP);
      fEngine_Agg := TRenderEngine_Agg.Create(fVG);
    end;

    procedure TForm1.AttachBMP(var aVG: Agg2D; var aBMP: TBitmap);
    var
      tmpBuffer: pointer;
      tmpStride: integer;
    begin
      tmpStride := integer(aBMP.ScanLine[1]) - integer(aBMP.ScanLine[0]);

      if tmpStride < 0 then
        tmpBuffer := aBMP.ScanLine[aBMP.Height - 1]
      else
        tmpBuffer := aBMP.ScanLine[0];

      aVG.attach(tmpBuffer, aBMP.Width, aBMP.Height, tmpStride);
    end;

    procedure TForm1.OnScenePaint(Sender: TObject);
    begin
      Self.fBMP.Canvas.FillRect(Self.ClientRect);

    //  Self.fBMP.Canvas.ellipse(20, 20, 80, 80);  // Work
    //  Self.fVG.ellipse(50, 50, 30, 30);          // Work
    //  Self.fEngine_BMP.DrawEllipse;              // Work
      Self.fEngine_Agg.DrawEllipse;                // Do not work

      Self.Canvas.Draw(0, 0, fBMP);
    end;

    procedure TForm1.OnSceneResize(Sender: TObject);
    begin
      fBMP.Width := IfThen(ClientWidth > 0, ClientWidth, 2);
      fBMP.Height := IfThen(ClientHeight > 0, ClientHeight, 2);

      Self.AttachBMP(fVG, fBMP);
    end;

    end.

如果我删除所有出现的过程参数的 var 前缀,第二个画圆代码也会停止工作,我不太明白。为方便起见,单位如下所示:

    unit Unit1;

    interface

    uses
      agg_2D,
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs;

    type
      TRenderEngine_BMP = class;
      TRenderEngine_Agg = class;
      TForm1 = class;

      TRenderEngine_BMP = class
      private
        fBMP: TBitmap;
      public
        constructor Create(aBMP: TBitmap);
        procedure DrawEllipse;
      end;

      TRenderEngine_Agg = class
      private
        fVG: Agg2D;
      public
        constructor Create(aVG: Agg2D);
        procedure DrawEllipse;
      end;

      TForm1 = class(TForm)
        procedure FormCreate(Sender: TObject);
      private
         Private declarations 

        fBMP: TBitmap;
        fVG: Agg2D;
        fEngine_BMP: TRenderEngine_BMP;
        fEngine_Agg: TRenderEngine_Agg;

        procedure AttachBMP(aVG: Agg2D; aBMP: TBitmap);
        procedure OnSceneResize(Sender: TObject);
        procedure OnScenePaint(Sender: TObject);
      public
         Public declarations 
      end;

    var
      Form1: TForm1;

    implementation

    $R *.dfm

    uses
      Math;

     TRenderEngine_BMP 

    constructor TRenderEngine_BMP.Create(aBMP: TBitmap);
    begin
      Self.fBMP := aBMP;
    end;

    procedure TRenderEngine_BMP.DrawEllipse;
    begin
      Self.fBMP.Canvas.ellipse(20, 20, 80, 80);
    end;

     TRenderEngine_Agg 

    constructor TRenderEngine_Agg.Create(aVG: Agg2D);
    begin
      Self.fVG := aVG;
    end;

    procedure TRenderEngine_Agg.DrawEllipse;
    begin
      Self.fVG.ellipse(50, 50, 30, 30);
    end;

     TForm1 

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      Self.OnResize := $IFDEF FPC @ $ENDIF OnSceneResize;
      Self.OnPaint := $IFDEF FPC @ $ENDIF OnScenePaint;

      fBMP := TBitmap.Create;
      fBMP.PixelFormat := pf32bit;
      fBMP.Canvas.Brush.Style := bsSolid;
      fBMP.Canvas.Brush.Color := clBlue;
      fBMP.Width := ClientWidth;
      fBMP.Height := ClientHeight;

      fVG.Construct;
      Self.AttachBMP(fVG, fBMP);

      fEngine_BMP := TRenderEngine_BMP.Create(fBMP);
      fEngine_Agg := TRenderEngine_Agg.Create(fVG);
    end;

    procedure TForm1.AttachBMP(aVG: Agg2D; aBMP: TBitmap);
    var
      tmpBuffer: pointer;
      tmpStride: integer;
    begin
      tmpStride := integer(aBMP.ScanLine[1]) - integer(aBMP.ScanLine[0]);

      if tmpStride < 0 then
        tmpBuffer := aBMP.ScanLine[aBMP.Height - 1]
      else
        tmpBuffer := aBMP.ScanLine[0];

      aVG.attach(tmpBuffer, aBMP.Width, aBMP.Height, tmpStride);
    end;

    procedure TForm1.OnScenePaint(Sender: TObject);
    begin
      Self.fBMP.Canvas.FillRect(Self.ClientRect);

    //  Self.fBMP.Canvas.ellipse(20, 20, 80, 80);  // Work
    //  Self.fVG.ellipse(50, 50, 30, 30);          // Do not Work
    //  Self.fEngine_BMP.DrawEllipse;              // Work
      Self.fEngine_Agg.DrawEllipse;                // Do not work

      Self.Canvas.Draw(0, 0, fBMP);
    end;

    procedure TForm1.OnSceneResize(Sender: TObject);
    begin
      fBMP.Width := IfThen(ClientWidth > 0, ClientWidth, 2);
      fBMP.Height := IfThen(ClientHeight > 0, ClientHeight, 2);

      Self.AttachBMP(fVG, fBMP);
    end;

    end.

【问题讨论】:

你说“不工作”是什么意思?您收到什么错误消息?在编译时还是在运行时?如果没有这样的信息,你就会让人们更难帮助你。 @Marjan Venema:感谢您的宝贵时间!代码画了一个圆圈。带有“不工作”评论的那一行没有给出圆圈或其他任何内容。 【参考方案1】:

我很难理解你在这里做什么。我认为您的基本问题是Agg2Dobject,值类型也是如此。你拿它的副本,以便有两份而不是一份。作者选择使用 object 而不是类,但这样做需要您非常注意值语义而不是 TObject 后代的引用语义。

要使其工作的快速技巧是将fVG: Agg2D; 更改为fVG: ^Agg2D; 并在TRenderEngine_Agg.Create 中将Self.fVG := aVG 更改为Self.fVG := @aVG。随着这种变化,椭圆被绘制出来。

现在,我认为您需要重新考虑您的设计。如果你想在渲染类中封装一个 Agg2D 对象,那很好,但你不能复制 Agg2D 对象。

以下是我将如何编写代码来处理该问题:

TRenderEngine_Agg = class
private
  fVG: Agg2D;
public
  constructor Create;
  procedure AttachBMP(aBMP: TBitmap);
  procedure DrawEllipse;
end;

constructor TRenderEngine_Agg.Create;
begin
  fVG.Construct;
end;

procedure TRenderEngine_Agg.AttachBMP(aBMP: TBitmap);
var
  tmpBuffer: pointer;
  tmpStride: integer;
begin
  tmpStride := integer(aBMP.ScanLine[1]) - integer(aBMP.ScanLine[0]);

  if tmpStride < 0 then
    tmpBuffer := aBMP.ScanLine[aBMP.Height - 1]
  else
    tmpBuffer := aBMP.ScanLine[0];

  fVG.attach(tmpBuffer, aBMP.Width, aBMP.Height, tmpStride);
end;

procedure TRenderEngine_Agg.DrawEllipse;
begin
  Self.fVG.fillColor(30, 50, 20);
  Self.fVG.blendMode(BlendContrast );
  Self.fVG.ellipse(50, 50, 30, 30);
end;

procedure TForm20.OnSceneResize(Sender: TObject);
begin
  fBMP.Width := IfThen(ClientWidth > 0, ClientWidth, 2);
  fBMP.Height := IfThen(ClientHeight > 0, ClientHeight, 2);

  fEngine_Agg.AttachBMP(fBMP);
end;

这个想法是将所有与 Agg2D 对象有关的东西放在TRenderEngine_Agg 中。如果你这样做,那么我认为你会是金色的!

【讨论】:

@David Heffernan:非常感谢您的帮助!我会尝试你的建议!同时,您提到 Agg2D 是对象类型,因此是值类型? (我的意思是,Rob 刚刚提到它也是引用类型。) @Xichen Rob 在这种情况下是不正确的。我想他不熟悉 Agg2D,这是一段非常不标准的代码。如果使用object 而不是class。如果有帮助,您可以像想到 record 一样想到 object 这是一个对象而不是一个类?这样就可以解释了。人们仍然这样做吗? 是的,我删除了我的答案。关于“var”的部分是真实的,但无关紧要。关于未初始化tmpBuffer 的部分是错误的,因此无关紧要。 (感谢我无意中误以为这是一个好答案的三个人。您帮助我获得了“纪律严明”的徽章。)我对“var”的cmets总结如下:仅在分配时使用“var”参数的新值,如X := Y,而不是X.Property := YX.Method @Rob:非常感谢您再次撰写有关使用 var 前缀的最佳实践的 cmets!

以上是关于如何正确传递带有 var 前缀的对象类型的参数?的主要内容,如果未能解决你的问题,请参考以下文章

如何在大型机汇编程序中对一对带有“+”或“-”前缀的传递值求和?

js 函数参数传递引用类型与基本类型

函数的参数传递

Scala隐式参数

将模板类型传递给宏[重复]

给定传递给它的参数类型,如何确定函数参数的类型?