2006. augusztus 6., vasárnap

Dynamic Arrays in Delphi


Problem/Question/Abstract:

Dynamic Arrays in Delphi

Answer:

Borland Delphi 4 features a number of Object Pascal language enhancements, as usual. In this article, I'll address a very handy language enhancement takes the ideas of Open Parameters and Long Strings back to the basics of arrays in the so-called Dynamic Arrays.

In Delphi 4, in addition to declaring static arrays such as

X: array[1..42] of string;

we can now also declare dynamic arrays. Dynamic arrays specify type information (the number of dimensions and the type of the elements) but not the number of elements. Thus

X: array of string;

M: array of array of Integer;

declares two dynamic arrays. X is a one-dimensional arrays of Strings, while M is a two dimensional array of Integers (like a Matrix).

Dynamic arrays do not have a fixed size or length. Instead, memory for a dynamic array is (re-)allocated when we assign a value to the array or pass it to the SetLength procedure. Hence, the above declarations for X and M do not allocate memory. To create the array in memory, call SetLength. For example, given the declarations above,

SetLength(X, 42);

allocates an array of 42 Strings, indexed 0 to 41. Dynamic arrays are always integer-indexed, always starting from 0.

After calling SetLength, the previous content of the dynamic array - if any - is copied along (so data never gets lost if we constantly increase or decrease the length of the array). Using the above knowledge, we can write a small - and very inefficient, of course - program to read a number of lines from a textfile, and only allocate the exact number of strings that are needed.

{$R+}
{$APPTYPE CONSOLE}
var
  X: array of string;
  i: Integer;
begin
  while not eof do
  begin
    SetLength(X, Length(X) + 1); // very inefficient...
    readln(X[High(X)])
  end;
  for i := 0 to High(X) do
    writeln(X[i])
end.

Dynamic-array variables are implicitly pointers and are managed by the same reference-counting technique used for Long Strings. To deallocate a dynamic array, assign nil to a variable that references the array or pass the variable to Finalize; either of these methods disposes of the array, provided there are no other references to it.

{$R+}
program Delphi4;
{$APPTYPE CONSOLE}
uses
  Dialogs;
var
  X, Y: array of string;
  i: Integer;
begin
  SetLength(X, 7);
  Y := X;
  X[3] := 'Dynamic Arrays in Delphi 4';
  SetLength(X, 42);
  Y := X;
  SetLength(Y, 4);
  ShowMessage(Y[3]);
  X := nil;
  Finalize(Y);
end.




Warning: we should not apply the dereference operator (^) to a dynamic-array variable or pass it to the New or Dispose procedure.

If X and Y are variables of the same dynamic-array type, X:=Y allocates X to the length of Y and points X to the same array as Y. Unlike strings, arrays are not automatically copied (i.e. made unique) before they are written to, but they keep pointed to the same - shared - memory area! For example, after this code executes

var
  X, Y: array of string;
begin
  SetLength(X, 1);
  X[0] := 'Hello, world';
  Y := X;
  Y[0] := 'Answer';
end;

the value of X[0] is 'Answer'.

Unlike Long Strings, that get "split" when we change one of them (to get a unique copy), dynamic arrays keep pointed to the same area. A bit unexpected, perhaps, but at least we don't get delayed performance hits (like with Long Strings)...
Of course, since dynamic array contents are copied when we call SetLength, that's also all it takes (a call to SetLength) to create a unique copy of a dynamic array.

Assigning to a dynamic-array index (for example, X[42] := 'Answer') does not reallocate the array (we need to call SetLength to do that). Out-of-range indexes are not reported at compile time, but will raise an exception at run-time (with $R+ compiler directive).

When dynamic-array variables are compared, their references are compared, not their array values. Thus, after execution of the code

var
  X, Y: array of string;
begin
  SetLength(X, 1);
  SetLength(Y, 1);
  X[0] := 'Hello, world!';
  Y[0] := 'Hello, world!';
end;

X = Y returns False but X[0] = Y[0] returns True.

To truncate a dynamic array, pass it to the Copy function and assign the result back to the array variable. For example, if X is a dynamic array, X := Copy(X, 0, 2) truncates all but the first 2 elements of X.

Once a dynamic array has been allocated, we can pass it to the standard functions Length, High, and Low. Length returns the number of elements in the array, High returns the array's highest possible index (Length - 1), and Low always returns 0. For a zero-length array, High indeed returns -1, so in that case High(X) < Low(X).

To instantiate the multi-dimensional array M (see declaration on top of this paper), we need to call SetLength with two integer arguments:

SetLength(M, 10, 5);

allocates an 10-by-5 array, and M[9,4] denotes an element of that array.

We can also create multidimensional dynamic arrays that are not rectangular. The first step is to call SetLength, passing it parameters for the first n dimensions of the array. For example,

var
  M: array of array of Integer;
begin
  SetLength(M, 10);

allocates ten rows for M but no columns. Then, we can allocate the columns one at a time (giving them different lengths); for example

SetLength(M[2], 42);

makes the third column of M 42 integers long. At this point (even if the other columns haven't been allocated) we can assign values to the third column for example, M[2][41] := 7.

Nincsenek megjegyzések:

Megjegyzés küldése