Perché un ciclo for
Uno switch sceglie un ramo da eseguire una volta. Ma i programmi reali devono fare qualcosa ripetutamente: stampare ogni punteggio, sommare una lista di numeri, disegnare 10 righe di una griglia. Il ciclo for è il cavallo di battaglia del C++ per ripetere codice un numero noto di volte, con un contatore integrato che controlli tu.
Tutto ciò che serve a un ciclo for sta in un'unica intestazione compatta, quindi tutta la storia del "quante volte e come" è visibile a colpo d'occhio.
L'intestazione in tre parti
L'intestazione di un ciclo for ha tre parti separate da punto e virgola: un'inizializzazione, una condizione e un aggiornamento.
for (initializer; condition; update) {
// corpo: viene eseguito finché la condizione è vera
}
Vengono eseguite in un ordine preciso: l'inizializzazione una volta sola all'inizio; poi la condizione viene controllata prima di ogni iterazione; il corpo viene eseguito solo se la condizione è true; e l'aggiornamento viene eseguito alla fine di ogni iterazione, subito prima che la condizione venga controllata di nuovo.
Qui int i = 0 viene eseguito una volta. Poi si controlla i < 5: finché è vera, il corpo stampa e i++ incrementa il contatore. Quando i arriva a 5 la condizione è false, quindi il ciclo termina e viene stampato done. Il corpo viene eseguito esattamente 5 volte, con i che assume i valori da 0 a 4.
Dichiarare il contatore nell'intestazione (int i = 0) limita lo scope di i al ciclo: dopo la graffa di chiusura non esiste più, ed è esattamente ciò che vuoi.
Contare in avanti, all'indietro e a passi
La parte di aggiornamento non si limita a i++. Puoi contare all'indietro, avanzare di qualsiasi quantità o scorrere l'indice di un array.
Il primo ciclo gira finché i > 0, decrementando ogni volta, quindi stampa 5 4 3 2 1. Il secondo aggiunge 2 a ogni passaggio e usa <= 10 perché 10 è un valore che vogliamo includere. Abbina la condizione all'aggiornamento: contare all'indietro va con > o >=, contare in avanti con < o <=.
Scorrere un array
L'uso più comune di un ciclo di conteggio è percorrere un array tramite indice. Il contatore funge anche da posizione che leggi.
Nota che la condizione è i < n, non i <= n. Un array di 5 elementi ha indici validi da 0 a 4; l'indice 5 è oltre la fine. Leggere scores[5] è comportamento indefinito: potrebbe stampare spazzatura, far crashare il programma o sembrare funzionare e corrompere la memoria in silenzio. Lo schema i < n è la scelta sicura per qualsiasi array con indici a partire da zero.
Se ti servono solo i valori e non l'indice, un for basato su range è più pulito. Usa il classico ciclo con indice quando ti serve davvero la posizione.
break e continue
Due parole chiave ti permettono di cambiare il flusso a metà ciclo. break esce subito dal ciclo; continue salta il resto dell'iterazione corrente e passa all'aggiornamento.
Il primo ciclo si ferma nel momento in cui trova 7 e non controlla il resto. Il secondo usa continue per saltare la stampa del corpo ogni volta che i è pari: l'aggiornamento i++ viene comunque eseguito, quindi il ciclo continua ad avanzare. Un'insidia sottile: continue salta all'aggiornamento, quindi se usi continue in un ciclo il cui contatore viene aggiornato dentro il corpo invece che nell'intestazione, puoi saltare per sbaglio quell'aggiornamento e girare all'infinito.
Cicli annidati
Metti un for dentro un altro per lavorare con griglie, tabelle o coppie. Il ciclo interno viene eseguito per intero a ogni singolo passo del ciclo esterno.
Questo stampa una tabellina 3x3. Il ciclo esterno fissa una riga (row), quello interno percorre ogni colonna (col) di quella riga, poi un a capo chiude la riga. Dai ai contatori nomi distinti (row/col, non i/i): riusare lo stesso nome nasconde quello esterno e produce bug sconcertanti. Fai attenzione anche al costo: annidare un ciclo di n passi dentro un altro di n passi esegue il corpo n * n volte, e cresce in fretta.
Insidie comuni
Poche trappole causano la maggior parte dei bug nei cicli for in C++:
- Errore di uno:
i <= ncon una dimensione che parte da zero legge un elemento oltre la fine. Usai < n. - Underflow degli unsigned: contando all'indietro con un tipo unsigned non si va mai sotto zero.
for (size_t i = n - 1; i >= 0; i--)gira all'infinito, perchéi >= 0è sempre vera per un valore unsigned: quandoivale0,i--ricomincia da un numero positivo enorme. Usa unintsigned per i conteggi all'indietro, oppure riscrivi la condizione. - Modificare il contatore dentro il corpo: cambiare
isia nel corpo sia nell'intestazione rende imprevedibile il numero di iterazioni. Scegli un solo punto.
// BUG: infinite loop - unsigned i is never < 0
for (size_t i = n - 1; i >= 0; i--) {
process(arr[i]);
}
I contatori a virgola mobile sono un altro pericolo silenzioso: for (double x = 0.0; x != 1.0; x += 0.1) potrebbe non arrivare mai esattamente a 1.0, perché 0.1 non si può memorizzare con precisione. Cicla con un conteggio intero e calcola il valore dentro il corpo, oppure usa < invece di !=.
Prossimo: Cicli while
Il ciclo for dà il meglio quando conosci il numero di ripetizioni in anticipo. Ma a volte devi ripetere finché una condizione non cambia, come leggere l'input fino alla fine del file o riprovare finché non va a buon fine, senza un numero fisso di passi. È il compito del ciclo while, che riduce l'intestazione a una sola condizione. Ne parliamo nella prossima pagina.
Domande frequenti
Come si scrive un ciclo for in C++?
Metti nell'intestazione tre parti separate da punto e virgola: un'inizializzazione, una condizione e un aggiornamento. for (int i = 0; i < 5; i++) { cout << i; } esegue il corpo con i che vale 0, 1, 2, 3, 4. Il ciclo si ferma non appena la condizione diventa false.
Qual è la differenza tra un ciclo for e un ciclo for basato su range in C++?
Un for classico ti dà un contatore di indice che controlli tu (for (int i = 0; i < n; i++)), utile quando ti serve la posizione o vuoi avanzare in modo personalizzato. Un for basato su range (for (int x : v)) nasconde l'indice e ti passa semplicemente ogni elemento: più pulito quando ti servono solo i valori.
Perché il mio ciclo for in C++ viene eseguito una volta di troppo o una volta in meno?
È il classico errore di uno (off-by-one). Usare <= invece di < con una dimensione che parte da zero esegue un'iterazione in più e legge oltre la fine dell'array; usare < quando volevi includere l'ultimo valore ne esegue una in meno. Per un array di dimensione n, lo schema sicuro è for (int i = 0; i < n; i++).