Arrays
Pascal is a very clean and easy-to-read language. If you can write code at all, you should be able to understand the basics of the Pascal syntax. The Pascal array syntax is no exception to this rule. Artfully constructed and robustly engineered, Pascal arrays are a powerful feature of the language.
Here is how to declare an array of Integers in Pascal:
procedure TForm1.Button1Click(Sender: TObject); var MyArray: array [0..10] of Integer; MyNum: Integer; begin MyArray[0] := 1; MyNum := MyArray[0]; end;
In this code, you find an array of 11 Integers. The first member of the array is at offset 0, and the last is at offset 10. The syntax for capturing this construction is simplicity itself. It is hard to imagine how the concept of an array could be expressed more elegantly or more clearly.
Pascal arrays are very flexible. Note that you can declare not only the top of the range of values in an array, but also the bottom:
procedure TForm1.Button1Click(Sender: TObject); var MyArray: array [12..24] of Integer; MyNum: Integer; begin MyArray[12] := 1; MyNum := MyArray[12]; end;
In this example, the first element of the array is at offset 12, and the last is at 24. You can discover at runtime the range of values in an array:
procedure TForm1.Button1Click(Sender: TObject); var MyArray: array [12..24] of Integer; HighValue, LowValue: Integer; begin HighValue := High(MyArray); LowValue := Low(MyArray); Label1.Caption := `High Value: ` + IntToStr(HighValue); Label2.Caption := `Low Value: ` + IntToStr(LowValue); end;
The High function returns the upper limit of an array, which, in this case, is 24. Low returns the low range, which, in this case, is 12.
Array Constants
When I pick up a new language, I always seem to struggle to find an example of how to declare array constants. This next sample shows two array constants, one with Integers and the other with Strings:
procedure TForm1.Button1Click(Sender: TObject); type TMyStringArray = array[0..2] of String; const MyIntArray: array[0..3] of Integer = (1, 2, 3, 4); MyStringArray: TMyStringArray = (`One', `Two', `Three'); begin ListBox1.Items.Add(IntToStr(MyIntArray[0])); // prints 1 ListBox1.Items.Add(MyStringArray[0]); // prints `one' end;
In this example, I declare the type of the Integer array in the const statement and the type of the String array in a type section. I do this simply to show that, depending on your needs or preferences, you can use either syntax.
Dynamic Arrays and Arrays of Objects
Unlike C/C++, Pascal has no trouble handling arrays of objects. The following syntax discovers all the buttons on a form, places them in an array, and gives you access to their properties and methods:
procedure TForm1.Button3Click(Sender: TObject); var ButtonArray: array of TButton; i, j, Num: Integer; begin Num := 0; for i := 0 to ComponentCount - 1 do if Components[i] is TButton then Inc(Num); SetLength(ButtonArray, Num); j := 0; for i := 0 to ComponentCount - 1 do if Components[i] is TButton then begin ButtonArray[j] := TButton(Components[i]); Inc(j); end; for i := 0 to High(ButtonArray) do ListBox1.Items.Add(ButtonArray[i].Caption); end;
The Button3Click method shows the Pascal language doing all sorts of glorious thing to delight and amuse us. The code discovers all the TButton objects on a form, declares an array just large enough to hold these buttons, and then places the buttons in the array. Finally, the captions of each button are placed in a list box, as shown in Figure 3.1.
ButtonArray is declared to be an array of TButton, with no declared bottom or top range. Instead, the range of the array will be discovered at runtime.
Figure 3.1 A list box is put to use holding the captions of each button placed on this form.
To find out how many elements we need in the array, the code makes use of information maintained by the main form of this application. In particular, all forms maintain a list of the components that have been dropped on them.
The program uses the Form1.ComponentCount property to determine how many components are on the form:
for i := 0 to ComponentCount - 1 do
Then each element of the array of components maintained by the main form is iterated over and is checked to see if it is a button:
if Components[i] is TButton then
Perhaps some clarity could be gained if these methods were written thus:
procedure TForm1.Button3Click(Sender: TObject); var ButtonArray: array of TButton; i, j, Num: Integer; begin Num := 0; for i := 0 to Self.ComponentCount - 1 do if Self.Components[i] is TButton then Inc(Num);
In this rewrite of the first lines of the Button3Click method, I make explicit the fact that ComponentCount and the Components array both belong to Form1.
C/C++, JAVA NOTE
Object Pascal uses the term Self where Java or C++ would use this. Depending on your frame of mind, this personification of objects could be appealing or distracting. Despite the different flavors associated with the two terms, their meaning is ultimately identical.
When you know the number of buttons that were dropped on the form, you can set the size of the array of buttons:
SetLength(ButtonArray, Num);
The SetLength method does exactly what you would expect: It allocates memory in the array for the number of buttons that you have dropped on the form.
NOTE
You will perhaps recall that SetLength can also be used to set the length as an AnsiString. Because a string is really just an array of Char, this is not quite the coincidence that it might appear at first. Other functions that you can use on both types include Copy and Length.
The next chunk of code simply iterates over the components again, adding the buttons into the array as each is discovered:
for i := 0 to ComponentCount - 1 do if Components[i] is TButton then begin ButtonArray[j] := TButton(Components[i]); Inc(J); end;
Finally, the code displays the names of the buttons in a list box:
for i := 0 to High(ButtonArray) do ListBox1.Items.Add(ButtonArray[i].Caption);
Note that the syntax for accessing the members of the array of buttons is utterly clean and intuitive:
ButtonArray[i].Caption
It turns out that the SetLength procedure can be used not only to allocate memory for a String, but also to reallocate it. The following rewrite of the Button3Click method shows how to put this to use:
procedure TForm1.Button3Click(Sender: TObject); var ButtonArray: array of TButton; i, Num: Integer; begin Num := 0; for i := 0 to Self.ComponentCount - 1 do if Self.Components[i] is TButton then begin Inc(Num); SetLength(ButtonArray, Num); ButtonArray[Num - 1] := TButton(Components[i]); end; for i := 0 to High(ButtonArray) do ListBox1.Items.Add(ButtonArray[i].Caption); end;
Here the method is shortened and cleaned up a bit by continually reallocating the memory for the ButtonArray with the SetLength method.
NOTE
You might expect that repeatedly reallocating memory has some overhead associated with it, so it is possible that this second solution is not necessarily any faster than the first technique. People who are interested in such matters can explore the matter further on their own. Unless you are using these methods in a large, frequently called loop, don't worry about using one method or the otherthey both execute in essentially zero time.
If you want to shorten the length of a dynamic array, use the Copy function:
procedure TForm1.Button5Click(Sender: TObject); const BigLen = 24; SmallLen = 12; var MyArray: array of Double; i: Integer; begin SetLength(MyArray, BigLen); for i := 0 to High(MyArray) do MyArray[i] := Sqr(i); MyArray := Copy(MyArray, 0, SmallLen); for i := 0 to High(MyArray) do ListBox1.Items.Add(Format(`Value of array = %f', [MyArray[i]])); end;
This code first sets the length of MyArray to 24. Next it assigns values to each member of the array. The array is then shortened to length 12. Finally, the program displays the elements of the array to show that the shortening did not destroy the values that it held. Note that you can use High, Low and Length on dynamic arrays, just as you can on normal arrays and on strings.
Pascal arrays can contain multiple dimensions. Here is an example of how multi-dimensional arrays works:
procedure TForm1.Button6Click(Sender: TObject); const XSize = 5; YSize = 5; var MyTwoDim: array[0..XSize, 0..YSize] of Integer; i, j: Integer; begin for i := 0 to YSize do for j := 0 to XSize do MyTwoDim[j, i] := j * i; for i := 0 to YSize do for j := 0 to XSize do StringGrid1.Cells[j, i] := IntToStr(MyTwoDim[j, i]); end;
The code declares a two-dimensional array of Integer. Both dimensions contain five elements. The code then fills out the array with the first five elements of the multiplication table. A TStringGrid allows the program to display the content of the array, as shown in Figure 3.2. The key to the TStringGrid object is the Cells property, which represents the array of cells in the grid. The syntax for using the Cells property is the same as for using a two-dimensional array of String.
Figure 3.2 The TStringGrid object from the Additional page in the Component Palette can help you display your data in a clean and logical fashion.
You may declare an array in the normal fashion and then pass it to a method as a parameter. In such cases, you need not specify the size of the parameter in the function that you pass to it:
procedure TForm1.ShowArray(MyArray: array of String); var i: Integer; begin for i := 0 to High(MyArray) do ListBox1.Items.Add(MyArray[i]); end; procedure TForm1.Button7Click(Sender: TObject); var MyArray: array [0..2] of String; begin MyArray[0] := `You need not declare the size'; MyArray[1] := `of an array you pass to a method.'; MyArray[2] := `Instead, just declare an open array'; ShowArray(MyArray); end;
Here you can see that I do not explicitly state the size of the array to be passed to the ShowArray method. Instead, I declare it as an open array of String and then use the High method to determine its actual dimensions.
Debug Your Arrays: Turn on Range Checking
When developing applications that use arrays, it is best to select Project, Options, Compiler and turn on Range Checking. Range Checking is off, by default. This is a technique used by the compiler to check that you are not trying to write past the end of an array. For instance, if you allocate an array of 10 bytes in size and try to write to the nonexistent 11th byte of the array, the Range Checking mechanism would catch the error and raise an exception. Without range checking, sometimes writing past the end of an array will raise an exception; at other times, it will corrupt the memory of a program in some subtle and seemingly undetectable manner. This can lead to debugging sessions that last for hours or even days. If you turn on Range Checking, however, the error will appear immediately, and it will be clearly flagged. After debugging your code that uses arrays, you should turn off Range Checking because it slows down your code.
In this section on arrays, I discussed the basic facts about arrays in a few short paragraphs and then covered the more complex subject of dynamic arrays and open arrays. This look at arrays ended with a brief meditation on the importance of range checking.