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

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

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

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

 type
PMyData = ^TMyData;
TMyData = record
v1: Integer;
v2: string;
end; // 对扩展属性设置
var
entry: PMyData;
begin
new(entry);
entry.v1 := xx;
entry.v2 := yy;
Combobox1.Lines.AddObject('test', Pointer(entry));
Listbox1.Items.AddObject('test', Pointer(entry));
with Listview1.Items.Add do
begin
caption := 'test';
subItems.Add('sub-item');
data := entry;
end;
end; // 从扩展属性中读取
var
index: Integer;
entry: PMyData;
begin
index := Combobox1.ItemIndex;
if index <> - then
begin
entry := 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是相当方便,不过对于里面的代码,只能表示呵呵。

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

 // 首先, 先定义好数据格式
const
CMD_ = $;
CMD_ = $; type
PProtocolData = ^TProtocolData;
TProtocolData = packed record
len: int32;
cmd: int32;
case Integer of
CMD_: ( cmd_: TProtocolCmd01; );
CMD_: ( cmd_: TProtocolCmd02; );
end; // 打包/封包,直接利用格式,进行转换+写入
var
data: PProtocolData;
buffer: array [..MAX_SIZE - ] of Byte;
begin
data := @buffer[];
data.len := xx;
data.cmd := CMD_;
data.cmd_ := cmd__data;
send(data, data.len);
end; // 解包,直接用数据格式指针转换buffer
procedure do_some(buffer: Pointer; size: int32);
var
data: PProtocolData;
begin
data := buffer;
if size < data.len then
errormsg_and_exit('data packet is invalid.'); case data.cmd of
CMD_: do_cmd_(data.cmd_);
CMD_: do_cmd_(data.cmd_);
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;
type
PMyDateString = ^TMyDateString;
TMyDateString = packed record
YY: array [..] of Char;
S1: Char;
MM: array [..] of Char;
S2: Char;
DD: array [..] of Char;
S3: Char;
HH: array [..] of Char;
S4: Char;
NN: array [..] of Char;
S5: char;
SS: array [..] of Char;
end;
var
p: PMyDateString;
yy, mm, dd, hh, nn, ss: Word;
begin
if Length(s) < sizeof(TMyDateString) then
error_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, );
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中最自由的部分。

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

 type
TMyEvent = procedure(AParam: Integer; Custom: Pointer); procedure DoJob(event: TMyEvent; custom: Pointer);
begin
if assigned(@event) then
event(, custom);
end; // 上面是最简单的,也是经典回调或事件写法吧,经常写事件或接口API,都明白怎么回事。 // 然后调用,对于custom: Pointer就自由了,请看:
type
PMyData = ^TMyData;
TMyData = record
x, y: Integer;
end; procedure OnEvent1(param: Integer; var data: TMyData);
begin
data.x := param * ;
data.y := param * ;
end; procedure TForm1.Button1Click(Sender: TObject);
var
data: TMyData;
begin
data.x := ;
data.y := ;
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

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

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

 type
PStrRec = ^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 [..] of Char;
end; procedure TForm1.FormCreate(Sender: TObject);
begin
ReportMemoryLeaksOnShutdown := True;
end; procedure TForm1.Button1Click(Sender: TObject);
var
p: PStrRec;
s: string;
begin
p := AllocMem(sizeof(TStrRec) + sizeof(Char) * );
p.ref := ;
p.len := ;
StrPLcopy(p.data, 'abc', ); Pointer(s) := @p.data[];
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处理所需

 type
PMyData = ^TMyData;
TMyData = record
v1, v2: Integer;
end; PMyDataEx = ^TMyDataEx;
TMyDataEx = record
data: TMyData;
dataEx1: Integer;
dataEx2: Integer;
end; procedure DoData(p: PMyData);
begin
p.v1 := ;
p.v2 := ;
end; procedure DoDataEx(p: PMyData);
var
e: PMyDataEx;
begin
e := Pointer(p);
e.dataEx1 := ;
e.dataEx2 := ;
end;

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

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

     使用指针偏移目地:

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

但有几点好处:

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

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

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

坏处也是有的:

a: 增加代码复杂度

b: 出错几率相对大

哈,有好有坏,:)

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

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

2014.10.18 by qsl

delphi.指针.应用的更多相关文章

  1. delphi.指针.PChar

    此文是delphi.指针.应用姊妹篇,想细化一下PChar应用,所以有了此文. 注意: 1:此文讲的是PChar与字符串相关操作,其它方法暂不多讲. 2:由于D分开Ansi/Unicode的两种完全不 ...

  2. Delphi指针的用法

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

  3. Delphi 指针大全(光看不练是学不会的)

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

  4. delphi指针简单入门

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

  5. delphi 指针 认识

    delphi 指针分为类型指针和无类型指针: 类型指针分为PChar.PInteger.PString等. 无类型指针Pointer. PPChar/PP...为指针的指针 @和Addr一样,为获取变 ...

  6. Delphi指针详解

    Delphi指针详解2007-12-04 06:08:57|  分类: DLL学习 阅读91 评论0   字号:大中小 订阅 大家都认为,C语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用 ...

  7. DELPHI指针的使用

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

  8. Delphi指针及其它(转)

    一.指针:指向一个内存地址的变量或参数. 二.定义指针的方式如下: P: Pointer; //定义了可以指向任何类型的指针,Pointer 为无类型指针: Q, R: ^TType; //定义了指向 ...

  9. delphi.指针.应用----应用重要 多看 多练

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

随机推荐

  1. 运用CADisplayLink来开启定时器

    CADisplayLink来开启定时器 CADisplayLink是以屏幕刷新频率将内容绘制到屏幕上的定时器,每秒60Hz.使用的时候,先创建一个CADisplayLink对象,将其添加到一个RunL ...

  2. rigidbody2D.velocity 提示缺少using?用的unity5?

    请用 GetComponent<Rigidbody2D>().velocity

  3. 点击每个li节点,都弹出其文本值及修改

    点击每个li节点,都弹出其文本值 1,获取所有的li节点 var liNodes=document.GetElementsByTagName("li"); 2,使用for循环进行遍 ...

  4. [AHOI 2009] 维护序列(线段树模板题)

    1798: [Ahoi2009]Seq 维护序列seq Time Limit: 30 Sec  Memory Limit: 64 MB Description 老师交给小可可一个维护数列的任务,现在小 ...

  5. 【我是老中医】codeblocks无法编译的问题解决方法

    前几天把codeblocks的文件夹移动到移动硬盘里面,结果发现从此以后代码不能编译了,当时没有注意,就改用vs写代码,发现真的不是很习惯,正好学妹也碰到这种问题问我怎么解决,然后就百度了一下. 我的 ...

  6. Get Jenkins job build queue length

    Jenkins API doesn’t provide the job build queue length. Hence, it seems we have to parse the html to ...

  7. LINUX测试环境部署mysql(三)

    安装配置mysql 1.安装 查看有没有安装过: yum list installed mysql* rpm -qa | grep mysql* 查看有没有安装包: yum list mysql* 安 ...

  8. Http协议:彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法

    转载:http://mp.weixin.qq.com/s/uWPls0qrqJKHkHfNLmaenQ 导语 Http 缓存机制作为 web 性能优化的重要手段,对从事 Web 开发的小伙伴们来说是必 ...

  9. SpringMVC 拦截器不拦截静态资源的三种处理方式

    SpringMVC提供<mvc:resources>来设置静态资源,但是增加该设置如果采用通配符的方式增加拦截器的话仍然会被拦截器拦截,可采用如下方案进行解决: 方案一.拦截器中增加针对静 ...

  10. Android从零开始--安装

    1.下载安装eclipse.adt和Android sdk(以前一直以为Android使用的sdk也是java jdk呢,呵呵) 2.都安装完成后配置eclipse的Android的环境,将Andro ...