Záměna obsahu proměnných

Jak víme, program vykonává řádky kódu postupně, proto není možné výměnu obsahu dvou proměnných realizovat „z ruky do ruky“, tj. stylem A = B, B = A. Po přiřazení A = B bychom přišli o původní obsah proměnné A. Některé programovací jazyky mají pro tyto účely funkci Swap. Ve VBA si ovšem musíme pomoci jinak.

Univerzální technikou je použití pomocné proměnné.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Sub ZamenaObsahuPromennychA()

    'univerzální postup
   
    Dim A As Integer
    Dim B As Integer
   
    'pomocná proměnná
    Dim C As Integer

    A = 3
    B = 5

    C = A
    A = B
    B = C

End Sub

Za vyřešení číselné úlohy výše uvedeným způsobem by student získal bod. Další by obdržel, pokud by si dokázal poradit bez pomocné proměnné. To lze provést dokonce dvěma způsoby.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Sub ZamenaObsahuPromennychB()

    'čísla
   
    Dim A As Integer
    Dim B As Integer

    A = 3
    B = 5

    A = A + B
    B = A - B
    A = A - B

End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Sub ZamenaObsahuPromennychC()

    'čísla

    Dim A As Integer
    Dim B As Integer

    A = 3    '011 ve dvojkové soustavě
    B = 5    '101 ve dvojkové soustavě

    'XOR ... exclusive or, exkluzivní disjunkce
    've VBA operátor Xor

    'B | A | A Xor B
    '0 | 0 | 0
    '0 | 1 | 1
    '1 | 0 | 1
    '1 | 1 | 0

    '6 ... 110 ve dvojkové soustavě
    A = A Xor B
    '3 ... 011 ve dvojkové soustavě
    B = A Xor B
    '5 ... 101 ve dvojkové soustavě
    A = A Xor B

End Sub

Existuje alternativní metoda i pro řetězce?
Ano, a dokonce může být několikrát rychlejší než při užití pomocné proměnné, jež vyžaduje spousty práce s pamětí počítače. Bavíme-li se o pojmu „string“ ve VBA (Visual Basic 4 a novější), pak máme na mysli Basic String (BSTR). Textový řetězec je v paměti uložen ve dvoubajtovém Unicode poli znaků, jemuž předchází 4 bajty (informace o délce), a který je ukončen dvoubajtovým znakem Chr(0). Na místo v paměti přitom míří ukazatel (pointer). Vtip je tedy v tom, že nemusíme měnit přímo obsah dvou proměnných, ale prohodíme jejich ukazatele, podobně jako nějaký rošťák vymění cedulky WC muži a WC ženy. K tomu všemu už ovšem potřebujeme API, především funkci CopyMemory. Upozorňuji, že její nesprávné užití může sestřelit stávající instanci aplikace Excel (nic víc, nic míň, sešit prostě před experimentováním ukládejte). Uvedená deklarace API funkce CopyMemory je platná pro 32bitový Excel. Její syntaxe odpovídá standardu, nicméně si ji řada programátorů přizpůsobuje.

1
2
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDst As _
    Any, pSrc As Any, ByVal ByteLen As Long)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Sub ZamenaObsahuTextovychPromennychAPI()

    'datový typ String
    'práce s ukazateli a API
   
    'rychlejší oproti pomocné proměnné
    'dojde k pouhé výměně ukazatelů na řetězce
   
    Dim X As String
    Dim Y As String
   
    Dim T As Long
   
    X = "Spejbl"
    Y = "Hurvínek"

    'uložení ukazatele na X
    T = StrPtr(X)
    'kopie ukazatele Y do ukazatele X, 32bit
    CopyMemory ByVal VarPtr(X), ByVal VarPtr(Y), 4
    'kopie ukazatele T do ukazatele Y
    CopyMemory ByVal VarPtr(Y), T, 4

End Sub

Funkce StrPtr (string pointer), VarPtr (variable pointer), případně ObjPtr (object pointer) v dokumentaci VBA (VB) jen tak nenajdete. Jsou nedokumentované. Zjednodušeně řečeno, vrací adresu proměnné v paměti.

Pokud se vrátíme k číslům, pak může zpracování čísel typu Long s pomocí API vypadat následovně:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sub ZamenaObsahuPromennychLongAPI()

    'datový typ Long
    'práce s ukazateli a API

    Dim X As Long
    Dim Y As Long

    Dim T As Long

    X = 100
    Y = 300

    'Long ... 4 bajty v paměti
   
    CopyMemory T, ByVal VarPtr(X), 4
    CopyMemory ByVal VarPtr(X), ByVal VarPtr(Y), 4
    CopyMemory ByVal VarPtr(Y), T, 4

End Sub

Pozn. Funkci CopyMemory můžeme nasadit i na prosté kopírování z jedné proměnné do druhé, dokonce i na pole.

1
2
3
4
5
6
7
8
9
10
11
12
13
Sub KopieTextAPI()

    'datový typ String

    Dim X As String
    Dim Y As String

    X = "Spejbl"
    Y = Space(Len(X))

    CopyMemory ByVal StrPtr(Y), ByVal StrPtr(X), LenB(X)

End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Sub KopieLongAPI()

    'datový typ Long (4 bajty)

    Dim X As Long
    Dim Y As Long

    X = 123456

    'CopyMemory Y, X, 4

    'alternativně
    'CopyMemory ByVal VarPtr(Y), X, 4

End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Sub KopiePoleLongAPI()

    'pole, datový typ Long

    Dim aPoleZdroj() As Variant
    Dim aPoleCil() As Variant

    aPoleZdroj = Array(1492, 3.1415, 65536, #8/19/2014#)
    aPoleCil = Array(1, 2, 3, 4, 5)

    'Variant ... 16 bajtů paměti pro číslo
    'celkem 16 bajtů * počet prvků v poli
    'tj. 16 * (UBound(aPoleZdroj) + 1)
    'lépe
    '16 * (UBound(aPoleZdroj) - LBound(aPoleZdroj)+ 1)
   
    Dim X As Long
    X = LenB(aPoleZdroj(0))

    CopyMemory ByVal VarPtr(aPoleCil(0)), ByVal VarPtr(aPoleZdroj(0)), 16 * _
        (UBound(aPoleCil) - LBound(aPoleCil) + 1)

End Sub

Pozn. Funkci CopyMemory lze uplatnit i v případě spojování (nabalování) řetězců.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Sub TechnikaNabalovaniAPI()

    Dim aPoleRetezce() As Variant
    Dim sRetezec As String
    Dim i As Integer
    Dim lPos As Long

    aPoleRetezce = Array("M", "á", "n", "i", "č", "k", "a")

    'vymezení délky budoucího řetězce
    sRetezec = Space(UBound(aPoleRetezce) - LBound(aPoleRetezce) + 1)

    'pro každý znak
    For i = 0 To UBound(aPoleRetezce)

        'nabalení znaku na řetězec
        CopyMemory ByVal StrPtr(sRetezec) + lPos, ByVal StrPtr(aPoleRetezce(i)), _
            LenB(aPoleRetezce(i))
       
        'úprava pozice
        lPos = lPos + LenB(aPoleRetezce(i))

    Next i

End Sub

Do hry se také často zapojí úzké spojení řetězec-bytové pole. Ale to už je jiná kapitola.

Na závěr dnešního článku ještě dva tipy.

Dvoustavová proměnná
Dvoustavovou proměnnou je myšlena proměnná typu Boolean, v níž chceme cyklicky zaměňovat True na False a naopak. První z výpisů bych ohodnotil jedním bodem, druhý dvěma…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Sub DvoustavovaPromennaA()

    'negace pravdivostní hodnoty
   
    Dim bPromenna As Boolean

    'bPromenna = ...

    'obsahuje proměnná False?
    If bPromenna = False Then
        bPromenna = True
    Else
        bPromenna = False
    End If

End Sub

Stačí si uvědomit, že ve skutečnosti provádíme negaci pravdivostní hodnoty, pro kterou slouží ve VBA operátor Not. Ten vhodně doplníme deklarací proměnné jako Static.

1
2
3
4
5
6
7
8
9
10
11
12
Sub DvoustavovaPromennaB()

    'negace pravdivostní hodnoty

    'procedura si bude pamatovat hodnotu proměnné
    'při dalším volání
    Static bPromenna As Boolean

    'negace operátorem Not
    bPromenna = Not bPromenna

End Sub

Dvoučíselná proměnná
Tímto pojmem míním proměnnou, která má nabývat jedné ze dvou číselných hodnot. Představte si formulář, který po klepnutí na tlačítko má odkrýt další nástroje, tj. rozbalit se. Fakticky upravujeme pouze jeho výšku (neberu nyní v potaz nějaký efekt rozbalení).

1
2
3
4
5
6
7
8
9
10
Private Sub CommandButton1_Click()

    'výchozí výška formuláře H = 100    
    'hodnoty H1 = 100, H2 = 300
    'H1 + H2 = 400

    'změna výšky formuláře
    Me.Height = 400 - Me.Height

End Sub