Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Wie eigenen Record definieren und verwenden? (https://www.delphipraxis.net/106731-wie-eigenen-record-definieren-und-verwenden.html)

TonyMontana 15. Jan 2008 17:52


Wie eigenen Record definieren und verwenden?
 
Also, ich habe eine Frage zu folgender Definition meines Records:

Delphi-Quellcode:
type
  TMyRec = record
  Value : Integer;
  Text : String;
  StringList : TStringList;
end;
So habe ich den erstmal definiert. Ich dachte jetzt, ich kann einfach eine Variable folgendermaßen deklarieren und dann benutzen:

Delphi-Quellcode:
var
 MyRec: TMyRec;
Dann habe ich einfach mal den Feldern Werte zugewiesen, so:

Delphi-Quellcode:
begin
 With MyRec do begin
  Value := 0;
  Text := 'bla';
  StringList.Text := ListBox.Items.Text //<-- Fehler
 end;
end;
An markierter Stelle erhalte ich dann eine Zugriffsverletzung...
Wer kann mir erklären, wie ich den Record profi-mäßig benutze :mrgreen:
Ich vermute mal, ich muss wohl noch Speicher reservieren?!
Dabei sah das in der Delphi-Hilfe so aus, als ob man einfach nur die Variable deklarieren muss und Delphi den rest erledigt ...
Ich bekomme ja auch erst die Zugriffsverletzung bei der StringList, ist das ein unzulässiges Feld???
Oder muss ich irgendwie sowas verwenden?:

Delphi-Quellcode:
 
 MyRec.StringList := TStringList.Create;
 ...
 MyRec.StringList.Free;
Ist halt ne eigene Klasse, das TStringList... Aber ich glaube, mit Constructor und Destructor ging es auch nicht ohne schwere Fehler.

Ich bin aber auch ein Anfänger :oops:

dominikkv 15. Jan 2008 17:59

Re: Wie eigenen Record definieren und verwenden?
 
Zitat:

Zitat von TonyMontana
Ich vermute mal, ich muss wohl noch Speicher reservieren?!

jup
Zitat:

Zitat von TonyMontana
Dabei sah das in der Delphi-Hilfe so aus, als ob man einfach nur die Variable deklarieren muss und Delphi den rest erledigt ...

nope
Zitat:

Zitat von TonyMontana
Ich bekomme ja auch erst die Zugriffsverletzung bei der StringList, ist das ein unzulässiges Feld???

Zitat:

Zitat von TonyMontana
Oder muss ich irgendwie sowas verwenden?:
Delphi-Quellcode:
 
 MyRec.StringList := TStringList.Create;
 ...
 MyRec.StringList.Free;

jo

Klaus01 15. Jan 2008 18:00

Re: Wie eigenen Record definieren und verwenden?
 
Guten Abend,

wenn Du die StringList im Record benutzen willst,
solltest Du sie auch vorher erstellen.

Delphi-Quellcode:
MyRec.StringList := TStringList.create;
Grüße
Klaus

sirius 15. Jan 2008 18:07

Re: Wie eigenen Record definieren und verwenden?
 
Und du musst sie auch selber wieder löschen.

TonyMontana 15. Jan 2008 18:16

Re: Wie eigenen Record definieren und verwenden?
 
Ok funktioniert jetzt ohne Fehler :thumb:

Hatte ich wohl überlesen in der Delphi Hilfe, dass man für Felder, die Instanzen von Klassen sind Speicher reservieren muss...

Aber dann ist doch das Feld an sich nicht _unzulässig_, sondern erfordert nur eine Speicherreservierung, die man selbst vornehmen muss, oder? Immerhin wird es ja kompiliert.

Ist noch alles etwas undurchsichtig für mich, wann ich Speicher reservieren muss und wann das Delphi selbst erledigt, gerade auch bei Strings und so...

Kann mir jemand verraten, wo man das -verständlich- nachlesen kann? Ich finde die Delphi Hilfe nicht immer optimal muss ich sagen...

Ach ja, falls ich die StringList öfter mal benutzen möchte, wäre es ja blödsinn, für diese ständig Speicher zu reservieren und freizugeben. Wo packe ich dann am besten das
Delphi-Quellcode:
MyRec.StringList := TStringList.Create;
und
Delphi-Quellcode:
MyRec.StringList.Free;
hin? Das Erste am besten im OnCreate Ereignis des Formulars und das Zweite wohl am besten im OnDestroy Ereignis des Formulars, oder?

Danke schonmal für die schnellen Antworten. :thumb:

DeddyH 15. Jan 2008 18:18

Re: Wie eigenen Record definieren und verwenden?
 
Wenn Du mit Instanzen von Klassen arbeiten willst, solltest Du statt eines Records eine eigene Klasse definieren, die sich um die einzelnen Instanzen kümmert.

TonyMontana 15. Jan 2008 18:34

Re: Wie eigenen Record definieren und verwenden?
 
Ok, würde ich gerne. Leider bin ich noch nicht so fit in diesen Sachen. Wie sähe das dann ungefähr aus?

Christian Seehase 15. Jan 2008 18:57

Re: Wie eigenen Record definieren und verwenden?
 
Moin Tony,

Zitat:

Zitat von TonyMontana
Hatte ich wohl überlesen in der Delphi Hilfe, dass man für Felder, die Instanzen von Klassen sind Speicher reservieren muss...

Ich fürchte, ich muss Deine Verwirrung erst noch einmal etwas steigern. ;-)

Die Felder sind keine Instanzen von Klassen, sondern können Zeiger auf Instanzen von Klassen enthalten.
Das mit dem Zeiger ist jetzt nicht der Knackpunkt, da dies eh der normale Weg ist auf Instanzen zuzugreifen.
Wichtiger ist, dass man nicht den Speicher für die Felder, sondern für die Instanzen der Klassen reservieren muss.
Man kann diesen Feldern ja schliesslich auch die Adressen von Instanzen zuweisen, die man auch noch ganz woanders erzeugt, und gespeichert hat.

Ein Beispiel:
Ich habe eine TObjectList, in der ich eine Reihe von Objekten gespeichert habe.
Nun möchte ich diese gerne in einer Liste ausgeben.
Um den Zugriff zu erleichtern übergebe ich nun jedes Objekt, der Reihe nach, an eine Variable des gespeicherten Typs, und kann dann mit dieser arbeiten, ohne, dass ich hierfür noch einmal extra Speicher reservieren muss:

Delphi-Quellcode:
TMyEntry = class(TObject)
private
  FsTitel : string
  FsInhalt : string;
public
  property Titel : string read FsTitel write FsTitel;
  property Name : string read FsName write FsName;
end;

TMyList = class(TObjecList)
end;

// Fml soll hier ein Feld vom Typ TMyList sein
// lv soll hier ein TListView sein

var
  me : TMyEntry;
  i : integer;
  li : TListItem;

begin
  for i := 0 to ml.Count-1 do begin
    li := lv.Items.Add;
    // Es wird kein Speicher reserviert, da dies schon früher passiert ist
    // und die Adresse ist in der Objektliste gespeichert
    me := ml[i] as TMyEntry;
    li.Caption := me.Titel;
    li.SubItems.Add(me.Name);
  end;
end;
Zitat:

Zitat von TonyMontana
Aber dann ist doch das Feld an sich nicht _unzulässig_, sondern erfordert nur eine Speicherreservierung, die man selbst vornehmen muss, oder? Immerhin wird es ja kompiliert.

Das Feld selber ist ja auch völlig OK, nur enthält es nichts sinnvolles, aber das interessiert den Compiler nicht ;-)

Zitat:

Zitat von TonyMontana
Ist noch alles etwas undurchsichtig für mich, wann ich Speicher reservieren muss und wann das Delphi selbst erledigt, gerade auch bei Strings und so...

Ganz genau genommen, reservierst Du auch bei Strings den Speicher selber, nur fällt das nicht so auf, wie bei Objekten.
Wenn Du, z.B., aus einem TFileStream lesen willst, so kannst Du hierfür einen String nehmen:

Delphi-Quellcode:
var
  fs  : TFileStream;
  sBuf : string;

begin
  fs := TFileStream.Create('Pfad zur Datei',fmOpenRead);
  try
    // Und hier knallt es, weil eben kein Speicher für den String reserviert wurde.
    fs.Read(sBuf[1],fs.Size);
    // was man so lösen kann:
    SetLength(sBuf,fs.Size);
    fs.Read(sBuf[1],fs.Size);
  finally
    fs.Free;
  end;
end;
Grundsätzlich kannst Du sagen:
Wenn Du mit einem Zeiger arbeiten willst, muss der vorher etwas sinnvolles enthalten.
Objekte sind, auch wenn die Compilermagic das verschleiert, nichts weiter als Zeiger.

(Es gibt Ausnahmen, da es Funktionen gibt, die die Speicherreservierung selbständig machen, aber das ist dann bei denen auch meist dokumentiert, da man, i.d.R., den Speicher dann trotzdem selber freigeben muss.)

Zitat:

Zitat von TonyMontana
Kann mir jemand verraten, wo man das -verständlich- nachlesen kann? Ich finde die Delphi Hilfe nicht immer optimal muss ich sagen...

Ich hoffe es ist verständlicher geworden, wenn nicht, frag' einfach noch mal nach.

Zitat:

Zitat von TonyMontana
Das Erste am besten im OnCreate Ereignis des Formulars und das Zweite wohl am besten im OnDestroy Ereignis des Formulars, oder?

Das wäre eine gute Position.
Für die Stelle an der man ein Objekt instanziert, bzw. wieder freigibt, gibt es allerdings kein Patentrezept.
Grundsätzlich sollte man es so spät wie möglich erzeugen, und so früh wie möglich wieder freigeben.
(das gilt für alle Resourcen, z.b. auch Handles)

TonyMontana 15. Jan 2008 20:48

Re: Wie eigenen Record definieren und verwenden?
 
Zitat:

Zitat von DeddyH
Wenn Du mit Instanzen von Klassen arbeiten willst, solltest Du statt eines Records eine eigene Klasse definieren, die sich um die einzelnen Instanzen kümmert.

Was meinst du damit, dass sich die Klasse um die einzelnen Instanzen kümmert? Bedeutet das, dass die Klasse dann automatisch Speicher reserviert und freigibt oder muss ich dann in dieser neuen Klasse TMyRec, abgeleitet von TObject, dann noch den Constructor und Destructor überschreiben?

Bisher sah das ganze ja so aus:
Delphi-Quellcode:
type
  TMyRec = record
  Value : Integer;
  Text : String;
  StringList : TStringList;
end;
Wie sähe dann eine Klasse aus, die oben stehende Felder bereitstellt?

Das ist warscheinlich kompletter unsinn: :mrgreen:
Delphi-Quellcode:
type
  TMyRec = class(TObject)
private
  mrValue: Integer;
  mrText: String;
  mrStringList: TStringList;
public
  property Value: Integer read mrValue write mrValue;
  property Text: string read mrText write mrText;
  constructor Create;
  destructor Destroy;
end;

implementation

constructor TMyRec.Create;
begin
 inherited;
 // mrStringList Speicher reservieren?
end;

destructor TMyRec.Destroy;
begin
// mrStringList Speicher freigeben
inherited;
end;
Property für mrStringList fehlt, ich habe mal angenommen man setzt sowas wie mr vor die Felder um die zugehörigkeit zu verdeutlichen, mr steht für MyRec...
Wie reserviere ich den Speicher jetzt im Constructor bzw. gebe ihn im Destructor wieder frei?

Oder ist das nicht möglich oder empfehlenswert?

Ich bin ratlos. :cry:

Edit: Das mit der Compilermagic habe ich irgednwie nicht verstanden, was ist das? Also im Prinzip meinst du damit, dass ich nicht merke, dass ich mit Zeigern arbeite, oder?
Ist dann also jedes Feld eines Objekts ein Zeiger? Warum muss ich dann für den String und den Integer keinen Speicher reservieren?
Ich meine, beim Integer kann ich mir denken, dass automatisch speicher reserviert werden kann, ein integer hat ja eine feste größe aber die Strings können doch soweit ich weiß fast beliebig groß sein.

Progman 15. Jan 2008 22:04

Re: Wie eigenen Record definieren und verwenden?
 
Delphi-Quellcode:
constructor TMyRec.Create;
begin
inherited;
 mrStringList:=TStringList.Create;
end;

destructor TMyRec.Destroy;
begin
 mrStringList.Free;
inherited;
end;
So einfach ;)

dominikkv 15. Jan 2008 22:05

Re: Wie eigenen Record definieren und verwenden?
 
Delphi-Quellcode:
type
  TMyRec = class(TObject)
  private
    FValue: Integer;
    FText: String;
    FStringList: TStringList;
    function getList(i: Integer): string;
    procedure SetList(i: Integer; const Value: string);
  public
    property Value: Integer read FValue write FValue;
    property Text: string read FText write FText;
    property SringList[i: Integer]: string read getList write SetList;
    constructor Create;
    destructor Destroy;
end;





constructor TMyRec.Create;
begin
  inherited;
  FStringList := TStringList.Create;
end;

destructor TMyRec.Destroy;
begin
  FreeAndNil(FStringList);
  inherited;
end;


function TMyRec.getList(i: Integer): string;
begin
  if I < FStringList.Count then
    Result := FStringList[i] else
    Result := '';
end;

procedure TMyRec.SetList(i: Integer; const Value: string);
begin
  if i < FStringList.Count then
    FStringList[i] := Value else
    FStringList.Add(Value);
end;
beim integer macht das der compiler für dich.

Christian Seehase 15. Jan 2008 23:08

Re: Wie eigenen Record definieren und verwenden?
 
Moin Tony,

nicht jedes Feld in einem Objekt ist ein Zeiger, sondern jede Variable, die vom Typ einer, beliebigen, Klasse ist, ist ein Zeiger.

Beispielsweise hast Du in vielen Programmen ein Formular.
Als Standard wäre das Form1, vom Typ TForm1, wobei Form1 die Adresse der Instanz von TForm1 enthält, nachdem das Formular erzeugt wurde. Form1 ist also ein Zeiger, der "nur" auf die Instanz der Klasse TForm1 verweist.
Mit Hilfe von SizeOf kannst Du Dir anzeigen lassen, wieviel Platz eine Variable belegt. Bei Form1 wäre das Ergebnis 4, da ein Zeiger bei einem 32-Bit Compiler eben 4 Byte gross ist. Es spielt dabei also keine Rolle, wieviele Komponenten, Felder oder sonstige Daten das Formular enthält. Das meinte ich mit der Compilermagic. Man kann Form1 direkt verwenden, ohne sich darum kümmern zu müssen, dass es sich eigentlich um einen Zeiger handel. Der Compiler "weiss" bei einer Variablen, die eine Klasse als Typ hat, dass es sich um einen Zeiger handelt, und man muss sich nicht darum kümmern, wenn man, z.B., auf Felder zugreifen will. (Stichwort: Dereferenzieren, Symbol: ^)

Bei einem String handelt es sich zwar nicht um ein Objekt, aber eine Stringvariable liefert bei SizeOf auch immer 4 zurück, da es sich auch hier um einen Zeiger handelt, nur übernimmt hier die interne Speicherverwaltung das Reservieren und Freigeben des erforderlichen Speichers (die ShortStrings lasse ich hier einmal aussen vor).
Wenn man die Adresse, die eine Stringvariable enthält als Nullpunkt ansieht, "weiss" die Speicherverwaltung, dass sich an der Adresse minus 4 die Länge, und an der Adresse -8 ein Referenzzähler für den String befindet, und an der Adresse selber der eigentliche Inhalt des Strings beginnt.
Die Speicherverwaltung sorgt auch dafür, dass ein solcher String immer mit #00 beendet wird.
So ist es also kein Problem mittels eines einfach Typcasts aus einem String ein PChar zu machen, und diesen so, z.B., direkt in einer API-Funktion zu verwenden, die ja mit den delphitypischen Strings nichts anfangen können.
Da ein String eigentlich auch nur ein Zeiger ist, kann man also auch einen Record, der einen String enthält auch nicht so einfach in einer Datei speichern, sondern muss sich dafür etwas einfallen lassen.

Zeiger braucht man eigentlich immer dann, wenn die Datenmenge, die sich dahinter verbirgt, nicht festliegt.
(es mag zwar auch Klassen geben, bei denen sich die Grösse nicht verändert, da dies in der Masse der Fälle aber nicht der Fall ist, lohnt es sich nicht für diese einen Sonderfall zu generieren.)

TonyMontana 16. Jan 2008 01:06

Re: Wie eigenen Record definieren und verwenden?
 
@dominikkv: Danke für die Mühe! So funktioniert es jetzt ohne Zugriffsverletzungen oder ähnliches :-D

@Christian Seehase: Aha, so langsam wird mir das alles etwas klarer, danke für die gute Erklärung. Da du es angesprochen hast, wie speichere ich einen Record bzw. ein Objekt in eine Datei? Dazu müsste ich ja wissen, wie viel Speicher für das Objekt reserviert worden ist. Und SizeOf liefert ja dann nur die Größe des Pointers selbst, also 4 Bytes...
Muss man dann den Pointer auf die Klasseninstanz irgendwie dereferenzieren um dann mit SizeOf die Größe zu ermitteln? Sonst habe ich nichts brauchbares in der Delphi Hilfe dazu gefunden, ich weiß aber auch nicht genau wonach ich suchen sollte.
Jedenfalls wäre es nicht unpraktisch, wenn ich Objekte einfach speichern und wieder laden könnte.

Bis hierhin schonmal DANKE!

:dp: :bounce2: :bounce1: :bouncing4:

QuickAndDirty 16. Jan 2008 07:35

Re: Wie eigenen Record definieren und verwenden?
 
@TonyMonatna:
Du must ja keine Typisierte Datei nehmen und Dateistreams sin dein Thema um das ch mich später kümmern würde.
Speicher es doch einfach in eine Text Datei.
Delphi-Quellcode:
Procedure Save;
var f:textFile;
Begin
  AssignFile(f,c:\'bla.cfg');
  Rewrite(f); // oder Append

  WriteLN(f,Myrec.text);
   // &c.

  CloseFile(f);
end;
zudem können alle Objekte die von mit der Klasse TPersistent Verwand sind sich selbst in eine Datei speichern und von dort laden.

Lies dir dazu einfach mal die Funktionen durch:
TStringlist.savetofile
TStringlist.loadfromFile

TonyMontana 16. Jan 2008 10:54

Re: Wie eigenen Record definieren und verwenden?
 
@QuickAndDirty: Danke für deinen Vorschlag. Aber mich würde es dennoch mal interessieren, wie ich herausfinden kann, wie viel Speicher ein Objekt belegt und wie ich das ganze dann in einem Rutsch speichern kann, die Klasse kann ja mal sehr viele Felder enthalten und dann ist das manuelle speichern irgendwann sehr umständlich.
Sind Streams dafür geeignet? Ich habe mir auch kurz ein tutorial angesehen zum Thema typisierte Dateien und speichern, dort steht allerdings dabei, dass man nur ShortStrings verwenden kann und zB Integer, damit die Länge eines Datensatzes klar ist. Muss ich dann die Länge eines Dynamischen Objekts selbst berechnen um es speichern zu können?

DeddyH 16. Jan 2008 11:03

Re: Wie eigenen Record definieren und verwenden?
 
Zitat:

Zitat von TonyMontana
Muss ich dann die Länge eines Dynamischen Objekts selbst berechnen um es speichern zu können?

In einer typisierten Datei ist jedes Element genau gleich groß, also wirst Du so nicht weiter kommen.

TonyMontana 16. Jan 2008 11:16

Re: Wie eigenen Record definieren und verwenden?
 
Zitat:

Zitat von DeddyH
In einer typisierten Datei ist jedes Element genau gleich groß, also wirst Du so nicht weiter kommen.

Ja, das ist schon klar. Aber ich möchte ja auch wissen, wie ich ein Objekt speichern kann, von dem ich zur Entwicklungszeit die Größe noch nicht weiß. Es muss doch möglich sein, herauszufinden, wie viel speicher ein Objekt aktuell belegt oder nicht? :gruebel:
Wie gesagt, SizeOf funktioniert laut Christian Seehase nicht, da man dann die Größe des Pointers auf die Klasseninstanz erhält ...
Und die ist nun mal 4 Bytes unabhängig davon, wie viel Speicher von dem Objekt belegt wird.


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:31 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