1. Introduzione
Cominciamo con una domanda: perché mai annidare un ciclo dentro un altro? Spesso i dati o i problemi non sono organizzati su un’unica linea, ma, ad esempio, in una tabella, in una griglia o persino in una struttura multidimensionale. Mettiamo che vogliate stampare a schermo la tabellina, attraversare un array bidimensionale oppure calcolare le intersezioni tra tutte le coppie di elementi. In questi casi un solo ciclo non basta — serve un ciclo nel ciclo.
Nella programmazione un ciclo annidato è come due sveglie: quella esterna inizia a suonare e al suo interno se ne avvia un’altra che suonerà ogni volta finché la prima è attiva. Così, mentre si svolge una «iterazione esterna», quella interna percorre tutto il suo ciclo (e lo fa di nuovo per ogni iterazione dell’esterna).
Un buon esempio sono ore e minuti. Le ore sono il ciclo esterno da 0 a 23, i minuti sono il ciclo interno da 0 a 59. A ogni avanzamento del ciclo esterno, quello interno fa in tempo a scorrere tutti i suoi valori.
2. Sintassi dei cicli annidati
In Java la sintassi dei cicli annidati non differisce da quella dei cicli normali — basta scrivere un ciclo all’interno del corpo di un altro. Vediamo alcuni esempi con for e while:
// Ciclo esterno for
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++) // Ciclo interno for
{
System.out.print(i + "," + j + " ");
}
System.out.println(); // A capo dopo il ciclo interno
}
Qui il ciclo esterno controlla la variabile i (da 0 a 2), mentre quello interno la variabile j (da 0 a 3). Per ogni valore di i il ciclo interno percorre completamente da j == 0 a j == 3. Se eseguite questo codice, vedrete una piccola tabella di coordinate:
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
Un esempio analogo con l’utilizzo di while:
int i = 0;
while (i < 3)
{
int j = 0;
while (j < 4)
{
System.out.print(i + "," + j + " ");
j++;
}
System.out.println();
i++;
}
Attenzione: a ogni iterazione del ciclo esterno la variabile del ciclo interno (j) deve essere reinizializzata; altrimenti vedremo solo una riga!
3. Esempi di utilizzo dei cicli annidati
Esempio 1: stampa di una scacchiera (8x8)
Come primo esercizio, stampiamo a schermo una scacchiera classica in celle nere e bianche (supponiamo che ‘#’ sia nera e ‘.’ bianca). Realizziamolo con cicli annidati for:
for (int row = 0; row < 8; row++)
{
for (int col = 0; col < 8; col++)
{
// Se la somma di indice di riga e di colonna è pari, la cella è bianca, altrimenti è nera
if ((row + col) % 2 == 0)
System.out.print("_");
else
System.out.print("#");
}
System.out.println(); // Dopo ogni riga, vai a capo
}
Risultato:
_#_#_#_#
#_#_#_#_
_#_#_#_#
#_#_#_#_
_#_#_#_#
#_#_#_#_
_#_#_#_#
#_#_#_#_
Punto importante: l’annidamento fa sì che per ogni riga (row) si percorrono completamente tutte le colonne (col). Senza annidamento non otterremmo la struttura della scacchiera — solo una riga o una colonna.
Esempio 2: tabellina
Uno dei classici casi d’uso dei cicli annidati! Stampiamo la tabellina da 1–9:
for (int i = 1; i <= 9; i++)
{
for (int j = 1; j <= 9; j++)
{
System.out.print(i * j + "\t");
}
System.out.println();
}
La formattazione i * j + "\t" aggiunge tabulazioni, così la tabella appare allineata.
Risultato:
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
...
9 18 27 36 45 54 63 72 81
4. Cicli annidati e loro controllo — particolarità
Sull’effetto di break e continue nei cicli annidati
Qui cascano in molti, soprattutto all’inizio! Se usate break o continue nel ciclo interno, essi influiscono solo su quel ciclo. Quello esterno continua a funzionare come se nulla fosse.
Esempio: uscita anticipata solo dal ciclo interno
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
if (j == 3)
break; // usciamo solo dal ciclo interno!
System.out.print(i + "," + j + " ");
}
System.out.println();
}
Risultato:
0,0 0,1 0,2
1,0 1,1 1,2
2,0 2,1 2,2
Se avete bisogno di uscire immediatamente da entrambi i cicli annidati (per esempio per terminare la ricerca al primo riscontro positivo in una tabella), di solito si usa un flag o una tecnica specifica (per esempio return, se tutto avviene all’interno di una funzione).
5. Visualizzazione dei cicli annidati
A volte è difficile «vedere» la sequenza di esecuzione dei cicli annidati. Diamo un’occhiata al seguente diagramma di flusso:
In forma tabellare — quante iterazioni in totale ci saranno con i da 1 a 3, j da 1 a 4?
| i | j (iterato per ogni i) | Iterazioni del ciclo interno |
|---|---|---|
| 1 | 1, 2, 3, 4 | 4 |
| 2 | 1, 2, 3, 4 | 4 |
| 3 | 1, 2, 3, 4 | 4 |
| Totale: 3 × 4 = 12 |
6. Errori e insidie nell’uso dei cicli annidati
Un errore comune è inizializzare in modo scorretto la variabile del ciclo interno. Per esempio, dichiararla fuori dal ciclo esterno ma non azzerarla a ogni passo. Di conseguenza il ciclo interno può non eseguirsi affatto oppure comportarsi in modo errato.
int j = 0;
for (int i = 0; i < 3; i++)
{
while (j < 4) // Ops! j potrebbe già essere pari a 4 dopo la prima iterazione.
{
System.out.print(i + "," + j + " ");
j++;
}
System.out.println();
}
Qui il ciclo verrà eseguito una sola volta. Ricordate di inizializzare le variabili dei cicli interni all’interno dei cicli esterni!
Inoltre, se per errore scrivete due cicli annidati con le stesse variabili (for (int i = 0; ...) { for (int i = 0; ...) { ... } }), il compilatore si lamenterà: la variabile è già definita.
GO TO FULL VERSION