delphi.指针.应用----应用重要 多看 多练
来自: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,如果自己需要与之上下文相关的扩展数据,最方便使用了。
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.指针.应用----应用重要 多看 多练的更多相关文章
- delphi.指针.PChar
此文是delphi.指针.应用姊妹篇,想细化一下PChar应用,所以有了此文. 注意: 1:此文讲的是PChar与字符串相关操作,其它方法暂不多讲. 2:由于D分开Ansi/Unicode的两种完全不 ...
- Delphi指针的用法
DELPHI指针的使用 大家都认为,C语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上.因此,说指针是C语言的灵魂,一点都不为过.同时,这种说法也让很多人产生误解,似乎只有C语言的指针才 ...
- Delphi 指针大全(光看不练是学不会的)
大家都认为,C语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上.因此,说指针是C语言的灵魂,一点都不为过.同时,这种说法也让很多人产生误解,似乎只有C语言的指针才能算指针.Basic不支 ...
- delphi指针简单入门
delphi指针简单入门: 看一个指针用法的例子: 1 var 2 X, Y: Integer; // ...
- delphi 指针 认识
delphi 指针分为类型指针和无类型指针: 类型指针分为PChar.PInteger.PString等. 无类型指针Pointer. PPChar/PP...为指针的指针 @和Addr一样,为获取变 ...
- Delphi指针详解
Delphi指针详解2007-12-04 06:08:57| 分类: DLL学习 阅读91 评论0 字号:大中小 订阅 大家都认为,C语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用 ...
- DELPHI指针的使用
DELPHI指针的使用 大家都认为,C语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上.因此,说指针是C语言的灵魂,一点都不为过.同时,这种说法也让很多人产生误解,似乎只有C语言的指针才 ...
- delphi.指针.应用
注:初稿...有点乱,可能增删改... 因为指针应用,感觉不好写,请大家指出错误,谢谢. 注意: 本文着重点讲的是指针的各类型的应用或使用,而不是说这种方法不应该+不安全+危险+不提倡使用. 其它:本 ...
- Delphi指针及其它(转)
一.指针:指向一个内存地址的变量或参数. 二.定义指针的方式如下: P: Pointer; //定义了可以指向任何类型的指针,Pointer 为无类型指针: Q, R: ^TType; //定义了指向 ...
随机推荐
- python之time和os模块
1.time.time()获得的是一个时间戳,距离1970年以来多少秒 2.time.strftime(),按固定格式设置时间 import time print(time.localtime())# ...
- @section script{}的使用
1,MVC视图中,JS代码被放在下面的Razor代码中(@section script{}) 2,这样做的好处是:在视图进行JS编码时是一个很好 的实践,在共享视图(_layout.cshtml),存 ...
- DPDK vhost库
原创翻译,转载请注明出处. vhost库实现了一个用户空间的virtio net server,允许用户直接处理virtio ring队列.换句话说,它让用户可以从VM virtio网络设备读取或写入 ...
- mysqli DB封装
<?php class DB { //私有的属性 private static $dbcon = false; private $host; private $port; private $us ...
- capacilitys 持续集成
目前看好像是说以docker为例来看看这个权限到底是怎么来的? 可以通过在二进制上setcap得到,也可以通过函数自己用setcap得到,两种方法,docker肯定是第二种方法啊,docker中间肯定 ...
- windows下Memcached 架设及java应用(转)
1 Memcache是什么 Memcache是danga.com的一个项目,最早是为 LiveJournal 服务的,目前全世界不少人使用这个缓存项目来构建自己大负载的网站,来分担数据库的压力. 它可 ...
- 【Python】Linux crontab定时任务配置方法(详解)
CRONTAB概念/介绍 crontab命令用于设置周期性被执行的指令.该命令从标准输入设备读取指令,并将其存放于“crontab”文件中,以供之后读取和执行. cron 系统调度进程. 可以使用它在 ...
- python 的sets list dictionary
http://blog.csdn.net/joseph_happy/article/details/6717412 http://blog.csdn.net/joseph_happy/article/ ...
- 在.cs代码文件中无法识别控件
原因:由于直接复制别人的网页文件到项目. 解决方案,自己右键,新建网页,再把控件代码复制到 aspx和 cs
- [洛谷P2044][NOI2012]随机数生成器
题目大意:给你$m,a,c,X_0,n,g$,求$X_{n+1}=(a\cdot X_n+c) \bmod{m}$,最后输出对$g$取模 题解:矩阵快速幂+龟速乘,这里用了$long\;double$ ...