Go. Структуры данных
Слайсы: создание, срезы, встроенные функции (append, copy)

Введение в слайсы
Представьте, что у вас есть лента бумаги, на которой можно писать сколько угодно текста. В отличие от блокнота с фиксированным количеством страниц (массивы), вы можете отрезать от ленты любой кусок или приклеить к ней новый. Слайсы в Go работают точно так же — это динамические «окна» в массивы, которые могут изменять свой размер.
Давайте разберёмся, что такое слайсы, как их создавать и использовать в Go.
Содержание
Что такое слайс?
Слайс — это динамическое представление (или «вид») на underlying array (базовый массив). В отличие от массивов, слайсы могут изменять свою длину и являются более гибкими в работе.
package main

import "fmt"

func main() {
    // Создаём слайс
    numbers := []int{10, 20, 30, 40, 50}
    
    fmt.Println("Слайс:", numbers)           // [10 20 30 40 50]
    fmt.Println("Длина слайса:", len(numbers)) // 5
    
    // Добавляем элемент
    numbers = append(numbers, 60)
    fmt.Println("После append:", numbers)     // [10 20 30 40 50 60]
    fmt.Println("Новая длина:", len(numbers)) // 6
}
Что здесь происходит?
  • []int{…}
    Создаём слайс целых чисел
  • len(numbers)
    Получаем текущую длину слайса (количество элементов)
  • append(numbers, 60)
    Добавляем новый элемент в конец слайса
Создание слайсов
Способы создания слайсов
1. Литерал слайса
Самый простой и интуитивный способ — создать слайс с помощью литерала, похоже на инициализацию массива, но без указания размера.
package main

import "fmt"

func main() {
    // Полная инициализация
    numbers := []int{10, 20, 30, 40, 50}
    fmt.Println("Полная инициализация:", numbers)
    
    // Пустой слайс
    empty := []int{}
    fmt.Println("Пустой слайс:", empty, "длина:", len(empty))
    
    // Слайс строк
    names := []string{"Alice", "Bob", "Charlie"}
    fmt.Println("Слайс строк:", names)
}
Что здесь происходит?
  • []int{10, 20, 30, 40, 50}
    Создаём слайс с начальными значениями
  • []int{}
    Создаём пустой слайс (длина равна 0)
  • Go автоматически создаёт базовый массив и возвращает слайс, ссылающийся на него
2. Срез из массива или слайса
Слайс можно создать из существующего массива или другого слайса, используя операцию «перенарезки» (reslicing). Такой слайс будет ссылаться на тот же базовый массив.
package main

import "fmt"

func main() {
    // Создаём массив
    array := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
    
    // Создаём слайс из массива
    slice1 := array[2:6] // элементы с индекса 2 по 5 (не включая 6)
    fmt.Println("Срез [2:6]:", slice1) // [2 3 4 5]
    
    slice2 := array[:4] // от начала до индекса 3
    fmt.Println("Срез [:4]:", slice2) // [0 1 2 3]
    
    slice3 := array[3:] // от индекса 3 до конца
    fmt.Println("Срез [3:]:", slice3) // [3 4 5 6 7]
    
    slice4 := array[:] // весь массив
    fmt.Println("Срез [:]:", slice4) // [0 1 2 3 4 5 6 7]
}
Что здесь происходит?
  • array[2:6]
    Берём элементы с индекса 2 до 6 (не включая 6)
  • array[:4]
    Если low опущен, начинаем с 0
  • array[3:]
    Если high опущен, берём до конца массива
  • array[:]
    Создаём слайс на весь массив
  • Важно: слайс ссылается на исходный массив, не копирует данные
Базовые операции со слайсами
Добавление элементов (append)
Функция append() — это встроенная функция Go для добавления элементов в конец слайса. Она принимает слайс и один или несколько элементов для добавления, а возвращает новый слайс с добавленными элементами.
package main

import "fmt"

func main() {
    // Добавление одного элемента
    numbers := []int{10, 20, 30}
    numbers = append(numbers, 40)
    fmt.Println("После append одного элемента:", numbers) // [10 20 30 40]
    
    // Добавление нескольких элементов
    numbers = append(numbers, 50, 60, 70)
    fmt.Println("После append нескольких:", numbers) // [10 20 30 40 50 60 70]
    
    // Добавление другого слайса (с ...)
    other := []int{80, 90}
    numbers = append(numbers, other...)
    fmt.Println("После append слайса:", numbers) // [10 20 30 40 50 60 70 80 90]
    
    // Добавление среза массива (с ...)
    array := [3]int{100, 200, 300}
    numbers = append(numbers, array[:]...)
    fmt.Println("После append массива:", numbers) // [10 20 30 40 50 60 70 80 90 100 200 300]
}
Копирование слайсов (copy)
Функция copy() используется для копирования элементов из одного слайса в другой. Она копирует минимальное количество элементов из len(src) и len(dst), то есть копирует столько элементов, сколько поместится в слайс-приёмник. Функция возвращает количество реально скопированных элементов.
package main

import "fmt"

func main() {
    src := []int{10, 20, 30, 40, 50}
    dst := []int{0, 0, 0} // слайс из 3 элементов
    
    // Копируем min(len(src), len(dst)) элементов
    copied := copy(dst, src)
    fmt.Printf("Источник: %v\n", src)
    fmt.Printf("Приёмник: %v\n", dst)
    fmt.Printf("Скопировано элементов: %d\n", copied) // 3
    
    // Копирование в слайс большей длины
    dst2 := []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
    copied2 := copy(dst2, src)
    fmt.Printf("Приёмник2: %v\n", dst2)
    fmt.Printf("Скопировано элементов: %d\n", copied2) // 5
}
Создание среза (slicing)
Операция среза (slicing) позволяет создавать новые слайсы из существующих слайсов или массивов. Синтаксис [low:high] берёт элементы начиная с индекса low и заканчивая индексом high-1. Можно опускать low (по умолчанию 0) или high (по умолчанию длина слайса).
package main

import "fmt"

func main() {
    numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    // Срез [low:high]
    slice1 := numbers[2:7] // элементы с 2 по 6
    fmt.Println("numbers[2:7]:", slice1) // [2 3 4 5 6]
    
    // От начала до high
    slice2 := numbers[:5] // элементы с 0 по 4
    fmt.Println("numbers[:5]:", slice2) // [0 1 2 3 4]
    
    // От low до конца
    slice3 := numbers[3:] // элементы с 3 до конца
    fmt.Println("numbers[3:]:", slice3) // [3 4 5 6 7 8 9]
    
    // Весь слайс
    slice4 := numbers[:] // все элементы
    fmt.Println("numbers[:]:", slice4) // [0 1 2 3 4 5 6 7 8 9]
    
    // Срезы разделяют базовый массив
    slice5 := numbers[2:5]
    slice5[0] = 999 // изменяем элемент в срезе
    fmt.Println("Изменённый срез:", slice5) // [999 3 4]
    fmt.Println("Оригинальный слайс:", numbers) // [0 1 999 3 4 5 6 7 8 9]
}
Срезы разделяют базовый массив. Изменение элемента в одном срезе может повлиять на другие срезы, созданные из того же массива.
Продвинутые операции со слайсами
Слайсы слайсов (двумерные структуры)
В Go можно создавать многомерные структуры данных, используя слайсы слайсов. Это позволяет работать с матрицами, таблицами и другими двумерными данными. В отличие от массивов, слайсы позволяют создавать «зубчатые» структуры, где каждая строка может иметь разную длину.
package main

import "fmt"

func main() {
    // Прямоугольная матрица (все строки одинаковой длины)
    matrix := [][]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }
    
    fmt.Println("Прямоугольная матрица:")
    for _, row := range matrix {
        fmt.Println(row)
    }
    
    // Зубчатый массив (строки разной длины)
    jagged := [][]int{
        {1, 2},
        {3, 4, 5},
        {6, 7, 8, 9},
        {10},
    }
    
    fmt.Println("\nЗубчатый массив (jagged array):")
    for i, row := range jagged {
        fmt.Printf("Строка %d (длина %d): %v\n", i, len(row), row)
    }
    
    // Динамическое добавление строк
    matrix = append(matrix, []int{10, 11, 12})
    fmt.Println("\nПосле добавления строки:")
    for _, row := range matrix {
        fmt.Println(row)
    }
}
Пакет slices
Начиная с Go 1.21, в стандартную библиотеку добавлен пакет slices, который предоставляет множество полезных функций для работы со слайсами.
package main

import (
    "cmp"
    "fmt"
    "slices"
)

func main() {
    numbers := []int{3, 1, 4, 1, 5, 9, 2, 6}
    names := []string{"Charlie", "Alice", "Bob", "Alice"}
    
    // Сортировка (изменяет слайс на месте)
    slices.Sort(numbers)
    fmt.Println("Отсортированные числа:", numbers) // [1 1 2 3 4 5 6 9]
    
    // Сортировка строк
    slices.Sort(names)
    fmt.Println("Отсортированные имена:", names) // [Alice Alice Bob Charlie]
    
    // Поиск элемента
    index := slices.Index(numbers, 5)
    fmt.Printf("Индекс элемента 5: %d\n", index) // 4
    
    // Проверка наличия элемента
    contains := slices.Contains(numbers, 7)
    fmt.Printf("Содержит ли 7: %t\n", contains) // false
    
    // Сравнение слайсов
    equal := slices.Equal([]int{1, 2, 3}, []int{1, 2, 3})
    fmt.Printf("Слайсы равны: %t\n", equal) // true
    
    // Минимальный и максимальный элементы
    min := slices.Min(numbers)
    max := slices.Max(numbers)
    fmt.Printf("Минимум: %d, Максимум: %d\n", min, max) // 1, 9
    
    // Разворот
    slices.Reverse(names)
    fmt.Println("Развёрнутые имена:", names) // [Charlie Bob Alice Alice]
    
    // Удаление последовательных дубликатов
    slices.Sort(names) // сначала сортируем
    names = slices.Compact(names)
    fmt.Println("Уникальные имена:", names) // [Alice Bob Charlie]
    
    // Двоичный поиск (для отсортированных слайсов)
    sortedNumbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    pos, found := slices.BinarySearch(sortedNumbers, 5)
    fmt.Printf("Позиция 5: %d, найден: %t\n", pos, found) // 4, true
    
    // Кастомная сортировка
    type Person struct {
        name string
        age  int
    }
    
    people := []Person{
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35},
    }
    
    // Сортировка по возрасту
    slices.SortFunc(people, func(a, b Person) int {
        return cmp.Compare(a.age, b.age)
    })
    
    fmt.Println("Отсортированные по возрасту:", people)
}
Когда использовать слайсы?
Используйте слайсы, когда:
  • Размер коллекции может изменяться
  • Нужно динамически добавлять или удалять элементы
  • Работаете с функциями, которые принимают коллекции переменной длины
  • Требуется большая гибкость в работе с данными
  • Хотите избежать копирования больших объёмов данных
Используйте массивы, когда:
  • Размер коллекции известен заранее и не изменится
  • Нужна максимальная производительность доступа к элементам
  • Работаете с низкоуровневыми операциями (например, сетевые пакеты)
  • Требуется детерминированное использование памяти
Заключение
Теперь вы знаете основы работы со слайсами в Go. Давайте подведём итоги:
Ключевые моменты:
  • Слайсы — это динамические представления на базовые массивы
  • Слайсы могут изменять свою длину с помощью append
  • Слайсы передаются в функции по значению (копируется заголовок слайса, но не данные)
  • Операция срезания (slicing) создаёт новый слайс, но разделяет базовый массив
  • Будьте осторожны с разделением базовых массивов между слайсами
  • Пакет slices предоставляет множество полезных функций (Go 1.21+)
Что дальше?
Теперь вы готовы погрузиться глубже в работу слайсов или перейти к изучению других структур данных в Go:
  • Слайсы
    Внутренее устройство
  • Мапы
    Объявление, добавление и удаление элементов, итерация
  • Структуры
    Определение, инициализация, доступ к полям
Поздравляю, вы только что освоили основы работы со слайсами в Go!
Теперь вы можете эффективно использовать их в своих программах для работы с динамическими коллекциями данных.