(delphi11最新学习资料) Object Pascal 学习笔记---第5章第1节(动态数组)

5.1.4 动态数组

​ 在传统的Pascal中,数组的大小是固定的,并且在声明数据类型时限制了元素的数量。然而,Object Pascal支持动态数组的直接和本地实现。

注解:“直接实现动态数组” 与使用指针和动态内存分配来获得类似效果的方法截然不同… 后者代码非常复杂且容易出错。 顺便说一句,动态数组是大多数现代编程语言中唯一的一种结构形式。

​ 动态数组是动态分配的,并进行引用计数(使得参数传递更快,因为只传递引用,而不是完整数组的副本)。当您完成使用数组时,可以通过将其变量设置为nil或将长度设置为零来清除数组,由于动态数组是引用计数的,编译器将自动释放内存。请注意,这仅适用于数组项使用的内存:如果数组保存对其他位置内存的引用(如对象引用),您需要确保在释放数组本身之前清理这些对象使用的内存。

​ 使用动态数组时,您可以声明一个数组类型而不指定元素的数量,然后使用SetLength过程设置数组的大小:

var
  Array1: array of Integer;
  
begin
  // 这将导致运行时范围检查错误
  // Array1[0] := 100;
  SetLength(Array1, 10);
  Array1[0] := 100; // 这是可以的
end;

​ 在为数组设定长度并在堆上分配所需的内存之前,你不能使用数组。如果你这样做,要么会出现范围检查错误(如果相应的编译器选项处于激活状态),要么会在 Windows 平台上出现访问违规(Access Violation),或者在其他平台上出现类似的内存访问错误。SetLength 调用会将所有的值设置为零。数组初始化以后,你就可以立即开始读写数组值,而不必担心内存错误(除非超越了数组边界)。

​ 如果确实需要显式分配内存,你也不必直接释放内存。在上面的代码片段中,当代码结束且 Array1 变量退出作用域时,编译器会自动释放其内存(在本例中是已分配的 10 个整数)。因此,虽然可以将动态数组变量赋值为 nil 或调用 SetLength 时赋值为 0,但一般不需要这样做(也很少这样做)。

​ 请注意,SetLength 过程也可以用来调整数组的大小,如果要增大数组,则不会丢失当前的内容;如果要缩小数组,则会丢失一些元素。由于在最初的 SetLength 调用中只指定了数组的元素个数,动态数组的索引总是从 0 开始,直到元素个数减 1。换句话说,动态数组不支持经典静态 Pascal 数组的两个特性:非零低限和非整数索引。同时,动态数组与大多数基于 C 语法的语言中数组的工作方式更为相像。

​ 要查询动态数组的当前大小,与静态数组一样,你可以使用 Length、High 和 Low 函数。但是,对于动态数组,Low 总是返回 0,而 High 总是返回长度减 1。这意味着,对于一个空数组,High 返回-1(仔细想想,这是一个奇怪的值,因为它比 Low 返回的值低)。

​ 因此,在 DynArray 示例中,我使用自适应循环从动态数组中填充和提取信息。这是类型和变量定义:

type
  TIntegersArray = array of Integer;
  
var
  IntArray1: TIntegersArray;

​ 使用以下循环,为数组分配内存并用匹配索引的值填充:

var
  I: Integer;
  
begin
  SetLength(IntArray1, 20);
  for I := Low(IntArray1) to High(IntArray1) do
    IntArray1[I] := I;
end;

​ 第二个按钮的代码既显示每个值又计算平均值,类似于先前示例中的代码,但包含在一个循环中:

var
  I: Integer;
  Total: Integer;
  
begin
  Total := 0;
  for I := Low(IntArray1) to High(IntArray1) do
  begin
    Inc(Total, IntArray1[I]);
    Show(I.ToString + ': ' + IntArray1[I].ToString);
  end;
  Show('Average: ' + (Total / Length(IntArray1)).ToString);
end;

​ 这段代码的输出是相当明显的(大部分被省略):

0: 0
1: 1
2: 2
3: 3
...
17: 17
18: 18
19: 19
Average: 9.5

​ 除了Length、SetLength、Low和High之外,还有其他一些常见的过程可用于数组,比如Copy函数,它允许您复制数组的一部分(或全部)。请注意,您还可以将一个数组从一个变量分配给另一个变量,但在这种情况下,您不是在进行完全复制,而是使两个变量引用相同的内存中的同一个数组。

​ 仅在DynArray示例的最后部分中有略微复杂的代码,它以两种不同的方式将一个数组复制到另一个数组:

  • 使用Copy函数,该函数在新的数据结构中使用单独的内存区域复制数组数据
  • 使用赋值运算符,它实际上创建了一个别名,即一个新变量,引用相同的内存中的相同数组

​ 在这一点上,如果您修改新数组的元素之一,您将会影响原始版本,或者根据复制的方式而不影响它。这是完整的代码:

var
  IntArray2: TIntegersArray;
  IntArray3: TIntegersArray;
  
begin
  // 别名
  IntArray2 := IntArray1;
  // 单独的复制
  IntArray3 := Copy(IntArray1, Low(IntArray1), Length(IntArray1));
  // 修改项目
  IntArray2[1] := 100;
  IntArray3[2] := 100;
  // 检查每个数组的值
  Show(Format('[%d] %d -- %d -- %d', [1, IntArray1[1], IntArray2[1], IntArray3[1]]));
  Show(Format('[%d] %d -- %d -- %d', [2, IntArray1[2], IntArray2[2], IntArray3[2]]));
end;

您将得到的输出如下:

[1] 100 -- 100 -- 1
[2] 2 -- 2 -- 100

​ 对IntArray2的更改会波及到IntArray1,因为它们只是对同一物理数组的两个引用;对IntArray3的更改是独立的,因为它有数据的独立副本。

动态数组的本地操作

​ 动态数组在Delphi XE7中引入了对常量数组的赋值和连接的支持。以下是一个示例,演示了这些操作:

var
  DI: array of Integer;
  I: Integer;

begin
  DI := [1, 2, 3];       // 初始化
  DI := DI + DI;         // 连接
  DI := DI + [4, 5];      // 混合连接

  for I in DI do
  begin
    Show(I.ToString);
  end;
end;

​ 注意此代码中使用for-in循环遍历数组元素,这是DynArrayConcat示例的一部分。这些数组可以基于任何数据类型,从简单的整数到记录和类。

​ 除了赋值和连接之外,还可以在动态数组上使用对字符串常见的Insert和Delete等函数。

​ 以下是使用Insert和Delete的示例:

var
  DI: array of Integer;
  I: Integer;

begin
  DI := [1, 2, 3, 4, 5, 6];

  Insert([8, 9], DI, 4);
  Delete(DI, 2, 1);  // 删除第三个项目(zero-based index)
end;

你可能感兴趣的:(Object,Pascal,Handbook,学习,笔记,delphi,delphi11,Object,Pascal)