Ссылочная природа срезов и копирование
1. Срезы — ссылочный тип
Поверхностное копирование при присваивании
original := []int{1, 2, 3}
copySlice := original // Копируется только структура (ptr, len, cap)
original[0] = 999
fmt.Println(original) // [999 2 3]
fmt.Println(copySlice) // [999 2 3] — тоже изменился!
Механизм:
original ──┐
├──→ [999, 2, 3] (один массив в памяти)
copySlice ──┘
Оба среза указывают на один и тот же базовый массив.
2. Независимое копирование: функция copy()
Синтаксис
copy(dst, src) // dst — куда, src — откуда
Возвращает: количество скопированных элементов.
Правило копирования
Копируется min(len(dst), len(src)) элементов
Пример: полная копия
source := []int{10, 20, 30, 40, 50}
dest := make([]int, len(source)) // Создаём приёмник той же длины
copied := copy(dest, source)
fmt.Println(source) // [10 20 30 40 50]
fmt.Println(dest) // [10 20 30 40 50]
fmt.Println(copied) // 5
Проверка независимости
source[0] = 999
fmt.Println(source) // [999 20 30 40 50]
fmt.Println(dest) // [10 20 30 40 50] — не изменился!
Вывод: copy() создаёт глубокую копию данных.
3. Особенности copy()
Если len(dest) < len(src)
source := []int{1, 2, 3, 4, 5}
dest := make([]int, 3) // Только 3 элемента
copied := copy(dest, source)
fmt.Println(dest) // [1 2 3] — скопированы первые 3
fmt.Println(copied) // 3
Правило: Копируется столько, сколько поместится в dest.
Если len(dest) > len(src)
source := []int{1, 2, 3}
dest := make([]int, 5) // 5 элементов, инициализированы нулями
copied := copy(dest, source)
fmt.Println(dest) // [1 2 3 0 0]
fmt.Println(copied) // 3
Остальные элементы сохраняют исходные значения (нули).
4. Сравнение: присваивание vs copy()
| Операция | Результат | Независимость |
|---|---|---|
s2 := s1 |
Копируется структура (ptr) | ❌ Изменения видны через оба среза |
copy(s2, s1) |
Копируются данные | ✅ Полная независимость |
5. Итерация по срезам
Способ 1: Классический for
items := []string{"apple", "banana", "cherry"}
for i := 0; i < len(items); i++ {
fmt.Printf("[%d] = %s\n", i, items[i])
}
Вывод:
[0] = apple
[1] = banana
[2] = cherry
Способ 2: for range (индекс + значение)
for index, value := range items {
fmt.Printf("[%d] = %s\n", index, value)
}
Тот же результат, но компактнее.
Способ 3: Только значения
for _, value := range items {
fmt.Printf("%s\n", value)
}
Вывод:
apple
banana
cherry
_ игнорирует индекс.
Способ 4: Только индексы
for index := range items {
fmt.Printf("Индекс: %d\n", index)
}
Вывод:
Индекс: 0
Индекс: 1
Индекс: 2
6. Практические примеры copy()
Удаление элемента (безопасно)
s := []int{1, 2, 3, 4, 5}
idx := 2 // Удаляем элемент с индексом 2
// Создаём новый срез без элемента
result := make([]int, 0, len(s)-1)
result = append(result, s[:idx]...)
result = append(result, s[idx+1:]...)
fmt.Println(result) // [1 2 4 5]
fmt.Println(s) // [1 2 3 4 5] — оригинал не изменён
Клонирование среза
original := []int{1, 2, 3}
// Способ 1: через copy
clone1 := make([]int, len(original))
copy(clone1, original)
// Способ 2: через append
clone2 := append([]int{}, original...)
fmt.Println(clone1) // [1 2 3]
fmt.Println(clone2) // [1 2 3]
7. Типичные ошибки
❌ Забыли создать приёмник
var dest []int // nil-срез, len=0
source := []int{1, 2, 3}
copied := copy(dest, source)
fmt.Println(copied) // 0 — ничего не скопировано!
Решение: Инициализировать dest с нужной длиной:
dest := make([]int, len(source))
❌ Путаница присваивания и копирования
s1 := []int{1, 2, 3}
s2 := s1 // ❌ НЕ копия, а ссылка!
s2[0] = 999
fmt.Println(s1) // [999 2 3] — изменился!
Решение: Использовать copy():
s2 := make([]int, len(s1))
copy(s2, s1)
8. Итоги
✅ Срезы — ссылочный тип: присваивание копирует только структуру
✅ copy(dst, src) — глубокое копирование данных
✅ Копируется min(len(dst), len(src)) элементов
✅ Функция возвращает количество скопированных элементов
✅ Перебор: классический for или for range
✅ for _, v := range — игнорирование индекса
✅ for i := range — только индексы
Ключевое правило:
slice2 := slice1 → ссылка (shallow copy)
copy(slice2, slice1) → независимая копия (deep copy)