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

Срез в функции

В дне 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 │               массив один
                └────┴────┴────┘

Заголовок в main функция не видит и поменять не может — как int в дне

  1. Отсюда правило: функция, которая меняет длину среза, возвращает срез, а вызывающий присваивает результат.
Код для чтения · разбираем, набирать не нужно
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.go
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])
}
▶ Выполните
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