1. Introduzione
Partiamo dalla domanda: perché mai dovremmo mettere un ciclo dentro un altro? Il fatto è che spesso i nostri dati o i nostri task non sono organizzati solo in una linea, ma magari in una tabella, una griglia o addirittura in una struttura multidimensionale. Supponiamo che tu voglia stampare la tabellina, scorrere un array bidimensionale o calcolare tutte le intersezioni tra coppie di elementi. Qui un solo ciclo non basta — serve un ciclo dentro un altro ciclo.
Nel mondo della programmazione, un ciclo annidato è come avere due sveglie: quella esterna inizia a suonare, e dentro di lei parte un'altra che suona ogni volta finché la prima è attiva. Quindi, mentre va avanti un'iterazione "esterna", quella interna fa tutto il suo giro (e lo rifà per ogni iterazione dell'esterna).
Un buon esempio sono le ore e i minuti. Le ore sono il ciclo esterno da 0 a 23, i minuti sono il ciclo interno da 0 a 59. Ad ogni cambiamento del ciclo esterno, quello interno riesce a scorrere tutti i suoi valori.
2. Sintassi dei cicli annidati
In C# la sintassi dei cicli annidati non è diversa da quella dei cicli normali — semplicemente scrivi un ciclo dentro il corpo di un altro. Vediamo qualche esempio con for e while:
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
Console.Write($"{i},{j} ");
}
Console.WriteLine();
}
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 va completamente da j=0 a j=3. Se esegui questo codice, vedrai una bella 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 simile usando while:
int i = 0;
while (i < 3)
{
int j = 0;
while (j < 4)
{
Console.Write($"{i},{j} ");
j++;
}
Console.WriteLine();
i++;
}
Occhio: dentro ogni giro del ciclo esterno, la variabile del ciclo interno (j) deve essere reinizializzata, altrimenti vedrai solo una riga!
3. Esempi di utilizzo dei cicli annidati
Esempio 1: stampa di una scacchiera (8x8)
Facciamo che il nostro primo task sia stampare una classica scacchiera sullo schermo, usando celle nere e bianche (diciamo che # è nera, _ è bianca). Lo facciamo con i cicli annidati for:
for (int row = 0; row < 8; row++)
{
for (int col = 0; col < 8; col++)
{
if ((row + col) % 2 == 0)
Console.Write("_");
else
Console.Write("#");
}
Console.WriteLine();
}
Risultato:
_#_#_#_#
#_#_#_#_
_#_#_#_#
#_#_#_#_
_#_#_#_#
#_#_#_#_
_#_#_#_#
#_#_#_#_
Nota importante: l'annidamento fa sì che per ogni riga (row) passiamo completamente su tutte le colonne (col). Senza annidamento non avremmo la struttura della scacchiera — solo una riga o una colonna.
Esempio 2: tabellina
Uno dei classici per i cicli annidati! Stampiamo la tabellina da 1 a 9:
for (int i = 1; i <= 9; i++)
{
for (int j = 1; j <= 9; j++)
{
Console.Write($"{i * j,3} ");
}
Console.WriteLine();
}
La formattazione ${i * j,3} aggiunge spazi per rendere la tabella ordinata.
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 la loro gestione — dettagli
Sull'effetto di break e continue nei cicli annidati
Qui molti principianti ci cascano! Se usi break o continue nel ciclo interno, questi influenzano solo quel ciclo. Quello esterno va avanti come se nulla fosse.
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
if (j == 3)
break; // usciamo solo dal ciclo interno!
Console.Write($"{i},{j} ");
}
Console.WriteLine();
}
Risultato:
0,0 0,1 0,2
1,0 1,1 1,2
2,0 2,1 2,2
Se vuoi uscire subito da entrambi i cicli annidati (tipo, interrompere la ricerca appena trovi la prima corrispondenza in una tabella), di solito si usa un flag o qualche trucco (ad esempio return — se tutto avviene dentro una funzione).
5. Visualizzazione dei cicli annidati
A volte è difficile "vedere" la sequenza di esecuzione dei cicli annidati. Guarda questo diagramma di flusso:
In forma tabellare — quante iterazioni totali ci saranno per i da 1 a 3, j da 1 a 4?
| i | j (scorre 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 trappole comuni con i cicli annidati
Un errore comune è inizializzare male la variabile del ciclo interno. Ad esempio, dichiararla fuori dal ciclo esterno e non azzerarla a ogni giro. Così il ciclo interno potrebbe non eseguire affatto, o funzionare male.
int j = 0;
for (int i = 0; i < 3; i++)
{
while (j < 4) // Ops! j potrebbe già essere 4 dopo il primo giro.
{
Console.Write($"{i},{j} ");
j++;
}
Console.WriteLine();
}
Qui il ciclo gira solo una volta. Ricordati di inizializzare le variabili dei cicli interni dentro quelli esterni!
Inoltre, se per sbaglio scrivi due cicli annidati con le stesse variabili, il compilatore ti sgrida: la variabile è già definita.
for (int i = 0; i < 3; i++)
{
for (int i = 0; i < 3; i++)
{
Console.Write($"{i},{i} ");
}
Console.WriteLine();
}
GO TO FULL VERSION