Введение в срезы

1. Чем срез отличается от массива

  • Массив: фиксированная длина, размер входит в тип.
    var arr [3]int  // всегда длина 3, [0 0 0]
                    // массив нельзя сравнить с nil
    
  • Срез: динамическая структура поверх массива.
    var s []int           // nil‑срез, длина 0, cap 0
    fmt.Println(s)        // []
    fmt.Println(s == nil) // true
    

Ключевое: у массива всегда есть память под все элементы; у среза по умолчанию — только сама «обёртка», без выделенного массива под данные.


2. Внутреннее устройство среза

Срез состоит из трёх частей:

  • указатель на базовый массив (pointer)
  • длина (len) — сколько элементов «видит» срез
  • вместимость (cap) — сколько максимум элементов можно положить, не перевыделяя память

Логически:

slice → (ptr, len, cap) → [элементы массива]

3. Создание и инициализация

Основные варианты:

// nil-срез
var s1 []int          // nil, len=0, cap=0

// литерал среза
s2 := []int{1, 2, 3}  // len=3, cap=3

// через make
s3 := make([]int, 0)  // len=0, cap=0, но не nil
s4 := make([]int, 5)  // len=5, cap=5, значения = 0

Массив vs срез по виду:

var a [3]int   // массив: [3]int
var s []int    // срез:  []int

4. Копирование и «ссылочность»

  • Массив при присваивании копируется целиком (deep copy).
  • Срез — только структура, включая указатель (shallow copy).
s1 := []int{1, 2, 3}
s2 := s1          // оба смотрят на один базовый массив

s2[1] = 100
fmt.Println(s1)   // [1 100 3]
fmt.Println(s2)   // [1 100 3]

Изменения через один срез видны через другой, пока они ссылаются на один и тот же базовый массив.


5. Добавление элементов: append

s := []int{1, 2, 3}
s = append(s, 999)      // всегда присваиваем результат
fmt.Println(s)          // [1 2 3 999]
  • При нехватке cap под капотом создаётся новый массив, данные копируются, указатель в срезе меняется.
  • Поэтому после append нужно делать присваивание обратно в переменную.

6. Нарезка (slicing)

Срез по диапазону индексов исходного среза/массива:

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

a := s[:2]    // [1 2]
b := s[2:]    // [3 4 5]
c := s[1:4]   // [2 3 4]
d := s[:]     // полное «окно» на тот же базовый массив

Правило индексов: [start:end]start включительно, end не включительно.


7. Удаление элемента через append

Специального метода remove нет, используем комбинацию slicing + append:

s := []int{1, 2, 3, 4, 5}
idx := 2 // хотим удалить элемент с индексом 2 (значение 3)

s = append(s[:idx], s[idx+1:]...)
fmt.Println(s)  // [1 2 4 5]

Идея: берём всё до удаляемого и всё после него, склеиваем в один срез.


8. Главное, что нужно запомнить

  • Срезы — динамическая обёртка над массивом: (ptr, len, cap).
  • Это ссылочный тип: при присваивании копируется указатель, а не данные.
  • Создаём срезы через []T{}, make([]T, n) или нарезкой.
  • Элементы добавляем через append, результат всегда присваиваем.
  • Удаление — через append(s[:i], s[i+1:]...).

Если нужны динамические коллекции — выбираем срезы, а не массивы.