В чем разница слайсов и массивов

В языке Go массивы и слайсы представляют собой структуры данных, предназначенные для хранения последовательностей элементов одного типа. Однако, между ними существует принципиальное различие в размере, передаче по значению, гибкости, внутреннем устройстве и практическом применении.

1. Определение и синтаксис

Массив (Array)

Массив — это фиксированная последовательность элементов одного типа, определяемая длиной на момент объявления.

var arr \[3\]int = \[3\]int{1, 2, 3}

Или сокращённо:

arr := \[3\]int{1, 2, 3}

Длина массива — часть его типа. Массивы [3]int и [4]int — разные типы.

Слайс (Slice)

Слайс — это гибкая, динамическая обёртка над массивом. Он не хранит данные сам по себе, а ссылается на массив.

slice := \[\]int{1, 2, 3}
Или из массива:
arr := \[5\]int{10, 20, 30, 40, 50}
slice := arr\[1:4\] // элементы 20, 30, 40

2. Фиксированность длины

  • Массив имеет жёстко заданную длину.

    • Изменить размер нельзя.

    • Размер входит в тип.

  • Слайс может изменять длину (через append) в пределах вместимости (capacity).

arr := \[3\]int{1, 2, 3}
// arr = append(arr, 4) // ошибка
slice := \[\]int{1, 2, 3}
slice = append(slice, 4) // OK: \[1 2 3 4\]

3. Передача в функции (по значению / ссылке)

Массив — по значению

При передаче массива в функцию — создаётся копия:

func modifyArray(a \[3\]int) {
a\[0\] = 100
}
arr := \[3\]int{1, 2, 3}
modifyArray(arr)
fmt.Println(arr) // \[1 2 3\]

Массив не изменится.

Слайс — по значению, но содержит ссылку на массив

При передаче слайса копируется только структура: указатель, длина, вместимость. Данные — общие.

func modifySlice(s \[\]int) {
s\[0\] = 100
}
slice := \[\]int{1, 2, 3}
modifySlice(slice)
fmt.Println(slice) // \[100 2 3\]

Слайс указывает на общий массив — изменения видны.

4. Тип данных

  • [3]int — это конкретный тип массива из 3 int.

  • []int — это тип слайса, без фиксированной длины.

func takesArray(a \[3\]int) {}
func takesSlice(s \[\]int) {}
takesArray(\[3\]int{1, 2, 3}) // OK
// takesArray(\[\]int{1, 2, 3}) // ошибка
takesSlice(\[\]int{1, 2, 3}) // OK

5. Внутреннее устройство

Массив:

  • Представляет собой непрерывный блок памяти фиксированной длины.

  • Сам содержит все элементы.

Слайс:

Слайс — это структура из 3 полей:

type slice struct {
data \*T // указатель на первый элемент массива
len int // длина (доступное количество элементов)
cap int // вместимость (размер массива)
}

6. Инициализация

Массив:

arr := \[5\]int{} // все значения 0

Можно указать значения не по порядку:

arr := \[5\]int{2: 10, 4: 20} // \[0 0 10 0 20\]

Слайс:

slice := make(\[\]int, 5) // длина 5, заполнено нулями
slice2 := make(\[\]int, 2, 5) // длина 2, capacity 5

7. Операции и функции

len() и cap()

arr := \[4\]int{1, 2, 3, 4}
fmt.Println(len(arr)) // 4
slice := arr\[1:3\]
fmt.Println(len(slice)) // 2
fmt.Println(cap(slice)) // 3  от среза до конца массива

append() — работает только со слайсами

s := \[\]int{1, 2}
s = append(s, 3, 4)

При превышении cap, создаётся новый массив.

8. Изменение данных

Слайс может изменить элементы массива:

arr := \[4\]int{1, 2, 3, 4}
s := arr\[1:3\] // \[2, 3\]
s\[0\] = 200
fmt.Println(arr) // \[1 200 3 4\]

Слайс не копирует элементы — это представление массива.

9. Массивы в структурах и интерфейсах

  • Массивы в Go не так часто используются напрямую из-за своей негибкости.

  • Их можно использовать как поля структур, например, [16]byte в net.IP.

type UUID \[16\]byte
  • Слайсы же чаще применяются при работе с данными переменной длины — строки, буферы, коллекции и т.д.

10. Сравнение массивов и слайсов

Характеристика Массив [N]T Слайс []T
Длина Фиксированная Изменяемая (через append)
--- --- ---
Типизация Часть типа Тип не включает длину
--- --- ---
Передача в функцию Полная копия Копия структуры, но общие данные
--- --- ---
Возможность расширения Нет Да
--- --- ---
Использование памяти Хранит данные напрямую Ссылка на массив
--- --- ---
Удобство Менее гибкий Гибкий и универсальный
--- --- ---
Совместимость Только точное совпадение Совместим с любым слайсом того же типа
--- --- ---
Функция append Неприменим Применим
--- --- ---
Методы len, cap Да Да
--- --- ---

11. Преобразование между массивами и слайсами

Из массива в слайс:

arr := \[5\]int{1, 2, 3, 4, 5}
s := arr\[1:4\] // \[2 3 4\]

Из слайса в массив:

s := \[\]int{1, 2, 3}
var a \[3\]int
copy(a\[:\], s) // копирует первые 3 элемента

Нельзя напрямую присвоить слайс массиву, потому что у них разные типы.

12. Сравнение значений

Массивы можно сравнивать напрямую (если элементы сравнимы):

a1 := \[3\]int{1, 2, 3}
a2 := \[3\]int{1, 2, 3}
fmt.Println(a1 == a2) // true

Слайсы нельзя сравнивать напрямую (кроме nil):

s1 := \[\]int{1, 2, 3}
s2 := \[\]int{1, 2, 3}
// fmt.Println(s1 == s2) // ошибка компиляции

Для сравнения слайсов нужно использовать reflect.DeepEqual или ручную проверку.

13. Срезы слайсов

Можно делать срезы от слайсов:

s := \[\]int{1, 2, 3, 4, 5}
sub := s\[1:4\] // \[2, 3, 4\]

Это создаёт новый слайс, указывающий на ту же память.

14. Nil-слайс и пустой слайс

var s1 \[\]int // nil-слайс
s2 := \[\]int{} // пустой слайс
fmt.Println(s1 == nil) // true
fmt.Println(s2 == nil) // false

Оба считаются пустыми (len == 0), но ведут себя немного по-разному в JSON, API и т.д.

15. Память и управление capacity

При добавлении элементов через append, если вместимость недостаточна — создаётся новый массив с увеличенным cap. Стратегия роста — обычно удвоение.

s := \[\]int{1, 2, 3}
fmt.Println(cap(s)) // 3
s = append(s, 4) // создаётся новый массив
fmt.Println(cap(s)) // обычно 6

Если важно управлять этим вручную — используйте make([]T, len, cap).

16. Применение на практике

  • Массивы — редко используются явно. Применяются в системных библиотеках, криптографии, IP/UUID.

  • Слайсы — основной способ хранения коллекций в Go:

    • динамические массивы,

    • очереди/стеки,

    • буферы,

    • JSON-структуры,

    • строки по символам (через []rune или []byte).