---
title: "Срезы: создание и индексация"
description: "Срезы в go: литералы []T, len и cap, nil- и пустой срез, make, обращение по индексу и полуинтервал s[i:j]. Разбираем на примерах в песочнице."
course: "Go с нуля: базовый синтаксис и конструкции"
url: "https://golangmaster.ru/courses/go-basics/lessons/17-srezy-basics/"
published: "2026-09-19"
updated: "2026-09-28"
minutes: 20
---

# Срезы: создание и индексация

*Курс «Go с нуля: базовый синтаксис и конструкции», урок 17 из 25. ~20 минут. Обновлено 28.09.2026. Источник: https://golangmaster.ru/courses/go-basics/lessons/17-srezy-basics/*

Срезы в go — главная коллекция языка: []int, []string и их родня встречаются в прикладном коде на каждом шагу, отодвинув массивы из урока 16 в ниши вроде хешей и буферов. Здесь разберём, как slice устроен внутри — окно в массив с заголовком из трёх слов, — литералы и make, пару len и cap, nil-срезы, обращение по индексу и полуинтервал s[i:j], после которого два среза вдруг делят одну память. Всё с примерами, которые можно запустить.

### Срезы в go: зачем они, если есть массивы

Корзина интернет-магазина: сначала в ней ноль товаров, потом три, к вечеру — тридцать. Тип [3]int из [урока про массивы](/courses/go-basics/lessons/16-massivy/) для такой задачи беспомощен: длина вшита в тип, новых ячеек не появится, а передача корзины в функцию скопирует все элементы целиком — массив ведь значение. Для всего, что растёт, сжимается и режется, в языке есть срез — slice; slice golang-коде — основная коллекция: []int, []string, []float64, []byte.

Срезы в go решают обе проблемы массива. Длина не входит в тип: []int из трёх элементов и []int из тридцати — один и тот же тип, они свободно присваиваются друг другу и передаются в одни и те же функции. А при присваивании копируется не содержимое, а крошечный заголовок — три машинных слова, какие бы данные за ним ни стояли. Поэтому сигнатуры прикладного кода набиты срезами: параметр []Order, возврат []string, поле []byte.

За гибкость платят новыми правилами. У среза две длины вместо одной — len и cap. Нулевое значение особое — nil. А данные при копировании не дублируются, а делятся: поменяли в одной переменной — поменялось в другой. Все эти правила мы разберём по очереди, начав с самого простого — создать срез и заглянуть в него.

### Песочница: первый срез

Запустите: срез печатается в квадратных скобках одной строкой, len() вернул пять. Индексация работает как у массива — temps[2] это третий элемент, счёт с нуля, а запись temps[4] = 25 поменяла пятницу. Теперь три опыта. Первый: попробуйте прочитать temps[5] — программа оборвётся ошибкой выхода за границу: резервный движок скажет «индекс за границами: 5 (длина 5)», настоящий Go — панику index out of range [5] with length 5; суть одна, индексы кончаются на четырёх. Второй: допишите ещё один элемент в литерал и запустите — тип []int остался тем же, длина не входит в тип среза, никакой правки объявления не нужно. Третий: напечатайте cap(temps) рядом с len(temps) — у свежего литерала числа совпадают, и почему это важно, станет ясно в середине урока.

```go
package main

import "fmt"

func main() {
        temps := []int{18, 21, 19, 24, 26}
        fmt.Println(temps)
        fmt.Println("дней:", len(temps))

        fmt.Println("среда:", temps[2])
        temps[4] = 25
        fmt.Println(temps)
}
```

### Срез — окно в массив: три слова заголовка

Вот картинка, которая объясняет все странности срезов разом. Данные среза лежат в обычном массиве — непрерывном ряде ячеек памяти, где элементы идут подряд. Сам срез — это не ряд, а окно, наведённое на часть ряда: маленький заголовок из трёх машинных слов. Первое — указатель, адрес ячейки, с которой окно начинается. Второе — len, сколько элементов видно через окно прямо сейчас. Третье — cap, ёмкость: сколько элементов можно было бы видеть, сдвинув правый край окна до конца массива.

Когда вы пишете t := s[1:3], ничего не копируется — заводится второе окно на ту же память, с указателем на вторую ячейку и len 2. Оба окна смотрят на одни ячейки, поэтому правка через одно видна через другое — это главный сюрприз урока, дойдём до него шаг за шагом. А пока запомните связку: массив — это сами ячейки, срез — окно на них. Массив копируется целиком, окно — тремя словами. Отсюда и дешевизна передачи среза в функцию: три слова в любом случае, хоть за окном миллион элементов.

Заголовок — внутреннее устройство, руками его не достать: указатель и cap не печатаются при выводе среза, видно только len элементов. Но len() и cap() дают два из трёх слов напрямую, и через них всё окно раскладывается на части.

### Литерал среза: []T{...}

Создаётся срез чаще всего литералом — перечислением значений в фигурных скобках после типа. Тип пишется с пустой парой квадратных скобок: []int{18, 21, 19} — срез из трёх чисел, []string{"go", "rust"} — из двух строк, [][]int{{1, 2}, {3, 4}} — срез срезов, о нём ближе к концу. Форма обманчиво похожа на литерал массива [3]int{...} — отличается лишь отсутствием числа в скобках, но разница принципиальная: здесь длина не вшита в тип, а считается по числу элементов в скобках.

Пустой литерал []int{} законен: это срез нулевой длины, готовый к заполнению — классическая заготовка под будущий append. Форма var s = []int{...} с полным типом тоже работает, как и у массивов; короткое объявление s := []int{...} практичнее, и дальше используем его. А вот [...] для срезов нет: две точки — это фишка массивов, где компилятору предлагают посчитать длину; срезу считать нечего, длина и так не входит в тип. Внутри литерала типы должны совпадать: []int{1, "два"} не скомпилируется ни в одном движке — никаких сюрпризов.

Литерал хорош, когда элементы известны на месте. Когда количество приходит в рантайме — из переменной, из ответа сервера, из аргументов, — нужен make, и у него будет отдельный раздел ниже.

### Проверьте себя: []int против [3]int

**Вопрос:** В программе рядом написаны a := []int{1, 2, 3} и b := [3]int{1, 2, 3}. Какое утверждение верно?

- Ничем не различаются: это две записи одного типа
- ✅ a — срез, длина не входит в тип; b — массив из трёх элементов, длина часть типа
- a — массив, потому что в скобках нет числа; b — срез
- Оба — массивы, различие только в способе печати

Пустая пара скобок перед типом — признак среза: []int может быть любой длины, и a, и срез из трёхсот элементов — один тип. Число в скобках — признак массива: [3]int — ровно три элемента, и [3]int с [4]int — разные типы, как мы разбирали в уроке 16. Тип на экране показывает %T: у среза оба движка напечатают []int — увидим это на примере ниже.

### []T против [N]T: длина не входит в тип

Разница типов — не синтаксический украшение, а рабочее правило, которое меняет поведение программы. Первое следствие: срезы разных длин совместимы. s := []int{1} и большой := []int{1, 2, 3, 4, 5} имеют один тип []int, поэтому s = большой — законное присваивание, и функция с параметром []int примет срез любого размера. Для массивов то же не проходит: [1]int и [5]int — два разных типа, и попытка присвоить один другому не компилируется.

Второе следствие: массив нельзя присвоить срезу и наоборот. var s []int = arr, где arr — массив, — ошибка компиляции даже при совпадающих длинах: это разные типы. Мост между ними — операция среза массива: s := arr[:] делает окно на весь массив, и вот уже тип []int; об операции среза ниже, она же работает на массиве. Третье следствие видят все, кто печатает типы: fmt.Printf с глаголом %T для среза напечатает []int — и в песочнице, и на настоящем компиляторе; для массива основной движок покажет [3]int, а запасной резервный интерпретатор отметится своей известной странностью из урока 16.

И ещё одна грань того же правила: тип []T описывает только ЧТО лежит внутри, но не СКОЛЬКО. Поэтому длина всегда ходит рядом со срезом — её возвращает len(), её передают в функции вторым аргументом, её проверяют в циклах. В сигнатуре func sum(nums []int) int нет размера — он приедет в значении.

### Тип на экране: %T у среза

```go
package main

import "fmt"

func main() {
        nums := []int{1, 2, 3}
        words := []string{"go", "rust"}

        fmt.Printf("%T\n", nums)
        fmt.Printf("%T\n", words)
}
```

Блок ранимый — оба движка дают одинаковый вывод: «[]int» и «[]string». Глагол %T печатает тип значения, и тип среза записывается именно так, как в объявлении: пустые квадратные скобки и тип элемента. Для сравнения: у массива [3]int основной движок напечатает [3]int, а резервный упрощённый — свою версию с interface {}, поэтому массив в ранимых примерах урока не фигурирует. Запустите блок и убедитесь, что тип из объявления и напечатанный тип совпадают буква в букву.

### len и cap: сколько лежит и сколько влезает

Вот два вопроса, которые задаёт любой срез: сколько элементов видно сейчас и сколько влезло бы, если раздвинуть окно до конца массива. Первый отвечает len(), второй — cap(). Диаграмма в словах: за срезом лежит гребёнка из семи ячеек; окно среза наставлено на ячейки со второй по четвёртую — через него видно три ячейки, len = 3; от начала окна до правого края гребёнки — шесть ячеек, cap = 6. len — про настоящее, cap — про запас.

У свежесозданных срезов оба числа совпадают. Литерал []int{18, 21, 19} — окно на весь массив из трёх ячеек, len 3 и cap 3; make([]int, 4) — четыре нуля, len 4 и cap 4. Это удобно запомнить и удобно использовать: в этом уроке пара len cap в go-примерах всегда равна, и только операция среза s[i:j] разводит числа — в настоящем Go у результата len станет j-i, а cap останется равным расстоянию от начала окна до конца массива, то есть cap(s) минус i. Резервный движок cap после срезания считает иначе — эта тонкость собрана в честном списке в конце урока, в ранимых примерах cap после срезания не используется.

Пока len и cap всегда равны, вопрос «зачем два числа» звучит справедливо. Расходятся они только при росте среза: операция append дописывает элементы, len ползёт вверх, cap двигается скачками — запас, который язык копит под будущие дописывания. Рост среза — тема следующего урока, и там cap из пассивного наблюдения превратится в рабочий инструмент.

### Что выведет код?

```go
package main

import "fmt"

func main() {
        s := []int{1, 2, 3, 4, 5}
        fmt.Println(len(s), cap(s))
}
```

**Что выведет программа?**

- ✅ 5 5: у литерала ёмкость равна длине
- 5 8: язык заранее оставляет запас под рост
- 0 5: срез пуст, пока его не заполнили
- Ошибку: у среза нет понятия ёмкости

Литерал []int{1, 2, 3, 4, 5} — окно, наставленное на весь массив из пяти ячеек: len 5 и cap 5. Запас появляется позже, когда срез растёт через append, — при создании из литерала его нет. Такой вывод дают и песочница, и настоящий Go: у свежего среза len и cap совпадают в обоих движках.

### nil-срез и пустой срез: разница без разницы

У каждой переменной в Go есть нулевое значение, и у среза оно особое: nil. Объявление var s []int без литерала даёт nil-срез — заголовок, который никуда не смотрит: окна нет, есть только три нулевых слова. Второй способ получить «пустоту» — пустой литерал s := []int{}: заголовок есть, окно наставлено на массив нулевой длины, элементов всё равно нет. Разница философская, но у неё есть измеримые проявления, и все они проверены пробником на обоих движках.

len() у обоих возвращает 0 — здесь nil-срез и пустой срез неотличимы. Сравнение с nil разводит их: у nil-среза s == nil даёт true, у пустого литерала — false. Практическое правило идиоматичного Go: на вопрос «пуст ли срез» отвечайте len(s) == 0 — это выражение истинно для обоих случаев и не заставляет читать код дальше. А вот печатать nil-срез в ранимых примерах мы не будем: настоящий Go выведет пустые скобки [], резервный упрощённый — слово <nil>; вывод разъезжается, поэтому в runnable-блоке урока nil-срез проверяется len и сравнением, а его печать остаётся текстовой темой.

Индексация nil-среза ошибки не прощает: s[0] при len 0 — выход за границу в обоих движках. Перебирать nil-срез безопасно — range сделает ноль оборотов. append к nil-срезу законен и создаёт данные — но это уже следующий урок.

### make: срез нужной длины

Вторая половина способов создать срез — функция make. make([]int, 4) выделяет массив на четыре ячейки, заполняет нулями и возвращает окно на все четыре: len 4, cap 4, печать даёт [0 0 0 0]. Для строк то же самое: make([]string, 3) — три пустые строки. make берёт тип и длину, остальное делает сам — это способ сказать языку «дай срез вот такого размера», не выписывая элементы руками.

Главное преимущество перед литералом: размер может быть переменной. n := 4; buf := make([]int, n) — законно и типично; литерал так не умеет, в нём элементы выписываются по одному, и переменной длины не бывает. Поэтому весь код вида «размер пришёл из данных» пишется через make: буфер на n сообщений, сетка на rows строк, палитра на colors цветов.

Заполняется make-срез по индексу — сам он нулей не меняет: buf[i] = ... в цикле, чаще всего range с одной переменной-индексом, как отрабатывали в уроке 15. У make есть и третья форма — make([]int, 3, 10) с ёмкостью; она связана с ростом срезов, и про неё есть отдельный честный callout сразу после примера: с этой формой движки песочницы расходятся, поэтому в ранимых примерах урока make вызывается только с длиной.

### Песочница: make и заполнение

Запустите: make выдал четыре нуля, len и cap равны четырём — размер пришёл из переменной n, для литерала такая запись невозможна. Цикл range с одной переменной-индексом заполнил буфер десятками. Опыты: поднимите n до 7 и запустите снова — править больше ничего не пришлось, make сам подстроился под новый размер, len и cap теперь семь. Затем при n = 4 попробуйте buf[7] = 1 — оба движка оборвут программу: make выделил ровно столько ячеек, сколько попросили, и запись за границей так же незаконна, как чтение. И напоследок замените (i + 1) * 10 на i * i — буфер станет квадратами индексов.

```go
package main

import "fmt"

func main() {
        n := 4
        buf := make([]int, n)
        fmt.Println(buf, len(buf), cap(buf))

        for i := range buf {
                buf[i] = (i + 1) * 10
        }
        fmt.Println(buf)
        fmt.Println("ёмкость:", cap(buf))
}
```

> **make с тремя аргументами: len и cap вместе.** make([]int, 3, 10) в настоящем Go создаёт срез длины 3 с ёмкостью 10: за окном лежит массив на десять ячеек, но видно пока три — запас под будущий append. Основной движок песочницы покажет len 3 cap 10. Запасной TS-интерпретатор третий аргумент понимает упрощённо и вернёт cap 3. Поэтому в ранимых примерах урока make вызывается только с длиной — на этой форме оба движка совпадают, а трёхаргументная форма останется темой урока про append, где она на своём месте.

### Обращение по индексу: от нуля до len-1

Индексация среза дословно повторяет массив из урока 16, и это хорошо: переучиваться не придётся. Индексы идут от 0 до len-1; s[0] — первый элемент, s[len(s)-1] — последний. Чтение и запись законны в этих пределах: s[2] = 30, s[0] *= 2, s[i]++ — всё работает. За пределами — ошибка. Чтение s[5] на срезе длины 5 роняет программу: резервный движок отвечает «индекс за границами: 5 (длина 5)», настоящий Go паникует index out of range [5] with length 5. Отрицательный индекс — тоже ошибка в обоих движках: за границей считается всё, что не в 0..len-1.

Классическая ловушка — s[len(s)]: на чтении последнего элемента рука сама тянется к len(s), но правильный индекс на единицу меньше. Вторая ловушка родом из других языков: запись за границей в Go НЕ растягивает срез. В Python список доживает до присваивания s[5] = 1, в Go это ошибка — срез не растёт записью, ростом занимается только append из следующего урока. Привыкайте: размер меняется явно, через make или append, а индексация работает внутри уже существующих ячеек.

Индексом может быть любое выражение с числом: переменная цикла, арифметика вроде s[i+1], результат функции. Главное — чтобы значение попало в 0..len-1, иначе та же ошибка выхода за границу.

### Проверьте себя: копия или общие данные

**Вопрос:** s := []int{5, 0, 9}, затем t := s и t[0] = 9. Что напечатает fmt.Println(s)?

- ✅ [9 0 9]: правка через t добралась до s
- [5 0 9]: t — независимая копия, правка осталась в ней
- [9 9]: правка затёрла два элемента
- Ошибку: присваивать один срез другому нельзя

При присваивании копируется заголовок — указатель, len, cap, — а данные остаются общими: оба окна смотрят на одни ячейки, и запись t[0] = 9 попала в s[0]. Такой вывод дают и песочница, и настоящий Go. Контраст с массивом из урока 16: там b := a копировал все элементы, и оригинал был в безопасности. У среза в безопасности только заголовок.

### Операция среза: полуинтервал s[i:j]

Самая мощная операция срезов прячется в знакомой синтаксисе индексации: s[i:j] — вырезать окно из окна. Запись означает «элементы с индекса i по индекс j-1 включительно»: правая граница НЕ входит в результат. Такая граница называется полуинтервалом, и её логика практична: длина результата считается в уме как j-i, а соседние окна стыкуются без наложений — s[:2] и s[2:] делят исходный срез ровно пополам, ничто не теряется и не дублируется. У []int{1, 2, 3, 4, 5} вырезка s[1:3] — это [2 3]: индексы 1 и 2, тройка с индексом 3 осталась за бортом.

Границы можно опускать с обеих сторон: s[1:] — от первого индекса до конца, s[:2] — от начала до второго индекса, s[:] — весь срез, копия заголовка. Пропуски — не элегантность, а способ не писать len(s) в каждой вырезке: s[1:] короче и честнее, чем s[1:len(s)]. Правая граница может равняться len(s), но не превышать его; левая не должна заходить за правую. Нарушения в настоящем Go — паника slice bounds out of range; резервный движок молча зажимает границы и ошибки не даёт — не полагайтесь на эту разницу, соблюдайте 0 <= i <= j <= len(s) всегда.

Операция среза работает и на массивах — и это мост из урока 16: arr := [5]int{1, 2, 3, 4, 5}; s := arr[1:3] — срез массива даёт именно срез, тип []int, окно на память массива. Массив остаётся значением, но теперь на него смотрит окно, и правки через окно достаются до массива. Результат срезания — всегда срез, даже если нарезать массив или строку.

### Что выведет код: вырезка

```go
package main

import "fmt"

func main() {
        s := []int{1, 2, 3, 4, 5}
        fmt.Println(s[1:3])
}
```

**Что выведет программа?**

- ✅ [2 3]: индексы 1 и 2, правая граница не включается
- [2 3 4]: правая граница входит в результат
- 2 3: печать без скобок
- Ошибку: индекс 3 за границей среза

Полуинтервал: s[1:3] берёт элементы с индексами 1 и 2, а индекс 3 — уже нет; len результата 3-1=2. Скобки в выводе рисует fmt — он печатает срезы в квадратных скобках через пробел. Правая граница, равная len(s), законна — ошибки нет. Оба движка дают одинаковый вывод: печать и длина результата срезания у них совпадают.

### Срез делит память с оригиналом

```go
package main

import "fmt"

func main() {
        s := []int{5, 0, 9}
        t := s[1:3]
        t[0] = 9

        fmt.Println(s)
        fmt.Println(t)
}
```

Настоящий Go — и основной движок песочницы — напечатает «[5 9 9]» и «[9 9]»: окно t наставлено на те же ячейки, что и s, запись t[0] = 9 попала прямо в s[1]. Кнопки у блока нет: запасной TS-интерпретатор при срезании копирует данные и покажет s нетронутым — «[5 0 9]», а блок обязан работать одинаково в любом режиме. Итог для памяти: присваивание среза делит данные в обоих движках (проверяли quiz-ом), вырезка s[i:j] делит данные в настоящем Go — проверяйте этот пример на локальном компиляторе или основном движке песочницы.

### Песочница: срезы срезов

Запустите: срез может быть элементом другого среза — тип [][]int, а полки разной длины, язык это допускает («рваный» список, в отличие от массивов, где строки обязаны совпадать). len(shelf) считает полки, вложенный range отдаёт индекс и строку целиком, len(row) — длину каждой полки. Запись shelf[1][2] = 9 попала в напечатанный срез: внутренние срезы делят память так же, как обычные. Опыты: допишите четвёртую полку в литерал — полок станет четыре; напечатайте shelf[0][1] и замените его на 100; попробуйте прочитать shelf[0][5] — на полке только две ячейки, оба движка оборвут программу ошибкой выхода за границу.

```go
package main

import "fmt"

func main() {
        shelf := [][]int{{3, 1}, {4, 1, 5}, {2}}
        fmt.Println(shelf)
        fmt.Println("полок:", len(shelf))

        for i, row := range shelf {
                fmt.Println(i, row, len(row))
        }

        shelf[1][2] = 9
        fmt.Println(shelf)
}
```

### Перебор среза: for и range

Перебирается срез теми же двумя циклами, что и массив. Классический for со счётчиком: аккумулятор объявляется до цикла, индекс бегёт от 0 до len(s) не включительно, внутри читаем s[i]. Range — короче и типичнее: подробно он разобран в [уроке 15](/courses/go-basics/lessons/15-range/), здесь только прикладная выжимка. Две переменные — for i, v := range s — дают индекс и копию значения; одна — for i := range s — только индекс, форма для записи по месту; подчёркивание for _, v := range s — когда нужен лишь смысл элемента. Внутри тела работают break и continue с правилами из [урока 14](/courses/go-basics/lessons/14-while-break-continue/) — range останавливается на break так же, как классический цикл.

Единственное, что требует постоянного внимания, — v из range копия. Присвоите v = v * 10 — изменится копия, срез не дрогнет; чтобы поменять элементы, пишите по индексу: s[i] *= 2. Двумерный срез перебирается вложенным range: внешний проходит полки, внутренний — значения внутри полки; строка из range — тоже копия заголовка, но данные за ней общие, поэтому row[0] = 9 из range всё равно изменит срез — тот самый общий заголовок.

Практика: перебор среза не может зациклиться — range всегда доходит до конца и останавливается; границей служит len, зафиксированный в заголовке цикла. Вечный цикл на range не написать — для этого есть вечный for из урока 14.

### Вставьте пропуски: срез из нулей

Допишите создание и заполнение: срез из трёх нулей через make и печать среза вместе с длиной. Правильная программа печатает «[0 0 9] 3».

```go
s := ___([]int, 3)
s[2] = 9
fmt.Println(s, ___(s))
```

Подсказка: Первый пропуск — функция, создающая срез нужной длины с нулевым содержимым. Второй — функция, возвращающая число элементов.

Правильные ответы в пропусках: `make`, `len`

### Соберите программу: перебор среза

Соберите программу: литерал среза строк, range с индексом и значением и финальная строка после цикла. Собранный вывод — «0 раз», «1 два», «2 три», «готово».

Правильный порядок строк:

1. `package main`
2. `import "fmt"`
3. `func main() {`
4. `words := []string{"раз", "два", "три"}`
5. `for i, w := range words {`
6. `fmt.Println(i, w)`
7. `}`
8. `fmt.Println("готово")`
9. `}`

### Шпаргалка: операции со срезами

| Запись | Что делает | В песочнице |
| --- | --- | --- |
| s := []int{5, 0, 9} | литерал: срез из трёх чисел | работает, вывод и len/cap совпадают в обоих движках |
| s := []int{} | пустой срез, len 0 | работает |
| var s []int | nil-срез, нулевое значение | len 0 и s == nil работают; печать nil-среза в резерве даёт <nil> вместо [] |
| s := make([]int, 4) | четыре нуля, len 4 cap 4 | работает |
| s := make([]int, 3, 10) | len 3, ёмкость 10 | основной движок cap 10, резервный cap 3 — ранимым не делаем |
| s[i] / s[i] = v | чтение и запись по индексу, 0..len-1 | работает |
| s[5] при len 5 | ошибка выхода за границу | оба движка роняют программу, текст ошибки разный |
| t := s[1:3] | полуинтервал, len 2 | печать и len работают; cap после срезания в резерве занижен |
| t := s | копия заголовка, данные общие | работает: правка через t видна в s |
| s[0:10] при len 3 | ошибка в настоящем Go (slice bounds) | резервный молча зажимает границу — проверяйте на настоящем Go |
| s[1:3:5] | трёхиндексный срез: len и cap | резервный молча игнорирует третий индекс — ранимым не делаем |

> **Честно о песочнице: два движка.** Каждый пример урока прогнан пробником на обоих движках песочницы. Совпадают: литералы, len, cap у свежего среза и make с длиной, индексация, ошибка выхода за индекс (факт, не текст), печать и длина результата s[i:j], открытые границы, передача среза в функцию, срезы срезов, поведение nil-среза по len и ==. Расходятся, все случаи проверены наблюдениями: (1) печать nil-среза — [] против <nil>; (2) make с тремя аргументами — cap 10 против 3; (3) cap после срезания — настоящий Go считает от начала окна до конца массива (cap(s) - i), резервный — просто длину результата; (4) правка через окно s[1:3] меняет оригинал в основном движке, резервный копирует; (5) срез за границей s[0:10] и перепутанный s[2:1] — настоящий Go паникует, резервный зажимает границы молча; (6) трёхиндексный s[1:3:5] резервный сводит к s[1:3]; (7) %q и %T на вложенных срезах резервный печатает упрощённо. Все эти формы сознательно вынесены из ранимых блоков урока.

### Карточки: срезы

- **Чем срез []int отличается от массива [3]int?** — У среза длина не входит в тип: []int любой длины — один тип, размер меняется. Массив держит длину в типе, [3]int и [4]int — разные типы. При присваивании срез копирует заголовок из трёх слов, массив — все элементы.
- **Что возвращает len() и cap() для среза и когда они равны?** — len — сколько элементов видно, cap — сколько влезает до конца массива за окном. У свежего литерала и make с длиной оба числа равны; расходятся они после роста срезом append и (в cap-семантике настоящего Go) после вырезки s[i:j].
- **Чем nil-срез отличается от пустого среза и как проверить «пуст ли»?** — var s []int — nil, заголовок никуда не смотрит, s == nil даёт true; []int{} — пустой, но не nil. len обоих равен 0. Идиоматичная проверка пустоты — len(s) == 0: она покрывает оба случая.
- **Что делает make([]int, 4)?** — Создаёт срез длины 4 из нулей: len 4, cap 4. Размер может быть переменной — главное отличие от литерала. Заполняется по индексу; запись за границей срез не растягивает — ростом занимается append.
- **Какие элементы попадут в t := s[1:3] и какова длина результата?** — Полуинтервал: элементы с индексами 1 и 2, правая граница 3 не включается, len(t) = j-i = 2. Границы можно опускать: s[1:] — до конца, s[:2] — с начала, s[:] — весь срез.
- **Почему t := s; t[0] = 9 меняет s?** — Присваивание копирует заголовок — указатель, len, cap, — данные остаются общими: оба среза смотрят на одни ячейки. То же при передаче в функцию. Вырезка s[i:j] делит память так же — в настоящем Go.

## Коротко: что запомнить

- Срез — главная коллекция Go: длина не входит в тип []T, размер не фиксируется, а копируется заголовок из трёх слов, а не данные.
- Срез — окно в массив: указатель на начало, len (сколько видно) и cap (сколько влезает до конца массива); len и cap у свежего среза равны.
- Создание: литерал []int{5, 0, 9}, пустой []int{}, make([]int, 4) для переменной длины; nil-срез var s []int и пустой срез имеют len 0, различает их только сравнение с nil.
- Индексация — от 0 до len-1; выход за границу роняет программу в обоих движках, запись срез не растягивает — ростом занимается append.
- s[i:j] — полуинтервал: правая граница не включается, len результата j-i; границы опускаются: s[1:], s[:2], s[:]; срез массива arr[1:3] даёт срез []int.
- Срез делит память: t := s; t[0] = 9 меняет s, и окно после вырезки смотрит на те же ячейки — в настоящем Go правка через t достаётся оригиналу.
- Перебор — классическим for с len() или range из урока 15; v в range — копия, менять срез можно только по индексу.

## Домашнее задание

Соберите недельный отчёт на срезах: temps := []int{18, 21, 19, 24, 26, 22, 20}. Классическим for с len() посчитайте сумму — должно выйти 150. Range-ом с подчёркиванием найдите максимум — 26. Вырежьте выходные: t := temps[5:] и напечатайте t — получится [22 20]. Затем сделайте squares := make([]int, 5), заполните его квадратами чисел 10, 20, 30, 40, 50 через range с индексом и напечатайте вместе с длиной: [100 400 900 1600 2500] 5. Все четыре задачи решаются приёмами этого урока — append не понадобится, он в следующем уроке.

## Вопросы и ответы по уроку

### Чем срез отличается от массива и почему в коде почти всегда срезы?

Длина среза не входит в тип, размер меняется, а присваивание копирует заголовок из трёх слов, не данные. Массив держит длину в типе и копируется целиком. Поэтому сигнатуры функций, возвраты и поля структур набиты срезами; массивы остаются в нишах — хеши [32]byte, буферы, сетки.

### Зачем срезу cap, если len и cap у литерала равны?

Равенство держится у свежесозданных срезов. После вырезки s[i:j] в настоящем Go cap становится расстоянием от окна до конца массива за ним, а при росте через append запас ёмкости экономит пересоздания массива. В резервном движке песочницы cap после срезания считается иначе, поэтому в этом уроке cap берётся только у свежих срезов.

### Что означает ошибка «индекс за границами» и как её избежать?

Индексы среза идут от 0 до len-1; обращение к s[len(s)] или отрицательное значение роняет программу. Перед обращением по переменному индексу проверяйте len; последний элемент — s[len(s)-1]. Срезы срезания подчиняются правилу 0 <= i <= j <= len(s), иначе настоящий Go паникует slice bounds out of range.
