Как слайсы работают
В языке Go слайсы (slice) представляют собой обёртку над массивом, которая предоставляет гибкий способ работы с последовательностями элементов. Это один из фундаментальных типов данных в языке, обеспечивающий мощные и безопасные операции над коллекциями без необходимости работать с указателями и вручную управлять памятью.
Слайсы — не самостоятельная структура хранения, а описание части массива, включающее метаинформацию: длину и вместимость (capacity). Они используются повсеместно — в функциях, структуре данных, обработке входных и выходных данных.
1. Что такое слайс
Слайс в Go устроен как структура:
type sliceHeader struct {
Data uintptr // указатель на первый элемент в массиве
Len int // количество доступных элементов
Cap int // вместимость от Data до конца массива
}
Реальный тип: []T, где T — тип элементов.
Пример:
arr := \[5\]int{10, 20, 30, 40, 50}
s := arr\[1:4\] // содержит элементы: 20, 30, 40
-
len(s) = 3
-
cap(s) = 4 (от arr[1] до конца arr)
2. Срезы и массивы
Слайсы не хранят данные — они ссылаются на массив. Несколько слайсов могут ссылаться на один и тот же массив:
a := \[5\]int{1, 2, 3, 4, 5}
s1 := a\[1:4\]
s2 := a\[2:\]
s1\[1\] = 99 // изменяет a\[2\], s2 тоже увидит это изменение
Таким образом, слайсы передают ссылку на данные, а не копию.
3. Создание слайсов
3.1. С помощью литерала
s := \[\]int{1, 2, 3}
Создаётся массив и слайс, ссылающийся на него.
3.2. С помощью make
s := make(\[\]int, 5) // len=5, cap=5
s := make(\[\]int, 3, 10) // len=3, cap=10
make создаёт массив нужной длины и возвращает слайс.
3.3. Из массива
arr := \[5\]int{1, 2, 3, 4, 5}
s := arr\[1:4\] // элементы 2, 3, 4
4. len и cap
-
len(slice) — количество элементов в слайсе.
-
cap(slice) — сколько элементов можно получить от начала до конца массива без перераспределения памяти.
a := \[5\]int{1, 2, 3, 4, 5}
s := a\[1:3\] // len=2, cap=4
5. Слайс как динамический массив
При добавлении элементов с помощью append, Go может перераспределить память, если не хватает cap:
s := \[\]int{1, 2, 3}
s = append(s, 4) // len=4, cap может увеличиться
Если cap достаточно — элемент добавляется в существующий массив. Если нет — создаётся новый массив, и данные копируются в него.
6. Механизм append
s := \[\]int{1, 2, 3}
s2 := append(s, 4, 5)
Алгоритм append:
-
Проверяет cap.
-
Если cap >= len + n, расширяет len и записывает элементы.
-
Иначе:
-
выделяет новый массив;
-
копирует старые элементы;
-
добавляет новые;
-
возвращает новый слайс.
-
Важно: append может создать новый слайс. Старый и новый могут указывать на разные массивы.
7. Copy и sharing данных
s1 := \[\]int{1, 2, 3}
s2 := s1 // ссылаются на один массив
s3 := make(\[\]int, 3)
copy(s3, s1) // копирует данные, s3 теперь независим
Функция copy(dst, src) копирует элементы поэлементно. Полезно, если нужно избежать совместного владения памятью.
8. Слайсы и функции
Слайсы передаются по значению, но содержат ссылку на данные:
func modify(s \[\]int) {
s\[0\] = 100 // изменит оригинал
s = append(s, 200) // это будет новая копия
}
Изменения данных внутри слайса будут видны, но если append приводит к копии — это будет новая память.
9. Слайсы с нулевой длиной и nil
var s \[\]int // nil-слайс
s2 := \[\]int{} // пустой, но не nil
Свойство | var s []int | []int{} |
---|---|---|
s == nil | true | false |
--- | --- | --- |
len(s) | 0 | 0 |
--- | --- | --- |
cap(s) | 0 | 0 |
--- | --- | --- |
10. Обрезка слайса (резка)
s := \[\]int{1, 2, 3, 4, 5}
sub := s\[1:4\] // элементы: 2, 3, 4
Формат [low : high]:
-
s[low:high] включает индексы от low до high-1.
-
len = high - low
-
cap = исходная_cap - low
Можно также делать:
s := arr\[low:high:max\]
Где max ограничивает capacity результата.
11. Реалокация и поведение append
s1 := \[\]int{1, 2, 3}
s2 := append(s1, 4)
s1\[0\] = 99
fmt.Println(s2\[0\]) // может быть 99 или 1 в зависимости от cap(s1)
Если cap(s1) >= 4, то append не создаёт новый массив — s2 и s1 указывают на один и тот же массив.
Если cap(s1) < 4 — создаётся новый массив, s2 будет независимым.
12. Рост capacity
Алгоритм роста cap при append:
-
При маленьких слайсах: cap *= 2.
-
При больших: рост может быть менее агрессивным.
-
В Go 1.19+ рост может быть нелинейным для больших слайсов.
13. Удаление элемента
В Go нет встроенной функции remove. Можно сделать вручную:
s := \[\]int{1, 2, 3, 4}
i := 2
s = append(s\[:i\], s\[i+1:\]...)
Удаляет элемент с индексом i.
14. Слайсы и zero value
Ноль значения []T — это nil. Это корректный слайс, можно выполнять append, len, range:
var s \[\]int
s = append(s, 1) // OK
15. Итерация
for i, v := range s {
fmt.Println(i, v)
}
Можно также использовать обычный for:
for i := 0; i < len(s); i++ {
fmt.Println(s\[i\])
}
16. Слайсы внутри структур
Слайсы можно использовать как поля:
type Buffer struct {
data \[\]byte
}
Поскольку слайс содержит указатель, передача структуры по значению передаёт копию слайда, но не самих данных — будьте осторожны при модификации.
17. Слайсы и интерфейсы
Интерфейсы можно использовать для представления слайсов любого типа, но:
var x interface{} = \[\]int{1,2,3}
fmt.Println(x.(\[\]int)) // type assertion
18. Обходные техники и оптимизация
- Использование [:0] для повторного использования памяти:
buf := make(\[\]byte, 0, 1024)
buf = buf\[:0\] // обнуляем длину, не выделяя память заново
- Использование copy вместо append при объединении:
copy(dst\[len(dst):\], src)
- Предварительное выделение cap для избежания аллокаций:
s := make(\[\]int, 0, 1000)
19. Безопасность при передаче в другие горутины
Если слайс используется в нескольких горутинах — защита обязательна (через мьютекс, каналы). Иначе возможна гонка доступа к памяти (race condition).
20. Сравнение с другими языками
-
В отличие от Python и JavaScript, слайсы Go — низкоуровневые и эффективные, ближе к std::vector в C++.
-
Не поддерживают встроенные методы как filter, map, reduce, но легко реализуются вручную.