Tra
le strutture iterative disponibili in Visual Basic, esiste
anche il ciclo While
Wend, che è certamente
il meno conosciuto e meno utilizzato; 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
iterativa bisogna fare attenzione 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 soprattutto 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
(path in inglese), ovvero la posizione esatta 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 attraversare per giungere
fino al file: per fare ciò occorre contare quanti sono
i separatori di path 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
path completo, è inutile cominciare a cercare dal primo
carattere 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 carattere.
Il parametro è facoltativo, e se non viene specificato
la ricerca partirà dal primo carattere.
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 effettuata 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
è effettuato 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 è ignorata, perché il confronto
è effettuato in semplice modalità testuale.
Per chiarire la differenza, basta scrivere nella finestra
immediata:
?
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" trovata 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 cercata non si trova in quella in cui è
fatta 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
path basta un semplice ciclo do
loop, utilizzando un
contatore da incrementare ogni volta che instr trova un "\":
dobbiamo però fare attenzione a spostare sempre in
avanti il punto di inizio della ricerca (il parametro start),
perché se cerchiamo sempre a partire dal primo carattere,
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 "\"
trovato, ovvero che memorizzi il valore restituito da instr:
la ricerca successiva dovrà partire dal carattere successivo:
Dim
intPos As Integer
Dim strPath As String
strPath = "C:\documenti\immagini\esempio.jpg"
Do
intPos = Instr(intPos+1, strPath, "\")
Loop |
intPos
memorizza la posizione dell'ultimo backslash trovato: poiché
essa è inizializzata a 0, alla prima iterazione la
ricerca partirà dal primo carattere, come avviene nella
maggior parte dei casi (se vogliamo farla partire dal terzo
carattere, perché i primi due contengono il nome dell'unità,
basta inizializzare intPos=2 prima del Do); nelle iterazioni
successive, partirà dal carattere successivo all'ultimo
backslash trovato nel path.
Il parametro "compare" è stato tralasciato
perché non ha alcuna influenza: non esiste un "\"
maiuscolo e un "\" minuscolo; comunque l'impostazione
predefinita è vbBinaryCompare.
La variabile strPath è inizializzata 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 trovato un "\" e quindi possiamo continuare la
ricerca; nel secondo caso vuol dire che non ha trovato nulla
e quindi la ricerca deve essere fermata perché non
ci sono ulteriori "\" nell'ultima parte del percorso.
Pertanto la condizione potrebbe essere:
oppure:
Bisogna
decidere dove mettere la condizione, se all'inizio o alla
fine del ciclo: se intPos è inizializzata a 0, certamente
la condizione non dovrà essere posta all'inizio, altrimenti
il ciclo non eseguirebbe neppure un'istruzione:
Dim
intPos As Integer
Dim strPath As
String
strPath = "C:\documenti\immagini\esempio.jpg"
Do While intPos>0 'intPos
è =0, quindi il ciclo
non è eseguito
intPos = Instr(intPos+1,strPath,"\")
Loop |
D'altra
parte, mettere la condizione alla fine comporta la perdita
di una preziosa informazione: la posizione dell'ultimo backslash
esistente nel path, 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 strPath As
String
strPath = "C:\documenti\immagini\esempio.jpg"
Do
intPos = Instr(intPos+1,strPath,"\")
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 alternativa.
Ad esempio si potrebbe utilizzare un'altra variabile per memorizzare
il valore precedente di intPos:
Dim
intPos As Integer
Dim intPosPrec As
Integer
Dim strPath As
String
strPath = "C:\documenti\immagini\esempio.jpg"
Do
intPosPrec = intPos
intPos = Instr(intPos+1, strPath, "\")
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 strPath As
String
strPath = "C:\documenti\immagini\esempio.jpg"
Do While Instr(intPos+1,
strPath, "\")
intPos = Instr(intPos+1, strPath, "\")
Loop |
In
questo caso la condizione può essere spostata all'inizio
del ciclo: se questo non viene eseguito neppure una volta,
significa che non ci sono "\" nel percorso; infatti
instr() restituisce zero, che viene valutato come valore logico
False, e quindi la condizione per eseguire il ciclo non è
verificata. Una soluzione analoga può essere adottata
inizializzando a un valore positivo la variabile intPos (ad
esempio al valore 2, come suggerito prima):
Dim
intPos As Integer
Dim strPath As
String
strPath = "C:\documenti\immagini\esempio.jpg"
IntPos = 2
Do While intPos
IntPos = Instr(intPos+1, strPath, "\")
Loop |
Insomma,
quando usate i cicli state attenti 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 path, 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 strPath As
String
strPath="C:\documenti\immagini\esempio.jpg"
Do
IntPosPrec = intPos
IntPos = instr(intPos+1, strPath, "\")
ReDim Preserve
strCartelle(Ubound(strCartelle)+1)
StrCartelle(Ubound(strCartelle))
= Mid$(strPath, intPosPrec+1, intPos - _ intPosPrec)
Loop While intPos |
Il
vettore strCartelle contiene i nomi delle cartelle presenti
nel path: è un vettore dinamico, che viene ridimensionato
all'interno del ciclo ogni volta che viene trovato un "\".
Il ridimensionamento, che avevamo brevemente già visto
(lez. 14), avviene in base al valore della funzione Ubound,
che restituisce il limite superiore attuale del vettore: se
il vettore ha tre elementi (da 0 a 2), Ubound restituisce
2; attenzione: 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à assegnati: infatti l'istruzione
Redim usata da sola non fa altro che riallocare lo spazio
di memoria disponibile per il vettore, reinizializzando tutti
i suoi elementi e quindi di fatto cancellando i valori precedentemente
assegnati. Il nome di ogni cartella è estratto attraverso
la funzione Mid, che prende da una stringa (il primo parametro)
un intervallo di caratteri 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 negativo.
Nel nostro caso la posizione iniziale del nome della cartella
è naturalmente il carattere successivo al penultimo
backslash trovato, 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 matrice) con dimensioni definite, altrimenti
genera un errore: e siccome alla prima iterazione strCartelle
non ha una dimensione definita, indovinate 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 negativo.
Nella
prossima lezione spiegherò come risolvere gli errori:
nel frattempo però sarebbe bene che ci proviate voi
da soli (tanto è facile
), eventualmente spedendomi
le vostre soluzioni. Per chi non lo sapesse, il mio indirizzo
è:
giorgio102@libero.it
Avete tempo fino a quando sarà pubblicata la prossima
lezione.
|