Создание срезов через make() и slicing
1. Функция make() для создания срезов
Синтаксис с двумя аргументами
make([]тип, длина)
Создаёт срез с len и cap, равными указанному значению, заполненный нулями:
slice := make([]int, 5)
fmt.Println(slice) // [0 0 0 0 0]
fmt.Println(len(slice)) // 5
fmt.Println(cap(slice)) // 5
Синтаксис с тремя аргументами
make([]тип, длина, ёмкость)
Создаёт срез с указанными len и cap:
slice := make([]int, 3, 10)
fmt.Println(slice) // [0 0 0]
fmt.Println(len(slice)) // 3
fmt.Println(cap(slice)) // 10
Важно: cap >= len всегда.
2. Нарезка (slicing) массивов и срезов
Синтаксис
array[start:end] // От start включительно до end невключительно
Варианты использования
| Синтаксис | Описание |
|---|---|
arr[:3] |
Первые 3 элемента (индексы 0, 1, 2) |
arr[2:] |
С индекса 2 до конца |
arr[:] |
Все элементы |
arr[1:4] |
Элементы с индексами 1, 2, 3 |
3. Примеры нарезки
arr := [7]int{10, 20, 30, 40, 50, 60, 70}
// индексы: 0 1 2 3 4 5 6
arr[1:4] — элементы 1, 2, 3
slice := arr[1:4]
fmt.Println(slice) // [20 30 40]
fmt.Println(len(slice)) // 3
fmt.Println(cap(slice)) // 6
Почему cap = 6? Ёмкость считается от начала среза до конца массива: 7 - 1 = 6.
arr[:3] — первые три
slice := arr[:3]
fmt.Println(slice) // [10 20 30]
Эквивалентно: arr[0:3]
arr[4:] — с четвёртого до конца
slice := arr[4:]
fmt.Println(slice) // [50 60 70]
Индексы: 4, 5, 6.
arr[:] — копия всех элементов
slice := arr[:]
fmt.Println(slice) // [10 20 30 40 50 60 70]
Важно: Это не глубокая копия — срез указывает на тот же массив.
4. Длина и ёмкость при slicing
data := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s := data[2:5]
fmt.Println(s) // [2 3 4]
fmt.Println(len(s)) // 3 — количество элементов (5-2)
fmt.Println(cap(s)) // 8 — от индекса 2 до конца массива (10-2)
Формула cap для среза:
cap = len(базовый_массив) - start_индекс
5. Функция append()
Важное правило
Результат append() ВСЕГДА нужно присваивать:
vals := []int{1, 2, 3}
vals = append(vals, 4) // ✅ Правильно
❌ Ошибка:
append(vals, 4) // Результат потерян!
Добавление одного элемента
vals := []int{1, 2, 3}
fmt.Printf("До: %v (len=%d, cap=%d)\n", vals, len(vals), cap(vals))
// До: [1 2 3] (len=3, cap=3)
vals = append(vals, 4)
fmt.Printf("После: %v (len=%d, cap=%d)\n", vals, len(vals), cap(vals))
// После: [1 2 3 4] (len=4, cap=6)
Ёмкость увеличилась с 3 до 6 автоматически.
Добавление нескольких элементов
vals = append(vals, 5, 6, 7)
fmt.Println(vals) // [1 2 3 4 5 6 7]
6. Механика роста ёмкости (повторение)
Правило
len < cap: добавление без перевыделенияlen == cap: создаётся новый массив с увеличенной ёмкостью
Стратегия роста
Малые срезы (cap < ~256): cap × 2
Большие срезы: cap × 1.25 (примерно)
Пример роста
growth := make([]int, 0, 2)
fmt.Printf("Начало: len=%d, cap=%d\n", len(growth), cap(growth))
// Начало: len=0, cap=2
for i := 0; i < 10; i++ {
growth = append(growth, i)
fmt.Printf("i=%d: len=%d, cap=%d\n", i, len(growth), cap(growth))
}
Вывод:
i=0: len=1, cap=2
i=1: len=2, cap=2
i=2: len=3, cap=4 ← удвоение
i=3: len=4, cap=4
i=4: len=5, cap=8 ← удвоение
i=5: len=6, cap=8
i=6: len=7, cap=8
i=7: len=8, cap=8
i=8: len=9, cap=16 ← удвоение
i=9: len=10, cap=16
7. Важность больших тестов
Совет из материала:
Тестируйте на больших объёмах (500k элементов), а не на малых (2-10 элементов).
Почему:
- На малых данных кажется, что рост всегда ×2
- На больших — переход к росту ~25%
- Точная формула может меняться между версиями Go
8. Ключевые правила len и cap
| Утверждение | Пояснение |
|---|---|
cap >= len |
Всегда истинно |
cap после make([]T, n) |
Равен n |
cap после make([]T, n, m) |
Равен m |
cap после arr[i:j] |
len(arr) - i |
cap растёт при append |
Только если len == cap |
9. Итоги
✅ make([]T, len) — срез длины len, заполнен нулями
✅ make([]T, len, cap) — срез с резервом ёмкости
✅ arr[start:end] — нарезка от start до end-1
✅ arr[:] — срез всех элементов массива
✅ append() всегда требует присваивания результата
✅ cap среза = от начала среза до конца базового массива
✅ Тестируйте рост на больших данных для понимания поведения
Формула slicing:
arr[start:end]
len = end - start
cap = len(arr) - start