Code zur Verarbeitung von Zeichenfolgen in Classic Visual Basic

Formatieren von Text als „proper-case

Wenn alle Wörter in einer Zeichenfolge mit einem Großbuchstaben beginnen sollen, kann der folgende Code verwendet werden:

Dim NewText As String
Dim OldText As String
OldText = "Das ist ein Text."
NewText = StrConv$(OldText, vbProperCase)   ' Resultat ist "Das Ist Ein Text.".

Entfernen von Nullzeichen aus einer Zeichenfolge

Viele API-Funktionen fügen an Zeichenfolgen ein Nullzeichen (auch oft als Nullbyte bezeichnet, Chr$(0)) an. Die Funktion NullTrim entfernt dieses:

Private Function NullTrim(ByVal Text As String) As String
    NullTrim = Left$(Text, InStr(Text, vbNullChar) - 1)
End Function

Schnelles Vertauschen von Zeichenfolgen

Beispielsweise bei Sortierverfahren muß das Vertauschen von Zeichenfolgen schnell gehen. Eine naive Methode zum Vertauschen zweier Zeichenfolgen würde folgendermaßen aussehen:

Private Sub SwapStrings(ByRef Text1 As String, ByRef Text2 As String)
    Dim t As String
    t = Text1
    Text1 = Text2
    Text2 = t
End Sub

Der Nachteil der Prozedur ist, daß zuerst einer der beiden Zeichenfolgen temporär gespeichert werden muß, d. h., es muß eine neue Zeichenfolgenvariable angelegt werden. Dies gerade ist unter Visual Basic sehr langsam, weil Speicher für die Zeichenfolge reserviert werden muß. Daraufhin wird die zweite Zeichenfolge über die erste kopiert und zum Schluß noch die zwischengespeicherte (erste) Zeichenfolge auf die Zweite.

Betrachtet man die Struktur einer Variablen des Typs String genauer, so sieht man, daß diese aus einem Kopf und einem Körper besteht. Der Kopf enthält lediglich einen Zeiger (die Adresse) auf den Körper, der wiederum die Daten, nämlich die Zeichenfolge, enthält. Wäre es daher nicht viel einfacher, nur die beiden Köpfe auszuwechseln? Jeder der Zeichenfolgendeskriptoren enthält nur eine vier Byte lange Adresse. Somit müssen bei der Vertauschung zweier Zeichenfolgen nur insgesamt vier zusätzliche Bytes reserviert und 12 Bytes verschoben werden. Der dadurch entstehende Performancezuwachs macht sich dann schnell bemerkbar. Im Folgenden ist der Code von Jost Schwider, angegeben, von dem die vorher beschriebene Vertauscherprozedur stammt:

Private Declare Sub PokeLng Lib "kernel32.dll" Alias "RtlMoveMemory" ( _
    ByVal Addr As Long, _
    ByRef Value As Long, _
    Optional ByVal Bytes As Long = 4 _
)

'
' Vertauschen zweier Zeichenfolgen, ohne die Daten zu kopieren. Es werden nur die
' Zeiger auf die Datenteile der Zeichenfolgen vertauscht.
'
Private Sub SwapStr(ByRef a As String, ByRef b As String)
    Dim p As Long
    p = StrPtr(a)
    Call PokeLng(VarPtr(a), StrPtr(b))
    Call PokeLng(VarPtr(b), p)
End Sub

Dieses Beispiel verwendet die undokumentierte Funktion VarPtr, die die Adresse einer Variablen in diesem Fall des Zeichenfolgendeskriptors zurück gibt. Um direkt einen Zeiger auf die Daten zu bekommen, kann die StrPtr-Funktion eingesetzt werden.

Codieren von Zahlen als Zeichenfolge

Unter Visual Basic ist es möglich, Dateien mittels des wahlfreien Zugriffsmodus zu schreiben und zu lesen. Dies ist besonders praktisch, wenn man Elemente eines benutzerdefinierten Datentyps in eine Datei speichern will. Enthält diese Struktur Mitglieder, die Ganzzahlen sind, so werden diese nicht als Zeichenfolge in die Datei geschrieben, sondern codiert, sodaß der Speicherbedarf für die Zahl bedeutend geringer ist, als wenn sie als normaler Text gespeichert worden wäre. Leider steht diese oder eine ähnliche Funktionalität nur dem binären Zugriffsmodus zur Verfügung, daher muß die Zeichenfolge zuerst manuell codiert, dann gespeichert und anschließend nach dem Lesen wieder decodiert werden. Dazu bieten sich folgenden Funktionen an:

'
' Wandelt eine als Zeichenfolge codierte Zahl in eine Dezimalzahl um.
'
Private Function StringToDecimal(ByVal Number As String) As Long
    Dim i As Long, n As Long
    n = Len(Number)
    For i = n To 1 Step -1
        StringToDecimal = _
            StringToDecimal + 256 ^ (n - i) * Asc(Mid$(Number, i, 1))
    Next i
End Function

'
' Codieren einer Dezimalzahl als Zeichenfolge.
'
Private Function DecimalToString(ByVal Number As Long) As String
    Dim r As Long
    Do While Number > 255
        r = Number Mod 256
        Number = Number \ 256
        DecimalToString = Chr$(r) & DecimalToString
    Loop
    DecimalToString = Chr$(Number) & DecimalToString
End Function

Private Sub Main()
    Dim s As String
    
    ' Codierung und anschließende Decodierung einer Zahl.
    s = DecimalToString(1234567)
    Call MsgBox( _
        "Codiere 1234567" & vbNewLine & _
        "DecimalToString(1234567):  " & s & vbNewLine & _
        "StringToDecimal(1234567):  " & CStr(StringToDecimal(s)) _
    )
End Sub

Die Funktionsweise der beiden Funktionen entspricht dem Konvertieren zwischen zwei Zahlensystemen. Dabei wird beim Codieren einer Zahl diese auf die Basis 256 konvertiert, diese kann dann einfach als Zeichen interpretiert werden. Beim Decodieren wird der Vorgang umgekehrt.

Bei der Verwendung dieser Funktionen ist zu beachten, daß die Resultate keine feste Länge besitzen und daher nach Schreiben in der Datei eventuell falsche Werte ausgelesen werden können. Gleich wie im Dezimalsystem müssen diese Zeichenfolgen nämlich rechts ausgerichtet werden, die Stellen, die auf eine feste Länge fehlen, müssen mit dem Nullzeichen ausgefüllt werden.

Verketten von Zeichenfolgen

Unter Visual Basic sollte der Operator & anstelle von + zur Verknüpfung zweier Zeichenfolgen verwendet werden. In früheren Versionen hatte der Additionsoperator + eine doppelte Bedeutung als Additionsoperator für numerische Datentypen und als Verknüpfungsoperator für Zeichenfolgen. Visual Basic heute besitzt den Zeichenfolgenverknüpfungsoperator &, der für diesen Zweck verwendet werden soll. Das ist vor Allem dann evident, wenn man Zeichenfolgen verknüpfen will, die Zahlen enthalten. Es kann dann vorkommen, daß anstelle einer längeren Zeichenfolge die Summe der beiden Zeichenfolgen erzeugt wird. Der Operator + sollte nur bei numerischen Datentypen verwendet werden.

Weiters ist auch Vorsicht bei Zeichenfolgenabteilungen in Schleifen geboten. Es wirkt sich eine Zeichenfolgenabteilung auf mehrere Zeilen und die Verknüpfung mit dem &-Operator auf die Laufzeit aus. Der Durchlauf der ersten For-Schleife braucht länger, da erst die Zeichenfolge zusammengefügt werden muß, wohingegen dies bei der zweiten For-Schleife entfällt. Besonders beim Ausführen in der Entwicklungsumgebung wirkt sich dies stark aus, in der fertigen Anwendung ist der Unterschied schon kleiner, aber noch immer vorhanden:

Dim Test As String
Dim n As Long
Dim OldTime As Double

' Mit zusammengesetztem String.
OldTime = Timer
For n = 1 To 1000000
    Test = _
        "Hello" & _
        " World!"
Next n
Call MsgBox(CStr(Timer - OldTime))

' Mit einzelnem String.
OldTime = Timer
For n = 1 To 1000000
    Test = "Hello World!"
Next n
Call MsgBox(CStr(Timer - OldTime))

Umkehren einer Zeichenfolge

Visual Basic stellt die Funktion StrReverse zur Verfügung, um eine Zeichenfolge umzukehren, also wird z. B. aus Hello World! die Zeichenfolge !dlroW olleH. Diese Funktion kann man aber auch mit einfachen Mitteln selbst programmieren:

Private Function ReverseString(ByVal Text As String) As String
    Dim n As Long
    For n = Len(Text) To 1 Step -1
        ReverseString = ReverseString & Mid$(Text, n, 1)
    Next n
End Function

Umwandeln einer Zeichenfolge in ein Array von Bytes

Ab und zu ist es erforderlich, eine Zeichenfolge in ein Array des Typs Byte zu konvertieren. Dies kann einerseits mit der RtlMoveMemory-API-Funktion geschehen, andererseits kann folgende Funktion verwendet werden, die ohne API-Aufrufe auskommt, aber etwas langsamer ist:

Private Function StrToByteArray(ByVal Text As String) As Byte()
    Dim b() As Byte
    ReDim b(0 To Len(Text) - 1)
    Dim i As Long
    For i = 1 To Len(Text)
        b(i - 1) = Asc(Mid$(Text, i, 1))
    Next i
    StrToByteArray = b
End Function

Ein Beispiel für die Verwendung könnte folgendermaßen aussehen:

Dim b() As Byte
b = StrToByteArray("Hallo")

' String byteweise ausgeben.
Dim s As String, i As Long
For i = 0 To UBound(b)
    s = s & Chr$(b(i))
Next i
Debug.Print s

Null-Zeichenfolgen in Datenbanken

Ein oft gefragtes Problem sind auch Null-Felder in Datenbanken. Access-Datenbanken verwenden NULL in String-Feldern, die noch keinen Wert besitzen. In Visual Basic wird ein Typumwandlungsfehler ausgelöst, wenn man versucht, den Inhalt eines solchen Feldes in eine Variable zuzuweisen. Folgender Code bedient sich hier eines Tricks, es wird einfach der Inhalt des Feldes mit einer leeren Zeichenfolge verknüpft, wodurch kein Fehler mehr auftritt. Auf diese Weise kann man sich alle Abfragen sparen:

Dim dbBiblio As Database
Dim rsAuthors As Recordset
Dim strYear As String 
Set dbBiblio = OpenDatabase("biblio.mdb")
Set rsAuthors = dbBiblio.OpenRecordset("Authors")
strYear = "" & rsAuthors![Year Born]

Ermitteln der Zeichenanzahl bis zum n-ten Vorkommen eines Zeichens in einer Zeichenfolge

Die Funktion RefChar ermittelt die Anzahl der Zeichen bzw. die Länge des Textes vor dem n-ten Vorkommen eines Zeichens in einer Zeichenfolge. Wird das Zeichen nicht n Mal gefunden, gibt die Funktion −1 zurück:

Private Sub Main()
    Const s As String = "Eine Fähigkeit ist etwas, was wir tun können."
    
    ' Ermittle Anzahl der Zeichen bis zum 2. Vorkommen von "e".
    Call MsgBox(RefChar(s, "e", 2))
End Sub

Private Function RefChar( _
    ByVal Text As String, _
    ByVal Reference As String, _
    ByVal Count As Long _
)
    Dim n As Long, p As Long
    Reference = Left$(Reference, 1)
    p = 1
    For n = 1 To Count
        p = InStr(p + 1, Text, Reference)
        If p = 0 Then
            RefChar = -1
            Exit Function
        End If
    Next n
    RefChar = p
End Function

Die nachfolgend angegebene Funktion CountCrLfs zählt die Anzahl der Vorkommen von vbCrLf in einer Zeichenfolge:

Private Sub Main()
    Dim s As String
    s = _
        vbCrLf & _
        "Hallo" & vbCrLf & _
        "Welt" & vbCrLf & _
        "Bla" & vbCrLf & _
        vbCr & vbCrLf
    Call MsgBox(CStr(CountCrLfs(s)) & " CRLF-Umbrüche.")
End Sub

Private Function CountCrLfs(ByVal Text As String) As Long
    Dim n As Long, Count As Long
    Dim IsLastCr As Boolean
    Dim Char As String
    IsLastCr = False
    For n = 1 To Len(Text)
        Char = Mid$(Text, n, 1)     ' Aktuelles Zeichen holen.
        If Char = vbCr Then         ' Anfang eines Umbruchs.
            IsLastCr = True
        ElseIf IsLastCr Then
            If Char = vbLf Then
                Count = Count + 1
                IsLastCr = False
            End If
        Else
            If IsLastCr Then
                IsLastCr = False
            End If
        End If
    Next n
    CountCrLfs = Count
End Function

Isolieren einer Zahl aus beliebigem Text

Ab und zu ist es nicht möglich, Benutzereingaben direkt auf numerische Eingaben zu prüfen, beispielsweise wenn eine Zahl aus einer Datei eingelesen werden soll. Die folgende Funktion extrahiert eine Zahl aus einer Zeichenfolge, indem nichtnumerische Zeichen entfernt werden. Bei mehreren Dezimaltrennzeichen wird das erste verwendet:

Private Function PurgeNumericInput(ByVal Text As String) As Variant
    Dim HasDecimal As Boolean, i As Long
    Dim s As String
    Text = Trim$(Text)
    If Len(Text) = 0 Then
        Exit Function
    End If
    For i = 1 To Len(Text)
        Select Case Mid$(Text, i, 1)
            
            ' Is this character a number?
            Case "0" To "9"
                
                ' Add it to the string being built.
                s = s & Mid$(Text, i, 1)
            
            ' The character is a decimal.
            Case ".", ","
                If Not HasDecimal Then
                    HasDecimal = True
                    s = s & "."
                End If
        End Select
    Next i
    PurgeNumericInput = Val(s)
End Function

Private Sub Main()
    Call MsgBox( _
        PurgeNumericInput( _
            "$44E.mbedded letters and spaces 33 a few more " & _
            "pieces of garbage .9" _
        ) _
    )
End Sub