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

Прежде чем я перейду к тому, как работать со срезами, мне нужно упомянуть, что подавляющее количество экспертов по программированию сейчас говорят, что первая структура данных, которую вы должны учитывать при необходимости хранения данных, - это какой-то тип динамической структуры, такой как срез. Современные языки программирования реализуют эти структуры данных намного эффективнее, чем в прошлом, и существует так много удобств с динамическими структурами, как срез, что нет смысла использовать массив, когда срез, вектор или ArrayList, или список тоже подойдет.

Основы работы с срезами

Как я уже упоминал, срез - это несвязанная последовательность данных одного и того же типа данных. Когда срез создается с нуля, также создается базовый массив. Если срез создается с нуля, он начинается с емкости, равной 0, и новая емкость добавляется по мере добавления новых элементов. Это делается путем выделения новой памяти базовому массиву.

Срез объявляется так же, как массив, но размер не указывается. Вот некоторые примеры:

var names[] string
var numbers[] int
var flags[] bool

Срезы также можно создавать из существующего массива. Вы делаете это, ссылаясь на нужные элементы массива с помощью оператора среза. Оператор среза записывается как [start: stop], где start - это элемент, с которого вы хотите начать срез, а stop - это элемент, на котором вы хотите остановиться, но который не включает этот элемент.

В следующем примере срез firstThree получает первые три элемента массива numbers:

numbers := [size]int{1,2,3,4,5}
firstThree := numbers[0:3]

Вы можете переместить весь массив в срез, не указав начальное и конечное значения:

firstThree := numbers[:]

Вот еще один пример извлечения фрагмента из массива, который извлекает два последних элемента:

lastTwo := numbers[3:]

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

func main() {
  const size = 5
  numbers := [size]int{1,2,3,4,5}
  lastTwo := numbers[3:]
  fmt.Println(lastTwo) // [4 5] is displayed
  numbers[3] = 5
  fmt.Println(lastTwo) // [5 5] is displayed
}

Другой способ создания среза - встроенная функция make. Эта функция принимает спецификатор среза типа данных, длину среза и емкость (общее количество доступных элементов) среза. Шаблон синтаксиса для функции make выглядит так:

slice-name: = make ([] тип данных, длина, емкость)

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

slice-name: = make ([] тип-данных, длина)

Вот фрагмент кода, демонстрирующий, как использовать функцию make:

numbers := make([]int, 0)

Если вы начинаете с пустого среза, вы можете добавлять элементы в срез с помощью функции append. Шаблон синтаксиса для добавления:

slice-name = append (имя-фрагмента, элемент)

Вы должны переназначить добавленный фрагмент обратно в фрагмент, потому что вы не можете быть уверены, что новый фрагмент по-прежнему будет ссылаться на базовый массив старого фрагмента. Синтаксически вы все равно должны выполнить задание.

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

package main
import (
  "fmt"
  "math/rand"
  "time"
)
func main() {
  seed := rand.NewSource(time.Now().Unix())
  rng := rand.New(seed)
  var numbers[] int
  for i:=1; i<=10; i++ {
    numbers = append(numbers, rng.Intn(100))
  }
  fmt.Print(numbers)
}

Копирование фрагментов и другие функции фрагментов

Начнем со сравнений. Два среза нельзя сравнивать на равенство с оператором ==. Если вы попробуете, то получите ошибку. Вместо этого вам придется написать функцию для этого. Вот один пример:

package main
import (
  "fmt"
  "math/rand"
  "time"
)
func equal(s1, s2 []int) bool {
  if len(s1) != len(s2) {
    return false
  }
  for i := range s1 {
    if s1[i] != s2[i] {
      return false
    }
  }
  return true
}
func main() {
  seed := rand.NewSource(time.Now().Unix())
  rng := rand.New(seed)
  numbers := make([]int, 0)
  for i:=1; i<=10; i++ {
    numbers = append(numbers, rng.Intn(100))
  }
  numbersCopy := make([]int, len(numbers))
  copy(numbersCopy, numbers)
  fmt.Println(numbers)
  fmt.Println(numbersCopy)
  if equal(numbers, numbersCopy) {
    fmt.Print("The same.")
  } else {
    fmt.Print("Different.") 
  }
}

Вы можете написать функцию для переворота содержимого фрагмента (встроенной функции реверса нет, как в других языках). Вот один пример, взятый из The Go Programming Language Донована и Керниган:

package main
import (
  "fmt"
  "math/rand"
  "time"
)
func reverse(sl[] int) {
  for i, j := 0, len(sl)-1; i < j; i, j = i+1, j-1 {
    sl[i], sl[j] = sl[j], sl[i]
  }
}
func main() {
  seed := rand.NewSource(time.Now().Unix())
  rng := rand.New(seed)
  numbers := make([]int, 0)
  for i:=1; i<=10; i++ {
    numbers = append(numbers, rng.Intn(100))
  }
  fmt.Println(numbers)
  reverse(numbers)
  fmt.Println(numbers)
}

Есть две встроенные функции, которые вы можете использовать со срезами. Одна функция, len, возвращает длину фрагмента, как в этом примере:

for i:=1; i<=10; i++ {
  numbers = append(numbers, rng.Intn(100))
}
fmt.Println(numbers)
fmt.Printf("The length of numbers is %d.\n", len(numbers))

Вы также можете получить емкость среза с помощью функции cap. Емкость слайса - это общее количество элементов, которые могут быть добавлены в слайс без необходимости выделения памяти для дополнительного места для хранения.

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

func main() {
  numbers := make([]int, 0)
  for i:=1; i<=10; i++ {
    numbers = append(numbers, i)
    fmt.Printf("The capacity of numbers is %d.\n", cap(numbers))
  }
}

Результат этой программы:

The capacity of numbers is 1.
The capacity of numbers is 2.
The capacity of numbers is 4.
The capacity of numbers is 4.
The capacity of numbers is 8.
The capacity of numbers is 8.
The capacity of numbers is 8.
The capacity of numbers is 8.
The capacity of numbers is 16.
The capacity of numbers is 16.

Обработка всех элементов среза

Обработать все элементы среза можно двумя способами. Первый способ - это индексированный цикл for. Вот программа, демонстрирующая эту технику:

func main() {
  grades := []int {88, 71, 91, 83, 100}
  var total int
  for i:=0; i<len(grades); i++ {
    total += grades[i] 
  }
  average := float64(total) / float64(len(grades))
  fmt.Printf("The average is %.2f.\n", average)
}

Более безопасный способ перебора значений среза - использовать цикл range for. Вот программа, переписанная с помощью цикла range for:

func main() {
  grades := []int {88, 71, 91, 83, 100}
  var total int
  for _, grade := range grades {
    total += grade
  }
  average := float64(total) / float64(len(grades))
  fmt.Printf("The average is %.2f.\n", average)
}

Кусочек ломтика

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

Благодарим за прочтение этой статьи. Отправляйте комментарии и предложения по электронной почте.