来自:http://www.cnblogs.com/qiusl/p/4026459.html

---------------------------------------------------------------

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

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

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

其它:本文说的是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,如果自己需要与之上下文相关的扩展数据,最方便使用了。

typePMyData = ^TMyData;TMyData = recordv1: Integer;v2: string;end;// 对扩展属性设置
varentry: PMyData;
beginnew(entry);entry.v1 := xx;entry.v2 := yy;Combobox1.Lines.AddObject('test', Pointer(entry));Listbox1.Items.AddObject('test', Pointer(entry));with Listview1.Items.Add dobegincaption := 'test';subItems.Add('sub-item');data := entry;end;
end;// 从扩展属性中读取
varindex: Integer;entry: PMyData;
beginindex := Combobox1.ItemIndex;if index <> -1 thenbeginentry := Pointer(Combobox1.Lines.Objects[index]);ShowMessage(entry.v2);end;// listbox1如上使用// ListItem取的是data属性
end;

其它转换:

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是相当方便,不过对于里面的代码,只能表示呵呵。

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

// 首先, 先定义好数据格式
constCMD_01 = $0001;CMD_02 = $0002;typePProtocolData = ^TProtocolData;TProtocolData = packed recordlen: int32;cmd: int32;case Integer ofCMD_01: ( cmd_01: TProtocolCmd01; );CMD_02: ( cmd_02: TProtocolCmd02; );end;// 打包/封包,直接利用格式,进行转换+写入
vardata: PProtocolData;buffer: array [0..MAX_SIZE - 1] of Byte;
begindata := @buffer[0];data.len := xx;data.cmd := CMD_01;data.cmd_01 := cmd_01_data;send(data, data.len);
end;// 解包,直接用数据格式指针转换buffer
procedure do_some(buffer: Pointer; size: int32);
vardata: PProtocolData;
begindata := buffer;if size < data.len thenerrormsg_and_exit('data packet is invalid.');case data.cmd ofCMD_01: do_cmd_01(data.cmd_01);CMD_02: do_cmd_02(data.cmd_02);end;
end;

额,得注意:只适合定长的类型,如果有不定长的格式,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

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

// s = '2014-10-01 08:09:10'
function ToDateTime(const s: string): TDatetime;
typePMyDateString = ^TMyDateString;TMyDateString = packed recordYY: array [0..3] of Char;S1: Char;MM: array [0..1] of Char;S2: Char;DD: array [0..1] of Char;S3: Char;HH: array [0..1] of Char;S4: Char;NN: array [0..1] of Char;S5: char;SS: array [0..1] of Char;end;
varp: PMyDateString;yy, mm, dd, hh, nn, ss: Word;
beginif Length(s) < sizeof(TMyDateString) thenerror_and_exit('invalid date string');p := Pointer(s);yy := ToWord(p.YY, sizeof(p.YY));mm := ToWord(p.MM, sizeof(p.MM));...result := EncodeDate(yy, mm, dd) + EncodeTime(hh, nn, ss, 0);
end;

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

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

3:Pointer与var的转换

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

Delphi定义
function ReadFile(hFile: THandle; var Buffer; nNumberOfBytesToRead: DWORD;var lpNumberOfBytesRead: DWORD; lpOverlapped: POverlapped): BOOL; stdcall;
C++定义
BOOL ReadFile(HANDLE hFile,    // handle of file to read LPVOID lpBuffer,    // address of buffer that receives data  DWORD nNumberOfBytesToRead,    // number of bytes to read LPDWORD lpNumberOfBytesRead,    // address of number of bytes read LPOVERLAPPED lpOverlapped     // address of structure for data
   );var Buffer   ==>  LPVOID lpBuffer
var lpNumberOfBytesRead: DWORD;  ==> LPDWORD lpNumberOfBytesRead

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

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

如果对应于C/C++来说,它应该是LPVOID,这是D自有的数据类型,它是需要传递数据内存块起始位置。如string[1], Integer/及sizeof可计算长度的,直接传递。

扯远了,继续看第四参数,才是我要表达的重点,var X: DWORD  等价于 X: PWORD; 这是D中最自由的部分。

然后延伸:我定义一函数/方法或回调指针,如:

typeTMyEvent = procedure(AParam: Integer; Custom: Pointer);procedure DoJob(event: TMyEvent; custom: Pointer);
beginif assigned(@event) thenevent(10, custom);
end;// 上面是最简单的,也是经典回调或事件写法吧,经常写事件或接口API,都明白怎么回事。// 然后调用,对于custom: Pointer就自由了,请看:
typePMyData = ^TMyData;TMyData = recordx, y: Integer;end;procedure OnEvent1(param: Integer; var data: TMyData);
begindata.x := param * 2;data.y := param * 3;
end;procedure TForm1.Button1Click(Sender: TObject);
vardata: TMyData;
begindata.x := 0;data.y := 0;DoJob(@OnEvent1, @data);ShowMessageFmt('x: %d, y: %d', [data.x, data.y]));
end;

看明白了没?OnEvent1对应的回调第二参数写成var data: TMyData,且编绎+结果正确。

Pointer只是一个指针,var也是传地址进行操作,本质是一样的,所以,我们写这个custom: Pointer是可以多变的。

以上,只是一个例子,请再回头看Pointer与对象,其它数据类型转换,你就可以自己继续延伸,写自己的自由写法了。

指针方法二: 指针+-运算

指针的+-运算,即:p := p + 1; p := p - 1; 非inc(...)及 p := Pointer(Cardinal(p) + 1);此类。

D2007(包括)以下版本,这种操作仅限于PChar(PAnsiChar)可以这样进行操作,D2010开始PByte也可以了:)

PAnsiChar +- len           => PAnsiChar;

PAnsiChar +- PAnsichar => len

就这是原因,因为两地址相加减,可得到长度,在字符串解析过程中,保留一个指针A,另一指针B进行规则匹配,

然后B-A,就得到一个规则内的长度,这个写字符或内存状态解析,是一个常用手法。

PAnsiChar支持下标(p[x] := xx; (p + x)^ := xx),其它, PByte要到高版本才支持。

注:个人更喜欢用PAnsiChar进行操作,而不是PByte, Cardinal, NativeUInt, IntPtr之类。

原因很简单:

a: PAnsiChar从低版本(ansi)兼容到高版本的XE(unicode),且一直支持+-操作

b: PByte的+-到D2010才支持

c: Cardinal进行+-,到了XE2 x64后,就不对了,因为x64的指针值是8字节

d: NativeUInt/IntPtr低版本不支持。

。。。其它用法,想到再加。

指针应用方法三: 偏移

指针偏移在D里面,估计大家都很少用它操作,但估计个个都在用。

因为不管用哪种语言,这种操作手法是最常用的,因为这手法,内存管理用的最多了:D

都是用了这些简单的手法进行一系列的操作后,才返回给使用者的。

看下面一段很有意思的代码:

typePStrRec = ^TStrRec;TStrRec = packed record{$if defined(CPUX64)}padding: LongInt;{$ifend}{$if CompilerVersion >= 20.0}code: Word;elem: Word;{$ifend}ref: Integer;len: Integer;data: array [0..0] of Char;end;procedure TForm1.FormCreate(Sender: TObject);
beginReportMemoryLeaksOnShutdown := True;
end;procedure TForm1.Button1Click(Sender: TObject);
varp: PStrRec;s: string;
beginp := AllocMem(sizeof(TStrRec) + sizeof(Char) * 10);p.ref := 1;p.len := 10;StrPLcopy(p.data, 'abc', 3);Pointer(s) := @p.data[0];ShowMessage(s);
end;

输入完这代码后,运行后点击一下button,显示了abc的BOX,然后退出。是不是发现没有泄露啊:)

上面代码,其实就是模拟了string构成,p就是string的基本构成,然后s接管了p的生存期,然后出了Button1Click作用域后,s就会自动给free了。

额,扯远了,string构成不是重点,重点是偏移,平常所用的string,其实就是一个偏移的手法,只不过D隐藏了。

上面例子中,Pointer(s) := @p.data[0]就是一个偏移的做法。

指针偏移概念(个人理解):

a: 后偏移:在真实地址后,根据自己的规则,进行移动固定字节后(后偏移),得到的新地址返回给调用者。

这种方式是:GetMem取得X+Y个字节,X=自己分配规则固定长度,Y=req.size,返回时,地址向后移动X (addr := P + X)

释放的时候,将地址向后移动向前移动X字节,再行FreeMem,string就是这种情况。

b: 不偏移:申请到的地址,不移动地址,但它申请的长度比请求的大,其它长度,用于其它处理。

还是: P := GetMem(X+Y),其中,X长度是A处理所需,Y长度是B处理所需

typePMyData = ^TMyData;TMyData = recordv1, v2: Integer;end;PMyDataEx = ^TMyDataEx;TMyDataEx = recorddata: TMyData;dataEx1: Integer;dataEx2: Integer;end;procedure DoData(p: PMyData);
beginp.v1 := 1;p.v2 := 2;
end;procedure DoDataEx(p: PMyData);
vare: PMyDataEx;
begine := Pointer(p);e.dataEx1 := 10;e.dataEx2 := 20;
end;

额,好像跟偏移没啥关系,不过,个人觉得还是归纳到这里。

偏移,用话来说就是:你操作你的,我操作我的,各不相干。

     使用指针偏移目地:

         上面已经举例偏移,可能有人会觉得麻烦,有啥子用。我也觉得这种方式的代码确实有些限制,也不好写。

但有几点好处:

a: 减少内存分配次数:少一次分配就加一分效率,在某些场合是能省则省。

b:定位查找快,如果不用这法子,查找这地址与之相匹配的上下文(context),你得循环?hash?

这玩意用个指针+-的法子,就可以找到对应的数据,为何不用?

坏处也是有的:

a: 增加代码复杂度

b: 出错几率相对大

哈,有好有坏,:)

先写到这里了。以后想到啥再写,或整理,或细化一下,感觉写得不是很工整。。。

额,水平有限,如有雷同,就是盗版!

2014.10.18 by qsl

转载于:https://www.cnblogs.com/del88/p/5446568.html

delphi.指针.应用----应用重要 多看 多练相关推荐

  1. Delphi指针大全

    Delphi指针理解 看一个指针用法的例子:      1          var      2              X,    Y:    Integer;        //    X   ...

  2. delphi指针简单入门

    delphi指针简单入门:         看一个指针用法的例子:     1         var     2             X,   Y:   Integer;       //   ...

  3. DELPHI 指针使用用的一篇好文 收藏

    DELPHI 指针使用用的一篇好文 收藏     Delphi里自己管理内存的两对函数 new(),dispose()和getmem(),freemem() 大家都认为,C语言之所以强大,以及其自由性 ...

  4. Delphi指针用法

    delphi 中一切皆指针,任何类型都是指针,不错,你没看错.不过本篇不详细的说明delphi中指针的用法,因为篇幅所限,只是简单说明一下.delphi 中有的语法看起来比较奇怪如ppointer等, ...

  5. Delphi 指针, 静态数组, 动态数组

    https://www.cnblogs.com/shangdawei/archive/2013/04/30/3051656.html指针 : 指针是一个特殊的变量, 它里面存储的数值被解释成为内存里的 ...

  6. 关于delphi指针(转)

    浅谈Object Pascal的指针    大家都认为,C语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上.因此,说指针是C语言的灵魂,一点都不为过.同时,这种说法也让很多人 产生误解, ...

  7. Delphi指针样式控件代码

    郑重感谢:本文代码来自Delphi盒子用户janker (janker),谢谢janker (janker)对Delphi社区做出的奉献!欢迎加入Delphi开发局QQ群:32422310  Delp ...

  8. Delphi下使用指针的简单总结

    由于最近公司太忙,好久没有更新我的BLOG了.原来想着写写关于HOOK驱动的文章,可是最后想想好久已经没有做驱动的东西了,怕写出来有错误,于是作罢.开发游戏也有一段时间了,发现使用DELPHI来开发网 ...

  9. Delphi笔记整理(二)

    ◇[DELPHI]字符的加密与解密 function cryptstr(const s:string; stype: dword):string; var i: integer; fkey: inte ...

最新文章

  1. javascript语法糖_语法糖和JavaScript糖尿病
  2. centos 6.4 postfix mysql_postfix+dovecot+mysql+extmail安装笔记(基于CentOS 6)
  3. 安卓项目打开有时候manifests不见了_【必看】暴力0鲁项目详细操作及玩法如何跳过广告,不分享群等...
  4. 这21 个刁钻的HashMap 面试题,我把阿里面试官吊打了!
  5. getaddrinfo函数
  6. 在驱动和应用程序间共享内存
  7. CSDN博客投票活动开始了
  8. 高通发布一系列新型WiFi芯片:兼容WiFi 6技术
  9. RFC 8998: ShangMi (SM) Cipher Suites for TLS 1.3
  10. 虚拟运营商人工服务器,四大必想之事:倒闭、价格、网络
  11. 第十篇:扩展SOUI的控件及绘图对象(ISkinObj)
  12. SimpleXMLRPC_SimpleXMLRPCServer (Internet) – Python 中文开发手册 - Break易站
  13. 360在网站安全防护中的实践
  14. 【题解】Luogu P5405 [CTS2019]氪金手游
  15. astar不能用了_截图快捷键,手把手教你截屏快捷键Ctrl+Alt+A不能用了怎么办
  16. 什么是soft matting方法_NMS、 soft-nms、softer-nms
  17. 前台离岗提示语_前台卫生温馨提示语
  18. HM代码-码控(1)-乱七八糟的初始化
  19. 【论文分享】用于中文零代词解析的带有配对损失的分层注意力网络
  20. 手撕RPC系列(2)—客户端基于stub动态代理的RPC

热门文章

  1. python tkinter库四则运算_python tkinter 编写心理学试验程序干扰任务之四则运算 psychopy...
  2. 我的docker随笔5:docker-compose的安装与使用
  3. sscanf一小用法
  4. Linux下弹出CDROM的程序(参考网上程序)
  5. 深度学习入门笔记:Day-10
  6. java构造方法 隐含三步_Java入门总结--------类和对象关系以及构造方法
  7. python主进程退出时子进程也退出_主进程被杀死时,如何保证子进程同时退出,而不变为孤儿进程(三)...
  8. vue如何取消下拉框按回车自动下拉_如何用大白菜重装系统|大白菜怎么重装系统教程详解...
  9. 【Flink】FLink PipelineExecutorFactory 基于工厂模式的任务提交与SPI机制
  10. 【kafka】支持超高并发的kafka网络设计