53魔力发布网,提供最好最新的开机信息

查看完整版本: 【转载】动态数组解释

妖哥 2008-5-26 14:28

【转载】动态数组解释

目录
===============================================================================
⊙ 动态数组类型
⊙ 生存期自管理的数据类型
⊙ 一维动态数组
⊙ 一维动态数组存储与访问
===============================================================================

本文排版格式为:
    正文由窗口自动换行;所有代码以 80 字符为边界;中英文字符以空格符分隔。

(作者保留对本文的所有权利,未经作者同意请勿在在任何公共媒体转载。)

(作者:汇编 QQ 241897582。)

正文
===============================================================================
⊙ 生存期自管理的数据类型
===============================================================================
1、D提供了多种生存期自管理(life-circle self-management)类型的数据类型,如:
2、AnsiString、WideString、动态数组、Variant、OleVariant、interface 与 dispinterface;
3、生存期自管理的基本意义
   1、D允许声明两种类型的变量:
     1、静态变量(static  variable)
       1、静态变量必须在程序段的说明部分给出它们的类型定义和变量说明,编译器将根据所
          作的说明为它们在内存中分配固定的存储位置和固定大小的存储区;
       2、只有当静态变量的生存期(life-circle)结束时(程序退出它所在的程序段)
          它所占用的内存才被释放;
       3、在程序执行过程中,静态变量的存储区不能改变,不能在程序执行过程中为其
          随时分配内存单元(动态分配),也不允许在作用域内不再使用它时释放其占
          用的内存单元。
       4、具有以上特性的数据结构为静态存储结构(static storage structure);
         
     2、动态变量(dynamic variable)
       1、动态存储结构的典型代表为指针(pointer) ;
       2、动态存储结构的最大缺陷在于所申请的内存在使用完毕后经常不会合理释放
          以及容易造成指针访问错误;
   2、静态存储结构不够灵活,普通的动态存储结构又不好控制,因而给出了一种生存存
      期自管理技术,并使用该技术实现了多种数据结构;
4、生存其自管理又称自动垃圾回收(automatic garbage collection)
   1、自动垃圾回收是指在变量离开作用域时自动释放其所占用资源的方法。
   2、其关键技术就是所谓的引用计数(reference count);

===============================================================================
⊙ 动态数组类型
===============================================================================
1、下标类型、维数和基类型确定静态数组的确切大小;
2、动态数组,不需要在声明时指定数组大小;
3、动态数组具有不固定的大小或长度;
4、动态数组在程序中动态地开辟数组存储空间提供了有效的途径;

===============================================================================
⊙ 一维动态数组
===============================================================================
1、一维动态数组声明与一维静态数组声明的主要区别在于:
  1、一维动态数组没有下标类型描述,其元素个数不确定;
  2、当声明静态数组变量时,编译器给数组分配固定大小的内存区域;
  3、动态数组由于没有固定的大小,编译器只给动态数组变量分配一个内存地址;
2、运行时动态数组的大小由程序调用 SetLength 动态确定;
  1、当使用 SetLength 过程指定数组大小时,动态数组变量对应的那个内存区域将保存
     SetLength 过程分配的内存区域的地址;
  2、真正存储数组元素的内存由D统一分配与管理,程序不需要关心。
  3、这意味着动态数组变量相当于指针,它指向 SetLength 过程所分配的内存区域;
  4、动态数组下标的下界总为0,上界则比数组大小值小1;

===============================================================================
⊙ 一维动态数组存储与访问
===============================================================================
1、声明一维数组变量 A、B
   var A,B:array of integer;
  1、程序分别为 A、B 开辟固定的内存空间(32位,存储指针值);图1(a)
  2、A、B的初始化内容均为 Nil;
  3、当使用 SetLength 过程为动态数组分配空间时,系统将创建保存动态数组值的内存空间,
     并将 A 和 B 的值设置为对应数组 A 或 B 的首地址;
  4、图1(b) 中假设数组 A、B的首地址分别为 p、q;此时D自动将动态数组的所有元素初始化为0;

    |--------|                   |--------|            A[0] A[1] A[2] A[3] A[4]
   A|   Nil  | SetLength(A,5)   A|    p   | ------->  |----|----|----|----|----|
    |--------|                   |--------|           |--0-|--0-|--0-|--0-|--0-|
              ---------------->
    |--------|                   |--------|            B[0] B[1] B[2] B[3] B[4]
   B|   Nil  | SetLength(B,5)   B|   q    | ------->  |----|----|----|----|----|
    |--------|                   |--------|           |--0-|--0-|--0-|--0-|--0-|

      图1(a)                        图1(b)


5、设置数组 A 和 B 的值:
    var j:integer;
    begin
      for j:=0 to 4 do
        begin
          A[j] := j + 1;
          if j <> 0 then B[j] := j + 5 else B[j] := 1;
        end;
    end;
   设置数组 A 和 B 的值,设置后的结果如图2

     |--------|            A[0] A[1] A[2] A[3] A[4]
    A|    p   | ------->  |----|----|----|----|----|
     |--------|           |--1-|--2-|--3-|--4-|--5-|
              
     |--------|            B[0] B[1] B[2] B[3] B[4]
    B|   q    | ------->  |----|----|----|----|----|
     |--------|           |--1-|--6-|--7-|--8-|--9-|

         图2     

6、当执行数组赋值语句 B := A 后:
   1、A 和 B 将指向同一个内存地址;
   2、动态数组 B 将指向动态数组 A 的首地址 p;
   3、这一点与静态数组变量的赋值方式有着根本的不同;

     |--------|            A[0] A[1] A[2] A[3] A[4]
    A|    p   | ------->  |----|----|----|----|----|
     |--------|    /--->  |--1-|--2-|--3-|--4-|--5-|
                  /
     |--------|  /         B[0] B[1] B[2] B[3] B[4]
    B|   q    | /         |----|----|----|----|----|
     |--------|           |--1-|--6-|--7-|--8-|--9-|

        图3

7、在 6 的情况下对动态数组 B 操作 也就是对 动态数组 A 变量的操作
    当执行 B[0] := 0 时 如图4

     |--------|            A[0] A[1] A[2] A[3] A[4]
    A|    p   | ------->  |----|----|----|----|----|
     |--------|    /--->  |--0-|--2-|--3-|--4-|--5-|
                  /
     |--------|  /         B[0] B[1] B[2] B[3] B[4]
    B|   q    | /         |----|----|----|----|----|
     |--------|           |--1-|--6-|--7-|--8-|--9-|

     当执行  B[0] := 0 时 ,A[0] 的值发生了变化,变为 0 了;

8、当我想让动态数组的内存空间释放时,只需 B:=nil;
   即让动态数组 B 不指向任何空间,操作后效果如图4


     |--------|            A[0] A[1] A[2] A[3] A[4]
    A|    p   | ------->  |----|----|----|----|----|
     |--------|           |--0-|--2-|--3-|--4-|--5-|
                  
     |--------|            B[0] B[1] B[2] B[3] B[4]
    B|   nil  |           |----|----|----|----|----|
     |--------|           |--1-|--6-|--7-|--8-|--9-|

       图4

    1、这样算是动态数组 B 的内存空间释放了吗?
    2、我们发现在 动态数组 B 原来指向的位置内存空间并没有释放啊;
    3、不要紧,就那样放在那里吧,垃圾回收会去自动处理;
    4、垃圾回收会把没有用的内存空间释放掉;
       (垃圾工没上班或罢工了咋办)
    5、垃圾工什么时候去收垃圾?
    6、我扔掉的垃圾我发现我得需要,我可以在捡回来吗?
    7、如何捡回来?指向 A 首地址到是好捡 在来一次 B:=A 即可;
    8、我如何捡回来 B 原来指向的空间呢?即指向 B[0] B[1]....?
       即获得 B 以前的值: 1、6、7、8、9 呢?

9、如果需要在运行时缩小动态数组的长度,则可以使用 Copy 函数
  1、假设 A 为动态数组,其实现为:
     var A:array of integer;
     begin
        SetLength( A,5 );
     end;
    简单地使用语句
    A := Copy( A,0,3 );
   将保留动态数组的前 3 个元素,后面的 2 个元素自动删除;
2、Copy 函数有三个参数:
    1、第一个参数为动态数组;
    2、第二个参数表示从原数组的此位置开始保留原始元素;
    3、第三个参数则表示为裁剪后动态数组长度。
3、调用 Copy 函数裁剪数组:
    1、D会开辟一个隐含的动态数组区域;
    2、新开辟的动态数组区域具有裁剪后的长度;
    3、假设新开辟的动态数组使用变量 B 访问;
    4、Copy 函数将把 A 的前三个元素 逐一复制到 B 中;
    5、然后删除动态数组 A 各元素所占用的空间;
    6、将 B 的首地址赋给动态数组变量 A;
    7、最后删除隐含创建的动态数组变量 B ;
    8、割断动态数组变量 B 与 B 的元素所占用的空间的联系;

     |--------|            A[0] A[1] A[2] A[3] A[4]
    A|    p   | ----x--->  |----|----|----|----|----|
     |--------|\           |--1-|--2-|--3-|--4-|--5-|
                \           A := Copy( A,0,3 );
                 \
     |--------|   \----->   B[0] B[1] B[2]
    B|   q    | ----x--->  |----|----|----|
     |--------|            |--1-|--2-|--3-|


10、总结
  1、动态数组变量为隐式的指针,它由D统一管理;
  2、如果要释放动态数组,简单地将其赋为 nil 即可;
  3、D 使用了计数技术自动管理用户程序对该片内存区的访问;
  4、在发现已经没有任何代码再使用它时,自动放释;
  5、“指针的指向”与“指针的引用”
     1、指针的指向,指针面向着内存区域称为指向;指针指向内存区域
     2、指针的引用,内存区域面向着指针称为引用;内存区域引用指针
  6、假设程序中不存在其他对 B 的引用,则 B 原来所占用资源的引用计数
     已降为0, D会自动将其释放;
  7、动态数组变量虽为指针,但不能使用指针操作符;
  8、New 、Dispose 过程分配释放动态数组的内存;
  9、对于静态数组,一旦下标越界,编译器会提示错误信息;
10、对于动态数组,D不负责越界检查;
11、对于动态数组,下标越界编译器不会指出此错误,也不会给数组重新分配内存空间;
12、这意味着在使用动态数组时,我们必须自已解决下标越界问题;
13、在不完全了解动态数组下标上界的情况下,应使用 High 函数;
14、虽然动态数组的下标下界总为0,但也可以使用 Low 函数;
15、一旦静态数组分配了内存,其大小就是确定的,程序再也不能改变其长度;
16、动态数组则不然,可以在运行时改变动态数组的长度;
17、如果需要在运行时缩小动态数组的长度,则可以使用 Copy 函数;

===============================================================================
⊙ 结束
===============================================================================

妖哥 2008-5-26 14:31

关于动态数组的问题,如果没有深入了解其内存分配的机制,会发现越来越多的怪毛病

而且setlength这个方法的实现流程也不是很清楚。

在我反复的setlength一个全局动态数组的时候,变长没事,变短就出错,invalid pointer错误,指针错误

而非常见鬼的是,同样的代码,我昨天编译运行出错,而且结束程序弹出一大堆的异常,而今天再次编译,却运行的稳稳的。

直接导致我根本无从发现问题到底出现在哪里,我确信自己并没有修改过其中的代码……

这是一个值得讨论的问题。
页: [1]
查看完整版本: 【转载】动态数组解释
妖城魔力欢迎您