В чем ключевые различия между массивом (array) и срезом (slice) в Go?

Ответ

Массив и срез — это базовые структуры для работы с последовательностями данных в Go, но они имеют фундаментальные различия.

Массив (Array)

  • Фиксированный размер: Размер массива определяется на этапе компиляции и является частью его типа. Например, [3]int и [5]int — это два совершенно разных, несовместимых типа.
  • Передача по значению: При передаче в функцию массив всегда копируется целиком. Это может быть неэффективно для больших массивов.
  • Хранение: Часто размещается на стеке, если размер позволяет, что обеспечивает быстрый доступ.

Срез (Slice)

  • Динамический размер: Срез — это легковесная обертка над массивом, размер которой может меняться во время выполнения с помощью функции append.
  • Внутренняя структура: Срез представляет собой заголовок (header), который содержит три поля:
    1. Указатель на первый элемент базового массива.
    2. Длина (len) — количество элементов в срезе.
    3. Ёмкость (cap) — количество элементов в базовом массиве от указателя до конца массива.
  • Передача по ссылке (по поведению): В функцию передается копия заголовка среза. Так как заголовок содержит указатель на тот же базовый массив, изменения элементов среза внутри функции будут видны снаружи. Однако, если append вызовет переаллокацию (создание нового массива), связь с оригинальным срезом будет потеряна, поэтому append всегда нужно использовать с присваиванием: slice = append(slice, ...).

Сводная таблица отличий:

ХарактеристикаМассив ([N]T)Срез ([]T)
РазмерФиксированный, часть типаДинамический, может меняться
Передача в функциюПо значению (полное копирование)По значению (копируется заголовок), ведет себя как по ссылке
ГибкостьНизкаяВысокая (операции append, copy, срезы)
Нулевое значениеМассив, заполненный нулевыми значениями типаnil (указатель nil, len=0, cap=0)

Пример:

// Массив (фиксированный размер, тип [3]int)
arr := [3]int{1, 2, 3}

// Срез (динамический размер, тип []int)
slice := []int{1, 2, 3}
slice = append(slice, 4) // Размер изменился

// Срез, созданный из массива
sliceFromArr := arr[1:3] // len=2, cap=2

Вывод: В 99% случаев в Go для работы с коллекциями следует использовать срезы из-за их гибкости и удобства.