|
| What are records?
|
| Records are a useful and distinguishing feature of delphi. They provide a very neat way of having named data structures - groups of data fields. Unlike arrays, a record may contain different types of data.
|
| Records are fixed in size - the definition of a record must contain fixed length fields. We are allowed to have strings, but either their length must be specified (for example a : String[20]), or a pointer to the string is stored in the record. In this case, the record cannot be used to write the string to a file. The TPoint type is an example of a record. Before we go any further, let us look at a simple example.
|
| type
TCustomer = record
name : string[30];
age : byte;
end;
var
customer : TCustomer;
begin
// Set up our customer record
customer.name := 'Fred Bloggs';
customer.age := 23;
end;
|
|
| When we define a record, each field is simply accessed by name, separated by a dot from the record variable name. This makes records almost self documenting, and certainly easy to understand.
|
| Above, we have created one customer, and set up the customer record fields.
|
| Using the with keyword
|
| When we are dealing with large records, we can avoid the need to type the record variable name. This avoidance, however, is at a price - it can make the code more difficult to read:
|
| type
TCustomer = record
name : string[30];
age : byte;
end;
var
John, Nancy : TCustomer;
begin
// Set up our customer records
with John do
begin
name := 'John Moffatt'; // Only refer to the record fields
age := 67;
end;
with Nancy do
begin
name := 'Nancy Moffatt'; // Only refer to the record fields
age := 77;
end;
end;
|
|
| A more complex example
|
| In practice, records are often more complex. Additionally, we may also have a lot of them, and might store them in an array. The following example is a complete program that you may copy and paste into your Delphi product, making sure to follow the instructions at the start of the code.
|
| Please note that this is quite a complex piece of code - it uses a procedure that takes a variable number of parameters, specially passed in square brackets (see Procedure for more on procedures).
|
| // Full Unit code.
// -----------------------------------------------------------
// You must store this code in a unit called Unit1 with a form
// called Form1 that has an OnCreate event called FormCreate.
unit Unit1;
interface
uses
Forms, Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure ShowCustomer(const fields: array of string);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}// Include form definitions
procedure TForm1.FormCreate(Sender: TObject);
type
// Declare a customer record
TCustomer = Record
firstName : string[20];
lastName : string[20];
address1 : string[100];
address2 : string[100];
address3 : string[100];
city : string[20];
postCode : string[8];
end;
var
customers : array[1..3] of TCustomer;
i : Integer;
begin
// Set up the first customer record
with customers[1] do
begin
firstName := 'John';
lastName := 'Smith';
address1 := '7 Park Drive';
address2 := 'Branston';
address3 := 'Grimworth';
city := 'Banmore';
postCode := 'BNM 1AB';
end;
// Set up the second and third by copying from the first
customers[2] := customers[1];
customers[3] := customers[1];
// And then changing the first name to suit in each case
customers[2].firstName := 'Sarah';
customers[3].firstName := 'Henry';
// Now show the details of these customers
for i := 1 to 3 do
with customers[i] do ShowCustomer([firstName,
lastName,
address1,
address2,
address3,
city,
postCode]);
end;
// A procedure that displays a variable number of strings
procedure TForm1.ShowCustomer(const fields: array of string);
var
i : Integer;
begin
// Display all fields passed - note : arrays start at 0
for i := 0 to Length(fields)-1 do
ShowMessage(fields[i]);
ShowMessage('');
end;
end.
|
|
| The ShowMessage procedure is used to display the customer details.
Click on it in the code to learn more.
The displayed data is as follows:
John
Smith
7 Park Drive
Branston
Grimworth
Banmore
BNM 1AB
Sarah
Smith
7 Park Drive
Branston
Grimworth
Banmore
BNM 1AB
Henry
Smith
7 Park Drive
Branston
Grimworth
Banmore
BNM 1AB
|
|
| Packing record data
|
| By default, Delphi will pad out the record with fillers, where necessary, to make sure that fields are aligned on 2, 4 or 8 byte boundaries to improve performance. You can pack the data with the packed keyword to reduce the record size if this is more important than performance. See Packed for more on this topic.
|
| Records with variant parts
|
| Things get very interesting now. There are times when a fixed format record is not useful. First, we may wish to store data in the record in different ways. Second, we may want to store different types of data in a part of a record.
|
| The Delphi TRect type illustrates the first concept. It is defined like this:
|
| type
TRect = packed record
case Integer of
0: (Left, Top, Right, Bottom: Integer);
1: (TopLeft, BottomRight: TPoint);
end;
|
|
| Here we have a record that holds the 4 coordinates of a rectangle. The Case clause tells Delphi to map the two following sub-sections onto the same area (the end) of the record. These variant sections must always be at the end of a record. Note also that the case statement has no end statement. This is omitted because the record finishes at the same point anyway.
|
| The record allows us to store data in two ways:
|
| var
rect1, rect2 : TRect;
begin
// Setting up using integer coordinates
rect1.Left := 11;
rect1.Top := 22;
rect1.Right := 33;
rect1.Bottom := 44;
// Seting up rect2 to have the same coordinates, but using points instead
rect2.TopLeft := Point(11,22);
rect2.BottomRight := Point(33,44);
end;
|
|
| The TRect record showed two methods of reading from and writing to a record. The second concept is to have two or more record sub-sections that have different formats and lengths.
|
| This time we will define a fruit record that has a different attribute section depending on whether the fruit is round or long:
|
| type
// Declare a fruit record using case to choose the
// diameter of a round fruit, or length and height ohterwise.
TFruit = Record
name : string[20];
Case isRound : Boolean of// Choose how to map the next section
True :
(diameter : Single); // Maps to same storage as length
False :
(length : Single; // Maps to same storage as diameter
width : Single);
end;
var
apple, banana : TFruit;
begin
// Set up the apple as round, with appropriate dimensions
apple.name := 'Apple';
apple.isRound := True;
apple.diameter := 3.2;
// Set up the banana as long, with appropriate dimensions
banana.name := 'Banana';
banana.isRound := False;
banana.length := 7.65;
banana.width := 1.3;
// Let us display the fruit dimensions:
if apple.isRound
then ShowMessageFmt('Apple diameter = %f',[apple.diameter])
else ShowMessageFmt('Apple width = %f , length = %f',
[apple.width, apple.length]);
if banana.isRound
then ShowMessageFmt('Banana diameter = %f',[banana.diameter])
else ShowMessageFmt('Banana width = %f , length = %f',
[banana.width, banana.length]);
end;
|
|
| Apple diameter = 3.2
Banana width = 3.20 , length = 7.65
|
|
| Note that the Case statement now defines a variable, isRound to hold the type of the variant section. This is very useful, and recommended in variable length subsections, as seen in the code above. |
| | | | |