Einzelnen Beitrag anzeigen

Der_Unwissende

Registriert seit: 13. Dez 2003
Ort: Berlin
1.756 Beiträge
 
#17

Re: Was macht Free genau in einem Array

  Alt 9. Dez 2005, 14:13
Zitat von s.h.a.r.k:
Also ganz zu Beginn muss ich zu meiner Verteidigung noch sagen, dass dies meine erste Komponente ist, die ich programmiere - habs halt bisher mit ein paar Tutorials zusammenklamüsert...
Ist doch nicht schlimm, kein Grund zur Verteidigung. Sind alles nur Tipps (gelernt aus eigenen Fehlern, von denen es nicht wenige gab und immer noch genügend gibt).

Zitat von s.h.a.r.k:
PS: Was für ganz Dumme: Was heißt "iterieren"
Hab mich wohl mal wieder unnötig kompliziert ausgedrückt

Eigentlich meinte ich gar nicht so spezielle Dinge. Iterieren heißt eigentlich nur, dass du etwas, nun ja, Durchläufst. Hätte auch sagen können, nimm jedes Element aus dem Array, jeder Durchlauf ist dabei eine Iteration.

Zitat von s.h.a.r.k:
Nun gut, über eine "saubere" Syntax kann man sicherlich streiten, aber da hast du sicherlich nicht ganz unrecht. Habe halt bisher immer in diesem Stil programmiert und das von heute auf morgen mal so schnell aufzugeben ist nicht ganz so einfach. Habs Delphi nämlich in der Schule gelernt und der Lehrer hat es uns nun so beigebracht
Was den Stil angeht, so weiß ich dass es da (gerade von Lehrern verbreitet) sehr viele unterschiedliche Ansichten über guten Code-Stil gibt. Leider stimmt der (sogut wie?) nie mit den gegebenen Konventionen überein. Diese sind eigentlich sehr durchdacht und ich glaube ein Großteil der Menschen fängt anders an.
Ich weiß dass es nicht leicht ist sich umzugewöhnen (weswegen ich die Lehrer noch weniger verstehe), aber es ist wirklich ratsam. Hatte selbst mehr als lange alles andere als guten Stil und auch immer wieder kleine Fehler (dadurch). Diese zu finden ist bei besserem Stil wirklich schon deutlich leichter. Zudem musst du dich (wenn du dich an die Konvention hälst) nicht wirklich umgewöhnen, wenn du anderen Code liest. Gerade bei Projekten, die größer sind (und mit mehreren bearbeitet werden) ist es essenziell etwas einheitlicher zu arbeiten. Ich weiß wirklich gut, wie lange es bei mir gedauert hat, bis er mal durchgängig etwas besser wurde, aber du solltest es echt versuchen. Ist aber nur ein Tipp (über Pro und Contra lässt sich streiten und das haben andere schon genug gemacht).

Zitat von s.h.a.r.k:
Zitat:
Unsauber an deinem Programm ist es übrigens, dass du dein Array immer um eine Stelle vergrößerst oder verkleinerst (hatte hier auch schon jmd gesagt, oder?). Das ganze hat in Delphi echt Lustige Effekte, wenn ich mich nicht ganz irre gibt Delphi nämlich nicht den alten Platz frei, reserviert aber auch die neue Größe (alte Länge + 1) und damit hast du einen unnötigen Speicherbedarf.
Hier versteh ich nicht ganz was du damit meinst?! Wie genau sollte ich es denn sonst machen (wenn ich es dennoch mit einem Array probieren will?!
Nun ja, wie gesagt ich kann mich auch irren, aber ich denke Delphi macht bei einem setLength(array, length(array) + 1) folgendes:
Es alloziert neuen Speicher der Größe length(array) + 1, kopiert nicht alle Werte von Array rüber sondern einen Verweis auf das Array und gibt das alte dann auch garnicht frei. Somit hast du nun statt length(Array) + 1 gleich 2 * length(Array) + 1 Speicher belegt.
Aber unabhängig davon, dauert es einfach eine gewisse Zeit, Speicher zu allozieren (allozieren = Speicher reservieren/zuweisen). Diese Zeit ist dabei für 1, 10, 10000 Bytes kaum unterschiedlich. Deshalb lohnt es sich nicht, nur 1 Zelle zu allozieren, dann eine weitere und wieder eine weitere...
Es ist viel perfomanter, gleich Speicher für 100 Zellen zu allozieren (setLength(array, length(array) + 100)) und sich dann zu merken, wieviele eigentlich benutzt werden (im Moment). Du führst dann sozusagen einen Index mit dir rum, der zeigt immer auf die letzte benutzte Zelle (nicht mehr automatisch die letzte des Arrays).
Das setzen dieses Index kostet weniger Zeit als Windows nach mehr Speicher zu fragen, die Daten aus dem Array zu kopieren und das alte Array freizugeben. Nähert sich dann dein Index dem ende des tatsächlichen Arrays, so wird die Größe wieder um 100 erhöht...

Wie gesagt, ich denke Listen machen dass in Delphi intern automatisch (mit guten Werten). An sich solltest du solche Kapselungen immer eher verwenden als Eigenlösungen. Damit will ich dir nicht hier automatisch dazu raten, um Erfahrungen zu sammeln oder aus Spaß oder was weiß ich (nimm's wörtlich), kannst du jederzeit natürlich auch eigene Lösungen verwenden.
Nur sind fertige Klassen halt gut getestet (von jedem Delphi-Benutzer der diese Klasse mal benutzt + Borland mind. vor Auslieferung). Dann fließt dort immer direkt gutes Wissen ein. Hier möchte ich auch keine Lösung schlecht reden, aber ich glaube die Jungs von MS, Borland, etc. machen sich schon ordentlich viele Gedanken, kennen die Windows-API recht gut und Theoretische Informatik (und damit Effiziente Datenstrukturen und Algorithmen) sind denen nicht gerade fremd.
Das alles schließt nicht aus, dass du einen besseren Weg findest, aber die Wahrscheinlichkeit, dass du einen Fehler machst (oder weniger Perfomant arbeitest) ist nicht gering.
Großes Problem bei deiner Lösung wäre halt auch, dass es ein Array ist, das du genau hier brauchst. Beim nächsten Projekt musst du wieder von vorne anfangen. Würdest du nun denken, hey, ein Fibonacci-Heap ist total toll (für's Speichern der TDigital..), dann müsstest du überall umstellen. Dann würdest du merken, hm, nettes Modell aber irgendwie doch nicht das Richtige und würdest z.B. zu einem Binominialheap (sorry, hab's heute wohl mit Heaps) umsteigen, dann wieder in jeder Klasse ändern.
Ändert Borland hingegen die Klasse TObjectList und du verwendest sie, so würde sich für dich nichts ändern (ist ja schließlich eine Kapselung/Black Box, was intern passiert ist dir egal).

Zitat von s.h.a.r.k:
Warum kann ich darauf verzichten?! Ich finds schöner so eine Variable zu haben! und was genau meinst du mit Klassenvariablen initialisieren?!?
Gut, hatte mal einen Kollegen (der nebenbei bemerkt mittlerweile Informatik-Lehrer ist), der hat immer gerne einen Haufen Variablen eingeführt, die man eigentlich nicht gebraucht hätte. Die meisten davon waren dann recht global und wurden irgendwo mal benutzt und an anderen Stellen auch mal nicht. Wenn du nun an solch einem Code arbeitest, passiert es schnell, dass du auf das letzte Element mit Array[length(Array) - 1] zugreifst und dort irgendwas machst (es z.B. löscht), dann hast du das Problem, dass ein solcher Zeiger nicht mehr aktuell ist. Aber ist nur mein persönliches Trauma, wenn du sie schöner findest ist das ok (bin doch auch nur unwissend!)

Mit Klassenvariable initialisieren meine ich, dass FCount im Konstruktor ruhig den Wert 0 zugewiesen bekommen sollte. Man kann jetzt hier sagen, dass das Delphi automatisch macht, aber es ist sehr Borland Delphi spezifisch und damit fehlt dir eigentlich eine Garantie dafür. Bei Referenzen auf Klassen, weiß man, dass die nil sind (Achtung, rede immer noch über Klassenvariablen), bei prim. Typen sollte man ruhig initialisieren.
Bei lokalen Variablen muss man sogar initialisieren (wollte ich nur der Vollständigkeit halber sagen), sonst kommt es schnell zu richtig bösen Überraschungen.

Zitat von s.h.a.r.k:
Was ich grad noch gesehen hab - was machst diese Zeile hier, bzw zu was brauch ich die?
Zitat:
finalize(self.FItems);
Die gibt den reservierten Heap des Array frei.

So, noch mal schnell etwas zu TObjectList, eine ziemlich einfache Komponente. Listen kommen immer dann zum Einsatz, wenn du eine Variable Länge von Elementen hast (gibt auch dann noch bessere Datenstrukturen). Aber eine Liste macht nicht mehr, als sich ein Element zu merken und dazu den Nachfolger. Für den Nachfolger kannst du dann wieder gucken ob er einen Nachfolger hat. Ist das gespeicherte Element nil, ist die Liste leer. Findest du ein Element das keinen Nachfolger hat, ist es das letzte Element (gilt für einfach verkettete Listen). Du kannst dir ganz Analog auch den Vorgänger merken.
Anders als Arrays musst du die Größe nicht kennen, du kannst einfach beim einfügen dem letzten Element seinen Nachfolger anhängen. Wenn du eine Element E1, dass in der Liste zwischen E0 und E2 steht entfernst, so setzt du E2 als Nachfolger von E0 ein, fertig.
Anders als bei Arrays, kannst du aber nicht wahlfrei auf einen Index zugreifen. Du weißt halt nicht, wieviele Elemente deine Liste hat.
Nun ja, ich weiß nicht ob du Listen nicht schon gut kennst (es gibt natürlich auch doppelt verkettete, Vorgänger und Nachfolger bekannt, zyklische Letzter zeigt auf ersten, ...). Wäre aber ein anderes Thema.

In Delphi gibt es auch verschiedene Arten von Listen. Du hast eine TList, die speichert nur Pointer, wirst du eher nicht brauchen. Dann gibt es noch die wichtigeren TObjectList, TComponentList und TControlList (wahrscheinlich auch weitere, andere).
Aus ihren Namen geht schon hervor was sie speichern, TObjectList alles vom Typ TObject (und Nachfahren), TComponentList alles vom Typ TComponent (und Nachfahren), TControlList (na rate mal).

Damit kannst du also ein TControl (und Nachfahren) in jeder dieser Listen speichern, ein TObject aber nur in TObjectList. Einträge fügt man einfach mit einem Add hinzu, mit Delete wird ein Objekt entfernt. Extract entfernt ein Objekt und liefert es dir auch zurück.
Wenn du nun ein Objekt mit Extract entfernst, bekommst du entsprechend der Liste ein TObject, ein TComponent oder ein TControl. Dabei ist es egal was für einen Typen du mal reingesteckt hast, du hast nach dem rausholen (oder auch in der Liste) nur die Eigenschaften des entsprechenden Vorfahren. Du musst also einfach nur casten und hast wieder alle Eigenschaften.

Wichtig ist, diese drei Listen haben auch die Eigenschaft Items, die ist ein Array auf dass du wie gewohnt zugreifen kannst. Hinzufügen oder entfernen solltest du aber unbedingt über die Methoden Add/Delete/Extract, damit die Größe des Arrays auch korrekt angepasst wird.
Hier noch ein kleines Beispiel:

Delphi-Quellcode:
type
  TMyClass = class(TControl)
  ....
  ....
  end;

...

var list : TObjectList;
    i : Integer;
begin
  list := TObjectList.Create;
  
  // 3 neue Instanzen hinzufügen
  list.Add(TMyClass.Create);
  list.Add(TMyClass.Create);
  list.Add(TMyClass.Create);

  // letztes Element löschen
  list.delete(2);

  // alle Elemente einen Namen (name + Nummer) geben
  if list.Count > 0 then
    begin
      for i := 0 to list.Count - 1
        begin
          // TObject hat keinen Namen -> casten
          TMyClass(list.Items[i]).name = 'name' + IntToStr(i);
        end;
    end;

  // Liste freigeben, vorher alle Elmente löschen
  if list.Count > 0 then
    begin
      for i := 0 to list.Count - 1
        begin
          // Free ist Methode von TObject, kein casten nötig
          list.Items[i].Free;
        end;
  list.Free;
end;
Gruß Der Unwissende
  Mit Zitat antworten Zitat