Go ma dokładnie jedno słowo kluczowe pętli: for. Ma ono cztery postacie, które razem pokrywają każdą pętlę, jaką w innych językach piszesz przez for, while, do while lub foreach.
Nagłówka pętli nie otacza się nawiasami okrągłymi, a nawiasy klamrowe są zawsze wymagane.
Pętla z trzema klauzulami
for init; condition; post {
// body
}
initwykonuje się raz przed pętlą. Zwykle jest to krótka deklaracja zmiennej, jaki := 0, a ta zmienna istnieje tylko wewnątrz pętli.conditionjest sprawdzany przed każdą iteracją. Gdy jest fałszywy, pętla się kończy.- Wykonuje się ciało pętli.
postwykonuje się po każdej iteracji, zwykle jest toi++.
i++ jest w Go instrukcją, a nie wyrażeniem, więc post nie może zawierać i++, j--. Zamiast tego użyj przypisania równoległego: i, j = i+1, j-1.
Wszystkie trzy klauzule są opcjonalne. for ; i < 10; {} jest poprawne, a gofmt przepisuje to na for i < 10 {}, czyli następną formę.
Pętla z samym warunkiem (while w Go)
Z samym warunkiem for zachowuje się jak while w innych językach:
Nie ma osobnego do while. Strona o pętli while pokazuje, jak uzyskać zachowanie "wykonaj przynajmniej raz".
Pętle nieskończone
for bez niczego dalej kręci się w nieskończoność. Wychodzisz z niej przez break, return, os.Exit albo panic:
for {
line, err := reader.ReadString('\n')
if err != nil {
break
}
process(line)
}
To standardowy kształt dla serwerów, workerów i pętli wczytujących dane. for {} z pustym ciałem zużywa cały rdzeń procesora; w prawdziwym kodzie ciało na czymś blokuje (odczyt, odbiór z kanału, select).
Pętle range
range iteruje po kolekcji i w każdej iteracji zwraca jedną lub dwie wartości. Co to za wartości, zależy od tego, po czym iterujesz:
| Range po | Pierwsza wartość | Druga wartość |
|---|---|---|
| slice lub tablica | indeks | element (kopia) |
| string | indeks bajtu | runa (znak Unicode) |
| mapa | klucz | wartość |
| kanał | element | (brak) |
liczba całkowita n (Go 1.22) | od 0 do n minus 1 | (brak) |
| funkcja iteratora (Go 1.23) | to, co zwraca funkcja | to, co zwraca funkcja |
Trzy szczegóły widoczne w wyniku:
- Zmienna wartości to kopia.
v *= 2wewnątrz pętli nie zmieniłoby slice'a; żeby zmieniać elementy, odwołuj się do slice'a przez indeks,nums[i], tak jak w trzeciej pętli. - Indeks stringa to przesunięcie w bajtach.
ézajmuje dwa bajty, więc indeks skacze z 1 na 3. - Kolejność w mapie jest celowo losowa. Go ją losuje, żeby kod nie mógł na niej polegać.
slices.Sorted(maps.Keys(m))(Go 1.23) daje klucze po kolei.
range jest w pełni omówione na stronie o range, łącznie z kanałami, funkcjami iteratorów i zasadami kopiowania.
break i continue
break kończy najbardziej wewnętrzną pętlę. continue przechodzi do następnej iteracji (w pętli z trzema klauzulami najpierw wykonując post):
break i continue z etykietą
Zwykłe break opuszcza tylko najbardziej wewnętrzną pętlę. Żeby z zagnieżdżonej pętli wyjść z pętli zewnętrznej, oznacz ją etykietą:
Etykieta to identyfikator zakończony dwukropkiem, umieszczony bezpośrednio przed for. Go zgłasza nieużywaną etykietę jako błąd kompilacji, tak samo jak nieużywaną zmienną.
break wewnątrz switch i select
Wewnątrz for instrukcja break w gałęzi switch lub select opuszcza switch lub select, a nie pętlę:
for {
switch cmd := next(); cmd {
case "quit":
break // only exits the switch; the loop keeps running
}
}
Żeby opuścić pętlę z wnętrza switch lub select, użyj break z etykietą albo return.
Zmienne pętli od Go 1.22
Go 1.22 zmieniło zasięg zmiennych pętli: każda iteracja dostaje teraz nową kopię i i v. Wcześniej przez całą pętlę używana była jedna zmienna, co psuło domknięcia i gorutyny, które ją przechwytywały:
W Go 1.22 i nowszych ten kod wypisuje 0 1 2 oraz [aa bb cc]. Przy starej semantyce ten sam program zapisany z for i := 0; i < 3; i++ (range po liczbie całkowitej nie istniał przed 1.22) wypisywał 3 3 3, a gorutyny ścigały się o wspólne i i name. Dawne obejście, i := i na początku ciała pętli, nie jest już potrzebne.
Zachowanie wybiera się dla każdego modułu linią go w go.mod, a nie wersją kompilatora. Moduł, który nadal ma go 1.21, zachowuje starą semantykę nawet po zbudowaniu w Go 1.24.
Typowe błędy
Modyfikowanie kopii. for _, v := range items { v.Count++ } zmienia kopię. Użyj items[i].Count++ albo iteruj po slice'ie wskaźników.
Dopisywanie podczas iteracji range. range oblicza slice raz, na początku. Elementy dopisane w pętli nie są odwiedzane, więc pętla nadal się kończy, ale nie zobaczy nowych elementów.
Usuwanie ze slice'a po indeksie w pętli do przodu. Usunięcie elementu i przesuwa resztę w lewo, więc następny element zostaje pominięty. Zbuduj nowy slice, iteruj od końca albo użyj slices.DeleteFunc.
Pomyłka o jeden przy <=. for i := 0; i <= len(s); i++ w ostatnim przebiegu czyta s[len(s)] i wywołuje panic z index out of range. Użyj <, a jeszcze lepiej range.
Ciężka praca w warunku. Warunek wykonuje się w każdej iteracji. for i := 0; i < expensiveCount(); i++ za każdym razem wywołuje funkcję; oblicz wynik raz przed pętlą.
Float jako licznik. for x := 0.0; x != 1.0; x += 0.1 nigdy się nie kończy, bo 0.1 nie ma dokładnej reprezentacji binarnej. Licz liczbą całkowitą i z niej obliczaj floata.
Najczęściej zadawane pytania
Jak napisać pętlę for w Go?
Klasyczna forma ma trzy części rozdzielone średnikami i żadnych nawiasów okrągłych:
for i := 0; i < 5; i++ {
fmt.Println(i)
}
Od Go 1.22 możesz też napisać for i := range 5, co liczy od 0 do 4. Żeby przejść po slice'ie, użyj for i, v := range items.
Czy Go ma pętlę while?
Nie ma słowa kluczowego while, ale for z samym warunkiem to pętla while: for n > 0 { n /= 2 }. for { ... } bez warunku to pętla nieskończona, z której wychodzisz przez break lub return.
Jak przejść pętlą po mapie w Go?
for key, value := range m { ... }. Kolejność iteracji nie jest określona, a środowisko uruchomieniowe ją losuje, więc może się różnić między kolejnymi pętlami. Nigdy na niej nie polegaj. Dla stałej kolejności zbierz klucze, posortuj je przez slices.Sort i iteruj po posortowanych kluczach.
Jak wyjść z zagnieżdżonej pętli w Go?
Postaw etykietę przed zewnętrzną pętlą i podaj ją w break: outer: for ... { for ... { if found { break outer } } }. Zwykłe break opuszcza tylko najbardziej wewnętrzne for, switch lub select. continue outer analogicznie przechodzi do następnej iteracji pętli z etykietą.
Co zmieniło się w zmiennych pętli w Go 1.22?
Przed Go 1.22 pętla for miała jedną zmienną wspólną dla wszystkich iteracji, więc domknięcia i gorutyny uruchomione w pętli często widziały tylko końcową wartość. Od Go 1.22 każda iteracja dostaje własną kopię, więc przechwycenie i lub v w domknięciu działa zgodnie z oczekiwaniami. Nowe zachowanie obowiązuje, gdy go.mod modułu zawiera go 1.22 lub nowszą wersję.