Diverser Code in Classic Visual Basic

Inhaltsverzeichnis

Eine weitere Variante ist eine andere Schreibweise des vorigen Beispiels: Man verwendet dabei anstelle der numerischen Operationen bitweise exklusive Oder-Verknüpfungen. Dieses Verfahren ist in Visual Basic 6.0 sogar schneller als die Version mit den numerischen Operatoren.

Korrekte Verwendung der Funktion Now

Vorsicht mit dem Gebrauch der Funktion Now. Probieren Sie die folgenden beiden Beispiele:

' Liefert heutiges Datum vierstellig.
Ret1 = Format$(Now, "0000")

' Liefert heutiges Datum im Format TT.MM.JJJJ.
Ret2 = Format$(x, "DD.MM.YYYY")

Das obige Beispiel funktioniert nur vor 12 Uhr mittags, sollte es später sein, so rundet Visual Basic auf den nächsten Tag auf, da die Funktion Now einen Wert des Typs Variant liefert, der in diesem Fall auf den nächsten Tag aufgerundet wird. Mit der Int-Anweisung kann Abhilfe geschaffen werden:

Ret1 = Format$(Int(Now), "0000")
Ret2 = Format$(x, "DD.MM.YYYY")

Kaufmännisches Runden von Zahlen

Die Round-Funktion kann nicht kaufmännisch runden. Folgendes Beispiel würde 2 als Ergebnis liefern:

Call MsgBox(CStr(Round(2.5, 0)))

Verwendet man jedoch die die folgende Funktion, erhält man den kaufmännisch gerundeten Wert 3. Bei negativen Zahlen wird ein eigentlich nicht korrektes Resultat zurückgegeben, der Leser kann sich aber leicht überlegen, wie der Code richtig aussehen müßte:

Private Function MRound( _
    ByVal Number As Double, _
    ByVal Decimals As Integer _
) As Double
    MRound = Int(Number * 10 ^ Decimals + 0.5) / 10 ^ Decimals
End Function

Private Sub Main()
    
    ' Folgender Code rundet richtig auf n Nachkommastellen, hier 2.5 auf
    ' 0 Nachkommastellen.
    Call MsgBox(CStr(MRound(2.5, 0)))
    
    ' Folgender Code rundet richtig auf positive Ganzzahlen, 2.5 ist die
    ' zu rundende Zahl.
    Call MsgBox(CStr(Fix(2.5 + 0.5)))
End Sub

Entfernen eines Eintrags aus einem Array

Folgende Funktion kann eingesetzt werden, um ein Element aus einem Array zu entfernen. Es werden dabei alle nachfolgenden Elemente um einen Platz nach vor kopiert und das letzte Element entfernt. Diese Funktion hat aber im schlimmsten Fall lineare Laufzeit. Muß man aus einem Array Elemente entfernen, kann ggf. die Datenstruktur der linearen Liste bessere Resultate liefern, da hier die selbe Operation in konstanter Zeit durchgeführt werden kann, weil keine Elemente kopiert werden müssen:

Private Sub RemoveItemFromArray( _
    ByRef Array As Variant, _
    ByVal Index As Long _
)
    If Index <= UBound(Array) And Index >= LBound(Array) Then
        Dim i As Long
        For i = Index To UBound(Array)
            Array(Index) = Array(Index + 1)
        Next i
        ReDim Preserve Array(LBound(Array) To UBound(Array) - 1)
    End If
End Sub

Der Aufruf könnte folgendermaßen erfolgen:

Dim c() As Integer  ' Es muß sich um ein dynamisches Array handeln.
ReDim c(0 To 3) As Integer
c(0) = 2
c(1) = 22
c(2) = 23123
c(3) = 10
Call RemoveItemFromArray(c, 2)
Debug.Print c(2)    ' Dieses Element hat den Wert 10.
Debug.Print c(3)    ' Löst einen Fehler aus, da nicht mehr vorhanden.

Umwandeln von vorzeichenlosen in vorzeichenbehaftete Ganzzahlen

Um einen vorzeichenlosen Integer (im Bereich von 0 bis 65.536) in einen Visual-Basic-Integer zu konvertieren, um ihn z. B. an API-Funktionen zu übergeben, kann folgende Funktion verwendet werden:

Private Sub Main()
    Call MsgBox( _
        "UInt" & vbTab & "Int" & vbNewLine & _
        "0" & vbTab & UIntToInt(0) & vbNewLine & _
        "422" & vbTab & UIntToInt(422) & vbNewLine & _
        "21744" & vbTab & UIntToInt(21744) & vbNewLine & _
        "32767" & vbTab & UIntToInt(32767) & vbNewLine & _
        "43021" & vbTab & UIntToInt(43021) & vbNewLine & _
        "65536" & vbTab & UIntToInt(65536) _
    )
End Sub

Private Function UIntToInt(ByVal UInt As Long) As Integer
    If UInt <= 32767 Then               ' &H7FFF
        UIntToInt = CInt(UInt)
    Else
        UIntToInt = CInt(UInt - 65536)  ' &H10000
    End If
End Function

Zurücksetzen von Objekten für ihre erneute Verwendung

Bei der Verwendung von eigenen Klassen in Schleifen kommt es oft vor, daß nach jedem Durchlauf die Eigenschaften des Objekts zurückgesetzt werden. Im schlimmsten Fall handelt es sich dabei um eine große Anzahl von Eigenschaften und Variablen, die entweder öffentlich zugänglich sind oder innerhalb der Klasse durch eigene Methoden auf bestimmte Startwerte gesetzt werden. In diesem Fall werden viele Programmierer auf eine bedeutend einfachere Methode zurückgreifen.

Nehmen wir an, wir müssen in einer Schleife 1.000 mal eine Klasse einsetzen, um bestimmte Operationen durchzuführen. Zu beachten ist, daß wir an Position (1) im folgenden Code immer erwarten, daß alle internen Variablen der Klasse zurückgesetzt sind. Anstatt eine aufwendige Reset-Prozedur zu implementieren, die auch noch bei Änderungen der Mitglieder der Klasse angepaßt werden muß, machen es sich viele Programmierer leichter und setzen nach jedem Schleifendurchlauf die Klasse auf das Schlüsselwort Nothing, wodurch der Speicher freigegeben wird und damit auch alle Variablen gelöscht werden. Anschließend wird am Anfang der Schleife der Variablen dann wieder eine neue Instanz der Klasse zugewiesen:

Dim i As Long
Dim MyParser As CParser
For i = 1 To 1000
    Set MyParser = New CParser  ' (1)
    
    ' Set properties here and call various class methods.
    ' Many properties and vars are set to other values.
    
    Set MyParser = Nothing
Next i

Zusammenfassend kann gesagt werden, daß im obenstehenden Code immer für jeden Schleifendurchlauf Speicher (und das kann bei einer umfangreichen Klasse gar nicht so wenig sein) freigegeben und anschließend wieder neu belegt werden muß. So schnell Speicheroperationen auch sind, bei einer großen Anzahl von Durchläufen kann dies bereits sehr viel Zeit in Anspruch nehmen.

Wie bereits erwähnt, wäre es in den meisten Fällen effizienter, eine eigene Reset-Methode bzw. mehrere spezifische Reset-Methoden zu programmieren, in denen dann nur die entsprechenden Variablen zurückgesetzt werden. Dadurch entfällt die Freigabe des Speichers, das erneute Anfordern und das Setzen der Variablen auf die Anfangswerte. Der Code würde dann folgendermaßen aussehen:

Dim i As Long
Dim MyParser As CParser
Set MyParser = New CParser
For i = 1 To 1000
    
    ' Set properties here and call various class methods.
    ' Many properties and vars are set to other values.
    
    Call MyParser.Reset
Next i
Set MyParser = Nothing

Vergleichen zweier Instanzen eines benutzerdefinierten Datentyps

Variablen und Konstanten des gleichen Datentyps können mit dem Operator = auf Gleichheit getestet werden. Bei benutzerdefinierten Datentypen ist dies nicht so einfach möglich. Ein Ansatz, trotzdem zwei „Instanzen“ eines benutzerdefinierten Typs auf Gleichheit zu überprüfen besteht darin, die von den beiden Strukturen belegten Speicherbereiche in jeweils einer Zeichenfolge zu kopieren und diese dann zu vergleichen:

Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" ( _
    ByVal pDst As String, _
    ByRef pSrc As Person, _
    ByVal ByteLen As Long _
)

Private Type Person
    Name As String * 100
    Age As Byte
    Size As Long
    Male As Boolean
End Type

Private Sub Form_Load()
    Dim p1 As Person
    With p1
        .Name = "Max Mustermann"
        .Age = 20
        .Size = 200
        .Male = True
    End With
    Dim p2 As Person
    With p2
        .Name = "Max Mustermann"
        .Age = 20
        .Size = 200
        .Male = True
    End With
    Call MsgBox(Equal(p1, p2))  ' Wahr.
    p2.Name = "Donald Duck"
    Call MsgBox(Equal(p1, p2))  ' Falsch.
End Sub

Private Function Equal(ByRef p1 As Person, ByRef p2 As Person) As Boolean
    Dim Length As Long
    Length = Len(p1)
    Dim s1 As String, s2 As String
    s1 = Space$(Length)
    s2 = s1
    Call CopyMemory(s1, p1, Length)
    Call CopyMemory(s2, p2, Length)
    Equal = (s1 = s2)
End Function