Срез и массив — разница
Массив [3]int | Срез []int | |
|---|---|---|
| Длина | фиксированная, часть типа | меняется, append добавляет элементы |
Присваивание b := a | копирует все элементы | копирует только «окно», элементы общие |
| Передача в функцию | функция получает копию | функция меняет те же элементы |
Сравнение == | можно | нельзя (только с nil) |
| Где используют | редко: ключи map, буферы известного размера | почти всегда |
Короче: массив — это значение, срез — это ссылка на массив плюс длина и ёмкость. В обычном коде на 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:
dst := make([]int, len(src))
copy(dst, src)Ловушка: append в часть среза
Часть s[a:b] наследует ёмкость исходного среза. Если за её концом в массиве ещё есть место, append запишет новый элемент прямо туда — поверх данных исходного среза:
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 — граница ёмкости:
part := base[1:3:3] // len 2, cap 2 — append скопирует в новый массивФункция, которая добавляет элементы
Функция может менять элементы переданного среза, но append внутри неё не изменит длину среза у вызывающего: срез передаётся как значение «окна». Поэтому функции, которые добавляют элементы, возвращают новый срез — так же, как сам append:
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.
Когда всё-таки массив
- Размер — часть смысла: координаты
[3]float64, хеш[32]byte(так возвращаетsha256.Sum256). - Нужен ключ map из нескольких значений: массив сравнивается через
==, срез — нет. - Нужна копия «бесплатно» при присваивании.
Из массива всегда можно получить срез: arr[:] — срез на весь массив, без копирования. Подробнее о срезах — в уроке Массивы, срезы и append.