传统的Pascal 语言其数组大小是预先确定的,当你用数组结构声明数据类型时,你必须指定数组元素的个数。专业程序员也许知道些许动态数组的实现技术,一般是采用指针,用手工分配并释放所需的内存。

Delphi 4中增加了非常简单的动态数组实现方法,实现过程效仿我前面讲过的动态长字符串。与长字符串一样,动态数组的内存动态分配并且引用记数,不过动态数组不支持 copy-on-write 技术。这不是个大问题,因为你可以把变量值设置为nil释放数组内存。

这样你就可以声明一个不指定元素个数的数组,并用SetLength 过程给数组分配一个特定大小的内存,SetLength 过程还可以改变数组大小而不影响其内容,除此外还有一些字符串过程也可用于数组,如Copy 函数。

以下摘录的代码突出了一点,这就是:定义数组后必须先为它分配内存,然后才能开始使用:

procedure TForm1.Button1Click(Sender: TObject);
var
Array1: array of Integer;
begin
Array1 [1] := 100; // error
SetLength (Array1, 100);
Array1 [99] := 100; // OK
...
end;
如果你只定义一个数组元素个数,那么索引总是从0开始。Pascal 中的普通数组既能用不为零的下标,也能用非整数的下标,但动态数组均不支持这两种下标。象普通数组一样,你可以通过Length、High和Low 函数了解到动态数组的状况,不过对于动态数组,Low 函数返回值总是0,High函数返回数组大小减1,这意味着空的动态数组其函数High返回值是-1,这是一个很怪的值,因为它比Low的返回值还小。

图 8.1: 例 DynArr 窗体

以上作了简短的介绍,现在举个简例,例名DynArr ,见图8.1。例子实在是很简单,其实动态数组没有什么特别复杂地方。我想通过该例说明几个程序员可能犯的错误。程序中声明了两个全程数组并在OnCreate 事件中初始化了第一个数组:

var
Array1, Array2: array of Integer;

procedure TForm1.FormCreate(Sender: TObject);
begin
// allocate
SetLength (Array1, 100);
end;
这样就把数组所有值设置为0。完成这段代码你马上就能读写数组元素的值,而不用害怕内存出错,当然条件是你没有试图访问超过数组上界的元素。为了更好地初始化,程序中添加了一个按钮,执行数组元素赋值操作:

procedure TForm1.btnFillClick(Sender: TObject);
var
I: Integer;
begin
for I := Low (Array1) to High (Array1) do
Array1 [I] := I;
end;
Grow 按钮用于修改数组大小,但并不影响数组内容。单击Grow 按钮后,你可以用Get value按钮进行检验:

procedure TForm1.btnGrowClick(Sender: TObject);
begin
// grow keeping existing values
SetLength (Array1, 200);
end;

procedure TForm1.btnGetClick(Sender: TObject);
begin
// extract
Caption := IntToStr (Array1 [99]);
end;
Alias 按钮的OnClick 事件代码稍复杂些,程序通过 := 算子把一个数组拷贝给另一个数组,从而有效地创建了一个别名(一个新变量,但引用内存中同一数组)。从中可见,如果你改变了其中一个数组,那么另一个同样也会改变,因为它们指向同一个内存区:

procedure TForm1.btnAliasClick(Sender: TObject);
begin
// alias
Array2 := Array1;
// change one (both change)
Array2 [99] := 1000;
// show the other
Caption := IntToStr (Array1 [99]);
在btnAliasClick 事件中增加了两部分操作内容。第一部分是数组等同测试,不过并不是测试实际的数组元素,而是测试数组所引用的内存区,检测变量是不是内存中同一数组的两个别名:

procedure TForm1.btnAliasClick(Sender: TObject);
begin
...
if Array1 = Array2 then
Beep;
// truncate first array
Array1 := Copy (Array2, 0, 10);
end;
btnAliasClick 事件的第二部分内容是调用Copy 函数。该函数不仅把数据从一个数组移到另一个数组,而且用函数创建的新数组取代第一个数组,结果变量Array1 所引用的是11个元素的数组,因此,按Get value 和Set value 按钮将产生一个内存错误,并且触发一个异常(除非你把范围检查range-checking 选项关掉,这种情况下,错误仍在但屏幕上 不会显示异常)。虽然如此,Fill 按钮仍能正常工作,因为需要修改的数组元素由数组当前的下标范围确定。

自从有了动态数组,链表除了在教科书里出现外,已经很少在实际编程中被使用了,事实也是如此,数组的确比传统链表快得多,而且也方便的多。

从 Delphi4起,开始了内建各种类型的动态数组支持。但是,对我们来说动态数组支持似乎做的不够彻底,因为Delphi竟然连删除、插入、移动连续元素的函数都没有提供,让人使用起来总觉得不够爽!!! J 。作为一名程序员,我们当然要有自己解决问题的能力,下面就让我们简单介绍一下Delphi 下的动态数组。

在Delphi中,数组类型有静态数组(a : array[0..1024] of integer)、动态数组(var a : array of integer)、指针数组(即指向静态数组的指针)和开放数组(仅用于参数传递)。静态数组、指针数组有速度快的好处,动态数组有大小可变的优势,权衡之下就有了折衷的办法,那就是定义的动态数组在必要时转换为指针。

动态数组声明之后,只有下面几个函数可供操作:

1. 设置数组大小,可以任意缩减或增加数组大小

Procedure SetLength(var S ; NewLength : integer);

2. 取出连续元素,复制给另一个数组变量

Function Copy(s;Index,Count : integer) : array ;

3. 取得数组大小及上下限

Function Length(s):integer;

Function High(x):integer;

Function Low(x):integer;

值得注意的是,不加const或var修饰的动态数组会被作为形参传递,而动态数组用const修饰并不意味着你不能修改数组里的元素(不信你可以字自己在程序中试试。还有一点是High函数调用了Length 函数,所以我们在获取数组上限时最好直接用 Length(s) 函数。

动态数组在内存空间中占用4个字节. 动态数组在内存中的分配表如下:

偏移量 内容

-8 32-bit 引用计数

-4 32-bit 数组长度

0..数组长度 * (元素尺寸) - 1 数组元素 元素尺寸=Sizeof(元素类型)

根据上面的分配情况,可以得到如下结果:

如果我们想要清空一个动态数组只需要把“数组长度”和“引用计数”清空即可。”引用上面的一句话就是:“权衡之下就有了折衷的办法,那就是定义的动态数组在必要时转换为指针。”下面是清空动态数组的函数:

procedure DynArraySetZero(var A);

var

P: PLongint; //占用4个字节,正好符合 32 位内存排列

begin

P := PLongint(A); // 指向 A 的地址

Dec(P); //P 地址偏移量是 sizeof(A),指向了数组长度

P^ := 0; // 长度清空

Dec(P); // 指向引用计数

P^ := 0; //计数清空。

end;

上面的函数就这么简单,而且效率也非常高。

下面让我们再来看看怎样删除动态数组中的元素,函数体如下:

{************************************

A 变量类型 , elSize = SizeOf(A)

index 开始删除的位置索引 ,Count 删除的数量

****************************************}

procedure DynArrayDelete(var A; elSize: Longint; index, Count: Integer);

var

len, MaxDelete: Integer;

P : PLongint; //4 个字节的长整形指针

begin

P := PLongint(A);// 取的 A 的地址

if P = nil then

Exit;

{

下面这句完全等同于 Dec(P) ; len := P^ 因为 Dec(P) = Pchar(P) – 4 同样是移动4 字节的偏移量,只不过后者按字节来移动 }

len := PLongint(PChar(P) - 4)^; // 变量的长度 ,偏移量 -4

if index >= len then //要删除的位置超出范围,退出

Exit;

MaxDelete := len - index; // 最多删除的数量

Count := Min(Count, MaxDelete); // 取得一个较小值

if Count = 0 then // 不要求删除

Exit;

Dec(len, Count);// 移动到要删除的位置

MoveMemory(PChar(P)+index*elSize , PChar(P)+(index + Count)*elSize , (len-index)*elSize); //移动内存

Dec(P); //移出 “数组长度”位置

Dec(P); //移出“引用计数” 位置

//重新再分配调整内存,len 新的长度. Sizeof(Longint) * 2 = 2*Dec(P)

ReallocMem(P, len * elSize + Sizeof(Longint) * 2);

Inc(P); // 指向数组长度

P^ := len; // new length

Inc(P); // 指向数组元素,开始的位置

PLongint(A) := P;

end;

对上面的例子,我们需要注意的是 elSize 参数 ,它必须是 SizeOf(DyArray_Name),表示元素所占用的字节数。

相信看了上面的例子后,对于动态数组的拷贝,移动想必也可以自己实现了吧 J

后续:

其实,Delphi 对许多类型的内存分配都很相似,比如 string 类型,其实它和动态数组是很相似的,我们完全可以把它拿来当成动态数组。实质上 string 是 Pchar 的简易版本。不管怎么说,了解一些内存的分配对我们这些开发人员来说还是有一些好处的。

http://blog.csdn.net/procedure1984/article/details/3297092

Delphi 的动态数组的更多相关文章

  1. Delphi泛型动态数组的扩展--转贴

    此文章转载于http://www.raysoftware.cn/?p=278&tdsourcetag=s_pcqq_aiomsg的博客 从Delphi支持泛型的第一天起就有了一种新的动态数组类 ...

  2. [转]delphi 删除动态数组的指定元素

    type TArr = array of TPoint; {把数组先定义成一个类型会方便许多, 这里仅用 TPoint 测试} {删除动态数组指定元素的过程: 参数 arr 是数组名, 参数 Inde ...

  3. delphi动态数组指针问题

    就一个button事件 procedure TForm1.btn7Click(Sender: TObject); Type TMyArr = array of array of array of In ...

  4. Delphi内存管理(Integer、Boolean、Record、枚举等都是在作用域内编译器自动申请内存,出了作用域自动释放;另外,字符串、Variant、动态数组、接口也是由Delphi自动管理)

    一.什么是堆.栈? 程序需要的内存空间分为 heap(堆) 和 stack(栈),heap 是自由存储区, stack 是自动存储区,使用 heap 需要手动申请.手动释放, stack 是自动申请. ...

  5. delphi 静态3维数组。 严重占用堆栈 切记。 应该用动态数组, 非要用静态数组的话, 要在编译器里 把 堆栈 调大

    delphi 代码正确, 但是运行就崩溃. 原因为 定义了  一些   静态3维数组. 应该扩大 软件的 堆栈 设置.    然后正常解决问题 静态3维数组.   严重占用堆栈   切记. 应该用动态 ...

  6. 关于Delphi中二维数组的声明和大小调整(对非基本类型数据,小心内存泄漏)

    这是一个实例: procedure TMainForm.Button1Click(Sender: TObject);var  arr:array of array of string;begin  s ...

  7. C++Builder 中动态数组的使用(转)

    源:http://i.cnblogs.com/EditPosts.aspx?opt=1 和AnsiString类型一样,动态数组是为了和DELPHI中的动态数组相兼容而定义,在BCB中,动态数组是用模 ...

  8. 【delphi】Byte数组与String类型的转换

    string string = AnsiString = 长字符串,理论上长度不受限制,但其实受限于最大寻址范围2的32次方=4G字节: 变量Str名字是一个指针,指向位于堆内存的字符序列,字符序列起 ...

  9. Delphi 变体数组 Dataset Locate 查找定位

    Format 函数 Delphi 支持“开参数”和动态数组,变体数组,使用时的语法类似 Delphi 中的集合:采用两个方括号把不同类型的变量括起来(这太方便了啊),也可以采用声明一个 TVarRec ...

随机推荐

  1. windows平台下杀死指定端口的进程(转载)

    在windows命令行窗口下执行: 1.查看所有的端口占用情况 C:\>netstat -ano 协议    本地地址                     外部地址              ...

  2. 并发编程:c++11 多线程中随机数重复问题

    srand(time(NULL)); 是我们熟悉的c++随机函数,用时间做种子.但由于在多线程环境下若想在子线程中随机出不同的随机数则需随机种子的不同.但time以秒计算,略显不足,故参考这篇文章解决 ...

  3. Linux学习之Makefile文件的编写

    转自:http://goodcandle.cnblogs.com/archive/2006/03/30/278702.html 目的:       基本掌握了 make 的用法,能在Linux系统上编 ...

  4. poj 2155 matrix 二维线段树

    题目链接 区间翻转, 单点查询, 查询操作我真是不太明白...... #include <iostream> #include <vector> #include <cs ...

  5. Decorators and Wrappers in Python

    python代码一贯以优雅,简洁著称,而有时侯反而会让人难以理解,比如说wrapper(或者说decorator),这种方式提高了代码的可重用性,使用起来更简洁方便. 举个例子,比如WebApp常用的 ...

  6. JS笔记 入门第三

    认识DOM 文档对象模型DOM(Document Object Model)定义访问和处理HTML文档的标准方法.DOM 将HTML文档呈现为带有元素.属性和文本的树结构(节点树) 把上面的代码进行分 ...

  7. [C++]让CPU使用率曲线呈现为正弦曲线(一)

    看<编程之美>的第一题就很有意思: 写一个程序,让用户来决定Windows任务管理器的CPU占用率.可以实现下面三种情况: 1. CPU的占用率固定在50%,为一条直线: 2. CPU的占 ...

  8. delphi datasnap 心跳包

    为了能让我们的服务程序更加稳定,有些细节问题必须解决.就如上一讲中提到的客户端拔掉网线,造成服务器上TCP变成死连接,如果死连接数量过多,对服务器能长期稳定运行是一个巨大的威胁.另外,经过测试,如果服 ...

  9. BZOJ 1177 [Apio2009]Oil(递推)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1177 [题目大意] 给出一个矩阵,从中选出3个k*k且不相交的矩阵,使得其总和最大 [ ...

  10. CSS3学习----选择器、字体

     属性选择器: [att*=val]{}若att元素属性值包括val指定字符,则使用该样式 [att^=val]{}若att元素属性值开头字符为val,则使用该样式 [att&=val]{ ...