← Назад к вопросам

Что происходит со слайсом при применении Append?

1.0 Junior🔥 161 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что происходит со слайсом при вызове append в Go

Вызов встроенной функции append в Go — это ключевая операция для добавления элементов в слайс, но её поведение может быть неочевидным из-за внутреннего устройства слайсов. Понимание этого процесса критично для написания корректного и эффективного кода.

Внутреннее устройство слайса

Сначала вспомним, что слайс — это не массив, а дескриптор данных, состоящий из трёх компонентов:

  • Указатель (ptr) на первый элемент базового массива.
  • Длина (len) — количество элементов, доступных в слайсе.
  • Ёмкость (cap) — общий размер базового массива, начиная с первого элемента слайса.
// Структура слайса (логическое представление)
type sliceHeader struct {
    ptr *byte  // указатель на массив
    len int    // длина
    cap int    // ёмкость
}

Поведение функции append

Функция append(slice []T, elements ...T) []T работает по следующему алгоритму:

  1. Проверка наличия места — сравнивает текущую длину (len) с ёмкостью (cap).
  2. Если места достаточно (len < cap):
    • Добавляет элементы в существующий базовый массив.
    • Увеличивает значение len на количество добавленных элементов.
    • Возвращает тот же слайс (с обновлённой длиной), но это новый дескриптор с изменённым len.
    • Исходный слайс и новый слайс разделяют один базовый массив.
func main() {
    s1 := make([]int, 2, 4) // len=2, cap=4
    s1[0], s1[1] = 1, 2
    
    s2 := append(s1, 3) // len(s1)=2 < cap=4 → места хватит
    // s1 и s2 разделяют один базовый массив
    fmt.Println(s1) // [1 2]
    fmt.Println(s2) // [1 2 3]
    
    // Изменение через s2 влияет на s1, если затронут общий участок
    s2[0] = 99
    fmt.Println(s1) // [99 2] — изменилось!
    fmt.Println(s2) // [99 2 3]
}
  1. Если места недостаточно (len >= cap):
    • Создаётся новый базовый массив с увеличенной ёмкостью.
    • Алгоритм роста обычно удваивает ёмкость (для маленьких слайсов), но для больших использует более консервативные коэффициенты (1.25x).
    • Все элементы из старого массива копируются в новый.
    • Добавляемые элементы помещаются в конец нового массива.
    • Возвращается новый слайс, который ссылается на новый массив.
    • Старый и новый слайсы теперь полностью независимы.
func main() {
    s1 := []int{1, 2} // len=2, cap=2
    s2 := append(s1, 3) // len=cap=2 → места НЕТ, создаётся новый массив
    
    s2[0] = 99 // меняем только s2
    fmt.Println(s1) // [1 2] — не изменился
    fmt.Println(s2) // [99 2 3]
    
    // Демонстрация роста ёмкости
    var s []int
    for i := 0; i < 10; i++ {
        fmt.Printf("len=%d cap=%d\n", len(s), cap(s))
        s = append(s, i)
    }
}

Важные нюансы и рекомендации

  1. Всегда присваивайте результат append:

    slice = append(slice, element) // Правильно
    append(slice, element)         // НЕПРАВИЛЬНО! Результат может быть потерян
    
  2. Производительность при переаллокации:

    • Копирование массива — операция O(n), где n — количество элементов.
    • Для минимизации переаллокаций предварительно задавайте достаточную ёмкость через make, если знаете ожидаемый размер.
  3. Совместное использование базового массива:

    • Если несколько слайсов ссылаются на один массив, изменения через один слайс могут повлиять на другие.
    • Используйте copy() или полное выражение среза (slice[start:end:end]) для создания независимых копий.
  4. append с распространением (...)

    a := []int{1, 2}
    b := []int{3, 4}
    c := append(a, b...) // Добавление всех элементов b
    

Заключение

Функция append — это интеллектуальный конструктор, который либо модифицирует существующий слайс, когда это безопасно, либо создаёт новый с увеличенной ёмкостью. Ключевое понимание заключается в разделении дескриптора слайса (значение в стеке) и базового массива (данные в куче). При работе с append всегда помните о возможных побочных эффектах из-за общего базового массива и неизбежных затратах на переаллокацию при исчерпании ёмкости. Это знание помогает избежать распространённых ошибок и писать более эффективный код на Go.

Что происходит со слайсом при применении Append? | PrepBro