Due modi per cambiare il flusso di un ciclo
La condizione di un ciclo decide quando finisce, ma a volte la vera risposta arriva a metà del corpo. Stai cercando in un array e trovi l'elemento al terzo posto: perché controllare gli altri 997? Oppure stai elaborando una lista e una voce è vuota: saltala e vai avanti.
C ha una parola chiave per ciascun caso:
break: esci subito dal ciclo. L'esecuzione riprende dalla prima istruzione dopo il ciclo.continue: abbandona solo questo passaggio. Passa al successivo.
Il primo ciclo stampa 1 2 3 4 e si ferma. Il secondo stampa 1 2 3 4 6 7 8 9 10: ha saltato un valore ed è andato avanti.
break in un ciclo
L'uso tipico è una ricerca che si ferma appena ha successo:
Poiché found viene impostato prima del break, il codice dopo il ciclo può distinguere tra "trovato" e "elementi finiti". Questa coppia, una variabile risultato più un break, è l'idioma di ricerca standard in C. Il contatore del ciclo invece non è utilizzabile dopo, se è dichiarato nell'intestazione, perché esce dallo scope alla graffa di chiusura.
break esce sempre da un solo ciclo: il for, while, do-while o switch più vicino che lo racchiude.
continue in un ciclo
continue serve per le voci da saltare senza interrompere il lavoro:
Dove salta continue dipende dal ciclo:
for -> la parte di aggiornamento dell'intestazione (il contatore avanza comunque)
while -> il test della condizione (il resto del corpo non viene eseguito)
do-while -> il test della condizione in fondo
Questa differenza non è teorica. In un ciclo for, continue non può mai saltare l'incremento del contatore, perché l'incremento sta nell'intestazione. In un ciclo while sì:
/* BUG: continue salta i++, quindi si blocca al primo numero pari */
int i = 0;
while (i < 10) {
if (i % 2 == 0) {
continue;
}
printf("%d ", i);
i++;
}
Sposta i++ sopra il continue, oppure scrivilo come ciclo for, dove l'intestazione rende impossibile sbagliare.
Un break nello switch non è un break del ciclo
break termina il ciclo o lo switch più vicino che lo racchiude, quello dei due che è più vicino. Metti uno switch dentro un ciclo e ogni break al suo interno appartiene allo switch:
Il comando q stampa il suo messaggio e il ciclo continua con cc. Se l'uscita doveva terminare il ciclo, quel break non l'ha fatto. Le soluzioni sono un flag, un goto oppure spostare il ciclo in una funzione e usare return: le vedi tutte qui sotto.
Uscire da cicli annidati
Un singolo break esce da un solo livello. Dentro due cicli annidati termina solo quello interno, e il ciclo esterno inizia il passaggio successivo come se nulla fosse.
C non ha break 2 né break con etichetta. Tre opzioni oneste:
Un flag. Portabile, ovvio e un po' rumoroso:
Il flag compare in due punti, la condizione esterna e l'assegnazione, e questo è il costo. Con tre livelli di annidamento diventa davvero sgradevole.
goto. Il tanto criticato salto di C, e questo è l'unico caso in cui i programmatori C esperti lo usano senza scusarsi:
Un goto in avanti verso un'etichetta subito dopo i cicli fa esattamente quello che farebbe un break con etichetta in altri linguaggi, in una riga e senza flag da tenere sincronizzati. La reputazione di goto deriva dai salti all'indietro e dai salti nel mezzo di altri blocchi, che producono davvero un flusso di controllo illeggibile. Un singolo salto in avanti fuori da cicli annidati non è questo. Il kernel Linux usa lo stesso schema per i percorsi di pulizia. Usalo in modo consapevole e con parsimonia; non lasciare che il tabù ti spinga verso un'alternativa con tre flag più difficile da leggere.
Una funzione e return. Di solito la risposta migliore, perché una ricerca merita un nome:
return esce da tutti i cicli della funzione, non richiede flag né etichette, e il codice di ricerca ora ha un nome e può essere testato da solo. Quando l'annidamento è così profondo che uscirne diventa un problema, spesso è un segnale che quel blocco voleva comunque essere una funzione.
Quando aiutano e quando fanno danni
break e continue si guadagnano il loro posto quando permettono a un ciclo di dire "questo caso è finito" una volta sola, in alto, invece di avvolgere tutto il corpo in un if. Confronta:
/* con continue: il lavoro vero non è indentato */
for (int i = 0; i < n; i++) {
if (!isValid(a[i])) continue;
if (isDuplicate(a[i])) continue;
process(a[i]);
}
/* senza: ogni controllo aggiunge un livello */
for (int i = 0; i < n; i++) {
if (isValid(a[i])) {
if (!isDuplicate(a[i])) {
process(a[i]);
}
}
}
Fanno danni quando sono troppi. Un ciclo con cinque break sparsi e tre continue ha una condizione di uscita che non è scritta da nessuna parte: chi legge deve simulare l'intero corpo per sapere quando si ferma. Due linee guida che reggono bene: tieni i controlli con continue tutti insieme in cima al corpo e, se un ciclo ha più di uno o due break, chiediti se quel lavoro non dovesse farlo la condizione nell'intestazione.
Due trappole minori. continue in un do-while salta al test in fondo, che è comunque la cosa successiva: innocuo, ma sorprende. E break fuori da qualsiasi ciclo o switch è un errore di compilazione, l'unico sbaglio di questa pagina che il compilatore individua al posto tuo.
Domande frequenti
Qual è la differenza tra break e continue in C?
break termina del tutto il ciclo e l'esecuzione riprende dopo di esso. continue abbandona solo il passaggio corrente e passa al successivo: all'aggiornamento in un ciclo for, oppure direttamente al test della condizione in un while o do-while.
Come si esce da un ciclo annidato in C?
Un singolo break esce solo dal ciclo più interno. Per uscire da entrambi puoi impostare un flag e verificarlo nella condizione del ciclo esterno, usare goto per saltare a un'etichetta dopo i cicli oppure, di solito la soluzione più pulita, spostare i cicli in una funzione e usare return.
break esce dal ciclo o solo dallo switch?
Da quello che lo racchiude più da vicino. Un break dentro uno switch che si trova in un ciclo termina solo lo switch; il ciclo prosegue. Per uscire dal ciclo dall'interno di uno switch serve un flag, un goto o un return.
Perché continue causa un ciclo infinito nel mio while?
Perché continue salta alla condizione e ignora il resto del corpo, compreso l'incremento del contatore se si trova alla fine. In un ciclo for l'aggiornamento sta nell'intestazione e quindi viene sempre eseguito; in un ciclo while sposta l'aggiornamento prima del continue oppure usa un for.