Для начала хотелось бы сказать, что же такое массивы и слайсы.
Массивы
Массив в 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]) }
Что выведет 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
Вопросы
Чем отличаются массивы и слайсы в Go?
Почему размер массива нельзя изменить после создания?
Какие параметры есть у слайса и для чего они нужны?
Что происходит при добавлении элементов в слайс с помощью
append?В каком случае при
appendпроисходит копирование данных?Почему изменения элементов слайса внутри функции видны снаружи?
В чём разница между длиной (
len) и ёмкостью (cap) слайса?Как влияет превышение
capacityна работу слайса?Почему массивы при передаче в функцию не изменяются?
В каких случаях стоит использовать массив, а в каких - слайс?
Задачи
1)
// Какой будет результат выполнения программы? func main() { a := []string{"a", "b", "c"} b := a[1:2] b[0] = "q" fmt.Println(a) }
Объясните:
почему изменился исходный слайс,
какой элемент был изменён и почему.
2)
// Что выведет код и почему? 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.
3)
// Что выведет код и почему? 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) }
Объясните:
изменился ли исходный слайс,
почему изменения видны за пределами функции.
4)
// Что выведет программа? 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.
5)
// Почему слайс не был очищен? func modify(a []int) { a = a[:0] } func main() { sl := []int{1, 2, 3} modify(sl) fmt.Println(sl) }
Объясните, почему длина слайса не изменилась вне функции.
6)
// Какой будет результат? func main() { a := []int{1, 2, 3} b := a[:2] c := a[1:] b[1] = 100 c[0] = 200 fmt.Println(a) }
Объясните:
какие элементы были изменены,
почему результат именно такой.
Рекомендация
Для лучшего понимания рекомендуется запускать примеры локально и выводить значения len и cap после каждой операции со слайсами.
Ответы на вопросы по массивам и слайсам в Go
1. Чем отличаются массивы и слайсы в Go?
Массив - это фиксированная структура данных, которая копируется при передаче.
Слайс - это динамическая структура, которая ссылается на данные.
Пример с пояснениями
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] }
Почему так произошло?
Массив при передаче в функцию полностью копируется, поэтому изменения происходят только в копии.
Слайс при передаче не копирует данные, а ссылается на них, поэтому изменения видны снаружи.
2. Почему размер массива нельзя изменить после создания?
Потому что размер массива — часть его типа.
Пример
var a [3]int var b [4]int // a и b — разные типы
Почему так произошло?
В Go тип [3]int и [4]int - это разные типы.
Поэтому размер массива нельзя изменить, можно только создать новый массив другого размера.
3. Какие параметры есть у слайса и для чего они нужны?
У слайса есть:
длина (
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 создаст новый слайс.
4. Что происходит при добавлении элементов в слайс с помощью append?
append либо добавляет элемент в существующую память, либо создаёт новый слайс.
Пример
sl := make([]int, 0, 2) sl = append(sl, 1) sl = append(sl, 2) // Пока cap не превышена - используется та же память sl = append(sl, 3) // cap превышена → создаётся новый слайс
Почему так произошло?
Go всегда старается использовать существующую память, но если места не хватает — создаёт новую и копирует данные.
5. В каком случае при append происходит копирование данных?
Когда длина слайса становится больше его ёмкости.
Пример
sl := make([]int, 2, 2) // len = 2, cap = 2 sl = append(sl, 3) // cap не хватает → копирование
Почему так произошло?
Потому что cap - это физический предел текущего участка памяти.
При его превышении Go вынужден выделить новое место.
6. Почему изменения элементов слайса внутри функции видны снаружи?
Потому что функция работает с теми же данными, а не с копией.
Пример
func modify(s []int) { s[0] = 10 // Меняем первый элемент } func main() { sl := []int{1, 2, 3} modify(sl) fmt.Println(sl) // [10 2 3] }
Почему так произошло?
Слайс передаётся в функцию как структура, которая указывает на данные.
Поэтому изменение элемента влияет на исходный слайс.
7. В чём разница между len и cap?
len - сколько элементов видно сейчас.cap - сколько элементов можно добавить без расширения памяти.
Пример
sl := make([]int, 1, 3) // sl = [0] fmt.Println(len(sl)) // 1 fmt.Println(cap(sl)) // 3
Почему так произошло?
len - управляет логикой работы со слайсом,cap - оптимизацией памяти.
8. Как влияет превышение capacity на работу слайса?
Превышение cap приводит к созданию нового слайса.
Пример
a := make([]int, 0, 1) a = append(a, 1) // cap ещё хватает b := append(a, 2) // cap превышена → b новый слайс
Почему так произошло?
Go не может записать данные за пределы выделенной памяти,поэтому создаёт новый участок и копирует данные.
9. Почему массивы при передаче в функцию не изменяются?
Потому что массив копируется целиком.
Пример
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 остался без изменений.
10. В каких случаях стоит использовать массив, а в каких — слайс?
В большинстве случаев - слайс.
Почему?
массивы редко нужны из-за фиксированного размера
слайсы удобнее, гибче и используются почти везде
массивы чаще встречаются во внутренней реализации или в учебных примерах
Решения и объяснения задач
Задача 1
Условие
// Какой будет результат выполнения программы? 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].
Задача 2
Условие
// Что выведет код и почему? 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 остаётся прежним.
Задача 3
Условие
// Что выведет код и почему? 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.
Задача 4
Условие
// Что выведет программа? 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.
Задача 5
Условие
// Почему слайс не был очищен? 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 осталась прежней.
Задача 6
Условие
// Какой будет результат? 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], поэтому второе присваивание перезаписывает первое.