Go ha esattamente una parola chiave per i cicli: for. Ha quattro forme, e insieme coprono ogni ciclo che negli altri linguaggi scriveresti con for, while, do while o foreach.
L'intestazione del ciclo non va tra parentesi tonde, e le parentesi graffe sono sempre obbligatorie.
Il ciclo a tre clausole
for init; condition; post {
// body
}
initviene eseguito una volta prima del ciclo. Di solito è una dichiarazione breve di variabile comei := 0, e quella variabile esiste solo dentro il ciclo.conditionviene controllata prima di ogni iterazione. Quando è falsa, il ciclo termina.- Viene eseguito il corpo.
postviene eseguito dopo ogni iterazione, di solitoi++.
In Go i++ è un'istruzione, non un'espressione, quindi post non può contenere i++, j--. Usa invece un'assegnazione parallela: i, j = i+1, j-1.
Tutte e tre le clausole sono facoltative. for ; i < 10; {} è valido, e gofmt lo riscrive come for i < 10 {}, che è la forma successiva.
Ciclo con sola condizione (il while di Go)
Con la sola condizione, for si comporta come il while degli altri linguaggi:
Non esiste un do while separato. La pagina sul ciclo while mostra come ottenere il comportamento "esegui almeno una volta".
Cicli infiniti
Un for senza niente dopo gira all'infinito. Ne esci con break, return, os.Exit o un panic:
for {
line, err := reader.ReadString('\n')
if err != nil {
break
}
process(line)
}
È la forma standard per server, worker e cicli di input. Un for {} con il corpo vuoto consuma un intero core della CPU; nel codice reale il corpo si blocca su qualcosa (una lettura, una ricezione da un channel, un select).
Cicli range
range itera su una collezione e produce uno o due valori a ogni iterazione. Quali siano dipende da cosa stai scorrendo:
| Range su | Primo valore | Secondo valore |
|---|---|---|
| slice o array | indice | elemento (una copia) |
| stringa | indice del byte | rune (carattere Unicode) |
| map | chiave | valore |
| channel | elemento | (nessuno) |
intero n (Go 1.22) | da 0 a n meno 1 | (nessuno) |
| funzione iteratore (Go 1.23) | ciò che la funzione produce | ciò che la funzione produce |
Tre dettagli che l'output mostra:
- La variabile del valore è una copia.
v *= 2dentro il ciclo non modificherebbe lo slice; per modificare gli elementi, accedi allo slice tramite indice connums[i], come fa il terzo ciclo. - L'indice di una stringa è un offset in byte.
éoccupa due byte, quindi l'indice salta da 1 a 3. - L'ordine delle mappe è casuale di proposito. Go lo rende casuale così il codice non può dipenderne.
slices.Sorted(maps.Keys(m))(Go 1.23) restituisce le chiavi in ordine.
range è trattato per intero, compresi i channel, le funzioni iteratore e le regole di copia, nella pagina su range.
break e continue
break termina il ciclo più interno. continue salta all'iterazione successiva (eseguendo prima post, in un ciclo a tre clausole):
break e continue con etichetta
Un semplice break esce solo dal ciclo più interno. Per uscire da un ciclo esterno dall'interno di uno annidato, metti un'etichetta sul ciclo esterno:
Un'etichetta è un identificatore seguito dai due punti, posto subito prima del for. Go segnala un'etichetta inutilizzata come errore di compilazione, proprio come una variabile inutilizzata.
break dentro switch e select
Dentro un for, un break in un caso di switch o select esce dallo switch o dal select, non dal ciclo:
for {
switch cmd := next(); cmd {
case "quit":
break // only exits the switch; the loop keeps running
}
}
Usa un break con etichetta, oppure return, per uscire dal ciclo dall'interno di uno switch o di un select.
Variabili di ciclo da Go 1.22
Go 1.22 ha cambiato lo scope delle variabili di ciclo: ogni iterazione ora riceve una nuova copia di i e v. Prima, una sola variabile veniva riutilizzata per tutto il ciclo, il che rompeva le closure e le goroutine che la catturavano:
Con Go 1.22 e successivi questo stampa 0 1 2 e [aa bb cc]. Con la vecchia semantica lo stesso programma scritto con for i := 0; i < 3; i++ (il range su un intero non esisteva prima della 1.22) stampava 3 3 3, e le goroutine entravano in corsa sulle variabili condivise i e name. La vecchia soluzione alternativa, i := i all'inizio del corpo del ciclo, non serve più.
Il comportamento viene scelto per modulo dalla riga go di go.mod, non dalla versione del compilatore. Un modulo che indica ancora go 1.21 mantiene la vecchia semantica anche se compilato con Go 1.24.
Errori comuni
Modificare la copia. for _, v := range items { v.Count++ } modifica una copia. Usa items[i].Count++, oppure fai il range su uno slice di puntatori.
Aggiungere elementi durante il range. range valuta lo slice una sola volta, all'inizio. Gli elementi aggiunti dentro il ciclo non vengono visitati, quindi il ciclo termina comunque, ma non vedrà i nuovi elementi.
Eliminare da uno slice per indice in un ciclo in avanti. Rimuovere l'elemento i sposta a sinistra i successivi, quindi l'elemento seguente viene saltato. Costruisci un nuovo slice, cicla all'indietro oppure usa slices.DeleteFunc.
Errore di uno con <=. for i := 0; i <= len(s); i++ legge s[len(s)] nell'ultimo passaggio e va in panic con index out of range. Usa < o, meglio ancora, range.
Lavoro pesante nella condizione. La condizione viene eseguita a ogni iterazione. for i := 0; i < expensiveCount(); i++ chiama la funzione ogni volta; calcolala una volta prima del ciclo.
Usare un float come contatore. for x := 0.0; x != 1.0; x += 0.1 non termina mai, perché 0.1 non ha una rappresentazione binaria esatta. Conta con un intero e calcola il float a partire da esso.
Domande frequenti
Come si scrive un ciclo for in Go?
La forma classica ha tre parti separate da punti e virgola e nessuna parentesi tonda:
for i := 0; i < 5; i++ {
fmt.Println(i)
}
Da Go 1.22 puoi anche scrivere for i := range 5, che conta da 0 a 4. Per scorrere uno slice, usa for i, v := range items.
Go ha un ciclo while?
Non ha la parola chiave while, ma un for con la sola condizione è un ciclo while: for n > 0 { n /= 2 }. for { ... } senza condizione è un ciclo infinito da cui esci con break o return.
Come itero su una map in Go?
for key, value := range m { ... }. L'ordine di iterazione non è specificato, e il runtime lo rende casuale, quindi può cambiare da un ciclo all'altro. Non farci mai affidamento. Per un ordine stabile, raccogli le chiavi, ordinale con slices.Sort e cicla sulle chiavi ordinate.
Come esco da un ciclo annidato in Go?
Metti un'etichetta prima del ciclo esterno e indicala nel break: outer: for ... { for ... { if found { break outer } } }. Un semplice break esce solo dal for, switch o select più interno. Allo stesso modo continue outer salta all'iterazione successiva del ciclo con l'etichetta.
Cosa è cambiato per le variabili di ciclo in Go 1.22?
Prima di Go 1.22, un ciclo for aveva una sola variabile condivisa da tutte le iterazioni, quindi closure e goroutine avviate nel ciclo vedevano spesso tutte il valore finale. Da Go 1.22 ogni iterazione ha una propria copia, quindi catturare i o v in una closure funziona come ti aspetti. Il nuovo comportamento si applica quando il go.mod del modulo indica go 1.22 o successivo.