软件注册站
热情软件屋

 
怎样在Pascal程序运行中分配存储空间
编号: QA004804    
建立日期: 2003年12月22日 最后修改日期: 2003年12月22日
所属类别: Delphi - 其他方面
   
    操作系统: windows 98
    编程工具: turbo pascal 7.0
    问题: 怎样在程序运行中分配存储空间?就像VB中的redim一样。
    水平: 中级(丁欧南)
   
    可以借助于Pascal的指针来实现动态分配空间的工作。
    指针是存放指定类型(或未定义类型)变量内存地址的变量,因此指针间接引用一个值。定义指针不需用特定的关键字,而用一个特殊字符,这个特殊字符是脱字符号(^),见下例:
    type
     PointerToInt = ^Integer;
    
    

    一旦你定义了指针变量,你就可以用@ 符号把另一个相同类型变量的地址赋给它。见下例:
    var
     P: ^Integer;
     X: Integer;
    begin
     P := @X;
     // change the value in two different ways
     X := 10;
     P^ := 20;
    

    如果定义了一个指针P,那么P表示指针所指向的内存地址,而P^表示内存所存储的实际内容。因此,在上面的代码中, P^ 与X相等。
    除了表示已分配内存的地址外,指针还能通过New 例程在堆中动态分配内存,不过当你不需要这个指针时,你也必须调用Dispose 例程释放你动态分配的内存。
    var
     P: ^Integer;
    begin
     // initialization
     New (P);
     // operations
     P^ := 20;
     ShowMessage (IntToStr (P^));
     // termination
     Dispose (P);
    end;
    

    如果指针没有值,你可以把nil 赋给它。这样,你可以通过检查指针是否为nil 判断指针当前是否引用一个值。这经常会用到,因为访问一个空指针的值会引起一个访问冲突错误,也就是大家知道的“一般保护错”(GPF)。
    Pascal还定义了一个Pointer 数据类型,它表示无类型的指针(就象C语言中的void* )。Pascal中,动态分配内存的函数是GetMem(),与之对应的释放函数为FreeMem()。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;
    
    

    对于以上这个例子,要注意一个问题,就是分配内存的单位是字节(BYTE),因此在使用GetMem时,其第二个参数如果想当然的写成 20,那么就会出问题了(内存访问越界)。因为GetMem(ptr, 20);实际只分配了20个字节的内存空间,而一个整形的大小是四个字节,那么访问第五个之后的所有元素都是非法的了。
    传统的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的返回值还小。
    以上作了简短的介绍,现在举个简例。程序中声明了两个全程数组并在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 按钮仍能正常工作,因为需要修改的数组元素由数组当前的下标范围确定。

    

此问题由李海回答。

附加关键字:编程, 源程序, programming, source code, Delphi, VCL, Borland, 其他方面,

   
 
把这个问题推荐给朋友
   
 
   
您的意见类别
您的名字
您的电子邮件
您的建议(请尽可能详细)
 
 

版权所有 1997-2008 热情软件屋
如果您有任何建议和意见, 请给我发个电子邮件 askpro@china-askpro.com
Web Designed by ZebraStudio