Rzadko ostatnio pytam, bo większość potrafię sam rozwiązać, a jak nie to Google chętnie pomaga; Tym razem nie tyle nie wiem jak coś zrobić, ale potrzebuję sprawdzenia czy dobrze to robię;
Otóż kończę już powoli moduł z klasami mojego typu plików konfiguracyjnych i pozostała mi jedynie opcja linkowania plików zewnętrznych podczas parsowania;
Metoda parsująca przetwarza zawartość pliku tekstowego (lub binarnego, ale to nieistotne teraz) i jeśli napotka linię deklaracji pliku zewnętrznego - uruchamia procedurę dodającą na liście przeparsowanych plików nową pozycję, po czym tworzy nową instancję klasy parsera i rozpoczyna parsowanie tego nowego pliku - całość działa rekurencyjnie, bo jeśli plik dołączany także posiada deklarację plików dołączanych, dla nich tworzone są kolejne instancje parsera i tak w koło (rekurencyjnie);
Z racji tej, że pasuje sprawdzać jakie pliki zostały już przeparsowane (aby wykluczyć duplikaty powodujące zapętlenie programu) klasa parsera posiadać musi macierz, w której zapisywane są kolejne parsowane pliki; Przed parsowaniem sprawdzane jest czy nowy plik dołączany istnieje już na liście i jeśli tak - rzucony zostaje wyjątek; Jeśli nie - można parsować;
Problem w tym, że wszystkie instancje klas parserów jakie tworzone są rekurencyjnie muszą korzystać z jednej (tej samej) macierzy z nazwami plików, które już zostały przeparsowane; Wymyśliłem więc, że klasa parsera w konstruktorze otrzymywać będzie wartość logiczną (oznaczać ma to czy utwórzyć w pamięci pola czy przepisać adresy z argumentów do pól klasy), otrzymywać będzie także wskaźnik na macierz z nazwami plików (przy pierwszym utworzeniu będzie to nil
) oraz wskaźnik na zmienną z ilością elementów w macierzy (także nil
jeśli to pierwsza klasa);
Wszystko w teorii wygląda dobrze, testowałem takie rozwiązanie w odrębnym programie na testowych klasach i wszysko działa jak należy; Obsługa macierzy dynamicznej za pomocą wskaźnika jest nieco utrudniona, ale jakoś się udało; Martwi mnie jednak to, czy napisany kod ma jakieś wycieki lub inne błędy, które mogą crashować program;
Kod programu testowego poniżej:
program datafrnsh;
{$MODE OBJFPC}{$H+}
type
TLinkedFilesList = array of PAnsiString;
PLinkedFilesList = ^TLinkedFilesList;
type
TLinkedFilesInfoRec = record
FList: PLinkedFilesList;
FCount: PInteger;
FDataFurnished: Boolean;
end;
type
TContainer = class(TObject)
private
FLinkedFilesInfoRec: TLinkedFilesInfoRec;
FSubContainer: TContainer;
public
constructor Create(AFurnishData: Boolean; AList: PLinkedFilesList; ACount: PInteger);
destructor Destroy(); override;
public
procedure AddFileToContainer(AFileName: AnsiString);
procedure AddFileToSubContainer(AFileName: AnsiString);
procedure DeleteFile(AIndex: Integer);
public
procedure ShowFileNames();
end;
constructor TContainer.Create(AFurnishData: Boolean; AList: PLinkedFilesList; ACount: PInteger);
begin
inherited Create();
with FLinkedFilesInfoRec do
begin
FDataFurnished := AFurnishData;
if FDataFurnished then
begin
FList := AList;
FCount := ACount;
FSubContainer := nil;
end
else
begin
New(FList);
SetLength(FList^, 0);
New(FCount);
FCount^ := 0;
FSubContainer := TContainer.Create(True, FList, FCount);
end;
end;
end;
destructor TContainer.Destroy();
begin
with FLinkedFilesInfoRec do
if FDataFurnished then
begin
FList := nil;
FCount := nil;
FSubContainer.Free();
end
else
begin
SetLength(FList^, 0);
Dispose(FList);
Dispose(FCount);
end;
inherited Destroy();
end;
procedure TContainer.AddFileToContainer(AFileName: AnsiString);
var
pstrFileName: PAnsiString;
begin
with FLinkedFilesInfoRec do
begin
SetLength(FList^, FCount^ + 1);
pstrFileName := @FList^[FCount^];
pstrFileName^ := AFileName;
Inc(FCount^);
end;
end;
procedure TContainer.AddFileToSubContainer(AFileName: AnsiString);
begin
FSubContainer.AddFileToContainer(AFileName);
end;
procedure TContainer.DeleteFile(AIndex: Integer);
var
pstrFileName: PAnsiString;
I: Integer;
begin
with FLinkedFilesInfoRec do
if FCount^ > 0 then
begin
pstrFileName := @FList^[AIndex];
pstrFileName^ := '';
for I := AIndex + 1 to FCount^ - 1 do
FList^[I - 1] := FList^[I];
Dec(FCount^);
SetLength(FList^, FCount^);
end;
end;
procedure TContainer.ShowFileNames();
var
pstrFileName: PAnsiString;
strFileName: AnsiString;
I: Integer;
begin
with FLinkedFilesInfoRec do
begin
WriteLn('Count: ', FCount^, #10);
for I := 0 to FCount^ - 1 do
begin
pstrFileName := @FList^[I];
strFileName := pstrFileName^;
WriteLn(strFileName);
end;
WriteLn('-----------'#10);
end;
end;
begin
with TContainer.Create(False, nil, nil) do
try
AddFileToContainer('+ first');
AddFileToContainer('+ second');
AddFileToSubContainer(' + third');
AddFileToSubContainer(' + fourth');
AddFileToContainer('+ fifth');
AddFileToContainer('+ sixth');
ShowFileNames();
DeleteFile(0);
DeleteFile(4);
DeleteFile(2);
ShowFileNames();
finally
Free();
ReadLn;
end;
end.
Wykonanie kodu spowoduje wyświetlenie na ekranie konsoli:
Count: 6
+ first
+ second
+ third
+ fourth
+ fifth
+ sixth
-----------
Count: 3
+ second
+ third
+ fifth
-----------
Pewne zabezpieczenia można jeszcze dodać, ale do testów to co jest wystarczy;
Moje pytanie brzmi: czy powyższy sposób dodawania i usuwania elementów tablicy jest poprawny/optymalny, czy trzeba go jakoś zmienić/zoptymalizować?
Chodzi mi tylko o to samo dodawanie elementów do macierzy i jej czyszczenie - uniknięcie wycieków i innych kłopotów; Podczas działania mechanizmu parsowania elementy nie będą usuwane - po skończeniu parsowania wszystkich plików macierz zostanie wyzerowana i usunięta z pamięci, ale dla testów dodałem i taką metodę, która działa;
Potrzebuję porady, ponieważ jak do tej pory nie operowałem na wskaźniku do dynamicznej macierzy i pomimo tego, że powyższy kod działa - nie mam 100% pewności, że w którymś momencie się nie wywali; Do posta dołączam plik z kodem programu gotowego do skompilowania i przetestowania; Dzięki z góry za pomoc.