Lesson 7
Records.
Arrays was introduced in the previous lesson as a means of storing data. When an array is declared and used, it can store one type of data only. For example, given the declaration :
var
N : array [ 1..10 ] of integer;
the array N can store 10 items and the items can only be of type INTEGER. What if you wanted to store inter-related items of different types?
Your personal particulars consists of different data types. Let's say we want to store your particulars which includes your name, IC number, address, age, telephone number, bank account number and balance, gross salary per month, credit card account limit and a few more particulars, we would notice that there is a mixture of data types involved.
Your name, address, IC number, telephone number and bank account number are all string items. Your age, bank account balance, gross salary per month and credit card account limit are all numeric items. So you see that using arrays are not very suitable for storing items of different types because many different arrays have to be created.
In this case, records are best used. Records are collections of inter-related data items which may be of different types. A record is a structured data type. The individual items in the record are known as fields. A record name must be used to reference a group of fields. This will show the relationship between the record and individual fields. Basically, the use of records is just to store data. Records, after all, are just memory structures.
There are only two important things you have to learn about using records. The first is how to declare a record and second is how to 'use' the record to store data and other processing. Other concepts and techniques which uses records is more on data processing and just requires the values from the record to be used for calculations or other processes.
Records are also very flexible in a sense that you can use them to store almost any data you want and in a structure which suits you most. Records make programs easier to read too, by identifying several names under one record variable. This is especially helpful if you get confused about which variable belongs to which group of data.
This is the TYPE declaration syntax:
TYPE
RECTYPE = RECORD
field-1 : field-type-1;
field-2 : field-type-2;
.......
.......
field-n : field-type-n;
END; { of record }
and, of course, the type has to be used in the variable declaration heading in order for data to be stored. Types cannot store values, remember?
Here is a program which uses records.
program
UseOneRecord;
uses CRT;
type
EmpRecType = record
Name : string;
EmpNum : string[ 5 ];
HoursWorked : real;
HourlyRate : real;
end;
var
EmpRecord : EmpRecType;
begin { main program }
ClrScr;
WriteLn('Please Enter Employee Details.');
Write('Name : ');
ReadLn( EmpRecord.Name );
Write('Employee Number : ');
ReadLn( EmpRecord.EmpNum );
Write('Hours Worked : ');
ReadLn( EmpRecord.HoursWorked );
Write('Hourly Rate : ');
ReadLn( EmpRecord.HourlyRate );
with EmpRecord do
begin
WriteLn('Name : ', Name );
WriteLn('Employee Number : ', EmpNum );
WriteLn('Hours Worked : ', HoursWorked );
WriteLn('Hourly Rate : ', HourlyRate );
end;
ReadLn;
end. { main program }
As you can see, a record type (EMPRECTYPE) is declared in the TYPE section and later used as a type for EMPRECORD. This makes EMPRECORD the record variable which you use to access the fields. The fields declared are NAME, EMPNUM, HOURSWORKED and HOURLYRATE. You can also see that the type of the fields declared in the record are different.
Accessing the records is a different matter. If you notice, you will see that the record variable (EMPRECORD) is used every time the fields are to be accessed. Pascal programs must have the record name along with the field name if a record's field is to be accessed.
This can be done in two ways.
1) Using the full stop (.)
2) Using the WITH..DO statement.
The full stop is used to separate the record name and its fields' name. In the first part of the program, this method is used to access the fields of the record. In all the READLN statements you can see that the record name is specified, followed by the field name (ie. EMPRECORD.NAME, EMPRECORD.EMPNUM, EMPRECORD.HOURLYRATE and EMPRECORD.HOURSWORKED). Usually, this method is used when a programmer wants to access only one or two fields in a record. If more fields are to be accessed the WITH..DO statement is best used.
In the second part of the program, where the details of an employee is being printed, you can see the use of the WITH..DO statement. The WITH..DO statement is used to specify the record name and which fields you wish to access.
WITH recordname DO
Statements;
In the statement body of the WITH..DO, any of the recordname's fields may be accessed along with other declared identifiers. As with most other statement bodies, compound statements must be placed in between a BEGIN..END block. When using WITH..DO, you will notice that you have to specify the record name only once. Unlike the full stop(.), the WITH..DO is used when all or most of the record's fields are to be accessed.
So, let's hope this clarifies the declaration and accessing of records.
Normally, when dealing with records, one record would not be much use to anyone. Imagine a telephone book which can store only one name. Is it much good to anyone? When handling records, there are usually more than one record with which you should work on. There are two ways to store more than one record.
1) Use arrays of records
or 2) Use a file.
The second method will be covered in later lessons. Let us see how we can store more than one record by using arrays of records.
program
ManyRecords;
uses CRT;
type
NameRec = record
Name : string[20];
Address : string[40];
TelNo : string[12];
end;
NameArray = array [ 1..100 ] of NameRec;
var
Names : NameArray;
RecNum : integer;
Choice : char;
procedure GetRecordData ( var OneRec : NameRec );
begin
WriteLn('Enter Friend''s Data:'); WriteLn;
with OneRec do
begin
Write('Enter Name : '); ReadLn( Name );
Write('Address : '); ReadLn( Address );
Write('Telephone : '); ReadLn( TelNo );
end;
end; { GetRecordData }
procedure DisplayRecord ( OneRec : NameRec );
begin
with OneRec do
begin
WriteLn('Name : ', Name );
WriteLn('Address : ', Address );
WriteLn('Telephone : ', TelNo );
end;
end; { DisplayRecord }
procedure AddNewRecord;
var
NewRec : NameRec;
begin
ClrScr;
WriteLn('Add New Record to List.'); WriteLn;
if RecNum = 100 then
WriteLn('List is full. Cannot add more names.')
else begin
GetRecordData( NewRec );
RecNum := RecNum + 1;
Names[ RecNum ] := NewRec;
WriteLn('New record added.');
end;
Write('Press <ENTER>'); ReadLn;
end; { AddNewRecords }
procedure DisplayAllRecords;
var
I : integer;
begin
ClrScr;
for I := 1 to RecNum do
WriteLn( I:4,') ',Names[ I ].Name );
Write('Press <ENTER>'); ReadLn;
end; { DisplayAllNames }
procedure SearchForRecord;
var
Wanted : string;
I : integer;
Found : Boolean;
begin
ClrScr;
WriteLn('Search for a record.'); WriteLn;
Write('Enter Name : '); ReadLn( Wanted );
I := 1; Found := False;
while ( not Found ) and ( I <= RecNum ) do
if Wanted = Names[ I ].Name then
Found := True
else
I := I + 1;
if not Found then
WriteLn('Record is not found.')
else
DisplayRecord( Names[ I ] );
WriteLn;
Write('Press <ENTER>'); ReadLn;
end; { SearchForRecord }
begin { main program }
RecNum := 0;
repeat
ClrScr;
Write('[A]dd / [D]isplay / [S]earch / [Q]uit. Choose : ');
ReadLn( Choice ); Choice := upcase( Choice );
case Choice of
'A' : AddNewRecord;
'D' : DisplayAllRecords;
'S' : SearchForRecord;
else if Choice <> 'Q' then
begin
WriteLn('Wrong Choice !');
Write('Press <ENTER>'); ReadLn;
end;
end; { case Choice }
until Choice = 'Q';
end. { main program }
This program uses an array of 100 records ( array [1..100] of NameRec; ). This means that the array has got 100 different memory locations and each of those locations is of a record type. If this program is improved, it can be made into an electronic telephone book.
There are only 3 functions though. The first being able to add a new record to the list, the second is to display all the records, and the third is to search for a record you require.
Adding to the list of records is easy. Simply increase the counter (RECNUM) by one and this will provide for the subscript to the next available array cell. This also means that the RECNUM variable stores the number of records currently in the list.
In order to display all the records, the DISPLAYALLRECORDS procedure simply uses a FOR..DO loop which executes 1 to RECNUM times, since RECNUM contains the number of records, all the records in the list at the moment will be displayed.
To search for a record, we must first have a key. A record key is the field which is used when access to the record's data is required. In this case, the key field used is NAME. This field is used to compare with the name the user wants (WANTED). If the name is found ( IF Wanted = Names[ I ].Name THEN Found := True ) the flag FOUND is set to TRUE or else ( ELSE I := I + 1) the counter is incremented by 1 in order to access the next record in the list. Each and every record is compared, starting at the first record until the last, until a match is found or until there is no more records to compare.
After the search is done, the Boolean flag is checked ( IF Found THEN ) and if the record is found, the details of the record will be printed (DISPLAYRECORD( Names[ I ]), if not (ELSE) an error message will appear (WRITELN('Record is not found.')). Note that the value of the subscript (I) is very important. If the subscript is of the wrong value, the wrong record's detail will be printed, or the record may never even be found.
Please note the placements of subscripts just after the array name. Subscripts are not placed after the field name because the fields are not arrays. The records are the ones grouped together in an array and therefore the subscripts should be placed after the array name.
You will notice that once the program completes execution, the data in the array is 'lost'. This is because arrays are storage locations in the computer's main memory (RAM) and not on disk. If the program is modified to use a disk file, then the data can be saved.
There are also record constants which you can use to initialize you record contents:
type
NameRec = record
Name : string[20];
Addr : string[40];
Tel : string[12];
end;
const
BlankRec : NameRec = ( Name : ''; Addr : ''; Tel : '' );
var
Names : array [ 1..100 ] of NameRec;
Now, if the program was required to initialize the array of records it would normally look like this:
for I := 1 to 100 do
with Names[ I ] do
begin
Name := '';
Addr := '';
Tel := '';
end;
If you were to use the BLANKREC constant it would be much shorter:
for I := 1 to 100 do
Names[ I ] := BlankRec;
See? Other than initializing, it can also be used to mark a record which is to be deleted.
Records with structured fields.
Other than simple types, the record fields can also consist of structured types. Consider this declaration :
type
DateRec = record
DD, MM, YY : integer;
end;
QArray = array [ 1..10 ] of string[15];
EmpRec = record
Name : string[ 20 ];
Salary : real;
Post : string[ 20 ];
DateEmployed : DateRec;
Qualification : QArray;
end;
var
OneEmployee : EmpRec;
Have you considered how to access all the fields in the ONEEMPLOYEE record? Let's say we want to output all the fields. Our procedure would have to have two WITH..DO because there is a record (DateEmployed : DateRec) within a record (OneEmployee : EmpRec) and also a loop to output the array (Qualification : QArray) in the record.