2009-02-27 70 views
6

我一直在开发一段时间,而且到目前为止我还没有在开发中使用指针。在Delphi中使用指针

那么指针的好处是什么?应用程序运行速度更快还是使用更少的资源?因为我确定指针是重要的,你能“指向”我的一些文章,基本但很好开始在Delphi中使用指针吗? Google给了我太多太特殊的结果。

回答

30

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

  • 你可以给你想要的大小的内存。
  • 你只需要改变一个指针指向一个不同的内存部分,这可以节省大量的时间复制。

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包含这段内存的地址。 堆变量存在,直到您释放它们。这意味着如果在不释放对象的情况下退出funcion Test2,该对象仍然存在于堆上。但是你将无法访问它,因为地址(变量MyClass)消失了。

最佳做法

它几乎总是优选分配和在同一水平解除分配指针变量。

例如:

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

如果可以的话,尽量避免返回一个对象的实例功能。调用者是否需要处理该对象并不确定。这会造成内存泄漏或崩溃。

+0

希望这是足够的信息,我可以添加更多,如果你喜欢。 – 2009-02-27 15:40:21

2

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

请参阅Gamecats回答指针的优点。

在这个About.com article中,您可以找到Delphi中指针的基本说明。

2

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

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

10

你已经拿到了很多很好的答案到目前为止,但你已经与指针打交道时,你把长字符串,动态数组和对象,你应该开始引用答案开始怀疑,为什么你使用指针,而不是长字符串,动态数组和对象引用。在许多情况下,鉴于德尔福在你身上隐藏了很好的工作,是否有理由继续使用指针?

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

指针可以用来对付不同大小的数据类型(在编译时未知)

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

在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()。现在,写在一个循环中字符串的ň字符会导致UniqueString()被称为ñ倍,即使之后的第一时间确保了引用计数为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(),当第一个字符串字符的地址被分配给字符指针。