在Go中串联两个切片


477

我正在尝试将切片[1, 2]和切片结合起来[3, 4]。如何在Go中执行此操作?

我试过了:

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

但是得到了:

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

但是,文档似乎表明这是可能的,我想念的是什么?

slice = append(slice, anotherSlice...)

Answers:


876

在第二个切片之后添加点:

//---------------------------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}...)
}

37
append()一个可变参数函数,并且...允许您从切片将多个参数传递给可变参数函数。

11
当切片很大时,这完全是表现吗?还是编译器没有真正将所有元素作为参数传递?
蟾蜍

15
@Toad:它实际上并没有传播出去。在foo()上面的示例中,is参数保存原始切片的副本,也就是说,该参数具有对同一基础数组len和cap的轻量引用的副本。如果foo函数更改了成员,则更改将在原始成员上看到。这是一个演示。因此,唯一真正的开销是,如果您还没有一个新的切片,它将创建一个新的切片,例如:foo(1, 2, 3, 4, 5)这将创建一个新的切片来is保存。

2
啊。如果我理解正确,可变参数函数实际上就像参数数组(而不是堆栈中的每个参数)一样实现吗?既然您传递了切片,它实际上就一一对应了吗?
蟾蜍

@Toad:是的,当您...在现有切片上使用时,它只是传递该切片。当您传递各个参数时,它将它们收集到一个新的切片中并传递它。我没有确切的力学的第一手资料,但我猜,这:foo(1, 2, 3, 4, 5)和这样的:func foo(is ...int) {刚去糖这样:foo([]int{1, 2, 3, 4, 5})和这样的:func foo(is []int) {

77

附加并复制切片

可变参数函数append将零个或多个值附加xs type S(必须是切片类型),并返回结果切片(也是type)S。这些值x将传递到类型为的参数,...T其中T是的元素类型,S并且适用各自的参数传递规则。作为特殊情况,append还接受可分配给type []byte的第一个参数,string其后是type 的第二个参数 ...。这种形式附加了字符串的字节。

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}

将参数传递给...参数

如果f是带有最终参数类型的可变参数...T,则函数中的参数等于类型参数[]T。在每次调用时f,传递给final参数的参数是一个新的type切片,[]T其连续元素是实际参数,所有这些参数都必须可分配给type T。因此,切片的长度是绑定到最终参数的参数个数,并且对于每个调用站点可能有所不同。

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

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

6
注意:对我来说,一般使用append(slice1,slice2 ...)似乎很危险。如果slice1是较大数组的切片,则该数组的值将被slice2覆盖。(这让我感到胆怯,这似乎不是一个普遍的问题?)
雨果

7
@Hugo如果您“移交”了阵列的一个切片,那么知道切片“所有者”将能够查看/覆盖超出切片当前长度的阵列部分。如果您不希望这样做,可以使用一个完整的切片表达式(以形式a[low : high : max]),该表达式还指定最大容量。例如,切片a[0:2:4]将具有的容量,4并且不能被切片以包括超出该范围的元素,即使此后的数组中有一千个元素也是如此。
icza

30

没有其他答案,但是我发现文档中的简短解释比其中的示例更容易理解:

功能附加

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

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

作为一种特殊情况,将字符串附加到字节片是合法的,如下所示:

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

1
谢谢!对我来说很有价值!
科尔雅文·伊凡

23

我认为必须指出并知道,如果目标切片(您追加到的切片)具有足够的容量,则通过将目标切片(为了增加长度而必须增加其长度),追加将“就地” 进行能够容纳附加元素)。

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

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

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]

我们创建了一个a带有length 的“支持”数组10。然后,我们x通过切片该a数组来创建目标切片,y使用复合文字创建切片[]int{3, 4}。现在,当我们追加yx,结果是预期的[1 2 3 4],但什么可奇怪的是,支持数组a也发生了变化,由于产能x就是10这足以追加y到它,所以x是resliced这也将使用相同的a支持数组,并append()将复制其中的元素y

如果要避免这种情况,可以使用具有以下形式的完整切片表达式

a[low : high : max]

构造切片,并通过将切片设置为来控制所得切片的容量max - low

请参见修改后的示例(唯一的区别是我们创建时x像这样x = 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]

如您所见,我们得到了相同的x结果,但是后备数组a没有改变,因为的容量x是“ only” 2(由于完整slice表达式a[:2:2])。因此,要执行追加操作,将分配一个新的支持数组,该数组可以存储xand 的元素,这与y有所不同a


2
这对我面临的问题非常有帮助。谢谢。
艾迪(Aidy)

9

我想强调@icza的答案,并对其进行一些简化,因为它是一个至关重要的概念。我认为读者熟悉切片

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

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

为说明起见,让我们不要根据切片而是根据基础数组来读取表达式:

“采用(底层)'a'数组,并将数组'b'中的元素追加到其上。如果数组'a'具有足够的容量以包含'b'中的所有元素-底层'c'数组将不是新数组,实际上它将是数组'a'。基本上,切片'a'将显示基础数组'a'的len(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)

感谢您指出这些副作用。与此改进的szenario形成惊人的对比。play.golang.org/p/9FKo5idLBj4尽管提供过多的容量,但您应该仔细考虑这些令人困惑的副作用,以反对直觉。
olippuner

5

append()函数和散布运算符

可以使用append标准golang库中的方法将两个切片连接起来。这类似于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]


2

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

如果f是可变参数,其最终参数p为type ...T,则ftype内p等于type []T

如果f在没有实际参数的情况下调用p,则传递给的p值为nil

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

给定功能和调用

func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", "Anna", "Eileen")
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.