Menu

Ciclo while in C: sintassi, esempi ed errori comuni

Come il ciclo while in C ripete finché una condizione cambia: la regola della condizione prima di tutto, i cicli con sentinella, la lettura dell'input fino a EOF, while(1) e come passare da for a while.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Ripetere senza un conteggio

Un ciclo for è lo strumento giusto quando sai, o puoi calcolare, quanti passaggi ti servono. Ma molto lavoro non arriva con un conteggio. Leggere ogni riga di un file finché non finisce. Continuare a chiedere una password finché non è giusta. Dimezzare un numero finché non arriva a 1. In questi casi la domanda non è "quante volte" ma "fino a quando", ed è esattamente ciò che esprime while.

Nessuno ha calcolato "sette" in anticipo. Il ciclo va semplicemente avanti finché la condizione è vera.

Sintassi e la regola della condizione prima di tutto

while (condizione) {
    // corpo: eseguito finche la condizione e' diversa da zero
}

La condizione viene verificata prima di ogni passaggio, compreso il primo. Tre conseguenze:

  • Un ciclo la cui condizione è falsa fin dall'inizio viene eseguito zero volte. while (0) { ... } non esegue mai il corpo.
  • Ciò che la condizione verifica deve essere inizializzato prima del ciclo, altrimenti stai verificando un valore spazzatura.
  • Qualcosa nel corpo deve prima o poi rendere falsa la condizione, altrimenti il ciclo non finisce mai.

Come per if, le parentesi sono obbligatorie e dopo di esse non va il punto e virgola. Quel punto e virgola di troppo è la versione per i cicli dello stesso bug:

/* BUG: l'istruzione vuota e' il corpo, questo gira all'infinito */
while (i < 10);
{
    printf("%d\n", i);
    i++;
}

i non viene mai incrementata, quindi la condizione non cambia mai. gcc -Wall segnala il corpo vuoto.

La regola di verità del C vale qui esattamente come in if/else: il ciclo continua finché l'espressione è diversa da zero e si ferma quando vale zero.

La forma standard: inizializza, verifica, aggiorna

Quasi ogni ciclo while corretto ha tre parti distribuite in tre punti:

Sono le stesse tre parti contenute nell'intestazione di un for, solo che non sono raccolte in una riga. Ed è questa la risposta onesta a "for o while?": se tutte e tre le parti riguardano un unico contatore, un for le mette dove chi legge può controllarle insieme. Se l'aggiornamento è sparso in un corpo lungo, o la condizione riguarda qualcosa che non è un conteggio, while è la forma più chiara.

Dimenticare la parte 3 è il bug dei cicli while. Se un ciclo si blocca, la prima cosa da controllare è se il corpo cambia davvero ciò che la condizione legge.

Cicli con sentinella

Una sentinella è un valore speciale che significa "fermati". Somma numeri finché l'utente non inserisce 0:

Il 99 dopo lo 0 non viene mai raggiunto: la sentinella ha chiuso il ciclo. Nota che la quantità di numeri non è mai nota in anticipo, ed è proprio per questo che si usa un while.

Le stringhe C funzionano allo stesso modo: terminano con una sentinella '\0', quindi scorrerne una è un while da manuale:

Leggere l'input fino a EOF

Il while più comune nel mondo reale legge l'input finché non ce n'è più. La regola è controllare il valore di ritorno della funzione di input, non il valore che ha salvato.

getchar restituisce il carattere successivo, oppure la costante EOF quando l'input finisce:

Due dettagli importanti. c è un int perché EOF è un valore negativo che su molti sistemi non sta in un char: dichiararla char può far sì che il confronto non risulti mai vero. E l'assegnazione sta dentro la condizione: (c = getchar()) != EOF legge un carattere, lo salva e lo confronta in un solo passo. Le parentesi interne sono obbligatorie, perché != ha precedenza più alta di =; senza di esse salveresti in c il risultato del confronto. Questo è uno dei pochi casi in cui un'assegnazione dentro una condizione è idiomatica e non un bug.

La stessa forma funziona con scanf, il cui valore di ritorno è il numero di elementi convertiti con successo:

int n;
while (scanf("%d", &n) == 1) {
    /* n contiene un numero valido */
}

Il ciclo termina alla fine del file e al primo input che non è un numero, che di solito è proprio quello che vuoi.

Quello che non dovresti scrivere è while (!feof(f)). feof indica se una lettura è già fallita, quindi è ancora falso quando sei sull'ultimo elemento: il corpo del ciclo viene eseguito un'altra volta e la lettura fallisce, elaborando dati vecchi. Verifica direttamente la lettura.

while (1) e break

A volte la condizione di uscita si conosce solo a metà del corpo: dopo la lettura, ma prima dell'elaborazione. La risposta idiomatica è un ciclo volutamente infinito con un'uscita esplicita:

while (1) è sempre vero, quindi l'unico modo per uscire è break (oppure return o exit). Vedrai anche for (;;) usato allo stesso scopo; producono lo stesso codice compilato, e quale dei due preferisca un progetto è una questione di stile.

Usa questa forma quando il test di uscita appartiene davvero al centro del corpo. Quando il test sta naturalmente all'inizio, mettilo nella condizione dove chi legge può vederlo: un while (1) il cui unico break è la prima istruzione è solo una condizione travestita.

Passare da for a while e viceversa

Qualsiasi ciclo for può essere scritto come un ciclo while e viceversa. La corrispondenza è meccanica:

/* for */                          /* while equivalente */
for (init; cond; update) {         init;
    corpo;                         while (cond) {
}                                      corpo;
                                       update;
                                   }

Quindi questi due stampano la stessa cosa:

L'unico punto in cui l'equivalenza non regge è continue. In un ciclo for, continue salta all'aggiornamento nell'intestazione, quindi il contatore avanza comunque. Nella versione while, continue torna direttamente alla condizione e salta l'i++ alla fine del corpo: un ciclo infinito immediato.

/* BUG: continue salta i++, quindi il ciclo gira all'infinito sul primo numero pari */
int i = 0;
while (i < 10) {
    if (i % 2 == 0) {
        continue;
    }
    printf("%d ", i);
    i++;
}

Se un ciclo while usa continue, assicurati che l'aggiornamento avvenga prima, oppure usa un for, dove l'intestazione lo garantisce.

Trappole comuni

  • Niente aggiorna la condizione. Il corpo deve cambiare ciò che la condizione legge. Se un programma si blocca, guarda prima qui.
  • Un punto e virgola dopo l'intestazione. while (cond); rende l'istruzione vuota il corpo del ciclo.
  • Verificare una variabile mai inizializzata. La condizione viene eseguita prima del corpo, quindi il valore che legge deve già esistere.
  • char invece di int per getchar. EOF non sta in un char; il confronto allora può non risultare mai vero, oppure un byte legittimo può essere scambiato per EOF.
  • while (!feof(f)). Esegue un passaggio di troppo. Verifica la lettura.
  • Dare per scontato che il corpo venga eseguito almeno una volta. Non è così. Quando ti serve un passaggio garantito, per esempio stampare un menu prima di controllare la scelta, quello è un ciclo do-while.

Domande frequenti

Come si scrive un ciclo while in C?

while (condizione) { corpo }. La condizione viene verificata prima di ogni passaggio, quindi il corpo viene eseguito solo finché la condizione è diversa da zero, e per niente se è falsa fin dall'inizio. Qualcosa nel corpo deve prima o poi cambiare la condizione, altrimenti il ciclo non finisce mai.

Qual è la differenza tra for e while in C?

Sono ugualmente potenti: qualsiasi for può essere riscritto come while. Usa for quando un contatore, il suo limite e il suo passo vanno messi insieme in un'unica intestazione, e while quando la condizione di arresto non è un conteggio: leggere fino alla fine del file, riprovare finché non riesce o ciclare finché un flag non cambia.

Come leggo l'input fino a EOF in C?

Controlla il valore di ritorno della funzione di input, non il valore che ha letto: while (scanf("%d", &n) == 1) { ... } si ferma alla fine del file o al primo input non numerico, e while ((c = getchar()) != EOF) { ... } legge un carattere alla volta. Non ciclare mai con while (!feof(f)): esegue un passaggio in più oltre la fine.

Perché il mio ciclo while in C è infinito?

Di solito perché niente nel corpo cambia il valore che la condizione verifica, oppure perché un punto e virgola di troppo dopo l'intestazione (while (i < 10);) ha reso l'istruzione vuota il corpo del ciclo. Entrambi compilano senza problemi, quindi controlla che il corpo aggiorni davvero la variabile della condizione.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA