Mastering Delphi Programming:A Complete Reference Guide
上QQ阅读APP看书,第一时间看更新

Arrays

Delphi supports two kinds of arrays. One is of a fixed size and has settable lower and upper bounds. We call them static arrays. Others have a lower bound fixed at zero, while the upper bound is variable. In other words, they can grow and shrink. Because of that, they are called dynamic arrays.

The following code fragment shows how static and dynamic arrays are declared in Delphi:

var
sarr1: array [2..22] of integer; // static array
sarr2: array [byte] of string; // static array
darr1: array of TDateTime; // dynamic array
darr2: TArray<string>; // dynamic array

Every array stores elements of some type. The compiler will treat these elements just the same as variables of that type. In the second declaration above, for example, the element type is string. This is a managed type and all 256 elements of the array will be initialized to nil automatically.

Static arrays are pretty dull. They have a constant size and as such are created exactly the same way as simple types (except that they typically use more space). Dynamic arrays, on the other hand, are similar to strings, but with a twist.

When you declare a dynamic array, it will start as empty and the variable will contain a nil pointer. Only when you change an array size (by using SetLength or a TArray constructor), memory will be allocated and a pointer to that memory will be stored in the dynamic array variable. Any change to array size will reallocate this storage memory—just like with the strings.

Modifying elements of an array is simple—again just like with the strings. An address of the element that is being modified is calculated and then item data is copied from one location to another. Of course, if array elements are not of a simple type, appropriate rules for that type will apply. Setting an element of array of string brings in the rules for the string type and so on.

When you assign one array to another, Delphi again treats them just the same as strings. It simply copies one pointer (to the array data) into another variable. At the end both variables contain the same address. A reference count in the array memory is also incremented. Again, just as strings.

The similarities with string types end here. If you now modify one element in one of the arrays, the same change will apply to the second array! There is no copy-on-write mechanism for dynamic arrays and both array variables will still point to the same array memory.

This is great from the efficiency viewpoint, but may not be exactly what you wanted. There's, luckily, a workaround that will split both arrays into two separate copies. Any time you call a SetLength on a shared array, the code will make this array unique, just like UniqueString does to strings. Even if the new length is the same as the old one, a unique copy of an array will still be made.

The following code from the DataTypes demo demonstrates this. Firstly, it allocates the arr1 array by using a handy syntax introduced in Delphi XE7. Then it assigns arr1 to arr2. This operation merely copies the pointers and increments the reference count. Both pointers and contents of the arrays are then shown in the log.

After that, the code modifies one element of arr1 and logs again. To complete the test, the code calls SetArray to make arr2 unique and modifies arr1 again:

procedure TfrmDataTypes.btnSharedDynArraysClick(Sender: TObject);
var
arr1, arr2: TArray<Integer>;
begin
arr1 := [1, 2, 3, 4, 5];
arr2 := arr1;
ListBox1.Items.Add(Format('arr1 = %p [%s], arr2 = %p [%s]',
[PPointer(@arr1)^, ToStringArr(arr1),
PPointer(@arr2)^, ToStringArr(arr2)]));

arr1[2] := 42;
ListBox1.Items.Add(Format('arr1 = %p [%s], arr2 = %p [%s]',
[PPointer(@arr1)^, ToStringArr(arr1),
PPointer(@arr2)^, ToStringArr(arr2)]));

SetLength(arr2, Length(arr2));
arr1[2] := 17;
ListBox1.Items.Add(Format('arr1 = %p [%s], arr2 = %p [%s]',
[PPointer(@arr1)^, ToStringArr(arr1),
PPointer(@arr2)^, ToStringArr(arr2)]));
end;

The output of the program shows that after the assignment both arrays point to the same memory and contain the same data. This doesn't change after arr1[2] := 42. Both arrays still point to the same memory and contain the same (changed) data.

After SetLength, however, arrays point to a separate memory address and changing arr1 only changes that array: