Indice
Capitolo 5.
Costrutti e cicli
In questo capitolo facciamo rientrare nella categoria dei costrutti tutte le keyword Perl che servono a generare un ciclo di iterazione o a eseguire codice in base al verificarsi di alcune condizioni.
1. Vero o falso?
Prima di iniziare a valutare come fare test con Perl vale decisamente la pena di descrivere cosa è vero e cosa è falso.
0 è falso, 1 è vero. E fin qui nulla di sconvolgente.
Una qualsiasi variabile non definita è falsa.
Una stringa non nulla è vera, una stringa nulla (es. "") è falsa. Quindi se 0 è falso, "0" invece
è vero
in quanto stringa di testo non nulla. Occorre quindi prestare molta attenzione al contesto nel quale i dati vengono valutati. Ad esempio: sappiamo che 0 è falso. Ma sarà vero o falso:
$test = "" . 0
? In questo caso la stringa è nulla, quindi il risultato è la
cifra
0, quindi falso!
2. if e unless
if
è il costrutto più fondamentale. Serve ad eseguire un arbitrario blocco di codice in base al verificarsi (o meno) di alcune condizioni. Vediamo subito la sintassi minima di
if
:
Sintassi minima di
if
Fig. 1
1:
#!
/usr/bin/perl
2:
3:
#
4:
# Sintassi minima di if
5:
#
6:
#if ( CONDIZIONE ) {
7:
# [ ... blocco di codice ... ]
8:
#}
9:
10:
$int = 5;
11:
12:
if ( $int == 5 ) {
13:
print "\$int vale 5";
14:
}
Ovviamente è un esempio stupido, in quanto abbiamo la sicurezza che $int valga 5, ma non temete: stiamo per espandere l'esempio per dargli un minimo di senso!
Sintassi completa di if
Fig. 2
1:
#!
/usr/bin/perl
2:
3:
@values = (5,6,7,8);
4:
$int = $values[ rand(4) ];
5:
6:
if ( $int == 5 ) {
7:
print "\$int vale 5";
8:
} elsif ( $int == 6 ) {
9:
print "\$int vale 6";
10:
} else {
11:
print "\$int ha un altro valore!";
12:
}
Cosa è cambiato? Prima di tutto il valore di
$int
ora viene scelto a caso fra i valori di
@values
(per i curiosi,
rand
genera un valore a caso fra 0 e il limite passato come argomento; quindi
$int = $values[ rand(4) ];
setta
$int
in base al valore contenuto nella posizione
rand(4)
).
Ma la cosa fondamentale è che il nostro costrutto
if
ora ha ragione di esistere. Alla linea 6 si verifica che
$int
valga 5, alla riga 8 (con
elsif
) si verifica che valga 6, alla 10 si decide che nessuno dei valori precedentemente verificati sussite e quindi si sceglie una via alternativa. Lanciando ripetutamente lo script, esso darà risultati differenti ad ogni esecuzione.
else
serve a scegliere una via di default (ossia una via da usare quando nessun'altra è praticabile).
elsif
è un ibrido fra
else
ed
if
, consente di tracciare un'altra via ma solo se il test di questa via è stato verificato.
if
ha una negazione naturale in
unless
che vale (né più né meno)
if not
. Quindi
if ( not $int )
è identico a
unless ( $int )
. Vedremo poi con i modificatori di comando come
unless
assuma maggiore significato.
3. while e until
Il costrutto
while
serve a ripetere un blocco di istruzioni finchè la condizione valutata da
while
è vera. Vediamo un esempio:
Sintassi di while
Fig. 3
1:
#!
/usr/bin/perl
2:
3:
$int = 0;
4:
while ( $int < 10 ) {
5:
print "\$int vale $int\n";
6:
$int++;
7:
}
In questo esempio l'istruzione da valutare e' che
$int
sia minore di 10. All'interno del blocco di codice la variabile viene incrementata di una unità ad ogni esecuzione, con l'istruzione
$int++
. In questo modo, quando
$int == 10
la condizione non è più valida e
while
termina.
Esistono due keyword in grado di modificare il corso di un ciclo
while
: sono
last
e
next
.
next
interrompe l'esecuzione del blocco di codice e salta direttamente
alla successiva iterazione
.
last
blocca l'esecuzione del blocco di codice ed esce dal ciclo
while
.
Un metodo più originale per scrivere il precedente ciclo
while
può essere il seguente:
while con last
Fig. 4
1:
#!
/usr/bin/perl
2:
3:
$int = 0;
4:
while ( 1 ) {
5:
print "\$int vale $int\n";
6:
$int++;
7:
if ( $int >= 10 ) {
8:
last;
9:
}
10:
}
Vediamo invece un uso serio di
next
! Diciamo che vogliamo modificare il codice dell'esempio precedente in modo che non stampi la riga in caso
$int
valga 5:
Uso di
next
Fig. 5
1:
#!
/usr/bin/perl
2:
3:
$int = 0;
4:
while ( $int < 10 ) {
5:
if ( $int == 5 ) {
6:
$int++;
7:
next;
8:
}
9:
print "\$int vale $int\n";
10:
$int++;
11:
}
In questo modo quando il ciclo esegue l'istruzione condizionale, se
$int
vale 5 viene incrementata e subito dopo il blocco di codice viene interrotto e si passa alla successiva iterazione del
while
con
$int
che vale 6.
Questa soluzione è sicuramente valida ma esiste una possibilità sicuramente più elegante:
continue
. Mediante l'uso di questa keyword è possibile specificare un secondo blocco di codice da eseguire alla fine del primo blocco. L'esecuzione avviene anche in caso di chiamata a
next
. Vediamo come:
La keyword
continue
Fig. 6
1:
#!
/usr/bin/perl
2:
3:
$int = 0;
4:
while ( $int < 10 ) {
5:
if ( $int == 5 ) {
6:
next;
7:
}
8:
print "\$int vale $int\n";
9:
} continue {
10:
$int++;
11:
}
In questo secondo esempio, anche quando il blocco di codice esegue il
next
, la variabile
$int
sarà comunque incrementata di una unità in quanto l'istruzione di incremento si trova nel blocco specificato da
continue
.
Esiste in Perl la possibilità di usare un keyword di significato opposto a
while
che ne è una completa negazione. Questa keyword è
until
. Tramite
until
il codice precedente si può riscrivere come:
L'uso di
until
Fig. 7
1:
#!
/usr/bin/perl
2:
3:
$int = 0;
4:
5:
#
6:
# ...equivalente a
7:
#
8:
# while ( not $int >= 10 )
9:
#
10:
# oppure a
11:
#
12:
# while ( $int < 10 )
13:
#
14:
until ( $int >= 10 ) {
15:
if ( $int == 5 ) {
16:
next;
17:
}
18:
print "\$int vale $int\n";
19:
} continue {
20:
$int++;
21:
}
4. for e foreach
Il costrutto
for
server ad iterare un blocco di codice variando il contenuto di una variabile in un insieme di valori. Vediamo subito il primo esempio:
Il ciclo
for
Fig. 8
1:
#!
/usr/bin/perl
2:
3:
for ( $int = 0; $int < 10; $int++ ) {
4:
print "\$int vale $int\n";
5:
}
Avrete già capito che questo codice è l'equivalente del codice con
while
scritto negli esempi precedenti.
Anche nei cicli
for
valgono le keyword
next
e
last
. Anche qui possiamo quindi includere una condizione per evitare che venga stampata la linea per
$int
pari a 5:
Uso di
next
nei cicli
for
Fig. 9
1:
#!
/usr/bin/perl
2:
3:
#
4:
# Sintassi di for:
5:
# for ( INIT; TEST; INCR ) {
6:
# blocco di codice;
7:
# }
8:
#
9:
for ( $int = 0; $int < 10; $int++ ) {
10:
if ( $int == 5 ) {
11:
next;
12:
}
13:
print "\$int vale $int\n";
14:
}
In questo caso non abbiamo bisogno di usare tecniche particolari per assicurarci che
$int
sia incrementata anche in caso di
last
in quanto l'istruzione di incremento è inclusa nella dichiarazione iniziale del
for
. Ovviamente la prima clausola inizializza la variabile (
$int = 0;
) mentre la seconda clausola è quella da verificare ad ogni iterazione (
$int < 10;
).
La seconda forma di
for
enumera esplicitamente i valori sui quali la variabile viene iterata. Per questa seconda versione si usa la forma
foreach
che esprime meglio il concetto di iterazione per ciascun elemento elencato. In realtà i programmatori fluenti in $Perl usano scrivere comunque la forma
for
in quanto più compatta:
foreach
Fig. 10
1:
#!
/usr/bin/perl
2:
3:
foreach $var ( 1, 2, 3, "abc", "xyz", 3.14 ) {
4:
print "A questo giro \$var contiene $var\n";
5:
}
Notate come in questo caso la specifica dei valori da iterare sia necessariamente più lunga ma consenta di includere valori differenti (stringhe di testo e numeri assieme), cosa non possibile con la prima sintassi. Questa sintassi è particolarmente utile per iterare sui valori di un array:
Iterazione sui valori di un array
Fig. 11
1:
#!
/usr/bin/perl
2:
3:
@array = ( 1, 2, 3, "abc", "Tx0's Cammelling Perl" );
4:
for $var ( @array ) {
5:
print "A questo giro \$var contiene $var\n";
6:
}
5. I modificatori di comando
Le istruzioni specificate in questo capitolo possono essere usate in coda ad una istruzione come "modificatori di comando", aggiungendo un nuovo significato (condizionale o iterativo) al comando. Ad esempio se vogliamo che una istruzione sia eseguita solo se una condizione è verificata possiamo usare uno dei due seguenti esempi:
if
standard e come "modificatore di comando"
Fig. 12
1:
#!
/usr/bin/perl
2:
3:
$int = 23;
4:
5:
if ( $int == 23 ) {
6:
print '$int vale 23!';
7:
}
8:
9:
print '$int vale 23!' if $int == 23;
Notate come
if
sia usato in coda al comando print nel secondo esempio ottenendo una sintassi decidamente più compatta, elegante e leggibile (suona più o meno come "stampa questo se $int vale 23"). Ancora più interessante risulta il
for
come modificatore:
for
come modificatore
Fig. 13
1:
#!
/usr/bin/perl
2:
3:
@elementi = ( 'abc', q/elemento 2/, qw/uno due tre/ );
4:
print for @elementi;
Con una istruzione incredibilmente compatta abbiamo mostrato (ok, magari un po' appiccicati) l'intero contenuto di un array.
6. Label di riferimento
È possibile contrassegnare un ciclo
for
o
while
ma anche un qualsiasi blocco di codice incluso fa una coppia di parentesi graffe con una etichetta. Queste label servono a fare riferimento al costrutto in maniera inequivocabile con le istruzioni
next
e
last
. Questa opportunità risulta molto utile in caso di costrutti nidificati (ossia un ciclo all'interno di un altro ciclo) in quanto una istruzione come
next
si riferisce normalmente al ciclo più prossimo e non sarebbe altrimenti possibile interromperne uno più esterno.
Facciamo l'esempio di una coppia di cicli
for
nidificati per esplorare una matrice bidimensionale:
Due cicli
for
nidificati
Fig. 14
1:
#!
/usr/bin/perl
2:
3:
ESTERNO: for $x ( 1, 2, 3, 4 ) {
4:
for $y ( 10, 20, 30 ) {
5:
last ESTERNO if $x == 4;
6:
print "X x Y: ", $x * $y;
7:
}
8:
}
Il comando
last
si riferisce in questo caso al comando
for
marcato come
ESTERNO
e non a quello più vicino (ossia il più interno).
7. Come si scrive uno schema switch/case
In Perl non esiste un costrutto particolare per descrivere una struttura a scelta multipla. È sicuramente possibile scrivere un'elenco di
if
concatenati (orrore!!), ma questa non è la soluzione più semplice. Impostando la variabile
$_
sul valore da confrontare si può usare un semplice and logico (
&&
) in questo modo:
Scrittura di un costrutto switch/case
Fig. 15
1:
#!
/usr/bin/perl
2:
3:
$_ = 'abc';
4:
5:
SWITCH: {
6:
/abc/ && do {
7:
print "Le prime tre lettere dell'alfabeto\n";
8:
last SWITCH;
9:
};
10:
/uvz/ && do {
11:
print "Le ultime tre lettere dell'alfabeto\n";
12:
last SWITCH;
13:
};
14:
/nomatch/ && do {
15:
print "Sorry, non ho trovato una corrispondenza adeguata!\n";
16:
last SWITCH;
17:
};
18:
}
Notate come l'uso della label
SWITCH
consenta il salto della restante parte di test se uno trova una corrispondenza valida. Inoltre l'uso della label aiuta a specificare agli occhi del lettore che si tratta dell'equivalente di un costrutto
switch
e a capire a cosa il
last
faccia riferimento.
Inizio Capitolo
Indice