2011. június 16., csütörtök

How to store records in a TList when their number is unknown until runtime


Problem/Question/Abstract:

How to store records in a TList when their number is unknown until runtime

Answer:

To store a number of records ( probably number unknown until runtime ), one would use a Delphi TList object. TList is basically an array of pointers that grows as needed, up to 16K pointers can be stored in a TList. It will accept anything that even remotely looks like a pointer (a pointer is an address, normally of a bit of data that has been allocated from the heap, and needs 4 bytes to store the address). If you work with dynamically allocated data items you need to take care of releasing this memory to the system heap again if it is no longer needed. It is easy to forget this, especially if the data items are kept in a list. It is thus a good idea to derive a custom list class from TList that takes care of freeing the memory for the items it stores automatically.


type
  TRecord = record { the record type }
    { ... }
  end;
  PRecord = ^TRecord; { pointer type for pointers to TRecords }
  TRecordList = class(TList) { a customized version of TList to hold PRecord pointers }
  private
    procedure SetRecord(index: Integer; Ptr: PRecord);
    function GetRecord(index: Integer): PRecord;
  public
    procedure Clear;
    destructor Destroy; override;
    property Records[i: Integer]: PRecord read GetRecord write SetRecord;
  end;

  {Methods of TRecordList}

procedure TRecordList.SetRecord(index: Integer; Ptr: PRecord);
var
  p: PRecord;
begin
  { get the pointer currently in slot index }
  p := Records[index];
  if p <> Ptr then
  begin
    { if it is different from the one we are asked to put into this slot, check if it is <> Nil. If so, dispose of the memory it points at! }
    if p <> nil then
      Dispose(p);
    { store the passed pointer into the slot }
    Items[index] := Ptr;
  end;
end;

function TRecordList.GetRecord(index: Integer): PRecord;
begin
  { return the pointer in slot index, typecast to PRecord }
  Result := PRecord(Items[index]);
end;

procedure TRecordList.Clear;
var
  i: Integer;
  p: PRecord;
begin
  { dispose of the memory pointed to by all pointers in the list that are not Nil }
  for i := 0 to Pred(Count) do
  begin
    p := Records[i];
    if p <> nil then
      Dispose(p);
  end;
  { call the Clear method inherited from TList to set Count to 0 }
  inherited Clear;
end;

destructor TRecordList.Destroy;
begin
  { clear the list to dispose of any pointers still stored first }
  Clear;
  inherited Destroy;
end;



All we did up to here was declaring types, lets put them to use now. First we need an instance of TRecordList to store pointers to dynamically allocated records in. That may be a field in a form, for example. Code to create and destroy the list has to be added to the forms OnCreate and OnDestroy handlers.


{ in a forms public section: }
RecordList: TRecordList;

{ in the forms OnCreate handler }
RecordList := TRecordList.Create;

{ in the forms OnDestroy handler }
RecordList.Free;


To add a record to the list you use code like this:


var
  Ptr: PRecord; { local variable in a method }

  New(Ptr); { allocate a record on the heap }
  with Ptr^ do
  begin { note the caret to dereference the pointer }
    { put data into the fields of the record }
  end;
  recordIndex := RecordList.Add(Ptr);


You do this sequence for each record you need to store. Each record now resides at a specific slot in the list and you can access it via the index of this slot. Indices start at 0 and run to RecordList.Count-1.

Nincsenek megjegyzések:

Megjegyzés küldése