День 17 · Срезы изнутри: len, cap и общий массив · страница 7 из 7

Трассировка: len, cap и общий массив

Дефект «два среза на один массив» не виден в коде — он виден в таблице. Колонок у среза становится три: сам срез, длина и ёмкость. И как только два среза смотрят в один массив, запись в одну клетку меняет и другую.


Два среза в одной таблице

Ситуация. Программа с номерами строк:

Код для чтения · разбираем, набирать не нужно
 1  package main
 2
 3  import "fmt"
 4
 5  func main() {
 6  	stock := []int{7, 4, 9, 2}
 7  	head := stock[:2]
 8  	head = append(head, 5)
 9  	head = append(head, 8)
10  	head = append(head, 1)
11  	head[0] = 6
12  	fmt.Println(stock, head)
13  }
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day17/scratch$ go run .
[7 4 5 8] [6 4 5 8 1]
шаг строка stock len(stock) cap(stock) head len(head) cap(head)
1 6 [7 4 9 2] 4 4 - - -
2 7 [7 4 9 2] 4 4 [7 4] 2 4
3 8 [7 4 5 2] 4 4 [7 4 5] 3 4
4 9 [7 4 5 8] 4 4 [7 4 5 8] 4 4
5 10 [7 4 5 8] 4 4 [7 4 5 8 1] 5 8
6 11 [7 4 5 8] 4 4 [6 4 5 8 1] 5 8
7 12 [7 4 5 8] 4 4 [6 4 5 8 1] 5 8

Как это читать. Шаг 2: окно head — две ячейки, ёмкость 4, до конца массива. Шаги 3 и 4: длина меньше ёмкости, append пишет в общий массив — 9 затёрта пятёркой, 2 восьмёркой; на шаге 4 длина сравнялась с ёмкостью. Шаг 5: места нет, у head свой массив, ёмкость 8 переписана из запуска. Шаг 6: head[0] меняет уже новый массив, в stock по-прежнему 7.

Правила для срезов. Правила 1–24 из памятки блока 3 (course page 15 2) и правила 25–26 дня 16 действуют. К ним ещё три.

  1. У каждого среза в таблице три колонки: сам срез, len и cap. В клетку среза по правилу 25 идут элементы с нулевого по len - 1; ячейки за длиной не пишутся. У подсреза s[a:b] длина b - a, ёмкость cap(s) - a.
  2. Срезы, взятые один из другого, делят массив. Запись через любой из них на том же шаге меняет все срезы, чьё окно видит эту ячейку; len и cap от записи не меняются.
  3. append при len меньше cap пишет в тот же массив, при len, равном cap, — в новый. В первом случае элемент на том же шаге появляется во всех срезах, чьё окно видит ячейку, а cap прежний. Во втором срез с этого шага ни с кем ячеек не делит, а новый cap не вычисляют: он вписан в заготовку или взят из вывода.

Самопроверка: клетка среза изменилась там, где его имени в строке нет, — в строке стоит сосед по массиву, правило 28 или 29.

Ту же таблицу можно увидеть в выводе программы: опыт с отладочной печатью — в course extra 17.


Таблица в файле

Задание trace — в формате дней 02, 08 и 16: table.txt рядом с program.go, менять можно только ?. На шаге 7 cap(part) вписан — правило 29. Числа у каждого свои.


Попробуйте сейчас: задание trace.

Цель: все ? в trace/table.txt заменены значениями без запуска.

1. Откройте задание:

▶ Выполните
cd ~/gocourse/day17/trace
cat program.go
cat table.txt

2. Нарисуйте массив cells и окно part. Заполните все ? не запуская программу — шаг за шагом, по правилам 27–29.

3. Заполнив таблицу целиком, запустите программу и сверьте последнюю строку. Спорные шаги проверяйте только в копии, program.go не меняйте:

▶ Выполните
go run .
cp program.go ~/gocourse/day17/scratch/main.go

Готово, когда: в table.txt не осталось ?, пункт trace зелёный.


Что может пойти не так

Что видите Что это значит Что делать
В таблице ещё осталось знаков ?: 4 заполнено не всё найти ?: в VS Code Ctrl+F
Строк с шагами в таблице 8, а нужно 9 строку удалили или добавили переименовать table.txt и выполнить course lab 17
на шаге с записью в cells колонка part не изменилась окна делят ячейку правило 28
после append в part колонка cells не изменилась была свободная ёмкость, запись в общий массив правило 29
в клетке среза лишние числа за длиной в срез попали ячейки за окном правило 27: только до len

Попробуйте сейчас: коммиты дня.

Цель: работа дня в репозитории, не меньше четырёх непустых коммитов.

1. Репозиторий заведён на странице 4, truck, report и keep закоммичены сразу после работы; пропущенный коммит сделайте сейчас. Перед коммитом — ритуал дня 14:

▶ Выполните
cd ~/gocourse/day17
gofmt -l .
go vet ./...
go test ./...

2. Закоммитьте таблицу и посмотрите историю:

▶ Выполните
git status -s
git add .
git commit -m "trace: таблица с len и cap"
git log --oneline

Готово, когда: пункт git зелёный. В последнем коммите лежат тест keep, починенный truck.go и дописанный report/main.go; коммиты без изменений не считаются.


Словарик ошибок

Сегодняшние сообщения — про границы подсреза, make, copy и slices. Допишите в ~/errors.md те, что встретили сами: слева текст ошибки, тире, справа — что он значил в вашем коде.

Образец · набирать не нужно
panic: runtime error: slice bounds out of range [:7] with capacity 5 - взял подсрез дальше конца массива
panic: slices.Max: empty list - искал максимум в пустом срезе

К концу дня в словарике не меньше 34 разборов — записи за все дни считаются вместе. Разделитель — тире с пробелами; слева обязательно текст самого сообщения, своими словами пересказанная ошибка не считается. Кандидаты дня: invalid slice indices, length and capacity swapped, not enough arguments for copy, undefined: slices, (no value) used as value, invalid append: argument must be a slice.


Итог дня

Что Суть
срез заголовок из трёх чисел — указатель, len, cap — и массив под ним
подсрез s[a:b] окно на тот же массив; запись через него видна в исходном
append при len < cap пишет в тот же массив, при len == cap — в новый
копия slices.Clone(s) или make и copy
срез в функции копия заголовка: элементы общие, длина — нет
тест над срезом сверяет и результат, и что вход не изменился

Материал дня закончен. Задания — course lab 17, дополнительное чтение — course extra 17