Bir programın büyük kısmı veri listeleriyle uğraşır: bir dosyadaki satırlar, bir API'den dönen kullanıcılar, bir sensörden okunan ölçümler. Go'da bu listelerin neredeyse tamamı dilim (slice) ile temsil edilir. Dilimleri iyi anlamak, Go kodunu hem doğru hem de verimli yazmanın ön koşuludur. Kullanımı çok kolay görünür ama altında birkaç önemli ayrıntı saklar ve bu ayrıntıları bilmeyen geliştiriciler er ya da geç "bu değer nasıl değişti?" diye saatlerce hata ayıklar.
Dilimleri anlamanın en iyi yolu, önce üzerine kurulu oldukları diziyi (array) tanımaktır. Bir diziyi sabit sayıda gözü olan bir yumurta kolisi gibi düşünebilirsin: Koli üretildiği anda göz sayısı bellidir ve bir daha değişmez. Dilim ise bu kolinin üzerine konmuş, belirli bir aralığı gösteren hareketli bir çerçevedir. Çerçeveyi kaydırabilir, daraltabilir, yer kalmadığında daha büyük bir koliye taşınmasını isteyebilirsin. Ama çerçeve yumurtaları taşımaz, yalnızca onları gösterir.
Bu dersin sonunda bir dilimin bellekte üç alanlı küçük bir başlıktan (işaretçi, uzunluk, kapasite) oluştuğunu, append'in kapasiteyi nasıl büyüttüğünü, iki dilim aynı alttaki diziyi paylaştığında hangi hataların ortaya çıktığını ve bunları copy ile tam dilim ifadesiyle nasıl önleyeceğini bileceksin. Son olarak çok boyutlu dilimleri kuracak ve standart kütüphanedeki slices paketiyle günlük işleri birkaç satırda halledeceksin.
Diziler: sabit uzunluk ve değer semantiği
Dizi (array), aynı tipten sabit sayıda elemanın bellekte yan yana durduğu yapıdır. Go'da bir dizinin uzunluğu tipinin bir parçasıdır: [3]int ile [4]int birbirinden tamamen farklı iki tiptir ve biri diğerinin yerine kullanılamaz. Bu yüzden uzunluk derleme zamanında bilinmelidir; bir değişkenle [n]int tanımlayamazsın.
package main
import "fmt"
func main() {
var scores [5]int // tüm elemanlar sıfır değerle başlar
scores[0] = 90
scores[4] = 75
days := [3]string{"Pazartesi", "Salı", "Çarşamba"}
primes := [...]int{2, 3, 5, 7, 11} // uzunluğu derleyici sayar
sparse := [5]int{1: 10, 3: 30} // indeksli literal
fmt.Println(scores, len(scores))
fmt.Println(days, len(days))
fmt.Println(primes, len(primes))
fmt.Println(sparse)
fmt.Printf("%T ve %T farklı tiplerdir\n", days, primes)
for i, d := range days {
fmt.Println(i, d)
}
}[90 0 0 0 75] 5 [Pazartesi Salı Çarşamba] 3 [2 3 5 7 11] 5 [0 10 0 30 0] [3]string ve [5]int farklı tiplerdir 0 Pazartesi 1 Salı 2 Çarşamba
Burada dört farklı tanımlama biçimi var. var scores [5]int ile beş elemanlı bir dizi oluşur ve her eleman int tipinin sıfır değeri olan 0 ile başlar. [...]int{...} yazdığında uzunluğu literaldeki eleman sayısından derleyici çıkarır; sonuç yine sabit uzunluklu bir dizidir. [5]int{1: 10, 3: 30} ise indeksli literaldir: yalnızca belirttiğin indeksler doldurulur, gerisi sıfır kalır.
Dizi sınırlarının dışına çıkmak Go'da hiçbir zaman sessizce geçmez. İndeks sabitse (scores[7]) derleyici hatayı yakalar; indeks çalışma zamanında hesaplanıyorsa program index out of range paniğiyle durur. C'deki gibi komşu belleği okuyup yazmak mümkün değildir.
Atama ve fonksiyona geçirme kopyalar
Go'da diziler değer tipleridir. Bir diziyi başka bir değişkene atadığında veya bir fonksiyona argüman olarak verdiğinde, bütün elemanlar tek tek kopyalanır. Bu davranışa değer semantiği denir ve dilimlerle aradaki en temel farktır.
package main
import "fmt"
func resetFirst(a [3]int) {
a[0] = 0 // yalnızca kopyayı değiştirir
}
func resetFirstPtr(a *[3]int) {
a[0] = 0 // işaretçi üzerinden orijinali değiştirir
}
func main() {
original := [3]int{1, 2, 3}
copied := original // üç elemanın tamamı kopyalanır
copied[1] = 99
fmt.Println("original:", original)
fmt.Println("copied: ", copied)
resetFirst(original)
fmt.Println("resetFirst sonrası:", original)
resetFirstPtr(&original)
fmt.Println("resetFirstPtr sonrası:", original)
fmt.Println("eşit mi?", original == [3]int{0, 2, 3})
}original: [1 2 3] copied: [1 99 3] resetFirst sonrası: [1 2 3] resetFirstPtr sonrası: [0 2 3] eşit mi? true
copied := original satırından sonra bellekte birbirinden bağımsız iki blok vardır:
original copied
┌───┬───┬───┐ ┌───┬────┬───┐
│ 1 │ 2 │ 3 │ │ 1 │ 99 │ 3 │
└───┴───┴───┘ └───┴────┴───┘
ayrı bellek ayrı bellekresetFirst fonksiyonu da kendi kopyasını alır, bu yüzden original değişmez. Orijinali değiştirmek istiyorsan diziye bir işaretçi (*[3]int) geçirmen gerekir. Dizilerin değer olmasının güzel bir yan etkisi de karşılaştırılabilir olmalarıdır: Aynı tipteki iki dizi == ile eleman eleman karşılaştırılır. Bu sayede diziler bir haritada anahtar olarak da kullanılabilir.
Diziler ne zaman kullanılır?
Günlük Go kodunda doğrudan dizi yazmak nadirdir, çünkü uzunluğun sabit olması çoğu durumda kısıtlayıcıdır. Yine de dizinin tam olarak doğru araç olduğu yerler vardır:
- Boyutu doğası gereği sabit olan veriler: bir RGB rengi
[3]uint8, bir IPv4 adresi[4]byte, satranç tahtası[8][8]Piece. - Hash değerleri:
sha256.Sum256fonksiyonu[32]bytedöndürür. - Harita anahtarı olarak kullanılacak sabit boyutlu bileşik değerler, örneğin
map[[2]int]boolile ızgara koordinatları.
Büyük dizileri değer olarak geçirmenin maliyetli olduğunu da unutma: 1 MB'lık bir dizi her fonksiyon çağrısında baştan sona kopyalanır. Esnek uzunluk ve ucuz geçirme gerektiğinde dilimlere geçiyoruz.
Dilim nedir? Başlık yapısı
Dilim, bir dizinin ardışık bir bölümüne bakan penceredir. Kendisi eleman saklamaz; elemanlar her zaman bir alttaki dizide (backing array) durur. Dilim değişkeninin kendisi yalnızca üç alandan oluşan küçük bir başlıktır (slice header):
- ptr: Alttaki dizide dilimin ilk elemanının adresi.
- len (uzunluk): Dilimden erişilebilen eleman sayısı.
len(s)ile okunur. - cap (kapasite): İlk elemandan alttaki dizinin sonuna kadar olan eleman sayısı.
cap(s)ile okunur.
Go çalışma zamanının kaynak kodunda bu başlık gerçekten de üç alanlı bir struct olarak tanımlıdır:
// runtime/slice.go
type slice struct {
array unsafe.Pointer // alttaki dizinin ilgili elemanını gösterir
len int
cap int
}Bir diziden dilim aldığında ortaya çıkan tablo şöyledir:
arr := [6]int{10, 20, 30, 40, 50, 60}
s := arr[1:4]
s (dilim başlığı)
┌───────┬───────┬───────┐
│ ptr ● │ len 3 │ cap 5 │
└───┼───┴───────┴───────┘
│
▼
arr ┌────┬────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │ 60 │
└────┴────┴────┴────┴────┴────┘
0 1 2 3 4 5
└──── len ───┘
└───────── cap ──────────┘s yalnızca 20, 30 ve 40'ı görür (len 3), ama arkasında dizinin sonuna kadar iki eleman daha vardır (cap 5). Aşağıdaki program bu tabloyu doğrular:
package main
import (
"fmt"
"unsafe"
)
func main() {
arr := [6]int{10, 20, 30, 40, 50, 60}
s := arr[1:4]
fmt.Println("s:", s, "len:", len(s), "cap:", cap(s))
s[0] = 99 // dilim üzerinden yazmak alttaki diziyi değiştirir
fmt.Println("arr:", arr)
fmt.Println("s[:cap(s)]:", s[:cap(s)]) // kapasiteye kadar genişletilebilir
fmt.Println("başlık boyutu:", unsafe.Sizeof(s), "byte")
}s: [20 30 40] len: 3 cap: 5 arr: [10 99 30 40 50 60] s[:cap(s)]: [99 30 40 50 60] başlık boyutu: 24 byte
Üç önemli gözlem var. Birincisi, s[0] = 99 ataması arr[1]'i değiştirdi, çünkü ikisi aynı bellek hücresidir. İkincisi, s[:cap(s)] ile dilimi kapasitesine kadar yeniden genişletebildik; len bir sınır değil, yalnızca pencerenin o anki genişliğidir. Ancak s[3] gibi len'in dışındaki bir indekse doğrudan erişmek, kapasite içinde olsa bile paniğe yol açar. Üçüncüsü, dilim başlığı 64 bit sistemlerde 24 byte'tır (üç tane 8 byte'lık alan). Dilimde bir milyon eleman da olsa başlık 24 byte kalır; bu yüzden dilimleri kopyalamak ve fonksiyonlara geçirmek ucuzdur. (unsafe paketini burada sadece boyutu göstermek için kullandık; normal kodda ihtiyacın olmayacak.)
Aşağıdaki görselleştirmede bir dilim oluşturup append ve dilimleme işlemleri uygula. Başlıktaki ptr, len ve cap değerlerinin nasıl değiştiğini, alttaki dizinin ne zaman yerinde kaldığını ve ne zaman yenisiyle değiştirildiğini izle.
make, append ve kapasite büyümesi
Dilim oluşturmanın yolları
Dilim oluşturmanın birkaç yolu vardır ve her birinin başlığı farklı başlar:
package main
import "fmt"
func show(name string, s []int) {
fmt.Printf("%s: %v len=%d cap=%d nil=%t\n", name, s, len(s), cap(s), s == nil)
}
func main() {
var a []int // nil dilim: alttaki dizi yok
b := []int{} // boş ama nil olmayan dilim
c := []int{7, 8, 9} // literal: len ve cap eleman sayısı kadar
d := make([]int, 3) // len=3, cap=3, elemanlar sıfır değerli
e := make([]int, 2, 10) // len=2, cap=10
arr := [5]int{1, 2, 3, 4, 5}
f := arr[1:3] // mevcut bir diziden
show("a", a)
show("b", b)
show("c", c)
show("d", d)
show("e", e)
show("f", f)
}a: [] len=0 cap=0 nil=true b: [] len=0 cap=0 nil=false c: [7 8 9] len=3 cap=3 nil=false d: [0 0 0] len=3 cap=3 nil=false e: [0 0] len=2 cap=10 nil=false f: [2 3] len=2 cap=4 nil=false
make([]T, len, cap) alttaki diziyi ayırır ve ilk len elemanı sıfır değerle doldurur. Kapasiteyi vermezsen len ile aynı olur. Dilim literali ([]int{7, 8, 9}) ise arka planda tam o boyutta bir dizi oluşturup ona bakan bir dilim döndürür. Dikkat et: Dilim literalinde köşeli parantezin içi boştur; [3]int{...} yazarsan dizi, []int{...} yazarsan dilim elde edersin.
append nasıl çalışır?
append(s, v1, v2, ...) elemanları dilimin sonuna ekler ve yeni bir dilim başlığı döndürür. Bu yüzden sonucu her zaman bir değişkene atarsın: s = append(s, x). Sonucu hiçbir yere atamazsan kod derlenmez. append perde arkasında iki durumdan birini yaşar:
Durum 1: len < cap (yer var) s = append(s, 4)
önce: len=3 cap=5 sonra: len=4 cap=5
┌───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ │ │ ───► │ 1 │ 2 │ 3 │ 4 │ │
└───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┘
Aynı dizi kullanılır, yalnızca len artar. Kopyalama yok.
Durum 2: len == cap (yer yok) s = append(s, 4)
önce: len=3 cap=3 sonra: len=4 cap=6
┌───┬───┬───┐ ┌───┬───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ kopyala ───► │ 1 │ 2 │ 3 │ 4 │ │ │
└───┴───┴───┘ └───┴───┴───┴───┴───┴───┘
eski dizi (kimse göstermiyorsa yeni, daha büyük dizi;
çöp toplayıcı temizler) s.ptr artık burayı gösterirİkinci durumda çalışma zamanı daha büyük yeni bir dizi ayırır, mevcut elemanları oraya kopyalar, yeni elemanı ekler ve yeni diziyi gösteren bir başlık döndürür. Kopyalama O(n) sürer ama kapasite her seferinde katlanarak büyüdüğü için bu pahalı adım nadiren yaşanır. Sonuçta n kez append yapmanın toplam maliyeti O(n), yani ekleme başına amortize O(1) olur. Bu analizin ayrıntılarını ve sıfırdan bir dinamik dizi yazmayı Diziler ve Dinamik Diziler dersinde bulabilirsin.
Kapasite büyümesini gözlemlemek
Teoriyi bir kenara bırakıp gerçekte ne olduğuna bakalım. Aşağıdaki program boş bir dilime tek tek eleman ekliyor ve kapasitenin her değiştiği anı işaretliyor:
package main
import "fmt"
func main() {
var s []int
for i := range 10 {
oldCap := cap(s)
s = append(s, i)
note := ""
if cap(s) != oldCap {
note = " <- yeni dizi ayrıldı"
}
fmt.Printf("len=%-2d cap=%-2d %v%s\n", len(s), cap(s), s, note)
}
}len=1 cap=1 [0] <- yeni dizi ayrıldı len=2 cap=2 [0 1] <- yeni dizi ayrıldı len=3 cap=4 [0 1 2] <- yeni dizi ayrıldı len=4 cap=4 [0 1 2 3] len=5 cap=8 [0 1 2 3 4] <- yeni dizi ayrıldı len=6 cap=8 [0 1 2 3 4 5] len=7 cap=8 [0 1 2 3 4 5 6] len=8 cap=8 [0 1 2 3 4 5 6 7] len=9 cap=16 [0 1 2 3 4 5 6 7 8] <- yeni dizi ayrıldı len=10 cap=16 [0 1 2 3 4 5 6 7 8 9]
Kapasite 1, 2, 4, 8, 16 diye ikiye katlanıyor. Dolayısıyla on eleman eklerken yalnızca beş kez yeni dizi ayrıldı; diğer altı append mevcut dizideki boş yere yazdı. Küçük dilimler için kural gerçekten "kapasiteyi ikiye katla"dır. Peki dilim büyüdükçe ne olur? Her seferinde ikiye katlamak, milyonlarca elemanlık bir dilimde gereğinden çok boş bellek ayırmak demektir. Bu yüzden Go belli bir eşikten sonra büyüme oranını yumuşatır:
package main
import "fmt"
func main() {
var s []int
oldCap := cap(s)
for i := range 6000 {
s = append(s, i)
if c := cap(s); c != oldCap {
if oldCap >= 128 {
fmt.Printf("cap %4d -> %4d (x%.2f)\n", oldCap, c, float64(c)/float64(oldCap))
}
oldCap = c
}
}
}cap 128 -> 256 (x2.00) cap 256 -> 512 (x2.00) cap 512 -> 848 (x1.66) cap 848 -> 1280 (x1.51) cap 1280 -> 1792 (x1.40) cap 1792 -> 2560 (x1.43) cap 2560 -> 3408 (x1.33) cap 3408 -> 5120 (x1.50) cap 5120 -> 7168 (x1.40)
Kapasite 256'ya ulaşana kadar oran 2'dir. Sonrasında çalışma zamanı (runtime/slice.go içindeki nextslicecap fonksiyonu) yeni kapasiteyi yaklaşık olarak newcap += (newcap + 3*256) / 4 formülüyle hesaplar. Bu formül büyük dilimlerde oranı yavaş yavaş 1.25'e yaklaştırır. Çıktıdaki oranların düzgün bir seri oluşturmamasının nedeni ise ikinci bir adımdır: Hesaplanan boyut, bellek ayırıcının kullandığı hazır blok boyutlarına (size class) yuvarlanır. Örneğin formül 512'den sonra 832 verir, ama 832 × 8 byte en yakın blok boyutuna yuvarlanınca kapasite 848 olur.
Kapasiteyi önceden ayırmak
Dilimin sonunda kaç eleman olacağını biliyorsan, kapasiteyi baştan ayırmak tüm ara kopyalamaları ortadan kaldırır:
// Kaç eleman geleceği belli: tek bir ayırma yeterli.
names := make([]string, 0, len(users))
for _, u := range users {
names = append(names, u.Name)
}Bu, özellikle döngü içinde çok sayıda eleman eklenen sıcak kod yollarında gözle görülür bir fark yaratır. Kesin sayıyı bilmiyorsan makul bir tahmin de işe yarar; tahmin aşılırsa append yine sorunsuz büyütür.
Dilimleme ve ortak alttaki dizi
Dilimleme ifadesi: s[low:high]
s[low:high] ifadesi, low indeksinden başlayıp high indeksine kadar (high dahil değil) giden yeni bir dilim başlığı üretir. Yeni dilimin uzunluğu high - low, kapasitesi ise cap(s) - low olur. low yazılmazsa 0, high yazılmazsa len(s) kabul edilir: s[:3], s[2:] ve s[:] geçerli ifadelerdir.
En önemli nokta şudur: Dilimleme hiçbir elemanı kopyalamaz. Yalnızca yeni bir başlık oluşturur, bu yüzden O(1) sürer. Ortaya çıkan dilim, orijinaliyle aynı alttaki diziyi paylaşır.
package main
import "fmt"
func main() {
months := []string{"Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran"}
q2 := months[3:6]
firstTwo := months[:2]
fmt.Println(q2, len(q2), cap(q2))
fmt.Println(firstTwo, len(firstTwo), cap(firstTwo))
q2[0] = "NİSAN" // months[3] ile aynı hücre
fmt.Println(months)
}[Nisan Mayıs Haziran] 3 3 [Ocak Şubat] 2 6 [Ocak Şubat Mart NİSAN Mayıs Haziran]
firstTwo yalnızca iki ay gösterse de kapasitesi 6'dır: Arkasında dizinin sonuna kadar dört hücre daha vardır. İşte bu görünmez kapasite, dilimlerle ilgili en sinsi hataların kaynağıdır.
Tuzak: append ile komşunun verisini ezmek
Bir alt dilime append yaptığında, kapasite yetiyorsa yeni eleman alttaki dizide sıradaki hücreye yazılır. O hücre başka bir dilimin, hatta orijinal dilimin görünen bir elemanı olabilir:
package main
import "fmt"
func main() {
nums := []int{1, 2, 3, 4, 5}
head := nums[:2] // len=2, cap=5
fmt.Println("head:", head, "len:", len(head), "cap:", cap(head))
head = append(head, 100) // kapasite yeterli: yeni dizi ayrılmaz
fmt.Println("head:", head)
fmt.Println("nums:", nums) // nums[2] ezildi!
}head: [1 2] len: 2 cap: 5 head: [1 2 100] nums: [1 2 100 4 5]
Bellekte olan biten şudur:
head := nums[:2]
nums ┌───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 4 │ 5 │
└───┴───┴───┴───┴───┘
head └─len─┘
└─────── cap ───────┘
head = append(head, 100) // head'in len'i dolu ama cap'te yer var
nums ┌───┬───┬─────┬───┬───┐
│ 1 │ 2 │ 100 │ 4 │ 5 │ <- 3 kayboldu
└───┴───┴─────┴───┴───┘
head └──── len ────┘Bu hatayı daha da kafa karıştırıcı yapan şey, append'in yeni dizi ayırıp ayırmadığının kapasiteye bağlı olmasıdır. Aynı kod bir durumda veriyi paylaşır, başka bir durumda paylaşmaz:
package main
import "fmt"
func main() {
base := make([]int, 3, 4) // bir boş yer var
x := append(base, 1)
y := append(base, 2) // x'in yazdığı hücreye yazar
fmt.Println("x:", x, "y:", y)
full := []int{0, 0, 0} // len=3, cap=3: boş yer yok
p := append(full, 1) // yeni dizi
q := append(full, 2) // başka bir yeni dizi
fmt.Println("p:", p, "q:", q)
}x: [0 0 0 2] y: [0 0 0 2] p: [0 0 0 1] q: [0 0 0 2]
İlk durumda x ve y aynı alttaki diziyi paylaşır ve ikinci append, birincinin yazdığı 1'in üzerine 2 yazar. İkinci durumda kapasite dolu olduğu için her append kendi dizisini ayırır ve sonuçlar bağımsızdır. Kodunun davranışı gizli bir kapasite değerine bağlıysa, bu bir hata beklemektedir.
Çözüm: tam dilim ifadesi s[low:high:max]
Go bu sorun için üç indeksli bir dilimleme biçimi sunar: tam dilim ifadesi (full slice expression). s[low:high:max] ifadesinde uzunluk yine high - low'dur, ama kapasite max - low ile sınırlanır. max değerini high'a eşit verirsen kapasite uzunluğa eşit olur ve bir sonraki append yeni bir dizi ayırmak zorunda kalır:
package main
import "fmt"
func main() {
nums := []int{1, 2, 3, 4, 5}
head := nums[0:2:2] // len=2, cap=2
fmt.Println("head:", head, "len:", len(head), "cap:", cap(head))
head = append(head, 100) // kapasite dolu: yeni dizi ayrılır
head[0] = -1 // artık nums'ı etkilemez
fmt.Println("head:", head)
fmt.Println("nums:", nums)
}head: [1 2] len: 2 cap: 2 head: [-1 2 100] nums: [1 2 3 4 5]
head := nums[0:2:2]
nums ┌───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 4 │ 5 │ <- dokunulmadı
└───┴───┴───┴───┴───┘
head └─len─┘
└─cap─┘
head = append(head, 100) // cap doluydu, yeni dizi ayrıldı
head ┌────┬───┬─────┬───┐
│ -1 │ 2 │ 100 │ │ ayrı bellek
└────┴───┴─────┴───┘İndeksler için kural 0 <= low <= high <= max <= cap(s) şeklindedir; bu sınırların dışına çıkan bir ifade panik oluşturur. Bir fonksiyondan iç verinin bir parçasını döndürürken veya bir dilimi parçalara bölerken tam dilim ifadesi kullanmak, çağıranın yapacağı bir append'in senin verini ezmesini önler.
copy ile gerçek kopya
Paylaşım istemediğinde elemanları gerçekten kopyalaman gerekir. Yerleşik copy(dst, src) fonksiyonu src'deki elemanları dst'ye kopyalar ve kopyalanan eleman sayısını döndürür. Kopyalanan sayı her zaman min(len(dst), len(src)) olur; copy hedefi asla büyütmez.
package main
import "fmt"
func main() {
src := []int{1, 2, 3, 4, 5}
// 1) Bağımsız kopya: hedefin uzunluğu kaynak kadar olmalı
dst := make([]int, len(src))
n := copy(dst, src)
dst[0] = 100
fmt.Println("kopyalanan:", n, "src:", src, "dst:", dst)
// 2) copy, iki uzunluktan küçük olanı kadar kopyalar
small := make([]int, 2)
fmt.Println("kopyalanan:", copy(small, src), "small:", small)
// 3) Klasik hata: len=0 olan hedefe hiçbir şey kopyalanmaz
empty := make([]int, 0, 5)
fmt.Println("kopyalanan:", copy(empty, src), "empty:", empty)
// 4) Örtüşen bölgeler güvenlidir: indeks 1'deki elemanı sil
i := 1
copy(src[i:], src[i+1:])
src = src[:len(src)-1]
fmt.Println("silme sonrası:", src)
// 5) string'den []byte'a kopyalama
buf := make([]byte, 3)
copy(buf, "Gopher")
fmt.Println(string(buf))
}kopyalanan: 5 src: [1 2 3 4 5] dst: [100 2 3 4 5] kopyalanan: 2 small: [1 2] kopyalanan: 0 empty: [] silme sonrası: [1 3 4 5] Gop
Programın her bölümü ayrı bir dersi gösteriyor. İlk bölümde dst kendi dizisine sahip olduğu için dst[0] = 100 ataması src'yi etkilemedi. Üçüncü bölüm, en sık yapılan copy hatasıdır: make([]int, 0, 5) ile oluşturulan dilimin kapasitesi 5 olsa da uzunluğu 0'dır ve copy yalnızca uzunluğa bakar. Dördüncü bölümde kaynak ve hedef aynı dizinin örtüşen parçalarıdır; copy bu durumu doğru yönetir. Böylece elemanları bir sola kaydırıp son elemanı kırparak ortadan silme yapmış olduk. Beşinci bölüm ise copy'nin string'den byte dilimine kopyalayabildiğini gösteriyor.
Bir dilimin tam kopyasını almanın kısa yolları da vardır: slices.Clone(s) veya append([]int(nil), s...). İkisi de yeni bir dizi ayırıp elemanları kopyalar. slices.Clone nil bir dilimi nil olarak korur.
nil dilim ve boş dilim
Sıfır değerli bir dilim, yani var s []int ile tanımlanan dilim nil dilimdir: işaretçisi nil, uzunluğu ve kapasitesi 0'dır. []int{} veya make([]int, 0) ile oluşturulan dilim ise boş ama nil olmayan bir dilimdir. İkisi çoğu durumda aynı davranır ama tamamen aynı değildir:
package main
import (
"encoding/json"
"fmt"
)
func main() {
var nilSlice []string
emptySlice := []string{}
fmt.Println(nilSlice == nil, len(nilSlice), cap(nilSlice))
fmt.Println(emptySlice == nil, len(emptySlice), cap(emptySlice))
fmt.Println(nilSlice, emptySlice) // fmt ikisini de [] yazar
for range nilSlice {
fmt.Println("bu satır hiç çalışmaz")
}
grown := append(nilSlice, "artık dolu") // nil dilime append güvenlidir
fmt.Println(grown, len(grown))
a, _ := json.Marshal(nilSlice)
b, _ := json.Marshal(emptySlice)
fmt.Println(string(a), string(b))
}true 0 0 false 0 0 [] [] [artık dolu] 1 null []
Pratikteki kural basit: Bir dilimin boş olup olmadığını s == nil ile değil, len(s) == 0 ile kontrol et; bu kontrol iki durumu da doğru yakalar. Yeni bir dilime ihtiyaç duyduğunda var s []int ile başlamak idiomatik Go'dur. Fark yalnızca dışarıya veri verirken önemlidir: Bir JSON API'si boş listeyi null yerine [] olarak göndermek zorundaysa boş dilim kullanmalısın. JSON ayrıntılarını Dosyalar, I/O ve JSON dersinde göreceksin.
Bir de şunu bil: Dilimler yalnızca nil ile karşılaştırılabilir. a == b gibi iki dilimi karşılaştıran bir ifade derleme hatası verir; eleman eleman karşılaştırma için slices.Equal kullanılır.
Dilimleri fonksiyonlara geçirmek
Fonksiyonlar dersinden hatırlarsan Go'da her şey değer ile geçirilir. Dilimler de bu kurala uyar: Fonksiyona başlık kopyalanır, alttaki dizi kopyalanmaz. Sonuç olarak iki başlık aynı diziyi gösterir:
main içindeki nums doubleAll içindeki s
┌───────┬───────┬────────┐ ┌───────┬───────┬────────┐
│ ptr ● │ len 3 │ cap 10 │ │ ptr ● │ len 3 │ cap 10 │
└───┼───┴───────┴────────┘ └───┼───┴───────┴────────┘
│ başlık kopyalandı │
└───────────────┬──────────────────┘
▼
┌───┬───┬───┬───┬─────────┐
│ 1 │ 2 │ 3 │ │ ... │ ortak alttaki dizi
└───┴───┴───┴───┴─────────┘Bu tablodan iki sonuç çıkar: Fonksiyon elemanları değiştirirse çağıran bunu görür. Fonksiyon başlığı değiştirirse (örneğin append ile uzunluğu artırırsa) çağıranın başlığı etkilenmez.
package main
import "fmt"
func doubleAll(s []int) {
for i := range s {
s[i] *= 2 // ortak dizideki elemanları değiştirir
}
}
func appendLost(s []int) {
s = append(s, 4) // yalnızca yerel başlığın len'i artar
fmt.Println("fonksiyon içinde:", s)
}
func appendReturned(s []int) []int {
return append(s, 4)
}
func main() {
nums := make([]int, 3, 10)
nums[0], nums[1], nums[2] = 1, 2, 3
doubleAll(nums)
fmt.Println("doubleAll sonrası:", nums)
appendLost(nums)
fmt.Println("appendLost sonrası:", nums, "len:", len(nums))
fmt.Println("dizide gizli duran:", nums[:4])
nums = appendReturned(nums)
fmt.Println("appendReturned sonrası:", nums, "len:", len(nums))
}doubleAll sonrası: [2 4 6] fonksiyon içinde: [2 4 6 4] appendLost sonrası: [2 4 6] len: 3 dizide gizli duran: [2 4 6 4] appendReturned sonrası: [2 4 6 4] len: 4
doubleAll elemanları ikiye katladı ve main bunu gördü. appendLost ise 4'ü eklediğini sanıyor ama main'deki nums'ın uzunluğu hâlâ 3. Asıl ilginç satır nums[:4]: Kapasite yettiği için append, 4'ü gerçekten ortak diziye yazmış. main bu elemanı yalnızca kendi len'i 3 olduğu için görmüyor. Kapasite dolu olsaydı append yeni bir dizi ayıracak ve fonksiyon içindeki sonraki değişiklikler çağırana hiç ulaşmayacaktı.
Buradan çıkan idiomatik kural: Bir dilimin uzunluğunu değiştiren fonksiyonlar, tıpkı append gibi yeni dilimi döndürmelidir (nums = appendReturned(nums)). Yalnızca elemanları okuyan veya yerinde değiştiren fonksiyonlar ise dilimi doğrudan alabilir. Bir dilime işaretçi (*[]int) geçirmek de mümkündür ama nadiren gerekir. İşaretçilerin ayrıntılarını İşaretçiler dersinde göreceksin.
Çok boyutlu dilimler
Go'da ayrı bir "matris" tipi yoktur; iki boyutlu veri, dilimlerden oluşan bir dilim ([][]int) ile temsil edilir. Dış dilimin her elemanı kendi başlığı ve kendi alttaki dizisi olan bağımsız bir dilimdir. Bu yüzden her satırı ayrıca oluşturman gerekir ve satırların uzunlukları birbirinden farklı olabilir:
package main
import "fmt"
func main() {
// 3x4 ızgara: önce satırları tutan dilim, sonra her satır
rows, cols := 3, 4
grid := make([][]int, rows)
for i := range grid {
grid[i] = make([]int, cols)
}
grid[1][2] = 7
for _, row := range grid {
fmt.Println(row)
}
// Satır uzunlukları farklı olabilir: Pascal üçgeni
pascal := make([][]int, 5)
for i := range pascal {
pascal[i] = make([]int, i+1)
pascal[i][0], pascal[i][i] = 1, 1
for j := 1; j < i; j++ {
pascal[i][j] = pascal[i-1][j-1] + pascal[i-1][j]
}
}
for _, row := range pascal {
fmt.Println(row)
}
}[0 0 0 0] [0 0 7 0] [0 0 0 0] [1] [1 1] [1 2 1] [1 3 3 1] [1 4 6 4 1]
make([][]int, rows) yalnızca rows tane nil satır üretir. Satırları oluşturmadan grid[1][2] = 7 yazmaya çalışsaydın index out of range paniği alırdın. Bellekteki düzen şöyledir:
grid (dış dilim) her satırın kendi dizisi
┌──────────────────┐
│ [0] ptr,len,cap ─┼──► ┌───┬───┬───┬───┐
├──────────────────┤ │ 0 │ 0 │ 0 │ 0 │
│ [1] ptr,len,cap ─┼──► ┌───┬───┬───┬───┐
├──────────────────┤ │ 0 │ 0 │ 7 │ 0 │
│ [2] ptr,len,cap ─┼──► ┌───┬───┬───┬───┐
└──────────────────┘ │ 0 │ 0 │ 0 │ 0 │Tek ayırmalı matris
Satır sayısı kadar ayrı ayırma yapmak çoğu zaman sorun değildir. Ama büyük ve dikdörtgen bir matriste tüm hücreleri tek bir bitişik dizide tutmak hem ayırma sayısını bire indirir hem de önbellek dostu erişim sağlar. Satırları bu tek dizinin parçaları olarak dilimleriz; her satırın kapasitesini tam dilim ifadesiyle sınırlamak, bir satıra yapılan append'in komşu satırı ezmesini önler:
package main
import "fmt"
func newMatrix(rows, cols int) [][]float64 {
data := make([]float64, rows*cols) // tüm hücreler için tek ayırma
m := make([][]float64, rows)
for i := range m {
start := i * cols
m[i] = data[start : start+cols : start+cols]
}
return m
}
func main() {
m := newMatrix(2, 3)
m[0][2] = 1.5
m[1][0] = 2.5
fmt.Println(m)
fmt.Println("satır len:", len(m[0]), "cap:", cap(m[0]))
}[[0 0 1.5] [2.5 0 0]] satır len: 3 cap: 3
Boyutu derleme zamanında belli olan küçük ızgaralar için dizilerin dizisi ([3][3]int) de iyi bir seçenektir: Tek parça bellekte durur, sıfır değeri hazırdır ve == ile karşılaştırılabilir. Ama değer semantiği gereği atamada tamamı kopyalanır.
slices paketi
Go 1.21 ile standart kütüphaneye gelen slices paketi, dilimlerle yapılan yaygın işleri jenerik fonksiyonlar olarak sunar. Eskiden elle döngü yazdığın arama, sıralama ve karşılaştırma işlemleri artık tek satırdır. Önce arama ve sıralamaya bakalım:
package main
import (
"fmt"
"slices"
)
func main() {
cities := []string{"İzmir", "Ankara", "Bursa", "Adana", "Trabzon"}
fmt.Println(slices.Contains(cities, "Bursa"))
fmt.Println(slices.Index(cities, "Adana"), slices.Index(cities, "Van"))
slices.Sort(cities) // yerinde sıralar
fmt.Println(cities)
i, found := slices.BinarySearch(cities, "Bursa")
fmt.Println(i, found)
i, found = slices.BinarySearch(cities, "Denizli")
fmt.Println(i, found) // yoksa, eklenseydi gireceği konum
slices.Reverse(cities) // yerinde ters çevirir
fmt.Println(cities)
}true 3 -1 [Adana Ankara Bursa Trabzon İzmir] 2 true 3 false [İzmir Trabzon Bursa Ankara Adana]
Contains ve Index dilimi baştan sona tarar, yani O(n) sürer; Index bulamazsa -1 döndürür. Sort dilimi yerinde, O(n log n) sürede sıralar (Go bunun için pdqsort algoritmasını kullanır; ayrıntılar Verimli Sıralama Algoritmaları dersinde). BinarySearch yalnızca sıralı bir dilimde doğru çalışır ve O(log n) sürer; iki değer döndürür: elemanın konumu (yoksa eklenmesi gereken konum) ve bulunup bulunmadığı. İkili aramanın nasıl çalıştığını Arama Algoritmaları dersinde adım adım göreceksin.
Çıktıdaki sıralamaya dikkat et: "İzmir" en sona düştü. slices.Sort string'leri byte değerlerine göre karşılaştırır ve İ harfi UTF-8'de ASCII harflerinden daha büyük byte'larla kodlanır. Türkçe alfabetik sıralama için dile duyarlı bir karşılaştırma gerekir; bu konuya String, Byte ve Rune dersinde değineceğiz.
Şimdi dilimi değiştiren ve karşılaştıran fonksiyonlara geçelim:
package main
import (
"fmt"
"slices"
)
func main() {
nums := []int{10, 20, 30, 40, 50}
backup := slices.Clone(nums) // bağımsız kopya
nums = slices.Insert(nums, 2, 25, 27) // indeks 2'ye iki eleman ekle
fmt.Println("Insert:", nums)
nums = slices.Delete(nums, 0, 2) // [0, 2) aralığını sil
fmt.Println("Delete:", nums)
fmt.Println("backup:", backup)
fmt.Println("eşit mi?", slices.Equal(nums, backup))
fmt.Println("eşit mi?", slices.Equal(backup, []int{10, 20, 30, 40, 50}))
data := []int{1, 2, 3, 4, 5}
short := slices.Delete(data, 1, 3)
fmt.Println(short, data) // Delete, geride kalan hücreleri sıfırlar
}Insert: [10 20 25 27 30 40 50] Delete: [25 27 30 40 50] backup: [10 20 30 40 50] eşit mi? false eşit mi? true [1 4 5] [1 4 5 0 0]
Insert ve Delete tıpkı append gibi yeni bir dilim döndürür, bu yüzden sonucu atamayı unutma. İkisi de araya ekleme veya silme için sonraki elemanları kaydırdığından O(n) sürer. Son iki satır önemli bir ayrıntıyı gösteriyor: Delete aynı alttaki dizi üzerinde çalışır ve kısalan dilimin arkasında kalan hücreleri sıfır değerle doldurur (Go 1.22'den beri). Böylece silinen elemanlar işaretçi içeriyorsa çöp toplayıcı onları serbest bırakabilir. Ama eski data değişkenini kullanmaya devam edersen bu sıfırları görürsün; silme sonrası yalnızca döndürülen dilimi kullan.
Pakette bunlardan fazlası var: Özel sıralama için slices.SortFunc(s, func(a, b T) int { ... }) ve cmp.Compare, en büyük ve en küçük eleman için slices.Max ve slices.Min, ardışık tekrarları silmek için slices.Compact. Jenerik fonksiyonların nasıl yazıldığını Jenerikler dersinde, slices.Sorted ve slices.Collect gibi iteratör alan fonksiyonları İteratörler dersinde göreceksin.
İşlemlerin maliyeti
Sık yapılan hatalar
appendsonucunu atamamak.append(s, x)tek başına derlenmez;s = append(s, x)yazmalısın. Başka bir değişkene atarsan (t := append(s, x))svet'nin aynı diziyi paylaşıp paylaşmadığı kapasiteye bağlı olur.- Alt dilime
appendyapıp orijinal veriyi ezmek. Bir alt dilimi başka koda verirken ya da ona ekleme yapacaksan tam dilim ifadesi (s[low:high:high]) veyaslices.Clonekullan. make([]T, n)sonrasıappendyapmak. Baştantane sıfır değer oluşur. Boş başlayıp dolduracaksanmake([]T, 0, n)yaz.copyhedefinin uzunluğunu 0 bırakmak.copykapasiteye değil uzunluğa bakar; hedefimake([]T, len(src))ile oluştur.rangedeğişkenini değiştirip dilimin değiştiğini sanmak.for _, v := range s { v *= 2 }yalnızcavkopyasını değiştirir; elemanı değiştirmek içins[i] *= 2yaz.- Çok boyutlu dilimde tüm satırlara aynı dilimi atamak. Döngü dışında tek bir
row := make([]int, cols)oluşturup hergrid[i] = rowyaparsan bütün satırlar aynı diziyi gösterir; bir hücreyi değiştirmek tüm sütunu değiştirir. Her satır için döngü içinde ayrımakeçağır. - Boşluğu
s == nilile kontrol etmek. Boş ama nil olmayan dilimler bu kontrolden kaçar;len(s) == 0kullan. - Büyük bir dizinin küçük parçasını uzun süre saklamak. Parça yaşadıkça dizinin tamamı bellekte kalır; parçayı
slices.Cloneile kopyala.
Alıştırmalar
reverse(s []int) fonksiyonunu slices.Reverse kullanmadan yaz. Fonksiyon yeni bir dilim oluşturmamalı, elemanları yerinde yer değiştirmelidir. Boş ve tek elemanlı dilimlerde de doğru çalıştığından emin ol.
İpucu
İki indeksle başla: biri en başta (i), biri en sonda (j). s[i] ile s[j]'yi takas et, sonra i'yi artır, j'yi azalt. i < j olduğu sürece devam et. Go'da takas tek satırdır: s[i], s[j] = s[j], s[i].
Çözümü göster
package main
import "fmt"
func reverse(s []int) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}
func main() {
nums := []int{1, 2, 3, 4, 5}
reverse(nums)
fmt.Println(nums)
even := []int{10, 20, 30, 40}
reverse(even)
fmt.Println(even)
var empty []int
reverse(empty)
fmt.Println(empty, len(empty))
}[5 4 3 2 1] [40 30 20 10] [] 0
Fonksiyon dilimi döndürmüyor, çünkü uzunluğu değiştirmiyor; yalnızca ortak dizideki elemanların yerini değiştiriyor ve çağıran bunu doğrudan görüyor. Boş dilimde j başta -1 olduğu için döngü hiç çalışmaz. Döngü n/2 kez döner: zaman karmaşıklığı O(n), ek alan O(1). Jenerikler dersinden sonra aynı fonksiyonu her tip için func reverse[T any](s []T) biçiminde yazabileceksin.
Artan sırada sıralanmış bir []int alan ve tekrar eden elemanları silen dedupSorted(s []int) []int fonksiyonunu yaz. Yeni bir dilim ayırma; aynı alttaki diziyi kullan ve sonuç dilimini döndür. Örneğin [1 1 2 3 3 3 4 5 5] için sonuç [1 2 3 4 5] olmalı.
İpucu
İki indeks kullan: r her elemanı okur, w bir sonraki benzersiz elemanın yazılacağı konumu tutar. Dilim sıralı olduğu için bir eleman, en son yazılan benzersiz elemandan (s[w-1]) farklıysa yenidir. Sonunda s[:w] döndür.
Çözümü göster
package main
import (
"fmt"
"slices"
)
func dedupSorted(s []int) []int {
if len(s) == 0 {
return s
}
w := 1 // s[:w] her zaman benzersiz elemanları tutar
for r := 1; r < len(s); r++ {
if s[r] != s[w-1] {
s[w] = s[r]
w++
}
}
return s[:w]
}
func main() {
fmt.Println(dedupSorted([]int{1, 1, 2, 3, 3, 3, 4, 5, 5}))
fmt.Println(dedupSorted([]int{7, 7, 7}))
fmt.Println(dedupSorted([]int{}))
// Standart kütüphanedeki karşılığı:
fmt.Println(slices.Compact([]int{1, 1, 2, 3, 3, 3, 4, 5, 5}))
}[1 2 3 4 5] [7] [] [1 2 3 4 5]
s[:w] bölgesi her adımda "şimdiye kadar görülen benzersiz elemanlar" olarak kalır. Okuma indeksi yazma indeksinin her zaman önünde veya onunla aynı yerde olduğu için, henüz okunmamış bir elemanın üzerine yazma riski yoktur. Her eleman bir kez okunur: zaman O(n), ek alan O(1). Fonksiyon uzunluğu değiştirdiği için yeni dilimi döndürür. slices.Compact aynı işi yapar ve ek olarak geride kalan hücreleri sıfırlar.
chunk(s []int, size int) [][]int fonksiyonunu yaz: Dilimi en fazla size elemanlık ardışık parçalara bölsün, son parça daha kısa olabilir. Parçalar elemanları kopyalamamalı, orijinal diziyi paylaşmalı. Ama bir parçaya append yapıldığında bir sonraki parçanın verisi ezilmemeli. [1 2 3 4 5 6 7] ve size=3 için [[1 2 3] [4 5 6] [7]] bekleniyor.
İpucu
Her parçayı s[start:end] ile alırsan kapasitesi dizinin sonuna kadar uzanır. Kapasiteyi parçanın sonunda kesmek için tam dilim ifadesini kullan. end değerini hesaplarken min yerleşik fonksiyonu işine yarar.
Çözümü göster
package main
import "fmt"
func chunk(s []int, size int) [][]int {
var chunks [][]int
for start := 0; start < len(s); start += size {
end := min(start+size, len(s))
chunks = append(chunks, s[start:end:end])
}
return chunks
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7}
parts := chunk(nums, 3)
fmt.Println(parts)
for _, p := range parts {
fmt.Printf("len=%d cap=%d ", len(p), cap(p))
}
fmt.Println()
parts[0] = append(parts[0], 100) // kapasite dolu: yeni dizi
fmt.Println("parts:", parts)
fmt.Println("nums: ", nums)
}[[1 2 3] [4 5 6] [7]] len=3 cap=3 len=3 cap=3 len=1 cap=1 parts: [[1 2 3 100] [4 5 6] [7]] nums: [1 2 3 4 5 6 7]
s[start:end:end] sayesinde her parçanın kapasitesi uzunluğuna eşittir. parts[0]'a yapılan append yeni bir dizi ayırdı ve nums ile ikinci parça olduğu gibi kaldı. s[start:end] yazsaydın ilk parçanın kapasitesi 7 olurdu ve append, nums[3]'ü (yani ikinci parçanın ilk elemanını) 100 ile ezerdi. Fonksiyon her elemanı bir kez dilimler ama kopyalamaz: zaman O(n/size), parça başlıkları için alan O(n/size).
rotateClockwise(m [][]int) [][]int fonksiyonunu yaz. rows × cols boyutlu dikdörtgen bir matrisi saat yönünde 90 derece döndürüp cols × rows boyutlu yeni bir matris döndürsün; girdi değişmesin. Örneğin
1 2 3 4 1
4 5 6 → 5 2
6 3İpucu
Önce çıktının boyutlarını belirle ve her satırı make ile oluştur. Sonra tek bir hücrenin nereye gittiğini düşün: Girdideki ilk satır, çıktıda en sağ sütun olur; girdideki son satır en sol sütun olur. Yani m[r][c] değeri out[c][rows-1-r] konumuna gider.
Çözümü göster
package main
import "fmt"
func rotateClockwise(m [][]int) [][]int {
rows := len(m)
if rows == 0 {
return nil
}
cols := len(m[0])
out := make([][]int, cols)
for i := range out {
out[i] = make([]int, rows)
}
for r := range rows {
for c := range cols {
out[c][rows-1-r] = m[r][c]
}
}
return out
}
func printMatrix(m [][]int) {
for _, row := range m {
fmt.Println(row)
}
fmt.Println("---")
}
func main() {
m := [][]int{
{1, 2, 3},
{4, 5, 6},
}
printMatrix(rotateClockwise(m))
printMatrix(m) // girdi değişmedi
square := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
printMatrix(rotateClockwise(square))
}[4 1] [5 2] [6 3] --- [1 2 3] [4 5 6] --- [7 4 1] [8 5 2] [9 6 3] ---
Her m[r][c] hücresi tam bir kez okunur ve out[c][rows-1-r] konumuna yazılır. Formülü doğrulamak için köşelere bak: Sol üst m[0][0], çıktıda ilk satırın sonuna (out[0][rows-1]) gider; sol alt m[rows-1][0] ise çıktının sol üst köşesine (out[0][0]) gelir. Çıktı için ayrı satırlar ayırdığımızdan girdi korunur. Zaman karmaşıklığı O(rows × cols), yeni matris için alan da O(rows × cols). Kare matrislerde aynı işi ek bellek kullanmadan yapmak da mümkündür: önce transpozunu alıp sonra her satırı ters çevirmek yeterlidir.
Kısa sınav
a := [3]int{1, 2, 3}; b := a; b[0] = 9; fmt.Println(a[0]) ne yazdırır?
s := make([]int, 2, 10); t := s[1:3] sonrasında len(t) ve cap(t) nedir?
a := []int{1, 2, 3, 4}; b := a[:2]; b = append(b, 9); fmt.Println(a) ne yazdırır?
dst := make([]int, 0, 5); n := copy(dst, []int{1, 2, 3}) sonrasında n kaçtır?
var s []int ile tanımlanan nil dilim için hangisi YANLIŞTIR?
func add(s []int) { s = append(s, 4) } fonksiyonu nums := make([]int, 3, 10) ile çağrıldıktan sonra len(nums) kaçtır?
Özet
- Dizinin uzunluğu tipinin parçasıdır (
[3]int≠[4]int) ve diziler değer tipidir: atama ve fonksiyona geçirme tüm elemanları kopyalar. - Dilim, alttaki bir diziye bakan 24 byte'lık bir başlıktır: işaretçi, uzunluk ve kapasite. Dilimleme kopyalama yapmaz, O(1) sürer.
appendkapasite yettiğinde aynı diziye yazar, yetmediğinde daha büyük bir dizi ayırıp kopyalar. Kapasite küçük dilimlerde ikiye katlanır, büyüklerde oran yavaşça 1.25'e iner; bu sayede ekleme amortize O(1)'dir.- Aynı diziyi paylaşan dilimlerde
appendkomşu veriyi ezebilir. Tam dilim ifadesis[low:high:max]kapasiteyi sınırlayarak bunu önler. copyyalnızcamin(len(dst), len(src))kadar kopyalar; bağımsız kopya içinmake([]T, len(src))veyaslices.Clonekullan.- nil dilim ve boş dilim çoğu işlemde aynı davranır; boşluk kontrolünü
len(s) == 0ile yap, fark JSON gibi dış temsillerde ortaya çıkar. - Fonksiyona dilim geçirildiğinde başlık kopyalanır, elemanlar paylaşılır. Uzunluğu değiştiren fonksiyonlar yeni dilimi döndürmelidir.
[][]Ther satırı ayrı oluşturulan bağımsız dilimlerden oluşur;slicespaketiSort,BinarySearch,Contains,Index,Reverse,Insert,Delete,EqualveCloneile günlük işleri kısaltır.