Perché un ciclo for
if e switch scelgono un ramo e lo eseguono una volta. I programmi reali devono fare le cose ripetutamente: stampare ogni punteggio, sommare una lista di numeri, disegnare dieci righe di una griglia, provare ogni carattere di una stringa. Il ciclo for è il cavallo da tiro del C per ripetere codice un numero noto di volte, con un contatore che controlli tu.
Tutto ciò che serve a un ciclo for sta in un'unica intestazione compatta, quindi "quante volte e come" è visibile in una sola riga.
L'intestazione in tre parti
L'intestazione di un for ha tre parti separate da punti e virgola: un'inizializzazione, una condizione e un aggiornamento.
for (initializer; condition; update) {
// corpo: viene eseguito finché la condizione è diversa da zero
}
Vengono eseguite in un ordine preciso, e conoscerlo spiega ogni ciclo for che leggerai:
- L'inizializzazione viene eseguita una volta, prima di tutto il resto.
- Viene verificata la condizione. Se vale zero, il ciclo termina subito.
- Viene eseguito il corpo.
- Viene eseguito l'aggiornamento.
- Si torna al passo 2.
Segui il ciclo qui sopra con questa lista: int i = 0 viene eseguito una volta. 0 < 5 è vero, quindi il corpo stampa i = 0, poi i++ porta i a 1. La condizione viene verificata di nuovo, e così via. Quando i arriva a 5 il test fallisce, il corpo viene saltato e viene stampato fatto. Il corpo è stato eseguito esattamente cinque volte, con i che ha assunto i valori da 0 a 4.
Ne seguono direttamente due conseguenze. La condizione viene controllata prima del primo passaggio, quindi un ciclo la cui condizione parte falsa viene eseguito zero volte: for (int i = 10; i < 5; i++) non entra mai nel corpo. E l'aggiornamento viene eseguito dopo il corpo, quindi per tutto il corpo i mantiene ancora il vecchio valore.
Dichiarare il contatore nell'intestazione (int i = 0) è consentito da C99 ed è la scelta predefinita giusta: i esiste così solo dentro il ciclo, quindi non può entrare in conflitto con nulla di ciò che segue, e ogni ciclo di una funzione può riutilizzare liberamente il nome.
Contare in avanti, all'indietro e a passi
L'aggiornamento non è limitato a i++. Conta all'indietro, avanza di qualsiasi quantità o raddoppia a ogni passaggio:
Abbina la condizione all'aggiornamento. Contare in avanti va con < o <=; contare all'indietro va con > o >=. Sbagliare questo abbinamento è il modo in cui si scrive un ciclo che non finisce mai.
Nota che il secondo ciclo usa <= 10 perché 10 è un valore che vogliamo includere, mentre il primo esempio usava < 5 perché 5 non lo è. È da questa scelta che nascono i bug off-by-one, quindi falla consapevolmente: i < n viene eseguito n volte partendo da 0; i <= n viene eseguito n + 1 volte.
Scorrere un array
L'uso più comune di un ciclo di conteggio è percorrere un array tramite indice. Il contatore fa anche da posizione da leggere.
Due cose da portarsi via da questo esempio.
Il trucco di sizeof. sizeof(scores) è la dimensione dell'intero array in byte e sizeof(scores[0]) è la dimensione di un elemento, quindi il loro quoziente è il numero di elementi. Si adatta automaticamente quando aggiungi un elemento, cosa che un 5 scritto a mano non fa. Il problema, ed è grosso, è che funziona solo dove l'array stesso è nello scope. Passa l'array a una funzione e decade a puntatore, quindi lì sizeof dà invece la dimensione di un puntatore. Dentro una funzione, ricevi sempre la lunghezza come parametro separato.
La condizione è i < n, mai i <= n. Un array di cinque elementi ha indici validi da 0 a 4. Leggere scores[5] è comportamento indefinito: potrebbe stampare spazzatura, andare in crash o sembrare funzionare mentre corrompe in silenzio qualcos'altro. Il C non fa alcun controllo dei limiti, quindi la responsabilità è tutta tua.
Percorrere un array all'indietro è la stessa idea al contrario:
Parti da n - 1 (l'ultimo indice valido) e fermati a 0 incluso, per questo >= e non >. Qui tieni i come int signed; la prossima sezione spiega perché.
Cicli annidati
Metti un for dentro un altro per lavorare con griglie, tabelle o ogni coppia di elementi. Il ciclo interno viene eseguito per intero a ogni singolo passo del ciclo esterno.
Il ciclo esterno fissa una row; quello interno percorre ogni col di quella riga; la printf("\n") dopo il ciclo interno chiude la riga. Sposta quell'a capo dentro il ciclo interno e l'intera tabella diventa una sola colonna: vale la pena provarlo, perché rende concreta la struttura dell'annidamento.
Dai ai contatori nomi distinti. row/col o i/j vanno bene; riutilizzare i per entrambi nasconde il contatore esterno e produce risultati sconcertanti. E attento al costo: un ciclo di n passaggi dentro un altro di n esegue il corpo n * n volte, quindi una coppia di cicli da 1.000 iterazioni fa un milione di passaggi.
Ecco un ciclo annidato che fa un lavoro vero, un triangolo di stelle, dove il limite del ciclo interno dipende dal contatore esterno:
break e continue
Due parole chiave cambiano il flusso a metà ciclo. break esce subito dal ciclo; continue salta il resto del passaggio corrente e va all'aggiornamento.
Il primo ciclo si ferma nel momento in cui trova 7 e non controlla mai i restanti 93 valori. Il secondo usa continue per saltare la stampa dei numeri pari; l'i++ dell'intestazione viene comunque eseguito, quindi il ciclo continua ad avanzare. C'è molto di più su entrambi in break e continue, compreso come uscire da un ciclo annidato, cosa che un singolo break non può fare.
Cicli infiniti e parti vuote
Tutte e tre le parti dell'intestazione sono facoltative. Se ometti la condizione, viene trattata come sempre vera:
for (;;) {
/* gira per sempre: esci con break o return */
}
for (;;) è il ciclo infinito idiomatico del C, e i punti e virgola sono comunque obbligatori. Non è un errore finché qualcosa all'interno può fare break, return o exit: i cicli di eventi e i cicli "continua a chiedere finché l'input non è valido" si scrivono esattamente così.
Si possono omettere anche le altre parti. Se il contatore esiste già, salta l'inizializzazione; se è il corpo a farlo avanzare, salta l'aggiornamento:
int i = 0;
for (; i < n; ) {
/* ... */
i += step;
}
È lecito, ma si legge peggio del ciclo while che in realtà è. Preferisci un for quando il contatore, il suo limite e il suo passo stanno bene insieme nell'intestazione, e un ciclo while quando non è così.
L'operatore virgola nell'intestazione
L'inizializzazione e l'aggiornamento possono contenere ciascuno più espressioni separate da virgole, ed è così che si fanno girare due contatori insieme:
int i = 0, j = len - 1 dichiara entrambi i contatori e i++, j-- li fa avanzare entrambi. Questo è l'operatore virgola, che valuta in ordine i suoi operandi, prima quello a sinistra e poi quello a destra. Usalo per contatori davvero paralleli; stipare lavoro non correlato in un'intestazione serve solo a nasconderlo.
Trappole comuni
Una manciata di trappole spiega la maggior parte dei bug dei cicli for in C.
Off-by-one. i <= n su un array che parte da zero legge un elemento oltre la fine. Usa i < n per "n volte partendo da 0".
Un punto e virgola dopo l'intestazione. Questo compila e non è quasi mai ciò che intendevi:
/* BUG: il corpo del ciclo è l'istruzione vuota; la printf viene eseguita una volta */
for (int i = 0; i < 5; i++);
{
printf("%d\n", i);
}
Il ; diventa il corpo, quindi il ciclo gira cinque volte senza fare nulla e le graffe sotto vengono eseguite una volta come semplice blocco. gcc -Wall avvisa.
Contatori unsigned che contano all'indietro. Questo è un ciclo infinito:
/* BUG: un valore unsigned non è mai < 0 */
for (size_t i = n - 1; i >= 0; i--) {
process(a[i]);
}
i >= 0 è sempre vero per un tipo unsigned. Quando i arriva a 0, i-- ricomincia da un numero positivo enorme e il ciclo prosegue in memoria che non gli appartiene. Usa un int signed per i conteggi all'indietro, oppure scrivi la condizione come for (size_t i = n; i-- > 0; ), che decrementa e verifica in un solo passaggio e si ferma correttamente a zero.
Modificare il contatore in due punti. Cambiare i sia nel corpo sia nell'intestazione rende imprevedibile il numero di iterazioni. Scegli un solo punto.
Contatori in virgola mobile. for (double x = 0.0; x != 1.0; x += 0.1) potrebbe non terminare mai, perché 0.1 non ha una rappresentazione binaria esatta e la somma supera 1.0 senza mai centrarlo. Cicla con un conteggio intero e calcola il valore all'interno:
for (int i = 0; i <= 10; i++) {
double x = i / 10.0;
/* ... */
}
Cambiare la lunghezza di un array mentre lo percorri. La condizione i < n rilegge n a ogni passaggio, quindi se il corpo riduce la collezione, aggiusta l'indice in modo consapevole invece di lasciare che i due si disallineino.
Domande frequenti
Come si scrive un ciclo for in C?
Metti tre parti nell'intestazione separate da punti e virgola, un'inizializzazione, una condizione e un aggiornamento: for (int i = 0; i < 5; i++) { printf("%d\n", i); }. Il corpo viene eseguito cinque volte con i che assume i valori da 0 a 4, e il ciclo si ferma appena la condizione è falsa.
Come si scorre un array in C?
Fai contare un indice da 0 fino alla lunghezza esclusa: for (int i = 0; i < n; i++) { sum += a[i]; }. Per un array dichiarato nella stessa funzione puoi calcolare la lunghezza con sizeof(a) / sizeof(a[0]); una volta passato l'array a una funzione quel trucco non funziona più, quindi passa la lunghezza come parametro.
Perché il mio ciclo for in C viene eseguito una volta di troppo?
È il bug off-by-one. Con un array di dimensione n che parte da zero, gli indici validi vanno da 0 a n - 1, quindi i <= n esegue un'iterazione in più e legge oltre la fine: comportamento indefinito. La scelta sicura di default è i < n.
Si può dichiarare la variabile del ciclo dentro un for in C?
Sì, da C99: for (int i = 0; i < n; i++). La variabile esiste così solo dentro il ciclo, che è quello che vuoi. Compila con gcc -std=c99 o successivo (GCC e clang moderni usano C17 di default, quindi funziona e basta); il vecchio stile C89 dichiarava i prima del ciclo.