The contained data may have different datatypes of data.
A simple record can be defined like below :
//simple record
type
TPerson = record
Name : string;
Surname : string;
Age : integer;
end;
A person in this case has a name a surname and an age. Person is the container of the three attributes.
Records are fixed in size.
Now in order to access in a variable record we can do it with two methods (using a dot or the keyword with) shown both below :
procedure TForm1.Button1Click(Sender: TObject);
var John, Nick : TPerson;
begin
//first way of access
John.Name:='John';
John.Surname:='Black';
John.Age:=54;
PrintPerson(John);
//second way of access
with Nick do begin
Name:='Nick';
Surname:='TheGreek';
Age:=50;
end;
PrintPerson(Nick);
end;
The PrintPerson is a procedure like this :
procedure PrintPerson(P:TPerson);
begin
Form1.Memo1.Lines.Add('Name: ' + P.Name);
Form1.Memo1.Lines.Add('Surname: ' + P.Surname);
Form1.Memo1.Lines.Add('Age: ' + IntToStr(P.Age));
Form1.Memo1.Lines.Add('----------------');
end;
A record can hold also enumerated datatypes
//enumerations
type
TSpecies = ( sDog, sCat, sHorse, sElephant );
//record with enumeration
type
TAnimal = record
Species : TSpecies;
Color : TColor;
Name : string;
Age : integer;
end;
Notice that TColor is a datatype defined at Graphics unit. TColor is a RGB (3 x 8bit) datatype. In TColor alpha channel (transparency) is not included.
The value $FF0000 represents pure blue(B), $00FF00 is pure green(G), and $0000FF is pure red(R).
procedure TForm1.Button1Click(Sender: TObject);
var
Dambo : TAnimal;
begin
with Dambo do begin
Species := sElephant;
Color := clGray; //same as : $808080
Name := 'Dambo';
Age := 2;
end;
PrintAnimal(Dambo);
end;
Again the PrintAnimal procedure is shown below :
procedure PrintAnimal(A:TAnimal);
begin
Form1.Memo1.Lines.Add('Species: ' + GetEnumName(TypeInfo(TSpecies), Ord(A.Species)) );
Form1.Memo1.Lines.Add('Color: ' + IntToHex(A.Color,8));
Form1.Memo1.Lines.Add('Name: ' + A.name);
Form1.Memo1.Lines.Add('Age: ' + IntToStr(A.age));
Form1.Memo1.Lines.Add('----------------');
end;
A record can also contain another record or records. A domestic animal for example is an animal with an owner (person).
//record of record(s)
//a domestic animal is an animal that has an owner of type TPerson
type
TDomesticAnimal = record
Animal : TAnimal;
Owner : TPerson;
PedigreeNumber : integer;
end;
A domestic animal may have a "new" TPerson (Dada example) or an existing TPerson (Milo example)
procedure TForm1.Button1Click(Sender: TObject);
var John : TPerson;
Dada, Milo : TDomesticAnimal;
begin
with Dada do begin
Animal.Species:=sDog;
Animal.Color:=clWhite;
Animal.Name:='Dada';
Animal.Age:=4;
Owner.name:='Mario';
Owner.surname:='Rossi';
Owner.age:=19;
PedigreeNumber:=1254;
end;
PrintDomesticAnimal(Dada);
John.Name:='John';
John.Surname:='Black';
John.Age:=54;
with Milo do begin
Animal.Species:=sDog;
Animal.Color:=clBlack;
Animal.Name:='Milo';
Animal.Age:=9;
Owner:=John;
PedigreeNumber:=1255;
end;
PrintDomesticAnimal(Milo);
Again the PrintDomesticAnimal procedure this time is shown below :
procedure PrintDomesticAnimal(DA:TDomesticAnimal);
begin
Form1.Memo1.Lines.Add('Animal Species: ' +
GetEnumName(TypeInfo(TSpecies), Ord(DA.Animal.Species)) );
Form1.Memo1.Lines.Add('Animal Color: ' + IntToHex(DA.Animal.Color,8));
Form1.Memo1.Lines.Add('Animal Name: ' + DA.Animal.name);
Form1.Memo1.Lines.Add('Animal Age: ' + IntToStr(DA.Animal.age));
Form1.Memo1.Lines.Add('Animal Pedigree: ' + IntToStr(DA.PedigreeNumber));
Form1.Memo1.Lines.Add('Owner Name: ' + DA.Owner.name);
Form1.Memo1.Lines.Add('Owner Surname: ' + DA.Owner.surname);
Form1.Memo1.Lines.Add('Owner Age: ' + IntToStr(DA.Owner.age));
Form1.Memo1.Lines.Add('----------------');
end;
Records are stored in the stack memory. What does this means ?
Stack is a region of computer memory that stores temporary variables created by each procedure and function.
In a more technical way is a LIFO (last in first out) order pile.
Every time a function declares a new local variable, it is "pushed" (added) onto the stack.
The last element can be also removed (pop).
So all the local variables seen till now are located in the stack.
Notice that global variables are stored in the global memory and not in the stack memory.
The global memory is reserved by your application when the program starts and remains allocated until your program ends.
More about memory allocation we will see in a later article.
For now keep in mind that local variables allocate memory from the stack.
Pascal aligns record fields on natural boundaries to improve performance.
Normally records have their elements aligned to multiples of (bytes) :
1 for byte
2 for word, smallint
4 for longword, integer, cardinal, single
8 for qword, Int64, double
These alignments [padding] ensure optimal access performance.
For example :
// Declare a simple (unpacked) record
type
TSimpleRecord = Record
ByteOne: Byte; //1 byte; Total=1 [multiple of 1] -> Total=1
ByteTwo: Byte; //1 byte; Total=2 [multiple of 1] -> Total=2
ByteThree: Byte;//1 byte; Total=3 [multiple of 1] -> Total=3
ByteFour: Byte; //1 byte; Total=4 [multiple of 1] -> Total=4
ByteFive: Byte; //1 byte; Total=5 [multiple of 1] -> Total=5
WordOne: Word; //2 bytes; Total=7 [multiple of 2] -> Total=8 [padded]
IntOne: Integer;//4 bytes; Total=12 [multiple of 4] -> Total=12
RealOne: double;//8 bytes; Total=20 [multiple of 8] -> Total=24 [padded]
end;
In order to find the storage byte size of a type or variable we can use the function SizeOf. Notice how the SimpleRecord allocates 24 bytes of stack memory instead of 19 bytes.
This fact is called padding alignment.
procedure TForm1.Button1Click(Sender: TObject);
var SimpleRecord : TSimpleRecord;
begin
Form1.Memo1.Lines.Add('Default record size = '+IntToStr(SizeOf(SimpleRecord)));
//this prints 24
end;
Packed records are records with no alignment padding. The Packed keyword compress the data into the smallest byte storage but lowers the performance.
For example :
{
The Packed keyword minimises the storage taken up by the defined record.
The default padding is removed.
The Packed keyword compress the data into the smallest byte storage but lowers the performance.
}
// Declare a packed record
type
TPackedRecord = Packed Record
ByteOne: Byte; //1 byte; Total=1
ByteTwo: Byte; //1 byte; Total=2
ByteThree: Byte;//1 byte; Total=3
ByteFour: Byte; //1 byte; Total=4
ByteFive: Byte; //1 byte; Total=5
WordOne: Word; //2 bytes; Total=7
IntOne: Integer;//4 bytes; Total=11
RealOne: double;//8 bytes; Total=19
end;
Now :
procedure TForm1.Button1Click(Sender: TObject);
var PackedRecord : TPackedRecord;
begin
Form1.Memo1.Lines.Add('Packed record size = '+IntToStr(SizeOf(PackedRecord)));
//this prints 19
end;
Bitpacked records is like packed records but in
this case, the compiler will attempt to align ordinal types on bit boundaries. Bitpacked records are excellent for rappresenting a bit to bit byte contruction.
We can define the follow subrange :
type
TBit = 0 .. 1; //subrange
Now we can now define Bitpacked records like below :
type
TEightBitByte = Bitpacked record //if it was a packed record it would be 8 bytes !
Bit0 : TBit; //1 byte -> but can be represented by 1 bit since it is a bitpacked record
Bit1 : TBit;
Bit2 : TBit;
Bit3 : TBit;
Bit4 : TBit;
Bit5 : TBit;
Bit6 : TBit;
Bit7 : TBit;
end;
type
THalfByte = Bitpacked record //if it was packed record it would be 4 bytes !
//Half byte is also called nibble
Bit0 : TBit; //1 byte -> can be represented by 1 bit
Bit1 : TBit;
Bit2 : TBit;
Bit3 : TBit; //Total 4 bits -> 1 byte with padding
end;
In order to measure the allocated memory in bits of this datatype we are using the BitSizeOf function.
procedure TForm1.Button1Click(Sender: TObject);
var
EightBitByte : TEightBitByte;
HalfByte : THalfByte;
begin
Form1.Memo1.Lines.Add('The size in bits of EightBitByte = ' +
IntToStr(BitSizeOf(EightBitByte)) +
', in bytes = ' + IntToStr(SizeOf(EightBitByte)));
Form1.Memo1.Lines.Add('The size in bits of Bit0 = ' +
IntToStr(BitSizeOf(EightBitByte.Bit0)));
EightBitByte.Bit0:=1;
Form1.Memo1.Lines.Add('EightBitByte.Bit0 = ' + IntToStr(EightBitByte.Bit0));
Form1.Memo1.Lines.Add('The size in bits of HalfByte = ' +
IntToStr(BitSizeOf(HalfByte)) +
', in bytes = ' + IntToStr(SizeOf(HalfByte)));
end;
Lastly we can define variant records. Variant records are useful when some fields of the record definition may vary.
The variant part (case) must be last in the record.
Below we can see two examples. In the first case we are using a simple chase of chars.
In the second example we are using a case of TShapeType enumerations defined before
//enumerations
type
TShapeType = ( stSquare, stRectangle, stCircle );
//Variant Record . A variant records can also be a packed record.
type
TShape = record
name : string;
color: TColor;
//from here and below you cannot use dynamic types like strings
//since inside variant records the compiler would not know which
//of the tagged cases it should initialize/finalize.
case typeFirstLetter : char of
'S': (side : integer); //square
'R': (x,y: integer); //rectangle
'C': (radius : integer); //circle
end;
type
TShapeBetter = record
name : string;
color: TColor;
case ShapeType : TShapeType of
stSquare: (side : integer);
stRectangle: (x,y: integer);
stCircle: (radius : integer);
end;
We can access the defined variant record like a normal record :
procedure TForm1.Button1Click(Sender: TObject);
var
ShapeBetter : TShapeBetter;
begin
ShapeBetter.name:='Large square';
ShapeBetter.color:=clBlue;
ShapeBetter.ShapeType:=stSquare;
ShapeBetter.side:=40;
Form1.Memo1.Lines.Add(ShapeBetter.name + ' with size: ' + IntToStr(ShapeBetter.side));
end;
That's all for now. In the next tutorial we will talk about arrays. This demo example can be downloaded from here
Part 1: Introduction - Part 2: Hello World - Part 3: Data types, constants and variables - Part 4: Operators - Part 5: Decisions and Loops - Part 6: Procedures and Functions - Part 7: Custom datatypes - Part 8: Enumerations, subranges and sets - [[ Part 9: Records ]]