Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Структура слайса в Go
Слайс (slice) в Go — это динамический массив, ключевая абстракция для работы с последовательностями данных. В отличие от массива, длина слайса может изменяться. Слайс не является самостоятельным типом данных в смысле хранения, он представляет собой обёртку (view) над массивом. Внутренняя структура слайса состоит из трех полей, которые хранятся в памяти как структура.
Три основных поля слайса
С точки зрения реализации в runtime Go, слайс содержит следующие поля:
struct Slice {
ptr *T // указатель на первый элемент базового массива
len int // текущая длина (количество элементов, доступных в слайсе)
cap int // ёмкость (максимальное количество элементов, которые могут быть размещены без реаллокации)
}
1. ptr (pointer)
- Это указатель на первый элемент базового (underlying) массива, в котором фактически хранятся данные слайса.
- Слайс не содержит данные самостоятельно — он лишь ссылается на часть массива (или весь массив).
- Именно поэтому несколько слайсов могут ссылаться на один и тот же базовый массив, что приводит к явлению aliasing (перекрытие).
2. len (length)
- Это текущая длина слайса — количество элементов, которые можно прочитать или записать в пределах слайса.
- Длина возвращается функцией
len()и определяет, сколько элементов доступноslice[0]доslice[len-1]. - Длина всегда меньше или равна ёмкости.
3. cap (capacity)
- Это ёмкость слайса — максимальное количество элементов, которые могут быть размещены в базовом массиве без выделения нового массива и копирования данных.
- Ёмкость возвращается функцией
cap()и определяет, до какого индекса можно расширить слайс с помощью операции re-slicing (slice[:cap]).
Пример внутреннего представления
Рассмотрим пример создания слайса:
package main
import (
"fmt"
"unsafe"
)
func main() {
arr := [5]int{1, 2, 3, 4, 5} // базовый массив
slice := arr[1:4] // слайс с элементами 2,3,4
// Получаем указатель, длину и ёмкость через unsafe (для демонстрации)
slicePtr := unsafe.Pointer(&slice)
sliceHeader := (*[3]int)(slicePtr)
fmt.Printf("ptr (примерный адрес): %v\n", sliceHeader[0])
fmt.Printf("len: %d\n", sliceHeader[1]) // len(slice) = 3
fmt.Printf("cap: %d\n", sliceHeader[2]) // cap(slice) = 4 (от arr[1] до конца arr)
}
Важно: Обычно программист не работает напрямую с этими полями, но понимание этой структуры критически важно для корректного использования слайсов.
Практические следствия структуры слайса
- Передача слайса в функции — передаётся не весь массив данных, а только эта структура (три поля), что очень эффективно.
- Изменения элементов — если два слайса ссылаются на один базовый массив, изменение элемента в одном слайсе повлияет на другой.
- Операции re-slicing — создание нового слайса из существующего (
slice[i:j]) не копирует данные, а лишь создаёт новую обёртку с новымиlenиcap, но тем жеptr. - Расширение слайса — если при добавлении через
append()ёмкость превышена, выделяется новый базовый массив,ptrизменяется на новый адрес,capувеличивается (обычно в 2 раза для небольших слайсов).
Это внутреннее представление объясняет, почему слайсы в Go одновременно эффективны (минимум копирования) и требуют внимания при работе (проблемы aliasing, реаллокации).