Delphi 的类型与指针[1]

先声明: 不要把我说的这些个东西当作教科书, 这都是自己的浅见; 同时希望得到指正.

Delphi 的指针分为 "类型指针" 和 "无类型指针" 两类.
Delphi 中的类型, 常用的也得有几百个, 我们可以给每种类型定义相应的类型指针.
其实 Delphi 已经为很多类型预定义了指针, 譬如数据类型: 
Integer 有对应的 PInteger;
Char 有对应的 PChar;
string 有对应的 PString;
再譬如: 
TPoint 有对应的 PPoint;
TColor 有对应的 PColor 等等.

另外, 指针也可以有指针, 譬如: PChar 是字符指针, PPChar 又是 PChar 的指针(这都是 Delphi 预定义的).

根据上面的例子, 咱们先总结一下类型与指针的命名规则:
类型约定用 T 打头(Delphi 常规的数据类型除外, 譬如: String);
指针约定用 P 打头;
指针的指针约定用 PP 打头.
类型和指针是不可分的两个概念, 指针本身也是一种类型 - "指针类型".

先认识一下指针相关的操作符(@、^、Addr):
@ @变量 获取变量指针
Addr Addr(变量)
^ 指针^ 获取指针指向的实际数据
var Pxxx: ^类型 定义 Pxxx 某种类型的指针的变量 
type Pxxx = ^类型  定义 Pxxx 为某种类型的指针


举例说明:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//Integer 与 PInteger
procedure TForm1.Button1Click(Sender: TObject);
var
  int: Integer;
  pint: PInteger; {定义类型指针, Integer 类型的指针}
begin
  int := 100;
  pint := @int;        {现在 pint 就是 int 的指针}
  pint^ := pint^ + 1{现在 pint^ 和 int 是一回事, 测试一下:}
  ShowMessage(IntToStr(int));   {101}
  ShowMessage(IntToStr(pint^)); {101}
end;

//直接定义类型指针
procedure TForm1.Button2Click(Sender: TObject);
var
  int: Integer;
  PMyInt: ^Integer;
begin
  int := 100;
  PMyInt := Addr(int); {这句和: PMyInt := @int; 相同}
  PMyInt^ := PMyInt^ + 1;
  ShowMessage(IntToStr(int));     {101}
  ShowMessage(IntToStr(PMyInt^)); {101}
end;

//先自定义指针类型
procedure TForm1.Button3Click(Sender: TObject);
type
  PInt = ^Integer;
var
  int: Integer;
  PMyInt: PInt;
begin
  int := 100;
  PMyInt := @int;
  PMyInt^ := PMyInt^ + 1;
  ShowMessage(IntToStr(int));     {101}
  ShowMessage(IntToStr(PMyInt^)); {101}
end;

//指针的指针
procedure TForm1.Button4Click(Sender: TObject);
var
  int: Integer;
  pint: PInteger;
  ppint: ^PInteger;
begin
  int := 100;
  pint := @int;
  ppint := @pint;
  ppint^^ := ppint^^ + 1;
  ShowMessage(IntToStr(int));     {101}
  ShowMessage(IntToStr(pint^));   {101}
  ShowMessage(IntToStr(ppint^^)); {101}
end;

end.

  
  
知道以上这些就可以操作了, 就可以看懂别人的代码了; 不过要想彻底明白指针到底是怎么回事, 需要从内存谈起.

内存中的数据除了 0 便是 1, 你把它当作图片、字符、数字等等, 那是你的事, 内存只认识 0 和 1.

Win32 系统除了使用硬内存以外, 还可以从硬盘上开辟虚拟内存;

因为 Win32 的内存地址范围在 4 个 G 以内(0..2 32 -1), 所以它最多能够给一个应用程序分配 4G 的运行空间; 并且其中的 2G 有系统管理, 实际上程序只有 2G 的自主空间. 还记得有说 String 最大长度是 2G 吗? 就是这个道理.

有 4G 的内存, 就有 4G 个地址, 也就是最多可以有 (1024*1024*1024*4 - 1 = 4294967295) 个内存地址, 这刚好是 Delphi 中 Cardinal 的最大值, 所以 32 位的指针类型追到底都是 Cardinal 类型的一个数字.

一个内存地址是 0..4294967295 之间的一个数字, 你可以通过内存地址读取或写入数据;
一个指针要用来索引或标识内存, 它也是 0..4294967295 之间的一个数字; 它们虽不相同, 但通过指针可以找到实际存储数据的内存地址, 并按指定的类型去读写它.

譬如:
var
  str: string;
  n: Cardinal;
  pstr: PString;
begin
  str := 'ABCDE';
  n := Cardinal(str); {获取内存地址}
  pstr := @str;       {现在 pstr 是 str 的指针}

  {n 与 pstr 的数字结果是(结果是随机的, 知道不一样就行了):}
  ShowMessage(IntToStr(n));              {4571092}
  ShowMessage(IntToStr(Cardinal(pstr))); {1244652}

  {但通过 pstr 可以找到 str}
  ShowMessage(pstr^); {ABCDE}
end;

  
  
程序运行后, 字符串所在的内存基本上是下面这个样子(以字节为单位), 上例中的 n 标识着 ↓ 的位置:
 
A B C D E

换二进制图示一下:
 
00001010 00001011 00001100 00001101 00001110

如果只看二进制, 这个数据到底是什么很难知道; 再说它为什么非得是字符串 "ABCDE" 呢? 这可不一定.

下面的例子中, 我们先是权且把它当作字符串, 但随着指针的移动, 字符串也在变化.

然后, 有分别把它分别用 Byte 指针(PByte) 和 Integer 指针(PInteger) 去读取它, 也会得到相应的值.

完整示例如下:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  str: string;
  ps: PChar;
  n: Cardinal;
begin
  str := 'ABCDE';
  ps := PChar(str);
  n := Cardinal(ps);
  //n := Cardinal(str); {这行可以代替上面两行}
  ShowMessage(IntToStr(n)); {结果是 Windows 随机管理的}

  ShowMessage(PChar(n));   {ABCDE}
  ShowMessage(PChar(n+1)); {BCDE}
  ShowMessage(PChar(n+2)); {CDE}
  ShowMessage(PChar(n+3)); {DE}
  ShowMessage(PChar(n+4)); {E}
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  str: string;
  n: Cardinal;
  pb: PByte;
begin
  str := 'ABCDE';
  n := Cardinal(str);
  ShowMessage(IntToStr(n)); {4571140; 这是我这里的结果, 这是随机的}

  pb := PByte(n);
  ShowMessage(IntToStr(pb^)); {65}
  pb := PByte(n+1);
  ShowMessage(IntToStr(pb^)); {66}
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  str: string;
  n: Cardinal;
  pint: PInteger;
begin
  str := 'ABCDE';
  n := Cardinal(str);
  ShowMessage(IntToStr(n)); {4571140; 这是我这里的结果, 这是随机的}

  pint := PInteger(n);
  ShowMessage(IntToStr(pint^)); {1145258561}
  pint := PInteger(n+1);
  ShowMessage(IntToStr(pint^)); {1162101570}
end;

end.

  
  
上面的第三个程序段的结果或许让你迷惑: 
第一个结果, 应该和 "ABCD" 有点关系才对啊, 怎么是: 1145258561 ?
第二个结果, 应该和 "BCDE" 有点关系才对啊, 怎么是: 1162101570 ?

为什么呢? 这当然没错, 听我解释:
1145258561 转换成十六进制是: 44434241, 写得清楚一点是: $44 $43 $42 $41; 还记得 Intel 等当下流行的 CPU 安排数据是倒着的吗?

自己算算下一个, 用附件中的计算器即可.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值