在 Go 中连接两个切片

Concatenate two slices in Go

提问人:Kevin Burke 提问时间:4/27/2013 最后编辑:iczaKevin Burke 更新时间:11/23/2022 访问量:596408

问:

我正在尝试将切片和切片结合起来.我怎样才能在 Go 中做到这一点?[1, 2][3, 4]

我试过了:

append([]int{1,2}, []int{3,4})

但得到了:

cannot use []int literal (type []int) as type int in append

但是,文档似乎表明这是可能的,我错过了什么?

slice = append(slice, anotherSlice...)
go append slice variadic-functions

评论


答:

1474赞 6 revs, 5 users 76%user1106925 #1

在第二个切片后添加点:

//                           vvv
append([]int{1,2}, []int{3,4}...)

这就像任何其他可变调函数一样。

func foo(is ...int) {
    for i := 0; i < len(is); i++ {
        fmt.Println(is[i])
    }
}

func main() {
    foo([]int{9,8,7,6,5}...)
}

评论

69赞 4/27/2013
append()一个可变参数函数,并且允许您将多个参数从切片传递给可变参数函数。...
26赞 Toad 9/24/2014
当切片很大时,这有什么表现吗?还是编译器没有真正将所有元素作为参数传递?
28赞 9/24/2014
@Toad:它实际上并没有把它们分散开来。在上面的示例中,该参数保存原始切片的副本,也就是说,它具有对同一基础数组 len 和 cap 的轻量级引用的副本。如果函数更改了某个成员,则该更改将在原始成员上看到。这是一个演示。因此,唯一真正的开销是,如果您还没有一个新切片,它会创建一个新切片,例如:这将创建一个可以容纳的新切片。foo()isfoofoo(1, 2, 3, 4, 5)is
3赞 Toad 9/25/2014
啊。如果我理解正确的话,可变参数函数实际上是像参数数组一样实现的(而不是堆栈上的每个参数)?既然你传入切片,它实际上是一对一映射的?
1赞 9/25/2014
@Toad:是的,当您在现有切片上使用时,它只会传递该切片。当您传递单个参数时,它会将它们收集到一个新的切片中并传递它。我没有确切的机制的第一手知识,但我猜这个:和这个:只是对这个:和这个:脱糖。...foo(1, 2, 3, 4, 5)func foo(is ...int) {foo([]int{1, 2, 3, 4, 5})func foo(is []int) {
108赞 peterSO 4/27/2013 #2

追加和复制切片

可变参数函数将零个或多个值追加到 的类型 中,该类型必须是切片类型,并返回结果 切片,也是 .这些值将传递给 type 其中 是 的元素类型,并且各自 参数传递规则适用。作为特例,append 也接受 第一个参数可分配给类型,第二个参数为 type,后跟 。此表单附加 字符串。appendxsSSx...TTS[]bytestring...

append(s S, x ...T) S  // T is the element type of S

s0 := []int{0, 0}
s1 := append(s0, 2)        // append a single element     s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)  // append multiple elements    s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)    // append a slice              s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}

将参数传递给 ...参数

如果 是具有最终参数类型的可变参数,则在 函数的参数等价于类型的参数。在 的每次调用 ,传递给 final 参数的参数都是一个新的 类型的切片,其连续元素是实际参数, 其中所有都必须可分配给类型 。切片的长度为 因此,绑定到最终参数的参数数和 每个呼叫站点都不同。f...T[]Tf[]TT

您的问题的答案是 Go 编程语言规范中的示例。例如s3 := append(s2, s0...)

s := append([]int{1, 2}, []int{3, 4}...)

评论

14赞 Hugo 6/3/2015
注意:append(slice1, slice2...) 的一般使用对我来说似乎很危险。如果 slice1 是较大数组的切片,则该数组的值将被 slice2 覆盖。(这让我感到畏缩,这似乎不是一个普遍关注的问题?
9赞 icza 2/17/2016
@Hugo 如果您“交出”数组的某个切片,那么要知道切片“所有者”将能够看到/覆盖数组中超出切片当前长度的部分。如果您不希望这样做,则可以使用完整的切片表达式(以 ) 的形式,该表达式还指定了最大容量。例如,切片的容量为 ,并且不能将其重新切片以包含超出该范围的元素,即使后备数组在此之后有一千个元素也是如此。a[low : high : max]a[0:2:4]4
57赞 fiatjaf 4/17/2015 #3

不反对其他答案,但我发现文档中的简要解释比其中的示例更容易理解:

函数追加

func append(slice []Type, elems ...Type) []Type附加内置 函数将元素追加到切片的末尾。如果有足够的 容量时,目标将被重新切片以容纳新元素。 如果没有,则将分配一个新的基础数组。附加 返回更新的切片。因此,有必要将 append 的结果,通常在保存切片本身的变量中:

slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)

作为特例,将字符串附加到字节切片是合法的, 喜欢这个:

slice = append([]byte("hello "), "world"...)

评论

2赞 Korjavin Ivan 5/16/2017
谢谢!对我来说很有价值!
0赞 MrR 9/16/2020
希望这是最好的答案!
39赞 icza 10/14/2016 #4

我认为重要的是要指出并知道,如果目标切片(您附加到的切片)具有足够的容量,则追加将“就地”发生,通过重新切片目标(切片以增加其长度以便能够容纳可附加元素)。

这意味着,如果目标是通过切片更大的数组或切片创建的,而该数组或切片包含超出结果切片长度的其他元素,则它们可能会被覆盖。

为了演示,请参阅以下示例:

a := [10]int{1, 2}
fmt.Printf("a: %v\n", a)

x, y := a[:2], []int{3, 4}
fmt.Printf("x: %v, y: %v\n", x, y)
fmt.Printf("cap(x): %v\n", cap(x))

x = append(x, y...)
fmt.Printf("x: %v\n", x)

fmt.Printf("a: %v\n", a)

输出(在 Go Playground 上尝试):

a: [1 2 0 0 0 0 0 0 0 0]
x: [1 2], y: [3 4]
cap(x): 10
x: [1 2 3 4]
a: [1 2 3 4 0 0 0 0 0 0]

我们创建了一个长度为 的“后备”数组。然后我们通过切片这个数组来创建目标切片,切片是使用复合文字创建的。现在当我们附加到 时,结果是预期的,但可能令人惊讶的是,后备数组也发生了变化,因为 的容量足以附加到它,所以被切片,它也将使用相同的后备数组,并将 的元素复制到那里。a10xay[]int{3, 4}yx[1 2 3 4]ax10yxaappend()y

如果要避免这种情况,可以使用具有

a[low : high : max]

它构造一个切片,并通过将结果切片设置为 来控制其容量。max - low

请参阅修改后的示例(唯一的区别是我们是这样创建的:xx = a[:2:2]

a := [10]int{1, 2}
fmt.Printf("a: %v\n", a)

x, y := a[:2:2], []int{3, 4}
fmt.Printf("x: %v, y: %v\n", x, y)
fmt.Printf("cap(x): %v\n", cap(x))

x = append(x, y...)
fmt.Printf("x: %v\n", x)

fmt.Printf("a: %v\n", a)

输出(在 Go Playground 上尝试)

a: [1 2 0 0 0 0 0 0 0 0]
x: [1 2], y: [3 4]
cap(x): 2
x: [1 2 3 4]
a: [1 2 0 0 0 0 0 0 0 0]

正如你所看到的,我们得到了相同的结果,但后备数组没有改变,因为容量是“仅”的(多亏了完整的切片表达式)。因此,为了进行追加,分配了一个新的后备数组,该数组可以存储 和 的元素,这与 不同。xax2a[:2:2]xya

评论

3赞 Aidy 6/29/2018
这对我面临的问题非常有帮助。谢谢。
0赞 patrick 8/3/2020
谢谢,非常有用 - 但是,仅当支持数组足够短以容纳新值时,才会发生所示的行为吗?例如,如果在您的示例中长度为 20,则会保持不变吗?ya
0赞 icza 8/3/2020
@patrick 是的,如果没有足够的空间来追加,则分配一个新的后备数组,复制旧内容,并在新的后备数组上执行追加,并使旧数组保持不变。尝试有多难?去游乐场append()
3赞 BaSO4 2/7/2018 #5

append([]int{1,2}, []int{3,4}...)会起作用。将参数传递给参数。...

如果 是 variadic 的,其最终参数为 type ,则在 的 type 中等价于 type 。fp...Tfp[]T

如果调用时没有实际参数,则传递给的值为 。fppnil

否则,传递的值是具有新基础数组的新类型切片,其连续元素是实际参数,所有参数都必须分配给 。因此,切片的长度和容量是绑定到每个调用站点的参数数,并且每个调用站点的参数数可能不同。[]TTp

给定函数和调用

func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", "Anna", "Eileen")
9赞 ASHWIN RAJEEV 10/4/2018 #6

append( ) 函数和扩展运算符

可以使用标准 golang 库中的 append 方法连接两个切片。这类似于函数操作。所以我们需要使用variadic...

package main

import (
    "fmt"
)

func main() {
    x := []int{1, 2, 3}
    y := []int{4, 5, 6}
    z := append([]int{}, append(x, y...)...)
    fmt.Println(z)
}

上述代码的输出为:[1 2 3 4 5 6]

评论

5赞 10/10/2020
我不确定你为什么不使用.z := append(x, y...)
66赞 D.C. Joo 11/6/2019 #7

我想强调@icza答案并稍微简化一下,因为它是一个至关重要的概念。我假设读者熟悉切片

c := append(a, b...)

这是对这个问题的有效回答。但是,如果您需要稍后在不同的上下文中使用切片“a”和“c”,则这不是连接切片的安全方法。

为了解释,让我们不要从切片的角度来阅读表达式,而是从基础数组的角度来理解:

“获取(基础)'a'数组,并将数组'b'中的元素附加到 它。如果数组 'a' 有足够的容量来包含 'b' 中的所有元素 - “C”的底层数组不会是一个新数组,它实际上是数组“A”。基本上,切片 'a' 将显示 len(a) 元素 底层数组 'a' 和切片 'c' 将显示数组 'a' 的 len(c)。

append() 不一定会创建一个新数组!这可能会导致意想不到的结果。请参阅 Go Playground 示例

如果要确保为切片分配了新数组,请始终使用 make() 函数。例如,这里有一些丑陋但足够有效的选项来完成任务。

la := len(a)
c := make([]int, la, la + len(b))
_ = copy(c, a)
c = append(c, b...)

la := len(a)
c := make([]int, la + len(b))
_ = copy(c, a)
_ = copy(c[la:], b)

评论

1赞 olippuner 12/10/2019
感谢您指出这些副作用。与这个修改后的 szenario 形成鲜明对比。play.golang.org/p/9FKo5idLBj4尽管在提供过剩容量时,人们应该仔细考虑这些令人费解的副作用,而不是合理的直觉。
0赞 Victor 8/2/2020
谢谢 Joo,我花了将近两个小时来寻找代码中的问题,因为我没有遵循您所说的不安全的 guileline 来连接您稍后将使用的两个切片(也许可以在此文档中包含该警告:blog.golang.org/slices)。感谢您的复制片段,它看起来很有品味!
1赞 Chris 12/4/2020
这应该是公认的答案。请记住,始终将 append 的输出保存到与第一个参数相同的变量中,如下所示:a := append(a, b...)
7赞 Gandharva S Murthy 11/9/2021 #8

要连接两个切片,

func main() {
    s1 := []int{1, 2, 3}
    s2 := []int{99, 100}
    s1 = append(s1, s2...)

    fmt.Println(s1) // [1 2 3 99 100]
}

将单个值追加到切片

func main() {
    s1 :=  []int{1,2,3}
    s1 := append(s1, 4)
    
    fmt.Println(s1) // [1 2 3 4]
}

将多个值追加到切片

func main() {
    s1 :=  []int{1,2,3}
    s1 = append(s1, 4, 5)
    
    fmt.Println(s1) // [1 2 3 4]
}
5赞 PeterM 4/13/2022 #9

似乎是泛型的完美用途(如果使用 1.18 或更高版本)。

func concat[T any](first []T, second []T) []T {
    n := len(first);
    return append(first[:n:n], second...);
}

评论

2赞 blackgreen 7/2/2022
append 已经是“泛型”的,所以人们可能会认为这不是类型参数的必要用例,不明显地使用三索引切片表达式来削减第一个切片的容量是一个明确的改进:n:n