Ответ
Срез (slice) в Go — это мощная, гибкая и удобная структура данных для управления последовательностями элементов одного типа. В отличие от массива, размер среза не является фиксированным.
Внутренняя структура (заголовок среза):
Срез — это легковесная структура (заголовок), которая описывает непрерывный участок базового массива. Она состоит из трёх полей:
// Псевдокод структуры
type sliceHeader struct {
Data unsafe.Pointer // 1. Указатель на начало базового массива
Len int // 2. Длина (len) - количество видимых элементов
Cap int // 3. Ёмкость (cap) - максимальная длина без переаллокации
}
len
(длина): Количество элементов, которые содержит срез. Доступно черезlen(s)
.cap
(ёмкость): Количество элементов в базовом массиве, начиная от указателя среза. Доступно черезcap(s)
.
Ключевые операции:
Создание среза:
- Литерал:
s1 := []int{1, 2, 3}
(len=3
,cap=3
) make
:s2 := make([]int, 5, 10)
(len=5
,cap=10
)- Из массива или другого среза:
s3 := arr[1:4]
- Литерал:
append
(добавление элементов):slice = append(slice, elem1, elem2, ...)
- Если
len < cap
: Новый элемент добавляется в базовый массив,len
увеличивается. Базовый массив не меняется. - Если
len == cap
: Происходит переаллокация. Go создает новый, больший массив (обычно с удвоенной ёмкостью), копирует в него все элементы из старого, добавляет новый элемент и обновляет указатель в заголовке среза. - Важно:
append
возвращает новый заголовок среза, который необходимо присваивать обратно:s = append(s, ...)
.
- Если
Slicing (создание нового среза из существующего):
newSlice := oldSlice[low:high]
newSlice
будет использовать тот же базовый массив, что иoldSlice
.len(newSlice)
будетhigh - low
.cap(newSlice)
будетcap(oldSlice) - low
.
Важный нюанс: общий базовый массив
Поскольку несколько срезов могут ссылаться на один и тот же базовый массив, изменение элементов в одном срезе может затронуть другой.
original := []int{10, 20, 30, 40, 50}
s1 := original[1:3] // {20, 30}, len=2, cap=4
s2 := original[2:5] // {30, 40, 50}, len=3, cap=3
fmt.Println(s1, s2) // [20 30] [30 40 50]
s1[1] = 99 // Изменяем элемент {20, 99}
// Изменение отразилось в original и s2, так как массив общий
fmt.Println(original) // [10 20 99 40 50]
fmt.Println(s2) // [99 40 50]
Это критически важная особенность, которую нужно понимать, чтобы избежать неожиданного поведения.