5.2 Делим slice на части

Давайте представим, у нас есть слайс, из которого мы хотим взять только часть данных, например:

// из слайса
slice := []int{100,200,300, 400, 500}

// мы хотим получить значения 200, 300 и положить их в новый слайс

В Go эта задача решается очень просто:

// start — индекс элемента, с которого мы должны начать (включительно)
// end — индекс элемента, до которого мы хотим взять значения (не включительно)
newSlice := slice[start:end]

// newSlice будет содержать значения []int{200,300}
newSlice := slice[1:3]

// если start == end, то получим пустой слайс
newSliceZero := slice[1:1] // => []int{}

// если выйти за границы начального слайса, то мы получим ошибку
panicSlice := slice[1:10] // => panic: slice bounds out of range

// start не может быть больше end
panicSlice2 := slice[3:1] // => panic: slice bounds out of range

Также существует возможность не указывать start или end, таким образом мы получим срез от самого начала или до самого конца:

slice := []int{100, 200, 300, 400, 500}

// вернем все значения до индекса 3, не включая элемент под этим индексом (нумерация в слайсе начинается с нуля)
skipStart := slice[:3] // => []int{100, 200, 300}

// вернем все значения начиная с индекса 3 (нумерация с нуля)
skipEnd := slice[3:] // => []int{400, 500}

Также в Go существует возможность получить «срез всего слайса», но если просто

slice2 := slice[:]

Синтаксис slice[:] создает «срез всего слайса», но если присвоить этот срез новой переменной без создания нового базового массива (underlying array), то мы получим копию слайса, который будет указывать на ту же самую область памяти.

slice := []int{100, 200, 300, 400, 500}
// берем «срез всего слайса» 
slice2 := slice[:]
// в новой переменной устанавливаем значение 
slice2[1] = 777

// в итоге изменяем обе переменные, так как указатель ссылается на одну и ту же область памяти
println(slice) // => []int{100, 777, 300, 400, 500}
println(slice2) // => []int{100, 777, 300, 400, 500}

Трёхиндексный срез

Представим, что у нас есть массив data, из которого мы делаем рабочий срез для обработки части данных.

При этом мы хотим защититься от случайной возможности изменить или расширить массив.

Для реализации такой возможности в Go используются трёхиндексные срезы, сравните:

  • Простой срез
  • Трехиндексный срез
package main

import "fmt"

func main() {
    data := []int{10, 20, 30, 40, 50}

    // обычный срез
    work := data[1:3] // элементы [20, 30]
    fmt.Println("work до append:", work)
    fmt.Println("data до append:", data)

    // добавляем элемент
    work = append(work, 99) 
    fmt.Println("\nПосле append:")
    fmt.Println("work:", work)
    fmt.Println("data:", data) // <- базовый массив частично изменился
}

Вывод

work до append: [20 30]
data до append: [10 20 30 40 50]

После append:
work: [20 30 99]
data: [10 20 30 99 50] // <- обратите внимание, что базовый слайс изменился
package main

import "fmt"

func main() {
    data := []int{10, 20, 30, 40, 50}

    // трёхиндексный срез ограничивает cap
    work := data[1:3:3] // start=1, end=3, cap=3
    fmt.Println("work до append:", work)
    fmt.Println("data до append:", data)

    // добавляем элемент, превышающий cap
    work = append(work, 99) 
    fmt.Println("\nПосле append:")
    fmt.Println("work:", work)
    fmt.Println("data:", data) // базовый массив не изменился
}

Вывод

work до append: [20 30]
data до append: [10 20 30 40 50]

После append:
work: [20 30 99]
data: [10 20 30 40 50] // <- базовый массив не изменился

Что делать, чтобы слайсы не разделяли один внутренний массив?

Использовать функцию копирования:

// Функция копирует в dst слайс значения из src среза и возвращает количество скопированных элементов, которое будет минимальным из len(src) и len(dst).

func copy(dst, src []Type) int

// пример использования
slice := []int{1, 2, 3, 4}
// создаем новый слайс нужной размерности
newSlice := make([]int, 2)
// копируем элементы из необходимого среза
copy(newSlice, slice[1:3])
newSlice[0] = 99
fmt.Println(slice) // <= начальный слайс не изменился

Использовать append:

// пример использования
slice := []int{1, 2, 3, 4}
// append возвращает новый слайс, где указатель ссылается на другой базовый массив
newSlice := append([]int{}, slice[1:3]...)
newSlice[0] = 99
fmt.Println(slice) // <= начальный слайс не изменился

Резюме

ЗаписьЧто делает
a[i:j]срез [i, j)
a[:j]от начала до j [0, j)
a[i:]от i до конца [i, конец]
a[:]«срез всего слайса»
a[i:j:k]срез с ограничением емкости (capacity)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *