range, bir koleksiyonu dolaşan for biçimidir. Her yinelemede bir ya da iki değer verir ve bu değerlerin ne olduğu üzerinde döndüğünüz tipe bağlıdır:
| İşlenen | İlk değer | İkinci değer | Notlar |
|---|---|---|---|
slice []T, dizi [n]T | indeks int | eleman T, bir kopya | |
diziye pointer *[n]T | indeks | eleman | |
| string | bayt indeksi int | rune | UTF-8 çözer |
map map[K]V | anahtar K | değer V | rastgele sıra |
channel chan T | eleman T | yok | channel kapanana kadar |
tam sayı n | 0'dan n eksi 1'e | yok | Go 1.22 |
func(yield func() bool) | yok | yok | Go 1.23 |
func(yield func(V) bool) | V | yok | Go 1.23 |
func(yield func(K, V) bool) | K | V | Go 1.23 |
Hangi Değişkenleri Alacağınızı Seçmek
for i, v := range s {} // both
for i := range s {} // index (or key) only
for _, v := range s {} // value only
for range s {} // neither: just repeat len(s) times
Kullanmadığınız bir değişkeni tanımlamak derleme hatasıdır, bu yüzden ihtiyacınız olmayanları yazmayın ya da boş tanımlayıcıyla atın. for range 3 {} bir şeyi üç kez tekrarlamanın en kısa yoludur.
Değer Bir Kopyadır
Her yinelemede eleman değer değişkenine kopyalanır. Değişkeni değiştirmek koleksiyonu değiştirmez:
Elemanlar büyük struct'lar olduğunda kopyalama zaman da harcar. Büyük struct'lardan oluşan bir slice için s[i] ile birlikte for i := range s kopyadan kaçınır.
String'ler Rune Verir
Bir string üzerinde range yapmak UTF-8'i çözer. İlk değer her karakterin başladığı bayt konumudur, bu yüzden bazı sayıları atlayabilir:
Karakterler yerine baytları dolaşmak için bir indeks döngüsü, for i := 0; i < len(s); i++, kullanın ya da []byte(s) üzerinde range yapın. UTF-8 ayrıntıları için runes and bytes sayfasına bakın.
Map'ler: Rastgele Sıra
Map dolaşma sırası belirtilmemiştir ve çalışma zamanı, kod tek bir sıraya bağımlı hale gelmesin diye onu rastgele yapar. Deterministik çıktı için anahtarları sıralayın:
Dönerken girdileri silmek güvenlidir ve henüz ulaşılmamış silinmiş bir girdi üretilmez. Girdi eklemek serbesttir ama öngörülemez: yeni bir anahtar aynı döngüde daha sonra görünebilir de görünmeyebilir de. Bir map'in fmt.Println ile yazdırılması anahtarları sıralı yazar; son satırın kararlı olmasının nedeni budur.
Channel'lar: Kapanana Kadar
Bir channel üzerinde range yapmak, channel kapanıp boşalana kadar değer alır. Onu kimse kapatmazsa döngü sonsuza kadar bloklanır (ve her goroutine bloklanmışsa çalışma zamanı fatal error: all goroutines are asleep - deadlock! ile durur):
Channel'ı alıcı değil, gönderici kapatır. Buffer'lı channel'lar ve select için channels sayfasına bakın.
Tam Sayılar (Go 1.22)
range n, 0'dan başlayıp n'den küçük değerleri sayar. Döngü değişkeninin tipi n'in tipidir:
for i := range 3 {} // i is int: 0, 1, 2
for i := range uint8(3) {} // i is uint8
for range 0 {} // runs zero times
Negatif bir n de sıfır kez çalışır. n, döngü başlamadan önce bir kez değerlendirilir.
range Neyi ve Ne Zaman Değerlendirir
range'den sonraki ifade, ilk yinelemeden önce bir kez değerlendirilir. Bir slice için range sonra o slice başlığını kullanır, bu yüzden uzunluğu sabittir:
Eklenen 99 hiç ziyaret edilmez, çünkü döngü uzunluğu 3 olarak sabitlenmişti. Ama nums[2] = 30 görülür, çünkü o anda nums hâlâ üzerinde dönülen slice ile aynı arka diziyi paylaşır. İki satırın yerini değiştirin, 30 da kaybolur: append 3'lük kapasiteyi aşar, yeni bir dizi ayırır ve yazma işlemi oraya düşer. Bir dizi değeri üzerinde range yapmak önce dizinin tamamını kopyalar, bu yüzden arr[2]'deki değişiklik görülmez; kopyadan kaçınmak için &arr ya da arr[:] üzerinde range yapın.
Fonksiyonlar Üzerinde range (Go 1.23)
Go 1.23'ten beri range iterator fonksiyonlarını da kabul eder. Bir iterator bir yield callback'i alır, onu her değer için bir kez çağırır ve yield false döndürdüğünde durur (bu, döngü gövdesi break ya da return çalıştırdığında olur):
iter.Seq[V] ve iter.Seq2[K, V], iki iterator biçiminin standart adlarıdır. Standart kütüphane bunları slices.All, slices.Values, slices.Backward, maps.Keys, maps.Values, maps.All fonksiyonlarından, Go 1.24'te ise strings.SplitSeq, strings.Lines ve bytes.SplitSeq'ten döndürür. Iterator, yield'ın dönüş değerine uymalıdır: false döndürdükten sonra yield'ı yeniden çağırmak panic'e yol açar.
Döngü Değişkenleri Yinelemeye Özgüdür (Go 1.22)
Go 1.22'den beri for ... range ile tanımlanan değişkenler her yinelemede yenidir. v'yi bir closure ya da goroutine içinde yakalamak, son elemanı tutmakla sonuçlanan paylaşılmış bir değişkeni değil, o yinelemenin değerini yakalar. Go 1.22'den önce döngünün içinde v := v gerekiyordu; bu satır artık gereksizdir. Kural go.mod içindeki go sürümüyle belirlenir, bu yüzden eski bir modül eski davranışı korur.
Sık Yapılan Hatalar
- Değer değişkeninin adresini almak. Go 1.22'den önce
ptrs = append(ptrs, &v)her seferinde aynı adresi saklıyordu. 1.22'den beri farklı adresler saklıyor, ama her biri slice elemanına değil bir kopyaya işaret ediyor. Slice'ın içine işaret etmek için&items[i]kullanın. - Map sırası beklemek. Küçük bir testte sıralı görünen çıktı sıralı kalmaz.
- Kimsenin kapatmadığı bir channel üzerinde range yapmak. Döngü ve onu çalıştıran goroutine sonsuza kadar bekler.
- Büyük bir dizi üzerinde değer olarak range yapmak. Önce dizinin tamamı kopyalanır. Bunun yerine onun bir slice'ı üzerinde range yapın.
Sıkça Sorulan Sorular
Go'da range ne döndürür?
İşlenene bağlıdır. Slice ya da dizi için: indeks ve eleman. String için: bayt indeksi ve rune. Map için: anahtar ve değer. Channel için: alınan her değer. Bir n tam sayısı için (Go 1.22): 0'dan n eksi 1'e. Bir iterator fonksiyonu için (Go 1.23): fonksiyon ne veriyorsa. İkinci değişkeni yazmayabilir ya da ilkini _ ile atabilirsiniz.
range değerini değiştirmek slice'ımı neden değiştirmiyor?
Değer değişkeni elemanın bir kopyasıdır. for _, v := range items { v.Price = 0 } yalnızca kopyayı değiştirir. Bunun yerine indeksi kullanın: for i := range items { items[i].Price = 0 }, ya da pointer'lardan oluşan bir slice üzerinde dönün.
Go'da bir tam sayı üzerinde range yapabilir miyim?
Evet, Go 1.22'den beri: for i := range 5 { ... }, i 0'dan 4'e giderek çalışır. for range 5 { ... } değişken olmadan beş kez tekrarlar. Modülün go.mod dosyası go 1.22 ya da sonrasını belirtmelidir.
Bir map üzerinde dönerken ondan silmek güvenli mi?
Evet. Henüz ulaşılmamış bir girdiyi silmek onun üretilmeyeceği anlamına gelir; mevcut girdiyi silmek de sorun değildir. Döngü sırasında girdi eklemek de serbesttir, ama yeni bir girdi ziyaret edilebilir de edilmeyebilir de, bu yüzden buna güvenmeyin.