› Sviluppare funzionalita su Microsoft Office con VBA › Scambiare contenuto di due variabili
-
AutoreArticoli
-
Ogni tanto accade di dover scambiare tra loro il contenuto di due variabili. Sembra proprio che il nostro VBA non permetta lo scambio direttamente, ma che sia necessario passare attraverso una variabile di appoggio temporanea:
tmp = s1 s1 = s2 s2 = tmpQuesto piccolo spuntino di Excel, tratto da uno dei suggerimenti di Francesco Balena, ci mostra invece che basta una chiamata ad una API piuttosto comune, CopyMemory....
[Leggi tutto al seguente link: https://www.excelvba.it/forumexcel/scambiare-contenuto-di-due-variabili/]Per utilzzare il codice con Excel 64 Bit
Option Explicit ' scambio si stringhe tra loro ' da I trucchi di VB6 di F. Balena #If VBA7 Then Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ (dest As Any, source As Any, ByVal numBytes As Long) #Else Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ (dest As Any, source As Any, ByVal numBytes As Long) #End If 'metodo classico Sub swap() Dim tmp As String, s1 As String, s2 As String Randomize Timer s1 = String(99999999#, Chr(Int(Rnd * 26) + 65)) s2 = String(99999999#, Chr(Int(Rnd * 26) + 65)) tmp = s1 s1 = s2 s2 = tmp End Sub 'metodo smart Sub swap2() Dim s1 As String Dim s2 As String #If VBA7 Then Dim saveAddr As LongPtr #Else Dim saveAddr As Long #End If Randomize Timer s1 = String(99999999#, Chr(Int(Rnd * 26) + 65)) s2 = String(99999999#, Chr(Int(Rnd * 26) + 65)) ' salva il descrittore della prima stringa saveAddr = StrPtr(s1) ' copia il descrittore di s2 in quello di s1 (32 bit) CopyMemory ByVal VarPtr(s1), ByVal VarPtr(s2), 4 ' completa lo scambio dei descrittori CopyMemory ByVal VarPtr(s2), saveAddr, 4 End Sub Sub test_swap() Dim t1 As Single, t2 As Single Dim f1 As Single, f2 As Single Dim s1 As String, s2 As String t1 = Timer swap t2 = Timer f1 = (t2 - t1) * 1000 t1 = Timer swap2 t2 = Timer f2 = (t2 - t1) * 1000 MsgBox "1° test tempo trascorso = " & _ Format(f1, "0.000 \m\s") & vbNewLine & _ "2° test tempo trascorso = " & _ Format(f2, "0.000 \m\s") End SubCiao
alcune considerazioni.
1) a me non è mai capitato di dovere scambiare il valore di due variabili.
2) che vantaggio c'è a scrivere tutta la procedura qui sopra descritta invece di usare una variabile temporanea?
3) se proprio fosse necessario avrei piuttosto pensato a qualcosa del genere
Sub pippo() Dim a Dim b a = 100.5 b = "pluto" a = a & "||Luca||" & b b = Left(a, InStr(1, a, "||Luca||") - 1) a = Right(a, Len(a) - Len("||Luca||") - Len(b)) End SubE' brutta ma comiuque più breve...
Ciao
Luca
Ciao Luca,
1) a me sì anche quando, magari solo per esercizio, mi sono trovato a scrivere le mie routine di sort; nota che banalmente ad esempio in python basta scrivere semplicemente a = b e lo swap è fatto 🙂
2) il vantaggio (teorico: parliamo di qualche millisecondo) è nella performance, il guadagno è nelle stringhe di grandi dimensioni;
3) benissimo, è sempre un'alternativa :), e si torna a parlare di performances perchè se devi scambiare due valori numerici poi li devi convertire e il compilatore "perde tempo".
Comunque anche a riscrivere procedure banali si tratta di dar corso a un pensiero laterale che male non fa, anche se naturalmente occorre tener conto delle correzioni di oggi per i sistemi a 64 bit come giustamente ha proposto Mirko.
Lo scopo del fare articoli un po' inusuali è anche per stimolare la curiosità
Ciao a tutti.
Sembra proprio che il nostro VBA non permetta lo scambio direttamente, ma che sia necessario passare attraverso una variabile di appoggio temporanea
Anche se, a causa dell'inefficienza del compilatore VBA, l'utilizzo della API può essere conveniente, trovo errata la premessa.
In nessun caso lo scambio può avvenire direttamente, allo stesso modo in cui non si può scambiare il contenuto di due bottiglie senza usare un terzo contenitore.
Se potessimo leggere il codice della function CopyMemory vedremmo che questa utilizza un'area di memoria temporanea (o un registro della CPU, forse, per variabili di pochi Byte) per effettuare lo scambio.
Allo stesso modo, se guardassimo le istruzioni Assembler generate dall'istruzione Python, probabilmente osserveremmo la stessa cosa.
Riguardo alle routines di ordinamento, che sono quelle in cui VBA ansima pesantemente
, io di solito copio i valori sul foglio e sfrutto l'ordinamento di Excel che è invece, piuttosto efficiente.zer0kelvin wrote:io di solito copio i valori sul foglio e sfrutto l'ordinamento di Excel
Questa è una tecnica normale sotto Excel ed è pienamente condivisibile.
Del resto il codice proposta voleva essere svincolato da Excel 🙂
Ciao a tutti
Ringrazio VF per le risposte. Non volevano essere sterili polemiche...
Però, a valle di tutto, volevo capire se c'è qualche vantaggio ad usare CopyMemory può portre qualche vantaggio pratico (chiedo scusa per la brutalità) ad un normale utilizzatore di VBA. Ovvero se vi sono altre possibilità di uso (diverse dallo swop di variabili) tale per cui risulti uno strumento utile.
Ciao a tutti.
PS concordo appieno con
Comunque anche a riscrivere procedure banali si tratta di dar corso a un pensiero laterale che male non fa, [...]
Lo scopo del fare articoli un po' inusuali è anche per stimolare la curiosità
Non ci sono mai sterili polemiche se si parla di descrivere codice e tecniche, il confronto è sempre, sempre costruttivo nel rispetto reciproco.
Luca73 wrote:se c'è qualche vantaggio ad usare CopyMemory
La risposta veloce è no, al livello delle applicazioni che costruiamo noi nel nostro quotidiano. Parlo per me ma credo che sia una questione condivisa, per lo più non siamo professionisti della programmazione ma dilettanti avanzati (qualcuno che si tira fuori c'è, mi viene in mente Oregon ossia A. G. che purtroppo non vedo più tra i nostri frequentatori).
La risposta complessa è sì, ci son vantaggi grandi a livello di ottimizzazione delle prestazioni soprattutto quando si macinano grandi moli di dati.
La risposta diplomatica è ni, nel senso che serve valutare caso per caso
Ciao a tutti,
@vecchio frac: molto interessante quanto proponi. Mi permetto di suggerire l'utilizzo di un timer più preciso (MicroTimer) per apprezzare le differenze anche su stringhe più corte (anche soli 9 caratteri!).
Ripropongo il tuo codice con le modifiche necessarie per poterlo provare subito (lanciare la sub TestRun):
`Option Explicit ' scambio si stringhe tra loro ' da I trucchi di VB6 di F. Balena Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ (dest As Any, source As Any, ByVal numBytes As Long) '-------------- copy & paste in a standard module --------------- ' 'MicroTimer function 'Found on the net ' #If VBA7 Then Private Declare PtrSafe Function getFrequency _ Lib "kernel32" _ Alias "QueryPerformanceFrequency" ( _ cyFrequency As Currency) _ As Long Private Declare PtrSafe Function getTickCount _ Lib "kernel32" _ Alias "QueryPerformanceCounter" _ (cyTickCount As Currency) _ As Long #Else Private Declare Function getFrequency _ Lib "kernel32" _ Alias "QueryPerformanceFrequency" ( _ cyFrequency As Currency) _ As Long Private Declare Function getTickCount _ Lib "kernel32" _ Alias "QueryPerformanceCounter" _ (cyTickCount As Currency) _ As Long #End If Public nCnt As Integer Public sMsgTmr As String Public sMsgMcrTmr As String Public Function MicroTimer() As Double Dim cyTicks1 As Currency Static cyFrequency As Currency MicroTimer = 0 If cyFrequency = 0 Then getFrequency cyFrequency getTickCount cyTicks1 If cyFrequency Then MicroTimer = cyTicks1 / cyFrequency End Function 'metodo classico Sub swap() Dim tmp As String, s1 As String, s2 As String Randomize Timer s1 = String(9999#, Chr(Int(Rnd * 26) + 65)) s2 = String(9999#, Chr(Int(Rnd * 26) + 65)) tmp = s1 s1 = s2 s2 = tmp End Sub 'metodo smart Sub swap2() Dim s1 As String Dim s2 As String Dim saveAddr As Long Randomize Timer s1 = String(9999#, Chr(Int(Rnd * 26) + 65)) s2 = String(9999#, Chr(Int(Rnd * 26) + 65)) ' salva il descrittore della prima stringa saveAddr = StrPtr(s1) ' copia il descrittore di s2 in quello di s1 (32 bit) CopyMemory ByVal VarPtr(s1), ByVal VarPtr(s2), 4 ' completa lo scambio dei descrittori CopyMemory ByVal VarPtr(s2), saveAddr, 4 End Sub Sub test_swap() Dim t1 As Single, t2 As Single Dim f1 As Single, f2 As Single, f3 As Single t1 = Timer swap t2 = Timer f1 = (t2 - t1) * 1000 t1 = Timer swap2 t2 = Timer f2 = (t2 - t1) * 1000 sMsgTmr = "Timer - run " & nCnt & vbLf & "1° test (swap) tempo trascorso = " & _ Format(f1, "0.0000 \m\s") & vbNewLine & _ "2° test (api) tempo trascorso = " & _ Format(f2, "0.0000 \m\s") End Sub Sub test_swapMT() Dim t1 As Double, t2 As Double Dim f1 As Double, f2 As Double, f3 As Double t1 = MicroTimer swap t2 = MicroTimer f1 = (t2 - t1) * 1000 t1 = MicroTimer swap2 t2 = MicroTimer f2 = (t2 - t1) * 1000 sMsgMcrTmr = "MicroTimer - run " & nCnt & vbLf & "1° test (swap) tempo trascorso = " & _ Format(f1, "0.0000 \m\s") & vbNewLine & _ "2° test (api) tempo trascorso = " & _ Format(f2, "0.0000 \m\s") End Sub Sub TestRun() nCnt = nCnt + 1 test_swap test_swapMT MsgBox sMsgTmr & vbLf & sMsgMcrTmr End Sub `Ciao.
Mi piace la funzione per il calcolo del tempo così accurata, io ne avevo una in canna però precisa solo fino al millisecondo
dai che ne facciamo un articoletto per la prossima volta!
-
AutoreArticoli
