Слайсы
Раз длина массива — это часть его типа, то массивы не подходят для хранения коллекций данных динамического размера. Эту задачу решает другой тип данных, который гораздо чаще используется на практике, — слайс (от англ. slice), или срез. Кроме того, слайс избавит нас от проблемы копирования массивов для присваивания.
Слайс — это последовательность переменной длины, состоящая из элементов одного типа. Тип слайса записывается как тип массива без указания размера. Можно инициализировать переменную типа «слайс» значениями, но, в отличие от массива, переменная без инициализации равна nil
.
Слайс очень похож на list
в языке Python, но имеет свои особенности.
Слайс — это обёртка над указателем массива, и в Go слайс используется как структура следующего вида:
- указатель на первый элемент базового массива —
ptr
; - длина слайса —
len
, количество элементов в слайсе; - ёмкость слайса —
cap
, количество элементов в массиве.
Параметры слайса len
и cap
могут быть получены с помощью вызова соответствующих встроенных функций len()
и cap()
.
Если просто объявить такую структуру, то она будет равна nil
.
Для создания слайса используется встроенная функция make()
:
Аргументы функции make
:
- Тип слайса (пустые квадратные скобки и тип элемента слайса).
- Длина слайса. Если не передана, то по умолчанию равна нулю.
- Ёмкость слайса — размер базового массива. Если значение не передано, то по умолчанию равна длине слайса.
Функция make
создаёт массив длиной cap
и записывает указатель на него в структуру слайса. Также она заполняет поля len
и cap
в этой структуре и возвращает её в виде переменной типа «слайс».
Даже если len
и cap
переданы как нулевые, сама структура уже не будет равна nil
. Она будет выделена в памяти, и указатель на базовый массив получит значение, отличное от nil
.
Если передать в функцию make
параметр cap
меньше len
, то будет вызвана ошибка компиляции или паника во время исполнения.
Слайс может быть создан из композитного литерала так же, как и массив. Единственное отличие — не указываем размер массива:
Длина и ёмкость слайса будут равны композитному литералу.
Новый слайс может быть создан на основе уже существующего слайса или массива. Для этого используется операция взятия слайса. Она выполняется с помощью двух скобок с двоеточием [i:j]
, где i
— индекс первого элемента нового слайса, а j
— индекс следующего элемента, не входящего в новый слайс.
Допускается не указывать i
и j
— в таком случае i
по умолчанию будет равен 0, j
— равен длине массива или слайса.
Таким образом, [:]
вернёт слайс всего массива, [:k]
— от начала и до k-го элемента, [k:]
— от k-го элемента до конца массива.
i
и j
должны быть неотрицательны и не больше len
, причем i
меньше или равно j
. Если эти условия не будут выполнены, возникнет ошибка компиляции или паника.
Рассмотрим пример с массивом среднесуточных температур из раздела про массивы.
Изменение размеров слайса
Как вы уже знаете, слайс — последовательность элементов динамического размера.
Уменьшение размера слайса производится через операцию взятия слайса. Результат взятия можно присвоить этому же слайсу:
Ёмкость массива при этом не изменяется.
Для добавления элементов к слайсу используется встроенная функция append
. Она принимает переменную типа «слайс» и одну или несколько переменных типа элемента слайса, затем возвращает новый слайс, который состоит из копии переданного слайса и переданных в него элементов.
‼️
Внимание:
append
не изменяет переданный ей слайс, а создаёт новый на основе переданного.
Рассмотрим пример:
Слайс a
изменился после выполнения первого append
, так как слайс b
хоть и увеличил свою длину, но не вышел за пределы базового массива.
Дело в механике append
: если ёмкость слайса позволяет разместить добавляемые элементы (то есть разница между длиной слайса и его ёмкостью больше или равна количеству размещаемых элементов), то append
возвращает новый слайс, который получен из существующего слайса добавлением новых элементов внутри базового массива.
Если же ёмкость слайса не позволяет разместить эти элементы, то создаётся новый базовый массив подходящего размера, в него копируются все элементы переданного слайса и добавляются новые. Именно поэтому в примере после второго append
слайс b
ссылается на новый базовый массив. Разница между ёмкостью нового базового массива и его длиной зависит от количества элементов. В примере длина базового массива для b
равна 5, а ёмкость — 6.
Для 1000 элементов разница будет другой:
Такой алгоритм работы применяется не только в Go, но и в Python, С++ и многих других языках программирования. Это связано с тем, что выделение нового участка памяти во время работы программы — достаточно дорогостоящая операция с точки зрения затрат вычислительных ресурсов, и выгоднее держать немного памяти про запас. Ёмкость базового массива как раз и задаёт такой запас.
Чтобы соединить два слайса, нужно распаковать слайс append(a,b...)
. Функция принимает некоторое количество отдельных элементов и преобразует слайс в список через распаковку.
Рассмотрим ещё несколько примеров.
Так этот процесс выглядит на картинке:
Здесь не очень понятно, будут ли новые слайсы ссылаться на тот же базовый массив или отправят свои копии в новый массив. Поэтому на практике функцию append
рекомендуют лишь для присвоения слайса самому себе: s = append(s, b)
.
Note
Чтобы использовать слайсы, нужно понимать механизм их работы. В противном случае вы будете получать ошибки, которые очень трудно найти. Старайтесь избегать ситуаций, когда на базовый массив ссылается несколько слайсов и происходит добавление или изменение элементов.
Операция взятия слайса поддерживает и третий параметр: [low:high:max]
— третьим параметром указывается ёмкость базового массива, необходимая для создания нового слайса. При этом max
должна быть меньше или равна ёмкости базового массива или слайса. Вряд ли вы встретите аналогичный пример на практике, но знать про него будет полезно.
Присваивание слайса и передача в функции
Присвоение друг другу переменных слайса даже самого внушительного размера не потребляет больших вычислительных мощностей, потому что сама по себе структура слайса всегда содержит всего три поля: ptr
, len
и cap
. Однако надо держать в уме, что эти переменные ссылаются на один и тот же массив, поэтому изменение в данных одного слайса может повлечь за собой изменение другого.
При передаче слайса в аргументы функции структура слайса копируется в локальную переменную внутри функции. Это позволяет изменить данные внутри слайса, переданного в функцию. Но если нужно добавить или удалить элементы из слайса, то эти изменения затронут только локальную переменную слайса.
Для примера возьмём пару функций из стандартной библиотеки:
Функция sort.Ints
сортирует полученный слайс целых чисел по возрастанию. Она не меняет размер и ёмкость слайса, поэтому может спокойно работать с ним.
Функция bytes.TrimSpace
принимает слайс байт и возвращает новый слайс байт, откуда были удалены начальные и конечные пробельные символы. Размер слайса должен измениться, а значит, bSlice
останется нетронутым. В итоге bytes.TrimSpace
подарит нам новый слайс.
Задание для самопроверки
Подумайте, можно ли реализовать функцию, которая всё же будет изменять размер переданного ей слайса?
Ответ: Можно использовать указатель на слайс, то есть тип аргумента должен быть, например, таким: *[]int
.
Копирование слайсов
Для копирования элементов из одного слайса в другой применяется функция copy([]T dest, []T src)
, где dest
— это слайс-приёмник, а src
— слайс-источник. Эта функция только перезаписывает элементы, поэтому количество скопированных элементов будет равно меньшей длине из двух слайсов.
Обход слайсов и доступ к элементам
Обход слайсов и доступ к элементам слайса происходят точно так же, как и для массивов. Чтобы добраться до элементов по индексу, используются квадратные скобки []
, циклы for
и for range
.
Полезные приёмы для работы со слайсами
В отличие от других языков программирования, Go не щеголяет обилием функций для работы со слайсами. Гоферы выкрутились и придумали несколько приёмов, которые позволяют решать частые задачи.
Удаление последнего элемента слайса:
Удаление первого элемента слайса:
Удаление элемента слайса с индексом i
:
Сравнение двух слайсов:
📂 Go | Последнее изменение: 18.08.2024 09:22