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

Что выведет код? Append и базовый массив

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

Условие

Определите, что выведет следующий код:

package main

import "fmt"

func main() {
    a := []int{1, 2, 3}
    b := append(a[:1], 10)
    fmt.Println(b, a)
}

Вопросы

  1. Что выведет программа?
  2. Почему значение в a изменилось?
  3. Когда append создаёт новый массив, а когда использует существующий?

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

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

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

Решение

Это задача на глубокое понимание того, как слайсы работают в Go, особенно взаимодействие append и shared backing array.

Ответ на вопросы

1. Что выведет программа?

[10 2 3] [1 10 3]

Отправка:

  • b = [10 2 3]
  • a = [1 10 3] (значение изменилось!)

2. Почему значение в a изменилось?

Потому что a[:1] и a делят один и тот же backing array (буферный массив).

Анализ кода

a := []int{1, 2, 3}

Внутреннее представление a:

slice a:
┌──────┬─────┬─────┐
│ ptr  │ 3   │ 3   │  ptr → [1, 2, 3] (буферный массив)
│      │ len │ cap │  len = 3, cap = 3
└──────┴─────┴─────┘
b := append(a[:1], 10)

Шаг 1: a[:1] создаёт новый слайс

slice a[:1]:
┌──────┬─────┬─────┐
│ ptr  │ 1   │ 3   │  ptr → [1, 2, 3] (ТОТЖЕ буферный массив!)
│      │ len │ cap │  len = 1, cap = 3
└──────┴─────┴─────┘

Шаг 2: append(a[:1], 10) добавляет 10

  • a[:1] имеет длину 1, capacity 3
  • Буффер может вместить ещё элементы (есть место!)
  • append не создаёт новый буфер, а пишет в существующий на позицию 1
Буферный массив после append:
[1, 10, 3]  ← 10 заменил 2!

slice b (результат append):
┌──────┬─────┬─────┐
│ ptr  │ 2   │ 3   │  ptr → [1, 10, 3]
│      │ len │ cap │  len = 2, cap = 3
└──────┴─────┴─────┘

Шаг 3: Оба слайса указывают на одну память

a = [1, 10, 3]  ← len = 3, cap = 3, видит весь буфер
b = [10, 3]     ← len = 2, cap = 3, видит буфер с позиции 1

НО в выводе:

fmt.Println(b, a)
// b выводит [10, 2, 3]? НЕТ!

Ждите, давайте пересчитаем!

Пересчёт

А, ошибка в моём анализе. Давайте правильно:

a := []int{1, 2, 3}   // backing array: [1, 2, 3]
b := append(a[:1], 10) // append к [1], результат [1, 10]

Программа выведет:

[1 10] [1 10 3]

Потому что:

  • b = [1, 10] — результат append к [1] с добавлением 10
  • a = [1, 10, 3] — исходный слайс теперь видит изменённый буфер

Но вопрос спрашивает другое! Давайте проверим ещё раз:

b := append(a[:1], 10)  // это не `append(a[:1], 10)`, это `b := append(a[:1], 10)`

a[:1] — срез от индекса 0 до 1 (не включая 1):

  • a[:1] = [1] (первый элемент)

append([1], 10) добавляет 10:

  • Результат: [1, 10]

Так как capacity оставшейся части a[1:] = 2 (позиции 1, 2), append использует существующий буфер:

Исходный буфер: [1, 2, 3]
После append:   [1, 10, 3]  ← 10 заменил 2 на позиции 1

Вывод:

b = [1, 10]       ← от позиции 0 до 2
a = [1, 10, 3]    ← от позиции 0 до 3, видит изменённый буфер

Визуализация памяти

Исходное состояние:
Буферный массив:  [1] [2] [3]
                  ↑ ↑  ↑  ↑
slice a:      ptr─┘ len=3, cap=3
slice a[:1]:  ptr─┘ len=1, cap=3

После append([1], 10):
Буферный массив:  [1] [10] [3]
                  ↑ ↑   ↑   ↑
slice b:      ptr─┘ len=2, cap=3
slice a:      ptr─┘ len=3, cap=3 (видит весь буфер)

3. Когда append создаёт новый массив, а когда использует существующий?

append использует существующий буфер, если:

  1. Есть свободное место в capacity

    a := []int{1, 2, 3}  // cap = 3, len = 3
    b := append(a[:1], 10)  // a[:1] имеет cap = 3, len = 1
    // свободное место = 3 - 1 = 2 → используется существующий буфер
    
  2. НО: это может изменить исходный слайс!

    a = [1, 10, 3]  ← второй элемент изменился
    

append создаёт НОВЫЙ буфер, если:

  1. Нет свободного места
    a := []int{1, 2, 3}  // cap = 3, len = 3
    b := append(a, 10)   // нужно место на 4-й элемент
    // свободного места 0 → создаётся НОВЫЙ буфер
    // a остаётся [1, 2, 3]
    // b = [1, 2, 3, 10] в НОВОМ буфере
    

Правило: когда append создаёт новый буфер

new_len = len(old) + count

if new_len <= cap(old):
    // используется существующий буфер
else:
    // создаётся НОВЫЙ буфер
    // new_cap = вычисляется по правилу роста Go

Правило роста capacity в Go

Когда нужен новый буфер, Go создаёт буфер с capacity:

if old_cap < 256:
    new_cap = old_cap * 2  (удваивается)
else:
    new_cap = old_cap * 1.25  (увеличивается на 25%)

Полный пример

package main

import "fmt"

func main() {
    // Пример 1: используется существующий буфер
    fmt.Println("=== Пример 1: существующий буфер ===")
    a := []int{1, 2, 3}
    fmt.Printf("a before: %v, cap: %d\n", a, cap(a))  // [1 2 3], cap: 3
    
    b := append(a[:1], 10)
    fmt.Printf("b: %v, cap: %d\n", b, cap(b))      // [1 10], cap: 3
    fmt.Printf("a after: %v, cap: %d\n", a, cap(a))  // [1 10 3], cap: 3 (ИЗМЕНИЛАСЬ!)
    
    // Пример 2: создаётся НОВЫЙ буфер
    fmt.Println("\n=== Пример 2: новый буфер ===")
    a2 := []int{1, 2, 3}
    fmt.Printf("a2 before: %v, cap: %d\n", a2, cap(a2))  // [1 2 3], cap: 3
    
    b2 := append(a2, 10)  // append к ПОЛНОМУ слайсу
    fmt.Printf("b2: %v, cap: %d\n", b2, cap(b2))      // [1 2 3 10], cap: 6 (новый буфер!)
    fmt.Printf("a2 after: %v, cap: %d\n", a2, cap(a2))  // [1 2 3], cap: 3 (НЕ ИЗМЕНИЛАСЬ!)
    
    // Пример 3: явное разоблачение проблемы
    fmt.Println("\n=== Пример 3: явная проблема ===")
    arr := []int{10, 20, 30, 40, 50}
    slice := arr[:2]  // [10, 20], cap = 5
    fmt.Printf("Before append: arr = %v\n", arr)
    
    slice = append(slice, 100)
    fmt.Printf("After append: arr = %v (ИЗМЕНИЛАСЬ!)\n", arr)
    // arr = [10, 20, 100, 40, 50] ← 30 заменился на 100
}

Как избежать этой проблемы?

Вариант 1: Создать явный новый буфер

a := []int{1, 2, 3}
buffer := make([]int, len(a[:1]), len(a[:1]))  // явно размер
copy(buffer, a[:1])
b := append(buffer, 10)  // append к копии
// a остаётся [1, 2, 3]

Вариант 2: Использовать полный слайс

a := []int{1, 2, 3}
b := append([]int{}, a[:1]...)  // append создаст новый буфер
b = append(b, 10)
// a остаётся [1, 2, 3]

Вариант 3: Явная копия

a := []int{1, 2, 3}
b := make([]int, len(a[:1]))
copy(b, a[:1])
b = append(b, 10)

Таблица: когда append изменяет исходный слайс

КодAppend использует буферa изменяется
append(a, x)Если cap(a) > len(a)❌ Нет (полный)
append(a[:k], x)Если cap(a) > k + 1⚠️ Да (если k < len(a))
append(a[:len(a)], x)Если cap(a) > len(a)✅ Нет
append(make([]...), ...)Всегда новый✅ Нет

Ключевые выводы

  1. Слайсы делят backing array — это может привести к неожиданным изменениям
  2. append переиспользует буфер, если есть место — это оптимизация, но может быть опасно
  3. Срезы слайса (a[:k]) имеют тот же capacity, что и оригинал
  4. Если нужно безопасное append, создавайте новый буфер или используйте copy
  5. Осторожность с append к срезам циклов for range!

Это частая ошибка в Go, которая приводит к неуловимым багам, потому что код выглядит безопасно, но неожиданно изменяет исходный слайс.

Что выведет код? Append и базовый массив | PrepBro