George Boole und seine Variablen

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');
[Fehler] Unit1.pas(42): Inkompatible Typen

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.

Kapitel: | Zurück | 1 | 2 | 3 |

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert