Введение
В предыдущем разделе мы обсуждали массивы в Go. Однако у массивов есть ограничения: после объявления и инициализации их длину нельзя изменить. Поэтому массивы не находят широкого применения в повседневном программировании. Напротив, слайсы (срезы) используются гораздо чаще, так как они представляют собой более гибкую структуру данных.
Основные моменты:
- Определение слайса
- Инициализация слайса
- Операции со слайсами: добавление, удаление, изменение и поиск
- Расширение слайса
- Обрезка (truncation) слайса
- Многомерные слайсы
Что такое слайс
Слайсы похожи на массивы; это контейнеры, хранящие элементы одного типа данных. Однако массивы ограничены: их длина фиксирована после создания. Хотя у массивов есть свои области применения, им не хватает гибкости. Поэтому в повседневной разработке чаще используются слайсы.
В Go слайсы реализованы на базе массивов. Слайс — это, по сути, динамический массив, длина которого может меняться. Мы можем выполнять такие операции, как добавление, удаление, изменение и поиск элементов в слайсе, что невозможно сделать напрямую с массивом фиксированной длины.
Определение слайса
Синтаксис инициализации слайса очень похож на синтаксис массива. Основное отличие заключается в том, что длину указывать не нужно. Взгляните на следующий код:
// Объявление массива длиной 5
var a1 [5]byte
// Объявление слайса
var s1 []byte
Слайс как ссылка на массив
Если мы объявим массив типа int, значением по умолчанию для каждого элемента будет 0.
Однако, если мы объявим слайс, его значением по умолчанию будет nil. Давайте создадим файл slice.go, чтобы проверить это:
touch ~/project/slice.go
Введите следующий код:
package main
import "fmt"
func main() {
var a [3]int
var s []int
fmt.Println(a[0] == 0) // true
fmt.Println(s == nil) // true
}
go run slice.go
После запуска кода на экране появится следующий результат:
true
true
Мы создали массив a и слайс s. Мы сравнили первый элемент массива a с нулем и проверили, является ли слайс s равным nil.
Как мы видим, при объявлении слайса его начальное значение — nil. Это происходит потому, что слайсы сами по себе не хранят данные; они лишь ссылаются на массивы. Слайс указывает на структуру базового массива.
Структура данных слайса
Слайс — это составной тип данных, также известный как структура (struct). Это тип, состоящий из полей разных типов. Внутренняя структура слайса состоит из трех элементов: указателя, длины и емкости.
type slice struct {
elem *type
len int
cap int
}
Как упоминалось ранее, эта структура ссылается на базовый массив. Указатель elem указывает на первый элемент массива, а type — это тип элементов ссылаемого массива.
len и cap представляют длину и емкость слайса соответственно. Вы можете использовать функции len() и cap() для получения этих значений.
На следующем изображении показано, что слайс ссылается на базовый массив типа int, имеет длину 5 и емкость 5:

Когда вы определяете новый слайс, указатель elem инициализируется нулевым значением (т.е. nil). Концепция указателей будет представлена в последующих работах. Пока просто запомните, что указатель ссылается на адрес значения в памяти. На изображении выше указатель elem указывает на адрес первого элемента базового массива.
Операции со слайсами: добавление, удаление, изменение и поиск
Обрезка массивов или слайсов
Поскольку базовой структурой слайса является массив, мы можем извлечь определенную часть массива в качестве ссылки для слайса. Следующий фрагмент кода демонстрирует это:
package main
import "fmt"
func main() {
// Определяем целочисленный массив длиной 10
a := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// Объявляем пустой слайс
var s1 []int
fmt.Println("Slice s1 is empty:", s1 == nil)
// Используем обрезку массива для получения слайса
s1 = a[1:5]
s2 := a[2:5]
s3 := a[:]
fmt.Println("Slice s1, s2, and s3 are empty:", s1 == nil, s2 == nil, s3 == nil)
fmt.Println("Elements in array a:", a)
fmt.Println("Elements in slice s1:", s1)
fmt.Println("Elements in slice s2:", s2)
fmt.Println("Elements in slice s3:", s3)
}
Результат будет следующим:
Slice s1 is empty: true
Slice s1, s2, and s3 are empty: false false false
Elements in array a: [0 1 2 3 4 5 6 7 8 9]
Elements in slice s1: [1 2 3 4]
Elements in slice s2: [2 3 4]
Elements in slice s3: [0 1 2 3 4 5 6 7 8 9]
В этой программе мы сначала объявляем и инициализируем массив a, а затем используем обрезку массива, чтобы присвоить его часть пустому слайсу s1. Таким образом мы создаем новый слайс.
s1[1:5] означает создание слайса из массива. Диапазон слайса — от индекса 1 массива a до индекса 5, не включая пятый элемент.
Примечание: В языках программирования индекс первого элемента равен 0, а не 1. Соответственно, второй элемент массива имеет индекс 1.
Мы используем оператор := для прямого присваивания обрезанного массива слайсу s2. То же самое относится к s3, но там диапазон не указан, поэтому он обрезает все элементы массива.
Изображение ниже визуализирует процесс обрезки. Обратите внимание, что зеленая часть слайса представляет собой ссылку на синий массив. Другими словами, они оба используют один и тот же базовый массив a.

Синтаксис обрезки слайсов выглядит так:
[start:end]
Оба аргумента start и end являются необязательными. Если мы хотим получить все элементы массива, мы можем опустить оба аргумента. Это продемонстрировано в s3 := a[:] в предыдущей программе.
Если мы хотим извлечь все элементы после определенного индекса, можно опустить параметр end. Например, a1[3:] извлечет все элементы, начиная с индекса 3.
Чтобы извлечь все элементы до определенного индекса, можно опустить параметр start. Например, a1[:4] извлечет все элементы от индекса 0 до индекса 4, не включая элемент с индексом 4.
Помимо извлечения слайса из массива, мы также можем извлечь новый слайс из уже существующего. Операция идентична работе с массивами. Вот простой пример:
package main
import "fmt"
func main() {
// Определяем целочисленный массив длиной 10
a := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// Создаем начальный слайс s1
var s1 []int
s1 = a[1:7]
fmt.Printf("Slice s1: %d\tLength: %d\tCapacity: %d\n", s1, len(s1), cap(s1))
// Извлекаем новый слайс s2 из начального слайса s1
s2 := s1[2:4]
fmt.Printf("Slice s1: %d\tLength: %d\tCapacity: %d\n", s2, len(s2), cap(s2))
}
Результат будет следующим:
Slice s1: [1 2 3 4 5 6] Length: 6 Capacity: 9
Slice s1: [3 4] Length: 2 Capacity: 7
В этой программе мы получили слайс s1, обрезав массив a. Диапазон s1 — от индекса 1 до 7. С помощью := мы извлекли новый слайс s2 из s1. Поскольку обрезка s1 непрерывна, новый слайс s2 также будет непрерывным.
Мы замечаем, что емкость слайса меняется при его обрезке. Правила таковы:
Если мы обрезаем слайс с емкостью c, длина s[i:j] будет j-i, а емкость станет c-i.
Для s1 базовым массивом является a, и емкость a[1:7] составляет 9 (т.е. 10-1).
Для s2 базовый массив тот же, что и у s1. Поскольку на предыдущем шаге у обрезанной части была емкость 9, емкость s1[2:4] становится 7 (т.е. 9-2).
Изменения значений слайса одновременно влияют на значения элементов базового массива
Поскольку слайс не хранит данные, а лишь ссылается на массив, изменение значения в слайсе одновременно изменит значение в базовом массиве. Продемонстрируем это:
package main
import "fmt"
func main() {
// Определяем целочисленный массив длиной 10
a := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := a[2:5]
s2 := a[:]
fmt.Println("Before Modification: ")
fmt.Println("Elements in array a: ", a)
fmt.Println("Elements in array a: ", s1)
fmt.Println("Elements in array a: ", s2)
// Изменяем значение по индексу 2 слайса s1 на 23
s1[2] = 23
fmt.Println("After Modification: ")
fmt.Println("Elements in array a: ", a)
fmt.Println("Elements in array a: ", s1)
fmt.Println("Elements in array a: ", s2)
}
Результат будет следующим:
Before Modification:
Elements in array a: [0 1 2 3 4 5 6 7 8 9]
Elements in array a: [2 3 4]
Elements in array a: [0 1 2 3 4 5 6 7 8 9]
After Modification:
Elements in array a: [0 1 2 3 23 5 6 7 8 9]
Elements in array a: [2 3 23]
Elements in array a: [0 1 2 3 23 5 6 7 8 9]
В этой программе и слайс s1, и слайс s2 ссылаются на массив a. Когда в слайсе s1 значение по индексу 2 меняется на 23, значения в массиве a и слайсе s2 также обновляются.
Мы видим, что значение по индексу 4 массива изменено на 23 (потому что s1 := a[2:5] означает, что s1[0] ссылается на a[2], s1[1] на a[3], а s1[2] на a[4]). Это приводит к изменению значения по индексу 4 как в массиве a, так и в слайсе s2.
Это может вызвать трудноотлаживаемые ошибки при разработке программ. Поэтому в повседневном программировании следует по возможности избегать ситуации, когда несколько слайсов ссылаются на один и тот же базовый массив.
Добавление элементов в слайс
В этом разделе мы познакомимся с функцией append, которая используется для добавления элементов в слайс. Синтаксис следующий:
func append(slice []Type, elems ...Type) []Type
Первый аргумент — это сам слайс slice, а остальные аргументы — элементы, которые нужно добавить. []Type в конце указывает на то, что функция append вернет новый слайс того же типа данных, что и исходный.
elems после ... означает, что это вариативный параметр, то есть можно передать один или несколько аргументов.
Вот пример использования функции append:
package main
import "fmt"
func main() {
// Определяем целочисленный массив длиной 10
a := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := a[1:7]
fmt.Printf("Initial s1 value: %d\tLength: %d\tCapacity: %d\n", s1, len(s1), cap(s1))
s1 = append(s1, 12)
fmt.Printf("Modified s1 value: %d\tLength: %d\tCapacity: %d\n", s1, len(s1), cap(s1))
s1 = append(s1, 14, 14)
fmt.Printf("Modified s1 value: %d\tLength: %d\tCapacity: %d\n", s1, len(s1), cap(s1))
}
Результат будет следующим:
Initial s1 value: [1 2 3 4 5 6] Length: 6 Capacity: 9
Modified s1 value: [1 2 3 4 5 6 12] Length: 7 Capacity: 9
Modified s1 value: [1 2 3 4 5 6 12 14 14] Length: 9 Capacity: 9
В этой программе мы сначала создаем слайс s1, обрезая массив a. Диапазон s1 — от индекса 1 до 7. Мы используем оператор := для присвоения возвращаемого значения append переменной s1. При добавлении элементов с помощью append емкость слайса может измениться. Емкость s1 удвоится, если количество элементов превысит текущую емкость.
Правила расширения слайсов следующие:
- Если базовый массив слайса может вместить новые элементы, емкость слайса не изменится.
- Если базовый массив не может вместить новые элементы, Go создаст новый массив большего размера, скопирует в него значения из оригинального слайса и затем добавит новое значение.
Примечание: Расширение слайса не всегда означает удвоение; это зависит от размера элементов, их количества, аппаратного обеспечения компьютера и других факторов. Для получения дополнительной информации обратитесь к продвинутым разделам о слайсах.
Удаление элементов из слайса
Go не предоставляет специальных ключевых слов или функций для удаления элементов из слайса, но мы можем использовать обрезку массива для достижения этой цели или даже более сложных манипуляций.
Следующий код удаляет элемент с индексом 5 в слайсе s и присваивает результат новому слайсу s1:
package main
import "fmt"
func main() {
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// Печать слайса s перед удалением
fmt.Println(s)
s1 := append(s[:5], s[6:]...)
// Печать слайсов s и s1 после удаления элемента с индексом 5
fmt.Printf("%d\n%d\n", s, s1)
}
Результат будет следующим:
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 6 7 8 9 9]
[0 1 2 3 4 6 7 8 9]
Ключ к удалению элемента по конкретному индексу лежит в инструкции append. Она присоединяет элементы, идущие после индекса 6, к элементам, находящимся до индекса 5.
Эта операция эквивалентна перезаписи сдвигом. Например, элемент 5 перезаписывается элементом 6, элемент 6 — элементом 7 и так далее, до элемента 9, который добавляется в новый слайс. Поэтому значение базового массива в этот момент становится [0 1 2 3 4 6 7 8 9 9].
Если мы проверим длину и емкость s1, то увидим, что длина равна 9, а емкость — 10. Это потому, что s1 является ссылкой на результат обрезки s. Они используют один и тот же базовый массив.
package main
import "fmt"
func main() {
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(s)
s1 := append(s[:5], s[6:]...)
// Проверка длины и емкости s1
fmt.Println(len(s1), cap(s1))
fmt.Printf("\n%d\n%d\n\n", s, s1)
// Изменение слайса s
s[3] = 22
fmt.Printf("%d\n%d\n", s, s1)
}
Результат будет следующим:
[0 1 2 3 4 5 6 7 8 9]
9 10
[0 1 2 3 4 6 7 8 9 9]
[0 1 2 3 4 6 7 8 9]
[0 1 2 22 4 6 7 8 9 9]
[0 1 2 22 4 6 7 8 9]
Упражнение
Помимо удаления элементов в конкретных позициях, мы также можем удалять целый диапазон элементов из слайса с помощью обрезки. Операция аналогична удалению элемента по индексу. Давайте выполним небольшое упражнение.
Создайте файл slice1.go. Создайте слайс a и инициализируйте его следующим образом. Затем используйте обрезку, чтобы создать другой слайс s, который не включает элементы больше 3 или меньше 7 (в контексте индексов или значений, согласно примеру вывода). В данном случае, судя по выводу, нужно удалить элементы со значениями 6, 5, 4. Напечатайте новый слайс.
a := []int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
Вывод:
[9 8 7 3 2 1 0]
- Подсказка: Обратите внимание на начальный индекс слайса (помните, индексация начинается с 0).
- Требования: Файл
slice1.goдолжен находиться в директории~/project.
Расширение слайсов
У слайса есть два атрибута: len и cap. len — это количество элементов, находящихся в слайсе в данный момент, а cap — максимальное количество элементов, которое слайс может вместить без перевыделения памяти.
Что происходит, когда количество добавляемых элементов превышает емкость слайса? Давайте выясним:
package main
import "fmt"
func main() {
s1 := make([]int, 3)
s2 := make([]int, 3, 5)
fmt.Println("Before Append in s1:", s1, "Length:", len(s1), "Capacity:", cap(s1))
fmt.Println("Before Append in s2:", s2, "Length:", len(s2), "Capacity:", cap(s2))
s1 = append(s1, 12)
s2 = append(s2, 22)
fmt.Println("After Append in s1:", s1, "Length:", len(s1), "Capacity:", cap(s1))
fmt.Println("After Append in s2:", s2, "Length:", len(s2), "Capacity:", cap(s2))
}
Чтобы запустить программу, выполните команду:
go run slice.go
Результат будет следующим:
Before Append in s1: [0 0 0] Length: 3 Capacity: 3
Before Append in s2: [0 0 0] Length: 3 Capacity: 5
After Append in s1: [0 0 0 12] Length: 4 Capacity: 6
After Append in s2: [0 0 0 22] Length: 4 Capacity: 5
Как видно из программы, когда мы добавляем элементы и их количество превышает исходную емкость, емкость слайса автоматически увеличивается.
Правила расширения:
- Если базовый массив может вместить новые элементы, емкость не меняется.
- Если места нет, Go создает новый массив большего размера, копирует данные и добавляет новые значения.
Примечание: Емкость не всегда удваивается ровно в два раза; алгоритм зависит от размера типа, количества элементов и версии Go.
Копирование слайсов
Мы можем использовать функцию copy для дублирования одного слайса в другой. Синтаксис:
func copy(dst, src []Type) int
dst — целевой слайс, src — исходный слайс. Возвращаемое значение int указывает на количество скопированных элементов, которое равно минимуму из len(dst) и len(src).
Примечание: Функция copy не расширяет целевой слайс.
Пример:
package main
import "fmt"
func main() {
s1 := []int{0, 1, 2, 3}
s2 := []int{8, 9}
s3 := []int{0, 1, 2, 3}
s4 := []int{8, 9}
// Копируем s1 в s2
n1 := copy(s2, s1)
// Копируем s4 в s3
n2 := copy(s3, s4)
fmt.Println(n1, s1, s2)
fmt.Println(n2, s3, s4)
}
Запустите программу:
go run slice.go
Результат:
2 [0 1 2 3] [0 1]
2 [8 9 2 3] [8 9]
В этой программе мы скопировали значения s1 в s2 и s4 в s3. Функция copy вернула количество перенесенных элементов.
Заметим, что первая функция copy перенесла s1[0, 1, 2, 3] в s2[8, 9]. Так как минимальная длина среди них — 2, скопировались только 2 значения. Целевой слайс s2 стал [0, 1].
Вторая функция copy перенесла s4[8, 9] в s3[0, 1, 2, 3]. Минимальная длина — 2, поэтому s3 изменился на [8, 9, 2, 3].
Итерация по слайсам
Обход (итерация) слайса аналогичен обходу массива. Все методы итерации массивов применимы и к слайсам.
Упражнение
В этом упражнении мы закрепим навыки итерации по слайсам и массивам.
Создайте файл slice2.go. Объявите массив a1 и слайс s1, инициализируйте их. Затем пройдите по элементам массива a1 и слайса s1, выводя их индексы и значения.
a1 := [5]int{1, 2, 3, 9, 7}
s1 := []int{1, 8, 12, 1, 3}
Вывод:
Element a1 at index 0 is 1
Element a1 at index 1 is 2
Element a1 at index 2 is 3
Element a1 at index 3 is 9
Element a1 at index 4 is 7
Element s1 at index 0 is 1
Element s1 at index 1 is 8
Element s1 at index 2 is 12
Element s1 at index 3 is 1
Element s1 at index 4 is 3
- Требования: Файл
slice2.goдолжен находиться в директории~/project. - Подсказка: Вы можете использовать цикл
rangeили классический цикл с индексом.
Резюме
В этом разделе мы изучили слайсы и способы их использования. По сравнению с массивами, слайсы гораздо более гибкие и универсальные. При работе с несколькими слайсами, ссылающимися на один массив, нужно быть осторожным, чтобы избежать неожиданных побочных эффектов при изменении данных.



