【问题标题】:Using Pointers in Delphi在 Delphi 中使用指针
【发布时间】:2009-02-27 15:17:00
【问题描述】:

我已经开发了一段时间了,到目前为止我还没有在我的开发中使用过指针。

那么指针有什么好处呢?应用程序运行速度更快还是使用的资源更少?

因为我确信指针很重要,你能“指点”我一些文章吗?基本但很好地开始在 Delphi 中使用指针? Google 给了我太多、太特别的结果。

【问题讨论】:

    标签: delphi pointers


    【解决方案1】:

    指针是指向一块内存的变量。优点是:

    • 你可以给那块内存你想要的大小。
    • 您只需将指针更改为指向另一块内存,从而节省大量复制时间。

    Delphi 使用了很多隐藏指针。例如,如果您正在使用:

    var
      myClass : TMyClass;
    begin
      myClass := TMyClass.Create;
    

    myClass 是一个指向对象的指针。

    另一个例子是动态数组。这也是一个指针。

    要更多地了解指针,您需要更多地了解内存。每条数据可以存在于不同的数据中。

    例如全局变量:

    unit X;
    
    interface
    
    var
      MyVar: Integer;
    

    在数据段中定义了一个全局变量。数据段是固定的。并且在程序的生命周期中,这些变量都是可用的。这意味着内存不能用于其他用途。

    局部变量:

    procedure Test;
    var
      MyVar: Integer;
    

    堆栈上存在局部变量。这是一块用于管理的内存。它包含函数的参数(好的,有些放在寄存器中,但现在不重要了)。它包含返回地址,因此如果程序结束,cpu 知道返回到哪里。它包含函数中使用的局部变量。 局部变量只存在于函数的生命周期中。如果函数结束,则无法以可靠的方式访问局部变量。

    堆变量:

    procedure Test2;
    var
      MyClass: TMyClass;
    begin
      MyClass := TMyClass.Create;
    

    变量 MyClass 是一个指针(它是在堆栈上定义的局部变量)。通过构造一个对象,您可以在堆上分配一块内存(不用于程序和堆栈的大块“其他”内存)。变量 MyClass 包含这块内存的地址。 堆变量在您释放它们之前一直存在。这意味着如果您退出函数 Test2 而不释放对象,则该对象仍然存在于堆中。但是您将无法访问它,因为地址(变量 MyClass)已消失。

    最佳做法

    几乎总是最好在同一级别分配和释放指针变量。

    例如:

    var
      myClass: TMyClass;
    begin
      myClass := TMyClass.Create;
      try
        DoSomething(myClass);
        DoSomeOtherthing(myClass);
      finally
        myClass.Free;
      end;
    end;
    

    如果可以,尽量避免使用返回对象实例的函数。调用者是否需要处理对象永远无法确定。这会造成内存泄漏或崩溃。

    【讨论】:

    • 谢谢,我愿意帮忙。但如果能得到赞赏总是好的。
    【解决方案2】:

    到目前为止,您已经得到了很多很好的答案,但是从您在使用长字符串、动态数组和对象引用时已经在处理指针的答案开始,您应该开始想知道为什么您会 使用指针,而不是长字符串、动态数组和对象引用。考虑到 Delphi 在很多情况下都能很好地隐藏指针,还有什么理由继续使用指针?

    让我举两个在 Delphi 中使用指针的例子。如果您主要编写业务应用程序,您会发现这可能与您完全无关。但是,如果您需要使用任何标准 Delphi 单元未导入的 Windows 或第三方 API 函数,并且在(例如)JEDI 库中找不到导入单元,则它可能变得很重要。它可能是在字符串处理代码中实现必要的最后一点速度的关键。

    指针可用于处理不同大小的数据类型(编译时未知)

    考虑 Windows 位图数据类型。每个图像可以有不同的宽度和高度,并且有不同的格式,从黑白(每像素 1 位)到 2^4、2^8、2^16、2^24 甚至 2^32 灰度值或颜色.这意味着在编译时不知道位图将占用多少内存。

    在 windows.pas 中有 TBitmapInfo 类型:

    type
      PBitmapInfo = ^TBitmapInfo;
      tagBITMAPINFO = packed record
        bmiHeader: TBitmapInfoHeader;
        bmiColors: array[0..0] of TRGBQuad;
      end;
      TBitmapInfo = tagBITMAPINFO;
    

    TRGBQuad 元素描述单个像素,但位图当然包含多个像素。因此,永远不会使用 TBitmapInfo 类型的局部变量,而总是使用指向它的指针:

    var
      BmpInfo: PBitmapInfo;
    begin
      // some other code determines width and height...
      ...
      BmpInfo := AllocMem(SizeOf(TBitmapInfoHeader)
        + BmpWidth * BmpHeight * SizeOf(TRGBQuad));
      ...
    end;
    

    现在使用指针可以访问所有像素,即使 TBitmapInfo 确实只有一个像素。请注意,对于此类代码,您必须禁用范围检查。

    这样的事情当然也可以用 TMemoryStream 类来处理,它基本上是一个友好的封装指向内存块的指针。

    当然,简单地创建一个TBitmap 并指定它的宽度、高度和像素格式要容易得多。再说一遍,Delphi VCL 确实消除了大多数需要指针的情况。

    指向字符的指针可以用来加速字符串操作

    与大多数微优化一样,这是仅在极端情况下使用的东西,在您分析并发现使用字符串的代码会消耗大量时间之后。

    字符串的一个很好的特性是它们是引用计数的。复制它们不会复制它们占用的内存,只会增加引用计数。只有当代码尝试修改引用计数大于 1 的字符串时,才会复制内存,创建引用计数为 1 的字符串,然后可以安全地对其进行修改。

    字符串的一个不太好的属性是它们是引用计数的。每个可能修改字符串的操作都必须确保引用计数为 1,否则对字符串的修改将是危险的。替换字符串中的字符就是这样的修改。为了确保引用计数为 1,每当写入字符串中的字符时,编译器都会添加对 UniqueString() 的调用。现在在循环中写入字符串的 n 个字符将导致 UniqueString() 被调用 n 次,即使在第一次之后是确保引用计数为 1。这意味着基本上 n - 1 次调用 UniqueString() 是不必要的。

    使用指向字符的指针是加速涉及循环的字符串操作的常用方法。想象一下,您希望(出于显示目的)用一个小点替换字符串中的所有空格。使用调试器的 CPU 视图并比较为此代码执行的代码

    procedure MakeSpacesVisible(const AValue: AnsiString): AnsiString;
    var
      i: integer;
    begin
      Result := AValue;
      for i := 1 to Length(Result) do begin
        if Result[i] = ' ' then
          Result[i] := $B7;
      end;
    end;
    

    使用此代码

    procedure MakeSpacesVisible(const AValue: AnsiString): AnsiString;
    var
      P: PAnsiChar;
    begin
      Result := AValue;
      P := PAnsiChar(Result);
      while P[0] <> #0 do begin
        if P[0] = ' ' then
          P[0] := $B7;
        Inc(P);
      end;
    end;
    

    在第二个函数中,只有一次调用 UniqueString(),此时第一个字符串字符的地址被分配给 char 指针。

    【讨论】:

      【解决方案3】:

      您可能已经使用过指针,但您只是不知道。类变量是指针,字符串是指针,动态数组是指针,Delphi 只是为你隐藏了它。当您执行 API 调用(将字符串转换为 PChar)时,您会看到它们,但即使这样,Delphi 也可以隐藏很多。

      有关指针的优势,请参阅 Gamecats 的答案。

      About.com article 中,您可以找到 Delphi 中指针的基本解释。

      【讨论】:

        【解决方案4】:

        某些数据结构需要指针。最简单的例子是链表。这种结构的优点是您可以重组元素而无需在内存中移动它们。例如,您可以拥有一个大型复杂对象的链表并快速交换其中任意两个,因为您确实必须调整两个指针而不是移动这些对象。

        这适用于许多语言,包括 Object Pascal (Delphi)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-08-15
          相关资源
          最近更新 更多