- Объявление среза
- Создание среза через make
- Добавление элементов
- Доступ к элементам
- Передача и возврат из функции
- Пример: заполнение среза в цикле и возврат
- Сравнение с массивами
Срезы (slices) — это основной способ работы с коллекциями переменной длины в Go. В отличие от массивов, срезы не фиксируют количество элементов в типе и позволяют изменять длину коллекции. По сути это аналог списков из других языков.
Технически срез — это обёртка над массивом. Он хранит в себе три вещи:
- Указатель на первый элемент.
- Текущую длину (
len()
). - Вместимость (
cap()
) — максимальное количество элементов, которые можно хранить, не выделяя новый массив.
Объявление среза
Объявление среза аналогично массиву, с одним исключением. Внутри скобок ничего не указывается, так как длина не известна и может меняться.
var nums []int
Значением по умолчанию в таком определении nums
будет nil
.
Срез можно создать напрямую с помощью литерала. В этом случае указывается тип элементов, а длина выводится из количества значений:
numbers := []int{10, 20, 30}
fmt.Println(numbers) // => [10 20 30]
fmt.Println(len(numbers)) // => 3
Можно создать пустой срез добавив в конце {}
:
var empty []string{}
fmt.Println(empty) // => []
fmt.Println(len(empty)) // => 0
Пустой срез — это не nil
, но он ведёт себя как пустая коллекция. Он готов к использованию и не вызывает ошибок при чтении длины, прохождении в цикле и других типичных операциях.
Такая запись (с добавлением {}
в конце) полезна, когда нужно объявить срез заранее, но значения будут добавлены позже (например, в цикле или через append
).
Создание среза через make
Функция make()
используется для создания срезов с заранее заданной длиной и (опционально) вместимостью. Это бывает полезно для предварительного выделения памяти и улучшения производительности.
nums := make([]int, 5) // длина = 5, вместимость = 5
fmt.Println(nums) // => [0 0 0 0 0]
Если вы знаете, что срез будет расширяться, но хотите избежать лишних перераспределений памяти, можно задать вместимость больше длины:
buffer := make([]int, 0, 1000) // длина 0, вместимость 1000
fmt.Println(len(buffer)) // => 0
fmt.Println(cap(buffer)) // => 1000
Это позволяет избежать постоянного выделения новой памяти при каждом append()
— важно в циклах или при работе с большими объёмами данных. С другой стороны, подобные оптимизации нужны далеко не всегда и заниматься ими имеет смысл только тогда, когда эта часть кода уже стала узким горлышком.
Добавление элементов
Для добавления элементов к срезу используется встроенная функция append()
. Она возвращает новый срез с добавленными элементами:
nums := []int{1, 2}
nums = append(nums, 3)
fmt.Println(nums) // => [1 2 3]
Если вместимость позволяет, append()
просто дописывает значение. Если нет — Go выделяет новый массив с увеличенной вместимостью.
Можно добавить сразу несколько элементов:
nums := []int{1}
nums = append(nums, 2, 3, 4)
fmt.Println(nums) // => [1 2 3 4]
Можно объединять срезы:
a := []int{1, 2}
b := []int{3, 4}
a = append(a, b...)
fmt.Println(a) // => [1 2 3 4]
Это удобно, например, при объединении результатов нескольких запросов или списков.
Доступ к элементам
Срезы поддерживают доступ по индексу. В этом смысле поведение идентично массивам:
nums := []int{5, 6, 7}
fmt.Println(nums[1]) // 6
nums[2] = 100
fmt.Println(nums) // [5 6 100]
Если обратиться по индексу, которого нет в срезе — будет ошибка во время выполнения (runtime panic), а не на этапе компиляции.
words := []string{"Go", "Rust"}
fmt.Println(words[2]) // panic: runtime error: index out of range
Поэтому перед обращением стоит проверять длину через len()
.
Передача и возврат из функции
Срезы передаются в функцию по значению, но это значение — структура, содержащая указатель на массив. То есть сами данные не копируются. Поэтому изменения внутри функции отражаются на оригинальных данных.
Также можно возвращать срез из функции:
func changeAndReturn(s []int) []int {
s[0] = 999
return s
}
func main() {
data := []int{1, 2, 3}
result := changeAndReturn(data)
fmt.Println(result) // => [999 2 3]
// изменения видны и в оригинале
fmt.Println(data) // => [999 2 3]
}
Пример: заполнение среза в цикле и возврат
func squares(n int) []int {
nums := []int{}
for i := 1; i <= n; i++ {
nums = append(nums, i * i)
}
return nums
}
func main() {
result := squares(5)
fmt.Println(result) // => [1 4 9 16 25]
}
Сравнение с массивами
Массив [n]T |
Срез []T |
|
---|---|---|
Фиксированная длина | Да | Нет |
Размер входит в тип | Да | Нет |
Добавление элементов | Нет | Да (append ) |
Передача в функцию | Полная копия | Копия структуры, но не данных |
Поддержка len() |
Да | Да |
Используется в практике | Редко | Да (везде) |