Arrays vs. Slices | golang

94 Created on 11 January 2025 at 08:53
  Go     It  

Массив — это последовательность элементов одного типа фиксированной длины.

  • В Go, массивы — это составной тип, но присваивается или передается в функцию значение. Присвоение одного массива другому копирует все элементы.
  • В частности, если передать массив функции, она получит копию массива, а не ссылку на него.
  • Размер массива является частью его типа. Типы [10]int и [20]int различны.

Объявление

var a [3]int
fmt.Println(a) // [0 0 0]

  • в квадратных скобках указывается размерность массива
  • далее тип элементов массива
  • объявленный массив без указания значений заполняется нулями (значения по-умолчанию), так как под него уже выделена память.

Инициализация

var arr [3]int = [3]int{1, 2, 3}

пример определения массива с указанием элементов

c := [...]int{1, 2, 3}

  • размер массива будет определен в зависимости от количества элементов при инициализации

Неполная инициализация

с := [5]int{1, 2, 3, 4}
fmt.Println(c) // [1, 2, 3, 4, 0]

  • У массивов в Go всегда фиксированный размер, определённый на этапе компиляции. размерность массива 5 — это уже часть типа и для него выделена память.
  • если массив не заполнен полностью явно, то оставшееся пространство заполняется нулями — значением по умолчанию.
  • если такое поведение неприемлемо в конкретной ситуации, лучше инициализировать так: c := [...]int{1, 2, 3}

Сравнение массивов

Поскольку можно последовательно сравнить все элементы массива, можно сравнить и сами массивы:

a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
c := [3]int{3, 2, 1}
d := [4]int{1, 2, 3}
fmt.Println(a == b) // true
fmt.Println(a == c) // false
fmt.Println(a == d) // error: cannot compare a == d (mismatched types [3]int and [4]int)

  • Но при этом нужно учитывать, что можно сравнить только массивы одного типа (массивы одинаковой длины, содержащие элементы одинакового типа).

Индексы

Для обращения к определенному элементу массива используются индексы.

  • Нумерация элементов начинается с 0.
  • Оператором индексации являются квадратные скобки — [idx].

``` func main() {
var numbers [5]int = [5]int{1,2,3,4,5}

fmt.Println(numbers[0])     // получение элемента по индексу
fmt.Println(numbers[4])     // 5

numbers[0] = 87  // изменение элемента по индексу

fmt.Println(numbers[0])     // 87

}```

range

В Go ключевое слово range используется для итерации по элементам коллекций, таких как: срезы (slices), массивы (arrays), карты (maps), строки (strings) и каналы (channels).

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

  • Если индекс или значение не нужны, их можно опустить, используя _ (иначе Go выдаст ошибку, что переменная не задействована)

for idx, item := range a {
    fmt.Printf("Элемент с индексом %d: %d\n", idx, elem)
}

Срезы

Срезы (slices) — это реализация динамического массива в Go.

  • Хоть он и представлен как отдельный тип данных, срез является оберткой над массивом и не может существовать без него.
  • При создании среза всегда будет уже иметься исходный массив или в памяти будет создан новый массив на который будет ссылаться текущий срез по индексу [:] от начала до конца.
  • Срез является ссылочным типом. При передаче среза в функцию или присваивании одной переменной со срезом другой, на самом деле передается в виде ссылки, а не копируется значение.
  • Срез это удобный механизм работы с массивом, который автоматически создает новый массив в случае переполнения исходного.

Основные характеристики среза

Сам массив (Array) — срез фактически является указателем на массив, и изменения в срезе могут изменять данные в массиве.

Длина (Length) — количество элементов среза.

Емкость (Capacity) — максимальное количество элементов, которое может содержать срез без выделения новой памяти.

  • Емкость среза определяется количеством элементов массива от начального индекса среза и до конца массива. (через make можно задать вручную)
  • Емкость среза не может быть больше емкости массива на который он ссылается.
  • После переполнения емкости, она автоматически увеличивается в 2 раза — выделяется новая область памяти и создается новый массив с большей размерностью — reallocation.
  • После перераспределения массива, срез теряет связь с исходным массивом и ссылается на новый, с большей размерностью.

len(s) — возвращает длину среза. cap(s) — возвращает емкость среза.

Инициализация

При инициализации среза сохраняется связь с первоначальным массивом до тех пор, пока емкость не переполнена.

Через литерал массива

s := []int{1, 2, 3, 4, 5} // инициализация среза с элементами
fmt.Println(s) // [1 2 3 4 5]

  • Размер в скобках не указывается, так как он динамический.
  • В этом примере неявно будет создан исходный массив размерностью 5 элементов.
  • На этот массив будет ссылаться срез начиная с 0-го индекса и до конца массива.
  • Длина и ёмкость среза будут равны пяти.
  • При попытке добавить новый элемент в срез, автоматически будет создан новый массив в два раза увеличенной размерностью.
  • Срез начнет ссылаться на него, связь с исходным массивом пропадет — reallocation.

Функция make

```s := make([]int, 3, 5)

fmt.Println(s) // [0 0 0] fmt.Println(len(s)) // 3 fmt.Println(cap(s)) // 5```

Функция make позволяет создать срез (каналы, карты) с заранее определенной длиной и емкостью.

  • Первый аргумент функции make указывает на тип среза.
  • Второй аргумент — это длина среза.
  • Третий аргумент (необязателен) — это емкость среза.
  • Массив будет заполнен элементами по умолчанию.
  • В этом примере будет неявно создан исходный массив размеров пять элементов и состоящий из нулей — [0, 0, 0, 0, 0].

Срез из массива

arr := [5]int{1, 2, 3, 4, 5} // исходный массив
s := arr[1:4] // срез с индексами от 1 до 3 (не включая 4)
fmt.Println(s) // [2 3 4]

  • Срез можно получить из уже существующего массива, указав диапазон индексов.
  • Используется оператор среза [i:j]

Операции

Оператор . . . (эллипсис) используется для распаковки среза в Go.

Изменение элемента среза

Поскольку срезы работают с одним и тем же массивом, изменения в срезе также отражаются на соответствующих данных в массиве.

s := []int{1, 2, 3}
s[1] = 99 // изменяем элемент с индексом 1
fmt.Println(s) // [1 99 3]

Добавление элементов с помощью append

Функция append используется для добавления новых элементов в срез. * Если ёмкость среза недостаточна, то произойдет перераспределение массива (reallocation) — будет создан новый массив, срез начнет ссылаться на него, связь со старым пропадет. * Если ёмкость среза достаточна, при выполнении append перераспределение памяти не произойдет, добавляемый элемент будет вставлен в исходный массив по индексу (замена элемента) следующим за индексом последнего элемента среза.

s := []int{1, 2, 3}
s = append(s, 4, 5, 6) // добавляем элементы
fmt.Println(s) // [1 2 3 4 5 6]

  • Первым параметром передается срез
  • Затем любое количество элементов, которые нужно добавить в конец среза

Важно помнить, что функция append создает новый срез на базе переданного. Это может привести к ошибкам при работе со срезом внутри функции:

  • Срез передается в функцию по ссылке, значит через эту ссылку можно изменять элементы исходного среза, который вне функции.
  • Но после использования append создается новый срез локально внутри функции и изменения элементов будут происходить только локально в функции.
  • Эту проблему можно решить используя указатели(pointers), но стоит помнить про перераспределение памяти в случае переполнения емкости среза.

Удаление элементов с помощью append

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

``` func main() { s := []int{1, 2, 3, 4, 5}

index := 2 // индекс элемента для удаления
s = append(s[:index], s[index+1:]...)

fmt.Println(s) // [1 2 4 5]

} ```

  • s[:index] — создаёт срез от начала до элемента перед указанным индексом.
  • s[index+1:] — создаёт срез, начиная с элемента, который идет после указанного индекса.
  • append() объединяет эти два среза.