Avatar ·

Как объединить два среза (slice) в Go: конкатенация срезов

Всем привет! Я только начал изучать Go и столкнулся с проблемой. У меня есть два среза целых чисел, и мне нужно их объединить в один. В других языках я привык к методам типа `.concat()` или `+` для массивов, но в Go это не работает. Вот мой код: ```go package main import "fmt" func main() { slice1 := []int{1, 2, 3} slice2 := []int{4, 5, 6} // Пытаюсь объединить как в Python result := slice1 + slice2 // Ошибка: invalid operation fmt.Println(result) } ``` Компилятор выдаёт ошибку: `invalid operation: slice1 + slice2 (operator + not defined on slice)`. Я понимаю, что в Go срезы — это не массивы в классическом понимании, но как тогда правильно их объединить? Нужно ли использовать цикл или есть встроенный способ? Также интересует, как это сделать эффективно, если срезы большие (например, по 10 000 элементов). Заранее спасибо за помощь! <<>>

Использование встроенной функции append с variadic-синтаксисом

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

Базовое решение

package main

import "fmt"

func main() {
    slice1 := []int{1, 2, 3}
    slice2 := []int{4, 5, 6}
    
    // Объединяем slice1 и slice2
    result := append(slice1, slice2...)
    
    fmt.Println(result) // [1 2 3 4 5 6]
}

Как это работает

Функция append принимает срез и произвольное количество элементов. Синтаксис slice2... "разворачивает" второй срез в отдельные элементы. По сути, это эквивалентно вызову append(slice1, 4, 5, 6).

Важные особенности

  • Изменение исходного среза: append может модифицировать slice1, если у него достаточно ёмкости (capacity). Если нет — создаётся новый срез.
  • Неизменяемость второго среза: slice2 остаётся нетронутым.
  • Работа с любыми типами: Метод работает для срезов любого типа — []string, []float64, срезов структур и т.д.

Пример с большими срезами

package main

import "fmt"

func main() {
    // Создаём срез с достаточной ёмкостью для эффективности
    slice1 := make([]int, 0, 10000)
    for i := 0; i < 5000; i++ {
        slice1 = append(slice1, i)
    }
    
    slice2 := make([]int, 5000)
    for i := 0; i < 5000; i++ {
        slice2[i] = i + 5000
    }
    
    // Эффективная конкатенация без лишних аллокаций
    result := append(slice1, slice2...)
    fmt.Println(len(result)) // 10000
}

Что делать, если нужно сохранить исходные срезы?

package main

import "fmt"

func main() {
    slice1 := []int{1, 2, 3}
    slice2 := []int{4, 5, 6}
    
    // Создаём новый срез, копируем оба
    result := make([]int, len(slice1)+len(slice2))
    copy(result, slice1)
    copy(result[len(slice1):], slice2)
    
    fmt.Println(result) // [1 2 3 4 5 6]
    fmt.Println(slice1) // [1 2 3] — не изменился
}

Производительность

Для больших срезов (от 1000+ элементов) рекомендуется использовать make + copy, так как это позволяет избежать лишних перераспределений памяти. append тоже может быть эффективным, если заранее выделить ёмкость через make.

В большинстве реальных случаев append(slice1, slice2...) — это лучший выбор: коротко, понятно, стандартно.

<<>>

Альтернативный подход: дженерики (Go 1.18+)

Если вы используете Go версии 1.18 или новее, можно написать универсальную функцию для конкатенации срезов любого типа:

package main

import "fmt"

// Concat объединяет два среза любого типа
func Concat[T any](a, b []T) []T {
    result := make([]T, len(a)+len(b))
    copy(result, a)
    copy(result[len(a):], b)
    return result
}

func main() {
    ints1 := []int{1, 2, 3}
    ints2 := []int{4, 5, 6}
    fmt.Println(Concat(ints1, ints2)) // [1 2 3 4 5 6]

    strs1 := []string{"hello", "world"}
    strs2 := []string{"from", "Go"}
    fmt.Println(Concat(strs1, strs2)) // [hello world from Go]
}

Плюсы и минусы

  • + Универсальность: одна функция для всех типов.
  • + Гарантированно не изменяет исходные срезы.
  • - Избыточно для простых случаев — проще использовать встроенный append.
  • - Может быть чуть медленнее из-за вызова функции (но в большинстве случаев разница незаметна).

Этот подход полезен, если вы часто объединяете срезы разных типов и хотите избежать дублирования кода. В остальных случаях придерживайтесь стандартного append.