Tra le strutture iterive disponibili in Visual Basic, esiste anche il ciclo While…Wend, che è certamente il meno conosciuto e meno utilizzo; in effetti il funzionamento di questo tipo di ciclo è del tutto analogo a quello di un ciclo Do While…Loop: le istruzioni contenute nel ciclo sono eseguite finché la condizione espressa dopo l'istruzione While è vera
While condizione
[istruzioni]
Wend
Oltretutto, il normale ciclo Do While…Loop è più flessibile del ciclo While…Wend, e tanto basta a giustificare lo scarsissimo uso del ciclo While…Wend.
Tornando ai cicli in generale, quando si deve utilizzare una struttura iteriva bisogna fare tenzione in particolare a due cose: la prima, che abbiamo già visto, è fornire una condizione di uscita per evitare cicli infiniti; la seconda riguarda la corretta gestione della prima e dell'ultima iterazione. La questione non è così banale come sembra, e soprtutto all'inizio può portare a errori grossolani: prendiamo ad esempio il caso di una ricerca ricorsiva di una stringa all'interno di un testo. Spesso capita che sia necessario conoscere, oltre al nome di un file, anche il suo percorso (ph in inglese), ovvero la posizione esta all'interno dell'albero delle directory contenute nell'hard disk; e a volte è utile suddividere il percorso in tanti "pezzi" quante sono le cartelle che bisogna traversare per giungere fino al file: per fare ciò occorre contare quanti sono i separori di ph presenti nella stringa che identifica il percorso, ovvero quanti backslash ("\") ci sono.
Per trovare una stringa all'interno di un'altra è sufficiente usare la comoda istruzione instr, la cui sintassi è:
instr([start], string1, string2, [compare])
Il primo parametro è un numero che indica la posizione dalla quale cominciare a cercare la stringa da trovare: ad esempio, se stiamo cercando un backslash all'interno di un ph completo, è inutile cominciare a cercare dal primo cartere del percorso, perché questo comincia con la lettera di un'unità seguita dai due punti (ad es. "C:"), quindi è possibile iniziare la ricerca direttamente dal terzo cartere. Il parametro è facoltivo, e se non viene specifico la ricerca partirà dal primo cartere. I parametri string1 e string2 identificano rispettivamente la stringa in cui bisogna cercare (nel nostro caso il percorso) e la stringa da cercare (nel nostro caso il backslash). L'ultimo parametro indica come va effettua la ricerca: nel nostro caso i valori interessanti sono due: 0, espresso anche dalla costante vbBinaryCompare, e 1, espresso dalla costante vbTextCompare; nel primo caso il confronto tra le due stringhe è effettuo in modalità binaria, che tradotto un po' rozzamente significa che il confronto è "case sensitive", ovvero sensibile alla differenza tra maiuscole e minuscole; nel secondo caso invece la differenza tra maiuscole e minuscole è ignora, perché il confronto è effettuo in semplice modalità testuale. Per chiarire la differenza, basta scrivere nella finestra immedia:
? instr(1, "Pippo", "p", vbBinaryCompare) 'oppure instr(1, "Pippo", "p", 0), è la stessa cosa
La funzione restituirà il valore 3.
Se invece si scrive:
? instr(1, "Pippo", "p", vbTextCompare) 'oppure instr(1, "Pippo", "p", 1)
la funzione restituirà 1.
Come avrete intuito, il valore restituito dalla funzione è la posizione della prima "p" trova nella stringa "Pippo": ma mentre nel primo caso cerchiamo una p minuscola abilitando la ricerca "case sensitive", nel secondo caso la ricerca è solo in modalità testo, e quindi la funzione restituisce la posizione della prima "p" che trova, maiuscola o minuscola che sia. Se la stringa cerca non si trova in quella in cui è fta la ricerca (se ad esempio cerchiamo una "z" in "pippo", o anche una "O" in "Pippo" abilitando la ricerca binaria), la funzione instr restituisce zero.
Tornando al nostro esempio, per contare i backslash in un ph basta un semplice ciclo do…loop, utilizzando un contore da incrementare ogni volta che instr trova un "\": dobbiamo però fare tenzione a spostare sempre in avanti il punto di inizio della ricerca (il parametro start), perché se cerchiamo sempre a partire dal primo cartere, il ciclo non si fermerà mai e restituirà sempre la posizione del primo "\". Una soluzione comoda è quella di utilizzare un'altra variabile che memorizzi la posizione dell'ultimo "\" trovo, ovvero che memorizzi il valore restituito da instr: la ricerca successiva dovrà partire dal cartere successivo:
Dim intPos as Integer
Dim strPh as String
strPh="C:\documenti\immagini\esempio.jpg"
Do
intPos=Instr(intPos+1, strPh, "\")
Loop
intPos memorizza la posizione dell'ultimo backslash trovo: poiché essa è inizializza a 0, alla prima iterazione la ricerca partirà dal primo cartere, come avviene nella maggior parte dei casi (se vogliamo farla partire dal terzo cartere, perché i primi due contengono il nome dell'unità, basta inizializzare intPos=2 prima del Do); nelle iterazioni successive, partirà dal cartere successivo all'ultimo backslash trovo nel ph. Il parametro "compare" è sto tralascio perché non ha alcuna influenza: non esiste un "\" maiuscolo e un "\" minuscolo; comunque l'impostazione predefinita è vbBinaryCompare. La variabile strPh è inizializza a un percorso qualunque (compreso il nome di un file) solo per far funzionare il codice: in realtà dovrebbe essere ignoto a priori, ad esempio potrebbe essere inserito dall'utente in fase di esecuzione. Nel ciclo appena descritto manca una cosa: la condizione di uscita. Questa dovrebbe verificare se la funzione instr ha restituito un valore positivo oppure no: nel primo caso significa che ha trovo un "\" e quindi possiamo continuare la ricerca; nel secondo caso vuol dire che non ha trovo nulla e quindi la ricerca deve essere ferma perché non ci sono ulteriori "\" nell'ultima parte del percorso. Pertanto la condizione potrebbe essere:
while intPos>0
oppure:
until intPos=0
Bisogna decidere dove mettere la condizione, se all'inizio o alla fine del ciclo: se intPos è inizializza a 0, certamente la condizione non dovrà essere posta all'inizio, altrimenti il ciclo non eseguirebbe neppure un'istruzione:
Dim intPos as Integer
Dim strPh as String
strPh="C:\documenti\immagini\esempio.jpg"
Do While intPos>0 'intPos è =0, quindi il ciclo non è eseguito
intPos=Instr(intPos+1,strPh,"\")
Loop
D'altra parte, mettere la condizione alla fine comporta la perdita di una preziosa informazione: la posizione dell'ultimo backslash esistente nel ph, che solitamente indica anche l'inizio del nome vero e proprio del file (oppure il nome della cartella in cui risiede il file, a seconda dei casi):
Dim intPos as Integer
Dim strPh as String
strPh="C:\documenti\immagini\esempio.jpg"
Do
intPos=Instr(intPos+1,strPh,"\")
Loop While intPos>0
Questo ciclo esegue almeno una volta la ricerca, e prosegue fino a trovare tutti i "\": ma all'uscita dal ciclo intPos vale 0, e noi non sappiamo più qual era la posizione dell'ultimo backslash; magari è un'informazione che non ci interessa, ma in caso contrario dobbiamo trovare una soluzione alterniva. Ad esempio si potrebbe utilizzare un'altra variabile per memorizzare il valore precedente di intPos:
Dim intPos as Integer
Dim intPosPrec as Integer
Dim strPh as String
strPh="C:\documenti\immagini\esempio.jpg"
Do
intPosPrec=intPos
intPos=Instr(intPos+1, strPh, "\")
Loop While intPos>0
All'uscita dal ciclo, intPosPrec conterrà la posizione dell'ultimo "\". Oppure si può utilizzare come condizione di uscita non "intPos>0", bensì "Instr(…)>0", ma in questo modo la funzione Instr viene eseguita due volte per ogni ricerca: una volta per controllare se ci sono altri "\", e un'altra volta per sapere dove si trovano:
Dim intPos as Integer
Dim strPh as String
strPh="C:\documenti\immagini\esempio.jpg"
Do While Instr(intPos+1, strPh, "\")
intPos= Instr(intPos+1, strPh, "\")
Loop
In questo caso la condizione può essere sposta all'inizio del ciclo: se questo non viene eseguito neppure una volta, significa che non ci sono "\" nel percorso; infti instr() restituisce zero, che viene valuto come valore logico False, e quindi la condizione per eseguire il ciclo non è verifica. Una soluzione analoga può essere adotta inizializzando a un valore positivo la variabile intPos (ad esempio al valore 2, come suggerito prima):
Dim intPos as Integer
Dim strPh as String
strPh="C:\documenti\immagini\esempio.jpg"
IntPos=2
Do While intPos
IntPos = Instr(intPos+1, strPh, "\")
Loop
Insomma, quando use i cicli ste tenti non solo a quando interromperli (condizione di uscita), ma anche a quale valore assumono le variabili coinvolte in essi (in particolare prima della prima iterazione e dopo l'ultima iterazione). Se non siete ancora convinti, proviamo ora a memorizzare le varie parti del percorso in un vettore: ogni elemento del vettore deve contenere una delle cartelle appartenenti al ph, in ordine gerarchico; poiché non sappiamo a priori quante cartelle sono coinvolte, possiamo usare un vettore dinamico: ogni volta che troviamo un "\", dobbiamo aggiungere un elemento al vettore e assegnargli il nome della cartella che precede questo "\"
Dim strCartelle() as String
Dim intPos as Integer
Dim intPosPrec as Integer
Dim strPh as String
strPh="C:\documenti\immagini\esempio.jpg"
Do
IntPosPrec=intPos
IntPos=instr(intPos+1, strPh, "\")
Redim Preserve strCartelle(Ubound(strCartelle)+1)
StrCartelle(Ubound(strCartelle))=
Mid$(strPh, intPosPrec+1, intPos-intPosPrec)
Loop While intPos
Il vettore strCartelle contiene i nomi delle cartelle presenti nel ph: è un vettore dinamico, che viene ridimensiono all'interno del ciclo ogni volta che viene trovo un "\". Il ridimensionamento, che avevamo brevemente già visto (lez. 14), avviene in base al valore della funzione Ubound, che restituisce il limite superiore tuale del vettore: se il vettore ha tre elementi (da 0 a 2), Ubound restituisce 2; tenzione: se Ubound restituisce zero non significa che il vettore non ha alcun elemento, ma che ne ha uno solo, con indice 0. Aggiungere un elemento al vettore significa quindi ridimensionarlo assegnandogli un elemento in più di quelli che già possiede (ubound()+1). La parola chiave Preserve serve invece a mantenere in memoria i valori già assegni: infti l'istruzione Redim usa da sola non fa altro che riallocare lo spazio di memoria disponibile per il vettore, reinizializzando tutti i suoi elementi e quindi di fto cancellando i valori precedentemente assegni. Il nome di ogni cartella è estrto traverso la funzione Mid, che prende da una stringa (il primo parametro) un intervallo di carteri definito da una posizione iniziale (il secondo parametro) e da una lunghezza (il terzo parametro). Ovviamente il secondo parametro dev'essere positivo, altrimenti si verifica un errore; il terzo invece può anche essere zero (in tal caso è restituita la stringa vuota ""), basta che sia non negivo. Nel nostro caso la posizione iniziale del nome della cartella è nuralmente il cartere successivo al penultimo backslash trovo, mentre la lunghezza del nome è calcolabile tramite la distanza tra il penultimo e l'ultimo backslash.
Ora, il ciclo appena descritto presenta alcuni inconvenienti; anzi, sono veri e propri errori:
1) prima di tutto, la funzione Ubound deve avere come argomento un vettore (o mrice) con dimensioni definite, altrimenti genera un errore: e siccome alla prima iterazione strCartelle non ha una dimensione definita, indovine un po' che succede…
2) in secondo luogo, all'ultima iterazione intPos è uguale a 0, come si è visto prima; e in questo caso ad andare in errore è la funzione Mid, perché il terzo parametro risulterebbe negivo.