Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Verschachtelte Schleifen beenden


HenryHux - Sa 02.10.10 16:06
Titel: Verschachtelte Schleifen beenden
Hi,
immoment habe ich ein Programm mit viele repeat-Schleifen und darin vielen verschiedenen, verschachtelten If-Abfragen.
Die Schleifen beende ich mit Variablen, also wenn der entsprechende If-Fall eingetreten ist, wird der Wert auf die Bedingung von Until gesetzt.
Jetzt wollte ich mal Fragen, geht das nicht einfacher?
Ich habe von break und goto gelesen.
break bringt mir aber nichts, weils nur eine Schleife beendet. (richtig verstanden?)
Womit kann ich eine ganze Schleife, mit mehreren kleineren beenden?

Lg

Henry


glotzer - Sa 02.10.10 16:08

mit goto und Labels
mal in der helife nachlesen :p


bole - Sa 02.10.10 16:17

Ich würde Goto unbedingt vermeiden! Die macht den Code extrem unleserlich und unwartbar. Break ist zwar schon besser aber meines Erachtens auch ein verkapptes GOTO.

Ich denke du solltest Deine Schleifenkonstruktion überarbeiten, durch die Wahl der richtigen Schleife kann man dies meist stark vereinfachen.


HenryHux - Sa 02.10.10 16:34

Jo wollte ich auch immer, kannte es aus anderen Sprachen und fand es nie gut es zu benutzen.
Aber jetzt sind in einem Programm so verschatelte Schleifen, mit vielen If Abfragen, die nicht parallel eintreten können.
Dh wenn die erste If Anweisung zutrifft laufen die anderen Anweisungen auch weiter durch.
Einerseits sehr langsam und zweitens mir irgendwie nicht sicher.
Und break könnte ich bei ja auch nur so setzen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
 If ... then
         begin 
            anweisung1;
            anweisung2;
            break;
         end;

Und das bringt mir ja auch keine Verbesserung.. Deswegen goto.
Danke aber tortzdem =)


jaenicke - Sa 02.10.10 16:44

Wie wäre es denn mit else?


HenryHux - Sa 02.10.10 16:50

Letztendlich hat das in repeat-Schleifen ja auch keinen Unterschied..


jaenicke - Sa 02.10.10 16:53

Ich habe das darauf bezogen:
user profile iconHenryHux hat folgendes geschrieben Zum zitierten Posting springen:
Dh wenn die erste If Anweisung zutrifft laufen die anderen Anweisungen auch weiter durch.
Einerseits sehr langsam und zweitens mir irgendwie nicht sicher.
Das hörte sich so an:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
if a then
begin
  xy;
end;
if b then
begin
  xy;
end;
Deshalb meinte ich das else, damit die zweite if-Abfrage nicht mehr ausgeführt wird.

Ja, ansonsten bleibt nur die Schleifenbedingungen gut zu setzen. ;-)

Und damit es nicht zu unübersichtlich wird, ist es natürlich sinnvoll manches in einzelne Methoden auszulagern.


HenryHux - Sa 02.10.10 17:00


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
if a then
begin
  xy;
end;
if b then
begin
  xy;
end;


Ist doch von der Geschwindigkeit und generell das gleiche wie

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
if a then
begin
  xy;
end;
else if b then
begin
  xy;
end;


Oder irre ich mich?


Marc. - Sa 02.10.10 17:09

Schau Dir doch einfach dazu den Debugger Code an. Breakpoint setzen (F5), Anwendung starten und mit Strg+Alt+C in das CPU-Fenster wechseln.
Hier sieht man, dass im Falle eines else am Ende der Ausführung der ersten Bedingung ein JMP hinter die Zweite gesetzt wird. ;) Somit ist dies u.U. schneller.


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
Unit1.pas.33if (s = 'a') then
00466568 8B45FC           mov eax,[ebp-$04]
0046656B BAB8654600       mov edx,$004665b8
00466570 E8BBF6F9FF       call @UStrEqual
00466575 7503             jnz $0046657a // Else
Unit1.pas.35: nop
00466577 90               nop // Die Befehlsätze innerhalb des ersten IFs; in diesem Fall nur "nop" (no process)
00466578 EB10             jmp $0046658a // JMP hinter die zweite Abfrage *
Unit1.pas.38if (s = 'b') then
00466578B45FC           mov eax,[ebp-$04]
0046657D BAC8654600       mov edx,$004665c8
00466582 E8A9F6F9FF       call @UStrEqual
00466587 7501             jnz $0046658a
Unit1.pas.40: nop
00466589 90               nop // Befehl im zweiten IF
Unit1.pas.42: end; 
004665833C0             xor eax,eax // *


HenryHux - Sa 02.10.10 17:22

Ok.
Immoment habe ich einfach alle If-Anweisungen ganz normal verschachtelt und hintereinander geschrieben.
Sollte ich generell lieber mit else arbeiten?


platzwart - Sa 02.10.10 17:36

Kannst du mal den realen Code posten? Dann könnte man sicherlich besser helfen...


HenryHux - Sa 02.10.10 17:45

In der einen procedure wo meine Frage drüber war sind mehr als 2k Zeilen drin.
Denke nicht gerade die beste Idee das hier zu posten =)
Aber meine Frage hat sich schon geklärt.
Habe die "parallelen" Ifs mit else if gemacht.
So wie ich das verstanden hab, werden sobald davon eine richtig ist, die danachstehenden Ifs alle nicht mehr abgefragt.


Tilman - Sa 02.10.10 17:47

Eventuell könnte auch case das richtige für dich sein. Btw: wenn eine Prozedur > als 2000 Zeilen Code hat, dann läuft irgendwas falsch ;) du solltest dann Codeteile in weitere prozeduren auslagern, und schauen ob du Code "wiederverwenden" kannst, also vielleicht in vielen Zeilen immer fast das selbe machst.


HenryHux - Sa 02.10.10 17:56

Case ist If, quasi mit oder, richtig? :D
Das wäre auch nicht gut, denn es sind viele verschiedene Aktionen in verschiedenen Situationen.
Das einzige was mir noch zu schaffen macht, ist die until Bedingung, die ich dann bei jedem If auf 0 setzen muss.
Ansonsten ist der Code schon fast maximal in anderen Funktionen bzw. Prodzeduren ausgelagert.


jaenicke - Sa 02.10.10 18:06

Wenn du else nutzt, dann wird der Teil im else nur ausgeführt, wenn die erste if-Abfrage nicht wahr war. Also genau was du willst.

Und zu den 2000 Zeilen wurde ja schon etwas gesagt:
Das ist dein größtes Problem... Also 200-300 Zeilen ist schon viel, aber das kann schon mal passieren. Aber 2000, in Worten zweitausend, Zeilen?!?

Da solltest du dringend deine Codestruktur korrigieren, dann lösen sich die anderen Probleme vermutlich von selbst...


HenryHux - Sa 02.10.10 18:18

Is mein erstes wirklich größeres Programm, das ich schreibe.
Nun schon fast seit einem Monat.
Aber dachte, das wäre völlig normal mal in einer Procedure ein bisschen mehr stehen zu aheb :D
Aber ich wüsste ernsthaft nicht, wie ich den Code noch viel mehr auslagern sollte.


jaenicke - Sa 02.10.10 18:24

user profile iconHenryHux hat folgendes geschrieben Zum zitierten Posting springen:
Aber ich wüsste ernsthaft nicht, wie ich den Code noch viel mehr auslagern sollte.
Wenn du eine einigermaßen neue Version von Delphi hast, macht Delphi das automatisch für dich. Nicht unbedingt schön, ich mache es meistens lieber manuell, aber es funktioniert.

Dafür markierst du den Teil, der ausgelagert werden soll, und gehst unter Refactoring auf Methode extrahieren.

Es gibt jedenfalls bestimmt Möglichkeiten zur Auslagerung. Du musst immer schauen was du logisch in kleine Stücke teilen kannst und das dann in eigene Prozeduren packen. So kannst du nebenbei solche Codeteile dann auch viel besser wiederverwenden.


HenryHux - Sa 02.10.10 18:37

Ok, danke für den Tipp, wusst ich nicht, werds gleich mal ausprobieren.
Habe jetzt mal alles mit GoTo bearbeitet.
Sind nur 2 Labels, weiß aber nicht ob das die beste Idee war.
Natürlich springt er sofort woanders hin, aber was mich stört, ist dass Labels anscheinend nur Lokal zu deklarieren sind.
D.h wenn ich per GoTo in einer andere Procedur springen will, geht das nicht direkt.
Habe jetzt am Schluss der Prozedur, wo ich rausspringen will, eine praktisch unerfüllbar If-Anweisung gesetzt, wo ich das Label rein positioniert habe.
Vor dem letzten Ende kommt dann nur noch mal der Verweis auf die andere Prozedur, dürfte also funktionieren.
Was haltet ihr davon?


elundril - Sa 02.10.10 18:41

user profile iconHenryHux hat folgendes geschrieben Zum zitierten Posting springen:
Ok, danke für den Tipp, wusst ich nicht, werds gleich mal ausprobieren.
Habe jetzt mal alles mit GoTo bearbeitet.
Sind nur 2 Labels, weiß aber nicht ob das die beste Idee war.
Natürlich springt er sofort woanders hin, aber was mich stört, ist dass Labels anscheinend nur Lokal zu deklarieren sind.
D.h wenn ich per GoTo in einer andere Procedur springen will, geht das nicht direkt.
Habe jetzt am Schluss der Prozedur, wo ich rausspringen will, eine praktisch unerfüllbar If-Anweisung gesetzt, wo ich das Label rein positioniert habe.
Vor dem letzten Ende kommt dann nur noch mal der Verweis auf die andere Prozedur, dürfte also funktionieren.
Was haltet ihr davon?


ja, keine schlechte idee wenn man Programmierer in der Herzinfakt- oder Selbstmordtot treiben will. Für ernstes Programmieren sind aber GoTos n absolutes No-Go. ;)

lg elundril


jaenicke - Sa 02.10.10 18:42

user profile iconHenryHux hat folgendes geschrieben Zum zitierten Posting springen:
Natürlich springt er sofort woanders hin, aber was mich stört, ist dass Labels anscheinend nur Lokal zu deklarieren sind.
D.h wenn ich per GoTo in einer andere Procedur springen will, geht das nicht direkt.
Das wäre ja der Super-GAU. Schlimm genug, dass es goto überhaupt gibt. Die Anwendungsfälle, in denen das wirklich sinnvoll ist kann man quasi an einer Hand abzählen. Dein Programm gehört zu 99,999% sicher nicht dazu...

user profile iconHenryHux hat folgendes geschrieben Zum zitierten Posting springen:
Was haltet ihr davon?
Nichts, mach es besser richtig statt solche Notkonstrukte zu basteln...


Marc. - Sa 02.10.10 18:42

user profile iconHenryHux hat folgendes geschrieben Zum zitierten Posting springen:
Ok, danke für den Tipp, wusst ich nicht, werds gleich mal ausprobieren.
Habe jetzt mal alles mit GoTo bearbeitet.
Sind nur 2 Labels, weiß aber nicht ob das die beste Idee war.

2000 Zeilen und GOTO - dafür gehörst Du gesteinigt. :mrgreen:
Du wirst sicher merken, dass Du Dich nach einer Weile überhaupt nicht mehr in deinem Quellcode zurechtfinden wirst.

Wie schaut denn diese Routine in etwa aus? Ich bin mir sicher, da ist noch sehr viel Optimierungsbedarf. ;)
Ansonsten einfach user profile iconjaenickes Vorschlag folgen. Quelltext auslagern und gegebenfalls via Klasse zusammenfassen. ;)


HenryHux - Sa 02.10.10 18:46

Ok, danke für die warnenden Hinweise.
Werde mich jetzt erstmal selbst geißeln und mir dann was anderes einfallen lassen :D
Mit dem Auslagern klappt soweit alles ganz gut, danke ist jetzt viel übersichtlicher geworden.

Lg

Henry


Martok - Sa 02.10.10 19:03

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Das wäre ja der Super-GAU. Schlimm genug, dass es goto überhaupt gibt. Die Anwendungsfälle, in denen das wirklich sinnvoll ist kann man quasi an einer Hand abzählen. Dein Programm gehört zu 99,999% sicher nicht dazu...

Wohl wahr. Nur - sein Problem ist genau der einizige vernünftige Anwendungsfall für goto.
Nicht umsonst haben viele Sprachen break&continue mit Parametern, so dass folgendes ginge:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
for i:= 0 to 100 do begin
  for j:= 0 to 100 do begin
    if foo then
      break(2);  //dieses break...
  end;
end;
//...landet hier

Ist aber in Delphi nicht, und damit bleibt lediglich Flag-gewurschtel übrig. Macht den Code nicht gerade lesbarer...


HenryHux - Sa 02.10.10 19:10

Ehrlich gesagt ist durch Goto mein Code nicht unübersichtilcher geworden.
Normalerweise hatte ich auf die nächste Prozedur gezeigt mit zb read3.click.
Jetzt steht da halt goto r3.
Für mich eingeltich besser so und schneller vor allem.


jaenicke - Sa 02.10.10 19:13

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Nicht umsonst haben viele Sprachen break&continue mit Parametern, so dass folgendes ginge:
Lesbarer ist es IMHO aber trotzdem nicht. :nixweiss:

Man selbst durchschaut es, aber wenn man sich sowas dann später anschaut, versteht man es auch kaum auf Anhieb und jemand anderes...

Wenn man sprechende Bezeichner wählt, sind Flags durchaus sinnvoll für diesen Zweck. Dann kann man direkt lesen wann die Schleife abgebrochen wird und muss eben nicht in der Prozedur suchen wo da rausgesprungen wird.

Klar, bei Mini-Prozeduren wo es z.B. eine einzelne Suchschleife ist oder so, da überblickt jeder auch ein break auf Anhieb, aber bei verschachtelten längeren Schleifen nicht.

user profile iconHenryHux hat folgendes geschrieben Zum zitierten Posting springen:
Jetzt steht da halt goto r3.
r3 wirst du aber später auch nicht mehr verstehen. ;-)
Sprechende Bezeichner wären schon sinnvoller.


Hidden - Sa 02.10.10 20:31

Moin!

Statt goto kann man meißtens auch den Code in Prozeduren ausgliedern. Das hilft der Übersicht auf jeden Fall, selbst wenn goto ihr nicht schaden würde.

Die Ablehnung von gotos ist verbreitet, und wir wollen unseren Code ja alle noch mal anderen zeigen. Also lassen wir das Ganze^^

lg,

PS: Wenn du den Code veröffentlichen kannst, optimieren wir da gerne nochmal für dich rum(auf Länge/Stil/Benennung/Kommentare/Formatierung/was du willst) :)


HenryHux - Sa 02.10.10 22:12

Danke für das Angebot!
Aber leider kann ich den Code noch nicht veröffentlichen. Top secret :D
Ich werde bestimmt noch ein paar Monate dadran sitzen, aber ich werde es wahrscheinlich OpenSource veröffentlichen.
Bis dahin werde ich garantiert noch einige Schwierigkeiten und Fragen damit haben :D
Und immoment bin ich ja nicht der einzige Coder dran, sind ja noch ein paar andere aus dem Forum, die tatkräftig helfen.
Aber wie gesagt, ich werde es dann iwann online stellen, so wie es immoment aussieht als Freeware.
Und btw, ich finde man brauch sich nicht für ein paar gotos schämen :P


alzaimar - So 03.10.10 17:30

Ist doch ganz einfach:

Aus

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
For i:=1 to 100 do
  For j:=1 to 100 do
    for k:=1 to 100 do
  ....
       if Bedingung then
          break; // oder goto
...
// Hier gehts weiter

macht man über refactoring eine eigene Prozedur:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
Procedure KomplexeSchleifen;
...
begin
  For i:=1 to 100 do
    For j:=1 to 100 do
      for k:=1 to 100 do
    ....
         if Bedingung then
            exit;
end;
...
...
begin
  KomplexeSchleifen();
  // Hier gehts weiter


Also auslagern. ist sowieso lesbarer.

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:

Wenn man sprechende Bezeichner wählt, sind Flags durchaus sinnvoll für diesen Zweck. Dann kann man direkt lesen wann die Schleife abgebrochen wird und muss eben nicht in der Prozedur suchen wo da rausgesprungen wird.

Bringt nur etwas, wenn das Abbruchkriterium hinterher verwendet wird. Ansonsten ist das Flag eine überflüssige und unverständlicherweise verwendete Variable.

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Klar, bei Mini-Prozeduren wo es z.B. eine einzelne Suchschleife ist oder so, da überblickt jeder auch ein break auf Anhieb, aber bei verschachtelten längeren Schleifen nicht.

Das Grundproblem ist doch, das man so viele verschachtelte Schleifen hat. Ich kenne eigentlich keinen Algorithmus, der mehr als drei verschachtelte Schleifen benötigt. Alles andere lässt sich durch Refactoring vereinfachen oder basiert auf einer falschen Datenstruktur.