Menu

Golang Slice: append, make, len ve cap, Paylaşım Hatası

Slice'lar Go'nun gündelik liste tipidir. make, append ve büyümeyi, len ile cap farkını, dilimlemenin arka diziyi nasıl paylaştığını (ve bunun yol açtığı hatayı), copy'yi, slices paketini, 2 boyutlu slice'ları ve nil ile boş farkını öğrenin.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Tek örnekte slice

Slice, bir diziye açılan büyüyebilen bir görünümdür. Bir literal ya da make ile oluşturur, append ile büyütür ve boyutunu len ile okursunuz.

[]string'in köşeli parantezlerinde uzunluk yoktur. Boyutu sabit olan [4]string gibi bir dizi tipinden farkı budur.

Slice nedir

Bir slice değeri üç alanlı küçük bir başlıktır:

  • arka dizideki bir elemana işaret eden bir pointer
  • bir uzunluk: slice'ın kaç elemanı görebildiği
  • bir kapasite: o pointer'dan arka dizinin sonuna kadar kaç eleman olduğu

Bir slice'ı kopyalamak (atamak, bir fonksiyona aktarmak) yalnızca bu başlığı kopyalar. İki kopya da aynı elemanlara işaret eder. Neredeyse her slice sürprizi bunu unutmaktan gelir.

make: uzunluk ve kapasite

make([]T, len, cap) bir arka dizi ayırır ve onun üzerinde bir slice döndürür. Kapasite isteğe bağlıdır ve varsayılan olarak uzunluğa eşittir.

Yaygın bir karışıklık: make([]int, 5) ardından beş append çağrısı size ilk beşi sıfır olan on eleman verir. Eleman eklemeyi planlıyorsanız make([]int, 0, 5), indeksle atamayı planlıyorsanız make([]int, 5) kullanın.

Son boyutu bildiğinizde kapasiteyi önceden ayırmak tekrarlanan büyümeyi önler. Bu bir optimizasyondur, zorunluluk değil.

append ve büyüme

append, elemanları mevcut uzunluğun ardına ekler. Kapasite yeterliyse yerinde yazar. Değilse daha büyük bir dizi ayırır, eski elemanları oraya kopyalar ve yeni diziye işaret eden bir slice döndürür.

Kapasite, slice küçükken kabaca iki katına çıkar, 256 elemanı geçtikten sonra ise daha küçük bir çarpanla (1.25x'e doğru) büyür. Kesin sayılar Go sürümleri arasında değişmiş bir çalışma zamanı ayrıntısıdır, bu yüzden asla onlara bağımlı kod yazmayın.

append farklı bir dizi döndürebileceği için sonucunu kullanmak zorundasınız:

append(s, 4)     // compile error: append(s, 4) (value of type []int) is not used
s = append(s, 4) // correct

Bütün bir slice'ı eklemek açma söz dizimini kullanır: s = append(s, other...).

Dilimleme arka diziyi paylaşır

s[low:high], aynı dizi üzerinde low indeksinden başlayıp high indeksinden hemen önce biten yeni bir slice başlığı oluşturur. Sınırlardan herhangi biri yazılmayabilir. Hiçbir eleman kopyalanmaz.

Çıktı:

[10 99 30 40 50]
3 4

Dilimlemeyi ucuz yapan bu paylaşımdır. En ünlü slice hatasının kaynağı da odur.

append paylaşım (aliasing) hatası

Bir alt slice'ın boş kapasitesi varsa ona eleman eklemek, üst slice'ın dizisine, üst slice'ın hâlâ kullandığı elemanların üzerine yazar.

Çıktı:

[1 2 100 4 5]
[1 2 100]
[cmd test] [cmd test]

a ve b, "cmd"'den sonraki dizi yuvasını paylaşır, bu yüzden ikinci append ilkinin üzerine yazdı. Kod düzgün görünür ve kapasite tesadüfen dolu olduğunda çalışır; bu hatanın yalnızca bazen ortaya çıkmasının nedeni budur.

İki çözüm:

  1. Tam slice ifadesi s[low:high:max], kapasiteyi max-low ile sınırlar. Boş kapasite olmadığında bir sonraki append yeni bellek ayırmak zorundadır. first := base[:2:2] ilk örneği güvenli hale getirir.
  2. Bir slice çağrıdan uzun yaşayacaksa ya da bağımsız olarak büyütülecekse açıkça kopyalayın: slices.Clone(prefix) ya da append([]string(nil), prefix...).

İlgili bir tuzak: devasa bir dizinin küçük bir slice'ı, dizinin tamamını çöp toplayıcı için canlı tutar. 100 MB'lık bir dosyayı okuyup data[:10]'u tutarsanız 100 MB bellekte kalır. Tuttuğunuz kısmı klonlayın.

copy

Yerleşik copy(dst, src), min(len(dst), len(src)) eleman kopyalar ve bu sayıyı döndürür. dst'yi asla büyütmez.

copy çakışan kaynak ve hedefi doğru ele alır, bu yüzden copy(s[1:], s) elemanları bozmadan sağa kaydırır.

slices paketi

Go 1.21'den beri standart slices paketi, eskiden elle yazdığınız işlemler için generic yardımcılar sunar.

FonksiyonNe yapar
Contains, Indexbir değer bulur (ContainsFunc, IndexFunc bir koşul fonksiyonu alır)
Sort, SortFunc, SortStableFuncyerinde sıralar
BinarySearchsıralı bir slice'ta arar
Insert, Delete, DeleteFuncekler ya da siler, yeni slice'ı döndürür
Compactardışık tekrarları kaldırır
Equal, Compareeleman eleman karşılaştırır
Clone, Reverse, Max, Minadlarının söylediğini
Collect, Sorted, Values, Alliterator'larla çalışır (Go 1.23)

Delete ve Insert, append gibi atamanız gereken bir slice döndürür. Sıralamanın kendi sayfası var: sıralama.

İndeksle dönerken eleman silmek bazı elemanları atlar. Bunun yerine slices.DeleteFunc kullanın:

s = slices.DeleteFunc(s, func(n int) bool { return n%2 == 0 }) // drop evens

2 boyutlu slice'lar

Slice'lardan oluşan bir slice, satırlarının uzunluğu farklı olabilen bir ızgara verir. Her satır ayrı ayrı ayrılmalıdır.

İçteki make'i unutmak her satırı nil bırakır ve grid[1][2] = 7, index out of range [2] with length 0 ile panic olur.

nil slice'lar ve boş slice'lar

Nil bir slice len, cap, range ve append için boş bir slice gibi davranır, bu yüzden sıfır durum olarak var s []T'yi tercih edin. Yalnızca fark önemliyse []T{} ile başlatın; bu da çoğunlukla JSON çıktısıdır (null ile []). Boşluğu s == nil ile değil, len(s) == 0 ile kontrol edin.

Slice'lar ve fonksiyonlar

Bir slice alan fonksiyon elemanlarını değiştirebilir ve çağıran değişiklikleri görür. Çağıranın uzunluğunu değiştiremez, çünkü başlığın bir kopyasını almıştır. Eleman ekleyen bir fonksiyon yeni slice'ı döndürmelidir:

func addAll(s []int, vals ...int) []int {
	return append(s, vals...)
}

append, slices.Delete ve slices.Insert'ün hepsinin bir slice döndürmesinin nedeni budur.

Sık yapılan hatalar

  • append'in sonucunu yok saymak. Tamamen atıldığında derlenmez, ama append(s, x)'i kullanmaya devam ettiğinizden farklı bir değişkene atamak bir mantık hatasıdır.
  • Tek bir tabandan iki append. Depolamayı paylaşabilirler. Tabanı klonlayın ya da tam slice ifadesi kullanın.
  • make([]T, n) ardından append. Başta n sıfır bırakır.
  • Index out of range. s[len(s)] panic olur. Son eleman s[len(s)-1]'dir.
  • Slice'ları == ile karşılaştırmak. Yalnızca s == nil derlenir. slices.Equal kullanın.

Sıkça Sorulan Sorular

Go'da bir slice'ın uzunluğu ile kapasitesi arasındaki fark nedir?

len(s), slice'ın şu anda tuttuğu eleman sayısıdır. cap(s), slice'ın ilk elemanından başlayarak arka diziye sığan eleman sayısıdır. append, boş kapasite varsa oraya yazar, yoksa yeni ve daha büyük bir dizi ayırır.

Go'da bir slice'a nasıl eleman eklenir?

Yerleşik append'i çağırın ve sonucu geri atayın: s = append(s, x). Birkaç değer (append(s, 1, 2, 3)) ya da başka bir slice (append(s, other...)) ekleyebilir. Dönüş değerini her zaman kullanın, çünkü append yeni bir diziye işaret eden bir slice döndürebilir.

Go'da bir slice'tan eleman nasıl silinir?

Sonraki elemanları aşağı kaydırıp kısalmış slice'ı döndüren slices.Delete(s, i, i+1) (Go 1.21) kullanın. Go 1.22'den beri boşalan kuyruk yuvalarını da sıfırlar. Sıra önemli değilse son elemanla yer değiştirip kırpın: s[i] = s[len(s)-1]; s = s[:len(s)-1].

Go'da bir slice'ın bir değeri içerip içermediği nasıl kontrol edilir?

Standart slices paketinden (Go 1.21) slices.Contains(s, v) kullanın. slices.Index(s, v) konumu ya da -1 döndürür, slices.ContainsFunc ise bir koşul fonksiyonu alır. Büyük veride tekrarlanan aramalar için bunun yerine bir map oluşturun.

Go'da nil slice ile boş slice arasındaki fark nedir?

var s []int nil'dir; s := []int{} boştur ama nil değildir. İkisinin de uzunluğu 0'dır, ikisi de len, range ve append ile çalışır. Fark s == nil kontrollerinde ve nil bir slice'ı null, boş bir slice'ı [] olarak kodlayan encoding/json'da ortaya çıkar.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA