32 вопросов
Массив - тип фиксированной длины, например [3]int; при присваивании копируется целиком. Слайс - заголовок (указатель на массив + len + cap); при присваивании копируется только заголовок, данные общие. Слайс можно расширять через append, массив - нет.
Заголовок слайса содержит: указатель на базовый массив (где начинаются данные), длину len и ёмкость cap. Тип слайса не включает размер; длина и ёмкость могут быть меньше длины базового массива.
append(s, x) добавляет элементы в конец. Если cap(s) хватает, данные пишутся в тот же массив и увеличивается len. Если нет - выделяется новый массив (обычно с большей ёмкостью), старые данные копируются, возвращается новый слайс. Результат нужно присваивать: s = append(s, x).
До Go 1.18: при нехватке cap новый cap обычно удваивался (для маленьких слайсов - быстрее). С Go 1.18 формула изменилась: рост зависит от текущей cap (примерно 2x при малых размерах, затем коэффициент уменьшается). Точная логика в runtime.
На 64-битной платформе заголовок слайса - 24 байта: указатель (8) + len (8) + cap (8). Сам слайс не содержит элементов - элементы лежат в базовом массиве, на который указывает заголовок.
Массив: var a [3]int, a := [3]int{1,2,3}. Слайс: var s []int (nil), s := []int{}, s := make([]int, 0, 10), s := a[:] (слайс по массиву). Длина массива - часть типа; у слайса - нет.
Слайс с нулевым значением: указатель nil, len 0, cap 0. var s []int - nil-слайс. Вызов append(s, 1) создаёт новый слайс. len(s) и cap(s) для nil равны 0. Срезы от nil-слайса тоже nil. Nil-слайс можно использовать как пустой (например, JSON сериализует nil slice в null, пустой []int{} - в []).
1) Срез разделяет базовый массив с исходным слайсом - изменение элементов видно в обоих. 2) Append через один слайс может перезаписать элементы другого, если cap общий. 3) Держа срез "хвоста", можно держать в памяти весь исходный массив (утечка). Используйте slices.Clip или копирование при необходимости.
s = append(s, s2...) - добавить все элементы s2 в s. Три точки раскрывают слайс в аргументы. Для объединения без изменения исходных: s := append([]T(nil), a...); s = append(s, b...) или предвыделить и copy.
По индексу i: s = append(s[:i], s[i+1:]...) - сдвигает хвост, меняет len. Порядок не сохраняется при удалении без сдвига: s[i] = s[len(s)-1]; s = s[:len(s)-1]. Оба способа могут оставлять ссылку на базовый массив (влияние на GC). Для обнуления удаляемого элемента (указатели): s[len(s)-1] = nil.
Линейная O(n): перебор элементов. Для отсортированного слайса - бинарный поиск: sort.SearchInts(a, x) или sort.Search, сложность O(log n). Пакет slices (Go 1.21+): slices.Contains, slices.Index - линейный поиск.
Удаление с начала через s = s[1:] - O(1) по времени, но сдвигает указатель по базовому массиву; весь массив остаётся в памяти до сборки мусора. Удаление через append(s[:0], s[1:]...) копирует хвост - O(n). Для частого удаления с начала рассмотрите очередь на кольцевом буфере или списке.
В среднем O(1) амортизированно: при достаточной cap - просто запись и увеличение len. При нехватке cap - выделение нового массива и копирование - O(n), но происходит редко, поэтому амортизированная стоимость добавления одного элемента - константа.
copy(dst, src) копирует элементы из src в dst; копируется минимум из len(dst) и len(src). Возвращает количество скопированных элементов. Не меняет len/cap слайсов. Используется для клонирования слайса, сдвига в пределах одного слайса (copy(s[:i], s[i+1:])) и т.д.
Нет. Одновременная запись (включая append) из нескольких горутин - data race. Чтение параллельно с записью - тоже race. Нужна синхронизация: мьютекс, канал или использование одного писателя. Параллельное чтение без записи безопасно.
Заголовок слайса передаётся по значению. Если в функции делают append, может выделиться новый массив и в локальную копию заголовка записывается новый указатель - вызывающий свой слайс не видит. Чтобы изменить слайс снаружи, передают указатель на слайс *[]T или возвращают новый слайс из функции и присваивают: s = append(s, x) и вернуть s.
Массив передаётся по значению - копируется целиком. Большие массивы лучше передавать по указателю *[N]T или передавать слайс по этому массиву. Слайс передаётся по значению (копируется заголовок), поэтому изменение элементов внутри функции видно снаружи, но изменение самого слайса (len/cap/указатель) снаружи не видно.
sort.Slice(s, func(i, j int) bool { return s[i] < s[j] }) сортирует слайс по заданной функции "меньше". sort.SliceStable - стабильная сортировка. Для срезов примитивов есть sort.Ints, sort.Strings и т.д. Пакет slices (Go 1.21+): slices.Sort, slices.SortFunc.
Выделяется новый массив с большей ёмкостью (по правилам роста runtime), в него копируются старые элементы, добавляется новый, возвращается слайс с указателем на новый массив. Старый массив перестаёт быть доступен по этому слайсу и может быть собран GC.
Nil-слайс: указатель nil, len 0, cap 0. Пустой слайс s := []int{} или make([]int, 0) - ненулевой указатель (может указывать на пустой или фиктивный массив), len 0, cap 0. Для len, append, range поведение одинаковое. Отличие проявляется при сериализации (nil -> null в JSON) и при сравнении с nil.
Да. for range по nil-слайсу выполнит ноль итераций. len(s) для nil равен 0. Итерировать по nil-слайсу безопасно и идиоматично.
Элементы лежат в базовом массиве в куче (или на стеке, если компилятор доказал, что срез не убегает). Заголовок слайса хранит указатель на первый элемент этого массива. Несколько слайсов могут разделять один массив (разные срезы одного массива или один и тот же слайс).
len(s) - количество доступных элементов (индексы 0..len-1). cap(s) - ёмкость: от начала слайса до конца базового массива. Всегда cap >= len. Append может расти без реаллокации, пока len < cap.
s = append(s[:i], append([]T{x}, s[i:]...)...) - вставка x на позицию i (с лишней аллокацией промежуточного слайса). Без лишней аллокации: s = append(s, 0); copy(s[i+1:], s[i:]); s[i] = x. Либо сдвиг через copy и запись в s[i].
make([]T, len, cap) - слайс длины len, ёмкости cap. Элементы - zero value типа T. make([]T, n) - len и cap равны n. Если cap опущен, он равен len. Предвыделение cap уменьшает реаллокации при последующих append.
Синтаксис s[low:high:max]: ограничивает cap результирующего слайса значением max - low. Дальнейший append не будет перезаписывать элементы исходного слайса за пределами [low:high]. Используется для "отрезания" слайса без общей ёмкости с хвостом.
Если сохранить "хвост" большого слайса tail := big[1000:], в tail хранится указатель на тот же массив. Весь массив не будет собран GC, пока доступен tail, даже если первые 1000 элементов больше не нужны. Решение: скопировать нужное в новый слайс или использовать slices.Clip (Go 1.21+), чтобы уменьшить cap.
slices.Clip(s) (Go 1.21+) уменьшает cap до len, отвязывая срез от "лишнего" хвоста базового массива - помогает избежать утечек. slices.Grow(s, n) возвращает слайс с гарантированной дополнительной ёмкостью для n элементов (для последующего append без реаллокации).
slices.Equal(a, b) (Go 1.21+) сравнивает элементы поэлементно; для сравнимых типов - то, что нужно. Для своих типов - slices.EqualFunc. Раньше использовали reflect.DeepEqual (медленнее и сравнивает "глубоко", в том числе типы).
sort.Slice(s, func(i, j int) bool { return s[i].Field < s[j].Field }). Стабильная сортировка: sort.SliceStable. В Go 1.21+: slices.SortFunc(s, func(a, b T) int { return cmp.Compare(a.Field, b.Field) }) с пакетом cmp.
Нет. Nil - нулевое значение типа слайса (указатель nil). Пустой - len 0, но ненулевой указатель (например, []int{}). Поведение при len, range, append одинаковое. Отличие: сериализация (nil -> null), сравнение с nil, иногда проверки в коде.
Встроенная функция clear(s) обнуляет все элементы слайса в диапазоне [0:len(s)] (zero value для типа элемента). Cap не меняется. Для слайса указателей полезно обнулить элементы, чтобы GC мог собрать данные. Для map clear(m) удаляет все пары.