Slice: len, cap, append и реаллокация
Зачем это на интервью
Slice — основной контейнер Go. Важно отличать его маленький заголовок от массива за ним: это объясняет стоимость append, неожиданные изменения данных и причины лишних аллокаций.
Минимум для E4
- Объяснить, что
len— доступная длина, аcap— граница роста в том же массиве. - Знать, что
appendвозвращает новый slice и может выделить новый массив. - Предвыделять ёмкость, когда известен размер результата.
Углубление для E5/Senior
Заголовок slice содержит указатель на backing array, длину и capacity; его копирование не копирует элементы. Алгоритм роста capacity — деталь runtime, не контракт: нельзя строить корректность или точный memory budget на предположении о коэффициенте роста. Для горячего пути измеряют аллокации (go test -benchmem), а не оптимизируют на глаз.
Ключевые понятия
make([]T, n, c) создаёт длину n и capacity c, где n <= c. Индексировать можно только до len-1; s[:cap(s)] расширяет видимую длину в пределах backing array. append(s, x) записывает в свободную capacity либо выделяет массив, копирует старые элементы и возвращает новый заголовок.
func squares(n int) []int {
out := make([]int, 0, n) // len=0, cap=n
for i := 0; i < n; i++ {
out = append(out, i*i)
}
return out
}
s := make([]int, 2, 3)
s[0], s[1] = 10, 20
s = append(s, 30) // та же backing storage допустима
s = append(s, 40) // может выделить новую; старый s нельзя ожидать изменённымНе игнорируйте результат append: функция получает копию заголовка, поэтому изменение длины останется локальным, если не вернуть или не присвоить результат.
Типовые вопросы
- Чем
lenотличается отcap?lenопределяет доступные элементы,cap— максимум длины при реслайсе без новой аллокации.
- Всегда ли
appendвыделяет память?- Нет: только когда свободной capacity недостаточно; это нельзя проверять по фиксированному правилу роста.
- Почему
appendнадо присваивать?- Он возвращает заголовок с новой длиной, а иногда и новым указателем.
- Когда использовать
make([]T, 0, n)?- Когда элементы будут добавляться
appendи известна ожидаемая верхняя оценкаn.
- Когда элементы будут добавляться
- Что произойдёт при
make([]int, 10)и затемappend?- В slice уже десять нулевых элементов;
appendдобавит после них. Для пустого результата нуженmake([]int, 0, 10).
- В slice уже десять нулевых элементов;
Практика
- Реализуйте
Filter([]int, func(int) bool) []intсmake(..., 0, len(in)). Готово: порядок сохранён, вход не меняется, benchmark показывает не более одной аллокации для непустого результата. - Объясните на примере переход
len/capпосле трёхappend. Готово: ответ не предполагает конкретный коэффициент роста runtime.
Частые ошибки и ловушки
- Путать
make([]T, n)с резервированием места. - Сохранять указатель на элемент и рассчитывать на его актуальность после
appendс реаллокацией. - Выводить алгоритм роста capacity из одного запуска и считать его API.
Связанные темы
Коллекции и работа с данными · Аллокации и GC