Go: Срезы

В Go вы почти никогда не будете использовать массивы напрямую. Вместо них вы будете использовать срезы. Срез – это легковесная структура, которая представляет собой часть массива. Есть несколько способов создать срез, и мы позже рассмотрим их подробнее. Первый способ является слегка измененным способом объявления массива:

scores := []int{1,4,293,4,9}

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

scores := make([]int, 10)

Мы используем make вместо new потому, что при создании среза происходит немного больше, чем просто выделение памяти (что делает new). В частности, мы должны выделить память для массива, а также инициализировать срез. В приведенном выше примере мы создаем срез длиной 10 и вместимостью 10. Длина – это размер среза. Вместимость – это размер лежащего в его основе массива. При использовании make мы можем указать эти два параметра отдельно:

scores := make([]int, 0, 10)  

Эта инструкция создает срез с длиной 0 и вместимостью 10. Для лучшего понимания взаимосвязи длины и вместимости, рассмотрим несколько примеров:

func main() {  
  scores := make([]int, 0, 10)
  scores[5] = 9033
  fmt.Println(scores)
}

Наш первый пример не работает. Почему? Потому, что срез имеет длину 0. Да, в его основе лежит массив, содержащий 10 элементов, но нам нужно явно расширить срез для получения доступа к этим элементам. Один из способов расширить срез – это append:

func main() {  
  scores := make([]int, 0, 10)
  scores = append(scores, 5)
  fmt.Println(scores) // выведет [5]
}

Но такой способ изменит смысл оригинального кода. Добавление элемента к срезу длинной 0 является установкой первого значения.

По определённым причинам наш нерабочий код требует установки элемента по индексу 5. Чтобы это сделать, мы должны сделать срез от нашего среза:

func main() {  
  scores := make([]int, 0, 10)
  scores = scores[0:6]
  scores[5] = 9033
  fmt.Println(scores)
}

Как сильно мы можем изменить размер среза? До размера его вместимости, в нашем случае это 10.

Вы можете подумать на самом деле это не решает проблему фиксированной длины массивов. Оказывается, что append это что-то особенное. Если основной массив заполнен, создается больший массив и все значения копируются в него. Go увеличивает массивы в два раза, вы сможете догадаться, что выведет данный код?

func main() { 
  scores := make([]int, 0, 5)
  c := cap(scores)
  fmt.Println(c)
  for i := 0; i < 25; i++ {
    scores = append(scores, i)
    // если вместимость изменена,
    // Go увеличивает массив, чтобы приспособиться к новым данным
    if cap(scores) != c {
      c = cap(scores)
      fmt.Println(c)
    }
  }
}

Изначальная вместимость переменной scores это 5. Для того, чтобы вместить 20 значений, она должна быть расширена 3 раза до вместимости в 10, 20 и наконец 40. И как последний пример, рассмотрим:

func main() {  
  scores := make([]int, 5)
  scores = append(scores, 9332)
  fmt.Println(scores)
}

Здесь вывод будет [0, 0, 0, 0, 0, 9332]. Возможно, вы думали что получится [9332, 0, 0, 0, 0]? Для человека это выглядит логично. Но для компилятора, вы говорите: добавить значение к срезу, который уже содержит 5 значений. В итоге, есть четыре способа инициализировать срез:

names := []string{"leto", "jessica", "paul"}  
checks := make([]bool, 10)  
var names []string  
scores := make([]int, 0, 20)  

Когда какой использовать? Первый не требует особых объяснений. Его можно использовать когда вы заранее знаете значения массива.

Второй полезен когда вам нужно записывать значения по определенным индексам среза. Например:

Третий случай – это пустой срез. Используется в сочетании с append, когда число элементов заранее неизвестно.

Последний способ позволяет задать изначальную вместимость; полезен когда у вас есть общее представление о том, сколько элементов вам нужно.

Даже если вы знаете размер, можно использовать append. Это момент по большей части зависит от ваших предпочтений:

func extractPowers(saiyans []*Saiyans) []int {  
  powers := make([]int, 0, len(saiyans))
  for _, saiyan := range saiyans {
    powers = append(powers, saiyan.Power)
  }
  return powers
}

Теперь рассмотрим эквивалент в Go:

scores := []int{1,2,3,4,5} 
slice := scores[2:4] 
slice[0] = 999 
fmt.Println(scores)


Результат: [1, 2, 999, 4, 5]. Это изменяет принцип кодирования. Например несколько функций принимают номер позиции в качестве параметра. В примере выше мы видим, что [X:] – это сокращение, которое означает от X до конца, а [:X] это короткая запись, означающая от начала до X. В отличие от других языков, Go здесь не поддерживает отрицательные индексы. Есди мы хотим получить все значения среза, кроме последнего, нам нужно выполнить:

scores := []int{1,2,3,4,5}  
scores = scores[:len(scores)-1] 

Еще одной полезной функцией является copy с помощью которой можно заполнить один срез значениями из другого. Пример использования ниже:

b = make([]T, len(a))
copy(b, a)

Также стоит упомянуть возможность добавления среза, к текущему, это делается при помощи функции append:

a = append(a, b...)

 

17.01.2017









 
архив

подписка