views:

2997

answers:

2

I originally had an array[1..1000] that was defined as a global variable. But now I need that to be n, not 1000 and I don't find out n until later. I know what n is before I fill the array up but I need it to be global therefore need a way to define the size of a global array at run time.

Context is filling an array with a linear transformation of the bytes in a file. I don't know how big the file is until someone wants to open it and the files can be of any size.

+40  A: 

As of Delphi 4, Delphi supports dynamic arrays. You can modify their sizes at run time and they will retain the data you stored in other elements at the old size. They can hold elements of any homogeneous type, including records and other arrays. You can declare a dynamic array the same as you declare normal, "static" arrays, but simply omit the array bounds:

var
  ArthurArray: array of TForm;

Although static arrays allow you to specify both the lower and upper bound, the low index of a dynamic array is always zero. The high index is given by the High function, which always returns one less than the length of the array. For any dynamic array x, High(x) = Length(x)-1.

A global variable can be accessed by any code, including local procedures.

A global variable of dynamic-array type will be initialized to be an empty array. Its length will be zero and High called on that array will be -1. Low on that array will still return zero.

At any time, you may resize a dynamic array. Use the SetLength function, just as you can do with strings:

var
  NumElements: Integer;
begin
  NumElements := GetNumberOfArthurForms();
  SetLength(ArthurArray, NumElements);
end;

If you have a multidimensional array, you can set their lengths in a loop:

var
  matrix: array of array of Double;
  i: Integer;
begin
  SetLength(matrix, height);
  for i := 0 to height - 1 do
    SetLength(matrix[i], width);
end;

There's a shortcut for that to set the lengths of all the inner arrays at once:

begin
  SetLength(matrix, height, width);
end;

Like I mentioned, dynamic arrays keep their old values when you resize them:

var
  data: array of string;
begin
  SetLength(data, 2);
  data[1] := 'foo';
  SetLength(data, 20);
  Assert(data[1] = 'foo');
end;

But if you shorten the array, any elements that resided beyond the new last element are gone forever:

begin
  SetLength(data, 20);
  data[15] := 'foo';
  SetLength(data, 2);
  // data[15] does not exist anymore.
  SetLength(data, 16);
  writeln(data[15); // Should print an *empty* line.
end;

My demonstrations above used strings. Strings are special in Delphi; they're managed by the compiler through reference counts. Because of that, new dynamic-array elements of type string are initialized to be empty. But if I had used integers instead, there would be no guarantee of the values of new elements. They might be zero, but they might be anything else, too, just like the initial values of standalone local variables.

The Delphi 7 help files are very good, I'm told. Please read more about dynamic arrays there. You can find demonstrations of their use throughout the VCL and RTL source code provided in your Delphi installation, as well as in nearly any Delphi code example produced in the last 10 years.

Rob Kennedy
+1, very complete answer. Impressive. Now please somebody edit the question summary to include "Delphi" and "dynamic arrays", so that people will find it in searches.
mghie
WOW all i need to know thank you very much, Amazing answer!
Arthur
Excellent answer!
gabr
Actually, when you expand a dynamic array, the memory of new entries in the array are guaranteed to be zeroed out, which means that integers are 0, pointers are NIL and strings (both long and short strings) are empty.I have been unable to find it documented in the Help, but when tracing through the code for SetLength it ends with a call to FillChar that clears out the extra allocated memory.
HeartWare
That's a relatively new development, @Heartware. As you said, it's not documented to work that way, so, like I said, there is no guarantee, and in fact, [the documentation even says that](http://docwiki.embarcadero.com/VCL/en/System.SetLength). The current implementation takes the easy way and always initializes new elements. I'm pretty sure the previous assembler implementation used the array's RTTI to determine what needed initialization. The next implementation may go back to that way. Perhaps the new faster implementation of FillChar obviated the assembler version.
Rob Kennedy
+1  A: 

First, here's a general answer to the first part of your question:

If your array is no longer static, you might want to consider using a TList, a TStringList or one of the many container classes in the Contnrs unit.

They may better represent what you are doing, provide additional capabilities you might need, e.g. sorting or name/value pairs, they dynamically grow as you need them, and have been very well optimized.


Then you said:

"Context is filling an array with a linear transformation of the bytes in a file. I don't know how big the file is until someone wants to open it and the files can be of any size."

For your specific problem, I would load the bytes in a file using:

  MyFileStream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite);
  Size := MyFileStream.Size - MyFileStream.Position;
  SetLength(Buffer, Size);
  MyFileStream.Read(Buffer[0], Size);

Then you can easily use a PChar pointer to go through each character or even each byte in the Buffer one by one and transform them the way you need to.

lkessler