Skip to content

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.

go
// 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:

go
var nums [5]int

Elemanlarla da başlatabilirsiniz:

go
nums := [5]int{1, 2, 3}

Derleyicinin uzunluğu otomatik olarak tahmin etmesini sağlayabilirsiniz:

go
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ğil

new fonksiyonu ile de bir pointer elde edebilirsiniz:

go
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:

go
length := 5 // Bu bir değişken
var nums [length]int

length bir değişkendir, bu yüzden dizi uzunluğunu başlatmak için kullanılamaz. Aşağıda doğru örnekler verilmiştir:

go
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ılan

Kullanım

Dizi adı ve indeks ile dizideki karşılık gelen elemana erişebilirsiniz.

go
fmt.Println(nums[0])

Aynı şekilde dizi elemanını değiştirebilirsiniz:

go
nums[0] = 1

Yerleşik len fonksiyonu ile dizi eleman sayısına erişebilirsiniz:

go
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.

go
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:

go
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]
go
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
[]int

Diziyi 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:

go
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:

go
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:

go
var nums []int // değer
nums := []int{1, 2, 3} // değer
nums := make([]int, 0, 0) // değer
nums := new([]int) // pointer

Slice 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.

go
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.

go
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:

go
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

Baştan eleman ekleme:

go
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:

go
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:

go
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:

go
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

Baştan n eleman silme:

go
nums = nums[n:]
fmt.Println(nums) //n=3 [4 5 6 7 8 9 10]

Sondan n eleman silme:

go
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:

go
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:

go
nums = nums[:0]
fmt.Println(nums) // []

Kopyalama

Slice kopyalarken hedef slice'ın yeterli uzunluğa sahip olduğundan emin olunmalıdır. Örneğin:

go
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ü:

go
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ü:

go
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

go
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:

go
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:

go
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:

go
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4] // cap = 9 - 3 = 6

Bu 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:

go
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:

go
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:

go
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:

go
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.

Golang by www.golangdev.cn edit