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

Своя копия, nil-срез и пустой срез

Подсрез и append дают окна на общий массив — быстро и обычно удобно. Когда общий массив мешает, нужен срез со своими ячейками. Способов два: make с copy или готовая slices.Clone.


make и copy

Ситуация. Нужна копия остатков, которую можно менять, не трогая исходную.

Код для чтения · разбираем, набирать не нужно
stock := []int{12, 40, 7}
own := make([]int, len(stock))
n := copy(own, stock)
own[0] = 99
fmt.Println(n, stock, own)

Что увидите.

Вывод · это печатает программа, набирать не нужно
3 [12 40 7] [99 40 7]

Как это читать.

Образец · набирать не нужно
 stock ──▶ ┌────┬────┬────┐
           │ 12 │ 40 │  7 │
           └────┴────┴────┘
 own   ──▶ ┌────┬────┬────┐
           │ 99 │ 40 │  7 │     свой массив
           └────┴────┴────┘

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

Код для чтения · разбираем, набирать не нужно
stock := []int{12, 40, 7}
own := make([]int, 2)
n := copy(own, stock)
fmt.Println(n, own)
Вывод · это печатает программа, набирать не нужно
2 [12 40]

У make бывает и третье число — ёмкость: make([]int, 0, 3) даёт срез длины 0 с местом на три элемента. Для copy он не годится — длина 0, поместится ноль элементов, — зато удобен, чтобы собирать элементы через append. Опыт с ним — в course extra 17.


slices.Clone — копия одной строкой

То же делает готовая функция из пакета slices. Пакету нужен свой import, поэтому программа целиком:

Код для чтения · разбираем, набирать не нужно
package main

import (
	"fmt"
	"slices"
)

func main() {
	stock := []int{12, 40, 7}
	own := slices.Clone(stock)
	own[0] = 99
	fmt.Println(stock, own)
	fmt.Println(len(own), cap(own))
}
Вывод · это печатает программа, набирать не нужно
[12 40 7] [99 40 7]
3 3

slices.Clone(s) возвращает новый срез со своим массивом и теми же элементами. В новом коде пишут обычно её; make и copy нужны, когда копируют в готовый срез или только часть. Копия подсреза — так же: slices.Clone(stock[:2]), две ячейки в своём массиве.


Когда копия нужна

Ситуация Копия
срез только читают: печатают, считают сумму, ищут максимум не нужна
s = append(s, x), и старым заголовком не пользуются не нужна
подсрез дополняют append или от одного среза нужны два продолжения нужна
срез меняют или сортируют, а исходный порядок ещё печатать нужна
функция меняет полученный срез, а вызывающему исходный нужен нужна

Копия стоит памяти и времени: её делают там, где один код меняет данные, а другой ещё собирается их читать.


Попробуйте сейчас: копия через copy и Clone.

Цель: два способа копии в одной программе, и оба не трогают вход.

1. Наберите:

✎ Наберите в файл scratch/main.go
package main

import (
	"fmt"
	"slices"
)

func main() {
	stock := []int{12, 40, 7, 25}
	one := make([]int, len(stock))
	copy(one, stock)
	two := slices.Clone(stock[1:3])
	one[0] = 0
	two[0] = 0
	fmt.Println(stock, one, two)
}

2. Запустите:

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

3. Уберите slices.Clone( с закрывающей скобкой — останется two := stock[1:3] — и сразу же строку "slices" из import: пока она на месте, программа не соберётся, компилятор скажет "slices" imported and not used. Теперь запустите снова: что стало со stock?

Готово, когда: в первом запуске stock без нулей, а во втором видно, какую его ячейку испортила запись через подсрез. После опыта верните import "fmt" одной строкой.


nil-срез и пустой срез

В дне 16 результат собирали так: var res []int, потом append. Это nil-срез: заголовок есть, массива нет. Бывает ещё пустой срез с массивом нулевой длины — []int{}.

Код для чтения · разбираем, набирать не нужно
var a []int
b := []int{}
fmt.Println(a, len(a), cap(a), a == nil)
fmt.Println(b, len(b), cap(b), b == nil)
a = append(a, 5)
b = append(b, 5)
fmt.Println(a, b)
Вывод · это печатает программа, набирать не нужно
[] 0 0 true
[] 0 0 false
[5] [5]

Как это читать. Для длины, цикла range, печати и append разницы нет: у обоих длина 0, оба печатаются как [], append к обоим выделяет массив. Разница только в сравнении == nil: «да» отвечает лишь срез без массива. В работе это сравнение почти не нужно — пустоту проверяют так: len(s) == 0.


Попробуйте сейчас: что выведет copy в пустой срез.

Цель: предсказать вывод и сдать ответ.

1. Не запуская, ответьте себе: сколько ячеек у own в момент copy?

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func main() {
	stock := []int{12, 40, 7}
	var own []int
	n := copy(own, stock)
	own = append(own, stock[0])
	own[0] = 99
	fmt.Println(n, len(own), stock[0])
}

2. Сдайте три числа через пробел:

▶ Выполните · выделенное замените своим
course answer day17.q4 ЧИСЛО ЧИСЛО ЧИСЛО

3. Проверьте запуском — наберите программу в черновик:

✎ Наберите в файл scratch/main.go
package main

import "fmt"

func main() {
	stock := []int{12, 40, 7}
	var own []int
	n := copy(own, stock)
	own = append(own, stock[0])
	own[0] = 99
	fmt.Println(n, len(own), stock[0])
}
▶ Выполните
cd ~/gocourse/day17/scratch
go run .

Готово, когда: пункт q4 в course check 17 зелёный.


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

Что видите Что это значит Что делать
copy вернул 0, копия печатается как [] приёмник длины 0: var own []int или make([]int, 0, n) make([]int, len(src)) или slices.Clone(src)
invalid argument: length and capacity swapped в make([]int, 5, 3) длина больше ёмкости сначала длина, потом ёмкость
invalid operation: not enough arguments for copy(stock) (expected 2, found 1) у copy два аргумента: куда и откуда copy(own, stock)
«копия» всё равно портит исходный срез это не копия, а подсрез или присваивание own := stock slices.Clone(stock)

Дальше: срез в функции — что копируется, а что остаётся общим — course next