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