Своя копия, 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]Как это читать.
make([]int, len(stock))заводит новый массив изlen(stock)ячеек с нулями и срез на него;copy(own, stock)переписывает элементы изstockвown— сначала куда, потом откуда — и возвращает, сколько переписал;own[0] = 99меняет ячейку своего массива,stockне затронут.
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 3slices.Clone(s) возвращает новый срез со своим массивом и теми же элементами. В новом коде пишут обычно её; make и copy нужны, когда копируют в готовый срез или только часть. Копия подсреза — так же: slices.Clone(stock[:2]), две ячейки в своём массиве.
Когда копия нужна
| Ситуация | Копия |
|---|---|
| срез только читают: печатают, считают сумму, ищут максимум | не нужна |
s = append(s, x), и старым заголовком не пользуются |
не нужна |
подсрез дополняют append или от одного среза нужны два продолжения |
нужна |
| срез меняют или сортируют, а исходный порядок ещё печатать | нужна |
| функция меняет полученный срез, а вызывающему исходный нужен | нужна |
Копия стоит памяти и времени: её делают там, где один код меняет данные, а другой ещё собирается их читать.
Попробуйте сейчас: копия через copy и Clone.
Цель: два способа копии в одной программе, и оба не трогают вход.
1. Наберите:
✎ Наберите в файлscratch/main.gopackage 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.gopackage 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