Есть ли в Go динамические массивы и как они работают?

Ответ

Прямого аналога с названием «динамический массив» в Go нет. Эту роль выполняют срезы (slices), которые являются гибкой и мощной абстракцией над обычными массивами.

Как срез работает под капотом?

Срез — это легковесная структура (заголовок среза), содержащая три поля:

  1. Указатель (pointer): Адрес первого элемента в базовом (скрытом) массиве.
  2. Длина (length, len): Количество элементов в срезе.
  3. Ёмкость (capacity, cap): Количество элементов в базовом массиве, начиная от указателя среза.

Именно связка len и cap обеспечивает "динамическое" поведение.

Динамический рост с помощью append

Когда вы добавляете элементы с помощью append:

  • Если len < cap: В базовом массиве еще есть место. Новый элемент просто записывается в следующую ячейку, и len среза увеличивается.
  • Если len == cap: Места нет. Go выделяет новый, больший массив, копирует в него все старые данные, добавляет новый элемент, и указатель в срезе обновляется, чтобы указывать на этот новый массив. Ёмкость (cap) также увеличивается (обычно удваивается для небольших срезов).

Примеры операций:

// Создание среза. len=3, cap=3
nums := []int{1, 2, 3}

// Добавление элемента. Происходит реаллокация.
// Новый базовый массив будет иметь cap, например, 6
nums = append(nums, 4) // [1, 2, 3, 4]

// Удаление элемента (например, по индексу 1)
// Реаллокации нет, используется тот же базовый массив
nums = append(nums[:1], nums[2:]...) // [1, 3, 4]

Ключевое отличие от массива (array)

  • Массив: [5]int — его длина 5 является частью типа. Размер фиксирован.
  • Срез: []int — его длина не является частью типа. Размер может меняться.