指针 : 指针是一个特殊的变量, 它里面存储的数值被解释成为内存里的一个地址.
(1) 指针对应着一个数据在内存中的地址, 得到了指针就可以自由地修改该数据. (2) 一个指针变量仅仅是存储一个内存的地址, 为指针所指向的内容分配空间是程序员要干的工作. (3) 如果一个指针没有指向任何数据, 它的值是nil, 它就被称为是零( nil )指针或空(null) 指针. (4) 要访问一个指针所指向的内容, 在指针变量名字的后面跟上^运算符. 这种方法称为对指针取内容. (5) 指针的指针就是用来存放指针所在的内存地址的. 要搞清一个指针, 需要了解以下内容:(1) 指针的类型. (2) 指针所指向的类型. (3) 指针的值(即指针所指向的内存区). (4) 指针本身所占据的内存区. 指针大小指针是一个无符号整数(unsigned int), 它是一个以当前系统寻址范围为取值范围的整数. 指针类型变量本身要占内存, 占用内存的大小与机器硬件 操作系统以及编译器都有关系, 最直接的关系就是编译器, 现在的编译器大都是32位(4B)的, 即使你的机器和操作系统都是是64位的, 所以指针类型变量一般占用4B空间(也就是可表示2^32次方的地址空间). 指针类型一个指针变量指示了内存的位置. PASCAL通用指针类型的名称是Pointer, Pointer有时又被称为无类型指针, 因为它只指向内存地址, 但编译器并不管指针所指向的数据, 所以建议你在大部分情况下用有类型的指针. 任何对象 结构 变量什么的, 在内存里面, 实质上就是字节流, 那么很有可能某一个字节数组array of char的内容刚好和某一个对象的字节流内容一样, 如果一个pointer指向的内容为上述字节内容, 你能区分是那个对象还是array of char的字节数组?
Pointer 作为一个无类型指针, 可以指向任何元素. 强制转换时, Delphi 并不知道 Pointer 指向的数据是什么类型. 例如TObject(p) 就是一种强制转换, 用于告诉编译器指针指向的数据是TObject的实例. 也就是说:编译器不能确定类型转换的正确性!你必须自己负责该指针的实际指向!总得说来, 无类型指针的转换是没有安全性的, 你必须明确指针的用途才可以使用. 类型指针在你的应用程序的Type部分用^ (或Pointer)运算符声明. 对于类型指针来说, 编译器能准确地跟踪指针所指内容的数据类型, 这样用指针变量, 编译器就能跟踪正在进行的工作. 对于编译器来说, 指针的类型可以用来标明地址所指向内存区域的大小 所指向的数据类型(整型 对象 方法), 以及进行指针运算时指针偏移的长度. 对于类型指针的操作最终反映到编译器的可执行代码中, 如果不参考可执行代码, 单凭一个内存地址, 我们无法判断地址的实际用途. 数据类型(1) 简单数据类型对于简单数据类型(或叫基础数据类型), 编译器分配内存时直接为其分配存储单元!(2) 复杂数据类型对于复杂数据类型, 编译器分配内存时先在栈中分配一个指针, 该指针占4个存储单元, 然后再在堆中创建对象的实体!并且使栈上的指针关联到堆中对象实体的首址处! 说明:可以使用SizeOf区别简单数据类型与复杂数据类型.
操作符 = <>
P = Q : 相同的地址
P <> Q : 不同的地址
操作符 + - 指针运算 Inc指针位移的字节数是以指针的类型决定的.
- : 计算两个PChar指针的Offset
对于“+/-”加来说,只支持 PChar 和整数相加,也就是说对于“+”和“-”操作来说,步长是一个字节
不过Delphi提供的一个函数Inc能实现除了 PChar 类型指针的自加寻址,这个就是你的指针类型是什么类型,步长便是此指针类型的大小
比如:
var a:array[0..4] of Integer; p1: ^integer; b:array[0..1] of char; p2: ^char; begin p1 := @a[0]; Inc(p1); // 当执行Inc(p1)时, 编译器会产生让p1前进sizeof(integer)步长的汇编代码, 之后p1将指向a[1]. Inc(p1,2); // Inc(p1,2)这句使得p1前进2个sizeof(integer)大小的步长, 之后p1将指向a[3]. p2 := @b[0]; Inc(p2); //--初始指针向后偏移1个字节,指向b[1] Inc(p1,2); //--指针向后偏移2*1个字节,指向b[3] end;操作符@ : 用来返回变量的内存中的存储地址, 或者是返回过程、函数和方法的入口地址。
(1)、对于变量X,@X返回的是X的地址。 如果编译选项{$T-}没有打开,则返回的是一个通用的指针,如果编译选项打开,则返回的是X的类型对应的指针。(2)、对于例程F (过程\函数),@F返回的是F的入口点,@F的类型是一个指针。(3)、当@用在类的方法中时,则方法的名称必须有类名, 例如@TMyclass.Dosomething指针指向TMyclass的dosomething方法。 (4)、对于过程变量P, @P把P转换成一个包含地址的无类型的指针变量(即@P等价于无类型指针P), 此时可以把一个无类型的指针值赋给过程变量P。 获得一个过程变量的内存地址使用@@。例如,@@P返回P的地址。
Addr是个返回值类型为Pointer的函数默认编译条件下的@运算符,在对任何变量、运行期常量、函数取地址后,与 Addr 的结果一样,返回值的类型为Pointer;Pointer类型的变量,可以与任何指针、对象、类类型变量自由转换,并且编译器不会给出任何警告。通过Project Options -> Compiler,勾上“Typed @ operator”,或是通过预编译指令$T+或$TYPEDADDRESS ON,来改变@运算符的行为I : Integer; PC : PChar := @I; // 出现一个编译错误,提示 Char 与 Integer 是不同的类型操作符^(1) 当它出现在类型定义的前面时如 ^typename 表示指向这种类型的指针; (2) 当它出现在指针变量后边时, 如 point^ 返回指针指向的变量的值; 过程\函数\方法指针(1) 当一个过程变量在赋值语句的左边时, 编译器期望一个过程值在赋值语句的右边. 这种赋值使得左边的变量可以指向右边定义的过程或者函数入口点. 换句话说, 可以通过该变量来引用声明的过程或者函数, 可以直接使用参数的引用. (2) 在赋值语句“过程变量 := 过程值”中, 左边变量的类型决定了右边的过程或者方法指针解释. (3) 无论何时一个过程变量(procedural variable)出现在一个表达式中, 它表示调用所指向的函数或者过程. (4) 任何过程变量可以赋成nil, 表示指证什么也不指向. 但是试图调用一个nil值的过程变量导致一个错误, 为了测试一个过程变量是否可以赋值, 用标准的赋值函数Assigned. 如下所示:
if Assigned(OnClick) then OnClick(X);结构:
(1) 过程\函数指针是一个32位的指针. (2) 方法指针是指向对象方法的指针, 实现上是一个对象指针加上一个过程\函数指针组成. 方法指针的结构如下:
TMethod = record Code: Pointer; //指向过程\函数的指针 Data: Pointer; //指向对象的指针 end;
类指针DELPHI中的类是一个指针, 这个指针指向类在内存中所占据的一块空间. 类指针与VMT指针地址相同. 类的类型在DELPHI中我们用TObject TComponent等等标识符表示类, 它们在DELPHI的内部实现为各自的VMT数据. 而用class of保留字定义的类的类型, 实际就是指向相关类的指针. 例如“ TClass = class of TObject”, 从概念上说, TClass是TObject类的类型, 实际上TClass就是指向TObject的指针类型!有了类的类型, 我们就可以将类赋值给使用“类的类型”声明的变量(即类变量), 从而将类作为变量来使用. 对象指针DELPHI中的对象是一个指针, 这个指针指向该对象在内存中所占据的一块空间. 我们可以试着用sizeof函数获取对象的大小, 结果是 4 字节, 这正是一个32位指针的大小. 而对象的真正大小应该用MyObject.InstanceSize获得.
注意:对象虽然是用指针实现, 具有指针所有特性, 但毕竟定义为Class, 所以只能用“对象.成员 对象.方法”的方式来调用, 而不能用“对象^.成员 对象^.方法”的方式来调用.
例如我们最熟悉的Form, 当我们调用Form1.Edit1.Text时, 其实Form1 Edit1都是指针, 但是只能用Form1.Edit1.Text来调用, 而不能用Form1^.Edit^.Text.
动态内存分配动态分配内存的函数是GetMem(),与之对应的释放函数为FreeMem()(传统 Pascal中获取内存的函数是New()和 Dispose(),但New()只能获得对象的单个实体的内存大小,无法取得连续的存放多个对象的内存块)。
var ptr, ptr2 : ^integer; i : integer; begin GetMem(ptr, sizeof(integer) * 20); //这句等价于C的 ptr = (int*) malloc(sizeof(int) * 20); ptr2 := ptr; //保留原始指针位置 for i := 0 to 19 do begin ptr^ := i; Inc(ptr); end; FreeMem(ptr2); end;数组指针 : Delphi 中可以定义静态数组指针,然后指向你要操作的内存区块首地址
取数组在内存中的首地址,应该使用 @MyArr[0] 这种形式,无论它是在堆中还是在栈中,
这样的写法能保证不会错,而且简单明了,因为它直接给出了内存地址值。
静态数组的数组名等同于取第一个元素
type TPMyArr = ^TMyArr; TMyArr = array [ 0 .. 100 ] of Integer; var I : Integer; PMyArr : TPMyArr; p : Pointer; begin p := @I; PMyArr := @I; PMyArr[ 0 ] := 100; TPMyArr( p )[ 0 ] := 200; end;动态数组的数组名是指向该数组的指针
var MyArray : array [ 1 .. 100 ] of Char; MyDynamicArray : array of Byte; begin FillChar( MyArray, sizeof( MyArray ), 0 ); SetLength( MyDynamicArray, 100 ); FillChar( Pointer( MyDynamicArray )^, length( MyDynamicArray ), 0 ); end;静态数组 static_array:array[0...99] of char;
@static_array 和 @static_array[0]等价, 都是静态数组中第一个元素的指针. 静态数组名 static_array 是一个变量, 等价于静态数组中第一个元素 static_array = static_array[0]@static_array = @static_array[0]
// procedure FillChar(var X; Count: Integer; Value: Ordinal);// This function does not perform any range checking.// This method has an untyped parameter, which can lead to memory corruption. // To avoid this problem, use SizeOf to find the number of bytes appropriate // to fill for the data type of the X parameter.// 无类型参数需要传递一个变量, 而不是变量的地址//// function SizeOf(var X): Integer;// Returns the number of bytes occupied by a variable or type.// SizeOf returns 0 when the argument is an untyped variable.//
FillChar( static_array, sizeof( static_array ), 0 );FillChar( static_array[0], sizeof( static_array ), 0 );
动态数组 dynamic_array:array of char; dynamic_array 和 @dynamic_array[0]等价, 都是静态数组中第一个元素的指针. 动态数组名dynamic_array是一个指针, @dynamic_array 动态数组指针的指针
dynamic_array = @dynamic_array[0]dynamic_array^ = dynamic_array[0]
// procedure FillChar(var X; Count: Integer; Value: Ordinal);FillChar( dynamic_array[0], length( dynamic_array ), 0 );FillChar( dynamic_array^, length( dynamic_array ), 0 );FillChar( Pointer(dynamic_array)^, length( dynamic_array ), 0 );
动态数组第一个元素前面的2个Integer分别是引用计数和数组当前长度。但是只有动态数组长度不为0时,这8个字节才可以访问,否则Access Violation。动态数组的索引都是从0开始,所以请注意避免索引越界错误。当动态数组长度为0时,虽然不可以读取或设置其第一个元素的值,但是却可以获取该元素地址。比如@DynArr[0]不会产生运行时错误,它返回nil可以用SetLength改变动态数组的长度, 释放动态数组的内存可以用SetLength将其长度设置为0,或者直接赋nil值。当然,如果你不手动释放,编译器会为你处理的。但是如果你存储的是对象或缓冲区的指针,你必须负责释放他们。
转载于:https://www.cnblogs.com/shangdawei/archive/2013/04/30/3051656.html
相关资源:数据结构—成绩单生成器