Per ripetere più volte lo stesso blocco di codice si utilizzano alcuni costrutti che implementano i loop. Di questi ne distinguiamo due tipi; quelli che prevedono un numero predefinito cicli e quelli che si ripetono finché una condizione particolare non è raggiunta. In C le due situazioni sfumano l’una nell’altra, in quanto si preferisce usare il costrutto for quando il numero di iterazioni è predeterminato, al contrario si usa il while, ma la flessibilità della sintassi del C permette di usare indifferentemente i due costrutti.
Il ciclo for agisce in base a tre parametri, nessuno dei quali è obbligatorio. La modalità standard con cui si usa è la seguente
for (i = 0; i < N; i++) {
/* a lot of code */
}
La variabile i è di tipo intero, N invece può essere una variabile intera, un valore costante esplicita o definita con una #define. Questo tipo di scrittura si usa per esegue esattamente N volte un blocco di codice, e durante il ciclo la variabile i assume valori da 0 a N-1. All’uscita dal for i sarà valorizzata a N. La sintassi di questo costrutto si presta bene ad essere usato in modi anche molto diversi dall’uso standard che abbiamo esposto.
Il for segue una logica sintetizzata da questo schema, dove A, B, C rappresentano un blocco di una o più operazioni e LOOP il blocco di istruzioni da eseguire più volte:
for(A;B;C) LOOP;
|
______|______
| A |
| |
¯¯¯¯¯¯|¯¯¯¯¯¯
------------|
| / \
| / \ F
| / B \____ EXIT
| \ ? /
| \ /
| \ /
| | T
| ______|______
| | LOOP |
| | |
| ¯¯¯¯¯¯|¯¯¯¯¯¯
| ______|______
| | C |
| | |
| ¯¯¯¯¯¯|¯¯¯¯¯¯
-------------
Il blocco A è di inizializzazione e viene eseguito una sola volta. Si passa poi alla valutazione del blocco B, il cui vincolo è restituire un risultato logico, ovvero un valore che può essere interpretato come vero o falso: se è vero l’esecuzione al blocco LOOP. Infine si esegue il blocco C, dopodiché si ritorna a valutare B e si termina quando B restituisce falso.
Ad esempio, creiamo una funzione che calcoli il fattoriale di un numero:
int Fact(int n)
{
int i, fact;
fact = 1;
for (i = 1; i <= n; i++) {
fact *= i;
}
return fact;
}
Si può notare che la variabile i non deve essere necessariamente inizializzata a zero, ma gli si può assegnare qualunque valore, in base alle nostre necessità, e può essere anche un numero negativo. La condizione di uscita ci permette di eseguire il prodotto indicato nel loop esattamente n volte; ogni volta con un valore via via crescente di i, poiché questa variabile viene incrementata ad ogni ciclo. Questa rappresenta la modalità standard in cui viene usato il for, e pertanto è immediatamente comprensibile da tutti, ma la stessa funzione può essere implementata come segue ottenendo un risultato assolutamente equivalente:
int Fact(int n)
{
for (int i = n; i > 1; n *= --i) ;
return n;
}
In quest’esempio la variabile i è dichiarata all’interno del for pertanto la sua visibilità è limitata all’interno della sua struttura complessiva ed è valorizzato con il numero di cicli da effettuare e viene decrementato ad ogni ciclo, e subito dopo il decremento viene effettuato il prodotto, nella stessa operazione; azione che in C è perfettamente lecita, rendendo inutile aggiungere un blocco loop; infatti alla fine della riga del for compare il simbolo ; ma si sarebbe potuto aggiungere anche una coppia di parentesi vuote {}.
La stessa funzione, riscritta nel modo seguente risulta ancor meno comprensibile: è stato invertito il ruolo tra indice e variabile di input/output, nel for è stato usato solo il blocco della condizione di uscita, ma all’interno vengono eseguite più operazioni, separate da una virgola e non c’è nessun confronto: come funziona questo codice?
int Fact(int n)
{
int i = n--;
for (; i *= n--, n;) {}
return i;
}
La virgola rappresenta un operatore a tutti gli effetti[*], viene valutata il lato sinistro della virgola, ovvero il prodotto e il decremento, poi il lato desto; il risultato dell’operatore virgola è il valore della variabile n. Questo risultato è interpretato come vero finché la variabile non è nulla, e falso quando vale zero: appena n si annulla il loop termina.
Abbinare il comando break al for permette l’uscita dal blocco di loop quando si verifica una condizione. Nel caso seguente è nota la lunghezza dell’array in cui si deve effettuare la ricerca del valore n, quindi il numero massimo di cicli è determinato. Una volta trovato il valore si può uscire dal ciclo, pertanto si può stabilire, in base al valore dell’indice se la lista è stata completamente scorsa, senza quindi trovare nulla, o se è stato trovato l’elemento.
int i;
int a[N] = { 7, 4, 9, 0, 6, 3 };
int n = 0;
for (i = 0; i < N; i++) {
if (a[i] == n)
break;
}
if (i == N)
printf("Valore non trovato\n");
else
printf("Trovato in posizione %d\n", i);
Un caso estremo è rappresentato da quest’esempio: il for è completamente senza parametri, ciò equivale a creare un loop infinito, che sarà interrotto dal verificarsi di una condizione, in questo caso l’estrazione di un determinato numero casuale.
srand(time(NULL));
for (;;) {
if (rand() == 0)
break;
}
Questo esempio mette in luce una il fondamentale rischio presente nell’uso dei loop, ovvero la necessità di fornire una possibilità di uscita dopo un numero limitato di cicli, pena la mancanza di controllo sulla durate del codice o addirittura la possibilità che il ciclo non termini mai!
Insomma il linguaggio C mette a disposizione uno strumento moto potente per la realizzazione e il controllo di loop, e per questo da usare con la massima attenzione.