Arrays vs. Slices | golang
Массив — это последовательность элементов одного типа фиксированной длины.
- В 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() объединяет эти два среза.