Срез в функции
В дне 11 параметр был копией: функция меняла свой int, а в main ничего не менялось. В дне 16 — факт без объяснения: функция меняет элемент среза, и main это видит. Противоречия нет: копируется и там и там, только у среза копируется заголовок, а массив остаётся один.
Копия заголовка, общий массив
Ситуация. Функция обнуляет первую ячейку и дописывает одну в конец.
package main
import "fmt"
func mark(cells []int) {
cells[0] = 0
cells = append(cells, 77)
fmt.Println("внутри:", cells, len(cells), cap(cells))
}
func main() {
cells := []int{12, 40, 7}
mark(cells)
fmt.Println("снаружи:", cells, len(cells), cap(cells))
}Что увидите.
внутри: [0 40 7 77] 4 6
снаружи: [0 40 7] 3 3Как это читать. При вызове mark(cells) параметр получает копию — как в дне 11. Но копируются три числа заголовка, а не ячейки массива:
main: cells указатель ─┐ len 3 cap 3
mark: cells указатель ─┤ len 3 cap 3 копия заголовка
▼
┌────┬────┬────┐
│ 12 │ 40 │ 7 │ массив один
└────┴────┴────┘cells[0] = 0— запись в ячейку общего массива, вmainеё видно: ячейка та же;cells = append(cells, 77)— места нет, новый массив, и новый заголовок присвоен копии вmark; вmainзаголовок прежний, длина 3.
Заголовок в main функция не видит и поменять не может — как int в дне
- Отсюда правило: функция, которая меняет длину среза, возвращает срез, а вызывающий присваивает результат.
package main
import "fmt"
func addGift(order []int, gift int) []int {
return append(order, gift)
}
func main() {
order := []int{3, 8}
order = addGift(order, 1)
fmt.Println(order)
}[3 8 1]append в функции «ничего не делает» — и всё-таки портит
Функция с append, результат которого не вернули, выглядит безобидно: изменение «пропало», и только. Но была у среза свободная ёмкость — и append успел записать элемент в общий массив, за окном вызывающего:
package main
import "fmt"
func addGift(order []int) {
order = append(order, 1)
}
func main() {
stock := []int{12, 40, 7, 25}
order := stock[:2]
addGift(order)
fmt.Println(len(order), stock[2])
}2 1Длина order в main осталась 2: новый заголовок остался в функции. А в stock[2] вместо 7 теперь 1. go vet молчит: присвоить параметру новое значение законно.
Попробуйте сейчас: что выведет append внутри функции.
Цель: предсказать вывод и сдать ответ.
1. Не запуская, нарисуйте массив
stock, заголовокorderвmainи его копию вaddGift:Код для чтения · разбираем, набирать не нужноpackage main import "fmt" func addGift(order []int, gift int) { order[0] = order[0] + gift order = append(order, gift) } func main() { stock := []int{3, 8, 6, 5} order := stock[:2] addGift(order, 1) fmt.Println(order[0], len(order), stock[2]) }2. Сдайте три числа через пробел:
▶ Выполните · выделенное замените своимcourse answer day17.q5 ЧИСЛО ЧИСЛО ЧИСЛО3. Проверьте запуском — наберите программу в черновик:
✎ Наберите в файлscratch/main.gopackage main import "fmt" func addGift(order []int, gift int) { order[0] = order[0] + gift order = append(order, gift) } func main() { stock := []int{3, 8, 6, 5} order := stock[:2] addGift(order, 1) fmt.Println(order[0], len(order), stock[2]) }▶ Выполнитеcd ~/gocourse/day17/scratch go run .Готово, когда: пункт
q5вcourse check 17зелёный.
Своя копия внутри функции
Ситуация. Функция собирает начало заказа и дописывает число поддонов. Заказ вызывающий печатает следом.
package main
import "fmt"
func withPallets(order []int) []int {
head := order[:2]
head = append(head, 9)
return head
}
func main() {
order := []int{12, 40, 7, 25}
first := withPallets(order)
fmt.Println(first)
fmt.Println(order)
}[12 40 9]
[12 40 9 25]Результат верный, а заказ испорчен: head — окно на массив заказа с ёмкостью 4, и append положил 9 в ячейку, где лежало 7. Дефект второй страницы, спрятанный в функцию: вызывающий не видит ни подсреза, ни append — он видит испорченный заказ.
Починка — дать head свой массив:
package main
import (
"fmt"
"slices"
)
func withPallets(order []int) []int {
head := slices.Clone(order[:2])
head = append(head, 9)
return head
}
func main() {
order := []int{12, 40, 7, 25}
first := withPallets(order)
fmt.Println(first)
fmt.Println(order)
}[12 40 9]
[12 40 7 25]Правило для своих функций: меняет или дописывает в полученном срезе, а вызывающему исходный ещё нужен — делает копию. Обещание пишут в комментарии над функцией: «входной срез не меняет».
Попробуйте сейчас: задание truck — своя копия внутри функции.
Цель:
firstTruckсобирает первую машину в своём массиве, а весь заказ печатается нетронутым.1. Репозиторий дня — один на все задания, коммит после каждого. Заведите его и запишите заготовки, пока ничего не тронуто:
▶ Выполнитеcd ~/gocourse/day17 git init git add . git commit -m "день 17: заготовки"2. Прочитайте условие — позиций в машине и поддонов у вас свои:
▶ Выполнитеcd ~/gocourse/day17/truck cat TASK.txt printf '12\n40\n7\n25\n3\n18\n' | go run .3. Откройте
truck.go. Нарисуйте заказ, окноtruckи его ёмкость: в какую ячейку заказа попадает число поддонов?4. Почините
truck.go— не больше четырёх строк. Любой способ со страницы 3:slices.Clone,makeиcopy.main.goне меняйте.5. Проверьте и закоммитьте:
▶ Выполнитеprintf '12\n40\n7\n25\n3\n18\n' | go run . printf '12\n40\n' | go run . diff ~/.course/orig/day17/truck/truck.go truck.go gofmt -l . git add . git commit -m "truck: своя копия в firstTruck"Готово, когда: «Весь заказ» совпадает с вводом, первая машина прежняя,
diffпоказывает не больше четырёх строк кода — пунктыtruckиtrucksmall. Проверка сама вызоветfirstTruckи сверит заказ до и после, аmain.go— с оригиналом.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
| функция поменяла элемент, и это видно снаружи | массив общий, копируется только заголовок | так и задумано; не нужно — копия внутри функции |
append внутри функции, а снаружи длина прежняя |
новый заголовок остался в функции | вернуть срез: s = f(s) |
| после вызова в срезе вызывающего чужое число за его окном | append в функции записал в свободную ячейку общего массива |
копия внутри функции |
исправили печать в main, а проверка всё равно красная |
функция по-прежнему портит вход, просто это не видно | чинить функцию, а не порядок печати |
cannot use first (variable of type []int) as int value in argument |
параметр объявлен int, а передали срез |
тип параметра — []int |
Дальше: готовые функции пакета slices — course next