Массивы и слайсы в Golang
- пятница, 30 января 2026 г. в 00:00:11
Для начала хотелось бы сказать, что же такое массивы и слайсы.
Массив в Go - это структура данных, которая представляет собой упорядоченную последовательность элементов одного типа фиксированной длины.
Давайте рассмотрим на примере:
package main
func main() {
/*
Массив создаётся в таком формате:
Имя := [количество элементов массива] тип данных {элементы массива, через запятую}
*/
arr := [3]int{0, 1, 2}
fmt.Println(arr[1])
}
На примере выше был создан массив с 3-мя элементами и типом данных int.
После создания массива мы не можем изменять его вместительность, однако можем менять сами элементы.
Каждый элемент массива имеет свой индекс. Для простоты понимания в примере элемент равен своему индексу.
Индексация в Go, как и в других языках программирования, начинается с 0.
package main
import "fmt"
func main() {
arr := [3]int{0, 1, 2}
fmt.Println(arr[1])
var arr1 [2]int
arr1[0] = 0 // указываем индекс элемента и значение
arr1[1] = 1
arr2 := [...]int{5, 6, 7} // массив автоматически посчитает, сколько элементов в нём находится
fmt.Println(arr2[1])
}
Массивы в Go являются обычными значениями, так что при их вызове они просто копируются. Поэтому при передаче или присваивании массивов создаётся копия их содержимого.
Если мы в одной функции создадим массив и внесём какие-то изменения в другой функции, то оригинальный массив не изменится.
package main
import "fmt"
func main() {
arr2 := [...]int{5, 6, 7}
fmt.Println(arr2[1]) // Первый по индексу элемент массива — это 6 (индексация начинается с нуля)
change(arr2)
fmt.Println(arr2) // Вывод после вызова функции
}
func change(arr2 [3]int) {
arr2[0] = 12 // Изменяем значение по индексу. Было 5, станет 12
fmt.Println(arr2) // Вывод (12 6 7)
}
Вывод в консоль:
6
[12 6 7]
[5 6 7]
На примере выше мы создали массив, после чего вызвали функцию и изменили его.
Оригинальный массив при этом не изменился, так как при передаче была создана копия содержимого массива.
После того как мы в общих чертах разобрались с массивами, можно перейти к слайсам.
Slice в Go - это структура данных динамического размера, которая «ссылается» на массив.
Слайсы создаются аналогично массивам, но имеют ряд отличий.
В работе со слайсами появляются такие термины, как длина (len) и ёмкость (capacity).
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
fmt.Println("cap:", cap(slice), "len:", len(slice)) // Узнаём вместимость и длину слайса
slice = append(slice, 4) // Добавляем в конец слайса элемент
fmt.Println("cap:", cap(slice), "len:", len(slice)) // Слайс копировался в более вместительный слайс, с запасом (обычно в 2 раза больше прошлой capacity)
slice = append(slice, 5, 6, 7) // Добавим ещё элементы, чтобы посмотреть, что происходит в этот раз
fmt.Println("cap:", cap(slice), "len:", len(slice))
}
Вывод в консоль:
cap: 3 len: 3
cap: 6 len: 4
cap: 12 len: 7На примере выше мы создаём слайс и выводим его ёмкость и длину (после создания слайса его ёмкость и длина равны).
После этого мы добавляем в конец слайса новый элемент и смотрим на изменения длины и ёмкости. Ёмкость увеличилась в 2 раза.
Далее мы добавляем ещё 3 элемента, чтобы снова переполнить слайс, и ёмкость нового слайса снова удваивается.
Размер слайса может изменяться в процессе работы программы.
Указателя на массив (pointer)
Длины (len)
Ёмкости (cap)
Когда длина слайса превышает его ёмкость, Go выделяет новый массив с увеличенной ёмкостью (обычно в 2 раза больше).
Старые данные копируются в новый массив.
Характеристика | Массив | Слайс |
|---|---|---|
Размер | Фиксированный | Динамический |
Передача в функцию | Передаётся копией | Передаётся по ссылке |
Память | Последовательный блок | Указатель на массив + метаданные |
Гибкость | Не изменяется | Можно добавлять/удалять элементы |
Чем отличаются массивы и слайсы в Go?
Почему размер массива нельзя изменить после создания?
Какие параметры есть у слайса и для чего они нужны?
Что происходит при добавлении элементов в слайс с помощью append?
В каком случае при append происходит копирование данных?
Почему изменения элементов слайса внутри функции видны снаружи?
В чём разница между длиной (len) и ёмкостью (cap) слайса?
Как влияет превышение capacity на работу слайса?
Почему массивы при передаче в функцию не изменяются?
В каких случаях стоит использовать массив, а в каких - слайс?
// Какой будет результат выполнения программы?
func main() {
a := []string{"a", "b", "c"}
b := a[1:2]
b[0] = "q"
fmt.Println(a)
}
Объясните:
почему изменился исходный слайс,
какой элемент был изменён и почему.
// Что выведет код и почему?
func mod(a []int) {
a = append(a, 125)
for i := range a {
a[i] = 5
}
fmt.Println(a)
}
func main() {
sl := []int{1, 2, 3, 4, 5}
mod(sl)
fmt.Println(sl)
}
Ответьте:
изменился ли исходный слайс,
какую роль сыграл append.
// Что выведет код и почему?
func mod(a []int) {
for i := range a {
a[i] = 5
}
fmt.Println(a)
}
func main() {
sl := make([]int, 4, 8)
sl[0] = 1
sl[1] = 2
sl[2] = 3
sl[3] = 5
mod(sl)
fmt.Println(sl)
}
Объясните:
изменился ли исходный слайс,
почему изменения видны за пределами функции.
// Что выведет программа?
func main() {
a := make([]int, 0, 2)
a = append(a, 1)
a = append(a, 2)
b := append(a, 3)
fmt.Println(a)
fmt.Println(b)
}
Подсказка: обратите внимание на значения len и cap.
// Почему слайс не был очищен?
func modify(a []int) {
a = a[:0]
}
func main() {
sl := []int{1, 2, 3}
modify(sl)
fmt.Println(sl)
}
Объясните, почему длина слайса не изменилась вне функции.
// Какой будет результат?
func main() {
a := []int{1, 2, 3}
b := a[:2]
c := a[1:]
b[1] = 100
c[0] = 200
fmt.Println(a)
}
Объясните:
какие элементы были изменены,
почему результат именно такой.
Для лучшего понимания рекомендуется запускать примеры локально и выводить значения len и cap после каждой операции со слайсами.
Массив - это фиксированная структура данных, которая копируется при передаче.
Слайс - это динамическая структура, которая ссылается на данные.
package main
import "fmt"
func changeArray(a [3]int) {
a[0] = 100
// Меняем первый элемент массива a
}
func changeSlice(s []int) {
s[0] = 100
// Меняем первый элемент слайса s
}
func main() {
arr := [3]int{1, 2, 3}
sl := []int{1, 2, 3}
changeArray(arr)
changeSlice(sl)
fmt.Println(arr) // [1 2 3]
fmt.Println(sl) // [100 2 3]
}
Массив при передаче в функцию полностью копируется, поэтому изменения происходят только в копии.
Слайс при передаче не копирует данные, а ссылается на них, поэтому изменения видны снаружи.
Потому что размер массива — часть его типа.
var a [3]int
var b [4]int
// a и b — разные типы
В Go тип [3]int и [4]int - это разные типы.
Поэтому размер массива нельзя изменить, можно только создать новый массив другого размера.
У слайса есть:
длина (len)
ёмкость (cap)
package main
import "fmt"
func main() {
sl := make([]int, 2, 5)
// len = 2 → сейчас доступно 2 элемента
// cap = 5 → можно добавить ещё 3 элемента без расширения памяти
fmt.Println(len(sl)) // 2
fmt.Println(cap(sl)) // 5
}
len показывает, сколько элементов сейчас в слайсе.cap показывает, сколько элементов можно добавить, прежде чем Go создаст новый слайс.
append либо добавляет элемент в существующую память, либо создаёт новый слайс.
sl := make([]int, 0, 2)
sl = append(sl, 1)
sl = append(sl, 2)
// Пока cap не превышена - используется та же память
sl = append(sl, 3)
// cap превышена → создаётся новый слайс
Go всегда старается использовать существующую память, но если места не хватает — создаёт новую и копирует данные.
Когда длина слайса становится больше его ёмкости.
sl := make([]int, 2, 2)
// len = 2, cap = 2
sl = append(sl, 3)
// cap не хватает → копирование
Потому что cap - это физический предел текущего участка памяти.
При его превышении Go вынужден выделить новое место.
Потому что функция работает с теми же данными, а не с копией.
func modify(s []int) {
s[0] = 10
// Меняем первый элемент
}
func main() {
sl := []int{1, 2, 3}
modify(sl)
fmt.Println(sl) // [10 2 3]
}
Слайс передаётся в функцию как структура, которая указывает на данные.
Поэтому изменение элемента влияет на исходный слайс.
len - сколько элементов видно сейчас.cap - сколько элементов можно добавить без расширения памяти.
sl := make([]int, 1, 3)
// sl = [0]
fmt.Println(len(sl)) // 1
fmt.Println(cap(sl)) // 3
len - управляет логикой работы со слайсом,cap - оптимизацией памяти.
Превышение cap приводит к созданию нового слайса.
a := make([]int, 0, 1)
a = append(a, 1)
// cap ещё хватает
b := append(a, 2)
// cap превышена → b новый слайс
Go не может записать данные за пределы выделенной памяти,поэтому создаёт новый участок и копирует данные.
Потому что массив копируется целиком.
func change(a [3]int) {
a[0] = 100
}
func main() {
arr := [3]int{1, 2, 3}
change(arr)
fmt.Println(arr) // [1 2 3]
}
Функция получила копию массива, а оригинальный массив в main остался без изменений.
В большинстве случаев - слайс.
массивы редко нужны из-за фиксированного размера
слайсы удобнее, гибче и используются почти везде
массивы чаще встречаются во внутренней реализации или в учебных примерах
// Какой будет результат выполнения программы?
func main() {
a := []string{"a", "b", "c"}
b := a[1:2]
b[0] = "q"
fmt.Println(a)
}
package main
import "fmt"
func main() {
a := []string{"a", "b", "c"}
// a = ["a", "b", "c"]
b := a[1:2]
// Берём срез с индексами [1:2] (2 НЕ включается)
// b берёт элементы начиная с a[1] и до a[2) → только один элемент
// b = ["b"]
b[0] = "q"
// Меняем b[0]
// НО b[0] — это на самом деле a[1]
// Поэтому меняется именно a[1]
/*
После изменения:
a = ["a", "q", "c"]
b = ["q"]
*/
fmt.Println(a) // ["a", "q", "c"]
}
Почему так произошло?
Слайс при создании нового среза не копирует данные, а использует те же значения. Поэтому изменение элемента через b меняет и a.
Индексация в новом слайсе начинается с 0, поэтому b[0] соответствует a[1].
// Что выведет код и почему?
func mod(a []int) {
a = append(a, 125)
for i := range a {
a[i] = 5
}
fmt.Println(a)
}
func main() {
sl := make([]int, 5, 5)
sl[0] = 1
sl[1] = 2
sl[2] = 3
sl[3] = 4
sl[4] = 5
mod(sl)
fmt.Println(sl)
}
package main
import "fmt"
func mod(a []int) {
/*
В функцию приходит слайс "a".
Это НЕ копия всех чисел, а ссылка на данные.
Но сейчас важнее другое:
В main мы создали sl с len=5 и cap=5.
То есть места "в запасе" НЕТ.
*/
a = append(a, 125)
/*
Мы добавляем 125.
Так как cap=5 и len=5 - места больше нет.
Значит append создаёт новый слайс в новом месте памяти
и копирует туда старые 5 элементов + добавляет 125.
Теперь "a" внутри функции - это УЖЕ ДРУГИЕ данные,
и он НЕ связан со sl из main.
*/
for i := range a {
a[i] = 5
// Меняем все элементы "a" на 5
}
fmt.Println(a)
// Выведет: [5 5 5 5 5 5]
}
func main() {
sl := make([]int, 5, 5)
sl[0] = 1
sl[1] = 2
sl[2] = 3
sl[3] = 4
sl[4] = 5
// sl = [1 2 3 4 5]
mod(sl)
fmt.Println(sl)
// sl не изменился, потому что внутри mod после append был создан новый слайс
// Выведет: [1 2 3 4 5]
}
Ожидаемый вывод:
[5 5 5 5 5 5]
[1 2 3 4 5]Почему так произошло?
Потому что append при заполненной ёмкости создаёт новый слайс, и изменения идут уже в нём. Исходный sl остаётся прежним.
// Что выведет код и почему?
func mod(a []int) {
for i := range a {
a[i] = 5
}
fmt.Println(a)
}
func main() {
sl := make([]int, 4, 8)
sl[0] = 1
sl[1] = 2
sl[2] = 3
sl[3] = 5
mod(sl)
fmt.Println(sl)
}
package main
import "fmt"
func mod(a []int) {
for i := range a {
a[i] = 5
// Меняем каждый элемент слайса на 5
}
fmt.Println(a) // [5 5 5 5]
}
func main() {
sl := make([]int, 4, 8)
// len=4 cap=8
sl[0] = 1
sl[1] = 2
sl[2] = 3
sl[3] = 5
// sl = [1 2 3 5]
mod(sl)
/*
Так как append не было, мы меняли существующие элементы.
Слайс "a" в функции и "sl" в main смотрят на одни и те же значения.
*/
fmt.Println(sl) // [5 5 5 5]
}
Ожидаемый вывод:
[5 5 5 5]
[5 5 5 5]Почему так произошло?
Потому что мы меняем элементы слайса напрямую, без создания нового слайса черезappend.
// Что выведет программа?
func main() {
a := make([]int, 0, 2)
a = append(a, 1)
a = append(a, 2)
b := append(a, 3)
fmt.Println(a)
fmt.Println(b)
}
package main
import "fmt"
func main() {
a := make([]int, 0, 2)
// len=0 cap=2
a = append(a, 1)
// a = [1] len=1 cap=2
a = append(a, 2)
// a = [1 2] len=2 cap=2 (ёмкость заполнена)
b := append(a, 3)
/*
Добавляем третий элемент, но cap=2 и len=2.
Значит append создаёт новый слайс "b" в новом месте памяти.
*/
/*
Теперь:
a = [1 2]
b = [1 2 3]
*/
fmt.Println(a) // [1 2]
fmt.Println(b) // [1 2 3]
}
Почему так произошло?
Ёмкости не хватило, поэтому append создал новый слайс для b.
// Почему слайс не был очищен?
func modify(a []int) {
a = a[:0]
}
func main() {
sl := []int{1, 2, 3}
modify(sl)
fmt.Println(sl)
}
package main
import "fmt"
func modify(a []int) {
a = a[:0]
/*
Мы сделали длину "a" равной 0, но только внутри функции.
Мы НЕ меняли переменную sl в main.
Мы просто присвоили новое значение локальной переменной "a".
*/
}
func main() {
sl := []int{1, 2, 3}
// sl = [1 2 3]
modify(sl)
fmt.Println(sl) // [1 2 3]
}
Почему так произошло?
Потому что в Go аргументы функции передаются как значения.
Мы изменили локальную переменную a, но переменная sl в main осталась прежней.
// Какой будет результат?
func main() {
a := []int{1, 2, 3}
b := a[:2]
c := a[1:]
b[1] = 100
c[0] = 200
fmt.Println(a)
}
package main
import "fmt"
func main() {
a := []int{1, 2, 3}
// a = [1 2 3]
b := a[:2]
// b берёт элементы a[0] и a[1]
// b = [1 2]
c := a[1:]
// c берёт элементы a[1] и a[2]
// c = [2 3]
b[1] = 100
/*
b[1] - это второй элемент b
НО он указывает на a[1]
Значит теперь:
a = [1 100 3]
*/
c[0] = 200
/*
c[0] - это первый элемент c
НО он тоже указывает на a[1]
Поэтому мы перезаписываем a[1] ещё раз:
a = [1 200 3]
*/
fmt.Println(a) // [1 200 3]
}
Почему так произошло?
Потому что b и c - это разные слайсы, но они ссылаются на одни и те же данные.b[1] иc[0] указывают на один и тот же элемент a[1], поэтому второе присваивание перезаписывает первое.