Typische Anfängerfehler
- Boolesche Logik – Grundlagen
- Programmieren mit Boolean-Werten
- Typische Anfängerfehler
Viele Anfänger haben so ihre Probleme mit den Boolean-Werten. Die wichtigsten Anfänger-Fehler seien hier einmal genannt, damit man auch gleich lernen kann sie zu vermeiden.
Doppelt gemoppelt hält besser
1 2 3 4 5 6 7 8 9 10 11 12 | if FileExists(FileName) = True then ...// oder if FileExists(FileName) = False then ... // oder var b: Boolean; begin b := True; if b = True then ... |
Das alles ist alles… falsch[1], wenn es auch in den meisten Fällen funktioniert.
Warum ist es nun falsch?
Das hat im Großen und Ganzen zwei Gründe. Der einleuchtendste zuerst:
Ein Boolean ist bereits Boolean. Man schreibt ja auch nicht:
1 | if (a > b) = True then |
Es ist einfach „doppelt gemoppelt“.
Für den zweiten Grund gibts erst einmal ein Codebeispiel:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // Diese Funktion soll das Verhalten mancher WinAPI-Funktionen imitieren: function GaaanzFieseAPIFunktion: Boolean; asm mov eax, -$01 end; procedure TForm1.Button1Click(Sender: TObject); begin if GaaanzFieseAPIFunktion = True then ShowMessage('Pech gehabt. Mich sieht man gar nicht!'); if GaaanzFieseAPIFunktion then ShowMessage('Hallo'); end; |
Nanu, was passiert denn hier? 😕 Es wird ja wirklich nur die zweite Message angezeigt…
Das hängt mit der internen Darstellung zusammen. Ein Boolean wird als ganzes Byte gespeichert(einzelne Bits lassen sich nicht direkt speichern). Nun hat aber ein Byte 256 mögliche Werte und nicht nur 2. Es gibt nun ziemlich viele Möglichkeiten, wie man einen Boolean in einem Byte kodieren kann. Nicht alle sind sinnvoll, aber es gibt immerhin mehrere sinnvolle Möglichkeiten. In Delphi ist False 0 und 1 ist True, wobei alles andere erstmal nicht vorgesehen ist. Das ist aber nicht überall so. Andere Programmiersprachen, Frameworks, APIs & Co. könnten Boolean-Werte ganz anders kodieren(und tun dies auch). Sobald man also mit irgendetwas in Berührung kommt, was nicht 100% Delphi ist, muss man vorsichtig sein. Und das ist schneller passiert, als man vielleicht vermutet. z.B. dann, wenn man auf WinAPI-Funktionen zugreift[2].
Und genau das simuliert dieser Quelltext. Die Dummy-API-Funktion liefert -1(also für die WinApi True) zurück. Da aber -1 <> 1(True)
ist, funktioniert das nicht ganz so, wie vielleicht gewünscht[3].
Wie macht mans also richtig?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // statt if FileExists(FileName) = True then // ganz einfach if FileExists(FileName) then // statt if FileExists(FileName) = False then // richtig: if not FileExists(FileName) then var b: Boolean; begin b := True; //statt if b = True then // richtig: if b then |
für was denn not?
Manchmal, findet man auch solche abenteuerliche Konstrukte:
1 2 3 4 5 6 | if CheckBox1.Checked then begin end else DoSth; end; |
Leere Anweisungsblöcke… 😯 Kurz und schmerzlos: So macht mans richtig:
1 2 3 4 | if not CheckBox1.Checked then begin DoSth; end; |
Gehts nicht komplizierter?
1 2 3 4 | if FileExists(FileName) then Result := False else Result := True; |
Auch dieser Code funktioniert. Allerdings liefert FileExists ja bereits einen Boolean-Wert zurück. Warum also nochmal abfragen? Man kann diese 4 Zeilen nämlich in einer einzigen schreiben:
1 | Result := not FileExists(FileName); |
Ganz ähnlich ist es hiermit:
1 2 3 4 | if (a > 0) and (a <= 10) then ImBereich := True else ImBereich := False; |
Auch das kann man in eine einzige Zeile schreiben:
1 | ImBereich := (a > 0) and (a <= 10); |
1 2 | if not (i = 5) then ... |
Auch das lässt sich besser bzw. lesbarer schreiben:
1 | if i <> 5 |
==> einfach die richtigen Operatoren in der richtigen Situation benutzen. not ist zwar in vielen Fällen hilfreich und sinnvoll, aber nicht immer nötig…
Boolean und Bit-Operatoren
1 2 3 4 5 6 | var a: Integer; begin a := 13; if a = 5 or 9 then ShowMessage('hallo'); |
Führt man diesen Code aus, wird man tatsächlich begrüßt. Aber warum? Das hängt mit den oben angesprochenen Bit-Operatoren zusammen. So, wie es hier steht, wird 5 or 9 ausgeführt. d.h. folgendes:
1 2 3 4 | 00000101 // 5 or 00001001 // 9 ----------- 00001101 // 13 |
Die eigentlich ausgeführte Abfrage lautet also:
1 | if a = 13 then |
Da dies mit ziemlicher Sicherheit nicht beabsichtigt ist, korrigiert man besser zu:
1 2 3 4 5 6 | var a: Integer; begin a := 13; if (a = 5) or (a = 9) then ShowMessage('hallo'); |
Hier erscheint dann kein Meldungsfenster mehr.
Klammern und warum man sie nicht vergessen sollte
Die Klammern im obigen Codebeispiel dienen nicht nur der besseren Lesbarkeit, sondern sind unabdingbar wichtig, damit sich das Programm korrekt übersetzen lässt. Auch dies ist ein beliebter Anfängerfehler. Lassen wir die Klammern weg, so erhalten wir einen nette Compilerfehler:
1 2 3 4 5 6 | var a: Integer; begin a := 5; if a = 5 or a = 9 then ShowMessage('hallo'); |
Setzen wir die Klammern wieder, lässt sich der Code problemlos übersetzen…
[1] Zumindest in Delphi. In dynamisch typisierten Sprachen, wie z.B. PHP kann dies durchaus richtig sein. In Delphi aber nicht…
[2] Dort gilt alles außer 0 als True, wobei bevorzugt -1(was kodiert einer Binärzahl aus lauter Einsen entspricht) verwendet wird.
[3] Muss man die Rückgabewerte solcher Funktionen in Variablen speichern, so bieten sich die extra dafür vorgesehenen Typen ByteBool, WordBool und LongBool an. Diese sorgen dann auch (unbemerkt) für die korrekte Umrechnung.