Cрезы (slices) с нуля
В теме слайсов мы разберем:
-
что такое слайсы, их наполнение, емкость и заголовок;
-
как передаются слайсы;
-
встроенные функции make и copy, а также механизм append;
-
что такое nil слайс.
Полшага назад или как из-за массивов появились слайсы
Одной из наиболее распространенных особенностей процедурных языков программирования является понятие массива. Массивы кажутся простыми вещами, но есть много вопросов, на которые необходимо ответить при их добавлении в язык, например:
-
Какой размер: фиксированный или переменный?
-
Является ли размер частью типа?
-
Как выглядят многомерные массивы?
-
Имеет ли смысл пустой массив?
Ответы на эти вопросы влияют на то, являются ли массивы просто особенностью языка или его основной частью.
В начале разработки Go требовалось около года, чтобы найти ответы на эти вопросы, прежде чем дизайн стал правильным. Ключевым шагом стало введение срезов, которые основывались на массивах фиксированного размера для создания гибкой и расширяемой структуры данных. Однако по сей день программисты, плохо знакомые с Go, часто спотыкаются о то, как работают срезы. Возможно, так происходит, потому что опыт работы с другими языками повлиял на их мышление.
В этой и следующих главах мы постараемся избавиться от путаницы. Для этого мы создадим слайсы, чтобы объяснить, как работает встроенная функция append и почему именно так.
Далее по тексту слайс, срез, slice — это названия одной сущности динамического массива в Go.
Срез или slice
Массивы занимают особое место в Go, но они негибкие, поэтому ты не часто увидишь их использование в коде Go. Слайсы используются повсеместно — это списки товаров, пользователей, объявлений и т. д..
Тип среза — это абстракция, построенная поверх типа массива Go.
Тип слайса в Go отличается от своего аналога массива двумя важными особенностями:
-
Срезы не имеют фиксированной длины. Длина слайса не объявляется как часть его типа, а хранится внутри самого слайса и может быть восстановлена с помощью встроенной функции len(slice).
-
Присвоение одной переменной слайса другой не создает копию содержимого слайса. Это связано с тем, что слайс напрямую не содержит своего содержимого, произойдет копирование структуры заголовка слайса. Вместо этого срез содержит указатель на его базовый массив, который содержит содержимое слайса.
Благодаря второму свойству два среза могут совместно использовать один и тот же базовый массив. Рассмотрим эти примеры.
- Нарезка слайса.
Самое время протестировать код!
В этом примере используется срез a и b, один и тот же базовый массив, хотя b начинается с другого смещения в этом массиве и имеет другую длину. Таким образом, изменения в базовом слайсе через b видимы для слайса a.
- Передача среза в функцию.
Самое время протестировать код!
В этом примере a передается аргументом в функцию negate. Функция negate перебирает элементы слайса s, инвертируя их знак. Несмотря на то что negate не возвращает значение или не имеет никакого доступа к объявлению a в main, содержимое a изменяется при передаче в функцию negate.
У большинства программистов есть интуитивное понимание того, как работает базовый массив слайса Go, потому что он соответствует тому, как работают концепции, подобные массивам, в других языках. Например, вот первый пример этого раздела, переписанный на Python:
А также в Руби:
То же самое относится к большинству языков, которые обрабатывают массивы как объекты или ссылочные типы.