Golang: Срезы
Срезы (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() | Да | Да |
Используется в практике | Редко | Да (везде) |
Задание
Реализуйте функцию AddDiscount(prices []float64, discount float64) []float64
, которая применяет скидку к последнему элементу среза цен. Если передан пустой срез, функция должна вернуть пустой срез.
Правила:
- Скидка передаётся в процентах, например
10
означает 10% скидки.
Примеры:
// 300 - 300 * 0.1
AddDiscount([]float64{100, 200, 300}, 10) // [100 200 270]
AddDiscount([]float64{}, 10) // []
AddDiscount([]float64{500}, 20) // [400]
Полезное
Команда проекта находится в телеграм-сообществе. Там можно задать любой вопрос и повлиять на проект
Если вы зашли в тупик, то самое время поговорить с нашим асситентом Тота во вкладке "ИИ-помощник":
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи. В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в обратной связи нашего сообщества
Ваше упражнение проверяется по этим тестам
package solution
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddDiscount(t *testing.T) {
a := assert.New(t)
a.Equal([]float64{100, 200, 270}, AddDiscount([]float64{100, 200, 300}, 10))
a.Equal([]float64{}, AddDiscount([]float64{}, 15))
a.Equal([]float64{400}, AddDiscount([]float64{500}, 20))
a.Equal([]float64{50, 90}, AddDiscount([]float64{50, 100}, 10))
}
Решение учителя откроется через:
20:00
