delphi.指针.应用

时间:2021-10-01 23:44:31

注:初稿...有点乱,可能增删改...

因为指针应用,感觉不好写,请大家指出错误,谢谢。

注意: 本文着重点讲的是指针的各类型的应用或使用,而不是说这种方法不应该+不安全+危险+不提倡使用。

其它:本文说的是x86环境,x64会有变化,且,只是讲述一些方法,细节部分,如果涉及到不同平台问题,勿太深究。:)

指针:按正规解释是:“指向另一内存块地址的变量”,它是一个变量值,只有4字节(x86=>sizeof(Pointer)=4, x64=8,以下都以x86为准)。

所以,它与内存其实息息相关,所以讲述前,我们要懂一个道理,指针,其实就是一个内存块地址的“代号”。

指针应用: 常用操作就是:New/GetMem后进行操作,然后Dispose/FreeMem,估计大伙都用的多了,这个不用多说了。

所想写的,是自己看到过的,写过的,遇到的及其延伸,总结一下,希望对大家有帮助。

指针方法一:强制转换 

      <警告:这种操作也最危险,不安全,容易造成越界形为,且难以发现问题>。

      先将警告放上面,这个表示:要慎重对待些操作,原因:

 a: 容易越界,且无错误提示。

越界即在超出规定安全范围内,引申在此,则是说操作:安全内存块范围外的内存块

有点绕口,不过很容易理解,安全内存块4字节,如果操作了4字节外的内存外就是越界。

越界的危害是严重级别,且难找的,如果细说,能说一堆,这里略过,因为着重点不在于此。 

b:数据不正确

强制转换,对于非恰当的数据时,它直接更改是数据值,在安全操作(不超出内存边界情况下),无任何提示,事后难以查觉。

数据的正确性给破坏,且无错误,对一个程序的危害是不言而喻了。

好了,危害说完,我得说:强制转换操作,确实很好用,且高效。

     下面开始列举我经常使用的操作:

     1:Pointer 与 TObject及子类实例的转换 

Pointer与对象实例的转换可以互换的,且没有编绎提示。 

因为对象实例其实说白了就是一个指针,只不过编绎器进行检查,来个编绎错误,不让你转换。 

其它:Pointer对其它数据类型的指针,也不需要:v := PDataType(p)这样写,直接: v := p; 反过来亦然。

注意:仅仅是Pointer类型, 所以,Pointer类型是强大的。

UI相关:在UI操作,很多组件是带有用户自定义的属性,,用于用户扩展属性的关联,如:

TStrings.Objects (TCombobox.Items/TListBox.Items/TMemo.Lines...)

TListItem.Data/TListNode.Data

TComponent.Tag: Integer --(只针对x86,不知道x64改为NativeInt没)

这类相关的扩展属性,要么是Pointer,要么是TObject,如果自己需要与之上下文相关的扩展数据,最方便使用了。

 View Code

其它转换:

sizeof(Pointer) = sizeof(Cardinal) = sizeof(Integer) = ... = 4 (x86)

所以,更多的时候,这类转换也是常用的,如:指针前进X字节:Pointer(Cardinal(p) + x); (x86)

还有Pointer与Cardinal/Integer相互转换,p = Pointer(v); v := Cardinal(p); ... 

Pointer类型转换很方便,所以,写组件时,为需要的类增加一个CustomData: Pointer,会是一种常态的写法:)

   2: buffer/Pointer与各类数据转换,及相关操作

更多的时候,我们需要与各种数据类型打交道,进行数据操作,协议封包(数据打包)

示例:发送一个数据包:格式:前4字节为长度,后4命令字,再根据命令字,进行跟随X字节。

通常的做法是:TMemoryStream,然后不断按协议进行Stream.Read/Write?经常能见到此似代码。

还有种做法:用string(ansi版本下)来代替TMemoryStream,因为Pos+Delete是相当方便,不过对于里面的代码,只能表示呵呵。

如果现在再写,会是写成如下:

 View Code

额,得注意:只适合定长的类型,如果有不定长的格式,buffer无法确认最大长度的,就得GetMem出场了(或SendBuffer+SendBufLen)

这写法的好处:

其一:数据类型更改好动手,比如协议版本升级,在cmd后面要加个seq: int32字段,按Stream的作法,你得先找到cmd写入的地方,

后面加句:Stream.Write(seq...),位置顺序不能变,如果位置不对,你就得抓瞎,抓协议数据包来找问题了。

如按上面,只要在定义中,cmd字段后加:seq: int32,然后,找个地方赋值就好了。

且重要的是:可以通过record定义,来知道协议的那几个字节都是做什么的,啥意思,这给后来开发人员减少出错的机会。

其二:解析(解包)简单

收到协议包的buffer后,判断一下包长度是否正常,正常就直接转换为对应指针类型,然后就p.xx就读取操作了。

如果按stream的方法,你得不停的Stream.Read(xxx)...

好了,你还用Stream的方法做协议打包解包吗?:D

其它定长转换,还有种典型就是:如果是固定长度格式的字符串解析,可以使用先定义,再转换,如时间:

 View Code

注:此法,只合适那种有固定格式的情况。

这里只是举例,是种思路,不是建议。一时半会的想不到更好的示例,就想到这个(时间一堆的函数可以转换,不用这样写)


   3:Pointer与var的转换

这个,不知怎么说了,所以写成var了,这个不好解释,请查示例:

 View Code

上面只是一个函数声明,其中第二参数与第四参数很有意思。

var buf; const buf; 在System.Move/Stream.Read/Write中很常见这类参数,具体名称,这个还真没注意,无类型参数(暂且这样叫)