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