Как объединить два среза (slice) в Go? Конкатенация срезов
Использование append с распаковкой (variadic)
В Go нет оператора для конкатенации срезов, но есть элегантный способ через встроенную функцию append с использованием оператора распаковки .... Это самый идиоматичный и производительный метод.
package main
import "fmt"
func main() {
slice1 := []int{1, 2, 3}
slice2 := []int{4, 5, 6}
// Ключевой момент: slice2... распаковывает slice2 в отдельные аргументы
result := append(slice1, slice2...)
fmt.Println(result) // [1 2 3 4 5 6]
}
Как это работает
Функция append принимает срез и произвольное количество элементов. Синтаксис slice2... (variadic argument) распаковывает второй срез в отдельные элементы, как если бы мы написали append(slice1, 4, 5, 6).
Важные нюансы про память и исходные срезы
- Новый срез или модификация? Если в
slice1достаточно ёмкости (cap),appendможет перезаписать данные за его пределами и вернуть тот же базовый массив. Если ёмкости не хватает — выделяется новый массив, и данные копируются. - Когда это опасно? Пример:
a := []int{1, 2}
b := []int{3, 4, 5}
// cap(a) = 2, append не хватит места
c := append(a, b...)
a[0] = 999
fmt.Println(c) // [1 3 4 5] — a и c не связаны, всё ок
// А теперь опасный случай:
d := make([]int, 2, 5)
d[0] = 1
d[1] = 2
e := []int{3, 4}
f := append(d, e...)
d[0] = 999
fmt.Println(f) // [1 3 4] — f всё ещё ссылается на старый массив? НЕТ!
// На самом деле append создаст новый массив, потому что d и e — разные срезы
Главное правило: результат append всегда нужно присваивать переменной. Исходный срез slice1 не изменяется (но его базовый массив может быть изменён, если ёмкости хватало).
Безопасная конкатенация с копированием
Если вы хотите гарантированно получить независимую копию и не трогать исходные срезы, используйте make + copy:
func concatSlices[T any](slices ...[]T) []T {
totalLen := 0
for _, s := range slices {
totalLen += len(s)
}
result := make([]T, totalLen)
i := 0
for _, s := range slices {
i += copy(result[i:], s)
}
return result
}
// Использование:
r := concatSlices(slice1, slice2)
// r — полностью независимая копия
Производительность
append с распаковкой компилируется в эффективный код. Go runtime оптимизирует копирование через memmove. Для большинства случаев это лучший выбор. Если вы конкатенируете много срезов (десятки и больше), то версия с copy может быть чуть быстрее, но разница обычно незначительна.
Итог: используйте append(slice1, slice2...) — это стандартный идиоматичный способ. Помните про ёмкость, если важна независимость данных.
Альтернативный подход: функция copy для явного контроля
Если вам нужно объединить срезы и при этом точно знать, что исходные данные не будут затронуты, используйте комбинацию make и copy. Это даёт полный контроль над памятью.
func mergeSlices(s1, s2 []int) []int {
// Выделяем новый массив с суммарной длиной
result := make([]int, len(s1)+len(s2))
// Копируем первый срез
copy(result, s1)
// Копируем второй срез начиная с позиции len(s1)
copy(result[len(s1):], s2)
return result
}
// Пример:
a := []int{1, 2}
b := []int{3, 4}
c := mergeSlices(a, b)
a[0] = 100 // не влияет на c
fmt.Println(c) // [1 2 3 4]
Когда это полезно
- Когда вы работаете с большими срезами и хотите избежать неожиданного переиспользования памяти.
- Когда нужно объединить более двух срезов — можно легко расширить функцию.
- Когда важна читаемость кода для новичков: явное копирование понятнее, чем магия
....
Недостаток: немного больше кода и одно дополнительное выделение памяти (но в Go это нормально). Для простых случаев append с распаковкой остаётся предпочтительным вариантом.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги