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 |