Verwenden von Flags

Einleitung

Dieser Artikel beschreibt eine Programmiertechnik, die in der API-Programmierung unter Windows häufig Verwendung findet. Durch Einsatz der Technik kann einerseits die Lesbarkeit des Quellcodes erhöht, andererseits aber auch der Speicherbedarf der kompilierten Anwendung reduziert werden.

Definition von Flags

Flags können in den meisten Programmiersprachen eingesetzt werden, um Informationen über ausgewählte Eigenschaften oder Optionen, die nur die boolesche Werte wahr und falsch annehmen können, in einem Wert zusammenzufassen, um sie beispielsweise an eine Prozedur zu übergeben. Dazu wird nicht für jede Option ein eigener Parameter vom Datentyp Boolean vorgesehen, wie man es intuitiv tun würde, sondern nur ein Parameter, meist vom Datentyp Long.

Betrachtet man das Prinzip genauer, sieht man den Grund für die Bezeichnung Flag (zu Deutsch Flagge). Wie bei einem Schiff können gewisse Flaggen gesetzt sein oder nicht. Der Betrachter sieht nach, welche Flaggen gesetzt sind, und kann so etwa ablesen, ob es bestimmte Krankheiten auf dem Schiff gibt.

Bewertung der Verwendung von Flags

Die Liste zeigt einige Vorteile der im Folgenden an einem Beispiel genauer erläuterten Technik:

Natürlich hat diese Technik auch Nachteile wie die durch die Bitbreite des Aufzählungsdatentyps beschränkte Anzahl an Optionen, die zusammengefaßt werden können. Allerdings stellt dies in der Praxis selten ein Problem dar, da man meist mit wenigen Optionen auskommt und diese Technik nicht so weit treiben soll, logisch nicht zusammenhängende Dinge zusammenzufassen.

Vergleich anhand eines Beispiels

Folgende Prozedur wäre ungeschickt, da aus dem Prozeduraufruf nicht ersichtlich ist, welche Parameter gewählt wurden, falls die genaue Bedeutung der Parameter der Prozedur nicht bekannt ist. Besonders bei langen Programmen, bei denen der Programmierer nicht die Möglichkeit hat, sofort die Deklarationen der Prozeduren anzusehen (etwa bei der Benutzung von Komponenten, die nicht im Quellcode vorliegen), wird es einem schwer fallen, schnell ein Verständnis für die Funktionsweise des Quellcodes zu erwerben:

Call FormatText(…, True, True, False, False, True)

⋮

Private Function FormatText( _
    ByVal Text As String, _
    ByVal UpperCase As Boolean, _
    ByVal TrimSpaces As Boolean, _
    ByVal Reverse As Boolean, _
    ByVal AddPeriod As Boolean, _
    ByVal RemoveTabs As Boolean _
) As String
Aufruf und Kopf einer Prozedur unter Verwendung mehrerer boolescher Parameter zur Optionsauswahl.

Die nachstehende Lösung desselben Problems ist bedeutend einfacher zu verstehen, da bereits am Aufruf der Prozedur erkennbar ist, welche Optionen gewählt wurden. Im Folgenden ist wiederum ein Prozeduraufruf angegeben. Hier erkennt man sofort, daß die Prozedur etwas mit der Formatierung von Text zu tun hat. Außerdem ist nur mehr ein Parameter erforderlich, bei dem die einzelnen Konstanten bitweise durch ein logisches Oder verknüpft werden. Die benötigten Konstanten können in einem Aufzählungstyp zusammengefaßt werden. Der Basistyp von Aufzählungen ist der Datentyp Long:

Private Enum FormatTextFlags
    UpperCase = 1&
    TrimSpaces = 2&
    Reverse = 4&
    AddPeriod = 8&
    RemoveTabs = 16&
End Enum

⋮

Call FormatText(…, UpperCase Or TrimSpaces Or RemoveTabs)

⋮

Private Function FormatText( _
    ByVal Text As String, _
    ByVal Format As FormatTextFlags _
) As String
Aufruf und Kopf einer Prozedur unter Verwendung eines Aufzählungstyps zur Darstellung gewählter Optionen.

Nach welchem Schema die Werte der Konstanten festgelegt werden müssen und wie die Auswertung der gewählten Optionen innerhalb der Implementierung der Prozedur erfolgt, wird im Beispiel im nächsten Abschnitt erklärt.

Ermitteln ausgewählter Optionen

In folgendem Beispiel wird eine Funktion FormatText implementiert, die den in Text übergebenen Text entsprechend den in FormatTextFlags übergebenen Formatierungseigenschaften formatiert. Die Konstanten des Aufzählungstyps FormatTextFlags enthalten die Werte für die angebotenen Optionen.

Entscheidend bei der Wahl der Werte der Optionskonstanten ist, daß sich der Wert von Konstante zu Konstante immer verdoppelt. Nur so kann innerhalb der Prozedur, an welche die zusammengefaßten Werte übergeben werden, erkennen, welche Optionen ausgewählt wurden. Die Verdopplung des Wertes entspricht der Bildung von Zweierpotenzen, bei deren Darstellung im Rechner dann immer genau ein Bit gesetzt wird. In einem vereinfachten Modell (hier verwenden wir nur acht Bit und unterscheiden nicht zwischen negativen und positiven Zahlen) kann man sich das wie folgt vorstellen:

Darstellung von Zahlen im Computer
Basis Exponent Dezimale Entsprechung Darstellung im Computer
2 0 1 00000001
2 1 2 00000010
2 2 4 00000100
2 3 8 00001000
Funktionsweise von Flags.

Wie man leicht erkennen kann, ist es mit den 32 Bits des Datentyps Long möglich, bis zu 32 Wahrheitswerte zu repräsentieren. Durch das „Verodern“ der Konstanten werden dann die entsprechenden Bits gesetzt, also 0000010 Oder-verknüpft mit 0010000 ergibt 00100010.

Um in der Prozedur, hier FormatText, zu prüfen, ob eine bestimmte Option gewählt wurde, wird geprüft, ob das entsprechende Bit der Optionskonstante in dem in Format übergebenen Wert enthalten ist. Dazu wird folgender Code verwendet:

If CBool(FormatTextFlags And UpperCase) Then
    
    ' 'UpperCase' ist gewählt.
End If
Ermitteln, ob eine Option gesetzt wurde.

Dabei erfolgt eine bitweise Und-Verknüpfung. Ist das Ergebnis gleich null, dann ist die Option nicht gewählt, andernfalls schon. Man könnte anstelle des Vergleichs mit der Optionskonstante zur Überprüfung auf Auswahl der Option auch einen Vergleich auf Ungleichheit zu null durchführen. Auf diese Weise kann jede Option abgefragt und eine entsprechende Aktion ausgeführt werden. Zum Aufruf von FormatText mit mehreren Optionen werden im Parameter Format alle gewünschten Optionen durch ein bitweises Oder verknüpft:

… = FormatText(…, TrimWhitespace Or RemoveTabs)
Kombinieren von Optionen.

Schlußwort

Durch Verwendung von Flags können Komplexität und Umfang von Prozeduren erheblich reduziert werden. Flags sind dann geeignet, wenn sich die Schnittstelle der Prozedur bei Hinzufügen neuer Optionsparameter nicht ändern soll. Würde man für jede Option einen eigenen Parameter vom Datentyp Boolean vorsehen, würde die Erweiterung um eine neue Option ein Umschreiben des Codes erforderlich machen.

Vollständiges Codebeispiel

Private Enum FormatTextFlags
    UpperCase = 1&
    TrimSpaces = 2&
    Reverse = 4&
    AddPeriod = 8&
    RemoveTabs = 16&
End Enum

Private Sub Main()
    Const Text As String = "He said:    H ello" & vbTab & "World!   "
    Const NL As String = vbNewLine
    Call MsgBox( _
        "Originalzeichenfolge: """ & Text & """" & NL & NL & _
        "Formatoptionen: 'UpperCase'" & NL & _
        "Ergebnis: """ & FormatText(Text, UpperCase) & """" & NL & _
        NL & _
        "Formatoptionen: 'TrimSpaces', 'RemoveTabs'" & NL & _
        "Ergebnis: """ & _
        FormatText(Text, TrimSpaces Or RemoveTabs) & """" & NL & _
        NL & _
        "Formatoptionen: 'Reverse', 'AddPeriod', 'UpperCase'" & NL & _
        "Ergebnis: """ & _
        FormatText( _
            Text, Reverse Or AddPeriod Or UpperCase _
        ) & """" & NL & _
        NL & _
        "Formatoptionen: 'TrimSpaces', 'AddPeriod', 'RemoveTabs', " & _
        "'UpperCase'" & NL & _
        "Ergebnis: """ & _
        FormatText( _
            Text, _
            TrimSpaces Or AddPeriod Or RemoveTabs Or UpperCase _
        ) & """" _
    )
End Sub

Private Function FormatText( _
    ByVal Text As String, _
    ByVal Format As FormatTextFlags _
) As String
    If CBool(Format And UpperCase) Then
        Text = UCase$(Text)
    End If
    If CBool(Format And TrimSpaces) Then
        Text = Replace(Text, " ", "")
    End If
    If CBool(Format And Reverse) Then
        Text = StrReverse(Text)
    End If
    If CBool(Format And AddPeriod) Then
        Text = Text & "."
    End If
    If CBool(Format And RemoveTabs) Then
        Text = Replace(Text, vbTab, "")
    End If
    FormatText = Text
End Function
Das vollständige Beispiel.