Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Dynamisches Array verschachteln und speichern (https://www.delphipraxis.net/169208-dynamisches-array-verschachteln-und-speichern.html)

flosoft 4. Jul 2012 22:22

Delphi-Version: 2007

Dynamisches Array verschachteln und speichern
 
Hallo,

hatte hier in dem Forum schon mal nach einer Baumstruktur aus Objekten gefragt http://www.delphipraxis.net/168719-b...bjectlist.html.
Die für mich zu lösende Aufgabe hat sich in der Zwischenzeit etwas verändert, so dass ich das Ganze nochmal durch den Fleischwolf gedreht habe:
Rausgekommen ist nun eine Lösung mit zwei Records und zwei dynamischen Arrays :oops:
Ja, mir ist klar das es da bessere/modernere Ansätze gibt... Mir läuft nur gerade die Zeit weg und ich muss die Version 0.0.0.0.0.0.0.1 bald fertig haben. Deshalb kann ich mich leider z.Z. nicht tiefer in die Objektlisten etc. eindenken :cry:
Ausser: Ihr haltet mich mit allen Mitteln davon ab und bringt mich doch wieder zu den Objekten... :drunken:

Meine konkreten Fragen:
1. Spricht etwas gravierendes gegen die folgende Datenstruktur?
Delphi-Quellcode:
type
  TFTerm = record
    id: Integer;
    FVid: Integer;
    Typ: Integer; // 0 = Dreieck, 1 = Trapez ...
    Name: String;
    RangeMin: Double;
    RangeMax: Double;
    Param1: Double;
    Param2: Double;
    Einheit: String;
  end;

  TFzzV = record
    id: Integer;
    Typ: Integer; // 0 = Eingabe und 1 = Ausgabe
    Name: String;
    RangeXLow: Double;
    RangeXHigh: Double;
    Einheit: String;
    FTArray: Array of TFTerm;
end;

var
  FzzV: Array of TFzzV;
2. Speichern/Laden wird man das wohl am Besten mit einem Stream?


Danke für Eure Meinung

himitsu 4. Jul 2012 22:55

AW: Dynamisches Array verschachteln und speichern
 
Dagegen spricht erstmal nix, aber du kannst es vergessen die den TFzzV.Record einfach so in einen Stream oder sonstohin speichern oder kopieren(Move) zu können.

String, dynamische Arrays, Interfaces und teilweise auch Variants sind intern "Pointer", womit man also nicht den Inhalt speichern würde, sondern nur den Zeiger
und wenn man sowas direkt läd/kopiert, dann zerschießt man sich auch die automatische Speicherverwaltung, über welche diese Typen verfügen.


Also jedes Feld einzeln in den speichern oder man geht über die RTTI, bzw. nutz eine Serialisierungs-Lib, welche eventuell ebenfalls mit der RTTI arbeitet.


Der ShortString / String[123] und statische Arrays würden aber auch direkt gehn, da sie komplett im Record liegen, ohne Pointer.

NickelM 4. Jul 2012 23:07

AW: Dynamisches Array verschachteln und speichern
 
Das Ding ist du kannst nicht einfach eine dynamischen Array in einem Stream aller
Delphi-Quellcode:
Stream.WriteBuffer(FzzV[0],SizeOf(TFzzV));
speichern, da :
- Dynamischer Array ist nur ein Pointer(Adresse), die eine Größe von 4 Bytes hat.
- Delphi setzt vor dem Pointer vom dynamischen Array, ist die Größe bei SetLength initalisiert Delphi den Speicher für den Pointer vom Dynamischen Array. Ein Dynamischer Array ist nichts anderes als ein Statischer Array auf den du eine Pointer machst. Du kannst deshalb auch auf einen Dynamsichen Array auf Positionen zugreifen, die auserhalb des gesetzen Wertes liegen, ohne das der Compiler zumindest meckert, weil dahinter ein Statischer Array steckt. Nur beim zugriff, greifst du auf einen Speicher zu, der nicht für den Pointer (Dynamischen Array) resaviert ist.

Also du musst das Konzept in folgende Richtig ändern.
- Du musst die ID, die du im TFTerm hast dazuverwenden, den TFTerm den TFzzV zuordnen zukönnen.

Das würde dan etwa so Aussehen:
Delphi-Quellcode:
type
  TFTerm = record
    id: Integer;
    FVid: Integer;
    Typ: Integer; // 0 = Dreieck, 1 = Trapez ...
    Name: String;
    RangeMin: Double;
    RangeMax: Double;
    Param1: Double;
    Param2: Double;
    Einheit: String;
  end;

  TTermArray = array of TFTerm; //Damit der Compiler nicht meckert

  TFzzV = record
    id: Integer;
    Typ: Integer; // 0 = Eingabe und 1 = Ausgabe
    Name: String;
    RangeXLow: Double;
    RangeXHigh: Double;
    Einheit: String;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;
  FzzV: Array of TFzzV;
  TermArray : TTermArray;

implementation

{$R *.dfm}

//Hilfsfunktion, die dir den Count und ganz leicht eine Array zurückbekommst.
function FindTermsZuID(AId : Integer; out ATermArray : TTermArray) : Integer;
var I,NewIndex : Integer;
begin
  Result := 0;
  for I := Low(TermArray) to High(TermArray) do //So hast du direkt den gesammten Array zum druchsuchen
  if TermArray[I].FVid = AId then //Wenn ID übereinstimmt
  begin
    NewIndex := Result; //Zurzeit ist der Count noch 0, da der Erste Eintrag den Index 0 hat nehmen.
    Inc(Result); //Wollen ja den Count haben
    SetLength(ATermArray,Result); //Länge festlegen
    ATermArray[NewIndex] := TermArray[I]; //Zuweisen, bischen unsicher wegen Speicher aber dazukomme ich noch.
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var FoundTerms : TTermArray;
    TermsCount,I : Integer;
begin
   TermsCount := FindTermsZuID(5000,FoundTerms);
   if TermsCount > 0 then //Falls wir welche gefunden haben, in diem Fall solten es 2 sein
   begin
     for I := 0 to TermsCount -1 do
     begin
       ShowMessage(IntToStr(FoundTerms[I].id));
     end;
   end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SetLength(FzzV,1); //Test array
  FzzV[0].id := 5000; //Als Test
  SetLength(TermArray,2); //Terms als test Festlegen, um den Prinzip zuzeigen.
  TermArray[0].id := 15130; //Term ID, NICHT DIE FzzV ID!!
  TermArray[0].FVid := 5000; //Hier kommt die ID, zu wem der Term gehört.

  TermArray[1].id := 15200; //Term ID, NICHT DIE FzzV ID!!
  TermArray[1].FVid := 5000; //Hier kommt die ID, zu wem der Term gehört.
end;
EDIT : Jemand schneller, egal bin müde und hoffe dass es dafür reicht, was er machen möchte.
JETZT ABER WIRKLICH PENEN MAN MAN MAN!!!!....

Gruß NickelM

flosoft 5. Jul 2012 07:06

AW: Dynamisches Array verschachteln und speichern
 
Guten Morgen,

danke für die Hinweise.
@himitsu und @NickieIM: habe das mit dem Speichern schon fast vermutet.
Gut. Werde mir den Ansatz von NickieIM heute anschauen.

...Ich könnte mir auch eine XML-Datei als Speicherlösung vorstellen.
Array-Listen in XML hört sich doch gut an...:gruebel:

Furtbichler 5. Jul 2012 07:57

AW: Dynamisches Array verschachteln und speichern
 
Herr, lass ordentliche Bezeichnernamen vom Himmel regnen.

Spendiere den Records jeweils eine LoadFromStream und SaveToStream-Methode. In der Methode liest/schreibst Du jedes Feld einzeln.

Bei Strings schreibst Du zuerst die Länge und dann die Zeichen.

Beim Lesen liest Du zuerst die Länge und liest dann die entsprechende Anzahl von Zeichen.

Zum Speichern des Arrays speicherst Du zuerst die Anzahl und rufst dann für jedes Element seine SaveToStream-Methode auf.

Zum Lesen liest Du die Anzahl, erzeugst das Array und rufst dann für jedes Element des Arrays die LoadFromStream-Methode auf.

tgvoelker 5. Jul 2012 09:38

AW: Dynamisches Array verschachteln und speichern
 
Zitat:

Zitat von himitsu (Beitrag 1173611)
aber du kannst es vergessen die den TFzzV.Record einfach so in einen Stream

Code:
type PFzzV=^TFzzV;
     PFTerm=^TFTerm;
var pPFzzV:PFzzV;
    pI:LongInt;
    pPFTerm:PFterm;
...

pFzzV:=Pointer(Cardinal(FzzV)+Index*SizeOf(TFzzV));
Stream.Write(pPFzzV^,SizeOf(TFzzV));
For pI:=0To Length(pPFzzV^.FTArray)-1 Do Begin
  pPFTerm:=Pointer(Cardinal(pPFzzV^.FTArray)+pI*SizeOf(TFTerm));
  Stream.Write(pPFTerm^,SizeOf(TFTerm));
End;

Iwo Asnet 5. Jul 2012 09:45

AW: Dynamisches Array verschachteln und speichern
 
Und der Inhalt? Strings werden nicht korrekt gespeichert, die in dem Record enthaltenen varianten Arrays sowieso nicht.

Mach es so, wie Furtbichler vorgeschlagen hat. Wahlweise XML, aber das wäre mehr Arbeit, aber dafür besser, wenn Daten hinzukommen oder wegfallen.

Beim normalen (binären) Serialisieren würde ich dem Stream eine Versionsnummer spendieren, um etwaige Änderungen in der Datenreihenfolge oder -Aufkommen über die Versionsnummer zu begegnen.

tgvoelker 5. Jul 2012 10:26

AW: Dynamisches Array verschachteln und speichern
 
Zitat:

Zitat von Iwo Asnet (Beitrag 1173640)
Strings werden nicht korrekt gespeichert

wohl wahr. Wären als Shortstring zu implementieren.
Zitat:

Zitat von Iwo Asnet (Beitrag 1173640)
, die in dem Record enthaltenen varianten Arrays sowieso nicht.

Dafür gibt's die For-Schleife.

Iwo Asnet 5. Jul 2012 10:35

AW: Dynamisches Array verschachteln und speichern
 
Zitat:

Zitat von tgvoelker (Beitrag 1173643)
Dafür gibt's die For-Schleife.

Das ursprüngliche Datendesign enthielt verschachtelte Arrays in Records. Durch geeignete Speicherfunktionalität, z.B. durch die von mir beschriebene, muss man davon nicht abweichen.

himitsu 5. Jul 2012 11:25

AW: Dynamisches Array verschachteln und speichern
 
Zitat:

Zitat von tgvoelker (Beitrag 1173643)
Dafür gibt's die For-Schleife.

Gut, aber wie ist es mit dem Auslesen?
Beim Auslesen des records schreibst du einen "defekten" Pointer in den Record, also an die Stelle wo das Array steht.
Sobald du dann versuchst mit SetLength dem array seine größe zuzuweisen, weil du nun ja auch noch dieses befüllen willst, knallt es ganz furchtbar.
OK, man könnte jetzt das Speichermanagement mit wilden Pointeroperationen umgehen und dort diesen internen Pointer anpassen, aber dann doch lieber die Felder einzeln auslesen ... jedenfalls für die Personen, welche keine Ahnung davon haben wie z.b. die dynamischen arrays intern verwaltet werden.

tgvoelker 5. Jul 2012 11:29

AW: Dynamisches Array verschachteln und speichern
 
Zitat:

Zitat von Iwo Asnet (Beitrag 1173644)
Zitat:

Zitat von tgvoelker (Beitrag 1173643)
Dafür gibt's die For-Schleife.

Das ursprüngliche Datendesign enthielt verschachtelte Arrays in Records. Durch geeignete Speicherfunktionalität, z.B. durch die von mir beschriebene, muss man davon nicht abweichen.

Das impliziert zweierlei: erstens, daß der Algorithmus sich nicht auf "verschachtelte Arrays" anwenden ließe (was nicht stimmt, weil genau diese Verschachtelung durch die For-Schleife behandelt wird), und zweitens, daß der Algorithmus nicht geeignet wäre.

tgvoelker 5. Jul 2012 23:27

AW: Dynamisches Array verschachteln und speichern
 
Zitat:

Zitat von himitsu (Beitrag 1173649)
Zitat:

Zitat von tgvoelker (Beitrag 1173643)
Dafür gibt's die For-Schleife.

Gut, aber wie ist es mit dem Auslesen?
Beim Auslesen des records schreibst du einen "defekten" Pointer in den Record

Ja, das Auslesen ist schon etwas kniffliger, aber nicht wegen dem Zeiger, der wird einfach auf $00000000 gesetzt und dann die Länge des Arrays iterativ erhöht, sondern, weil man die nacheinander abgespeicherten Records unterscheiden muß. Das Kriterium dafür würde ich bei dem (wie ich vorhin schrieb, anstelle Stirng) Shortstring setzen, wobei da natürlich geprüft werden müßte, ob die Zeichen im String überhaupt zulässige Zeichen sind. Wahrscheinlich hätte ich im Rahmen der Implementierung die Records geändert und einen Strukturidentifikator eingefügt an die erste Stelle, das würde die Sache deutlich einfacher machen.

NickelM 6. Jul 2012 01:54

AW: Dynamisches Array verschachteln und speichern
 
An die Strings hatte ich auch garnicht mehr gedacht. :oops: War spät :-D
Wie schon erwähnt wurde im anderen Thread, eine Klasse von TObject mit eigener SaveToStream/File und LoadFromStream/File Methode wäre das Beste. Ich hab eine Record gespeichert der nur Typen wie Integer,Cardinal usw. enthielt. Wo die wichtigsten Sachen drin waren. Wenn ich einen String benötigt hatte, hab ich, wenns ging die Länge in den Record mit aufgenohmen. Natürlich sollte man packed records verwenden find ich, wenn man mit Dateien arbeitet.
Man wird find ich nicht wirklich darum herum kommen entweder feste Stringlängen alla
Delphi-Quellcode:
Text : String[10];
zumachen und damit abfinden, das leere Zeichen in der Datei liegen oder man schreibt wie erwähnt alle Typen per Hand, was man bei einer Klasse sowieso machen müsste.
Racord zuspeichern macht nur Sinn, wenn man "feste" Daten, also Daten die immer die gleiche Länge hat speichern möchte. Für dynamische Sachen wie Arrays, Strings muss man halt in eine Extra varible Schreiben.

Kurz noch zum Abschluss, wie schon erwähnt wurde mach dir ne Klasse und schreib ne SaveToStream/File und LoadFromStream/File Methode, die alle Werte einzeln reinschreibt.
Wäre das beste. Oder wenn du bei denen Records bleiben willst, musste du jede Variable im Record einzeln Schreiben, und beim Lesen in den Record eintragen.

Gruß NickelM


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:51 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz