Ответ
Функция append
добавляет элементы в конец слайса. Её поведение напрямую зависит от соотношения его длины (len
) и ёмкости (cap
).
Есть два основных сценария:
Ёмкость не превышена (
len < cap
):- Новый элемент добавляется в существующий базовый массив, на который ссылается слайс.
len
слайса увеличивается на 1.- Новый массив не создается. Изменения происходят "на месте".
Ёмкость исчерпана (
len == cap
):- Go выделяет новый, больший массив.
- Все элементы из старого массива копируются в новый.
- Новый элемент добавляется в конец нового массива.
- Функция
append
возвращает новый слайс, который ссылается на этот новый массив.len
иcap
этого нового слайса обновляются.
Стратегия роста capacity
:
- Для слайсов до 1024 элементов ёмкость обычно удваивается (
cap * 2
). - Для больших слайсов ёмкость растет медленнее, примерно на 25% (
cap * 1.25
), чтобы избежать чрезмерного расхода памяти.
Ключевые правила при работе с append
:
- Всегда присваивайте результат: Так как
append
может вернуть новый слайс, всегда используйте конструкциюslice = append(slice, elem)
. Иначе вы рискуете потерять данные при переаллокации. - Остерегайтесь сайд-эффектов: Если вы делаете
append
в срез, созданный из другого слайса, иcapacity
не превышена, вы можете неожиданно изменить данные в исходном слайсе.
Пример сайд-эффекта:
original := []int{1, 2, 3, 4, 5} // len=5, cap=5
subSlice := original[1:3] // {2, 3}, len=2, cap=4 (делит массив с original)
fmt.Printf("Original до: %vn", original)
// Append к subSlice не вызовет переаллокации, т.к. cap=4
subSlice = append(subSlice, 99)
// Изменился и исходный слайс!
fmt.Printf("Original после: %vn", original) // Вывод: Original после: [1 2 3 99 5]
fmt.Printf("Sub-slice: %vn", subSlice) // Вывод: Sub-slice: [2 3 99]