Коллекции: массивы, срезы и отображения

Массивы и срезы: создание, индексирование, len, cap, append и range

Содержание курса

До этого урока вы работали с отдельными переменными и функциями, которые их принимают. Как только данных становится больше одного — нужна коллекция. Go предлагает два инструмента: массив с фиксированной длиной и срез, который умеет расти. Они похожи внешне, но устроены по-разному, и путать их дорого.

Массив [N]T: тип, литерал, индексирование, len и cap

В Go тип массива записывается как [N]T, где N — числовая константа, а T — тип элементов. Важная деталь: N — это часть самого типа. [3]int и [5]int — два разных типа, точно так же как int и string. Присвоить одно другому или передать в функцию, ожидающую [3]int, массив [5]int — ошибка компиляции.

Создаётся массив литералом:

a := [3]int{10, 20, 30}

Здесь a имеет тип [3]int. Элементы нумеруются с нуля, последний доступный индекс — N−1, то есть для [3]int это индексы 0, 1, 2.

Чтение и запись — стандартные:

fmt.Println(a[0]) // 10
a[2] = 99
fmt.Println(a[2]) // 99

Что произойдёт, если выйти за границу? Здесь важно различать два случая.

Константный индекс — компилятор знает значение во время сборки и отклоняет программу сразу:

fmt.Println(a[3]) // ошибка компиляции: invalid argument: index 3 out of bounds [0:3]

Эта программа не соберётся вообще.

Переменный индекс — компилятор не может проверить его заранее, поэтому проверка переносится в момент выполнения. Если значение окажется вне диапазона, возникает panic:

i := 3
fmt.Println(a[i]) // runtime panic: index out of range [3] with length 3

Программа скомпилируется, но упадёт при запуске. Это не исключение, которое можно перехватить в обычном коде, — выполнение прекращается.

len(a) возвращает 3. cap(a) тоже возвращает 3 — у массива нет отдельной ёмкости, она совпадает с длиной и зафиксирована навсегда. Это принципиальное отличие от среза.

По синтаксису типа легко отличить массив от среза: [3]int — массив, []int — срез. Квадратные скобки без числа означают срез.