Глиф Учебники

Срез и массив — разница

Справочник · Go · 1 минута чтения

Массив [3]intСрез []int
Длинафиксированная, часть типаменяется, append добавляет элементы
Присваивание b := aкопирует все элементыкопирует только «окно», элементы общие
Передача в функциюфункция получает копиюфункция меняет те же элементы
Сравнение ==можнонельзя (только с nil)
Где используютредко: ключи map, буферы известного размерапочти всегда

Короче: массив — это значение, срез — это ссылка на массив плюс длина и ёмкость. В обычном коде на Go пишут срезы; массив нужен, когда длина известна заранее и не должна меняться.

Присваивание и передача в функцию

Go
package main

import "fmt"

func setFirstArr(a [3]int) {
    a[0] = 100
}

func setFirstSlice(s []int) {
    s[0] = 100
}

func main() {
    arr := [3]int{1, 2, 3}
    arrCopy := arr
    arrCopy[1] = 0
    setFirstArr(arr)
    fmt.Println(arr, arrCopy)

    sl := []int{1, 2, 3}
    slCopy := sl
    slCopy[1] = 0
    setFirstSlice(sl)
    fmt.Println(sl, slCopy)
}
Вывод
[1 2 3] [1 0 3]
[100 0 3] [100 0 3]

С массивом каждая переменная и каждая функция работают со своей копией. Со срезом sl и slCopy смотрят на один массив, и функция меняет исходные данные.

Чтобы получить независимую копию среза, используйте slices.Clone(sl) или copy:

Go
dst := make([]int, len(src))
copy(dst, src)

Ловушка: append в часть среза

Часть s[a:b] наследует ёмкость исходного среза. Если за её концом в массиве ещё есть место, append запишет новый элемент прямо туда — поверх данных исходного среза:

Go
package main

import "fmt"

func main() {
    base := []int{1, 2, 3, 4, 5}
    part := base[1:3]
    fmt.Println(part, len(part), cap(part))

    part = append(part, 99)
    fmt.Println(base)
}
Вывод
[2 3] 2 4
[1 2 3 99 5]

part — элементы 2 и 3, но ёмкость у неё 4: до конца массива base. Поэтому append не выделил новую память, а перезаписал base[3]. Если места нет, append копирует элементы в новый массив, и связь с исходным срезом пропадает — заранее по коду не всегда видно, какой из двух случаев сработает.

Чтобы append в часть никогда не трогал исходный срез, ограничьте ёмкость третьим числом — полный срез s[a:b:c], где c — граница ёмкости:

Go
part := base[1:3:3] // len 2, cap 2 — append скопирует в новый массив

Функция, которая добавляет элементы

Функция может менять элементы переданного среза, но append внутри неё не изменит длину среза у вызывающего: срез передаётся как значение «окна». Поэтому функции, которые добавляют элементы, возвращают новый срез — так же, как сам append:

Go
package main

import "fmt"

func addTwice(s []int, v int) []int {
    s = append(s, v)
    s = append(s, v)
    return s
}

func main() {
    nums := []int{1}
    nums = addTwice(nums, 7)
    fmt.Println(nums, len(nums))
}
Вывод
[1 7 7] 3

Без nums = … в main осталась бы старая длина 1.

Когда всё-таки массив

Из массива всегда можно получить срез: arr[:] — срез на весь массив, без копирования. Подробнее о срезах — в уроке Массивы, срезы и append.