Срез — окно на массив
В дне 16 срез вёл себя как список: добавили, прочитали по индексу, обошли циклом. Один факт остался без объяснения: функция меняет элементы среза, и это видно снаружи, хотя параметр int в дне 11 так не умел. Сегодня заглядываем внутрь среза.
День про ошибки, которых не видит компилятор: программа собирается, работает и тихо портит данные — один срез «сам» меняется, когда пишут в другой. Понять такое можно только по картинке памяти, поэтому картинок сегодня много.
Заголовок и массив
Ситуация. Остатки пяти ячеек:
stock := []int{12, 40, 7, 25, 3}
fmt.Println(stock, len(stock), cap(stock))Дальше показываем только строки из тела main — вставляйте их между { и } в scratch/main.go. С одним import "fmt" первая строка тела — шестая.
Что увидите.
[12 40 7 25 3] 5 5Как это читать. Числа лежат не в самой переменной stock. Литерал []int{…} делает две вещи: заводит массив из пяти ячеек — того вида [5]int, что был в дне 16, — и срез, который на него смотрит. Сам срез — три числа, их называют заголовком:
stock: указатель ─┐ len 5 cap 5
│
▼
массив: ┌────┬────┬────┬────┬────┐
│ 12 │ 40 │ 7 │ 25 │ 3 │
└────┴────┴────┴────┴────┘
0 1 2 3 4- указатель — с какой ячейки массива начинается срез; напрямую не печатается;
- длина — сколько элементов видно через срез:
len(stock); - ёмкость — сколько ячеек массива от начала среза до конца массива:
cap(stock).
Срез — это окно: указатель — где оно начинается, длина — ширина, ёмкость — сколько места до конца массива. Индексы считаются от начала окна, и видно только то, что внутри длины.
Подсрез: окно поуже
Ситуация. Нужны не все ячейки, а часть: со второй по третью.
stock := []int{12, 40, 7, 25, 3}
mid := stock[1:3]
fmt.Println(mid, len(mid), cap(mid))
head := stock[:2]
tail := stock[3:]
fmt.Println(head, len(head), cap(head))
fmt.Println(tail, len(tail), cap(tail))Что увидите.
[40 7] 2 4
[12 40] 2 5
[25 3] 2 2Как это читать. stock[1:3] — подсрез: окно с ячейки 1 до ячейки 3, не включая её. Новых ячеек не появляется: mid — ещё один заголовок на тот же массив.
stock: указатель ─┐ len 5 cap 5
mid: указатель ─┼────┐ len 2 cap 4
▼ ▼
массив: ┌────┬────┬────┬────┬────┐
│ 12 │ 40 │ 7 │ 25 │ 3 │
└────┴────┴────┴────┴────┘
0 1 2 3 4
└─ mid ─┘| Запись | Окно | len |
cap у stock из пяти |
|---|---|---|---|
stock[a:b] |
ячейки с a по b-1 |
b - a |
5 - a |
stock[:b] |
с начала, то же что stock[0:b] |
b |
5 |
stock[a:] |
до конца, то же что stock[a:len(stock)] |
5 - a |
5 - a |
Ёмкость подсреза считается от его начала до конца массива: mid начинается с ячейки 1, до конца массива четыре ячейки — cap 4. Ячейки за окном mid никуда не делись, просто через него их не видно.
Запись через подсрез
Ячейки общие — значит, запись через подсрез меняет исходный срез.
stock := []int{12, 40, 7, 25, 3}
mid := stock[1:3]
mid[0] = 99
fmt.Println(stock)
fmt.Println(mid)[12 99 7 25 3]
[99 7]mid[0] и stock[1] — одна и та же ячейка массива, у неё два адреса. Подсрез ничего не копирует: это способ работать с частью большого среза даром. Ошибкой это становится, когда автор кода думал, что подсрез — отдельная копия.
Попробуйте сейчас: len и cap подсреза.
Цель: предсказать
lenиcapтрёх подсрезов и сверить запуском.1. Наберите программу и до запуска запишите на бумаге, что напечатает каждая строка:
✎ Наберите в файлscratch/main.gopackage main import "fmt" func main() { cells := []int{8, 30, 14, 2, 55, 6} a := cells[2:5] b := cells[:4] c := cells[4:] fmt.Println(a, len(a), cap(a)) fmt.Println(b, len(b), cap(b)) fmt.Println(c, len(c), cap(c)) }2. Запустите:
▶ Выполнитеcd ~/gocourse/day17/scratch go run .Готово, когда: все девять чисел совпали с записанными, а если нет — вы нашли по картинке, где ошиблись.
Попробуйте сейчас: что выведет подсрез.
Цель: предсказать вывод программы и сдать ответ.
1. Не запуская, нарисуйте массив и окно
low:Код для чтения · разбираем, набирать не нужноpackage main import "fmt" func main() { stock := []int{30, 12, 45, 8, 20} low := stock[1:4] low[0] = 0 low[2] = low[2] + 5 fmt.Println(stock[1], stock[3], len(low)) }2. Сдайте три числа через пробел — как их напечатает
Println:▶ Выполните · выделенное замените своимcourse answer day17.q1 ЧИСЛО ЧИСЛО ЧИСЛО3. Проверьте запуском — наберите программу в черновик:
✎ Наберите в файлscratch/main.gopackage main import "fmt" func main() { stock := []int{30, 12, 45, 8, 20} low := stock[1:4] low[0] = 0 low[2] = low[2] + 5 fmt.Println(stock[1], stock[3], len(low)) }▶ Выполнитеcd ~/gocourse/day17/scratch go run .Готово, когда: пункт
q1вcourse check 17зелёный.
Что может пойти не так
Конец подсреза дальше конца массива:
stock := []int{12, 40, 7, 25, 3}
n := 7
head := stock[:n]
fmt.Println(head)stagiaire@lab:~/gocourse/day17/scratch$ go run .
panic: runtime error: slice bounds out of range [:7] with capacity 5
goroutine 1 [running]:
main.main()
/home/stagiaire/gocourse/day17/scratch/main.go:8 +0x51
exit status 2Та же паника и с числом прямо в коде — stock[:7]: ёмкость компилятор не проверяет, её узнают при запуске.
| Что видите | Что это значит | Что делать |
|---|---|---|
panic: runtime error: slice bounds out of range [:7] with capacity 5 |
конец подсреза дальше конца массива: у stock пять ячеек |
ограничить конец: if n > len(stock) { n = len(stock) } |
panic: runtime error: slice bounds out of range [3:1] |
начало подсреза больше конца, числа в переменных | проверить порядок: сначала меньшее, s[1:3] |
invalid slice indices: 1 < 3 |
то же самое, но оба числа прямо в коде — компилятор видит сам | поменять местами |
panic: runtime error: index out of range [3] with length 2 |
индекс считается от длины окна, а не от ёмкости | индекс меньше len, а не cap |
Дальше: append — когда он пишет в тот же массив, а когда в новый — course next