2008/02/14 | 好长时间没有使用delphi了,要复习一下关于数组
类别(语言类学习笔记) | 评论(0) | 阅读(520) | 发表于 20:34

Delphi之数组
     Object Pascal中可以建立丰富的数据类型。数组毫无疑问也是众多自定义数据类型中的一种。
     Type
       TA = array[0..9] of Integer;
     ...
     var
       A : TA;
     和下面这段代码通常效果是相同的(不同的地方在类型篇再说)
     var
       A : Array [0..9] of Integer;
     这相当于C中的
     int A[10];
     或Basic中的
     Dim A(9) as Long或 Dim A(0 to 9) as Long
下面将分几个方面讲OP的数组:
多维数组:
     多维数组的本质其实就是数组之数组。   
     type
      TA = array[0..9] of array [0..9] of Integer;
      TB = array[0..9, 0..9] of Integer;
      TCA = array[0..9] of Integer;
      TC = array[0..9] of TCA;
     在这里TA,TB,TC是等价的。在使用时是没有分别的。例如X[0][0]:=1;Tx[0,0]:=1;(X是TA、TB、TC类型)
     都是合法的。因为这几种类型都是在内存中开辟一块100x100xSizeOf(Integer)的区域,你根本无法区
     分他是如何申请的,也没有必要去区分。
     多维数组如何取得维数呢?
     前面已经说过多维数组就是数组之数组,所以可以利用下面的方法来取得多维数组的维数:
      var
        k:array[2..10,3..20]of integer;
      begin
       showmessage(Inttostr(Low(k))+'   '+Inttostr(High(k)));
       showmessage(Inttostr(Low(k[Low(K)]))+'   '+Inttostr(High(k[Low(K)])));//k[n]是array[3..20] of integer;的数组

      end;
动态数组:
     OP中动态数组的声明是
       type
         TA=Array of Integer;
     动态数组应用中十分广泛。现在有一种趋势就是在数据结构中用动态数组代替链表
     (到底哪个好哪个坏自有评价我们在这里不予以讨论)。
     可能你会说动态数组根本不必要使用,我就从来没有用过。 我不信你没有用过动态数组!
     String类型你用过吧,它近似可以说是动态数组的一种。
     动态数组的内存是在使用分配长度时才予以分配的,他的下界只能0(AnsiString字符串例外,下界是1,原因后面再说),
     动态数组的生存期是自管理的使用后一般不用释放,如果要强行释放就用把Nil附给他。
     使用动态数组往往爱犯的错误:
     1)和静态数组概念混淆:
     动态数组的地址并不是他第一个元素的地址,而在静态数组中我们大家常常使用这样的语句:
     var
       A, B : array[0..19] of Integer;
      CopyMemory(@A, @B, 20*SizeOf(Integer));
   或者CopyMemory(@A[0], @B[0], 20*SizeOf(Integer));
   都是正确的,因为静态数组中数组的首地址就是他第一个元素的地址。

     但是在动态数组中只有第二种写法会得到正确结果。即只能写成:
     var
       A, B : array of Integer;
     ... 
     SetLength(A, 20);
     SetLength(B, 20);
     CopyMemory(@A[0], @B[0], 20*SizeOf(Integer));    
     2)数组的附值:
     静态数组中的附值很简单
     var
       A, B : array[0..19] of Integer;
     ...
     A := B;
     即可对数组进行赋值;在动态数组中就要倍加小心,请看
     var
       A, B: array of Integer;
     begin
       Setlength(A, 10);
       SetLength(B, 10);
       B[1] := 50;
       A := B;
       B[1] := 100;        
     end; 
     a[1]是多少呢?按照常理A[1]的值应该是附值前的50,但恰恰相反A[1]的值是附值后B[1]再次被赋的值,
     100动态数组中A := B;仅仅是将动态数组A 的指针指向动态数组B,而并不是像我们希望的那样为A开辟一
     块空间。如果非要为A开辟一块空间就要用Copy来复制B的数据。
     AnsiString也是动态数组,所以同样的情况也存在于String类型。
特殊的数组:
     本来字符串想单独说一说,但是却因为它具有太多的动态数组特性所以不单独说了。
     再一个,这里的代码和说的以后你很可能是用不到的,但是能加深你对ObjectPascal的理解。
     在设计时你会知道它是如何工作的,通过这些你会更有效率的使用它。
     大多数字符串与其说是数组倒不如说他是个结构。但是我们用他的数组特性更多一些。
     1.ShorString:是为了与老的Pascal兼容而保留的类型。最大255个字符。
       Type
         TShortString
           Length:Byte;
           Data:array[1..Length] of Char;
         end;
     从结构上可以看出ShortString最大只能保存255个字符。我们可以做个实验
       var
         k:ShortString;
       begin
         k:='I am a Delphi fan!';
         k[0]:=Char(13);//k.Length被置为13
         showmessage(k);

动态数组

动态数组介绍----Delphi

 

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

    从 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 的简易版本。不管怎么说,了解一些内存的分配对我们这些开发人员来说还是有一些好处的。

0

评论Comments