Трассировка: 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 действуют. К ним ещё три.
- У каждого среза в таблице три колонки: сам срез,
lenиcap. В клетку среза по правилу 25 идут элементы с нулевого поlen - 1; ячейки за длиной не пишутся. У подсрезаs[a:b]длинаb - a, ёмкостьcap(s) - a. - Срезы, взятые один из другого, делят массив. Запись через любой из них на том же шаге меняет все срезы, чьё окно видит эту ячейку;
lenиcapот записи не меняются. 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.txt2. Нарисуйте массив
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