Ответ
Слайс в Go — это структура-заголовок, содержащая три поля: указатель на базовый массив, длину (len
) и ёмкость (capacity
). Функция append
добавляет элементы в конец слайса и возвращает обновленный слайс. Её поведение зависит от ёмкости исходного слайса.
Сценарий 1: Ёмкости достаточно (len < cap
)
- Новый элемент добавляется в тот же базовый массив, на который указывает слайс.
len
слайса увеличивается,cap
остается прежним.- Указатель на массив не меняется. Новый слайс (возвращаемый
append
) и старый будут ссылаться на один и тот же базовый массив. Это может приводить к неожиданным побочным эффектам, если не быть осторожным.
Сценарий 2: Ёмкости недостаточно (len == cap
)
- Выделяется память под новый, больший базовый массив. Его новая ёмкость обычно удваивается по сравнению со старой (для небольших слайсов).
- Все элементы из старого массива копируются в новый.
- Новый элемент добавляется в конец нового массива.
append
возвращает слайс, который указывает уже на этот новый массив. Старый массив, если на него больше нет ссылок, будет очищен сборщиком мусора.
Пример:
s1 := make([]int, 3, 4) // len=3, cap=4, массив: [0,0,0, _]
copy(s1, []int{1, 2, 3}) // s1 = [1, 2, 3]
// Сценарий 1: есть место в capacity
s2 := append(s1, 4) // len=4, cap=4. s2 использует тот же массив
fmt.Printf("s1: len=%d, cap=%d, %vn", len(s1), cap(s1), s1)
// s1: len=3, cap=4, [1 2 3]
fmt.Printf("s2: len=%d, cap=%d, %vn", len(s2), cap(s2), s2)
// s2: len=4, cap=4, [1 2 3 4]
// Сценарий 2: нет места, нужна новая аллокация
s3 := append(s2, 5) // len=5, cap=8 (удвоено). s3 указывает на новый массив
fmt.Printf("s3: len=%d, cap=%d, %vn", len(s3), cap(s3), s3)
// s3: len=5, cap=8, [1 2 3 4 5]
Ключевое правило
Функция append
может вернуть как слайс, указывающий на старый массив, так и на новый. Поэтому результат append
всегда нужно присваивать обратно в переменную, с которой вы работаете:
mySlice = append(mySlice, newElement)