我已经开发了一段时间了,到目前为止我还没有在开发过程中使用指针.
那么指针的好处是什么?应用程序运行得更快还是使用更少的资源?
因为我确信指针很重要,你可以"指出"一些文章,基本但很高兴开始在Delphi中使用指针吗?谷歌给了我太多,太特别的结果.
指针是指向一块内存的变量.优点是:
你可以给你想要的那块内存.
您只需将指针更改为指向不同的内存,这样可以节省大量的复制时间.
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;
如果可以,请尝试避免返回对象实例的函数.永远不确定调用者是否需要处理该对象.这会造成内存泄漏或崩溃.
到目前为止,您已经获得了很多好的答案,但是当您使用长字符串,动态数组和对象引用时已经处理指针的答案开始时,您应该开始想知道为什么要使用指针而不是长字符串,动态数组和对象引用.有没有理由继续使用指针,因为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类来处理,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;
在第二个函数中,当第一个字符串的地址被赋给char指针时,只有一个对UniqueString()的调用.