Что выведет код? Append и базовый массив
Условие
Определите, что выведет следующий код:
package main
import "fmt"
func main() {
a := []int{1, 2, 3}
b := append(a[:1], 10)
fmt.Println(b, a)
}
Вопросы
- Что выведет программа?
- Почему значение в
aизменилось? - Когда append создаёт новый массив, а когда использует существующий?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Это задача на глубокое понимание того, как слайсы работают в 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]с добавлением 10a = [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 использует существующий буфер, если:
-
Есть свободное место в capacity
a := []int{1, 2, 3} // cap = 3, len = 3 b := append(a[:1], 10) // a[:1] имеет cap = 3, len = 1 // свободное место = 3 - 1 = 2 → используется существующий буфер -
НО: это может изменить исходный слайс!
a = [1, 10, 3] ← второй элемент изменился
append создаёт НОВЫЙ буфер, если:
- Нет свободного места
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([]...), ...) | Всегда новый | ✅ Нет |
Ключевые выводы
- Слайсы делят backing array — это может привести к неожиданным изменениям
- append переиспользует буфер, если есть место — это оптимизация, но может быть опасно
- Срезы слайса (a[:k]) имеют тот же capacity, что и оригинал
- Если нужно безопасное append, создавайте новый буфер или используйте copy
- Осторожность с append к срезам циклов for range!
Это частая ошибка в Go, которая приводит к неуловимым багам, потому что код выглядит безопасно, но неожиданно изменяет исходный слайс.