【问题标题】:Concatenate two slices in Go在 Go 中连接两个切片
【发布时间】:2013-04-21 07:47:17
【问题描述】:

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

我试过了:

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

但是得到了:

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

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

slice = append(slice, anotherSlice...)

【问题讨论】:

    标签: go append slice variadic-functions


    【解决方案1】:
    推荐的答案 Go Language

    在第二个切片后添加点:

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

    【讨论】:

    • append() 一个可变参数函数,... 允许您将多个参数从切片传递给可变参数函数。
    • 当切片很大时,这是否完全有效?还是编译器并没有真正将所有元素作为参数传递?
    • @Toad:实际上并没有将它们分散开来。在上面的foo() 示例中,is 参数保存了原始切片的副本,也就是说,它具有对同一底层数组 len 和 cap 的轻量级引用的副本。如果foo 函数更改了成员,则更改将在原件上看到。 Here's a demo。因此,唯一真正的开销是如果您还没有一个新切片,它会创建一个新切片,例如:foo(1, 2, 3, 4, 5) 这将创建一个 is 将持有的新切片。
    • 啊。如果我理解正确,可变参数函数实际上是像参数数组一样实现的(而不是堆栈上的每个参数)?既然你传入了切片,它实际上是一对一映射的?
    • @Toad:是的,当您在现有切片上使用... 时,它只会传递该切片。当您传递单个参数时,它将它们收集到一个新切片中并传递它。我没有关于确切机制的第一手知识,但我猜这个:foo(1, 2, 3, 4, 5) 和这个:func foo(is ...int) { 只是去糖这个:foo([]int{1, 2, 3, 4, 5}) 和这个:func foo(is []int) {。跨度>
    【解决方案2】:

    Appending to and copying slices

    可变参数函数append 将零个或多个值x 附加到s S 类型的,必须是切片类型,并返回结果 切片,也是S 类型。值x 被传递给参数 键入...T 其中TS 的元素类型和相应的 参数传递规则适用。作为一种特殊情况, append 也接受 第一个参数可分配给类型[]byte,第二个参数为 string 类型后跟...。这种形式附加的字节 字符串。

    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}
    

    Passing arguments to ... parameters

    如果f 是具有最终参数类型...T 的可变参数,那么在 function 该参数等效于[]T 类型的参数。在 每次调用f,传递给最终参数的参数都是一个新的 []T 类型的切片,其连续元素是实际参数, 所有这些都必须可分配给类型T。切片的长度为 因此绑定到最终参数的参数数量可能 每个呼叫站点都不同。

    您的问题的答案是Go Programming Language Specification 中的示例s3 := append(s2, s0...)。例如,

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

    【讨论】:

    • 注意:一般使用 append(slice1, slice2...) 对我来说似乎很危险。如果 slice1 是更大数组的切片,则该数组的值将被 slice2 覆盖。 (这让我感到畏缩,这似乎不是一个普遍的问题?)
    • @Hugo 如果您“交出”数组的一个切片,则知道切片“所有者”将能够查看/覆盖超出切片当前长度的数组部分.如果您不想这样做,您可以使用full slice expression(以a[low : high : max] 的形式),它还指定最大容量。例如,切片a[0:2:4] 的容量为4,并且不能将其重新切片以包含超出此范围的元素,即使后备数组有一千个元素也是如此。
    【解决方案3】:

    与其他答案没有什么不同,但我发现the docs 中的简短解释比其中的示例更容易理解:

    函数追加

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

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

    作为一种特殊情况,将字符串附加到字节切片是合法的, 像这样:

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

    【讨论】:

    • 谢谢!对我很有价值!
    • 希望这是最佳答案!
    【解决方案4】:

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

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

    为了演示,请看这个例子:

    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]
    

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

    如果您想避免这种情况,您可以使用full slice expression,其格式为

    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 的容量“仅”为 2(感谢完整的切片表达式 a[:2:2] )。因此,为了进行追加,分配了一个新的后备数组,可以存储xy 的元素,这与a 不同。

    【讨论】:

    • 对我面临的问题很有帮助。谢谢。
    • 谢谢,非常有用 - 但是,如果后备数组足够短以适应新值,上述行为是否会发生?例如,如果在您的示例中 y 的长度为 20,那么 a 会保持不变吗?
    • @patrick 是的,如果没有足够的空间来追加,append() 分配一个新的后备数组,复制旧的内容,并在新的后备数组上执行追加并保留旧的原样.尝试有多难? Go Playground
    【解决方案5】:

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

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

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

    为了解释,让我们不是根据切片,而是根据底层数组来阅读表达式:

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

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

    如果要确保为切片分配新数组,请始终使用 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)
    

    【讨论】:

    • 感谢您指出这些副作用。与这个修改后的场景形成了惊人的对比。 play.golang.org/p/9FKo5idLBj4 虽然在提供过剩产能时,应该仔细考虑这些令人费解的副作用,而不是合理的直觉。
    • 谢谢 Joo,我花了将近两个小时来寻找代码中的一个问题,因为我没有遵循您所说的不安全的规则来连接您稍后将使用的两个切片(也许可以在此文档中包含该警告:blog.golang.org/slices)。并感谢复制 sn-p 它看起来很有品味!
    • 这应该是公认的答案。请记住,始终将 append 的输出保存到与第一个参数相同的变量中,如下所示:a := append(a, b...)
    【解决方案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]

    【讨论】:

    • 我不知道你为什么不使用z := append(x, y...)
    【解决方案7】:

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

    如果f 是带有...T 类型的最终参数p 的可变参数,则在fp 的类型等效于[]T 类型。

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

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

    给定函数和调用

    func Greeting(prefix string, who ...string)
    Greeting("nobody")
    Greeting("hello:", "Joe", "Anna", "Eileen")
    

    【讨论】:

      【解决方案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]
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-02-28
        • 2017-03-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-17
        相关资源
        最近更新 更多