Разделяемый базовый массив, copy и подсрезы
Содержание курса
В прошлом уроке мы создавали срезы через литералы и make, добавляли элементы через append и итерировались по ним через for range. Один момент остался за скобками: что именно происходит, когда срез передаётся в функцию или присваивается другой переменной? Данные копируются или нет? Ответ на этот вопрос меняет то, как нужно читать почти любой код с срезами.
Срез как указатель, длина и ёмкость; подсрез a[low:high] и общий базовый массив
Когда вы пишете s := []int{10, 20, 30, 40, 50}, Go выделяет массив из пяти int где-то в памяти, а переменная s — это маленькая структура из трёх полей: указатель на первый элемент этого массива, len(s) == 5 и cap(s) == 5. Сам срез — это описание участка, а не контейнер с данными.
Подсрез a[low:high] создаёт ещё одно такое описание того же массива:
package main
import "fmt"
func main() {
a := []int{10, 20, 30, 40, 50}
b := a[1:4] // элементы 20, 30, 40
fmt.Println("b:", b) // [20 30 40]
fmt.Println("len(b):", len(b)) // 3
fmt.Println("cap(b):", cap(b)) // 4
}
b указывает на тот же массив, что и a, просто начало сдвинуто на один элемент вперёд. Длина подсреза — high - low, то есть 4 - 1 = 3. Ёмкость вычисляется по формуле cap(a) - low: здесь cap(a) == 5, поэтому cap(b) == 5 - 1 == 4.
Ключевое следствие: поскольку базовый массив один и тот же, запись через b немедленно видна через a.
package main
import "fmt"
func main() {
a := []int{10, 20, 30, 40, 50}
b := a[1:4]
b[0] = 999 // b[0] — это a[1]
fmt.Println("a:", a) // [10 999 30 40 50]
fmt.Println("b:", b) // [999 30 40]
}
b[0] и a[1] — это одна и та же ячейка памяти. Нет никакого копирования; b просто смотрит на участок массива a под другим углом.
Именно здесь чаще всего возникает сюрприз: разработчик создаёт подсрез, изменяет его элементы и не ожидает, что исходный срез тоже изменился. Если такого поведения не нужно — нужна независимая копия, а не подсрез.
