Bab 5 dari 10
Array, Slice, dan Map
Tujuan Pembelajaran
Setelah bab ini, Anda mampu:
- membedakan array, slice, dan map;
- mengelola panjang dan kapasitas slice;
- memahami backing array,
append, dancopy; - membedakan koleksi nil dan kosong;
- memakai map dengan pola lookup
comma-ok.
1. Array: Ukuran Bagian dari Tipe
Array menyimpan elemen berjumlah tetap. Panjangnya menjadi bagian dari tipe; [3]int berbeda dari [4]int.
package main
import "fmt"
func main() {
var nol [3]int
prima := [4]int{2, 3, 5, 7}
otomatis := [...]string{"merah", "hijau", "biru"}
fmt.Println(nol, prima, otomatis)
fmt.Println(len(prima), prima[0])
}
Assignment dan parameter array menyalin seluruh array. Array berguna untuk ukuran intrinsik dan tetap, misalnya hash 32 byte. Untuk koleksi umum, slice lebih fleksibel.
2. Slice: Tampilan ke Backing Array
Slice bukan array. Slice adalah descriptor yang mereferensikan segmen backing array, dengan panjang (len) dan kapasitas (cap).
package main
import "fmt"
func main() {
angka := []int{10, 20, 30, 40, 50}
bagian := angka[1:4]
fmt.Println(bagian, len(bagian), cap(bagian))
bagian[0] = 99
fmt.Println(angka)
}
Batas a[low:high] mencakup low dan tidak mencakup high. Kapasitas dihitung dari awal slice sampai akhir backing array yang tersedia.
Buat slice dengan make ketika panjang atau kapasitas diketahui:
package main
import "fmt"
func main() {
a := make([]int, 3)
b := make([]int, 0, 8)
fmt.Println(a, len(a), cap(a))
fmt.Println(b, len(b), cap(b))
}
make([]int, 3) menghasilkan tiga elemen yang sudah dapat diindeks. make([]int, 0, 8) belum memiliki elemen, sehingga b[0] panic; kapasitas bukan panjang.
3. append, Kapasitas, dan Alias
append mengembalikan slice baru dan hasilnya harus disimpan. Jika kapasitas cukup, backing array lama dapat dipakai; jika tidak, runtime mengalokasikan array baru.
package main
import "fmt"
func main() {
nilai := make([]int, 0, 2)
nilai = append(nilai, 10, 20)
nilai = append(nilai, 30)
fmt.Println(nilai, len(nilai), cap(nilai))
}
Karena dua slice dapat berbagi backing array, perubahan atau append kadang terlihat melalui slice lain dan kadang tidak setelah realokasi. Jangan bergantung pada detail pertumbuhan kapasitas.
Gunakan full slice expression untuk membatasi kapasitas saat perlu mencegah append menimpa bagian lain:
semua := []int{1, 2, 3, 4}
kiri := semua[:2:2]
kiri = append(kiri, 9)
fmt.Println(semua, kiri)
Kapasitas kiri dibatasi dua, sehingga append memerlukan backing array baru.
4. Menyalin Slice
Assignment slice menyalin header, bukan elemen. Untuk salinan independen, gunakan copy atau append ke slice baru.
package main
import "fmt"
func main() {
asal := []int{1, 2, 3}
salinan := make([]int, len(asal))
copy(salinan, asal)
salinan[0] = 99
fmt.Println(asal, salinan)
}
copy(dst, src) mengembalikan jumlah elemen yang disalin, yaitu minimum panjang keduanya.
5. Slice Nil dan Kosong
var nilSlice []int
emptySlice := []int{}
madeEmpty := make([]int, 0)
Ketiganya memiliki panjang nol dan aman untuk range serta append. Hanya nilSlice == nil yang benar. Perbedaan dapat tampak pada serialisasi atau API eksternal (null versus []), sehingga ikuti kontrak boundary. Slice tidak dapat dibandingkan dengan slice lain; hanya dapat dibandingkan dengan nil.
6. Map dan comma-ok
Map menyimpan pasangan key-value. Key harus comparable; slice, map, dan function tidak dapat menjadi key.
package main
import "fmt"
func main() {
skor := map[string]int{
"Ayu": 90,
"Bima": 82,
}
skor["Citra"] = 88
nilai, ada := skor["Ayu"]
fmt.Println(nilai, ada)
hapusNilai, ada := skor["Doni"]
fmt.Println(hapusNilai, ada)
delete(skor, "Bima")
fmt.Println(len(skor))
}
Lookup key yang tidak ada menghasilkan zero value. Karena zero value mungkin nilai valid, gunakan bentuk nilai, ada := m[key] untuk membedakannya.
Map nil dapat dibaca dan menghasilkan zero value, tetapi penulisan menyebabkan panic:
var m map[string]int
fmt.Println(m["x"])
m = make(map[string]int)
m["x"] = 1
Map adalah reference-like descriptor. Assignment atau pengiriman ke fungsi membuat descriptor baru yang mengacu data map sama. Map tidak aman untuk read/write bersamaan tanpa sinkronisasi. Urutan iterasinya tidak ditentukan.
Pola penghitung:
package main
import "fmt"
func main() {
frekuensi := make(map[rune]int)
for _, r := range "banana" {
frekuensi[r]++
}
fmt.Println(frekuensi['a'])
}
Zero value int membuat increment langsung bekerja tanpa pemeriksaan keberadaan.
Kesalahan Umum
- Menganggap kapasitas sebagai elemen yang dapat diindeks. Indeks harus selalu kurang dari
len. - Mengabaikan hasil
append. Tuliss = append(s, x). - Mengira assignment slice menyalin data. Gunakan
copyuntuk independensi. - Tidak menyadari alias backing array. Salin atau batasi kapasitas ketika isolasi diperlukan.
- Menulis ke map nil. Inisialisasi dengan literal atau
make. - Mengandalkan zero value lookup untuk membuktikan key ada. Gunakan
comma-ok. - Mengandalkan urutan iterasi map. Urutkan key untuk keluaran stabil.
- Membandingkan dua slice atau map. Keduanya hanya dapat dibandingkan dengan
nil.
Latihan
- Buat array tujuh suhu harian, lalu hitung rata-ratanya melalui slice.
- Buat slice dengan kapasitas awal lima, tambahkan sepuluh elemen, dan cetak
len/capsetelah setiap append. - Demonstrasikan dua slice yang berbagi backing array, lalu buat versi independen dengan
copy. - Hitung frekuensi setiap kata dalam kalimat memakai
map[string]int. - Buat fungsi lookup kontak yang mengembalikan nomor dan
bool, termasuk kasus nomor kosong sebagai nilai valid. - Bandingkan perilaku slice nil dan kosong saat
len,range,append, dan pemeriksaan== nil.
Rangkuman
Array memiliki ukuran tetap yang menjadi bagian tipe. Slice menyediakan tampilan fleksibel ke backing array melalui pointer, panjang, dan kapasitas; append dapat mempertahankan atau mengganti storage, sedangkan copy membuat elemen independen. Map memberi lookup efisien tetapi tidak menjamin urutan. Pahami nilai nil, aliasing, batas indeks, dan pola comma-ok agar pengelolaan koleksi tetap benar.