Массивы (Arrays)
Массив в Go — это фиксированная по размеру последовательность элементов одного типа. Размер массива является частью его типа.
package main
import "fmt"
func main() {
// Declare with explicit size
var a [5]int // [0 0 0 0 0]
// Array literal
b := [3]string{"Go", "Rust", "Python"}
// Size inferred from elements
c := [...]int{10, 20, 30, 40, 50} // [...] = compiler counts
// Indexed initialization
d := [5]int{0: 100, 4: 500} // [100 0 0 0 500]
// Access and modify
a[0] = 1
a[4] = 5
fmt.Println(a) // [1 0 0 0 5]
fmt.Println(b) // [Go Rust Python]
fmt.Println(c) // [10 20 30 40 50]
fmt.Println(d) // [100 0 0 0 500]
fmt.Println(len(a)) // 5
// Arrays are VALUE types — assignment creates a COPY
original := [3]int{1, 2, 3}
copy := original
copy[0] = 999
fmt.Println(original) // [1 2 3] — not affected!
fmt.Println(copy) // [999 2 3]
// Arrays can be compared with ==
x := [3]int{1, 2, 3}
y := [3]int{1, 2, 3}
z := [3]int{1, 2, 4}
fmt.Println(x == y) // true
fmt.Println(x == z) // false
// [3]int and [4]int are DIFFERENT types — cannot compare
}
Ключевые свойства массивов
| Свойство | Описание |
|---|---|
| Размер фиксирован | [5]int и [10]int — разные типы |
| Value type | Присваивание и передача в функцию создают копию |
| Сравнимы | Можно сравнивать == и != |
Длина через len() |
Определена в compile-time |
| Передача в функцию | Копируется полностью (неэффективно для больших массивов) |
На практике: Массивы в Go используются редко. Почти всегда используют слайсы. Массивы полезны, когда размер известен на этапе компиляции и не меняется (например, SHA-256 хэш
[32]byte).
Слайсы (Slices)
Слайс — это динамическая обёртка над массивом. Это самая используемая коллекция в Go.
Внутреннее устройство слайса
Слайс состоит из трёх полей (slice header):
type slice struct {
ptr *array // Pointer to underlying array
len int // Number of elements in the slice
cap int // Capacity (size of underlying array from ptr)
}
Слайс: [1, 2, 3] (len=3, cap=5)
ptr ──────► ┌───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 0 │ 0 │ ← underlying array
└───┴───┴───┴───┴───┘
◄── len=3 ──►
◄──── cap=5 ─────────►
Создание слайсов
package main
import "fmt"
func main() {
// 1. Slice literal
s1 := []int{1, 2, 3, 4, 5}
// 2. make(type, length, capacity)
s2 := make([]int, 5) // len=5, cap=5, [0 0 0 0 0]
s3 := make([]int, 0, 10) // len=0, cap=10, []
// 3. Slicing an array or another slice
arr := [5]int{10, 20, 30, 40, 50}
s4 := arr[1:4] // [20, 30, 40] — elements at index 1, 2, 3
// 4. nil slice (var declaration without initialization)
var s5 []int // nil slice: s5 == nil, len=0, cap=0
// 5. Empty non-nil slice
s6 := []int{} // NOT nil, len=0, cap=0
s7 := make([]int, 0) // NOT nil, len=0, cap=0
fmt.Println(s1, len(s1), cap(s1)) // [1 2 3 4 5] 5 5
fmt.Println(s2, len(s2), cap(s2)) // [0 0 0 0 0] 5 5
fmt.Println(s3, len(s3), cap(s3)) // [] 0 10
fmt.Println(s4, len(s4), cap(s4)) // [20 30 40] 3 4
fmt.Println(s5, s5 == nil) // [] true
fmt.Println(s6, s6 == nil) // [] false
fmt.Println(s7, s7 == nil) // [] false
}
nil slice vs empty slice
| Характеристика | nil slice |
empty slice |
|---|---|---|
| Объявление | var s []int |
s := []int{} или make([]int, 0) |
== nil |
true |
false |
len(s) |
0 |
0 |
cap(s) |
0 |
0 |
append() |
Работает | Работает |
| JSON marshal | null |
[] |
range |
Не выполняется | Не выполняется |
Совет: Для JSON API используйте
[]int{}илиmake([]int, 0), чтобы получить[], а неnull.
Слайсинг (Subslicing)
package main
import "fmt"
func main() {
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// s[low:high] — from low (inclusive) to high (exclusive)
fmt.Println(s[2:5]) // [2 3 4]
fmt.Println(s[:3]) // [0 1 2] — from start
fmt.Println(s[7:]) // [7 8 9] — to end
fmt.Println(s[:]) // [0 1 2 ... 9] — full copy of header
// Three-index slice s[low:high:max] — controls capacity
sub := s[2:5:7]
fmt.Println(sub) // [2 3 4]
fmt.Println(len(sub)) // 3
fmt.Println(cap(sub)) // 5 (7 - 2)
// WARNING: subslice shares the underlying array!
sub[0] = 999
fmt.Println(s[2]) // 999 — original is modified!
}
Опасность разделяемого массива
package main
import "fmt"
func main() {
// Subslice shares the underlying array
original := []int{1, 2, 3, 4, 5}
sub := original[1:3] // [2, 3], shares array with original
// Appending within capacity modifies original!
sub = append(sub, 999)
fmt.Println(original) // [1 2 3 999 5] — element 4 was overwritten!
// SAFE way: use three-index slice to limit capacity
original2 := []int{1, 2, 3, 4, 5}
sub2 := original2[1:3:3] // len=2, cap=2 (no room for append)
sub2 = append(sub2, 999) // Forces new allocation
fmt.Println(original2) // [1 2 3 4 5] — not affected!
// Or use copy()
original3 := []int{1, 2, 3, 4, 5}
sub3 := make([]int, 2)
copy(sub3, original3[1:3])
sub3 = append(sub3, 999) // Independent
fmt.Println(original3) // [1 2 3 4 5] — not affected
}
append и copy
append
package main
import "fmt"
func main() {
var s []int
// Append single elements
s = append(s, 1)
s = append(s, 2, 3)
// Append another slice
more := []int{4, 5, 6}
s = append(s, more...)
fmt.Println(s) // [1 2 3 4 5 6]
// IMPORTANT: append may return a NEW slice!
// Always assign the result back
s = append(s, 7) // s = ... is REQUIRED
// Growth strategy (simplified):
// cap < 256: double capacity
// cap >= 256: grow by ~25% + some padding
s2 := make([]int, 0)
for i := 0; i < 10; i++ {
s2 = append(s2, i)
fmt.Printf("len=%d cap=%d\n", len(s2), cap(s2))
}
// len=1 cap=1
// len=2 cap=2
// len=3 cap=4
// len=4 cap=4
// len=5 cap=8
// ...
}
copy
package main
import "fmt"
func main() {
src := []int{1, 2, 3, 4, 5}
// copy(dst, src) — returns number of elements copied
dst := make([]int, 3)
n := copy(dst, src)
fmt.Println(dst, n) // [1 2 3] 3
// Copy to a larger slice
dst2 := make([]int, 10)
n = copy(dst2, src)
fmt.Println(dst2, n) // [1 2 3 4 5 0 0 0 0 0] 5
// Copy within the same slice (overlapping is safe)
s := []int{0, 1, 2, 3, 4}
copy(s[1:], s[0:]) // Shift right
fmt.Println(s) // [0 0 1 2 3]
// Deep clone a slice
original := []int{10, 20, 30}
clone := make([]int, len(original))
copy(clone, original)
// Or using append:
clone2 := append([]int(nil), original...)
// Or using slices.Clone (Go 1.21+):
// clone3 := slices.Clone(original)
clone[0] = 999
fmt.Println(original) // [10 20 30] — unaffected
fmt.Println(clone) // [999 20 30]
fmt.Println(clone2) // [10 20 30]
}
Slice Tricks
Стандартные приёмы работы со слайсами:
Удаление элемента
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
// Delete element at index i (preserving order)
i := 2 // Remove element '3'
s = append(s[:i], s[i+1:]...)
fmt.Println(s) // [1 2 4 5]
// Delete element (NOT preserving order) — faster
s2 := []int{1, 2, 3, 4, 5}
j := 2
s2[j] = s2[len(s2)-1] // Replace with last element
s2 = s2[:len(s2)-1] // Shrink
fmt.Println(s2) // [1 2 5 4]
}
Вставка элемента
func insert(s []int, index int, value int) []int {
// Grow by one
s = append(s, 0)
// Shift elements right
copy(s[index+1:], s[index:])
// Insert value
s[index] = value
return s
}
func main() {
s := []int{1, 2, 4, 5}
s = insert(s, 2, 3)
fmt.Println(s) // [1 2 3 4 5]
}
Фильтрация (без аллокации)
func filter(s []int, fn func(int) bool) []int {
// Reuse the same underlying array
result := s[:0] // len=0, same underlying array
for _, v := range s {
if fn(v) {
result = append(result, v)
}
}
return result
}
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
evens := filter(numbers, func(n int) bool { return n%2 == 0 })
fmt.Println(evens) // [2 4 6 8 10]
}
Реверс
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]
}
}
// With slices package (Go 1.21+)
// slices.Reverse(s)
Пакет slices (Go 1.21+)
Go 1.21 добавил пакет slices с обобщёнными (generic) функциями:
package main
import (
"fmt"
"slices"
)
func main() {
s := []int{3, 1, 4, 1, 5, 9, 2, 6}
// Sort
slices.Sort(s)
fmt.Println(s) // [1 1 2 3 4 5 6 9]
// Binary search (slice must be sorted)
i, found := slices.BinarySearch(s, 4)
fmt.Println(i, found) // 4 true
// Contains
fmt.Println(slices.Contains(s, 5)) // true
// Index
fmt.Println(slices.Index(s, 9)) // 7
// Min, Max
fmt.Println(slices.Min(s)) // 1
fmt.Println(slices.Max(s)) // 9
// Clone
clone := slices.Clone(s)
clone[0] = 999
fmt.Println(s[0]) // 1 (unaffected)
// Compact (remove consecutive duplicates)
dup := []int{1, 1, 2, 2, 3, 3, 3}
dup = slices.Compact(dup)
fmt.Println(dup) // [1 2 3]
// Reverse
r := []int{1, 2, 3, 4, 5}
slices.Reverse(r)
fmt.Println(r) // [5 4 3 2 1]
// Equal
a := []int{1, 2, 3}
b := []int{1, 2, 3}
fmt.Println(slices.Equal(a, b)) // true
// Delete (Go 1.21+)
d := []int{0, 1, 2, 3, 4}
d = slices.Delete(d, 1, 3) // Delete elements at indices 1, 2
fmt.Println(d) // [0 3 4]
// Insert
ins := []int{1, 2, 5, 6}
ins = slices.Insert(ins, 2, 3, 4)
fmt.Println(ins) // [1 2 3 4 5 6]
}
Производительность
Предварительное выделение ёмкости
package main
import "fmt"
func main() {
// BAD: no pre-allocation, causes multiple reallocations
var bad []int
for i := 0; i < 10000; i++ {
bad = append(bad, i)
}
// GOOD: pre-allocate with known size
good := make([]int, 0, 10000)
for i := 0; i < 10000; i++ {
good = append(good, i)
}
// BEST: if you know the exact size, fill directly
best := make([]int, 10000)
for i := range best {
best[i] = i
}
fmt.Println(len(bad), len(good), len(best))
}
Предотвращение утечек памяти
package main
// Memory leak: subslice keeps the entire original array alive
func getFirstThreeBad(data []int) []int {
return data[:3] // Holds reference to entire underlying array!
}
// Fixed: copy to a new slice
func getFirstThreeGood(data []int) []int {
result := make([]int, 3)
copy(result, data[:3])
return result // Original array can be garbage collected
}
// Same issue with strings (strings are backed by byte slices)
func getPrefix(s string) string {
return string([]byte(s[:10])) // Copy to release the original
}