Slice
Go'da dizi ve slice neredeyse tamamen aynı görünür, ancak işlevselliklerinde önemli farklar vardır. Dizi sabit uzunluklu bir veri yapısıdır, uzunluk belirtildikten sonra değiştirilemez. Slice ise değişken uzunlukludur, kapasite yetersiz olduğunda otomatik olarak genişler.
Dizi
Eğer depolanacak verinin uzunluğunu önceden biliyorsanız ve sonraki kullanımda genişletme ihtiyacı olmayacaksa, dizi kullanmayı düşünebilirsiniz. Go'da dizi değer türüdür, referans değil, baş elemana işaret eden pointer değildir.
TIP
Dizi değer türü olarak, dizi fonksiyona parametre olarak aktarıldığında, Go fonksiyonları değer ile aktardığı için, tüm dizi kopyalanır.
Başlatma
Dizi tanımlanırken uzunluk sadece bir sabit olabilir, değişken olamaz. Bir değişken tanımlayıp ardından dizi uzunluğu olarak kullanamazsınız.
// Doğru örnek
var a [5]int
// Yanlış örnek
l := 1
var b [l]intÖnce uzunluğu 5 olan bir int dizi başlatalım:
var nums [5]intElemanlarla da başlatabilirsiniz:
nums := [5]int{1, 2, 3}Derleyicinin uzunluğu otomatik olarak tahmin etmesini sağlayabilirsiniz:
nums := [...]int{1, 2, 3, 4, 5} // nums := [5]int{1, 2, 3, 4, 5} ile eşdeğer, üç nokta mutlaka bulunmalıdır, aksi takdirde slice oluşturulur, dizi değilnew fonksiyonu ile de bir pointer elde edebilirsiniz:
nums := new([5]int)Yukarıdaki birkaç yöntem nums için sabit boyutlu bellek tahsis eder, fark sadece sonuncusunun değerinin pointer olmasıdır.
Dizi başlatırken dikkat edilmesi gereken, uzunluğun bir sabit ifade olması gerektiğidir, aksi takdirde derlenemez. Sabit ifade ifadenin sonucunun bir sabit olmasıdır. Yanlış örnek:
length := 5 // Bu bir değişken
var nums [length]intlength bir değişkendir, bu yüzden dizi uzunluğunu başlatmak için kullanılamaz. Aşağıda doğru örnekler verilmiştir:
const length = 5
var nums [length]int // sabit
var nums2 [length + 1]int // sabit ifade
var nums3 [(1 + 2 + 3) * 5]int // sabit ifade
var nums4 [5]int // en yaygın kullanılanKullanım
Dizi adı ve indeks ile dizideki karşılık gelen elemana erişebilirsiniz.
fmt.Println(nums[0])Aynı şekilde dizi elemanını değiştirebilirsiniz:
nums[0] = 1Yerleşik len fonksiyonu ile dizi eleman sayısına erişebilirsiniz:
len(nums)Yerleşik cap fonksiyonu ile dizi kapasitesine erişebilirsiniz. Dizi kapasitesi dizi uzunluğuna eşittir. Kapasite slice için anlamlıdır.
cap(nums)Dilimleme
Dizi dilimleme formatı arr[startIndex:endIndex] şeklindedir, dilimlenen aralık sol kapalı sağ açıktır. Dizi dilimlendikten sonra, slice türüne dönüşür. Örnek:
nums := [5]int{1, 2, 3, 4, 5}
nums[:] // alt slice aralığı [0,5) -> [1 2 3 4 5]
nums[1:] // alt slice aralığı [1,5) -> [2 3 4 5]
nums[:5] // alt slice aralığı [0,5) -> [1 2 3 4 5]
nums[2:3] // alt slice aralığı [2,3) -> [3]
nums[1:3] // alt slice aralığı [1,3) -> [2 3]func main() {
arr := [5]int{1, 2, 3, 4, 5}
fmt.Printf("%T\n", arr)
fmt.Printf("%T\n", arr[1:2])
}Çıktı:
[5]int
[]intDiziyi slice türüne dönüştürmek için parametresiz dilimleme yapın. Dönüştürüldükten sonra slice ile orijinal dizi aynı belleği işaret eder. Slice'ı değiştirmek orijinal dizi içeriğinin değişmesine neden olur:
func main() {
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[:]
slice[0] = 0
fmt.Printf("array: %v\n", arr)
fmt.Printf("slice: %v\n", slice)
}Çıktı:
array: [0 2 3 4 5]
slice: [0 2 3 4 5]Dönüştürülmüş slice'ı değiştirmek isterseniz, aşağıdaki yöntemi kullanmanız önerilir:
func main() {
arr := [5]int{1, 2, 3, 4, 5}
slice := slices.Clone(arr[:])
slice[0] = 0
fmt.Printf("array: %v\n", arr)
fmt.Printf("slice: %v\n", slice)
}Çıktı:
array: [1 2 3 4 5]
slice: [0 2 3 4 5]Slice
Slice Go'da diziden çok daha yaygın kullanılır, bilinmeyen uzunluktaki verileri depolamak için kullanılır ve sonraki kullanımda sık sık ekleme ve silme işlemleri yapılabilir.
Başlatma
Slice başlatma yöntemleri şunlardır:
var nums []int // değer
nums := []int{1, 2, 3} // değer
nums := make([]int, 0, 0) // değer
nums := new([]int) // pointerSlice ile dizi arasındaki görünüş farkı sadece başlatma uzunluğunun olmamasıdır. Genellikle boş slice oluşturmak için make kullanmanız önerilir. Slice için make fonksiyonu üç parametre alır: tür, uzunluk, kapasite. Uzunluk ve kapasite arasındaki farkı açıklayalım: bir su kovası olduğunu varsayalım, su dolu değil. Kovanın yüksekliği kovanın kapasitesidir, toplam kaç yükseklikte su alabileceğini temsil eder. Kovadaki suyun yüksekliği uzunluğu temsil eder. Su yüksekliği mutlaka kova yüksekliğinden küçük veya eşit olmalıdır, aksi takdirde su taşar. Bu yüzden slice uzunluğu slice'taki eleman sayısını temsil eder, slice kapasitesi slice'ın toplam kaç eleman alabileceğini temsil eder. Slice ile dizi arasındaki en büyük fark slice kapasitesinin otomatik olarak genişleyebilmesidir, dizi ise genişleyemez. Daha fazla detay için Referans El Kitabı - Uzunluk ve Kapasite adresini ziyaret edin.
TIP
Slice'ın alt düzey uygulaması hala dizidir, referans türüdür. Basitçe alt düzey diziye işaret eden pointer olarak anlaşılabilir (doğası gereği slice Go'da bir struct'tır, alt düzey diziye işaret eden pointer, uzunluk değeri, kapasite değeri içerir). Bu yüzden slice fonksiyon parametresi olarak aktarıldığında alt düzey dizi kopyalanmaz, fonksiyon içinde传入 slice'da yapılan değişiklikler orijinal slice'ta yansır.
var nums []int yöntemiyle tanımlanan slice'ın varsayılan değeri nil'dir, bu yüzden onun için bellek tahsis edilmez. make ile başlatırken, sonraki genişletme bellek tüketimini etkili bir şekilde azaltmak için yeterli bir kapasite önceden tahsis etmeniz önerilir.
Kullanım
Slice'ın temel kullanımı dizi ile tamamen aynıdır, fark sadece slice uzunluğunu dinamik olarak değiştirebilmesidir. Aşağıda birkaç örnek verilmiştir.
Slice append fonksiyonu ile birçok işlem gerçekleştirebilir. Fonksiyon imzası şöyledir: slice öğe eklenecek hedef slice, elems eklenecek öğeler, dönüş değeri eklendikten sonraki slice.
func append(slice []Type, elems ...Type) []TypeÖnce uzunluğu 0, kapasitesi 0 olan boş slice oluşturun, ardından sonuna bazı öğeler ekleyin, son olarak uzunluk ve kapasiteyi çıktılayın.
nums := make([]int, 0, 0)
nums = append(nums, 1, 2, 3, 4, 5, 6, 7)
fmt.Println(len(nums), cap(nums)) // 7 8 uzunluk ve kapasitenin tutarsız olduğu görülebilir.Yeni slice'ta ayrılan buffer kapasitesi belirli bir kurala sahiptir. golang 1.18 sürüm güncellemesinden önce internetteki çoğu makale slice genişletme stratejisini şöyle tanımlıyordu: Orijinal slice kapasitesi 1024'ten küçük olduğunda, yeni slice kapasitesi orijinalin 2 katı olur; orijinal slice kapasitesi 1024'ü aştığında, yeni slice kapasitesi orijinalin 1.25 katı olur. 1.18 sürüm güncellemesinden sonra, slice genişletme stratejisi şu şekilde değişti: Orijinal slice kapasitesi (oldcap) 256'dan küçük olduğunda, yeni slice (newcap) kapasitesi orijinalin 2 katı olur; orijinal slice kapasitesi 256'yı aştığında, yeni slice kapasitesi newcap = oldcap+(oldcap+3*256)/4
Eleman Ekleme
Slice eleman ekleme de append fonksiyonu ile birlikte kullanılmalıdır. Mevcut slice:
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}Baştan eleman ekleme:
nums = append([]int{-1, 0}, nums...)
fmt.Println(nums) // [-1 0 1 2 3 4 5 6 7 8 9 10]Orta indeks i'den eleman ekleme:
nums = append(nums[:i+1], append([]int{999, 999}, nums[i+1:]...)...)
fmt.Println(nums) // i=3, [1 2 3 4 999 999 5 6 7 8 9 10]Sondan eleman ekleme, append'in en orijinal kullanımıdır:
nums = append(nums, 99, 100)
fmt.Println(nums) // [1 2 3 4 5 6 7 8 9 10 99 100]Eleman Silme
Slice eleman silme append fonksiyonu ile birlikte kullanılmalıdır. Mevcut slice:
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}Baştan n eleman silme:
nums = nums[n:]
fmt.Println(nums) //n=3 [4 5 6 7 8 9 10]Sondan n eleman silme:
nums = nums[:len(nums)-n]
fmt.Println(nums) //n=3 [1 2 3 4 5 6 7]Ortada belirtilen i indeksinden başlayarak n eleman silme:
nums = append(nums[:i], nums[i+n:]...)
fmt.Println(nums)// i=2, n=3, [1 2 6 7 8 9 10]Tüm elemanları silme:
nums = nums[:0]
fmt.Println(nums) // []Kopyalama
Slice kopyalarken hedef slice'ın yeterli uzunluğa sahip olduğundan emin olunmalıdır. Örneğin:
func main() {
dest := make([]int, 0)
src := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(src, dest)
fmt.Println(copy(dest, src))
fmt.Println(src, dest)
}[1 2 3 4 5 6 7 8 9] []
0
[1 2 3 4 5 6 7 8 9] []Uzunluğu 10 olarak değiştirirseniz, çıktı şu şekilde olur:
[1 2 3 4 5 6 7 8 9] [0 0 0 0 0 0 0 0 0 0]
9
[1 2 3 4 5 6 7 8 9] [1 2 3 4 5 6 7 8 9 0]Yineleme
Slice yineleme dizi ile tamamen aynıdır. for döngüsü:
func main() {
slice := []int{1, 2, 3, 4, 5, 7, 8, 9}
for i := 0; i < len(slice); i++ {
fmt.Println(slice[i])
}
}for range döngüsü:
func main() {
slice := []int{1, 2, 3, 4, 5, 7, 8, 9}
for index, val := range slice {
fmt.Println(index, val)
}
}Çok Boyutlu Slice
Önce aşağıdaki örneğe bakalım, resmi belgede de açıklama vardır: Effective Go - İki Boyutlu Slice
var nums [5][5]int
for _, num := range nums {
fmt.Println(num)
}
fmt.Println()
slices := make([][]int, 5)
for _, slice := range slices {
fmt.Println(slice)
}Çıktı:
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[]
[]
[]
[]
[]Görülebileceği gibi, aynı şekilde iki boyutlu dizi ve slice, iç yapıları farklıdır. Dizi başlatıldığında, birinci ve ikinci boyutun uzunluğu çoktan sabittir. Slice'ın uzunluğu ise sabit değildir, slice'taki her slice'ın uzunluğu farklı olabilir, bu yüzden ayrı ayrı başlatılmalıdır. Slice başlatma kısmını aşağıdaki kodla değiştirirseniz:
slices := make([][]int, 5)
for i := 0; i < len(slices); i++ {
slices[i] = make([]int, 5)
}Son çıktı:
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]Genişletilmiş İfade
TIP
Sadece slice genişletilmiş ifadeyi kullanabilir.
Slice ve dizi her ikisi de basit ifade ile dilimlenebilir, ancak genişletilmiş ifade sadece slice tarafından kullanılabilir. Bu özellik Go 1.2 sürümünde eklendi, esas olarak slice'ın alt düzey dizi paylaşımı okuma-yazma sorununu çözmek içindir. Ana format şöyledir, low<= high <= max <= cap ilişkisini karşılaması gerekir. Genişletilmiş ifade ile dilimlenen slice'ın kapasitesi max-low'dur:
slice[low:high:max]low ve high hala orijinal anlamlarını korur, eklenen max ise maksimum kapasiteyi ifade eder. Aşağıdaki örnekte max atlanmıştır, bu yüzden s2'nin kapasitesi cap(s1)-low olur:
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4] // cap = 9 - 3 = 6Bu durumda belirgin bir sorun vardır: s1 ve s2 aynı alt düzey diziyi paylaşır. s2'yi okurken veya yazarken, s1'in verilerini etkileyebilir. Aşağıdaki kod bu duruma aittir:
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4] // cap = 9 - 3 = 6
s2 = append(s2, 1) // yeni öğe ekle, kapasite 6 olduğu için genişletme yok, doğrudan alt düzey dizi değiştirildi
fmt.Println(s2)
fmt.Println(s1)Son çıktı:
[4 1]
[1 2 3 4 1 6 7 8 9]Açıkça s2'ye öğe eklendiği halde, s1 de birlikte değiştirildi. Genişletilmiş ifade bu tür sorunları çözmek için oluşturuldu. Sadece biraz değiştirerek bu sorun çözülebilir:
func main() {
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4:4] // cap = 4 - 3 = 1
s2 = append(s2, 1) // kapasite yetersiz, yeni alt düzey dizi tahsis et
fmt.Println(s2)
fmt.Println(s1)
}Şimdi sonuç normal:
[4 1]
[1 2 3 4 5 6 7 8 9]clear
go1.21'de clear yerleşik fonksiyonu eklendi. clear slice içindeki tüm değerleri sıfır değerine ayarlar:
package main
import (
"fmt"
)
func main() {
s := []int{1, 2, 3, 4}
clear(s)
fmt.Println(s)
}Çıktı:
[0 0 0 0]Slice'ı temizlemek isterseniz:
func main() {
s := []int{1, 2, 3, 4}
s = s[:0:0]
fmt.Println(s)
}Dilimlendikten sonra kapasite sınırlandırılır, bu da orijinal slice'ın sonraki elemanlarının üzerine yazılmasını önler.
