Передача слайсов в функции
Важно понимать, что даже если срез в своем заголовке содержит указатель на данные — поле Data — он сам по себе является значением. На самом деле это значение структуры, содержащее указатель (поле data), вместимость (поле cap) и длину (поле len). Это не указатель на структуру.
Когда мы передаем слайс в функцию, в ней будет своя копия, версия структуры заголовка слайса. Такое поведение имеет важные последствия. Также из глав про структуру вспомним, что у структуры есть свой размер. В нашем случае структура занимает 24 байта, то есть при передаче слайса в функцию в качестве аргумента скопируются все 24 байта памяти. Рассмотрим функцию:
Эта функция перебирает все элементы массива, увеличивая значения на единицу, через оператор инкремента ++.
Самое время протестировать код!
Несмотря на то, что структура заголовка слайса передается по значению, заголовок включает в себя указатель (поле data) на элементы массива, поэтому и исходный заголовок слайса, и копия заголовка, переданного в функцию, описывают один и тот же массив. Поэтому, когда функция осуществляет возврат (return), измененные элементы можно увидеть через исходную переменную среза. Также проблему можно решить передачей слайса по ссылке, в этом случае будет ссылка на один и тот же SliceHeader.
Встроенная функция make
Что, если мы хотим расширить срез сверх его возможностей, выйдя за рамки capacity? По определению capacity является пределом слайса. Но ты можешь добиться эквивалентного результата, выделив новый массив, скопировав данные и изменив срез для описания нового массива.
Начнем с выделения. Мы могли бы использовать встроенную функцию new для выделения массива большего размера, а затем нарезать результат.
Самое время протестировать код!
Но вместо этого проще использовать встроенную функцию make. Она выделяет новый массив и создает заголовок среза для его описания сразу. Функция make принимает три аргумента: тип среза, его начальную длину и его емкость — то есть длину массива, выделяемого для хранения данных среза. Этот вызов создает срез длиной 10 с местом для еще 5 (15–10), как ты можешь видеть, запустив его.
Самое время протестировать код!
Этот фрагмент удваивает объем нашего []int, но сохраняет его длину прежней.
Самое время протестировать код!
После выполнения этого кода у среза остается гораздо больше места для роста, прежде чем потребуется еще одно перераспределение.
При создании слайсов часто бывает так, что длина и емкость оказываются одинаковыми. У встроенной функции make есть сокращение для этого распространенного случая. Аргумент длины по умолчанию равен емкости, поэтому ты можешь не указывать его, чтобы установить для них одно и то же значение.
Самое время протестировать код!
Длина gophers и емкость среза равны 10.
Встроенная функция copy
Когда мы удвоили емкость нашего слайса, мы написали цикл для копирования старых данных в новый слайс. В Go есть встроенная функция copy, которая упрощает эту задачу. Его аргументами являются два среза, и он копирует данные из правого аргумента в левый. На скриншоте показан пример, переписанный для использования copy.
Самое время протестировать код!
Функция copy умная. Она копирует только то, что может, обращая внимание на длину обоих аргументов. Количество копируемых элементов будет равно наименьшей из длин двух срезов. Это значительно увеличивает производительность кода. Также copy возвращает целочисленное значение, количество скопированных элементов, хотя это не всегда стоит проверять.
Функция copy делает все правильно, когда источник и место назначения перекрываются, что означает, что ее можно использовать для перемещения элементов в одном слайсе. Посмотри на скриншоте, как использовать copy для вставки значения в середину среза.
Самое время протестировать код!
В этой функции есть несколько замечаний. Во-первых, функция обязательно должна вернуть обновленный слайс, потому что его длина изменилась. Во-вторых, он использует удобное сокращение. Выражение slice[ i: ] эквивалентно записи slice[ i: len(slice) ]
Очень важно понимать строгость значений с и до. C определенного элемента означает включительно элемент с указанного индекса: slice[ 3: ] — слайс начинается с элемента 4 с индексом 3.
Пример до элемента с индексом 3, то есть не доходя до элемента 4 с индексом 3: slice[ :3 ]
Ещё раз: с какого-то элемента означает включительно, до элемента означает до какого-то элемента, не включая этот элемент .
Самое время протестировать код!
Таким образом slice[ : ] просто означает сам срез, что полезно при нарезке массива. Это выражение — самый короткий способ сказать «срез, описывающий все элементы массива».