Entwickler-Ecke
Delphi Language (Object-Pascal) / CLX - Element löschen, Zeiger verbiegen
dr.eichhoernchen - Fr 25.09.09 18:50
Titel: Element löschen, Zeiger verbiegen
Hallo
ich hab ein Problem und zwar löscht mein Prog nicht an der richtigen Stelle
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49:
| type telement = class private inhalt:integer; next: telement; prev: telement; public constructor create(z:integer);virtual; end;
tliste = class private aktuell: telement; anfang: telement; ende: telement; public l:integer; ...
procedure tliste.loeschen(i:integer); var j:integer; begin if l<=1 then begin anfang.free; anfang:=nil; ende:=nil; aktuell:=nil; l:=0; end; if l>1 then begin zeigeranfang; for j:=0 to (i-1) do begin aktuell:=aktuell.next; end; if aktuell=ende then endeloeschen else if aktuell=anfang then anfangloeschen else begin aktuell.prev:=aktuell.next; aktuell.next:=aktuell.prev; aktuell.Free; l:=l-1; end; end; end; |
wenn ich das letzte Element anklicke, dann löscht es und wenn ich das erste Element anklicke, dann löscht es auch richtig. Wenn ich aber mittendrin anklicke, dann schreib es irgend ne Zahl ins Stringgrid..
Es liegt also vielleicht nur an den drei Zeilen
Delphi-Quelltext
1: 2: 3:
| aktuell.prev:=aktuell.next; aktuell.next:=aktuell.prev; aktuell.Free; |
aber wie soll ich die Zeiger denn anders verbiegen?
Ich wäre sehr dankbar um eine schnelle Antwort =)
Moderiert von
Gausi: Delphi-Tags hinzugefügt
anbest - Fr 25.09.09 18:53
Als aller erstes solltest du mal Tags für einen Delphi-Quellcode benutzen. Schreib einfach <span class="inlineSyntax"><span class="codecomment">{PROTECTTAG5ab48e23e0d1cb3ec5f1d34d479a331c}</span></span>
Heiko - Fr 25.09.09 19:57
Hallo und :welcome: in der EE,
zuerst einmal: die 3 Zeilen die den Zeiger verbiegen sind imho korrekt (ich sehe zu mindestens gerade keinen Fehler). Aber ein ganz paar Kommentare zum Quelli habe ich doch ;).
dr.eichhoernchen hat folgendes geschrieben : |
Delphi-Quelltext 1:
| constructor create(z:integer);virtual; | |
Wenn ich mich recht entsinne (arbeite mit Delphi inzwischen seit ~2 Jahren fast gar nicht mehr) ist virtual an der Stelle falsch. Wenn du es implementieren willst dann musst du schon override nehmen, da der constructor und destructor ja schon bei TObject definiert ist (wobei wenn ich mich sogar richtig entsinne: den TObject-Konstruktor musste man nicht überschreiben, aber den TObject-Destructor - schaue einfach mal auf die Hinweise). Und wenn du das Create gar nicht brauchst (also auch keine Methoden direkt am Objekt), nimm ein Record. Spart ein paar CPU-Takte. Da wir gerade bei den Objekten sind: wenn ich mich Recht entsinne musste man auch bei Klassen mit
GetMem(ory) bzw.
FreeMem arbeiten, damit es nicht zu problemen kommt (willkürlicher Inhalt der Objekte). Womöglich liegt bei dir da ein Fehler. (Wie gesagt: bin mir da nimmer sicher)
dr.eichhoernchen hat folgendes geschrieben : |
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7:
| if l<=1 then begin anfang.free; anfang:=nil; ende:=nil; aktuell:=nil; l:=0; end; | |
Das ergibt ein schönes Speicherleck. Du löschst die ganze Liste in dem du das erste Element löschst. Was macste mit den anderen? Die liegen noch fein im RAM rum - bis das Programm beendet wird ;). Und gleich noch etwas: wenn ein ungültiger Wert übergeben wird, z.B. -1, lösche nicht gleich die ganze Liste, sondern gib ne Fehlermeldung mittels
raise zurück. Wenn du unsauberen Code erlaubst, coden die Entwickler auch unsauber. Also sorge lieber dafür das es sauber ist ;).
Es gibt
else ;)
Was macht diese Funktion?
dr.eichhoernchen hat folgendes geschrieben : |
Delphi-Quelltext 1:
| for j:=0 to (i-1) do begin | |
Nur ein kleiner Tipp (ist Geschmackssache): für den Menschen ist es besser lesbar 1 bis i, statt 0 bis i-1, da du auch sagst 1. Element ist das erste Element im Speicher. Falls es deine Spezifikation erlaubt: starte bitte bei Listen bei 0 und nicht bei 1. Ist in der Entwicklung eigentlich allgemein üblich das 0 das erste Element ist und -1 ein ungültiges. Ein gemische aus beiden verkompliziert das ganze nur unnötig (Strings in Delphi sind mit dem StartIndex von 1 eine ausnahme, da historisch bedingt früher das erste Byte die Stringlänge beinhaltete).
Nur ein kleiner Tipp falls du es noch nicht kennst:
dec(l); macht das gleiche wie
l:=l-1 (vor allem bei längeren Variablen er höht es die Übersichtlichkeit - ist aber auch wieder Geschmacksache).
Ansonsten: Fehler sehe ich erst einmal keine
Tryer - Fr 25.09.09 21:43
Delphi-Quelltext
1: 2: 3:
| aktuell.prev.next := aktuell.next; aktuell.next.prev := aktuell.prev; aktuell.Free; |
Macht für mich mehr Sinn.
Grüsse, Dirk
Heiko - Fr 25.09.09 21:49
Tryer hat folgendes geschrieben : |
| Macht für mich mehr Sinn. |
Jo hast Recht - das ändern des Vorgängerzeigers bei dem zu löschenden Element bringt ja nicht viel.
dr.eichhoernchen - Fr 25.09.09 23:43
Vielen lieben Dank für eure schnellen Antworten =)
Aber zu dem "Speicherleck" wegen dem Liste löschen durch das erste Element:
da steht ja if l<=1 also ist die Liste schon leer oder hat nur noch ein Element übrig.
"Zeigeranfang" setzt meinen "aktuell"Zeiger in der Liste einfach auf das erste Element der Liste. Macht Sinn oder? ;) Ich bin kein Profi Programmierer..
soll ich jetzt 0 bis i-1 schreiben oder nicht? Ich habe bei 0 begonnen weil eben die meisten Sachen (sowie stringgrids oder arrays) bei 0 starten.
dec und inc sind mir bekannt, danke noch für den Tip, ich vergess es nur ständig ;)
Danke euch =)
Heiko - Fr 25.09.09 23:59
Arg, hab das l als i interpretiert^^
dr.eichhoernchen hat folgendes geschrieben : |
| Aber zu dem "Speicherleck" wegen dem Liste löschen durch das erste Element: |
Das mit dem Speicherleck bezog sich nur auf .Free. Aber wie ich gerade sehe arbeitest du ja mit
tElement und nicht mit
pElement. Von daher dürfte es doch kein Problem sein (wenn ich mit Zeigern arbeite, dann meistens, aus Gewohnheit, mit der p-Version - von daher meine ersten Bedenken ;) ). Überprüfen kannst du das Problem mit Speicherlecks übrigens ganz einfach: einfach FastMM4 einbinden - solange du aus der Delphi-IDE heraus das Programm startet meldet er sich schon bei Problemen beim beenden ;).
dr.eichhoernchen hat folgendes geschrieben : |
| da steht ja if l<=1 also ist die Liste schon leer oder hat nur noch ein Element übrig. |
Jo hast Recht. Dadurch entsteht aber ein anderes Problem: Zweimal Free auf das gleiche (nicht mehr existierende) Element, falls die Funktion mehrfach aufgerufen wird.
dr.eichhoernchen hat folgendes geschrieben : |
| "Zeigeranfang" setzt meinen "aktuell"Zeiger in der Liste einfach auf das erste Element der Liste. Macht Sinn oder? ;) Ich bin kein Profi Programmierer.. |
Ja klar macht das Sinn. Nur lohnt es sich einen Einzeiler auszulagern? (Und muss aktuell bei dir Bestandteil der Klasse sein oder würde auch eine lokale Variable reichen? [sparrt bissl RAM ;) ]).
dr.eichhoernchen hat folgendes geschrieben : |
| soll ich jetzt 0 bis i-1 schreiben oder nicht? Ich habe bei 0 begonnen weil eben die meisten Sachen (sowie stringgrids oder arrays) bei 0 starten. |
Ne ist schon richitg mit 0. Ich hatte halt das l>1 als i>1 (also index) gelesen, wes wegen ich dachte du fängst bei 1 erst an.
Aber daurch ein anderes Problem: Was passiert wenn du 2 Elemente bei dir einfügst, dann 2 löschst und dann wieder 2 einfügst? Denn wenn dein Start bei nil anfängt und das Ende auch dürfte es dann Probleme geben. Oder hast du es intern als Ring implementiert?
dr.eichhoernchen - Sa 26.09.09 13:04
Wenn ich zwei Elemente einfüge und lösche und wieder einfüge klappt das aber leider klappt das löschen an einer bestimmten Stelle immer noch nicht =(
meine prozedur sieht jetzt so aus :
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
| procedure tliste.loeschen(i:integer); var j:integer; begin if l<=1 then begin anfang.free; anfang:=nil; ende:=nil; aktuell:=nil; l:=0; end; if l>1 then begin zeigeranfang; for j:=0 to (i-1) do begin aktuell:=aktuell.next; end; if aktuell=ende then endeloeschen else if aktuell=anfang then anfangloeschen else begin aktuell.prev.next := aktuell.next; aktuell.next.prev := aktuell.prev; aktuell.Free; dec(l); end; end; end; |
aber vielleicht löscht das ja auch richtig und meine ausgabeprozedur ist falsch, die sieht wie folgt aus (klappt alledings bei allen andren buttons wie neues Element anhängen, an einer bestimmten Stelle einfügen, Ende löschen, Anfang löschen
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
| procedure TForm1.Ausgeben; var i:integer; begin if (not liste.listeleer) then begin stringgrid1.RowCount:=1; i:=0; liste.zeigeranfang; while not Liste.AmEnde do begin stringgrid1.RowCount:=i+2; stringgrid1.cells[0,i]:=inttostr(liste.gibinhalt); Liste.ZeigerWeiter; inc(i); end; stringgrid1.cells[0,i]:=inttostr(liste.gibinhalt); end else stringgrid1.cells[0,0]:=''; end; |
das ist nicht sehr sauber programmiert, ich weiß. aber mir gehts erstmal darum, dass das ganze Ding läuft ;)
Tryer - Sa 26.09.09 16:21
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
| procedure TListe.Loeschen(Index: Integer); var j: Integer; Akt: TElement; begin if FCount <= 1 then begin FreeAndNil(FAnfang); FEnde := nil; FCount := 0; end else begin Akt := FAnfang; for j := 0 to Pred(Index) do Akt := Akt.next;
if FEnde = Akt then FEnde := Akt.prev else Akt.next.prev := Akt.prev;
if FAnfang = Akt then FAnfang := Akt.next else Akt.prev.next := Akt.next;
Akt.Free; Dec(FCount); end; end; |
Ich finde das so besser zu lesen als wenn Du kleine Teile in andere Prozeduren auslagerst. Einen Wert wie "Aktuell" sollte man immer lokal deklarieren, dann hat man nämlich auch die Chance das ganze irgendwann threadsicher zu gestalten - und es wird übersichtlicher wenn man nicht überlegen muss wie denn die Funktion jetzt einen Zeiger verbiegt.
Wenn Du Dich bemühst von vornherein sauber zu programmieren ist die Chance das es mal läuft bedeutend grösser :D
Grüsse, Dirk
dr.eichhoernchen - Sa 26.09.09 16:46
Ich verstehe deinen Quellcode nicht. Keine Ahnung was das alles zu bedeuten hat.Aber trotzdem danke.
Tryer - Sa 26.09.09 16:57
:gruebel: Schade.. was ist daran unverständlich?
Ob es funktioniert weis ich auch nicht, es ist nur hier reingetippt :twisted:
Ich habe die Felder etwas anders benannt, da ich ein "FCount" zum Beispiel passender finde als "l". Das vorangestellte "F" ist mir zur Gewohnheit geworden bei alle privaten Feldern einer Klasse.
Wenn Du überprüfen willst ob es so funktioniert male drei Kästchen auf ein Blatt Papier (unverzichtbar beim programmieren) und die Zeiger prev und next als Pfeile dazwischen. Dann kannst Du das ganze leicht nachvollziehen. Für jeden Befehl malst Du halt drei neue Kästchen und wie die Zeiger zu dem Zeitpunkt stehen.
dr.eichhoernchen - Sa 26.09.09 17:04
ja natürlich, mit den kästen habe ich auch alle anderen prozeduren programmiert. malen hilft immer =) aber ich denke, dass es eben nur an den drei zeilen liegt
aktuell.next.prev:=aktuell.prev;
aktuell.prev.next:=aktuell.next;
aktuell.free;
weil das löschen am anfang und ende eben funktioniert
Tryer - Sa 26.09.09 17:43
Dann zeig uns mal EndeLoeschen, AnfangLoeschen und das Einfügen.
Das verbiegen der Zeiger um ein Element zu löschen ist so (unter der Voraussetzung das "Aktuell" richtig steht) korrekt.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2026 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!