Шаг 45.
Язык программирования Go.
Срезы

    На этом шаге рассмотрим срезы в Go.

    Срезы в языке Go более гибкие, более мощные и более удобные, чем массивы. Массивы передаются по значению (то есть копируются), хотя этого можно избежать с помощью указателей, тогда как срезы всегда передаются по ссылке, независимо от их длины и емкости. Массивы имеют фиксированный размер, тогда как размеры срезов могут изменяться. Все функции в стандартной библиотеке Go, образующие общедоступный API, используют срезы, а не массивы. Если нет каких-то определенных требований, всегда рекомендуется использовать срезы. И массивы, и срезы поддерживают операцию извлечения среза с использованием синтаксиса, представленного в табл. 1.

Таблица 1. Операции, поддерживаемые срезами
Операция Описание/результат
s[n] Элемент в позиции n в срезе s
s[n:m] Срез из среза s, начиная с элемента в позиции n и до элемента в позиции m-1 включительно
s[n:] Срез из среза s, начиная с элемента в позиции n и до последнего элемента в позиции len(s) - 1 включительно
s[:m] Срез из среза s, начиная с элемента в позиции 0 и до элемента в позиции m-1 включительно
s[:] Срез из среза s, начиная с элемента в позиции 0 и до последнего элемента в позиции len(s) - 1 включительно
cap(s) Емкость среза s; всегда больше или равна len(s)
len(s) Количество элементов в срезе s; всегда меньше или равно cap(s)
s = s[:cap(s)] Увеличит длину среза s до его емкости, если они не равны

    Срезы – это последовательности элементов одного типа фиксированной емкости и переменной длины. Несмотря на фиксированную емкость, срезы могут укорачиваться путем их усечения и удлиняться с помощью эффективной встроенной функции append(). Многомерные срезы можно создавать,используя элементы, которые сами являются срезами, при этом длины внутренних срезов в многомерном срезе могут быть разными.

    Несмотря на требование принадлежности элементов массивов и срезов к одному типу, на практике никаких ограничений не существует. Это обусловлено тем, что роль типа здесь может играть интерфейс. Поэтому элементы могут быть любого типа, при условии что все они поддерживают указанный интерфейс (то есть имеют метод или методы, определяемые интерфейсом). Можно даже создать массив или срез с типом пустого интерфейса, interface{} , и хранить в нем элементы любых типов, однако при обращении к ним придется использовать инструкции приведения типа и выбора по типу или механизм интроспекции, чтобы обеспечить корректное их использование.

    Для создания срезов используется следующий синтаксис:

make([]Тип, длина, емкость)
make([]Тип, длина)
[]Тип {}
[]Тип {значение1, значение2, ..., значениеN} 

    Встроенная функция make() используется для создания срезов, отображений и каналов. Когда она применяется для создания среза, то создает скрытый массив с элементами, инициализированными нулевыми значениями , и возвращает ссылку на срез, ссылающуюся на скрытый массив. Скрытый массив, как и все массивы в языке Go, имеет фиксированную длину, равную емкости среза, если для его создания использован первый способ, или длине, если использован второй способ, или числу элементов в фигурных скобках, если использован составной литерал (третий и четвертый способы).

    Емкость среза – это длина скрытого массива, а длина среза – любое количество элементов от нуля до значения емкости. При создании среза первым способом указанная длина должна быть меньше или равна емкости, хотя обычно этот способ используется, когда необходимо, чтобы первоначальная длина среза была меньше его емкости. Второй, третий и четвертый способы используются, когда желательно, чтобы длина совпадала с емкостью. Способ на основе составного литерала (четвертый) удобно использовать для создания среза с некоторыми начальными значениями.

    Синтаксическая конструкция []Тип{} эквивалентна вызову функции make([]Тип, 0) – обе создают пустой срез. Этот способ создания срезов не является бесполезным благодаря возможности использовать встроенную функцию append() для увеличения емкости среза. Однако на практике, когда требуется пустой срез, почти всегда лучше создавать его вызовом функции make(), указывая нулевую длину и ненулевую емкость, примерно равную ожидаемому числу элементов.

    Допустимыми значениями индексов срезов являются целые числа в диапазоне от 0 до len(срез) - 1. Длину среза можно уменьшить операцией усечения, а если емкость среза больше его длины, длину можно увеличить до значения емкости. Имеется также возможность увеличивать емкость срезов с помощью встроенной функции append().

    На рис. 1 приводится концептуальное представление взаимосвязи между срезами и их скрытыми массивами.


Рис.1. Взаимосвязи между срезами и их скрытыми массивами

    На рисунке изображен следующий срез:

s := []string{"A", "B", "C", "D", "E", "F", "G"}
t := s[:5] // [A B C D E]
u := s[3 : len(s)-1] // [D E F]
fmt.Println(s, t, u)
u[1] = "x"
fmt.Println(s, t, u)
//[A B C D E F G] [A B C D E] [D E F]
//[A B C D x F G] [A B C D x] [D x F] 

    Поскольку все переменные s, t и u ссылаются на одни и те же данные в памяти, изменение одной влечет изменение всех остальных, ссылающихся на те же данные.

s := new([7]string)[:]
s[0], s[1], s[2], s[3], s[4], s[5], s[6] = "A", "B", "C", "D", "E", "F", "G"

    Лучшими способами создания срезов являются встроенная функция make() и синтаксис составных литералов, а здесь показан способ, который не используется на практике, но проясняет связь между массивами и срезами. Первая инструкция создает указатель на массив вызовом встроенной функции new() и тут же извлекает срез, включающий весь массив. В результате создается срез, длина и емкость которого равны длине массива и каждому элементу присвоено нулевое значение – в данном случае пустая строка. Вторая инструкция завершает создание среза, заполняя элементы желаемыми начальными значениями, после чего данный срез s становится точно таким же, как созданный с помощью синтаксиса составных литералов в предыдущем фрагменте.

    На следующем шаге рассмотрим примеры двумерных массива и среза в Go.


Предыдущий шаг Содержание Следующий шаг