Много значений: массив и срез
До сих пор у каждого значения была своя переменная: qty, price, total. Сегодня значений будет столько, сколько пришло во вводе, — и заранее это число неизвестно. Для этого в Go есть срез.
Двадцать ячеек — двадцать переменных?
Ситуация. На стеллаже двадцать ячеек, в каждой свой остаток. Нужно сложить остатки, найти самую полную ячейку и перечислить те, где товара мало. Со знаниями блоков 1–3 это выглядит так:
cell1 := 12
cell2 := 40
cell3 := 5
// ... ещё семнадцать строк
total := cell1 + cell2 + cell3 // + ещё семнадцать слагаемыхБеда не в длине: цикл из дня 08 не умеет перебирать имена cell1, cell2, cell3, а завтра стеллаж станет на тридцать ячеек. Нужна одна переменная с рядом значений и способ сказать «элемент номер такой-то».
Массив: ряд фиксированной длины
Дальше показываем только строки из тела main — вставляйте их между { и } в scratch/main.go. С одним import "fmt" первая строка тела — шестая.
var cells [5]int
cells[0] = 12
fmt.Println(cells, len(cells))Что увидите.
[12 0 0 0 0] 5Как это читать.
| Запись | Что значит |
|---|---|
[5]int |
тип «ряд из пяти целых». Это массив |
var cells [5]int |
пять ячеек сразу, в каждой нулевое значение 0 — как у обычной var qty int |
cells[0] = 12 |
записать 12 в элемент с индексом 0 |
len(cells) |
длина — сколько элементов в ряду |
[12 0 0 0 0] |
так Println печатает ряд: элементы через пробел в квадратных скобках |
Главное свойство массива: длина — часть типа. [5]int и [6]int — разные типы, как int и string; длину нельзя взять из переменной и нельзя поменять. Поэтому дальше в курсе вместо массива почти всегда срез.
Срез: ряд, длина которого не записана в типе
stock := []int{12, 40, 5, 40, 7}
fmt.Println(stock[0], stock[2])
stock[2] = 15
fmt.Println(stock, len(stock))Что увидите.
12 5
[12 40 15 40 7] 5Как это читать. []int — квадратные скобки пустые: это срез целых, длина в тип не входит. В фигурных скобках — начальные элементы. Картинка того, что получилось в памяти:
индекс: 0 1 2 3 4
┌────┬────┬────┬────┬────┐
stock ─────────────► │ 12 │ 40 │ 15 │ 40 │ 7 │
└────┴────┴────┴────┴────┘
len(stock) = 5Сам ряд ячеек Go заводит за вас, а переменная stock — окно на него: видно, где ряд начинается и сколько в нём элементов. Как устроено окно — день 17. Сегодня достаточно трёх правил:
- Индекс считается с нуля. Первый элемент —
stock[0], последний —stock[len(stock)-1]. У среза из пяти элементов индекса 5 нет. stock[i]читается и пишется, как обычная переменная:stock[2] = 15меняет третий элемент, остальные не трогает.len(stock)— сколько элементов сейчас в срезе.
Номер ячейки на складе и индекс в срезе — разные вещи: ячейка 1 лежит в stock[0]. Эта единица разницы — источник половины сегодняшних ошибок.
Попробуйте сейчас: первый срез в черновике.
Цель: программа печатает срез, его длину и изменённый элемент.
1. Наберите в черновик:
✎ Наберите в файлscratch/main.gopackage main import "fmt" func main() { stock := []int{12, 40, 5, 40, 7} fmt.Println(stock[0], stock[2]) stock[2] = 15 fmt.Println(stock, len(stock)) }2. Запустите:
▶ Выполнитеcd ~/gocourse/day16/scratch go run .3. Напечатайте последний элемент, не считая его индекс в уме: допишите строку
fmt.Println(stock[len(stock)-1])и запустите снова.Готово, когда: вывод совпал с разделом «Что увидите» выше, а третья строка —
7.
Попробуйте сейчас: что выведет запись по индексу.
Цель: предсказать три числа, не запуская программу, и сдать ответ.
1. Не запуская, прочитайте строки тела
main:Код для чтения · разбираем, набирать не нужноstock := []int{12, 40, 5, 40} i := 2 stock[i] = stock[i-1] + 1 fmt.Println(stock[2], stock[i+1], len(stock))Индекс может быть не только числом, но и переменной или выражением —
stock[i-1]значит «элемент, индекс которого на единицу меньшеi».2. Сдайте три числа через пробел, в том порядке, в каком их напечатает программа:
▶ Выполните · выделенное замените своимcourse answer day16.q1 ЧИСЛО ЧИСЛО ЧИСЛО3. Проверьте запуском — замените тело
mainв черновике:✎ Наберите в файлscratch/main.gopackage main import "fmt" func main() { stock := []int{12, 40, 5, 40} i := 2 stock[i] = stock[i-1] + 1 fmt.Println(stock[2], stock[i+1], len(stock)) }▶ Выполнитеgo run .Готово, когда: пункт
q1вcourse check 16зелёный.
Что может пойти не так
У массива длина известна компилятору, поэтому выход за границу с индексом-числом он ловит сам. Строки тела main:
var cells [5]int
cells[5] = 3
fmt.Println(cells)stagiaire@lab:~/gocourse/day16/scratch$ go run .
# day16/scratch
./main.go:7:8: invalid argument: index 5 out of bounds [0:5]У среза длина станет известна только при запуске. Та же ошибка в срезе — здесь чтение stock[5] — компилируется молча и роняет программу уже во время работы:
stock := []int{12, 40, 5, 40, 7}
fmt.Println(stock[5])stagiaire@lab:~/gocourse/day16/scratch$ go run .
panic: runtime error: index out of range [5] with length 5
goroutine 1 [running]:
main.main()
/home/stagiaire/gocourse/day16/scratch/main.go:7 +0x9
exit status 2Что такое panic и как её читать — на третьей странице.
| Что видите | Что это значит | Что делать |
|---|---|---|
invalid argument: index 5 out of bounds [0:5] |
у массива из пяти элементов индексы 0–4, а написан 5 | последний индекс — длина минус один |
invalid argument: index -1 (constant of type int) must not be negative |
индекс меньше нуля | индексы начинаются с 0 |
panic: runtime error: index out of range [5] with length 5 |
в срезе пять элементов, а спросили индекс 5 | то же самое, но ловится только при запуске |
cannot use cells (variable of type [3]int) as []int value in argument to total |
передали массив туда, где ждут срез: это разные типы | объявите cells := []int{…} — без числа в скобках |
две строки: declared and not used: size и invalid array length size |
длина массива взята из переменной, а она должна быть известна до запуска | нужен срез, а не массив |
Попробуйте сейчас: репозиторий дня.
Цель: в
~/gocourse/day16есть репозиторий с первым коммитом — заготовками дня. Каждое задание дальше заканчивается своим коммитом.▶ Выполнитеcd ~/gocourse/day16 git init git add . git commit -m "день 16: заготовки" git log --onelineГотово, когда:
git log --onelineпечатает одну строку с вашим сообщением. Подпись автора задана ещё в день 11; если git просит её —git config --global user.nameиuser.email, как тогда.
Дальше: как срез растёт — append — course next