Ответ
Да, данные слайса можно изменить внутри функции, но с важными нюансами, касающимися его структуры.
Слайс в Go — это легковесная структура (дескриптор), содержащая три поля:
- Указатель на первый элемент базового массива.
- Длина (length) — количество элементов в слайсе.
- Ёмкость (capacity) — количество элементов в базовом массиве от начала слайса до конца массива.
При передаче в функцию копируется сама структура слайса, но указатель в этой копии продолжает ссылаться на тот же самый базовый массив.
Отсюда два сценария:
- Изменение существующих элементов:
s[0] = 100
. Это изменение происходит в базовом массиве, поэтому оно будет видно и за пределами функции. - Добавление элементов с помощью
append
:s = append(s, 4)
. Если ёмкости хватает, новый элемент добавится в тот же базовый массив. Однако изменится только полеlength
локальной копии слайса. Если ёмкости не хватает, будет выделен новый, больший массив, и указатель в локальной копии слайса будет указывать уже на него. В обоих случаях изменения, сделанныеappend
, не будут видны снаружи.
Пример:
package main
import "fmt"
// Эта функция изменяет элемент, но её попытка расширить слайс не видна снаружи.
func modifySlice(slice []int) {
slice[0] = 100
// Это изменение не отразится на оригинальном слайсе в main,
// так как изменяется локальная копия заголовка слайса.
slice = append(slice, 500)
fmt.Printf("Внутри функции: %v, len=%d, cap=%dn", slice, len(slice), cap(slice))
}
// Правильный способ - возвращать изменённый слайс.
func modifyAndReturnSlice(slice []int) []int {
slice[0] = 999
slice = append(slice, 4, 5, 6)
return slice
}
func main() {
s1 := make([]int, 3, 6) // len=3, cap=6
s1[0], s1[1], s1[2] = 1, 2, 3
fmt.Printf("До вызова: %v, len=%d, cap=%dn", s1, len(s1), cap(s1))
modifySlice(s1)
// Элемент изменился, а длина - нет.
fmt.Printf("После modifySlice: %v, len=%d, cap=%dnn", s1, len(s1), cap(s1))
s2 := []int{1, 2, 3}
fmt.Printf("s2 до вызова: %vn", s2)
s2 = modifyAndReturnSlice(s2)
fmt.Printf("s2 после modifyAndReturnSlice: %vn", s2)
}
// Вывод:
// До вызова: [1 2 3], len=3, cap=6
// Внутри функции: [100 2 3 500], len=4, cap=6
// После modifySlice: [100 2 3], len=3, cap=6
//
// s2 до вызова: [1 2 3]
// s2 после modifyAndReturnSlice: [999 2 3 4 5 6]
Вывод: Чтобы надёжно изменять слайс (особенно его длину) внутри функции, его нужно возвращать из функции и присваивать результат переменной, из которой он был передан.