Ciclo Do While in C#

Concetti Chiave
  • Il ciclo do-while in C# esegue il corpo del ciclo almeno una volta e poi continua a ripeterlo finché la condizione specificata è vera.
  • La condizione del ciclo viene controllata alla fine di ogni iterazione, garantendo che il corpo del ciclo venga eseguito almeno una volta.
  • Il ciclo do-while è utile quando si desidera eseguire un blocco di codice almeno una volta, indipendentemente dalla condizione iniziale.
  • È importante gestire correttamente le condizioni per evitare cicli infiniti, assicurandosi che la condizione diventi falsa ad un certo punto.

Ciclo do-while

Il ciclo do-while è simile al ciclo while.

La differenza principale è che controlla la condizione dopo ogni esecuzione del suo corpo del ciclo. Questo tipo di cicli è chiamato cicli con condizione alla fine (post-test loop). Un ciclo do-while appare così:

do
{
    // corpo del ciclo
    // eseguito almeno una volta
    // anche se la condizione è falsa
} while (condizione);

Il diagramma di flusso di un tipico ciclo do-while è riportato nella figura che segue:

Diagramma di flusso di un ciclo do while
Figura 1: Diagramma di flusso di un ciclo do while

Inizialmente il corpo del ciclo viene eseguito. Poi la sua condizione viene controllata. Se è vera, il corpo del ciclo viene ripetuto, altrimenti il ciclo termina. Questa logica viene ripetuta finché la condizione del ciclo non diviene falsa.

La caratteristica principale del ciclo do-while è che il corpo del ciclo viene eseguito almeno una volta.

Analogamente al ciclo while, se la condizione del ciclo è sempre vera, il ciclo non termina mai ottenendo così un ciclo infinito. Per evitare questo, dobbiamo assicurarci che la condizione del ciclo diventi falsa ad un certo punto.

Il ciclo do-while viene utilizzato quando vogliamo garantire che la sequenza di operazioni al suo interno venga eseguita ripetutamente e almeno una volta all'inizio del ciclo.

Vediamo qualche esempio di utilizzo del ciclo do-while.

Esempio: Calcolo del Fattoriale di un Numero

In questo esempio calcoleremo nuovamente il fattoriale di un numero dato n, ma questa volta invece di un ciclo while infinito useremo un do-while.

La logica è simile a quella dell'esempio visto nella lezione precedente:

Console.Write("n = ");
int n = int.Parse(Console.ReadLine());

decimal fattoriale = 1;

do
{
    fattoriale *= n;
    n--;
} while (n > 0);

Console.WriteLine("n! = " + fattoriale);

All'inizio partiamo con un risultato di 1 e moltiplichiamo consecutivamente il risultato ad ogni iterazione per n, e riduciamo n di un'unità, finché n raggiunge 0.

Questo ci dà il prodotto:

n! = n \cdot (n-1) \cdot (n-2) \cdots 3 \cdot 2 \cdot 1

Infine, stampiamo il risultato sulla console. Questo algoritmo esegue sempre almeno una moltiplicazione ed è per questo che non funzionerà correttamente quando n è minore o uguale a zero.

Ecco il risultato dell'esecuzione dell'esempio sopra per n=7:

n = 7
n! = 5040

Esempio: Fattoriale con Overflow

Ci si potrebbe chiedere cosa accade se impostiamo un valore grande per il numero n nell'esempio precedente, diciamo n=100.

Quando calcoliamo n! andremo in overflow del tipo decimal e il risultato sarà un'eccezione di tipo System.OverflowException:

n = 100
Unhandled exception. System.OverflowException: Value was either too large or too small for a Decimal.
   at System.Number.ThrowOverflowException(String message)
   at System.Decimal.DecCalc.ScaleResult(Buf24* bufRes, UInt32 hiRes, Int32 scale)
   at System.Decimal.DecCalc.VarDecMul(DecCalc& d1, DecCalc& d2)
   at System.Decimal.op_Multiply(Decimal d1, Decimal d2)
   at Program.<Main>$(String[] args) in Program.cs:line 8

Se vogliamo calcolare 100! possiamo utilizzare il tipo di dato BigInteger (che è stato introdotto a partire da .NET Framework 4.0 ed è mancante nelle versioni .NET più vecchie). Questo tipo rappresenta un intero, che può essere molto grande (ad esempio 100.000 cifre). Non c'è limite sulla dimensione dei numeri registrati nella classe BigInteger (purché, ovviamente, si abbia abbastanza RAM).

Quindi dobbiamo aggiungere using System.Numerics; prima dell'inizio della classe del nostro programma e sostituire decimal con BigInteger. Il programma ottiene la seguente forma:

using System.Numerics;

Console.Write("n = ");
int n = int.Parse(Console.ReadLine());
BigInteger fattoriale = 1;
do
{
    fattoriale *= n;
    n--;
} while (n > 0);
Console.WriteLine("n! = " + fattoriale);

Se ora eseguiamo il programma per n=100, otterremo il valore di 100!, che è un numero di 158 cifre:

n = 100
n! =
9332621544394415268169923885626670049071596826438162146859296389
5217599993229915608941463976156518286253697920827223758251185210
916864000000000000000000000000

Con BigInteger si possono calcolare fattoriali dal risultato gigantesco come 1000!, 10000! e persino 100000!. Ci vorrà del tempo, ma OverflowException non si verificherà. La classe BigInteger è molto potente ma funziona molte volte più lentamente di int e long. Purtroppo, il dotnet framework non ha un tipo di dato per i numeri in virgola mobile che può contenere numeri così grandi. Tuttavia, possiamo sfruttare la classe BigInteger per effettuare calcoli con numeri interi molto grandi suddividendo i numeri in parte intera e parte decimale, ma questo è un argomento per una lezione futura.

Esempio: Prodotto di Numeri in un Intervallo

Diamo un altro esempio più interessante di lavoro con i cicli do-while.

L'obiettivo è trovare il prodotto di tutti i numeri compresi nell'intervallo \left[n, m\right].

Ecco una soluzione di esempio per questo problema:

Console.Write("n = ");
int n = int.Parse(Console.ReadLine());

Console.Write("m = ");
int m = int.Parse(Console.ReadLine());

int numero = n;
long prodotto = 1;

do
{
    prodotto *= numero;
    numero++;
} while (numero <= m);

Console.WriteLine("prodotto[n...m] = " + prodotto);

Nel codice di esempio assegniamo consecutivamente a numero ad ogni iterazione i valori n, n+1, n+2 fino a m e nella variabile prodotto accumuliamo il prodotto di questi valori. Richiediamo all'utente di inserire n, che dovrebbe essere minore di m. Altrimenti riceveremo come risultato il numero n.

Se eseguiamo il programma per n=2 e m=6 otterremo il seguente risultato:

n = 2
m = 6
prodotto[n...m] = 720

Attenzione: il prodotto cresce molto rapidamente, quindi si potrebbe aver bisogno di usare BigInteger invece di long per il risultato calcolato. Inoltre si faccia attenzione all'overflow di interi nascosto. Il codice non controllato andrà silenziosamente in overflow e il codice sopra produrrà un output errato invece di mostrare un errore. Per superare questo problema, si può inserire la parola chiave checked prima del ciclo:

checked
{
    do
    {
        prodotto *= numero;
        numero++;
    } while (numero <= m);
}

In questo modo, se il prodotto supera il limite del tipo di dato (ossia se si verifica un overflow), il programma lancerà un'eccezione System.OverflowException e non continuerà a eseguire il codice successivo.